From d98f61f123567cbf8ead4105f20576af0194cc8d Mon Sep 17 00:00:00 2001 From: Davin Date: Mon, 30 Jan 2023 02:02:10 +1100 Subject: [PATCH 1/9] Added Wii support, opens SWD and SMD in separate dialogs --- .../{ => Codec}/ADPCMDecoder.cs | 2 +- VG Music Studio - Core/Codec/CodecEnums.cs | 180 + VG Music Studio - Core/Codec/DSPADPCM.cs | 1443 ++ VG Music Studio - Core/Codec/LayoutEnums.cs | 59 + VG Music Studio - Core/Codec/MetaEnums.cs | 479 + VG Music Studio - Core/NDS/DSE/DSEChannel.cs | 351 +- VG Music Studio - Core/NDS/DSE/DSECommands.cs | 8 + VG Music Studio - Core/NDS/DSE/DSEConfig.cs | 43 +- VG Music Studio - Core/NDS/DSE/DSEEngine.cs | 8 +- VG Music Studio - Core/NDS/DSE/DSEEnums.cs | 7 +- .../NDS/DSE/DSEExceptions.cs | 4 +- .../NDS/DSE/DSELoadedSong.cs | 43 +- .../NDS/DSE/DSELoadedSong_Events.cs | 1068 +- .../NDS/DSE/DSELoadedSong_Runtime.cs | 4 +- VG Music Studio - Core/NDS/DSE/DSEPlayer.cs | 12 +- VG Music Studio - Core/NDS/DSE/SMD.cs | 87 +- VG Music Studio - Core/NDS/DSE/SWD.cs | 1151 +- .../NDS/SDAT/SDATChannel.cs | 1 + .../Properties/Strings.Designer.cs | 29 +- .../Properties/Strings.resx | 13 +- .../VG Music Studio - Core.csproj | 2 +- VG Music Studio - Core/Wii/WiiUtils.cs | 7 + .../VG Music Studio - MIDI.csproj | 2 +- .../API/Core/Interop/CoreErrorHelper.cs | 96 + .../API/Core/Interop/CoreHelpers.cs | 86 + .../API/Core/Interop/CoreNativeMethods.cs | 233 + .../API/Core/Interop/WindowMessage.cs | 229 + .../API/Core/PropertySystem/PropVariant.cs | 754 + .../PropVariantNativeMethods.cs | 107 + .../API/Core/PropertySystem/PropertyKey.cs | 82 + .../Resources/LocalizedMessages.Designer.cs | 684 + .../API/Core/Resources/LocalizedMessages.resx | 327 + .../Shell/Common/DefaultShellImageSizes.cs | 42 + .../API/Shell/Common/IconReference.cs | 130 + .../API/Shell/Common/NativePoint.cs | 52 + .../API/Shell/Common/NativeRect.cs | 72 + .../API/Shell/Common/ShellEnums.cs | 360 + .../API/Shell/Common/ShellException.cs | 58 + .../API/Shell/Common/ShellFile.cs | 38 + .../API/Shell/Common/ShellFileSystemFolder.cs | 50 + .../API/Shell/Common/ShellFolder.cs | 10 + .../API/Shell/Common/ShellFolderItems.cs | 77 + .../API/Shell/Common/ShellHelper.cs | 82 + .../API/Shell/Common/ShellLibrary.cs | 694 + .../API/Shell/Common/ShellLink.cs | 112 + .../Shell/Common/ShellNonFileSystemFolder.cs | 15 + .../Shell/Common/ShellNonFileSystemItem.cs | 10 + .../API/Shell/Common/ShellObject.cs | 396 + .../API/Shell/Common/ShellObjectContainer.cs | 92 + .../API/Shell/Common/ShellObjectFactory.cs | 206 + .../Common/ShellSavedSearchCollection.cs | 13 + .../API/Shell/Common/ShellSearchCollection.cs | 16 + .../API/Shell/Common/ShellSearchConnector.cs | 20 + .../API/Shell/Common/ShellThumbnail.cs | 249 + .../API/Shell/Common/ShellThumbnailEnums.cs | 50 + .../API/Shell/Common/SortColumn.cs | 66 + .../Shell/Interop/Common/ShellCOMClasses.cs | 22 + .../API/Shell/Interop/Common/ShellCOMGuids.cs | 91 + .../Interop/Common/ShellCOMInterfaces.cs | 871 + .../Interop/Common/ShellNativeMethods.cs | 595 + .../Interop/Common/ShellNativeStructs.cs | 80 + .../Shell/Interop/Common/WindowUtilities.cs | 209 + .../KnownFolders/KnownFoldersCOMGuids.cs | 26 + .../KnownFolders/KnownFoldersCOMInterfaces.cs | 182 + .../KnownFolders/KnownFoldersNativeMethods.cs | 34 + .../PropertySystemCOMInterfaces.cs | 347 + .../PropertySystemNativeMethods.cs | 52 + .../ShellExtensions/HandlerNativeMethods.cs | 191 + .../ShellObjectWatcherNativeMethods.cs | 161 + .../Taskbar/TabbedThumbnailNativeMethods.cs | 222 + .../Interop/Taskbar/TaskbarCOMInterfaces.cs | 182 + .../Interop/Taskbar/TaskbarNativeMethods.cs | 183 + .../Shell/KnownFolders/DefinitionOptions.cs | 23 + .../KnownFolders/FileSystemKnownFolder.cs | 163 + .../API/Shell/KnownFolders/FolderCategory.cs | 36 + .../Shell/KnownFolders/FolderProperties.cs | 40 + .../API/Shell/KnownFolders/FolderTypes.cs | 173 + .../Shell/KnownFolders/FoldersIdentifiers.cs | 370 + .../API/Shell/KnownFolders/IKnownFolder.cs | 75 + .../Shell/KnownFolders/KnownFolderHelper.cs | 184 + .../Shell/KnownFolders/KnownFolderSettings.cs | 180 + .../API/Shell/KnownFolders/KnownFolders.cs | 576 + .../KnownFolders/NonFileSystemKnownFolder.cs | 163 + .../KnownFolders/RedirectionCapabilities.cs | 32 + .../Shell/PropertySystem/IShellProperty.cs | 66 + .../PropertySystem/PropertySystemException.cs | 40 + .../Shell/PropertySystem/ShellProperties.cs | 165 + .../API/Shell/PropertySystem/ShellProperty.cs | 363 + .../PropertySystem/ShellPropertyCollection.cs | 264 + .../ShellPropertyDescription.cs | 486 + .../ShellPropertyDescriptionsCache.cs | 36 + .../PropertySystem/ShellPropertyEnumType.cs | 100 + .../PropertySystem/ShellPropertyEnums.cs | 463 + .../PropertySystem/ShellPropertyFactory.cs | 195 + .../PropertySystem/ShellPropertyWriter.cs | 203 + .../PropertySystem/StronglyTypedProperties.cs | 16025 ++++++++++++++++ .../Shell/PropertySystem/SystemProperties.cs | 12302 ++++++++++++ .../Resources/LocalizedMessages.Designer.cs | 1251 ++ .../Shell/Resources/LocalizedMessages.resx | 516 + .../ShellObjectWatcherEnums.cs | 187 + .../API/Shell/Taskbar/JumpList.cs | 555 + .../Shell/Taskbar/JumpListCustomCategory.cs | 89 + .../JumpListCustomCategoryCollection.cs | 120 + .../API/Shell/Taskbar/JumpListItem.cs | 33 + .../Shell/Taskbar/JumpListItemCollection.cs | 111 + .../API/Shell/Taskbar/JumpListLink.cs | 201 + .../API/Shell/Taskbar/JumpListSeparator.cs | 99 + .../API/Shell/Taskbar/TabbedThumbnail.cs | 561 + ...TabbedThumbnailBitmapRequestedEventArgs.cs | 40 + .../Taskbar/TabbedThumbnailClosedEventArgs.cs | 29 + .../Shell/Taskbar/TabbedThumbnailEventArgs.cs | 46 + .../Shell/Taskbar/TabbedThumbnailManager.cs | 458 + .../Taskbar/TabbedThumbnailProxyWindow.cs | 90 + .../Taskbar/TabbedThumbnailScreenCapture.cs | 180 + .../API/Shell/Taskbar/TaskbarEnums.cs | 65 + .../API/Shell/Taskbar/TaskbarInterfaces.cs | 24 + .../API/Shell/Taskbar/TaskbarList.cs | 32 + .../API/Shell/Taskbar/TaskbarManager.cs | 272 + .../API/Shell/Taskbar/TaskbarWindow.cs | 218 + .../API/Shell/Taskbar/TaskbarWindowManager.cs | 820 + .../API/Shell/Taskbar/ThumbnailButton.cs | 344 + .../ThumbnailButtonClickedEventArgs.cs | 55 + .../Shell/Taskbar/ThumbnailToolbarManager.cs | 77 + .../Taskbar/ThumbnailToolbarProxyWindow.cs | 119 + .../UserRemovedJumpListItemsEventArg.cs | 23 + VG Music Studio - WinForms/MainForm.cs | 15 +- .../CellEditing/CellEditKeyEngine.cs | 520 + .../ObjectListView/CellEditing/CellEditors.cs | 325 + .../CellEditing/EditorRegistry.cs | 213 + .../ObjectListView/CustomDictionary.xml | 46 + .../ObjectListView/DataListView.cs | 240 + .../ObjectListView/DataTreeListView.cs | 240 + .../ObjectListView/DragDrop/DragSource.cs | 219 + .../ObjectListView/DragDrop/DropSink.cs | 1562 ++ .../ObjectListView/DragDrop/OLVDataObject.cs | 185 + .../ObjectListView/FastDataListView.cs | 169 + .../ObjectListView/FastObjectListView.cs | 422 + .../ObjectListView/Filtering/Cluster.cs | 125 + .../Filtering/ClusteringStrategy.cs | 189 + .../Filtering/ClustersFromGroupsStrategy.cs | 70 + .../Filtering/DateTimeClusteringStrategy.cs | 187 + .../Filtering/FilterMenuBuilder.cs | 369 + .../ObjectListView/Filtering/Filters.cs | 489 + .../Filtering/FlagClusteringStrategy.cs | 160 + .../ObjectListView/Filtering/ICluster.cs | 56 + .../Filtering/IClusteringStrategy.cs | 80 + .../Filtering/TextMatchFilter.cs | 642 + .../ObjectListView/FullClassDiagram.cd | 1261 ++ .../Implementation/Attributes.cs | 335 + .../Implementation/Comparers.cs | 330 + .../Implementation/DataSourceAdapter.cs | 628 + .../Implementation/Delegates.cs | 168 + .../ObjectListView/Implementation/Enums.cs | 104 + .../ObjectListView/Implementation/Events.cs | 2514 +++ .../Implementation/GroupingParameters.cs | 204 + .../ObjectListView/Implementation/Groups.cs | 761 + .../ObjectListView/Implementation/Munger.cs | 568 + .../Implementation/NativeMethods.cs | 1223 ++ .../Implementation/NullableDictionary.cs | 87 + .../Implementation/OLVListItem.cs | 325 + .../Implementation/OLVListSubItem.cs | 173 + .../Implementation/OlvListViewHitTestInfo.cs | 388 + .../Implementation/TreeDataSourceAdapter.cs | 262 + .../Implementation/VirtualGroups.cs | 341 + .../Implementation/VirtualListDataSource.cs | 349 + .../ObjectListView/OLVColumn.cs | 1909 ++ .../ObjectListView.DesignTime.cs | 550 + .../ObjectListView/ObjectListView.FxCop | 3521 ++++ .../ObjectListView/ObjectListView.cs | 10924 +++++++++++ .../ObjectListView/ObjectListView.shfb | 47 + .../Properties/Resources.Designer.cs | 113 + .../ObjectListView/Properties/Resources.resx | 137 + .../ObjectListView/Rendering/Adornments.cs | 743 + .../ObjectListView/Rendering/Decorations.cs | 973 + .../ObjectListView/Rendering/Overlays.cs | 302 + .../ObjectListView/Rendering/Renderers.cs | 3887 ++++ .../ObjectListView/Rendering/Styles.cs | 400 + .../ObjectListView/Rendering/TreeRenderer.cs | 309 + .../ObjectListView/Resources/clear-filter.png | Bin 0 -> 1381 bytes .../ObjectListView/Resources/coffee.jpg | Bin 0 -> 73464 bytes .../Resources/filter-icons3.png | Bin 0 -> 1305 bytes .../ObjectListView/Resources/filter.png | Bin 0 -> 1331 bytes .../Resources/sort-ascending.png | Bin 0 -> 1364 bytes .../Resources/sort-descending.png | Bin 0 -> 1371 bytes .../SubControls/GlassPanelForm.cs | 459 + .../SubControls/HeaderControl.cs | 1230 ++ .../SubControls/ToolStripCheckedListBox.cs | 189 + .../SubControls/ToolTipControl.cs | 699 + .../ObjectListView/TreeListView.cs | 2269 +++ .../Utilities/ColumnSelectionForm.Designer.cs | 190 + .../Utilities/ColumnSelectionForm.cs | 263 + .../Utilities/ColumnSelectionForm.resx | 120 + .../ObjectListView/Utilities/Generator.cs | 563 + .../ObjectListView/Utilities/OLVExporter.cs | 277 + .../Utilities/TypedObjectListView.cs | 561 + .../ObjectListView/VirtualObjectListView.cs | 1255 ++ .../ObjectListView/olv-keyfile.snk | Bin 0 -> 596 bytes .../TaskbarPlayerButtons.cs | 2 +- VG Music Studio - WinForms/TrackViewer.cs | 8 +- .../VG Music Studio - WinForms.csproj | 7 +- VG Music Studio.sln | 6 + 201 files changed, 101045 insertions(+), 709 deletions(-) rename VG Music Studio - Core/{ => Codec}/ADPCMDecoder.cs (97%) create mode 100644 VG Music Studio - Core/Codec/CodecEnums.cs create mode 100644 VG Music Studio - Core/Codec/DSPADPCM.cs create mode 100644 VG Music Studio - Core/Codec/LayoutEnums.cs create mode 100644 VG Music Studio - Core/Codec/MetaEnums.cs create mode 100644 VG Music Studio - Core/Wii/WiiUtils.cs create mode 100644 VG Music Studio - WinForms/API/Core/Interop/CoreErrorHelper.cs create mode 100644 VG Music Studio - WinForms/API/Core/Interop/CoreHelpers.cs create mode 100644 VG Music Studio - WinForms/API/Core/Interop/CoreNativeMethods.cs create mode 100644 VG Music Studio - WinForms/API/Core/Interop/WindowMessage.cs create mode 100644 VG Music Studio - WinForms/API/Core/PropertySystem/PropVariant.cs create mode 100644 VG Music Studio - WinForms/API/Core/PropertySystem/PropVariantNativeMethods.cs create mode 100644 VG Music Studio - WinForms/API/Core/PropertySystem/PropertyKey.cs create mode 100644 VG Music Studio - WinForms/API/Core/Resources/LocalizedMessages.Designer.cs create mode 100644 VG Music Studio - WinForms/API/Core/Resources/LocalizedMessages.resx create mode 100644 VG Music Studio - WinForms/API/Shell/Common/DefaultShellImageSizes.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/IconReference.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/NativePoint.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/NativeRect.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellEnums.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellException.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellFile.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellFileSystemFolder.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellFolder.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellFolderItems.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellHelper.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellLibrary.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellLink.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellNonFileSystemFolder.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellNonFileSystemItem.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellObject.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellObjectContainer.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellObjectFactory.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellSavedSearchCollection.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellSearchCollection.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellSearchConnector.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellThumbnail.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/ShellThumbnailEnums.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Common/SortColumn.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/Common/ShellCOMClasses.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/Common/ShellCOMGuids.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/Common/ShellCOMInterfaces.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/Common/ShellNativeMethods.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/Common/ShellNativeStructs.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/Common/WindowUtilities.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/KnownFolders/KnownFoldersCOMGuids.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/KnownFolders/KnownFoldersCOMInterfaces.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/KnownFolders/KnownFoldersNativeMethods.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/PropertySystem/PropertySystemCOMInterfaces.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/PropertySystem/PropertySystemNativeMethods.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/ShellExtensions/HandlerNativeMethods.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/ShellObjectWatcher/ShellObjectWatcherNativeMethods.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/Taskbar/TabbedThumbnailNativeMethods.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/Taskbar/TaskbarCOMInterfaces.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Interop/Taskbar/TaskbarNativeMethods.cs create mode 100644 VG Music Studio - WinForms/API/Shell/KnownFolders/DefinitionOptions.cs create mode 100644 VG Music Studio - WinForms/API/Shell/KnownFolders/FileSystemKnownFolder.cs create mode 100644 VG Music Studio - WinForms/API/Shell/KnownFolders/FolderCategory.cs create mode 100644 VG Music Studio - WinForms/API/Shell/KnownFolders/FolderProperties.cs create mode 100644 VG Music Studio - WinForms/API/Shell/KnownFolders/FolderTypes.cs create mode 100644 VG Music Studio - WinForms/API/Shell/KnownFolders/FoldersIdentifiers.cs create mode 100644 VG Music Studio - WinForms/API/Shell/KnownFolders/IKnownFolder.cs create mode 100644 VG Music Studio - WinForms/API/Shell/KnownFolders/KnownFolderHelper.cs create mode 100644 VG Music Studio - WinForms/API/Shell/KnownFolders/KnownFolderSettings.cs create mode 100644 VG Music Studio - WinForms/API/Shell/KnownFolders/KnownFolders.cs create mode 100644 VG Music Studio - WinForms/API/Shell/KnownFolders/NonFileSystemKnownFolder.cs create mode 100644 VG Music Studio - WinForms/API/Shell/KnownFolders/RedirectionCapabilities.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/IShellProperty.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/PropertySystemException.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/ShellProperties.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/ShellProperty.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyCollection.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyDescription.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyDescriptionsCache.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyEnumType.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyEnums.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyFactory.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyWriter.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/StronglyTypedProperties.cs create mode 100644 VG Music Studio - WinForms/API/Shell/PropertySystem/SystemProperties.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Resources/LocalizedMessages.Designer.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Resources/LocalizedMessages.resx create mode 100644 VG Music Studio - WinForms/API/Shell/ShellObjectWatcher/ShellObjectWatcherEnums.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/JumpList.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/JumpListCustomCategory.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/JumpListCustomCategoryCollection.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/JumpListItem.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/JumpListItemCollection.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/JumpListLink.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/JumpListSeparator.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnail.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailBitmapRequestedEventArgs.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailClosedEventArgs.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailEventArgs.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailManager.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailProxyWindow.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailScreenCapture.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarEnums.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarInterfaces.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarList.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarManager.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarWindow.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarWindowManager.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailButton.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailButtonClickedEventArgs.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailToolbarManager.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailToolbarProxyWindow.cs create mode 100644 VG Music Studio - WinForms/API/Shell/Taskbar/UserRemovedJumpListItemsEventArg.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/CellEditing/CellEditKeyEngine.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/CellEditing/CellEditors.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/CellEditing/EditorRegistry.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/CustomDictionary.xml create mode 100644 VG Music Studio - WinForms/ObjectListView/DataListView.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/DataTreeListView.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/DragDrop/DragSource.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/DragDrop/DropSink.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/DragDrop/OLVDataObject.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/FastDataListView.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/FastObjectListView.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Filtering/Cluster.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Filtering/ClusteringStrategy.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Filtering/ClustersFromGroupsStrategy.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Filtering/DateTimeClusteringStrategy.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Filtering/FilterMenuBuilder.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Filtering/Filters.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Filtering/FlagClusteringStrategy.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Filtering/ICluster.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Filtering/IClusteringStrategy.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Filtering/TextMatchFilter.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/FullClassDiagram.cd create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/Attributes.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/Comparers.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/DataSourceAdapter.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/Delegates.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/Enums.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/Events.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/GroupingParameters.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/Groups.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/Munger.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/NativeMethods.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/NullableDictionary.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/OLVListItem.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/OLVListSubItem.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/OlvListViewHitTestInfo.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/TreeDataSourceAdapter.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/VirtualGroups.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Implementation/VirtualListDataSource.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/OLVColumn.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/ObjectListView.DesignTime.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/ObjectListView.FxCop create mode 100644 VG Music Studio - WinForms/ObjectListView/ObjectListView.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/ObjectListView.shfb create mode 100644 VG Music Studio - WinForms/ObjectListView/Properties/Resources.Designer.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Properties/Resources.resx create mode 100644 VG Music Studio - WinForms/ObjectListView/Rendering/Adornments.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Rendering/Decorations.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Rendering/Overlays.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Rendering/Renderers.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Rendering/Styles.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Rendering/TreeRenderer.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Resources/clear-filter.png create mode 100644 VG Music Studio - WinForms/ObjectListView/Resources/coffee.jpg create mode 100644 VG Music Studio - WinForms/ObjectListView/Resources/filter-icons3.png create mode 100644 VG Music Studio - WinForms/ObjectListView/Resources/filter.png create mode 100644 VG Music Studio - WinForms/ObjectListView/Resources/sort-ascending.png create mode 100644 VG Music Studio - WinForms/ObjectListView/Resources/sort-descending.png create mode 100644 VG Music Studio - WinForms/ObjectListView/SubControls/GlassPanelForm.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/SubControls/HeaderControl.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/SubControls/ToolStripCheckedListBox.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/SubControls/ToolTipControl.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/TreeListView.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Utilities/ColumnSelectionForm.Designer.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Utilities/ColumnSelectionForm.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Utilities/ColumnSelectionForm.resx create mode 100644 VG Music Studio - WinForms/ObjectListView/Utilities/Generator.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Utilities/OLVExporter.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/Utilities/TypedObjectListView.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/VirtualObjectListView.cs create mode 100644 VG Music Studio - WinForms/ObjectListView/olv-keyfile.snk diff --git a/VG Music Studio - Core/ADPCMDecoder.cs b/VG Music Studio - Core/Codec/ADPCMDecoder.cs similarity index 97% rename from VG Music Studio - Core/ADPCMDecoder.cs rename to VG Music Studio - Core/Codec/ADPCMDecoder.cs index cb2a5d8..3595433 100644 --- a/VG Music Studio - Core/ADPCMDecoder.cs +++ b/VG Music Studio - Core/Codec/ADPCMDecoder.cs @@ -1,4 +1,4 @@ -namespace Kermalis.VGMusicStudio.Core; +namespace Kermalis.VGMusicStudio.Core.Codec; // TODO: Struct or something to prevent allocations internal sealed class ADPCMDecoder diff --git a/VG Music Studio - Core/Codec/CodecEnums.cs b/VG Music Studio - Core/Codec/CodecEnums.cs new file mode 100644 index 0000000..1e916a8 --- /dev/null +++ b/VG Music Studio - Core/Codec/CodecEnums.cs @@ -0,0 +1,180 @@ +namespace Kermalis.VGMusicStudio.Core.Codec; + +/* This code has been copied directly from vgmstream.h in VGMStream's repository * + * and modified into C# code to work with VGMS. Link to its repository can be * + * found here: https://github.com/vgmstream/vgmstream */ +public enum CodecType +{ + codec_SILENCE, /* generates silence */ + + /* PCM */ + codec_PCM16LE, /* little endian 16-bit PCM */ + codec_PCM16BE, /* big endian 16-bit PCM */ + codec_PCM16_int, /* 16-bit PCM with sample-level interleave (for blocks) */ + + codec_PCM8, /* 8-bit PCM */ + codec_PCM8_int, /* 8-bit PCM with sample-level interleave (for blocks) */ + codec_PCM8_U, /* 8-bit PCM, unsigned (0x80 = 0) */ + codec_PCM8_U_int, /* 8-bit PCM, unsigned (0x80 = 0) with sample-level interleave (for blocks) */ + codec_PCM8_SB, /* 8-bit PCM, sign bit (others are 2's complement) */ + codec_PCM4, /* 4-bit PCM, signed */ + codec_PCM4_U, /* 4-bit PCM, unsigned */ + + codec_ULAW, /* 8-bit u-Law (non-linear PCM) */ + codec_ULAW_int, /* 8-bit u-Law (non-linear PCM) with sample-level interleave (for blocks) */ + codec_ALAW, /* 8-bit a-Law (non-linear PCM) */ + + codec_PCMFLOAT, /* 32-bit float PCM */ + codec_PCM24LE, /* little endian 24-bit PCM */ + codec_PCM24BE, /* big endian 24-bit PCM */ + + /* ADPCM */ + codec_CRI_ADX, /* CRI ADX */ + codec_CRI_ADX_fixed, /* CRI ADX, encoding type 2 with fixed coefficients */ + codec_CRI_ADX_exp, /* CRI ADX, encoding type 4 with exponential scale */ + codec_CRI_ADX_enc_8, /* CRI ADX, type 8 encryption (God Hand) */ + codec_CRI_ADX_enc_9, /* CRI ADX, type 9 encryption (PSO2) */ + + codec_NGC_DSP, /* Nintendo DSP ADPCM */ + codec_NGC_DSP_subint, /* Nintendo DSP ADPCM with frame subinterframe */ + codec_NGC_DTK, /* Nintendo DTK ADPCM (hardware disc), also called TRK or ADP */ + codec_NGC_AFC, /* Nintendo AFC ADPCM */ + codec_VADPCM, /* Silicon Graphics VADPCM */ + + codec_G721, /* CCITT G.721 */ + + codec_XA, /* CD-ROM XA 4-bit */ + codec_XA8, /* CD-ROM XA 8-bit */ + codec_XA_EA, /* EA's Saturn XA (not to be confused with EA-XA) */ + codec_PSX, /* Sony PS ADPCM (VAG) */ + codec_PSX_badflags, /* Sony PS ADPCM with custom flag byte */ + codec_PSX_cfg, /* Sony PS ADPCM with configurable frame size (int math) */ + codec_PSX_pivotal, /* Sony PS ADPCM with configurable frame size (float math) */ + codec_HEVAG, /* Sony PSVita ADPCM */ + + codec_EA_XA, /* Electronic Arts EA-XA ADPCM v1 (stereo) aka "EA ADPCM" */ + codec_EA_XA_int, /* Electronic Arts EA-XA ADPCM v1 (mono/interleave) */ + codec_EA_XA_V2, /* Electronic Arts EA-XA ADPCM v2 */ + codec_MAXIS_XA, /* Maxis EA-XA ADPCM */ + codec_EA_XAS_V0, /* Electronic Arts EA-XAS ADPCM v0 */ + codec_EA_XAS_V1, /* Electronic Arts EA-XAS ADPCM v1 */ + + codec_IMA, /* IMA ADPCM (stereo or mono, low nibble first) */ + codec_IMA_int, /* IMA ADPCM (mono/interleave, low nibble first) */ + codec_DVI_IMA, /* DVI IMA ADPCM (stereo or mono, high nibble first) */ + codec_DVI_IMA_int, /* DVI IMA ADPCM (mono/interleave, high nibble first) */ + codec_NW_IMA, + codec_SNDS_IMA, /* Heavy Iron Studios .snds IMA ADPCM */ + codec_QD_IMA, + codec_WV6_IMA, /* Gorilla Systems WV6 4-bit IMA ADPCM */ + codec_HV_IMA, /* High Voltage 4-bit IMA ADPCM */ + codec_FFTA2_IMA, /* Final Fantasy Tactics A2 4-bit IMA ADPCM */ + codec_BLITZ_IMA, /* Blitz Games 4-bit IMA ADPCM */ + + codec_MS_IMA, /* Microsoft IMA ADPCM */ + codec_MS_IMA_mono, /* Microsoft IMA ADPCM (mono/interleave) */ + codec_XBOX_IMA, /* XBOX IMA ADPCM */ + codec_XBOX_IMA_mch, /* XBOX IMA ADPCM (multichannel) */ + codec_XBOX_IMA_int, /* XBOX IMA ADPCM (mono/interleave) */ + codec_NDS_IMA, /* IMA ADPCM w/ NDS layout */ + codec_DAT4_IMA, /* Eurocom 'DAT4' IMA ADPCM */ + codec_RAD_IMA, /* Radical IMA ADPCM */ + codec_RAD_IMA_mono, /* Radical IMA ADPCM (mono/interleave) */ + codec_APPLE_IMA4, /* Apple Quicktime IMA4 */ + codec_FSB_IMA, /* FMOD's FSB multichannel IMA ADPCM */ + codec_WWISE_IMA, /* Audiokinetic Wwise IMA ADPCM */ + codec_REF_IMA, /* Reflections IMA ADPCM */ + codec_AWC_IMA, /* Rockstar AWC IMA ADPCM */ + codec_UBI_IMA, /* Ubisoft IMA ADPCM */ + codec_UBI_SCE_IMA, /* Ubisoft SCE IMA ADPCM */ + codec_H4M_IMA, /* H4M IMA ADPCM (stereo or mono, high nibble first) */ + codec_MTF_IMA, /* Capcom MT Framework IMA ADPCM */ + codec_CD_IMA, /* Crystal Dynamics IMA ADPCM */ + + codec_MSADPCM, /* Microsoft ADPCM (stereo/mono) */ + codec_MSADPCM_int, /* Microsoft ADPCM (mono) */ + codec_MSADPCM_ck, /* Microsoft ADPCM (Cricket Audio variation) */ + codec_WS, /* Westwood Studios VBR ADPCM */ + + codec_AICA, /* Yamaha AICA ADPCM (stereo) */ + codec_AICA_int, /* Yamaha AICA ADPCM (mono/interleave) */ + codec_CP_YM, /* Capcom's Yamaha ADPCM (stereo/mono) */ + codec_ASKA, /* Aska ADPCM */ + codec_NXAP, /* NXAP ADPCM */ + + codec_TGC, /* Tiger Game.com 4-bit ADPCM */ + + codec_PSX_DSE_SQUARESOFT, /* SquareSoft Digital Sound Elements 16-bit PCM (For PSX) */ + codec_PS2_DSE_PROCYON, /* Procyon Studio Digital Sound Elements ADPCM (PS2 Version, encoded with VAG-ADPCM) */ + codec_NDS_DSE_PROCYON, /* Procyon Studio Digital Sound Elements ADPCM (NDS Version, encoded with IMA-ADPCM) */ + codec_WII_DSE_PROCYON, /* Procyon Studio Digital Sound Elements ADPCM (Wii Version, encoded with DSP-ADPCM) */ + codec_L5_555, /* Level-5 0x555 ADPCM */ + codec_LSF, /* lsf ADPCM (Fastlane Street Racing iPhone)*/ + codec_MTAF, /* Konami MTAF ADPCM */ + codec_MTA2, /* Konami MTA2 ADPCM */ + codec_MC3, /* Paradigm MC3 3-bit ADPCM */ + codec_FADPCM, /* FMOD FADPCM 4-bit ADPCM */ + codec_ASF, /* Argonaut ASF 4-bit ADPCM */ + codec_DSA, /* Ocean DSA 4-bit ADPCM */ + codec_XMD, /* Konami XMD 4-bit ADPCM */ + codec_TANTALUS, /* Tantalus 4-bit ADPCM */ + codec_PCFX, /* PC-FX 4-bit ADPCM */ + codec_OKI16, /* OKI 4-bit ADPCM with 16-bit output and modified expand */ + codec_OKI4S, /* OKI 4-bit ADPCM with 16-bit output and cuadruple step */ + codec_PTADPCM, /* Platinum 4-bit ADPCM */ + codec_IMUSE, /* LucasArts iMUSE Variable ADPCM */ + codec_COMPRESSWAVE, /* CompressWave Huffman ADPCM */ + + /* others */ + codec_SDX2, /* SDX2 2:1 Squareroot-Delta-Exact compression DPCM */ + codec_SDX2_int, /* SDX2 2:1 Squareroot-Delta-Exact compression with sample-level interleave */ + codec_CBD2, /* CBD2 2:1 Cuberoot-Delta-Exact compression DPCM */ + codec_CBD2_int, /* CBD2 2:1 Cuberoot-Delta-Exact compression, with sample-level interleave */ + codec_SASSC, /* Activision EXAKT SASSC 8-bit DPCM */ + codec_DERF, /* DERF 8-bit DPCM */ + codec_WADY, /* WADY 8-bit DPCM */ + codec_NWA, /* VisualArt's NWA DPCM */ + codec_ACM, /* InterPlay ACM */ + codec_CIRCUS_ADPCM, /* Circus 8-bit ADPCM */ + codec_UBI_ADPCM, /* Ubisoft 4/6-bit ADPCM */ + + codec_EA_MT, /* Electronic Arts MicroTalk (linear-predictive speech codec) */ + codec_CIRCUS_VQ, /* Circus VQ */ + codec_RELIC, /* Relic Codec (DCT-based) */ + codec_CRI_HCA, /* CRI High Compression Audio (MDCT-based) */ + codec_TAC, /* tri-Ace Codec (MDCT-based) */ + codec_ICE_RANGE, /* Inti Creates "range" codec */ + codec_ICE_DCT, /* Inti Creates "DCT" codec */ + + + codec_OGG_VORBIS, /* Xiph Vorbis with Ogg layer (MDCT-based) */ + codec_VORBIS_custom, /* Xiph Vorbis with custom layer (MDCT-based) */ + + + codec_MPEG_custom, /* MPEG audio with custom features (MDCT-based) */ + codec_MPEG_ealayer3, /* EALayer3, custom MPEG frames */ + codec_MPEG_layer1, /* MP1 MPEG audio (MDCT-based) */ + codec_MPEG_layer2, /* MP2 MPEG audio (MDCT-based) */ + codec_MPEG_layer3, /* MP3 MPEG audio (MDCT-based) */ + + + codec_G7221C, /* ITU G.722.1 annex C (Polycom Siren 14) */ + + + codec_G719, /* ITU G.719 annex B (Polycom Siren 22) */ + + + codec_MP4_AAC, /* AAC (MDCT-based) */ + + + codec_ATRAC9, /* Sony ATRAC9 (MDCT-based) */ + + + codec_CELT_FSB, /* Custom Xiph CELT (MDCT-based) */ + + + codec_SPEEX, /* Custom Speex (CELP-based) */ + + + codec_FFmpeg, /* Formats handled by FFmpeg (ATRAC3, XMA, AC3, etc) */ +} \ No newline at end of file diff --git a/VG Music Studio - Core/Codec/DSPADPCM.cs b/VG Music Studio - Core/Codec/DSPADPCM.cs new file mode 100644 index 0000000..520005a --- /dev/null +++ b/VG Music Studio - Core/Codec/DSPADPCM.cs @@ -0,0 +1,1443 @@ +using Kermalis.EndianBinaryIO; +using Kermalis.VGMusicStudio.Core.Util; +using System; +using System.Diagnostics; + +namespace Kermalis.VGMusicStudio.Core.Codec +{ + internal sealed class DSPADPCM + { + public static short Min = short.MinValue; + public static short Max = short.MaxValue; + + public static double[] Tvec = new double[3]; + + public DSPADPCMInfo Info; + public byte[] Data; + public uint NumADPCMNibbles; + //public static short[]? DataOutput; + + public static class DSPADPCMConstants + { + public const int BytesPerFrame = 8; + public const int SamplesPerFrame = 14; + public const int NibblesPerFrame = 16; + } + + public DSPADPCM(EndianBinaryReader r) + { + Info = new DSPADPCMInfo(r); + NumADPCMNibbles = Info.num_adpcm_nibbles; + Data = new byte[(Info.num_adpcm_nibbles / 2) + 9]; + + r.ReadBytes(Data); + r.Stream.Align(16); + return; + } + + #region DSP-ADPCM Info + public interface IDSPADPCMInfo + { + public short[] coef { get; } + public ushort gain { get; } + public ushort pred_scale { get; } + public short yn1 { get; } + public short yn2 { get; } + + public ushort loop_pred_scale { get; } + public short loop_yn1 { get; } + public short loop_yn2 { get; } + } + + public class DSPADPCMInfo : IDSPADPCMInfo + { + public uint num_samples { get; set; } + public uint num_adpcm_nibbles { get; set; } + public uint sample_rate { get; set; } + public ushort loop_flag { get; set; } + public ushort format { get; set; } + public uint sa { get; set; } + public uint ea { get; set; } + public uint ca { get; set; } + public short[] coef { get; set; } + public ushort gain { get; set; } + public ushort pred_scale { get; set; } + public short yn1 { get; set; } + public short yn2 { get; set; } + + public ushort loop_pred_scale { get; set; } + public short loop_yn1 { get; set; } + public short loop_yn2 { get; set; } + public ushort[] padding { get; set; } + + public DSPADPCMInfo(EndianBinaryReader r) + { + num_samples = r.ReadUInt32(); + + num_adpcm_nibbles = r.ReadUInt32(); + + sample_rate = r.ReadUInt32(); + + loop_flag = r.ReadUInt16(); + + format = r.ReadUInt16(); + + sa = r.ReadUInt32(); + + ea = r.ReadUInt32(); + + ca = r.ReadUInt32(); + + coef = new short[16]; + r.ReadInt16s(coef); + + gain = r.ReadUInt16(); + + pred_scale = r.ReadUInt16(); + + yn1 = r.ReadInt16(); + + yn2 = r.ReadInt16(); + + loop_pred_scale = r.ReadUInt16(); + + loop_yn1 = r.ReadInt16(); + + loop_yn2 = r.ReadInt16(); + + padding = new ushort[11]; + r.ReadUInt16s(padding); + } + } + #endregion + + #region DSP-ADPCM Convert + + //public object GetSamples(DSPADPCMInfo cxt, uint loopEnd) + //{ + // return DSPADPCMToPCM16(Data, loopEnd, cxt); + //} + + public static Span DSPADPCMToPCM16(Span dspadpcm, uint numSamples, DSPADPCMInfo cxt) + { + Span dataOutput = new short[dspadpcm.Length]; // This is the new output data that's converted to PCM16 + Span dataIndex = new short[numSamples]; + for (int i = 0; i < dataOutput.Length; i++) + { + dataOutput[i] = dataIndex[i]; + Decode(dspadpcm, dataOutput, ref cxt, numSamples); + } + return dataOutput; + } + #endregion + + #region DSP-ADPCM Encode + public static void Encode(short[] src, byte[] dst, DSPADPCMInfo cxt, uint samples) + { + short[] coefs = cxt.coef; + CorrelateCoefs(src, samples, coefs); + + int frameCount = (int)((samples / DSPADPCMConstants.SamplesPerFrame) + (samples % DSPADPCMConstants.SamplesPerFrame)); + + short[] pcm = src; + byte[] adpcm = dst; + short[] pcmFrame = new short[DSPADPCMConstants.SamplesPerFrame + 2]; + byte[] adpcmFrame = new byte[DSPADPCMConstants.BytesPerFrame]; + + short srcIndex = 0; + short dstIndex = 0; + + for (int i = 0; i < frameCount; ++i, pcm[srcIndex] += DSPADPCMConstants.SamplesPerFrame, adpcm[srcIndex] += DSPADPCMConstants.BytesPerFrame) + { + coefs = new short[2 + 0]; + + DSPEncodeFrame(pcmFrame, DSPADPCMConstants.SamplesPerFrame, adpcmFrame, coefs); + + pcmFrame[0] = pcmFrame[14]; + pcmFrame[1] = pcmFrame[15]; + } + + cxt.gain = 0; + cxt.pred_scale = dst[dstIndex++]; + cxt.yn1 = 0; + cxt.yn2 = 0; + } + + public static void InnerProductMerge(double[] vecOut, short[] pcmBuf) + { + pcmBuf = new short[14]; + vecOut = Tvec; + + for (int i = 0; i <= 2; i++) + { + vecOut[i] = 0.0f; + for (int x = 0; x < 14; x++) + vecOut[i] -= pcmBuf[x - i] * pcmBuf[x]; + + } + } + + public static void OuterProductMerge(double[] mtxOut, short[] pcmBuf) + { + pcmBuf = new short[14]; + mtxOut[3] = Tvec[3]; + + for (int x = 1; x <= 2; x++) + for (int y = 1; y <= 2; y++) + { + mtxOut[x] = 0.0; + mtxOut[y] = 0.0; + for (int z = 0; z < 14; z++) + mtxOut[x + y] += pcmBuf[z - x] * pcmBuf[z - y]; + } + } + + public static bool AnalyzeRanges(double[] mtx, int[] vecIdxsOut) + { + mtx[3] = Tvec[3]; + double[] recips = new double[3]; + double val, tmp, min, max; + + /* Get greatest distance from zero */ + for (int x = 1; x <= 2; x++) + { + val = Math.Max(Math.Abs(mtx[x] + mtx[1]), Math.Abs(mtx[x] + mtx[2])); + if (val < double.Epsilon) + return true; + + recips[x] = 1.0 / val; + } + + int maxIndex = 0; + for (int i = 1; i <= 2; i++) + { + for (int x = 1; x < i; x++) + { + tmp = mtx[x] + mtx[i]; + for (int y = 1; y < x; y++) + tmp -= (mtx[x] + mtx[y]) * (mtx[y] + mtx[i]); + mtx[x + i] = tmp; + } + + val = 0.0; + for (int x = i; x <= 2; x++) + { + tmp = mtx[x] + mtx[i]; + for (int y = 1; y < i; y++) + tmp -= (mtx[x] + mtx[y]) * (mtx[y] + mtx[i]); + + mtx[x + i] = tmp; + tmp = Math.Abs(tmp) * recips[x]; + if (tmp >= val) + { + val = tmp; + maxIndex = x; + } + } + + if (maxIndex != i) + { + for (int y = 1; y <= 2; y++) + { + tmp = mtx[maxIndex] + mtx[y]; + mtx[maxIndex + y] = mtx[i] + mtx[y]; + mtx[i + y] = tmp; + } + recips[maxIndex] = recips[i]; + } + + vecIdxsOut[i] = maxIndex; + + if (mtx[i] + mtx[i] == 0.0) + return true; + + if (i != 2) + { + tmp = 1.0 / mtx[i] + mtx[i]; + for (int x = i + 1; x <= 2; x++) + mtx[x + i] *= tmp; + } + } + + /* Get range */ + min = 1.0e10; + max = 0.0; + for (int i = 1; i <= 2; i++) + { + tmp = Math.Abs(mtx[i] + mtx[i]); + if (tmp < min) + min = tmp; + if (tmp > max) + max = tmp; + } + + if (min / max < 1.0e-10) + return true; + + return false; + } + + public static void BidirectionalFilter(double[] mtx, int[] vecIdxs, double[] vecOut) + { + mtx[3] = Tvec[3]; + vecOut = Tvec; + double tmp; + + for (int i = 1, x = 0; i <= 2; i++) + { + int index = vecIdxs[i]; + tmp = vecOut[index]; + vecOut[index] = vecOut[i]; + if (x != 0) + for (int y = x; y <= i - 1; y++) + tmp -= vecOut[y] * mtx[i] + mtx[y]; + else if (tmp != 0.0) + x = i; + vecOut[i] = tmp; + } + + for (int i = 2; i > 0; i--) + { + tmp = vecOut[i]; + for (int y = i + 1; y <= 2; y++) + tmp -= vecOut[y] * mtx[i] + mtx[y]; + vecOut[i] = tmp / mtx[i] + mtx[i]; + } + + vecOut[0] = 1.0; + } + + public static bool QuadraticMerge(double[] inOutVec) + { + inOutVec = Tvec; + + double v0, v1, v2 = inOutVec[2]; + double tmp = 1.0 - (v2 * v2); + + if (tmp == 0.0) + return true; + + v0 = (inOutVec[0] - (v2 * v2)) / tmp; + v1 = (inOutVec[1] - (inOutVec[1] * v2)) / tmp; + + inOutVec[0] = v0; + inOutVec[1] = v1; + + return Math.Abs(v1) > 1.0; + } + + public static void FinishRecord(double[] vIn, double[] vOut) + { + vIn = Tvec; + vOut = Tvec; + for (int z = 1; z <= 2; z++) + { + if (vIn[z] >= 1.0) + vIn[z] = 0.9999999999; + + else if (vIn[z] <= -1.0) + vIn[z] = -0.9999999999; + } + vOut[0] = 1.0; + vOut[1] = (vIn[2] * vIn[1]) + vIn[1]; + vOut[2] = vIn[2]; + } + + public static void MatrixFilter(double[] src, double[] dst) + { + src = Tvec; + dst = Tvec; + double[] mtx = new double[3]; + Tvec = mtx; + + mtx[2 + 0] = 1.0; + for (int i = 1; i <= 2; i++) + mtx[2 + i] = -src[i]; + + for (int i = 2; i > 0; i--) + { + double val = 1.0 - ((mtx[i] + mtx[i]) * (mtx[i] + mtx[i])); + for (int y = 1; y <= i; y++) + mtx[i - 1 + y] = (((mtx[i] + mtx[i]) * (mtx[i] + mtx[y])) + mtx[i] + mtx[y]) / val; + } + + dst[0] = 1.0; + for (int i = 1; i <= 2; i++) + { + dst[i] = 0.0; + for (int y = 1; y <= i; y++) + dst[i] += (mtx[i] + mtx[y]) * dst[i - y]; + } + } + + public static void MergeFinishRecord(double[] src, double[] dst) + { + src = Tvec; + dst = Tvec; + int dstIndex = 0; + double[] tmp = new double[dstIndex]; + double val = src[0]; + + dst[0] = 1.0; + for (int i = 1; i <= 2; i++) + { + double v2 = 0.0; + for (int y = 1; y < i; y++) + v2 += dst[y] * src[i - y]; + + if (val > 0.0) + dst[i] = -(v2 + src[i]) / val; + else + dst[i] = 0.0; + + tmp[i] = dst[i]; + + for (int y = 1; y < i; y++) + dst[y] += dst[i] * dst[i - y]; + + val *= 1.0 - (dst[i] * dst[i]); + } + + FinishRecord(tmp, dst); + } + + public static double ContrastVectors(double[] source1, double[] source2) + { + source1 = Tvec; + source2 = Tvec; + double val = (source2[2] * source2[1] + -source2[1]) / (1.0 - source2[2] * source2[2]); + double val1 = (source1[0] * source1[0]) + (source1[1] * source1[1]) + (source1[2] * source1[2]); + double val2 = (source1[0] * source1[1]) + (source1[1] * source1[2]); + double val3 = source1[0] * source1[2]; + return val1 + (2.0 * val * val2) + (2.0 * (-source2[1] * val + -source2[2]) * val3); + } + + public static void FilterRecords(double[] vecBest, int exp, double[] records, int recordCount) + { + vecBest[8] = Tvec[8]; + records = Tvec; + double[] bufferList = new double[8]; + bufferList[8] = Tvec[8]; + + int[] buffer1 = new int[8]; + double[] buffer2 = Tvec; + + int index; + double value, tempVal = 0; + + for (int x = 0; x < 2; x++) + { + for (int y = 0; y < exp; y++) + { + buffer1[y] = 0; + for (int i = 0; i <= 2; i++) + bufferList[y + i] = 0.0; + } + for (int z = 0; z < recordCount; z++) + { + index = 0; + value = 1.0e30; + for (int i = 0; i < exp; i++) + { + vecBest = new double[i]; + records = new double[z]; + tempVal = ContrastVectors(vecBest, records); + if (tempVal < value) + { + value = tempVal; + index = i; + } + } + buffer1[index]++; + MatrixFilter(records, buffer2); + for (int i = 0; i <= 2; i++) + bufferList[index + i] += buffer2[i]; + } + + for (int i = 0; i < exp; i++) + if (buffer1[i] > 0) + for (int y = 0; y <= 2; y++) + bufferList[i + y] /= buffer1[i]; + + for (int i = 0; i < exp; i++) + bufferList = new double[i]; + MergeFinishRecord(bufferList, vecBest); + } + } + + public static void CorrelateCoefs(short[] source, uint samples, short[] coefsOut) + { + int numFrames = (int)((samples + 13) / 14); + int frameSamples; + + short[] blockBuffer = new short[0x3800]; + short[] pcmHistBuffer = new short[2 + 14]; + + double[] vec1 = Tvec; + double[] vec2 = Tvec; + + double[] mtx = Tvec; + mtx[3] = Tvec[3]; + int[] vecIdxs = new int[3]; + + double[] records = new double[numFrames * 2]; + records = Tvec; + int recordCount = 0; + + double[] vecBest = new double[8]; + vecBest[8] = Tvec[8]; + + int sourceIndex = 0; + + /* Iterate though 1024-block frames */ + for (int x = (int)samples; x > 0;) + { + if (x > 0x3800) /* Full 1024-block frame */ + { + frameSamples = 0x3800; + x -= 0x3800; + } + else /* Partial frame */ + { + /* Zero lingering block samples */ + frameSamples = x; + for (int z = 0; z < 14 && z + frameSamples < 0x3800; z++) + blockBuffer[frameSamples + z] = 0; + x = 0; + } + + /* Copy (potentially non-frame-aligned PCM samples into aligned buffer) */ + source[sourceIndex] += (short)frameSamples; + + + for (int i = 0; i < frameSamples;) + { + for (int z = 0; z < 14; z++) + pcmHistBuffer[0 + z] = pcmHistBuffer[1 + z]; + for (int z = 0; z < 14; z++) + pcmHistBuffer[1 + z] = blockBuffer[i++]; + + pcmHistBuffer = new short[1]; + + InnerProductMerge(vec1, pcmHistBuffer); + if (Math.Abs(vec1[0]) > 10.0) + { + OuterProductMerge(mtx, pcmHistBuffer); + if (!AnalyzeRanges(mtx, vecIdxs)) + { + BidirectionalFilter(mtx, vecIdxs, vec1); + if (!QuadraticMerge(vec1)) + { + records = new double[recordCount]; + FinishRecord(vec1, records); + recordCount++; + } + } + } + } + } + + vec1[0] = 1.0; + vec1[1] = 0.0; + vec1[2] = 0.0; + + for (int z = 0; z < recordCount; z++) + { + records = new double[z]; + vecBest = new double[0]; + MatrixFilter(records, vecBest); + for (int y = 1; y <= 2; y++) + vec1[y] += vecBest[0] + vecBest[y]; + } + for (int y = 1; y <= 2; y++) + vec1[y] /= recordCount; + + MergeFinishRecord(vec1, vecBest); + + + int exp = 1; + for (int w = 0; w < 3;) + { + vec2[0] = 0.0; + vec2[1] = -1.0; + vec2[2] = 0.0; + for (int i = 0; i < exp; i++) + for (int y = 0; y <= 2; y++) + vecBest[exp + i + y] = (0.01 * vec2[y]) + vecBest[i] + vecBest[y]; + ++w; + exp = 1 << w; + FilterRecords(vecBest, exp, records, recordCount); + } + + /* Write output */ + for (int z = 0; z < 8; z++) + { + double d; + d = -vecBest[z] + vecBest[1] * 2048.0; + if (d > 0.0) + coefsOut[z * 2] = (d > 32767.0) ? (short)32767 : (short)Math.Round(d); + else + coefsOut[z * 2] = (d < -32768.0) ? (short)-32768 : (short)Math.Round(d); + + d = -vecBest[z] + vecBest[2] * 2048.0; + if (d > 0.0) + coefsOut[z * 2 + 1] = (d > 32767.0) ? (short)32767 : (short)Math.Round(d); + else + coefsOut[z * 2 + 1] = (d < -32768.0) ? (short)-32768 : (short)Math.Round(d); + } + } + + /* Make sure source includes the yn values (16 samples total) */ + public static void DSPEncodeFrame(short[] pcmInOut, int sampleCount, byte[] adpcmOut, short[] coefsIn) + { + pcmInOut = new short[16]; + adpcmOut = new byte[8]; + coefsIn = new short[8]; + coefsIn = new short[2]; + + int[] inSamples = new int[8]; + inSamples = new int[16]; + int[] outSamples = new int[8]; + outSamples = new int[14]; + + int bestIndex = 0; + + int[] scale = new int[8]; + double[] distAccum = new double[8]; + + /* Iterate through each coef set, finding the set with the smallest error */ + for (int i = 0; i < 8; i++) + { + int v1, v2, v3; + int distance, index; + + /* Set yn values */ + inSamples[i + 0] = pcmInOut[0]; + inSamples[i + 1] = pcmInOut[1]; + + /* Round and clamp samples for this coef set */ + distance = 0; + for (int s = 0; s < sampleCount; s++) + { + /* Multiply previous samples by coefs */ + inSamples[i + (s + 2)] = v1 = ((pcmInOut[s] * (coefsIn[i] + coefsIn[1])) + (pcmInOut[s + 1] * (coefsIn[i] + coefsIn[0]))) / 2048; + /* Subtract from current sample */ + v2 = pcmInOut[s + 2] - v1; + /* Clamp */ + v3 = (v2 >= 32767) ? 32767 : (v2 <= -32768) ? -32768 : v2; + /* Compare distance */ + if (Math.Abs(v3) > Math.Abs(distance)) + distance = v3; + } + + /* Set initial scale */ + for (scale[i] = 0; (scale[i] <= 12) && ((distance > 7) || (distance < -8)); scale[i]++, distance /= 2) + { + } + scale[i] = (scale[i] <= 1) ? -1 : scale[i] - 2; + + do + { + scale[i]++; + distAccum[i] = 0; + index = 0; + + for (int s = 0; s < sampleCount; s++) + { + /* Multiply previous */ + v1 = (((inSamples[i] + inSamples[s]) * (coefsIn[i] + coefsIn[1])) + ((inSamples[i] + inSamples[s + 1]) * (coefsIn[i] + coefsIn[0]))); + /* Evaluate from real sample */ + v2 = (pcmInOut[s + 2] << 11) - v1; + /* Round to nearest sample */ + v3 = (v2 > 0) ? (int)((double)v2 / (1 << scale[i]) / 2048 + 0.4999999f) : (int)((double)v2 / (1 << scale[i]) / 2048 - 0.4999999f); + + /* Clamp sample and set index */ + if (v3 < -8) + { + if (index < (v3 = -8 - v3)) + index = v3; + v3 = -8; + } + else if (v3 > 7) + { + if (index < (v3 -= 7)) + index = v3; + v3 = 7; + } + + /* Store result */ + outSamples[i + s] = v3; + + /* Round and expand */ + v1 = (v1 + ((v3 * (1 << scale[i])) << 11) + 1024) >> 11; + /* Clamp and store */ + inSamples[i + (s + 2)] = v2 = (v1 >= 32767) ? 32767 : (v1 <= -32768) ? -32768 : v1; + /* Accumulate distance */ + v3 = pcmInOut[s + 2] - v2; + distAccum[i] += v3 * (double)v3; + } + + for (int x = index + 8; x > 256; x >>= 1) + if (++scale[i] >= 12) + scale[i] = 11; + } while ((scale[i] < 12) && (index > 1)); + } + + double min = double.MaxValue; + for (int i = 0; i < 8; i++) + { + if (distAccum[i] < min) + { + min = distAccum[i]; + bestIndex = i; + } + } + + /* Write converted samples */ + for (int s = 0; s < sampleCount; s++) + pcmInOut[s + 2] = (short)(inSamples[bestIndex] + inSamples[s + 2]); + + /* Write ps */ + adpcmOut[0] = (byte)((bestIndex << 4) | (scale[bestIndex] & 0xF)); + + /* Zero remaining samples */ + for (int s = sampleCount; s < 14; s++) + outSamples[bestIndex + s] = 0; + + /* Write output samples */ + for (int y = 0; y < 7; y++) + { + adpcmOut[y + 1] = (byte)((outSamples[bestIndex] + outSamples[y * 2] << 4) | (outSamples[bestIndex] + outSamples[y * 2 + 1] & 0xF)); + } + } + + public static void EncodeFrame(short[] src, byte[] dst, short[] coefs, byte one) + { + coefs = new short[0 + 2]; + DSPEncodeFrame(src, 14, dst, coefs); + } + #endregion + + #region DSP-ADPCM Decode + + #region Method 1 + public static int DivideByRoundUp(int dividend, int divisor) + { + return (dividend + divisor - 1) / divisor; + } + + public static sbyte GetHighNibble(byte value) + { + return (sbyte)((value >> 4) & 0xF); + } + + public static sbyte GetLowNibble(byte value) + { + return (sbyte)((value) & 0xF); + } + + public static short Clamp16(int value) + { + if (value > Max) + return Max; + if (value < Min) + return Min; + return (short)value; + } + + public static void Decode(Span src, Span dst, ref DSPADPCMInfo cxt, uint samples) + { + short hist1 = cxt.yn1; + short hist2 = cxt.yn2; + short[] coefs = cxt.coef; + int srcIndex = 0; + int dstIndex = 0; + + int frameCount = DivideByRoundUp((int)samples, DSPADPCMConstants.SamplesPerFrame); + int samplesRemaining = (int)samples; + + for (int i = 0; i < frameCount; i++) + { + int predictor = GetHighNibble(src[srcIndex]) & 0x7; + int scale = 1 << GetLowNibble(src[srcIndex++]); + short coef1 = coefs[predictor * 2]; + short coef2 = coefs[predictor * 2 + 1]; + + int samplesToRead = Math.Min(DSPADPCMConstants.SamplesPerFrame, samplesRemaining); + + // Get bits per byte + byte bits = src[srcIndex++]; + + for (int s = 0; s < samplesToRead; s++) + { + int sample = (s % 2) == 0 ? GetHighNibble(bits) : GetLowNibble(bits); + sample = sample >= 8 ? sample - 16 : sample; + sample = (((scale * sample) << 11) + 1024 + ((coef1 * hist1) + (coef2 * hist2))) >> 11; + short finalSample = Clamp16(sample); + + hist2 = hist1; + hist1 = finalSample; + + //if (samplesToRead <= 14) { samplesToRead = 0; } + dst[dstIndex++] = finalSample; + if (dstIndex >= samplesToRead) break; + } + + samplesRemaining -= samplesToRead; + } + } + + public static void GetLoopContext(Span src, ref DSPADPCMInfo cxt, uint samples) + { + short hist1 = cxt.yn1; + short hist2 = cxt.yn2; + short[] coefs = cxt.coef; + int srcIndex = 0; + byte ps = 0; + + int frameCount = DivideByRoundUp((int)samples, DSPADPCMConstants.SamplesPerFrame); + int samplesRemaining = (int)samples; + + for (int i = 0; i < frameCount; i++) + { + ps = src[srcIndex]; + int predictor = GetHighNibble(src[srcIndex]) & 0x7; + int scale = 1 << GetLowNibble(src[srcIndex++]); + short coef1 = coefs[predictor * 2]; + short coef2 = coefs[predictor * 2 + 1]; + + int samplesToRead = Math.Min(DSPADPCMConstants.SamplesPerFrame, samplesRemaining); + + for (int s = 0; s < samplesToRead; s++) + { + int sample = s % 2 == 0 ? GetHighNibble(src[srcIndex]) : GetLowNibble(src[srcIndex++]); + sample = sample >= 8 ? sample - 16 : sample; + sample = (((scale * sample) << 11) + 1024 + (coef1 * hist1 + coef2 * hist2)) >> 11; + short finalSample = Clamp16(sample); + + hist2 = hist1; + hist1 = finalSample; + } + samplesRemaining -= samplesToRead; + } + + cxt.loop_pred_scale = ps; + cxt.loop_yn1 = hist1; + cxt.loop_yn2 = hist2; + } + #endregion + + #region Method 2 + + public class PlayConfigType + { + int config_set; /* some of the mods below are set */ + + /* modifiers */ + int play_forever; + int ignore_loop; + int force_loop; + int really_force_loop; + int ignore_fade; + + /* processing */ + double loop_count; + int pad_begin; + int trim_begin; + int body_time; + int trim_end; + double fade_delay; /* not in samples for backwards compatibility */ + double fade_time; + int pad_end; + + double pad_begin_s; + double trim_begin_s; + double body_time_s; + double trim_end_s; + //double fade_delay_s; + //double fade_time_s; + double pad_end_s; + + /* internal flags */ + int pad_begin_set; + int trim_begin_set; + int body_time_set; + int loop_count_set; + int trim_end_set; + int fade_delay_set; + int fade_time_set; + int pad_end_set; + + /* for lack of a better place... */ + int is_txtp; + int is_mini_txtp; + + } + + + public class PlayStateType + { + int input_channels; + int output_channels; + + int pad_begin_duration; + int pad_begin_left; + int trim_begin_duration; + int trim_begin_left; + int body_duration; + int fade_duration; + int fade_left; + int fade_start; + int pad_end_duration; + //int pad_end_left; + int pad_end_start; + + int play_duration; /* total samples that the stream lasts (after applying all config) */ + int play_position; /* absolute sample where stream is */ + + } + + public class Stream + { + /* basic config */ + int num_samples; /* the actual max number of samples */ + int sample_rate; /* sample rate in Hz */ + public int channels; /* number of channels */ + CodecType coding_type; /* type of encoding */ + LayoutType layout_type; /* type of layout */ + MetaType meta_type; /* type of metadata */ + + /* loopin config */ + int loop_flag; /* is this stream looped? */ + int loop_start_sample; /* first sample of the loop (included in the loop) */ + int loop_end_sample; /* last sample of the loop (not included in the loop) */ + + /* layouts/block config */ + int interleave_block_size; /* interleave, or block/frame size (depending on the codec) */ + int interleave_first_block_size; /* different interleave for first block */ + int interleave_first_skip; /* data skipped before interleave first (needed to skip other channels) */ + int interleave_last_block_size; /* smaller interleave for last block */ + int frame_size; /* for codecs with configurable size */ + + /* subsong config */ + int num_streams; /* for multi-stream formats (0=not set/one stream, 1=one stream) */ + int stream_index; /* selected subsong (also 1-based) */ + int stream_size; /* info to properly calculate bitrate in case of subsongs */ + char[] stream_name = new char[255]; /* name of the current stream (info), if the file stores it and it's filled */ + + /* mapping config (info for plugins) */ + uint channel_layout; /* order: FL FR FC LFE BL BR FLC FRC BC SL SR etc (WAVEFORMATEX flags where FL=lowest bit set) */ + + /* other config */ + int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */ + + + /* layout/block state */ + int full_block_size; /* actual data size of an entire block (ie. may be fixed, include padding/headers, etc) */ + int current_sample; /* sample point within the file (for loop detection) */ + int samples_into_block; /* number of samples into the current block/interleave/segment/etc */ + int current_block_offset; /* start of this block (offset of block header) */ + int current_block_size; /* size in usable bytes of the block we're in now (used to calculate num_samples per block) */ + int current_block_samples; /* size in samples of the block we're in now (used over current_block_size if possible) */ + int next_block_offset; /* offset of header of the next block */ + + /* loop state (saved when loop is hit to restore later) */ + int loop_current_sample; /* saved from current_sample (same as loop_start_sample, but more state-like) */ + int loop_samples_into_block;/* saved from samples_into_block */ + int loop_block_offset; /* saved from current_block_offset */ + int loop_block_size; /* saved from current_block_size */ + int loop_block_samples; /* saved from current_block_samples */ + int loop_next_block_offset; /* saved from next_block_offset */ + int hit_loop; /* save config when loop is hit, but first time only */ + + + /* decoder config/state */ + int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */ + int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to them */ + int ws_output_size; /* WS ADPCM: output bytes for this block */ + + + /* main state */ + public Channel[] ch; /* array of channels */ + Channel start_ch; /* shallow copy of channels as they were at the beginning of the stream (for resets) */ + Channel loop_ch; /* shallow copy of channels as they were at the loop point (for loops) */ + IntPtr start_vgmstream; /* shallow copy of the VGMSTREAM as it was at the beginning of the stream (for resets) */ + + IntPtr mixing_data; /* state for mixing effects */ + + /* Optional data the codec needs for the whole stream. This is for codecs too + * different from vgmstream's structure to be reasonably shoehorned. + * Note also that support must be added for resetting, looping and + * closing for every codec that uses this, as it will not be handled. */ + IntPtr codec_data; + /* Same, for special layouts. layout_data + codec_data may exist at the same time. */ + IntPtr layout_data; + + + /* play config/state */ + int config_enabled; /* config can be used */ + PlayConfigType config; /* player config (applied over decoding) */ + PlayStateType pstate; /* player state (applied over decoding) */ + int loop_count; /* counter of complete loops (1=looped once) */ + int loop_target; /* max loops before continuing with the stream end (loops forever if not set) */ + short[] tmpbuf; /* garbage buffer used for seeking/trimming */ + int tmpbuf_size; /* for all channels (samples = tmpbuf_size / channels) */ + + } + + /* read from a file, returns number of bytes read */ + public static int read_streamfile(Span dst, int offset, int length, StreamFile sf) + { + return read_streamfile(dst, offset, length, sf); + } + + /* return file size */ + public static int get_streamfile_size(StreamFile sf) + { + return get_streamfile_size(sf); + } + + public class StreamFile + { + + /* read 'length' data at 'offset' to 'dst' */ + static int read(Span dst, int offset, int length, StreamFile[] sf) + { + return read(dst, offset, length, sf); + } + + /* get max offset */ + static int get_size(StreamFile[] sf) + { + return get_size(sf); + } + + //todo: DO NOT USE, NOT RESET PROPERLY (remove?) + static int get_offset(StreamFile[] sf) + { + return get_offset(sf); + } + + /* copy current filename to name buf */ + static void get_name(string name, int name_size, StreamFile[] sf) + { + sf.SetValue(name, name_size); + } + + /* open another streamfile from filename */ + public StreamFile() + { + string filename; + int buf_size; + StreamFile[] sf; + } + + /* free current STREAMFILE */ + //void (* close) (struct _StreamFile sf); + + /* Substream selection for formats with subsongs. + * Not ideal here, but it was the simplest way to pass to all init_vgmstream_x functions. */ + int stream_index; /* 0=default/auto (first), 1=first, N=Nth */ + + } + + public class g72x_state + { + long yl; /* Locked or steady state step size multiplier. */ + short yu; /* Unlocked or non-steady state step size multiplier. */ + short dms; /* Short term energy estimate. */ + short dml; /* Long term energy estimate. */ + short ap; /* Linear weighting coefficient of 'yl' and 'yu'. */ + + short[] a = new short[2]; /* Coefficients of pole portion of prediction filter. */ + short[] b = new short[6]; /* Coefficients of zero portion of prediction filter. */ + short[] pk = new short[2]; /* + * Signs of previous two samples of a partially + * reconstructed signal. + */ + short[] dq = new short[6]; /* + * Previous 6 samples of the quantized difference + * signal represented in an internal floating point + * format. + */ + short[] sr = new short[2]; /* + * Previous 2 samples of the quantized difference + * signal represented in an internal floating point + * format. + */ + char td; /* delayed tone detect, new in 1988 version */ + }; + + public class Channel + { + public StreamFile streamfile = new StreamFile(); /* file used by this channel */ + public long channel_start_offset; /* where data for this channel begins */ + public long offset; /* current location in the file */ + + public int frame_header_offset; /* offset of the current frame header (for WS) */ + public int samples_left_in_frame; /* for WS */ + + /* format specific */ + + /* adpcm */ + public short[] adpcm_coef = new short[16]; /* formats with decode coefficients built in (DSP, some ADX) */ + public int[] adpcm_coef_3by32 = new int[0x60]; /* Level-5 0x555 */ + public short[] vadpcm_coefs = new short[8 * 2 * 8]; /* VADPCM: max 8 groups * max 2 order * fixed 8 subframe coefs */ + public short adpcm_history1_16; /* previous sample */ + public int adpcm_history1_32; + + public short adpcm_history2_16; /* previous previous sample */ + public int adpcm_history2_32; + + public short adpcm_history3_16; + public int adpcm_history3_32; + + public short adpcm_history4_16; + public int adpcm_history4_32; + + + //double adpcm_history1_double; + //double adpcm_history2_double; + + public int adpcm_step_index; /* for IMA */ + public int adpcm_scale; /* for MS ADPCM */ + + /* state for G.721 decoder, sort of big but we might as well keep it around */ + public g72x_state g72x_state = new g72x_state(); + + /* ADX encryption */ + public int adx_channels; + public short adx_xor; + public short adx_mult; + public short adx_add; + + }; + + public static int[] nibble_to_int = new int[16] {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1}; + + public static int get_nibble_signed(byte n, int upper) + { + /*return ((n&0x70)-(n&0x80))>>4;*/ + return nibble_to_int[(n >> (upper != 0 ? 4 : 0)) & 0x0f]; + } + + public static int get_high_nibble_signed(byte n) + { + /*return ((n&0x70)-(n&0x80))>>4;*/ + return nibble_to_int[n >> 4]; + } + + public static int get_low_nibble_signed(byte n) + { + /*return (n&7)-(n&8);*/ + return nibble_to_int[n & 0xf]; + } + + public static int clamp16(int val) + { + if (val > 32767) return 32767; + else if (val < -32768) return -32768; + else return val; + } + + public static void decode_ngc_dsp(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do) + { + byte[] frame = new byte[0x08] { 0,0,0,0,0,0,0,0 }; + int frame_offset; + int i, frames_in, sample_count = 0; + int bytes_per_frame, samples_per_frame; + int coef_index, scale, coef1, coef2; + int hist1 = stream.adpcm_history1_16; + int hist2 = stream.adpcm_history2_16; + + + /* external interleave (fixed size), mono */ + bytes_per_frame = 0x08; + samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 14 */ + frames_in = first_sample / samples_per_frame; + first_sample = first_sample % samples_per_frame; + + /* parse frame header */ + frame_offset = (int)((stream.offset + bytes_per_frame) * frames_in); + read_streamfile(frame, frame_offset, bytes_per_frame, stream.streamfile); /* ignore EOF errors */ + scale = 1 << ((frame[0] >> 0) & 0xf); + coef_index = (frame[0] >> 4) & 0xf; + + if (coef_index >= 8) { Debug.WriteLine($"DSP: incorrect coefs at %x\n", (uint)frame_offset); } + //if (coef_index > 8) //todo not correctly clamped in original decoder? + // coef_index = 8; + + coef1 = stream.adpcm_coef[coef_index * 2 + 0]; + coef2 = stream.adpcm_coef[coef_index * 2 + 1]; + + + /* decode nibbles */ + for (i = first_sample; i < first_sample + samples_to_do; i++) + { + int sample = 0; + byte nibbles = frame[0x01 + i / 2]; + + sample = (i & 1) != 0 ? /* high nibble first */ + get_low_nibble_signed(nibbles) : + get_high_nibble_signed(nibbles); + sample = ((sample * scale) << 11); + sample = (sample + 1024 + coef1 * hist1 + coef2 * hist2) >> 11; + sample = clamp16(sample); + + outbuf[sample_count] = sample; + sample_count += channelspacing; + + hist2 = hist1; + hist1 = sample; + } + + stream.adpcm_history1_16 = (short)hist1; + stream.adpcm_history2_16 = (short)hist2; + } + + + /* read from memory rather than a file */ + public static void decode_ngc_dsp_subint_internal(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do, Span frame) + { + int i, sample_count = 0; + int bytes_per_frame, samples_per_frame; + int coef_index, scale, coef1, coef2; + int hist1 = stream.adpcm_history1_16; + int hist2 = stream.adpcm_history2_16; + + + /* external interleave (fixed size), mono */ + bytes_per_frame = 0x08; + samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 14 */ + first_sample = first_sample % samples_per_frame; + if (samples_to_do > samples_per_frame) { Debug.WriteLine($"DSP: layout error, too many samples\n"); } + + /* parse frame header */ + scale = 1 << ((frame[0] >> 0) & 0xf); + coef_index = (frame[0] >> 4) & 0xf; + + if (coef_index >= 8) { Debug.WriteLine($"DSP: incorrect coefs\n"); } + //if (coef_index > 8) //todo not correctly clamped in original decoder? + // coef_index = 8; + + coef1 = stream.adpcm_coef[coef_index * 2 + 0]; + coef2 = stream.adpcm_coef[coef_index * 2 + 1]; + + for (i = first_sample; i < first_sample + samples_to_do; i++) + { + int sample = 0; + byte nibbles = frame[0x01 + i / 2]; + + sample = (i & 1) != 0 ? + get_low_nibble_signed(nibbles) : + get_high_nibble_signed(nibbles); + sample = ((sample * scale) << 11); + sample = (sample + 1024 + coef1 * hist1 + coef2 * hist2) >> 11; + sample = clamp16(sample); + + outbuf[sample_count] = sample; + sample_count += channelspacing; + + hist2 = hist1; + hist1 = sample; + } + + stream.adpcm_history1_16 = (short)hist1; + stream.adpcm_history2_16 = (short)hist2; + } + + private static sbyte read_8bit(int offset, StreamFile sf) + { + byte[] buf = new byte[1]; + + if (read_streamfile(buf, offset, 1, sf) != 1) return -1; + return (sbyte)buf[0]; + } + + /* decode DSP with byte-interleaved frames (ex. 0x08: 1122112211221122) */ + public static void decode_ngc_dsp_subint(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do, int channel, int interleave) + { + byte[] frame = new byte[0x08]; + int i; + int frames_in = first_sample / 14; + + for (i = 0; i < 0x08; i++) + { + /* base + current frame + subint section + subint byte + channel adjust */ + frame[i] = (byte)read_8bit( + (int)((stream.offset + + frames_in) * (0x08 * channelspacing) + + i / interleave * interleave * channelspacing + + i % interleave + + interleave * channel), stream.streamfile); + } + + decode_ngc_dsp_subint_internal(stream, outbuf, channelspacing, first_sample, samples_to_do, frame); + } + + + /* + * The original DSP spec uses nibble counts for loop points, and some + * variants don't have a proper sample count, so we (who are interested + * in sample counts) need to do this conversion occasionally. + */ + public static int dsp_nibbles_to_samples(int nibbles) + { + int whole_frames = nibbles / 16; + int remainder = nibbles % 16; + + if (remainder > 0) return whole_frames * 14 + remainder - 2; + else return whole_frames * 14; + } + + public static int dsp_bytes_to_samples(int bytes, int channels) + { + if (channels <= 0) return 0; + return bytes / channels / 8 * 14; + } + + /* host endian independent multi-byte integer reading */ + public static short get_16bitBE(Span p) + { + return (short)(((ushort)p[0] << 8) | ((ushort)p[1])); + } + + public static short get_16bitLE(Span p) + { + return (short)(((ushort)p[0]) | ((ushort)p[1] << 8)); + } + + public static short read_16bitLE(int offset, StreamFile sf) + { + byte[] buf = new byte[2]; + + if (read_streamfile(buf, offset, 2, sf) != 2) return -1; + return get_16bitLE(buf); + } + public static short read_16bitBE(int offset, StreamFile sf) + { + byte[] buf = new byte[2]; + + if (read_streamfile(buf, offset, 2, sf) != 2) return -1; + return get_16bitBE(buf); + } + + /* reads DSP coefs built in the streamfile */ + public static void dsp_read_coefs_be(Stream vgmstream, StreamFile streamFile, int offset, int spacing) + { + dsp_read_coefs(vgmstream, streamFile, offset, spacing, 1); + } + public static void dsp_read_coefs_le(Stream vgmstream, StreamFile streamFile, int offset, int spacing) + { + dsp_read_coefs(vgmstream, streamFile, offset, spacing, 0); + } + public static void dsp_read_coefs(Stream vgmstream, StreamFile streamFile, int offset, int spacing, int be) + { + int ch, i; + /* get ADPCM coefs */ + for (ch = 0; ch < vgmstream.channels; ch++) + { + for (i = 0; i < 16; i++) + { + vgmstream.ch[ch].adpcm_coef[i] = be != 0 ? + read_16bitBE(offset + ch * spacing + i * 2, streamFile) : + read_16bitLE(offset + ch * spacing + i * 2, streamFile); + } + } + } + + /* reads DSP initial hist built in the streamfile */ + public static void dsp_read_hist_be(Stream vgmstream, StreamFile streamFile, int offset, int spacing) + { + dsp_read_hist(vgmstream, streamFile, offset, spacing, 1); + } + public static void dsp_read_hist_le(Stream vgmstream, StreamFile streamFile, int offset, int spacing) + { + dsp_read_hist(vgmstream, streamFile, offset, spacing, 0); + } + public static void dsp_read_hist(Stream vgmstream, StreamFile streamFile, int offset, int spacing, int be) + { + int ch; + /* get ADPCM hist */ + for (ch = 0; ch < vgmstream.channels; ch++) + { + vgmstream.ch[ch].adpcm_history1_16 = be != 0 ? + read_16bitBE(offset + ch * spacing + 0 * 2, streamFile) : + read_16bitLE(offset + ch * spacing + 0 * 2, streamFile); ; + vgmstream.ch[ch].adpcm_history2_16 = be != 0 ? + read_16bitBE(offset + ch * spacing + 1 * 2, streamFile) : + read_16bitLE(offset + ch * spacing + 1 * 2, streamFile); ; + } + } + + + #endregion + + #endregion + + #region DSP-ADPCM Math + public static uint GetBytesForADPCMBuffer(uint samples) + { + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + if ((samples % DSPADPCMConstants.SamplesPerFrame) == frames) + frames++; + + return frames * DSPADPCMConstants.BytesPerFrame; + } + + public static uint GetBytesForADPCMSamples(uint samples) + { + uint extraBytes = 0; + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + uint extraSamples = (samples % DSPADPCMConstants.SamplesPerFrame); + + if (extraSamples == frames) + { + extraBytes = (extraSamples / 2) + (extraSamples % 2) + 1; + } + + return DSPADPCMConstants.BytesPerFrame * frames + extraBytes; + } + + public static uint GetBytesForPCMBuffer(uint samples) + { + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + if ((samples % DSPADPCMConstants.SamplesPerFrame) == frames) + frames++; + + return frames * DSPADPCMConstants.SamplesPerFrame * sizeof(int); + } + + public static uint GetBytesForPCMSamples(uint samples) + { + return samples * sizeof(int); + } + + public static uint GetNibbleAddress(uint samples) + { + int frames = (int)(samples / DSPADPCMConstants.SamplesPerFrame); + int extraSamples = (int)(samples % DSPADPCMConstants.SamplesPerFrame); + + return (uint)(DSPADPCMConstants.NibblesPerFrame * frames + extraSamples + 2); + } + + public static uint GetNibblesForNSamples(uint samples) + { + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + uint extraSamples = (samples % DSPADPCMConstants.SamplesPerFrame); + uint extraNibbles = extraSamples == 0 ? 0 : extraSamples + 2; + + return DSPADPCMConstants.NibblesPerFrame * frames + extraNibbles; + } + + public static uint GetSampleForADPCMNibble(uint nibble) + { + uint frames = nibble / DSPADPCMConstants.NibblesPerFrame; + uint extraNibbles = (nibble % DSPADPCMConstants.NibblesPerFrame); + uint samples = DSPADPCMConstants.SamplesPerFrame * frames; + + return samples + extraNibbles - 2; + } + #endregion + } +} diff --git a/VG Music Studio - Core/Codec/LayoutEnums.cs b/VG Music Studio - Core/Codec/LayoutEnums.cs new file mode 100644 index 0000000..f241948 --- /dev/null +++ b/VG Music Studio - Core/Codec/LayoutEnums.cs @@ -0,0 +1,59 @@ +namespace Kermalis.VGMusicStudio.Core.Codec; + +/* This code has been copied directly from vgmstream.h in VGMStream's repository * + * and modified into C# code to work with VGMS. Link to its repository can be * + * found here: https://github.com/vgmstream/vgmstream */ +public enum LayoutType +{ + /* generic */ + layout_none, /* straight data */ + + /* interleave */ + layout_interleave, /* equal interleave throughout the stream */ + + /* headered blocks */ + layout_blocked_ast, + layout_blocked_halpst, + layout_blocked_xa, + layout_blocked_ea_schl, + layout_blocked_ea_1snh, + layout_blocked_caf, + layout_blocked_wsi, + layout_blocked_str_snds, + layout_blocked_ws_aud, + layout_blocked_matx, + layout_blocked_dec, + layout_blocked_xvas, + layout_blocked_vs, + layout_blocked_mul, + layout_blocked_gsb, + layout_blocked_thp, + layout_blocked_filp, + layout_blocked_ea_swvr, + layout_blocked_adm, + layout_blocked_bdsp, + layout_blocked_mxch, + layout_blocked_ivaud, /* GTA IV .ivaud blocks */ + layout_blocked_ps2_iab, + layout_blocked_vs_str, + layout_blocked_rws, + layout_blocked_hwas, + layout_blocked_ea_sns, /* newest Electronic Arts blocks, found in SNS/SNU/SPS/etc formats */ + layout_blocked_awc, /* Rockstar AWC */ + layout_blocked_vgs, /* Guitar Hero II (PS2) */ + layout_blocked_xwav, + layout_blocked_xvag_subsong, /* XVAG subsongs [God of War III (PS4)] */ + layout_blocked_ea_wve_au00, /* EA WVE au00 blocks */ + layout_blocked_ea_wve_ad10, /* EA WVE Ad10 blocks */ + layout_blocked_sthd, /* Dream Factory STHD */ + layout_blocked_h4m, /* H4M video */ + layout_blocked_xa_aiff, /* XA in AIFF files [Crusader: No Remorse (SAT), Road Rash (3DO)] */ + layout_blocked_vs_square, + layout_blocked_vid1, + layout_blocked_ubi_sce, + layout_blocked_tt_ad, + + /* otherwise odd */ + layout_segmented, /* song divided in segments (song sections) */ + layout_layered, /* song divided in layers (song channels) */ +} \ No newline at end of file diff --git a/VG Music Studio - Core/Codec/MetaEnums.cs b/VG Music Studio - Core/Codec/MetaEnums.cs new file mode 100644 index 0000000..e4cb65a --- /dev/null +++ b/VG Music Studio - Core/Codec/MetaEnums.cs @@ -0,0 +1,479 @@ +namespace Kermalis.VGMusicStudio.Core.Codec; + +/* This code has been copied directly from vgmstream.h in VGMStream's repository * + * and modified into C# code to work with VGMS. Link to its repository can be * + * found here: https://github.com/vgmstream/vgmstream */ +public enum MetaType +{ + meta_SILENCE, + + meta_DSP_STD, /* Nintendo standard GC ADPCM (DSP) header */ + meta_DSP_CSTR, /* Star Fox Assault "Cstr" */ + meta_DSP_RS03, /* Retro: Metroid Prime 2 "RS03" */ + meta_DSP_STM, /* Paper Mario 2 STM */ + meta_AGSC, /* Retro: Metroid Prime 2 title */ + meta_CSMP, /* Retro: Metroid Prime 3 (Wii), Donkey Kong Country Returns (Wii) */ + meta_RFRM, /* Retro: Donkey Kong Country Tropical Freeze (Wii U) */ + meta_DSP_MPDSP, /* Monopoly Party single header stereo */ + meta_DSP_JETTERS, /* Bomberman Jetters .dsp */ + meta_DSP_MSS, /* Free Radical GC games */ + meta_DSP_GCM, /* some of Traveller's Tales games */ + meta_DSP_STR, /* Conan .str files */ + meta_DSP_SADB, /* Procyon Studio Digtial Sound Elements DSP-ADPCM (Wii) .sad */ + meta_DSP_WSI, /* .wsi */ + meta_IDSP_TT, /* Traveller's Tales games */ + meta_DSP_WII_MUS, /* .mus */ + meta_DSP_WII_WSD, /* Phantom Brave (Wii) */ + meta_WII_NDP, /* Vertigo (Wii) */ + meta_DSP_YGO, /* Konami: Yu-Gi-Oh! The Falsebound Kingdom (NGC), Hikaru no Go 3 (NGC) */ + + meta_STRM, /* Nintendo/HAL Labs Nitro Soundmaker STRM */ + meta_RSTM, /* Nintendo/HAL Labs NW4R Soundmaker RSTM (Revolution Stream, similar to STRM) */ + meta_AFC, /* AFC */ + meta_AST, /* AST */ + meta_RWSD, /* Nintendo/HAL Labs NW4R Soundmaker single-stream RWSD */ + meta_RWAR, /* Nintendo/HAL Labs NW4R Soundmaker single-stream RWAR */ + meta_RWAV, /* Nintendo/HAL Labs NW4R Soundmaker contents of RWAR */ + meta_CWAV, /* Nintendo/HAL Labs NW4C Soundmaker contents of CWAR */ + meta_FWAV, /* Nintendo/HAL Labs NW4F Soundmaker contents of FWAR */ + meta_THP, /* THP movie files */ + meta_SWAV, + meta_NDS_RRDS, /* Ridge Racer DS */ + meta_WII_BNS, /* Wii BNS Banner Sound (similar to RSTM) */ + meta_WIIU_BTSND, /* Wii U Boot Sound */ + + meta_ADX_03, /* CRI ADX "type 03" */ + meta_ADX_04, /* CRI ADX "type 04" */ + meta_ADX_05, /* CRI ADX "type 05" */ + meta_AIX, /* CRI AIX */ + meta_AAX, /* CRI AAX */ + meta_UTF_DSP, /* CRI ADPCM_WII, like AAX with DSP */ + + meta_DTK, + meta_RSF, + meta_HALPST, /* HAL Labs HALPST */ + meta_GCSW, /* GCSW (PCM) */ + meta_CAF, /* tri-Crescendo CAF */ + meta_MYSPD, /* U-Sing .myspd */ + meta_HIS, /* Her Ineractive .his */ + meta_BNSF, /* Bandai Namco Sound Format */ + + meta_XA, /* CD-ROM XA */ + meta_ADS, + meta_NPS, + meta_RXWS, + meta_RAW_INT, + meta_EXST, + meta_SVAG_KCET, + meta_PS_HEADERLESS, /* headerless PS-ADPCM */ + meta_MIB_MIH, + meta_PS2_MIC, /* KOEI MIC File */ + meta_PS2_VAGi, /* VAGi Interleaved File */ + meta_PS2_VAGp, /* VAGp Mono File */ + meta_PS2_pGAV, /* VAGp with Little Endian Header */ + meta_PS2_VAGp_AAAP, /* Acclaim Austin Audio VAG header */ + meta_SEB, + meta_STR_WAV, /* Blitz Games STR+WAV files */ + meta_ILD, + meta_PS2_PNB, /* PsychoNauts Bgm File */ + meta_VPK, /* VPK Audio File */ + meta_PS2_BMDX, /* Beatmania thing */ + meta_PS2_IVB, /* Langrisser 3 IVB */ + meta_PS2_SND, /* some Might & Magics SSND header */ + meta_SVS, /* Square SVS */ + meta_XSS, /* Dino Crisis 3 */ + meta_SL3, /* Test Drive Unlimited */ + meta_HGC1, /* Knights of the Temple 2 */ + meta_AUS, /* Various Capcom games */ + meta_RWS, /* RenderWare games (only when using RW Audio middleware) */ + meta_FSB1, /* FMOD Sample Bank, version 1 */ + meta_FSB2, /* FMOD Sample Bank, version 2 */ + meta_FSB3, /* FMOD Sample Bank, version 3.0/3.1 */ + meta_FSB4, /* FMOD Sample Bank, version 4 */ + meta_FSB5, /* FMOD Sample Bank, version 5 */ + meta_RWX, /* Air Force Delta Storm (XBOX) */ + meta_XWB, /* Microsoft XACT framework (Xbox, X360, Windows) */ + meta_PS2_XA30, /* Driver - Parallel Lines (PS2) */ + meta_MUSC, /* Krome PS2 games */ + meta_MUSX, + meta_LEG, /* Legaia 2 [no header_id] */ + meta_FILP, /* Resident Evil - Dead Aim */ + meta_IKM, + meta_STER, + meta_BG00, /* Ibara, Mushihimesama */ + meta_PS2_RSTM, /* Midnight Club 3 */ + meta_PS2_KCES, /* Dance Dance Revolution */ + meta_HXD, + meta_VSV, + meta_SCD_PCM, /* Lunar - Eternal Blue */ + meta_PS2_PCM, /* Konami KCEJ East: Ephemeral Fantasia, Yu-Gi-Oh! The Duelists of the Roses, 7 Blades */ + meta_PS2_RKV, /* Legacy of Kain - Blood Omen 2 (PS2) */ + meta_PS2_VAS, /* Pro Baseball Spirits 5 */ + meta_PS2_ENTH, /* Enthusia */ + meta_SDT, /* Baldur's Gate - Dark Alliance */ + meta_NGC_TYDSP, /* Ty - The Tasmanian Tiger */ + meta_DC_STR, /* SEGA Stream Asset Builder */ + meta_DC_STR_V2, /* variant of SEGA Stream Asset Builder */ + meta_NGC_BH2PCM, /* Bio Hazard 2 */ + meta_SAP, + meta_DC_IDVI, /* Eldorado Gate */ + meta_KRAW, /* Geometry Wars - Galaxies */ + meta_PS2_OMU, /* PS2 Int file with Header */ + meta_PS2_XA2, /* XG3 Extreme-G Racing */ + meta_NUB, + meta_IDSP_NL, /* Mario Strikers Charged (Wii) */ + meta_IDSP_IE, /* Defencer (GC) */ + meta_SPT_SPD, /* Various (SPT+SPT DSP) */ + meta_ISH_ISD, /* Various (ISH+ISD DSP) */ + meta_GSP_GSB, /* Tecmo games (Super Swing Golf 1 & 2, Quamtum Theory) */ + meta_YDSP, /* WWE Day of Reckoning */ + meta_FFCC_STR, /* Final Fantasy: Crystal Chronicles */ + meta_UBI_JADE, /* Beyond Good & Evil, Rayman Raving Rabbids */ + meta_GCA, /* Metal Slug Anthology */ + meta_NGC_SSM, /* Golden Gashbell Full Power */ + meta_PS2_JOE, /* Wall-E / Pixar games */ + meta_NGC_YMF, /* WWE WrestleMania X8 */ + meta_SADL, + meta_PS2_CCC, /* Tokyo Xtreme Racer DRIFT 2 */ + meta_FAG, /* Jackie Chan - Stuntmaster */ + meta_PS2_MIHB, /* Merged MIH+MIB */ + meta_NGC_PDT, /* Mario Party 6 */ + meta_DC_ASD, /* Miss Moonligh */ + meta_NAOMI_SPSD, /* Guilty Gear X */ + meta_RSD, + meta_PS2_ASS, /* ASS */ + meta_SEG, /* Eragon */ + meta_NDS_STRM_FFTA2, /* Final Fantasy Tactics A2 */ + meta_KNON, + meta_ZWDSP, /* Zack and Wiki */ + meta_VGS, /* Guitar Hero Encore - Rocks the 80s */ + meta_DCS_WAV, + meta_SMP, + meta_WII_SNG, /* Excite Trucks */ + meta_MUL, + meta_SAT_BAKA, /* Crypt Killer */ + meta_VSF, + meta_PS2_VSF_TTA, /* Tiny Toon Adventures: Defenders of the Universe */ + meta_ADS_MIDWAY, + meta_PS2_SPS, /* Ape Escape 2 */ + meta_PS2_XA2_RRP, /* RC Revenge Pro */ + meta_NGC_DSP_KONAMI, /* Konami DSP header, found in various games */ + meta_UBI_CKD, /* Ubisoft CKD RIFF header (Rayman Origins Wii) */ + meta_RAW_WAVM, + meta_WVS, + meta_XBOX_MATX, /* XBOX MATX */ + meta_XMU, + meta_XVAS, + meta_EA_SCHL, /* Electronic Arts SCHl with variable header */ + meta_EA_SCHL_fixed, /* Electronic Arts SCHl with fixed header */ + meta_EA_BNK, /* Electronic Arts BNK */ + meta_EA_1SNH, /* Electronic Arts 1SNh/EACS */ + meta_EA_EACS, + meta_RAW_PCM, + meta_GENH, /* generic header */ + meta_AIFC, /* Audio Interchange File Format AIFF-C */ + meta_AIFF, /* Audio Interchange File Format */ + meta_STR_SNDS, /* .str with SNDS blocks and SHDR header */ + meta_WS_AUD, /* Westwood Studios .aud */ + meta_WS_AUD_old, /* Westwood Studios .aud, old style */ + meta_RIFF_WAVE, /* RIFF, for WAVs */ + meta_RIFF_WAVE_POS, /* .wav + .pos for looping (Ys Complete PC) */ + meta_RIFF_WAVE_labl, /* RIFF w/ loop Markers in LIST-adtl-labl */ + meta_RIFF_WAVE_smpl, /* RIFF w/ loop data in smpl chunk */ + meta_RIFF_WAVE_wsmp, /* RIFF w/ loop data in wsmp chunk */ + meta_RIFF_WAVE_MWV, /* .mwv RIFF w/ loop data in ctrl chunk pflt */ + meta_RIFX_WAVE, /* RIFX, for big-endian WAVs */ + meta_RIFX_WAVE_smpl, /* RIFX w/ loop data in smpl chunk */ + meta_XNB, /* XNA Game Studio 4.0 */ + meta_PC_MXST, /* Lego Island MxSt */ + meta_SAB, /* Worms 4 Mayhem SAB+SOB file */ + meta_NWA, /* Visual Art's NWA */ + meta_NWA_NWAINFOINI, /* Visual Art's NWA w/ NWAINFO.INI for looping */ + meta_NWA_GAMEEXEINI, /* Visual Art's NWA w/ Gameexe.ini for looping */ + meta_SAT_DVI, /* Konami KCE Nagoya DVI (SAT games) */ + meta_DC_KCEY, /* Konami KCE Yokohama KCEYCOMP (DC games) */ + meta_ACM, /* InterPlay ACM header */ + meta_MUS_ACM, /* MUS playlist of InterPlay ACM files */ + meta_DEC, /* Falcom PC games (Xanadu Next, Gurumin) */ + meta_VS, /* Men in Black .vs */ + meta_FFXI_BGW, /* FFXI (PC) BGW */ + meta_FFXI_SPW, /* FFXI (PC) SPW */ + meta_STS, + meta_PS2_P2BT, /* Pop'n'Music 7 Audio File */ + meta_PS2_GBTS, /* Pop'n'Music 9 Audio File */ + meta_NGC_DSP_IADP, /* Gamecube Interleave DSP */ + meta_PS2_TK5, /* Tekken 5 Stream Files */ + meta_PS2_MCG, /* Gunvari MCG Files (was name .GCM on disk) */ + meta_ZSD, /* Dragon Booster ZSD */ + meta_REDSPARK, /* "RedSpark" RSD (MadWorld) */ + meta_IVAUD, /* .ivaud GTA IV */ + meta_NDS_HWAS, /* Spider-Man 3, Tony Hawk's Downhill Jam, possibly more... */ + meta_NGC_LPS, /* Rave Master (Groove Adventure Rave)(GC) */ + meta_NAOMI_ADPCM, /* NAOMI/NAOMI2 ARcade games */ + meta_SD9, /* beatmaniaIIDX16 - EMPRESS (Arcade) */ + meta_2DX9, /* beatmaniaIIDX16 - EMPRESS (Arcade) */ + meta_PS2_VGV, /* Rune: Viking Warlord */ + meta_GCUB, + meta_MAXIS_XA, /* Sim City 3000 (PC) */ + meta_NGC_SCK_DSP, /* Scorpion King (NGC) */ + meta_CAFF, /* iPhone .caf */ + meta_EXAKT_SC, /* Activision EXAKT .SC (PS2) */ + meta_WII_WAS, /* DiRT 2 (WII) */ + meta_PONA_3DO, /* Policenauts (3DO) */ + meta_PONA_PSX, /* Policenauts (PSX) */ + meta_XBOX_HLWAV, /* Half Life 2 (XBOX) */ + meta_AST_MV, + meta_AST_MMV, + meta_DMSG, /* Nightcaster II - Equinox (XBOX) */ + meta_NGC_DSP_AAAP, /* Turok: Evolution (NGC), Vexx (NGC) */ + meta_PS2_WB, /* Shooting Love. ~TRIZEAL~ */ + meta_S14, /* raw Siren 14, 24kbit mono */ + meta_SSS, /* raw Siren 14, 48kbit stereo */ + meta_PS2_GCM, /* NamCollection */ + meta_PS2_SMPL, /* Homura */ + meta_PS2_MSA, /* Psyvariar -Complete Edition- */ + meta_PS2_VOI, /* RAW Danger (Zettaizetsumei Toshi 2 - Itetsuita Kiokutachi) [PS2] */ + meta_P3D, /* Prototype P3D */ + meta_PS2_TK1, /* Tekken (NamCollection) */ + meta_NGC_RKV, /* Legacy of Kain - Blood Omen 2 (GC) */ + meta_DSP_DDSP, /* Various (2 dsp files stuck together */ + meta_NGC_DSP_MPDS, /* Big Air Freestyle, Terminator 3 */ + meta_DSP_STR_IG, /* Micro Machines, Superman Superman: Shadow of Apokolis */ + meta_EA_SWVR, /* Future Cop L.A.P.D., Freekstyle */ + meta_PS2_B1S, /* 7 Wonders of the ancient world */ + meta_PS2_WAD, /* The golden Compass */ + meta_DSP_XIII, /* XIII, possibly more (Ubisoft header???) */ + meta_DSP_CABELAS, /* Cabelas games */ + meta_PS2_ADM, /* Dragon Quest V (PS2) */ + meta_LPCM_SHADE, + meta_DSP_BDSP, /* Ah! My Goddess */ + meta_PS2_VMS, /* Autobahn Raser - Police Madness */ + meta_XAU, /* XPEC Entertainment (Beat Down (PS2 Xbox), Spectral Force Chronicle (PS2)) */ + meta_GH3_BAR, /* Guitar Hero III Mobile .bar */ + meta_FFW, /* Freedom Fighters [NGC] */ + meta_DSP_DSPW, /* Sengoku Basara 3 [WII] */ + meta_PS2_JSTM, /* Tantei Jinguji Saburo - Kind of Blue (PS2) */ + meta_SQEX_SCD, /* Square-Enix SCD */ + meta_NGC_NST_DSP, /* Animaniacs [NGC] */ + meta_BAF, /* Bizarre Creations (Blur, James Bond) */ + meta_XVAG, /* Ratchet & Clank Future: Quest for Booty (PS3) */ + meta_CPS, + meta_MSF, + meta_PS3_PAST, /* Bakugan Battle Brawlers (PS3) */ + meta_SGXD, /* Sony: Folklore, Genji, Tokyo Jungle (PS3), Brave Story, Kurohyo (PSP) */ + meta_WII_RAS, /* Donkey Kong Country Returns (Wii) */ + meta_SPM, + meta_VGS_PS, + meta_PS2_IAB, /* Ueki no Housoku - Taosu ze Robert Juudan!! (PS2) */ + meta_VS_STR, /* The Bouncer */ + meta_LSF_N1NJ4N, /* .lsf n1nj4n Fastlane Street Racing (iPhone) */ + meta_XWAV, + meta_RAW_SNDS, + meta_PS2_WMUS, /* The Warriors (PS2) */ + meta_HYPERSCAN_KVAG, /* Hyperscan KVAG/BVG */ + meta_IOS_PSND, /* Crash Bandicoot Nitro Kart 2 (iOS) */ + meta_BOS_ADP, + meta_QD_ADP, + meta_EB_SFX, /* Excitebots .sfx */ + meta_EB_SF0, /* Excitebots .sf0 */ + meta_MTAF, + meta_PS2_VAG1, /* Metal Gear Solid 3 VAG1 */ + meta_PS2_VAG2, /* Metal Gear Solid 3 VAG2 */ + meta_ALP, + meta_WPD, /* Shuffle! (PC) */ + meta_MN_STR, /* Mini Ninjas (PC/PS3/WII) */ + meta_MSS, /* Guerilla: ShellShock Nam '67 (PS2/Xbox), Killzone (PS2) */ + meta_PS2_HSF, /* Lowrider (PS2) */ + meta_IVAG, + meta_PS2_2PFS, /* Konami: Mahoromatic: Moetto - KiraKira Maid-San, GANTZ (PS2) */ + meta_PS2_VBK, /* Disney's Stitch - Experiment 626 */ + meta_OTM, /* Otomedius (Arcade) */ + meta_CSTM, /* Nintendo/HAL Labs NW4C Soundmaker CSTM (Century Stream) */ + meta_FSTM, /* Nintendo/HAL Labs NW4F Soundmaker FSTM (caFe? Stream) */ + meta_IDSP_NAMCO, + meta_KT_WIIBGM, /* Koei Tecmo WiiBGM */ + meta_KTSS, /* Koei Tecmo Nintendo Stream (KNS) */ + meta_MCA, /* Capcom MCA "MADP" */ + meta_XB3D_ADX, /* Xenoblade Chronicles 3D ADX */ + meta_HCA, /* CRI HCA */ + meta_SVAG_SNK, + meta_PS2_VDS_VDM, /* Graffiti Kingdom */ + meta_FFMPEG, + meta_FFMPEG_faulty, + meta_CXS, + meta_AKB, + meta_PASX, + meta_XMA_RIFF, + meta_ASTB, + meta_WWISE_RIFF, /* Audiokinetic Wwise RIFF/RIFX */ + meta_UBI_RAKI, /* Ubisoft RAKI header (Rayman Legends, Just Dance 2017) */ + meta_SXD, /* Sony SXD (Gravity Rush, Freedom Wars PSV) */ + meta_OGL, /* Shin'en Wii/WiiU (Jett Rocket (Wii), FAST Racing NEO (WiiU)) */ + meta_MC3, /* Paradigm games (T3 PS2, MX Rider PS2, MI: Operation Surma PS2) */ + meta_GHS, + meta_AAC_TRIACE, + meta_MTA2, + meta_NGC_ULW, /* Burnout 1 (GC only) */ + meta_XA_XA30, + meta_XA_04SW, + meta_TXTH, /* generic text header */ + meta_SK_AUD, /* Silicon Knights .AUD (Eternal Darkness GC) */ + meta_AHX, + meta_STM, /* Angel Studios/Rockstar San Diego Games */ + meta_BINK, /* RAD Game Tools BINK audio/video */ + meta_EA_SNU, /* Electronic Arts SNU (Dead Space) */ + meta_AWC, /* Rockstar AWC (GTA5, RDR) */ + meta_OPUS, /* Nintendo Opus [Lego City Undercover (Switch)] */ + meta_RAW_AL, + meta_PC_AST, /* Dead Rising (PC) */ + meta_NAAC, /* Namco AAC (3DS) */ + meta_UBI_SB, /* Ubisoft banks */ + meta_EZW, /* EZ2DJ (Arcade) EZWAV */ + meta_VXN, /* Gameloft mobile games */ + meta_EA_SNR_SNS, /* Electronic Arts SNR+SNS (Burnout Paradise) */ + meta_EA_SPS, /* Electronic Arts SPS (Burnout Crash) */ + meta_VID1, + meta_PC_FLX, /* Ultima IX PC */ + meta_MOGG, /* Harmonix Music Systems MOGG Vorbis */ + meta_OGG_VORBIS, /* Ogg Vorbis */ + meta_OGG_SLI, /* Ogg Vorbis file w/ companion .sli for looping */ + meta_OPUS_SLI, /* Ogg Opus file w/ companion .sli for looping */ + meta_OGG_SFL, /* Ogg Vorbis file w/ .sfl (RIFF SFPL) for looping */ + meta_OGG_KOVS, /* Ogg Vorbis with header and encryption (Koei Tecmo Games) */ + meta_OGG_encrypted, /* Ogg Vorbis with encryption */ + meta_KMA9, /* Koei Tecmo [Nobunaga no Yabou - Souzou (Vita)] */ + meta_XWC, /* Starbreeze games */ + meta_SQEX_SAB, /* Square-Enix newest middleware (sound) */ + meta_SQEX_MAB, /* Square-Enix newest middleware (music) */ + meta_WAF, /* KID WAF [Ever 17 (PC)] */ + meta_WAVE, /* EngineBlack games [Mighty Switch Force! (3DS)] */ + meta_WAVE_segmented, /* EngineBlack games, segmented [Shantae and the Pirate's Curse (PC)] */ + meta_SMV, /* Cho Aniki Zero (PSP) */ + meta_NXAP, /* Nex Entertainment games [Time Crisis 4 (PS3), Time Crisis Razing Storm (PS3)] */ + meta_EA_WVE_AU00, /* Electronic Arts PS movies [Future Cop - L.A.P.D. (PS), Supercross 2000 (PS)] */ + meta_EA_WVE_AD10, /* Electronic Arts PS movies [Wing Commander 3/4 (PS)] */ + meta_STHD, /* STHD .stx [Kakuto Chojin (Xbox)] */ + meta_MP4, /* MP4/AAC */ + meta_PCM_SRE, /* .PCM+SRE [Viewtiful Joe (PS2)] */ + meta_DSP_MCADPCM, /* Skyrim (Switch) */ + meta_UBI_LYN, /* Ubisoft LyN engine [The Adventures of Tintin (multi)] */ + meta_MSB_MSH, /* sfx companion of MIH+MIB */ + meta_TXTP, /* generic text playlist */ + meta_SMC_SMH, /* Wangan Midnight (System 246) */ + meta_PPST, /* PPST [Parappa the Rapper (PSP)] */ + meta_SPS_N1, + meta_UBI_BAO, /* Ubisoft BAO */ + meta_DSP_SWITCH_AUDIO, /* Gal Gun 2 (Switch) */ + meta_H4M, /* Hudson HVQM4 video [Resident Evil 0 (GC), Tales of Symphonia (GC)] */ + meta_ASF, /* Argonaut ASF [Croc 2 (PC)] */ + meta_XMD, /* Konami XMD [Silent Hill 4 (Xbox), Castlevania: Curse of Darkness (Xbox)] */ + meta_CKS, /* Cricket Audio stream [Part Time UFO (Android), Mega Man 1-6 (Android)] */ + meta_CKB, /* Cricket Audio bank [Fire Emblem Heroes (Android), Mega Man 1-6 (Android)] */ + meta_WV6, /* Gorilla Systems PC games */ + meta_WAVEBATCH, /* Firebrand Games */ + meta_HD3_BD3, /* Sony PS3 bank */ + meta_BNK_SONY, /* Sony Scream Tool bank */ + meta_SSCF, + meta_DSP_VAG, /* Penny-Punching Princess (Switch) sfx */ + meta_DSP_ITL, /* Charinko Hero (GC) */ + meta_A2M, /* Scooby-Doo! Unmasked (PS2) */ + meta_AHV, /* Headhunter (PS2) */ + meta_MSV, + meta_SDF, + meta_SVG, /* Hunter - The Reckoning - Wayward (PS2) */ + meta_VIS, /* AirForce Delta Strike (PS2) */ + meta_VAI, /* Ratatouille (GC) */ + meta_AIF_ASOBO, /* Ratatouille (PC) */ + meta_AO, /* Cloudphobia (PC) */ + meta_APC, /* MegaRace 3 (PC) */ + meta_WV2, /* Slave Zero (PC) */ + meta_XAU_KONAMI, /* Yu-Gi-Oh - The Dawn of Destiny (Xbox) */ + meta_DERF, /* Stupid Invaders (PC) */ + meta_SADF, + meta_UTK, + meta_NXA, + meta_ADPCM_CAPCOM, + meta_UE4OPUS, + meta_XWMA, + meta_VA3, /* DDR Supernova 2 AC */ + meta_XOPUS, + meta_VS_SQUARE, + meta_NWAV, + meta_XPCM, + meta_MSF_TAMASOFT, + meta_XPS_DAT, + meta_ZSND, + meta_DSP_ADPY, + meta_DSP_ADPX, + meta_OGG_OPUS, + meta_IMC, + meta_GIN, + meta_DSF, + meta_208, + meta_DSP_DS2, + meta_MUS_VC, + meta_STRM_ABYLIGHT, + meta_MSF_KONAMI, + meta_XWMA_KONAMI, + meta_9TAV, + meta_BWAV, + meta_RAD, + meta_SMACKER, + meta_MZRT, + meta_XAVS, + meta_PSF, + meta_DSP_ITL_i, + meta_IMA, + meta_XWV_VALVE, + meta_UBI_HX, + meta_BMP_KONAMI, + meta_ISB, + meta_XSSB, + meta_XMA_UE3, + meta_FWSE, + meta_FDA, + meta_TGC, + meta_KWB, + meta_LRMD, + meta_WWISE_FX, + meta_DIVA, + meta_IMUSE, + meta_KTSR, + meta_KAT, + meta_PCM_SUCCESS, + meta_ADP_KONAMI, + meta_SDRH, + meta_WADY, + meta_DSP_SQEX, + meta_DSP_WIIVOICE, + meta_SBK, + meta_DSP_WIIADPCM, + meta_DSP_CWAC, + meta_COMPRESSWAVE, + meta_KTAC, + meta_MJB_MJH, + meta_BSNF, + meta_TAC, + meta_IDSP_TOSE, + meta_DSP_KWA, + meta_OGV_3RDEYE, + meta_PIFF_TPCM, + meta_WXD_WXH, + meta_BNK_RELIC, + meta_XSH_XSD_XSS, + meta_PSB, + meta_LOPU_FB, + meta_LPCM_FB, + meta_WBK, + meta_WBK_NSLB, + meta_DSP_APEX, + meta_MPEG, + meta_SSPF, + meta_S3V, + meta_ESF, + meta_ADM3, + meta_TT_AD, + meta_SNDZ, + meta_VAB, + meta_BIGRP, +} \ No newline at end of file diff --git a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs index 4b1b83f..b9a2b44 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs @@ -1,10 +1,15 @@ -namespace Kermalis.VGMusicStudio.Core.NDS.DSE; +using Kermalis.VGMusicStudio.Core.Codec; +using Kermalis.VGMusicStudio.Core.Wii; +using System; + +namespace Kermalis.VGMusicStudio.Core.NDS.DSE; internal sealed class DSEChannel { public readonly byte Index; public DSETrack? Owner; + public string? SWDType; public EnvelopeState State; public byte RootKey; public byte Key; @@ -12,8 +17,8 @@ internal sealed class DSEChannel public sbyte Panpot; // Not necessary public ushort BaseTimer; public ushort Timer; - public uint NoteLength; - public byte Volume; + public uint NoteLength; + public byte Volume; private int _pos; private short _prevLeft; @@ -32,75 +37,118 @@ internal sealed class DSEChannel private byte _decay2; private byte _release; - // PCM8, PCM16, ADPCM - private SWD.SampleBlock _sample; + // PCM8, PCM16, ADPCM, DSP-ADPCM + private SWD.SampleBlock? _sample; // PCM8, PCM16 private int _dataOffset; // ADPCM - private ADPCMDecoder _adpcmDecoder; + private ADPCMDecoder? _adpcmDecoder; private short _adpcmLoopLastSample; private short _adpcmLoopStepIndex; + // DSP-ADPCM + //private DSPADPCM dspADPCM = new DSPADPCM(); + //private short[] _loopContext; + //private DSPADPCM _outputData; + //private DSPADPCM? _dspADPCM; + // PSG + private byte _psgDuty; + private int _psgCounter; - public DSEChannel(byte i) + public DSEChannel(byte i) { Index = i; } public bool StartPCM(SWD localswd, SWD masterswd, byte voice, int key, uint noteLength) { - SWD.IProgramInfo? programInfo = localswd.Programs.ProgramInfos[voice]; - if (programInfo is null) - { - return false; - } + if (localswd == null) { SWDType = masterswd.Type; } + else { SWDType = localswd.Type; } + + SWD.IProgramInfo programInfo; + if (localswd == null) { programInfo = masterswd.Programs!.ProgramInfos![voice]; } + else { programInfo = localswd.Programs!.ProgramInfos![voice]; } - for (int i = 0; i < programInfo.SplitEntries.Length; i++) - { - SWD.ISplitEntry split = programInfo.SplitEntries[i]; - if (key < split.LowKey || key > split.HighKey) - { - continue; - } + if (programInfo is null) + { + return false; + } - _sample = masterswd.Samples[split.SampleId]; - Key = (byte)key; - RootKey = split.SampleRootKey; - BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo.SampleRate); - if (_sample.WavInfo.SampleFormat == SampleFormat.ADPCM) - { - _adpcmDecoder = new ADPCMDecoder(_sample.Data); - } - //attackVolume = sample.WavInfo.AttackVolume == 0 ? split.AttackVolume : sample.WavInfo.AttackVolume; - //attack = sample.WavInfo.Attack == 0 ? split.Attack : sample.WavInfo.Attack; - //decay = sample.WavInfo.Decay == 0 ? split.Decay : sample.WavInfo.Decay; - //sustain = sample.WavInfo.Sustain == 0 ? split.Sustain : sample.WavInfo.Sustain; - //hold = sample.WavInfo.Hold == 0 ? split.Hold : sample.WavInfo.Hold; - //decay2 = sample.WavInfo.Decay2 == 0 ? split.Decay2 : sample.WavInfo.Decay2; - //release = sample.WavInfo.Release == 0 ? split.Release : sample.WavInfo.Release; - //attackVolume = split.AttackVolume == 0 ? sample.WavInfo.AttackVolume : split.AttackVolume; - //attack = split.Attack == 0 ? sample.WavInfo.Attack : split.Attack; - //decay = split.Decay == 0 ? sample.WavInfo.Decay : split.Decay; - //sustain = split.Sustain == 0 ? sample.WavInfo.Sustain : split.Sustain; - //hold = split.Hold == 0 ? sample.WavInfo.Hold : split.Hold; - //decay2 = split.Decay2 == 0 ? sample.WavInfo.Decay2 : split.Decay2; - //release = split.Release == 0 ? sample.WavInfo.Release : split.Release; - _attackVolume = split.AttackVolume == 0 ? _sample.WavInfo.AttackVolume == 0 ? (byte)0x7F : _sample.WavInfo.AttackVolume : split.AttackVolume; - _attack = split.Attack == 0 ? _sample.WavInfo.Attack == 0 ? (byte)0x7F : _sample.WavInfo.Attack : split.Attack; - _decay = split.Decay == 0 ? _sample.WavInfo.Decay == 0 ? (byte)0x7F : _sample.WavInfo.Decay : split.Decay; - _sustain = split.Sustain == 0 ? _sample.WavInfo.Sustain == 0 ? (byte)0x7F : _sample.WavInfo.Sustain : split.Sustain; - _hold = split.Hold == 0 ? _sample.WavInfo.Hold == 0 ? (byte)0x7F : _sample.WavInfo.Hold : split.Hold; - _decay2 = split.Decay2 == 0 ? _sample.WavInfo.Decay2 == 0 ? (byte)0x7F : _sample.WavInfo.Decay2 : split.Decay2; - _release = split.Release == 0 ? _sample.WavInfo.Release == 0 ? (byte)0x7F : _sample.WavInfo.Release : split.Release; - DetermineEnvelopeStartingPoint(); - _pos = 0; - _prevLeft = _prevRight = 0; - NoteLength = noteLength; - return true; - } - return false; + for (int i = 0; i < programInfo.SplitEntries.Length; i++) + { + SWD.ISplitEntry split = programInfo.SplitEntries[i]; + if (key < split.LowKey || key > split.HighKey) + { + continue; + } + + //if (_sample == null) { throw new NullReferenceException("Null Reference Exception:\n\nThere's no data associated with this Sample Block in this SWD. Please check to make sure the samples are being read correctly.\n\nCall Stack:"); } + _sample = masterswd.Samples![split.SampleId]; + Key = (byte)key; + RootKey = split.SampleRootKey; + switch (SWDType) // Configures the base timer based on the specific console's CPU and sample rate + { + case "wds ": throw new NotImplementedException("The base timer for the WDS type is not yet implemented."); // PlayStation + case "swdm": throw new NotImplementedException("The base timer for the SWDM type is not yet implemented."); // PlayStation 2 + case "swdl": BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo!.SampleRate); break; // Nintendo DS + case "swdb": BaseTimer = (ushort)(WiiUtils.PPC_Broadway_Clock + _sample.WavInfo!.SampleRate / 33); break; // Wii + } + if (_sample.WavInfo!.SampleFormat == SampleFormat.ADPCM) + { + _adpcmDecoder = new ADPCMDecoder(_sample.Data!); + } + //if (masterswd.Type == "swdb") + //{ + // _dspADPCM = _sample.DSPADPCM; + //} + //attackVolume = sample.WavInfo.AttackVolume == 0 ? split.AttackVolume : sample.WavInfo.AttackVolume; + //attack = sample.WavInfo.Attack == 0 ? split.Attack : sample.WavInfo.Attack; + //decay = sample.WavInfo.Decay == 0 ? split.Decay : sample.WavInfo.Decay; + //sustain = sample.WavInfo.Sustain == 0 ? split.Sustain : sample.WavInfo.Sustain; + //hold = sample.WavInfo.Hold == 0 ? split.Hold : sample.WavInfo.Hold; + //decay2 = sample.WavInfo.Decay2 == 0 ? split.Decay2 : sample.WavInfo.Decay2; + //release = sample.WavInfo.Release == 0 ? split.Release : sample.WavInfo.Release; + //attackVolume = split.AttackVolume == 0 ? sample.WavInfo.AttackVolume : split.AttackVolume; + //attack = split.Attack == 0 ? sample.WavInfo.Attack : split.Attack; + //decay = split.Decay == 0 ? sample.WavInfo.Decay : split.Decay; + //sustain = split.Sustain == 0 ? sample.WavInfo.Sustain : split.Sustain; + //hold = split.Hold == 0 ? sample.WavInfo.Hold : split.Hold; + //decay2 = split.Decay2 == 0 ? sample.WavInfo.Decay2 : split.Decay2; + //release = split.Release == 0 ? sample.WavInfo.Release : split.Release; + _attackVolume = split.AttackVolume == 0 ? _sample.WavInfo.AttackVolume == 0 ? (byte)0x7F : _sample.WavInfo.AttackVolume : split.AttackVolume; + _attack = split.Attack == 0 ? _sample.WavInfo.Attack == 0 ? (byte)0x7F : _sample.WavInfo.Attack : split.Attack; + _decay = split.Decay1 == 0 ? _sample.WavInfo.Decay1 == 0 ? (byte)0x7F : _sample.WavInfo.Decay1 : split.Decay1; + _sustain = split.Sustain == 0 ? _sample.WavInfo.Sustain == 0 ? (byte)0x7F : _sample.WavInfo.Sustain : split.Sustain; + _hold = split.Hold == 0 ? _sample.WavInfo.Hold == 0 ? (byte)0x7F : _sample.WavInfo.Hold : split.Hold; + _decay2 = split.Decay2 == 0 ? _sample.WavInfo.Decay2 == 0 ? (byte)0x7F : _sample.WavInfo.Decay2 : split.Decay2; + _release = split.Release == 0 ? _sample.WavInfo.Release == 0 ? (byte)0x7F : _sample.WavInfo.Release : split.Release; + DetermineEnvelopeStartingPoint(); + _pos = 0; + _prevLeft = _prevRight = 0; + NoteLength = noteLength; + return true; + } + return false; } - public void Stop() + public void StartPSG(byte duty, uint noteDuration) + { + _sample!.WavInfo!.SampleFormat = SampleFormat.PSG; + _psgCounter = 0; + _psgDuty = duty; + BaseTimer = 8006; // NDSUtils.ARM7_CLOCK / 2093 + Start(noteDuration); + } + + private void Start(uint noteDuration) + { + State = EnvelopeState.One; + _velocity = -92544; + _pos = 0; + _prevLeft = _prevRight = 0; + NoteLength = noteDuration; + } + + public void Stop() { if (Owner is not null) { @@ -113,9 +161,9 @@ public void Stop() private bool CMDB1___sub_2074CA0() { bool b = true; - bool ge = _sample.WavInfo.EnvMult >= 0x7F; - bool ee = _sample.WavInfo.EnvMult == 0x7F; - if (_sample.WavInfo.EnvMult > 0x7F) + bool ge = _sample!.WavInfo!.EnvMulti >= 0x7F; + bool ee = _sample.WavInfo.EnvMulti == 0x7F; + if (_sample.WavInfo.EnvMulti > 0x7F) { ge = _attackVolume >= 0x7F; ee = _attackVolume == 0x7F; @@ -273,9 +321,9 @@ private void UpdateEnvelopePlan(byte targetVolume, int envelopeParam) else { _targetVolume = targetVolume; - _envelopeTimeLeft = _sample.WavInfo.EnvMult == 0 + _envelopeTimeLeft = _sample!.WavInfo!.EnvMulti == 0 ? DSEUtils.Duration32[envelopeParam] * 1_000 / 10_000 - : DSEUtils.Duration16[envelopeParam] * _sample.WavInfo.EnvMult * 1_000 / 10_000; + : DSEUtils.Duration16[envelopeParam] * _sample.WavInfo.EnvMulti * 1_000 / 10_000; _volumeIncrement = _envelopeTimeLeft == 0 ? 0 : ((targetVolume << 23) - _velocity) / _envelopeTimeLeft; } } @@ -294,80 +342,121 @@ public void Process(out short left, out short right) // prevLeft and prevRight are stored because numSamples can be 0. for (int i = 0; i < numSamples; i++) { - short samp; - switch (_sample.WavInfo.SampleFormat) + switch (SWDType) { - case SampleFormat.PCM8: - { - // If hit end - if (_dataOffset >= _sample.Data.Length) - { - if (_sample.WavInfo.Loop) - { - _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); - } - else - { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; - } + case "wds ": + case "swdm": + case "swdl": + { + short samp; + switch (_sample!.WavInfo!.SampleFormat) + { + case SampleFormat.PCM8: + { + // If hit end + if (_dataOffset >= _sample.Data!.Length) + { + if (_sample.WavInfo.Loop) + { + _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + samp = (short)((sbyte)_sample.Data[_dataOffset++] << 8); + break; + } + case SampleFormat.PCM16: + { + // If hit end + if (_dataOffset >= _sample.Data!.Length) + { + if (_sample.WavInfo.Loop) + { + _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + samp = (short)(_sample.Data[_dataOffset++] | (_sample.Data[_dataOffset++] << 8)); + break; + } + case SampleFormat.ADPCM: + { + // If just looped + if (_adpcmDecoder!.DataOffset == _sample.WavInfo.LoopStart * 4 && !_adpcmDecoder.OnSecondNibble) + { + _adpcmLoopLastSample = _adpcmDecoder.LastSample; + _adpcmLoopStepIndex = _adpcmDecoder.StepIndex; + } + // If hit end + if (_adpcmDecoder.DataOffset >= _sample.Data!.Length && !_adpcmDecoder.OnSecondNibble) + { + if (_sample.WavInfo.Loop) + { + _adpcmDecoder.DataOffset = (int)(_sample.WavInfo.LoopStart * 4); + _adpcmDecoder.StepIndex = _adpcmLoopStepIndex; + _adpcmDecoder.LastSample = _adpcmLoopLastSample; + _adpcmDecoder.OnSecondNibble = false; + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + samp = _adpcmDecoder.GetSample(); + break; + } + case SampleFormat.PSG: + { + samp = _psgCounter <= _psgDuty ? short.MinValue : short.MaxValue; + _psgCounter++; + if (_psgCounter >= 8) + { + _psgCounter = 0; + } + break; + } + default: samp = 0; break; + } + samp = (short)(samp * Volume / 0x7F); + _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); + _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); + break; } - samp = (short)((sbyte)_sample.Data[_dataOffset++] << 8); - break; - } - case SampleFormat.PCM16: - { - // If hit end - if (_dataOffset >= _sample.Data.Length) - { - if (_sample.WavInfo.Loop) - { - _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); - } - else - { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; - } + case "swdb": + { + // If hit end + if (_dataOffset >= _sample!.Data!.Length) + { + if (_sample.WavInfo!.Loop) + { + _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + short samp = (short)(_sample.Data[_dataOffset++] | (_sample.Data[_dataOffset++] << 8)); + samp = (short)(samp * Volume / 0x7F); + _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); + _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); + break; } - samp = (short)(_sample.Data[_dataOffset++] | (_sample.Data[_dataOffset++] << 8)); - break; - } - case SampleFormat.ADPCM: - { - // If just looped - if (_adpcmDecoder.DataOffset == _sample.WavInfo.LoopStart * 4 && !_adpcmDecoder.OnSecondNibble) - { - _adpcmLoopLastSample = _adpcmDecoder.LastSample; - _adpcmLoopStepIndex = _adpcmDecoder.StepIndex; - } - // If hit end - if (_adpcmDecoder.DataOffset >= _sample.Data.Length && !_adpcmDecoder.OnSecondNibble) - { - if (_sample.WavInfo.Loop) - { - _adpcmDecoder.DataOffset = (int)(_sample.WavInfo.LoopStart * 4); - _adpcmDecoder.StepIndex = _adpcmLoopStepIndex; - _adpcmDecoder.LastSample = _adpcmLoopLastSample; - _adpcmDecoder.OnSecondNibble = false; - } - else - { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; - } - } - samp = _adpcmDecoder.GetSample(); - break; - } - default: samp = 0; break; } - samp = (short)(samp * Volume / 0x7F); - _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); - _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); } left = _prevLeft; right = _prevRight; diff --git a/VG Music Studio - Core/NDS/DSE/DSECommands.cs b/VG Music Studio - Core/NDS/DSE/DSECommands.cs index e07ac3b..fa3cb29 100644 --- a/VG Music Studio - Core/NDS/DSE/DSECommands.cs +++ b/VG Music Studio - Core/NDS/DSE/DSECommands.cs @@ -85,6 +85,14 @@ internal sealed class RestCommand : ICommand public uint Rest { get; set; } } +internal sealed class CheckIntervalCommand : ICommand +{ + public Color Color => Color.DarkViolet; + public string Label => "Check Interval"; + public string Arguments => Interval.ToString(); + + public uint Interval { get; set; } +} internal sealed class SkipBytesCommand : ICommand { public Color Color => Color.MediumVioletRed; diff --git a/VG Music Studio - Core/NDS/DSE/DSEConfig.cs b/VG Music Studio - Core/NDS/DSE/DSEConfig.cs index 6a68eed..434d314 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEConfig.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEConfig.cs @@ -1,5 +1,6 @@ using Kermalis.EndianBinaryIO; using Kermalis.VGMusicStudio.Core.Properties; +using System; using System.Collections.Generic; using System.IO; @@ -7,29 +8,39 @@ namespace Kermalis.VGMusicStudio.Core.NDS.DSE; public sealed class DSEConfig : Config { - public readonly string BGMPath; - public readonly string[] BGMFiles; + public readonly string SMDPath; + public readonly string[] SMDFiles; - internal DSEConfig(string bgmPath) + internal DSEConfig(string smdPath) { - BGMPath = bgmPath; - BGMFiles = Directory.GetFiles(bgmPath, "bgm*.smd", SearchOption.TopDirectoryOnly); - if (BGMFiles.Length == 0) + SMDPath = smdPath; + SMDFiles = Directory.GetFiles(smdPath, "*.smd", SearchOption.TopDirectoryOnly); + if (SMDFiles.Length == 0) { - throw new DSENoSequencesException(bgmPath); + throw new DSENoSequencesException(smdPath); } // TODO: Big endian files - var songs = new List(BGMFiles.Length); - for (int i = 0; i < BGMFiles.Length; i++) + var songs = new List(SMDFiles.Length); + for (int i = 0; i < SMDFiles.Length; i++) { - using (FileStream stream = File.OpenRead(BGMFiles[i])) + using (FileStream stream = File.OpenRead(SMDFiles[i])) { var r = new EndianBinaryReader(stream, ascii: true); - SMD.Header header = r.ReadObject(); - char[] chars = header.Label.ToCharArray(); - EndianBinaryPrimitives.TrimNullTerminators(ref chars); - songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(BGMFiles[i])} - {new string(chars)}")); + SMD.Header header = new SMD.Header(r); + if(header.Type == "smdl") + { + char[] chars = header.Label.ToCharArray(); + EndianBinaryPrimitives.TrimNullTerminators(ref chars); + songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(SMDFiles[i])} - {new string(chars)}")); + } + else if(header.Type == "smdb") + { + r.Endianness = Endianness.BigEndian; + char[] chars = header.Label.ToCharArray(); + EndianBinaryPrimitives.TrimNullTerminators(ref chars); + songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(SMDFiles[i])} - {new string(chars)}")); + } } } Playlists.Add(new Playlist(Strings.PlaylistMusic, songs)); @@ -41,8 +52,8 @@ public override string GetGameName() } public override string GetSongName(int index) { - return index < 0 || index >= BGMFiles.Length + return index < 0 || index >= SMDFiles.Length ? index.ToString() - : '\"' + BGMFiles[index] + '\"'; + : '\"' + SMDFiles[index] + '\"'; } } diff --git a/VG Music Studio - Core/NDS/DSE/DSEEngine.cs b/VG Music Studio - Core/NDS/DSE/DSEEngine.cs index a7a933e..0b064ba 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEEngine.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEEngine.cs @@ -1,4 +1,6 @@ -namespace Kermalis.VGMusicStudio.Core.NDS.DSE; +using System; + +namespace Kermalis.VGMusicStudio.Core.NDS.DSE; public sealed class DSEEngine : Engine { @@ -8,11 +10,11 @@ public sealed class DSEEngine : Engine public override DSEMixer Mixer { get; } public override DSEPlayer Player { get; } - public DSEEngine(string bgmPath) + public DSEEngine(string mainSWDFile, string bgmPath) { Config = new DSEConfig(bgmPath); Mixer = new DSEMixer(); - Player = new DSEPlayer(Config, Mixer); + Player = new DSEPlayer(mainSWDFile, Config, Mixer); DSEInstance = this; Instance = this; diff --git a/VG Music Studio - Core/NDS/DSE/DSEEnums.cs b/VG Music Studio - Core/NDS/DSE/DSEEnums.cs index 911c5f0..e10ca02 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEEnums.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEEnums.cs @@ -15,7 +15,8 @@ internal enum EnvelopeState : byte internal enum SampleFormat : ushort { - PCM8 = 0x000, - PCM16 = 0x100, - ADPCM = 0x200, + PCM8 = 0, + PCM16 = 1, + ADPCM = 2, + PSG = 3 } diff --git a/VG Music Studio - Core/NDS/DSE/DSEExceptions.cs b/VG Music Studio - Core/NDS/DSE/DSEExceptions.cs index 82c22e9..149ee6d 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEExceptions.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEExceptions.cs @@ -4,11 +4,11 @@ namespace Kermalis.VGMusicStudio.Core.NDS.DSE; public sealed class DSENoSequencesException : Exception { - public string BGMPath { get; } + public string SMDPath { get; } internal DSENoSequencesException(string bgmPath) { - BGMPath = bgmPath; + SMDPath = bgmPath; } } diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs index 8f7a943..9023159 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs @@ -1,7 +1,9 @@ using Kermalis.EndianBinaryIO; using Kermalis.VGMusicStudio.Core.Util; +using System; using System.Collections.Generic; using System.IO; +using static Kermalis.VGMusicStudio.Core.NDS.DSE.SMD; namespace Kermalis.VGMusicStudio.Core.NDS.DSE; @@ -12,35 +14,38 @@ internal sealed partial class DSELoadedSong : ILoadedSong public int LongestTrack; private readonly DSEPlayer _player; - private readonly SWD LocalSWD; + private readonly string SWDFileName; + private readonly string SMDFileName; + private readonly SWD LocalSWD; private readonly byte[] SMDFile; public readonly DSETrack[] Tracks; public DSELoadedSong(DSEPlayer player, string bgm) { _player = player; - - LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); + SWDFileName = bgm; + SMDFileName = bgm; + StringComparison comparison = StringComparison.CurrentCultureIgnoreCase; + //if (SWDFileName.StartsWith("bgm", comparison) == SMDFileName.StartsWith("bgm", comparison)) + //{ + // LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); + // } + // else if (SWDFileName.StartsWith("me") == SMDFileName.StartsWith("me")) + // { + // LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); + // } + // else if (SWDFileName.StartsWith("se") == SMDFileName.StartsWith("se")) + // { + // LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); + // } + //else { } SMDFile = File.ReadAllBytes(bgm); using (var stream = new MemoryStream(SMDFile)) { var r = new EndianBinaryReader(stream, ascii: true); - SMD.Header header = r.ReadObject(); - SMD.ISongChunk songChunk; - switch (header.Version) - { - case 0x402: - { - songChunk = r.ReadObject(); - break; - } - case 0x415: - { - songChunk = r.ReadObject(); - break; - } - default: throw new DSEInvalidHeaderVersionException(header.Version); - } + Header header = new Header(r); + if (header.Version != 0x415) { throw new DSEInvalidHeaderVersionException(header.Version); } + SongChunk songChunk = new SongChunk(r); Tracks = new DSETrack[songChunk.NumTracks]; Events = new List[songChunk.NumTracks]; diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs index b37dde9..93df0be 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs @@ -1,4 +1,5 @@ using Kermalis.EndianBinaryIO; +using Kermalis.VGMusicStudio.Core.Util; using System; using System.Collections.Generic; using System.Linq; @@ -108,274 +109,973 @@ private void AddTrackEvents(byte trackIndex, EndianBinaryReader r) } case 0x94: { - lastRest = (uint)(r.ReadByte() | (r.ReadByte() << 8) | (r.ReadByte() << 16)); + lastRest = r.ReadUInt24(); if (!EventExists(trackIndex, cmdOffset)) { AddEvent(trackIndex, cmdOffset, new RestCommand { Rest = lastRest }); } break; } - case 0x96: - case 0x97: - case 0x9A: - case 0x9B: - case 0x9F: - case 0xA2: - case 0xA3: - case 0xA6: - case 0xA7: - case 0xAD: - case 0xAE: - case 0xB7: - case 0xB8: - case 0xB9: - case 0xBA: - case 0xBB: - case 0xBD: - case 0xC1: - case 0xC2: - case 0xC4: - case 0xC5: - case 0xC6: - case 0xC7: - case 0xC8: - case 0xC9: - case 0xCA: - case 0xCC: - case 0xCD: - case 0xCE: - case 0xCF: - case 0xD9: - case 0xDA: - case 0xDE: - case 0xE6: - case 0xEB: - case 0xEE: - case 0xF4: - case 0xF5: - case 0xF7: - case 0xF9: - case 0xFA: - case 0xFB: - case 0xFC: - case 0xFD: - case 0xFE: - case 0xFF: + case 0x95: { + uint intervals = r.ReadByte(); if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + AddEvent(trackIndex, cmdOffset, new CheckIntervalCommand { Interval = intervals }); } break; } - case 0x98: + case 0x96: { if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new FinishCommand()); + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); } - cont = false; break; } - case 0x99: + case 0x97: { if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new LoopStartCommand { Offset = r.Stream.Position }); + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); } break; } - case 0xA0: + case 0x98: { - byte octave = r.ReadByte(); if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new OctaveSetCommand { Octave = octave }); + AddEvent(trackIndex, cmdOffset, new FinishCommand()); + r.Stream.Align(4); } + cont = false; break; } + case 0x99: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new LoopStartCommand { Offset = r.Stream.Position }); + } + break; + } + case 0x9A: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0x9B: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0x9C: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0x9D: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd }); + } + break; + } + case 0x9E: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd }); + } + break; + } + case 0x9F: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xA0: + { + byte octave = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new OctaveSetCommand { Octave = octave }); + } + break; + } case 0xA1: - { - sbyte change = r.ReadSByte(); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new OctaveAddCommand { OctaveChange = change }); + sbyte change = r.ReadSByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new OctaveAddCommand { OctaveChange = change }); + } + break; + } + case 0xA2: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xA3: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } case 0xA4: + { + byte tempoArg = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new TempoCommand { Command = cmd, Tempo = tempoArg }); + } + break; + } case 0xA5: // The code for these two is identical - { - byte tempoArg = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new TempoCommand { Command = cmd, Tempo = tempoArg }); + byte tempoArg = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new TempoCommand { Command = cmd, Tempo = tempoArg }); + } + break; } - break; - } - case 0xAB: - { - byte[] bytes = new byte[1]; - r.ReadBytes(bytes); - if (!EventExists(trackIndex, cmdOffset)) + case 0xA6: { - AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } - case 0xAC: - { - byte voice = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xA7: { - AddEvent(trackIndex, cmdOffset, new VoiceCommand { Voice = voice }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } - case 0xCB: - case 0xF8: - { - byte[] bytes = new byte[2]; - r.ReadBytes(bytes); - if (!EventExists(trackIndex, cmdOffset)) + case 0xA8: { - AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xD7: - { - ushort bend = r.ReadUInt16(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xA9: { - AddEvent(trackIndex, cmdOffset, new PitchBendCommand { Bend = bend }); + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xE0: - { - byte volume = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xAA: { - AddEvent(trackIndex, cmdOffset, new VolumeCommand { Volume = volume }); + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xE3: - { - byte expression = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xAB: { - AddEvent(trackIndex, cmdOffset, new ExpressionCommand { Expression = expression }); + byte[] bytes = new byte[1]; + r.ReadBytes(bytes); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + } + break; } - break; - } - case 0xE8: - { - byte panArg = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xAC: { - AddEvent(trackIndex, cmdOffset, new PanpotCommand { Panpot = (sbyte)(panArg - 0x40) }); + byte voice = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new VoiceCommand { Voice = voice }); + } + break; + } + case 0xAD: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xAE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xAF: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0x9D: case 0xB0: - case 0xC0: - { - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = Array.Empty() }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd }); + } + break; } - break; - } - case 0x9C: - case 0xA9: - case 0xAA: case 0xB1: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xB2: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xB3: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xB4: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xB5: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xB6: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xB7: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xB8: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xB9: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xBA: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xBB: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xBC: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xBD: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xBE: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xBF: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xC0: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xC1: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC2: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xC3: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xC4: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC5: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC6: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC7: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC8: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC9: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCA: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCB: + { + byte[] bytes = new byte[2]; + r.ReadBytes(bytes); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + } + break; + } + case 0xCC: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCD: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCF: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xD0: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xD1: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xD2: - case 0xDB: - case 0xDF: - case 0xE1: - case 0xE7: - case 0xE9: - case 0xEF: - case 0xF6: - { - byte[] args = new byte[1]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xA8: - case 0xB4: case 0xD3: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xD4: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xD5: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xD6: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xD7: + { + ushort bend = r.ReadUInt16(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new PitchBendCommand { Bend = bend }); + } + break; + } case 0xD8: - case 0xF2: - { - byte[] args = new byte[2]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xAF: - case 0xD4: - case 0xE2: - case 0xEA: - case 0xF3: - { - byte[] args = new byte[3]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) + case 0xD9: { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } - case 0xDD: - case 0xE5: - case 0xED: - case 0xF1: - { - byte[] args = new byte[4]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) + case 0xDA: { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xDB: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } case 0xDC: + { + byte[] args = new byte[5]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xDD: + { + byte[] args = new byte[4]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xDE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xDF: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE0: + { + byte volume = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new VolumeCommand { Volume = volume }); + } + break; + } + case 0xE1: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE2: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE3: + { + byte expression = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new ExpressionCommand { Expression = expression }); + } + break; + } case 0xE4: + { + byte[] args = new byte[5]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE5: + { + byte[] args = new byte[4]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE6: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xE7: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE8: + { + byte panArg = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new PanpotCommand { Panpot = (sbyte)(panArg - 0x40) }); + } + break; + } + case 0xE9: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xEA: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xEB: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xEC: + { + byte[] args = new byte[5]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xED: + { + byte[] args = new byte[4]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xEE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xEF: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xF0: - { - byte[] args = new byte[5]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + byte[] args = new byte[5]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF1: + { + byte[] args = new byte[4]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF2: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF3: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF4: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xF5: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xF6: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF7: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xF8: + { + byte[] bytes = new byte[2]; + r.ReadBytes(bytes); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + } + break; + } + case 0xF9: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFA: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFB: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFC: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFD: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFF: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } default: throw new DSEInvalidCMDException(trackIndex, (int)cmdOffset, cmd); } } diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs index e07c497..417f799 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs @@ -43,12 +43,12 @@ public void ExecuteNext(DSETrack track) DSEChannel? channel = _player.DMixer.AllocateChannel(); if (channel is null) { - throw new Exception("Not enough channels"); + throw new Exception("No channels were allocated."); } channel.Stop(); track.Octave = (byte)(track.Octave + oct); - if (channel.StartPCM(LocalSWD, _player.MasterSWD, track.Voice, n + (12 * track.Octave), duration)) + if (channel.StartPCM(LocalSWD, _player.MainSWD, track.Voice, n + (12 * track.Octave), duration)) { channel.NoteVelocity = cmd; channel.Owner = track; diff --git a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs index bfdcda2..54c563d 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; namespace Kermalis.VGMusicStudio.Core.NDS.DSE; @@ -8,7 +9,7 @@ public sealed class DSEPlayer : Player private readonly DSEConfig _config; internal readonly DSEMixer DMixer; - internal readonly SWD MasterSWD; + internal readonly SWD MainSWD; private DSELoadedSong? _loadedSong; internal byte Tempo; @@ -18,13 +19,14 @@ public sealed class DSEPlayer : Player public override ILoadedSong? LoadedSong => _loadedSong; protected override Mixer Mixer => DMixer; - public DSEPlayer(DSEConfig config, DSEMixer mixer) + public DSEPlayer(string mainSWDFile, DSEConfig config, DSEMixer mixer) : base(192) { DMixer = mixer; _config = config; + //string swdPath = Directory.GetFiles(mainSWDFile)[0]; - MasterSWD = new SWD(Path.Combine(config.BGMPath, "bgm.swd")); + MainSWD = new SWD(mainSWDFile); } public override void LoadSong(int index) @@ -35,7 +37,7 @@ public override void LoadSong(int index) } // If there's an exception, this will remain null - _loadedSong = new DSELoadedSong(this, _config.BGMFiles[index]); + _loadedSong = new DSELoadedSong(this, _config.SMDFiles[index]); _loadedSong.SetTicks(); } public override void UpdateSongState(SongState info) diff --git a/VG Music Studio - Core/NDS/DSE/SMD.cs b/VG Music Studio - Core/NDS/DSE/SMD.cs index 33cd44f..36d4d13 100644 --- a/VG Music Studio - Core/NDS/DSE/SMD.cs +++ b/VG Music Studio - Core/NDS/DSE/SMD.cs @@ -6,13 +6,10 @@ internal sealed class SMD { public sealed class Header // Size 0x40 { - [BinaryStringFixedLength(4)] public string Type { get; set; } // "smdb" or "smdl" - [BinaryArrayFixedLength(4)] public byte[] Unknown1 { get; set; } public uint Length { get; set; } public ushort Version { get; set; } - [BinaryArrayFixedLength(10)] public byte[] Unknown2 { get; set; } public ushort Year { get; set; } public byte Month { get; set; } @@ -21,40 +18,82 @@ public sealed class Header // Size 0x40 public byte Minute { get; set; } public byte Second { get; set; } public byte Centisecond { get; set; } - [BinaryStringFixedLength(16)] public string Label { get; set; } - [BinaryArrayFixedLength(16)] public byte[] Unknown3 { get; set; } + + public Header(EndianBinaryReader r) + { + Type = r.ReadString_Count(4); + + if (Type == "smdb") { r.Endianness = Endianness.BigEndian; } + + Unknown1 = new byte[4]; + r.ReadBytes(Unknown1); + + Length = r.ReadUInt32(); + + Version = r.ReadUInt16(); + + Unknown2 = new byte[10]; + r.ReadBytes(Unknown2); + + r.Endianness = Endianness.LittleEndian; + + Year = r.ReadUInt16(); + + Month = r.ReadByte(); + + Day = r.ReadByte(); + + Hour = r.ReadByte(); + + Minute = r.ReadByte(); + + Second = r.ReadByte(); + + Centisecond = r.ReadByte(); + + Label = r.ReadString_Count(16); + + Unknown3 = new byte[16]; + r.ReadBytes(Unknown3); + + if (Type == "smdb") { r.Endianness = Endianness.BigEndian; } + } } public interface ISongChunk { byte NumTracks { get; } } - public sealed class SongChunk_V402 : ISongChunk // Size 0x20 + public sealed class SongChunk : ISongChunk // Size 0x40 { - [BinaryStringFixedLength(4)] public string Type { get; set; } - [BinaryArrayFixedLength(16)] public byte[] Unknown1 { get; set; } - public byte NumTracks { get; set; } - public byte NumChannels { get; set; } - [BinaryArrayFixedLength(4)] + public ushort TicksPerQuarter { get; set; } public byte[] Unknown2 { get; set; } - public sbyte MasterVolume { get; set; } - public sbyte MasterPanpot { get; set; } - [BinaryArrayFixedLength(4)] - public byte[] Unknown3 { get; set; } - } - public sealed class SongChunk_V415 : ISongChunk // Size 0x40 - { - [BinaryStringFixedLength(4)] - public string Type { get; set; } - [BinaryArrayFixedLength(18)] - public byte[] Unknown1 { get; set; } public byte NumTracks { get; set; } public byte NumChannels { get; set; } - [BinaryArrayFixedLength(40)] - public byte[] Unknown2 { get; set; } + public byte[] Unknown3 { get; set; } + + public SongChunk(EndianBinaryReader r) + { + Type = r.ReadString_Count(4); + + Unknown1 = new byte[14]; + r.ReadBytes(Unknown1); + + TicksPerQuarter = r.ReadUInt16(); + + Unknown2 = new byte[2]; + r.ReadBytes(Unknown2); + + NumTracks = r.ReadByte(); + + NumChannels = r.ReadByte(); + + Unknown3 = new byte[40]; + r.ReadBytes(Unknown3); + } } } diff --git a/VG Music Studio - Core/NDS/DSE/SWD.cs b/VG Music Studio - Core/NDS/DSE/SWD.cs index 0b042fb..95b5c53 100644 --- a/VG Music Studio - Core/NDS/DSE/SWD.cs +++ b/VG Music Studio - Core/NDS/DSE/SWD.cs @@ -1,4 +1,5 @@ using Kermalis.EndianBinaryIO; +using Kermalis.VGMusicStudio.Core.Codec; using Kermalis.VGMusicStudio.Core.Util; using System; using System.Diagnostics; @@ -8,113 +9,170 @@ namespace Kermalis.VGMusicStudio.Core.NDS.DSE; internal sealed class SWD { - public interface IHeader + #region Header + public interface IHeader { // } - private sealed class Header_V402 : IHeader // Size 0x40 + public class Header : IHeader // Size 0x40 { - [BinaryArrayFixedLength(8)] - public byte[] Unknown1 { get; set; } - public ushort Year { get; set; } - public byte Month { get; set; } - public byte Day { get; set; } - public byte Hour { get; set; } - public byte Minute { get; set; } - public byte Second { get; set; } - public byte Centisecond { get; set; } - [BinaryStringFixedLength(16)] + public string Type { get; set; } + public byte[]? Unknown1 { get; set; } + public uint Length { get; set; } + public ushort Version { get; set; } + public byte[]? Unknown2 { get; set; } + public byte[]? Padding1 { get; set; } + public ushort Year { get; set; } + public byte Month { get; set; } + public byte Day { get; set; } + public byte Hour { get; set; } + public byte Minute { get; set; } + public byte Second { get; set; } + public byte Centisecond { get; set; } public string Label { get; set; } - [BinaryArrayFixedLength(22)] - public byte[] Unknown2 { get; set; } - public byte NumWAVISlots { get; set; } - public byte NumPRGISlots { get; set; } - public byte NumKeyGroups { get; set; } - [BinaryArrayFixedLength(7)] - public byte[] Padding { get; set; } - } - private sealed class Header_V415 : IHeader // Size 0x40 - { - [BinaryArrayFixedLength(8)] - public byte[] Unknown1 { get; set; } - public ushort Year { get; set; } - public byte Month { get; set; } - public byte Day { get; set; } - public byte Hour { get; set; } - public byte Minute { get; set; } - public byte Second { get; set; } - public byte Centisecond { get; set; } - [BinaryStringFixedLength(16)] - public string Label { get; set; } - [BinaryArrayFixedLength(16)] - public byte[] Unknown2 { get; set; } + public byte[]? Unknown3 { get; set; } public uint PCMDLength { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown3 { get; set; } + public byte[]? Unknown4 { get; set; } public ushort NumWAVISlots { get; set; } public ushort NumPRGISlots { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown4 { get; set; } + public byte NumKeyGroups { get; set; } + public byte[]? Unknown5 { get; set; } public uint WAVILength { get; set; } + public byte[]? Padding2 { get; set; } + + public Header(EndianBinaryReader r) + { + // File type metadata - The file type, version, and size of the file + Type = r.ReadString_Count(4); + if (Type == "swdb") + { + r.Endianness = Endianness.BigEndian; + } + Unknown1 = new byte[4]; + r.ReadBytes(Unknown1); + Length = r.ReadUInt32(); + Version = r.ReadUInt16(); + Unknown2 = new byte[2]; + r.ReadBytes(Unknown2); + + // Timestamp metadata - The time the SWD was published + r.Endianness = Endianness.LittleEndian; // Timestamp is always Little Endian, regardless of version or type, so it must be set to Little Endian to be read + + Padding1 = new byte[8]; // Padding + r.ReadBytes(Padding1); + Year = r.ReadUInt16(); // Year + Month = r.ReadByte(); // Month + Day = r.ReadByte(); // Day + Hour = r.ReadByte(); // Hour + Minute = r.ReadByte(); // Minute + Second = r.ReadByte(); // Second + Centisecond = r.ReadByte(); // Centisecond + if (Type == "swdb") { r.Endianness = Endianness.BigEndian; } // If type is swdb, restore back to Big Endian + + + // Info table + Label = r.ReadString_Count(16); + + switch (Version) // To ensure the version differences apply beyond this point + { + case 1026: + { + Unknown3 = new byte[22]; + r.ReadBytes(Unknown3); + + NumWAVISlots = r.ReadByte(); + + NumPRGISlots = r.ReadByte(); + + NumKeyGroups = r.ReadByte(); + + Padding2 = new byte[7]; + r.ReadBytes(Padding2); + + break; + } + case 1045: + { + Unknown3 = new byte[16]; + r.ReadBytes(Unknown3); + + PCMDLength = r.ReadUInt32(); + + Unknown4 = new byte[2]; + r.ReadBytes(Unknown4); + + NumWAVISlots = r.ReadUInt16(); + + NumPRGISlots = r.ReadUInt16(); + + Unknown5 = new byte[2]; + r.ReadBytes(Unknown5); + + WAVILength = r.ReadUInt32(); + + break; + } + } + } } - public interface ISplitEntry + public class ChunkHeader : IHeader // Size 0x10 + { + public string Name { get; set; } + public byte[] Padding { get; set; } + public ushort Version { get; set; } + public uint ChunkBegin { get; set; } + public uint ChunkEnd { get; set; } + + public ChunkHeader(EndianBinaryReader r, long chunkOffset, SWD swd) + { + long oldOffset = r.Stream.Position; + r.Stream.Position = chunkOffset; + + // Chunk Name + Name = r.ReadString_Count(4); + + // Padding + Padding = new byte[2]; + r.ReadBytes(Padding); + + // Version + Version = r.ReadUInt16(); + + // Chunk Begin + r.Endianness = Endianness.LittleEndian; // To ensure this is read in Little Endian in all versions and types + ChunkBegin = r.ReadUInt32(); + if (swd.Type == "swdb") { r.Endianness = Endianness.BigEndian; } // To revert back to Big Endian when the type is "swdb" + + // Chunk End + ChunkEnd = r.ReadUInt32(); + + r.Stream.Position = oldOffset; + } + } + #endregion + + #region SplitEntry + public interface ISplitEntry { byte LowKey { get; } byte HighKey { get; } - int SampleId { get; } + ushort SampleId { get; } byte SampleRootKey { get; } sbyte SampleTranspose { get; } byte AttackVolume { get; set; } byte Attack { get; set; } - byte Decay { get; set; } + byte Decay1 { get; set; } byte Sustain { get; set; } byte Hold { get; set; } byte Decay2 { get; set; } byte Release { get; set; } } - public sealed class SplitEntry_V402 : ISplitEntry // Size 0x30 + public class SplitEntry : ISplitEntry // 0x30 { - public ushort Id { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown1 { get; set; } - public byte LowKey { get; set; } - public byte HighKey { get; set; } - public byte LowKey2 { get; set; } - public byte HighKey2 { get; set; } - public byte LowVelocity { get; set; } - public byte HighVelocity { get; set; } - public byte LowVelocity2 { get; set; } - public byte HighVelocity2 { get; set; } - [BinaryArrayFixedLength(5)] + public byte Unknown1 { get; set; } + public byte Id { get; set; } public byte[] Unknown2 { get; set; } - public byte SampleId { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown3 { get; set; } - public byte SampleRootKey { get; set; } - public sbyte SampleTranspose { get; set; } - public byte SampleVolume { get; set; } - public sbyte SamplePanpot { get; set; } - public byte KeyGroupId { get; set; } - [BinaryArrayFixedLength(15)] - public byte[] Unknown4 { get; set; } - public byte AttackVolume { get; set; } - public byte Attack { get; set; } - public byte Decay { get; set; } - public byte Sustain { get; set; } - public byte Hold { get; set; } - public byte Decay2 { get; set; } - public byte Release { get; set; } - public byte Unknown5 { get; set; } - - [BinaryIgnore] - int ISplitEntry.SampleId => SampleId; - } - public sealed class SplitEntry_V415 : ISplitEntry // 0x30 - { - public ushort Id { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown1 { get; set; } public byte LowKey { get; set; } public byte HighKey { get; set; } public byte LowKey2 { get; set; } @@ -123,81 +181,253 @@ public sealed class SplitEntry_V415 : ISplitEntry // 0x30 public byte HighVelocity { get; set; } public byte LowVelocity2 { get; set; } public byte HighVelocity2 { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown2 { get; set; } - public ushort SampleId { get; set; } - [BinaryArrayFixedLength(2)] public byte[] Unknown3 { get; set; } + public ushort SampleId { get; set; } + public byte[] Unknown4 { get; set; } public byte SampleRootKey { get; set; } public sbyte SampleTranspose { get; set; } public byte SampleVolume { get; set; } public sbyte SamplePanpot { get; set; } public byte KeyGroupId { get; set; } - [BinaryArrayFixedLength(13)] - public byte[] Unknown4 { get; set; } + public byte[]? Unknown5 { get; set; } public byte AttackVolume { get; set; } public byte Attack { get; set; } - public byte Decay { get; set; } + public byte Decay1 { get; set; } public byte Sustain { get; set; } public byte Hold { get; set; } public byte Decay2 { get; set; } public byte Release { get; set; } - public byte Unknown5 { get; set; } + public byte Break { get; set; } + + ushort ISplitEntry.SampleId => SampleId; + + public SplitEntry(EndianBinaryReader r, SWD swd) + { + Unknown1 = r.ReadByte(); + + Id = r.ReadByte(); + + Unknown2 = new byte[2]; + r.ReadBytes(Unknown2); + + LowKey = r.ReadByte(); - [BinaryIgnore] - int ISplitEntry.SampleId => SampleId; + HighKey = r.ReadByte(); + + LowKey2 = r.ReadByte(); + + HighKey2 = r.ReadByte(); + + LowVelocity = r.ReadByte(); + + HighVelocity = r.ReadByte(); + + LowVelocity2 = r.ReadByte(); + + HighVelocity2 = r.ReadByte(); + + switch (swd.Version) + { + case 1026: + { + Unknown3 = new byte[5]; + r.ReadBytes(Unknown3); + + SampleId = r.ReadByte(); + + Unknown4 = new byte[2]; + r.ReadBytes(Unknown4); + + SampleRootKey = r.ReadByte(); + + SampleTranspose = r.ReadSByte(); + + SampleVolume = r.ReadByte(); + + SamplePanpot = r.ReadSByte(); + + KeyGroupId = r.ReadByte(); + + Unknown5 = new byte[15]; + r.ReadBytes(Unknown5); + + AttackVolume = r.ReadByte(); + + Attack = r.ReadByte(); + + Decay1 = r.ReadByte(); + + Sustain = r.ReadByte(); + + Hold = r.ReadByte(); + + Decay2 = r.ReadByte(); + + Release = r.ReadByte(); + + Break = r.ReadByte(); + + break; + } + case 1045: + { + Unknown2 = new byte[6]; + r.ReadBytes(Unknown2); + + SampleId = r.ReadUInt16(); + + Unknown3 = new byte[2]; + r.ReadBytes(Unknown3); + + SampleRootKey = r.ReadByte(); + + SampleTranspose = r.ReadSByte(); + + SampleVolume = r.ReadByte(); + + SamplePanpot = r.ReadSByte(); + + KeyGroupId = r.ReadByte(); + + Unknown4 = new byte[13]; + r.ReadBytes(Unknown4); + + AttackVolume = r.ReadByte(); + + Attack = r.ReadByte(); + + Decay1 = r.ReadByte(); + + Sustain = r.ReadByte(); + + Hold = r.ReadByte(); + + Decay2 = r.ReadByte(); + + Release = r.ReadByte(); + + Break = r.ReadByte(); + + break; + } + + // In the event that there's a SWD version that hasn't been discovered yet + default: throw new NotImplementedException("This version of the SWD specification has not been implemented into VG Music Studio."); + } + } } +#endregion - public interface IProgramInfo + #region ProgramInfo + public interface IProgramInfo { ISplitEntry[] SplitEntries { get; } } - public sealed class ProgramInfo_V402 : IProgramInfo + public class ProgramInfo : IProgramInfo { - public byte Id { get; set; } + public ushort Id { get; set; } public byte NumSplits { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown1 { get; set; } + public byte[] Unknown1 { get; set; } public byte Volume { get; set; } public byte Panpot { get; set; } - [BinaryArrayFixedLength(5)] public byte[] Unknown2 { get; set; } public byte NumLFOs { get; set; } - [BinaryArrayFixedLength(4)] public byte[] Unknown3 { get; set; } - [BinaryArrayFixedLength(16)] - public KeyGroup[] KeyGroups { get; set; } - [BinaryArrayVariableLength(nameof(NumLFOs))] - public LFOInfo LFOInfos { get; set; } - [BinaryArrayVariableLength(nameof(NumSplits))] - public SplitEntry_V402[] SplitEntries { get; set; } - - [BinaryIgnore] - ISplitEntry[] IProgramInfo.SplitEntries => SplitEntries; - } - public sealed class ProgramInfo_V415 : IProgramInfo - { - public ushort Id { get; set; } - public ushort NumSplits { get; set; } - public byte Volume { get; set; } - public byte Panpot { get; set; } - [BinaryArrayFixedLength(5)] - public byte[] Unknown1 { get; set; } - public byte NumLFOs { get; set; } - [BinaryArrayFixedLength(4)] - public byte[] Unknown2 { get; set; } - [BinaryArrayVariableLength(nameof(NumLFOs))] public LFOInfo[] LFOInfos { get; set; } - [BinaryArrayFixedLength(16)] - public byte[] Unknown3 { get; set; } - [BinaryArrayVariableLength(nameof(NumSplits))] - public SplitEntry_V415[] SplitEntries { get; set; } + public byte[]? Unknown4 { get; set; } + public KeyGroup[]? KeyGroups { get; set; } + public SplitEntry[] SplitEntries { get; set; } - [BinaryIgnore] ISplitEntry[] IProgramInfo.SplitEntries => SplitEntries; - } - public interface IWavInfo + public ProgramInfo(EndianBinaryReader r, SWD swd) + { + switch(swd.Version) + { + case 1026: + { + Id = r.ReadByte(); + + NumSplits = r.ReadByte(); + + Unknown1 = new byte[2]; + r.ReadBytes(Unknown1); + + Volume = r.ReadByte(); + + Panpot = r.ReadByte(); + + Unknown2 = new byte[5]; + r.ReadBytes(Unknown2); + + NumLFOs = r.ReadByte(); + + Unknown3 = new byte[4]; + r.ReadBytes(Unknown3); + + KeyGroups = new KeyGroup[16]; + + LFOInfos = new LFOInfo[NumLFOs]; + for (int i = 0; i < NumLFOs; i++) + { + LFOInfos[i] = new LFOInfo(r); + }; + + SplitEntries = new SplitEntry[NumSplits]; + + break; + } + + case 1045: + { + Id = r.ReadUInt16(); + + NumSplits = r.ReadByte(); + + Unknown1 = new byte[1]; + r.ReadBytes(Unknown1); + + Volume = r.ReadByte(); + + Panpot = r.ReadByte(); + + Unknown2 = new byte[5]; + r.ReadBytes(Unknown2); + + NumLFOs = r.ReadByte(); + + Unknown3 = new byte[4]; + r.ReadBytes(Unknown3); + + LFOInfos = new LFOInfo[NumLFOs]; + for (int i = 0; i < NumLFOs; i++) + { + LFOInfos[i] = new LFOInfo(r); + }; + + Unknown4 = new byte[16]; + r.ReadBytes(Unknown4); + + SplitEntries = new SplitEntry[NumSplits]; + for (int i = 0; i < NumSplits; i++) + { + SplitEntries[i] = new SplitEntry(r, swd); + } + + break; + } + + // In the event that there's a version that hasn't been discovered yet + default: throw new NotImplementedException("This Digital Sound Elements version has not been implemented into VG Music Studio."); + } + + } + + } + #endregion + + #region WavInfo + public interface IWavInfo { byte RootNote { get; } sbyte Transpose { get; } @@ -207,98 +437,299 @@ public interface IWavInfo uint SampleOffset { get; } uint LoopStart { get; } uint LoopEnd { get; } - byte EnvMult { get; } + byte EnvMulti { get; } byte AttackVolume { get; } byte Attack { get; } - byte Decay { get; } + byte Decay1 { get; } byte Sustain { get; } byte Hold { get; } byte Decay2 { get; } byte Release { get; } } - public sealed class WavInfo_V402 : IWavInfo // Size 0x40 - { - public byte Unknown1 { get; set; } - public byte Id { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown2 { get; set; } - public byte RootNote { get; set; } - public sbyte Transpose { get; set; } - public byte Volume { get; set; } - public sbyte Panpot { get; set; } - public SampleFormat SampleFormat { get; set; } - [BinaryArrayFixedLength(7)] - public byte[] Unknown3 { get; set; } - public bool Loop { get; set; } - public uint SampleRate { get; set; } - public uint SampleOffset { get; set; } - public uint LoopStart { get; set; } - public uint LoopEnd { get; set; } - [BinaryArrayFixedLength(16)] - public byte[] Unknown4 { get; set; } - public byte EnvOn { get; set; } - public byte EnvMult { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown5 { get; set; } - public byte AttackVolume { get; set; } - public byte Attack { get; set; } - public byte Decay { get; set; } - public byte Sustain { get; set; } - public byte Hold { get; set; } - public byte Decay2 { get; set; } - public byte Release { get; set; } - public byte Unknown6 { get; set; } - } - public sealed class WavInfo_V415 : IWavInfo // 0x40 + + public class WavInfo : IWavInfo // Size 0x40 { - [BinaryArrayFixedLength(2)] - public byte[] Unknown1 { get; set; } - public ushort Id { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown2 { get; set; } - public byte RootNote { get; set; } - public sbyte Transpose { get; set; } - public byte Volume { get; set; } - public sbyte Panpot { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown3 { get; set; } - public ushort Version { get; set; } - public SampleFormat SampleFormat { get; set; } - public byte Unknown4 { get; set; } - public bool Loop { get; set; } - public byte Unknown5 { get; set; } - public byte SamplesPer32Bits { get; set; } - public byte Unknown6 { get; set; } - public byte BitDepth { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown7 { get; set; } - public uint SampleRate { get; set; } - public uint SampleOffset { get; set; } - public uint LoopStart { get; set; } - public uint LoopEnd { get; set; } - public byte EnvOn { get; set; } - public byte EnvMult { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown8 { get; set; } - public byte AttackVolume { get; set; } - public byte Attack { get; set; } - public byte Decay { get; set; } - public byte Sustain { get; set; } - public byte Hold { get; set; } - public byte Decay2 { get; set; } - public byte Release { get; set; } - public byte Unknown9 { get; set; } - } + public byte[] Entry { get; set; } + public ushort Id { get; set; } + public byte[] Unknown2 { get; set; } + public byte RootNote { get; set; } + public sbyte Transpose { get; set; } + public byte Volume { get; set; } + public sbyte Panpot { get; set; } + public byte[] Unknown3 { get; set; } + public ushort Version { get; set; } + public SampleFormat SampleFormat { get; set; } + public byte Unknown4 { get; set; } + public bool Loop { get; set; } + public byte Unknown5 { get; set; } + public byte SamplesPer32Bits { get; set; } + public byte Unknown6 { get; set; } + public byte BitDepth { get; set; } + public byte[] Unknown7 { get; set; } + public uint SampleRate { get; set; } + public uint SampleOffset { get; set; } + public uint LoopStart { get; set; } + public uint LoopEnd { get; set; } + public byte EnvOn { get; set; } + public byte EnvMulti { get; set; } + public byte[] Unknown8 { get; set; } + public byte AttackVolume { get; set; } + public byte Attack { get; set; } + public byte Decay1 { get; set; } + public byte Sustain { get; set; } + public byte Hold { get; set; } + public byte Decay2 { get; set; } + public byte Release { get; set; } + public byte Break { get; set; } + + public WavInfo(EndianBinaryReader r, SWD swd) + { + // SWD version format check + switch(swd.Version) + { + + case 1026: + { + // The wave table Entry Variable + Entry = new byte[1]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() + r.ReadBytes(Entry); // Reads the byte + + // Wave ID + Id = r.ReadByte(); // Reads the ID of the wave sample + + // Currently undocumented variable(s) + Unknown2 = new byte[2]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() + r.ReadBytes(Unknown2); // Reads the bytes + + // Root Note + RootNote = r.ReadByte(); + + // Transpose + Transpose = r.ReadSByte(); + + // Volume + Volume = r.ReadByte(); + + // Panpot + Panpot = r.ReadSByte(); + + // Sample Format + if (swd.Type == "swdb") + { + r.Endianness = Endianness.LittleEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.BigEndian; + } + else + { + r.Endianness = Endianness.BigEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.LittleEndian; + } + + // Undocumented variable(s) + Unknown3 = new byte[7]; + r.ReadBytes(Unknown3); + + // Version + Version = r.ReadUInt16(); + + // Loop enable and disable + Loop = r.ReadBoolean(); + + // Sample Rate + SampleRate = r.ReadUInt32(); + + // Sample Offset + SampleOffset = r.ReadUInt32(); + + // Loop Start + LoopStart = r.ReadUInt32(); + + // Loop End + LoopEnd = r.ReadUInt32(); + + // Undocumented variable(s) + Unknown7 = new byte[16]; + r.ReadBytes(Unknown7); + + // Volume Envelop On + EnvOn = r.ReadByte(); + + // Volume Envelop Multiple + EnvMulti = r.ReadByte(); + + // Undocumented variable(s) + Unknown8 = new byte[6]; + r.ReadBytes(Unknown8); + + // Attack Volume + AttackVolume = r.ReadByte(); + + // Attack + Attack = r.ReadByte(); + + // Decay 1 + Decay1 = r.ReadByte(); + + // Sustain + Sustain = r.ReadByte(); + + // Hold + Hold = r.ReadByte(); + + // Decay 2 + Decay2 = r.ReadByte(); + + // Release + Release = r.ReadByte(); + + // The wave table Break Variable + Break = r.ReadByte(); + + break; + } + + case 1045: // Digital Sound Elements - SWD Specification 4.21 + { + // The wave table Entry Variable + Entry = new byte[2]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() + r.ReadBytes(Entry); // Reads the bytes + + // Wave ID + r.Endianness = Endianness.LittleEndian; // Changes the reader to Little Endian + Id = r.ReadUInt16(); // Reads the ID of the wave sample as Little Endian + if (swd.Type == "swdb") // Checks if the str string value matches "swdb" + { + r.Endianness = Endianness.BigEndian; // Restores the reader back to Big Endian + } + + // Currently undocumented variable + Unknown2 = new byte[2]; // Same as the one before + r.ReadBytes(Unknown2); + + // Root Note + RootNote = r.ReadByte(); + + // Transpose + Transpose = r.ReadSByte(); - public class SampleBlock + // Volume + Volume = r.ReadByte(); + + // Panpot + Panpot = r.ReadSByte(); + + // Undocumented variable + Unknown3 = new byte[6]; // Same as before, except we need to read 6 bytes instead of 2 + r.ReadBytes(Unknown3); + + // Version + Version = r.ReadUInt16(); + + // Sample Format + if (swd.Type == "swdb") + { + r.Endianness = Endianness.LittleEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.BigEndian; + } + else + { + r.Endianness = Endianness.BigEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.LittleEndian; + } + + // Undocumented variable(s) + Unknown4 = r.ReadByte(); + + // Loop enable or disable + Loop = r.ReadBoolean(); + + // Undocumented variable(s) + Unknown5 = r.ReadByte(); + + // Samples per 32 bits + SamplesPer32Bits = r.ReadByte(); + + // Undocumented variable(s) + Unknown6 = r.ReadByte(); + + // Bit Depth + BitDepth = r.ReadByte(); + + // Undocumented variable(s) + Unknown7 = new byte[6]; // Once again, create a variable to specify 6 bytes and to read using it + r.ReadBytes(Unknown7); + + // Sample Rate + SampleRate = r.ReadUInt32(); + + // Sample Offset + SampleOffset = r.ReadUInt32(); + + // Loop Start + LoopStart = r.ReadUInt32(); + + // Loop End + LoopEnd = r.ReadUInt32(); + + // Volume Envelop On + EnvOn = r.ReadByte(); + + // Volume Envelop Multiple + EnvMulti = r.ReadByte(); + + // Undocumented variable(s) + Unknown8 = new byte[6]; // Same as before + r.ReadBytes(Unknown8); + + // Attack Volume + AttackVolume = r.ReadByte(); + + // Attack + Attack = r.ReadByte(); + + // Decay 1 + Decay1 = r.ReadByte(); + + // Sustain + Sustain = r.ReadByte(); + + // Hold + Hold = r.ReadByte(); + + // Decay 2 + Decay2 = r.ReadByte(); + + // Release + Release = r.ReadByte(); + + // The wave table Break Variable + Break = r.ReadByte(); + + break; + } + + // In the event that there's a version that hasn't been discovered yet + default: throw new NotImplementedException("This version of the SWD specification has not yet been implemented into VG Music Studio."); + } + } + } + #endregion + + public class SampleBlock { - public IWavInfo WavInfo; - public byte[] Data; + public WavInfo? WavInfo; + public DSPADPCM? DSPADPCM; + public byte[]? Data; + //public short[]? Data16Bit; } public class ProgramBank { - public IProgramInfo?[] ProgramInfos; - public KeyGroup[] KeyGroups; + public ProgramInfo[]? ProgramInfos; + public KeyGroup[]? KeyGroups; } public class KeyGroup // Size 0x8 { @@ -308,8 +739,25 @@ public class KeyGroup // Size 0x8 public byte LowNote { get; set; } public byte HighNote { get; set; } public ushort Unknown { get; set; } + + public KeyGroup(EndianBinaryReader r, SWD swd) + { + r.Endianness = Endianness.LittleEndian; + Id = r.ReadUInt16(); + if (swd.Type == "swdb") { r.Endianness = Endianness.BigEndian; } + + Poly = r.ReadByte(); + + Priority = r.ReadByte(); + + LowNote = r.ReadByte(); + + HighNote = r.ReadByte(); + + Unknown = r.ReadUInt16(); + } } - public sealed class LFOInfo + public class LFOInfo { public byte Unknown1 { get; set; } public byte HasData { get; set; } @@ -321,166 +769,271 @@ public sealed class LFOInfo public ushort UnknownC { get; set; } public byte UnknownE { get; set; } public byte UnknownF { get; set; } + + public LFOInfo(EndianBinaryReader r) + { + Unknown1 = r.ReadByte(); + + HasData = r.ReadByte(); + + Type = r.ReadByte(); + + CallbackType = r.ReadByte(); + + Unknown4 = r.ReadUInt32(); + + Unknown8 = r.ReadUInt16(); + + UnknownA = r.ReadUInt16(); + + UnknownC = r.ReadUInt16(); + + UnknownE = r.ReadByte(); + + UnknownF = r.ReadByte(); + } } - public string Type; // "swdb" or "swdl" - public byte[] Unknown1; + public Header? Info; + public string Type; // "swdb" or "swdl" public uint Length; public ushort Version; - public IHeader Header; - public byte[] Unknown2; - public ProgramBank Programs; - public SampleBlock[] Samples; + public long WaviChunkOffset, WaviDataOffset, + PrgiChunkOffset, PrgiDataOffset, + KgrpChunkOffset, KgrpDataOffset, + PcmdChunkOffset, PcmdDataOffset, + EodChunkOffset; + public ChunkHeader? WaviInfo, PrgiInfo, KgrpInfo, PcmdInfo, EodInfo; + + public ProgramBank? Programs; + public SampleBlock[]? Samples; public SWD(string path) { using (var stream = new MemoryStream(File.ReadAllBytes(path))) { var r = new EndianBinaryReader(stream, ascii: true); - Type = r.ReadString_Count(4); - Unknown1 = new byte[4]; - r.ReadBytes(Unknown1); - Length = r.ReadUInt32(); - Version = r.ReadUInt16(); - Unknown2 = new byte[2]; - r.ReadBytes(Unknown2); - switch (Version) - { - case 0x402: - { - Header_V402 header = r.ReadObject(); - Header = header; - Programs = ReadPrograms(r, header.NumPRGISlots); - Samples = ReadSamples(r, header.NumWAVISlots); - break; - } - case 0x415: - { - Header_V415 header = r.ReadObject(); - Header = header; - Programs = ReadPrograms(r, header.NumPRGISlots); - if (header.PCMDLength != 0 && (header.PCMDLength & 0xFFFF0000) != 0xAAAA0000) - { - Samples = ReadSamples(r, header.NumWAVISlots); - } - break; - } - default: throw new InvalidDataException(); - } - } + Info = new Header(r); + Type = Info.Type; + Length = Info.Length; + Version = Info.Version; + Programs = ReadPrograms(r, Info.NumPRGISlots, this); + + switch (Version) + { + case 0x402: + { + Samples = ReadSamples(r, Info.NumWAVISlots, this); + break; + } + case 0x415: + { + if (Info.PCMDLength != 0 && (Info.PCMDLength & 0xFFFF0000) != 0xAAAA0000) + { + Samples = ReadSamples(r, Info.NumWAVISlots, this); + } + break; + } + default: throw new InvalidDataException(); + } + } + return; } - private static long FindChunk(EndianBinaryReader r, string chunk) + #region FindChunk + private static long FindChunk(EndianBinaryReader r, string chunk) { long pos = -1; long oldPosition = r.Stream.Position; - r.Stream.Position = 0; - while (r.Stream.Position < r.Stream.Length) + r.Stream.Position = 0; + while (r.Stream.Position < r.Stream.Length) { string str = r.ReadString_Count(4); - if (str == chunk) + if (str == chunk) { pos = r.Stream.Position - 4; - break; + break; } - switch (str) + switch (str) { case "swdb": - case "swdl": + { + r.Stream.Position += 0x4C; + break; + } + case "swdl": { r.Stream.Position += 0x4C; break; } default: { - Debug.WriteLine($"Ignoring {str} chunk"); + Debug.WriteLine($"Ignoring {str} chunk"); r.Stream.Position += 0x8; uint length = r.ReadUInt32(); r.Stream.Position += length; - r.Stream.Align(4); - break; + r.Stream.Align(16); + break; } - } - } + } + } r.Stream.Position = oldPosition; - return pos; + return pos; } + #endregion - private static SampleBlock[] ReadSamples(EndianBinaryReader r, int numWAVISlots) where T : IWavInfo, new() + #region SampleBlock + private SampleBlock[] ReadSamples(EndianBinaryReader r, int numWAVISlots, SWD swd) { - long waviChunkOffset = FindChunk(r, "wavi"); - long pcmdChunkOffset = FindChunk(r, "pcmd"); - if (waviChunkOffset == -1 || pcmdChunkOffset == -1) + // These apply the chunk offsets that are found to both local and the field functions, chunk header constructors are available here incase they're needed + long waviChunkOffset = swd.WaviChunkOffset = FindChunk(r, "wavi"); + long pcmdChunkOffset = swd.PcmdChunkOffset = FindChunk(r, "pcmd"); + long eodChunkOffset = swd.EodChunkOffset = FindChunk(r, "eod "); + if (waviChunkOffset == -1 || pcmdChunkOffset == -1) { throw new InvalidDataException(); } else { - waviChunkOffset += 0x10; - pcmdChunkOffset += 0x10; - var samples = new SampleBlock[numWAVISlots]; - for (int i = 0; i < numWAVISlots; i++) + WaviInfo = new ChunkHeader(r, waviChunkOffset, swd); + long waviDataOffset = WaviDataOffset = waviChunkOffset + 0x10; + PcmdInfo = new ChunkHeader(r, pcmdChunkOffset, swd); + long pcmdDataOffset = PcmdDataOffset = pcmdChunkOffset + 0x10; + EodInfo = new ChunkHeader(r, eodChunkOffset, swd); + var samples = new SampleBlock[numWAVISlots]; + for (int i = 0; i < numWAVISlots; i++) { - r.Stream.Position = waviChunkOffset + (2 * i); + r.Stream.Position = waviDataOffset + (2 * i); ushort offset = r.ReadUInt16(); - if (offset != 0) + if (offset != 0) { - r.Stream.Position = offset + waviChunkOffset; - T wavInfo = r.ReadObject(); - samples[i] = new SampleBlock - { - WavInfo = wavInfo, - Data = new byte[(int)((wavInfo.LoopStart + wavInfo.LoopEnd) * 4)], - }; - r.Stream.Position = pcmdChunkOffset + wavInfo.SampleOffset; - r.ReadBytes(samples[i].Data); - } + r.Stream.Position = offset + waviDataOffset; + WavInfo wavInfo = new WavInfo(r, swd); + switch (Type) + { + case "swdm": + { + throw new NotImplementedException("This Digital Sound Elements type has not yet been implemented."); + } + + case "swdl": + { + samples[i] = new SampleBlock + { + WavInfo = wavInfo, + Data = new byte[(int)((wavInfo.LoopStart + wavInfo.LoopEnd) * 4)], + }; + r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; + r.ReadBytes(samples[i].Data); + + break; + } + + case "swdb": + { + samples[i] = new SampleBlock + { + WavInfo = wavInfo, + //Data = new byte[samples[i].DSPADPCM!.Info.num_samples] + }; + r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; + samples[i].DSPADPCM = new DSPADPCM(r); + Span data = new short[samples[i].DSPADPCM!.Info.num_samples / 2]; + data = DSPADPCM.DSPADPCMToPCM16(samples[i].DSPADPCM!.Data, samples[i].DSPADPCM!.Info.num_samples, samples[i].DSPADPCM!.Info); + samples[i].Data = new byte[samples[i].DSPADPCM!.Info.ea]; + //wavInfo.LoopStart = wavInfo.LoopStart / 4; + //wavInfo.LoopEnd = wavInfo.LoopEnd / 4; + //samples[i].Data16Bit = new short[(int)((wavInfo.LoopStart + wavInfo.LoopEnd) / 4) / 4 + 85]; + //DSPADPCM.Decode(samples[i].DSPADPCM!.Data, samples[i].Data16Bit, ref samples[i].DSPADPCM!.Info, samples[i].DSPADPCM!.Info.num_samples); + int e = 0; + for (int d = 0; d < samples[i].DSPADPCM!.Info.num_adpcm_nibbles; d++) + { + samples[i].Data![e] = (byte)(data[d] >> 8); + if (e < samples[i].Data!.Length) { e += 1; } + if (e >= samples[i].Data!.Length) { break; } + samples[i].Data![e] = (byte)(data[d]); + if (e < samples[i].Data!.Length) { e += 1; } + if (e >= samples[i].Data!.Length) { break; } + } + + // Trying to implement an error message that informs anyone of a EndOfStreamException caused by a different encoding type. + //if (swd.PcmdDataOffset + samples[i].WavInfo!.SampleOffset + (samples[i].DSPADPCM!.NumADPCMNibbles / 2) + 9 > swd.EodChunkOffset) + //{ + // throw new EndOfStreamException("End of Stream Exception:\n" + + // "The number of ADPCM nibbles, divided by 2, plus 9 bytes, reads the sample data beyond this SWD.\n" + + // "\n" + + // "This is because VG Music Studio is incorrectly reading the actual size of the DSP-ADPCM sample data.\n" + + // "\n" + + // "If you are a developer of VG Music Studio, please check the code in DSPADPCM.cs\n" + + // "and verify the SWD file in a hex editor to make sure it's being read correctly.\n" + + // "\n" + + // "Call Stack:"); + //} + + //samples[i].DSPADPCM.GetSamples(samples[i].DSPADPCM.Info, samples[i].DSPADPCM.Info.ea); + //samples[i].Data = samples[i].DSPADPCM!.Data; + //Array.Resize(ref samples[i].Data, (int)((wavInfo.LoopStart + wavInfo.LoopEnd) / 6)); + //samples[i].Data = samples[i].DSPADPCM.Data; + break; + } + default: + { + throw new NotImplementedException("This Digital Sound Elements type has not yet been implemented."); + } + } + } } return samples; } } - private static ProgramBank? ReadPrograms(EndianBinaryReader r, int numPRGISlots) where T : IProgramInfo, new() + #endregion + + #region ProgramBank and KeyGroup + private static ProgramBank? ReadPrograms(EndianBinaryReader r, int numPRGISlots, SWD swd) { - long chunkOffset = FindChunk(r, "prgi"); - if (chunkOffset == -1) + long chunkOffset = swd.PrgiChunkOffset = FindChunk(r, "prgi"); + if (chunkOffset == -1) { return null; } - chunkOffset += 0x10; - var programInfos = new IProgramInfo?[numPRGISlots]; + swd.PrgiInfo = new ChunkHeader(r, chunkOffset, swd); + long dataOffset = swd.PrgiDataOffset = chunkOffset + 0x10; + var programInfos = new ProgramInfo[numPRGISlots]; for (int i = 0; i < programInfos.Length; i++) { - r.Stream.Position = chunkOffset + (2 * i); - ushort offset = r.ReadUInt16(); - if (offset != 0) + r.Stream.Position = dataOffset + (2 * i); + ushort offset = r.ReadUInt16(); + if (offset != 0) { - r.Stream.Position = offset + chunkOffset; - programInfos[i] = r.ReadObject(); - } + r.Stream.Position = offset + dataOffset; + programInfos[i] = new ProgramInfo(r, swd); + } } return new ProgramBank { ProgramInfos = programInfos, - KeyGroups = ReadKeyGroups(r), + KeyGroups = ReadKeyGroups(r, swd), }; } - private static KeyGroup[] ReadKeyGroups(EndianBinaryReader r) + private static KeyGroup[] ReadKeyGroups(EndianBinaryReader r, SWD swd) { - long chunkOffset = FindChunk(r, "kgrp"); - if (chunkOffset == -1) + long chunkOffset = swd.KgrpChunkOffset = FindChunk(r, "kgrp"); + if (chunkOffset == -1) { return Array.Empty(); } - r.Stream.Position = chunkOffset + 0xC; - uint chunkLength = r.ReadUInt32(); - var keyGroups = new KeyGroup[chunkLength / 8]; // 8 is the size of a KeyGroup - for (int i = 0; i < keyGroups.Length; i++) + ChunkHeader info = swd.KgrpInfo = new ChunkHeader(r, chunkOffset, swd); + swd.KgrpDataOffset = chunkOffset + 0x10; + r.Stream.Position = swd.KgrpDataOffset; + var keyGroups = new KeyGroup[info.ChunkEnd / 8]; // 8 is the size of a KeyGroup + for (int i = 0; i < keyGroups.Length; i++) { - keyGroups[i] = r.ReadObject(); - } + keyGroups[i] = new KeyGroup(r, swd); + } return keyGroups; } + #endregion } diff --git a/VG Music Studio - Core/NDS/SDAT/SDATChannel.cs b/VG Music Studio - Core/NDS/SDAT/SDATChannel.cs index 87fdf6d..fafeb32 100644 --- a/VG Music Studio - Core/NDS/SDAT/SDATChannel.cs +++ b/VG Music Studio - Core/NDS/SDAT/SDATChannel.cs @@ -1,4 +1,5 @@ using System; +using Kermalis.VGMusicStudio.Core.Codec; namespace Kermalis.VGMusicStudio.Core.NDS.SDAT; diff --git a/VG Music Studio - Core/Properties/Strings.Designer.cs b/VG Music Studio - Core/Properties/Strings.Designer.cs index eea96fb..0b12484 100644 --- a/VG Music Studio - Core/Properties/Strings.Designer.cs +++ b/VG Music Studio - Core/Properties/Strings.Designer.cs @@ -375,6 +375,15 @@ public static string FilterOpenSDAT { } } + /// + /// Looks up a localized string similar to Wave Data. + /// + public static string FilterOpenSWD { + get { + return ResourceManager.GetString("FilterOpenSWD", resourceCulture); + } + } + /// /// Looks up a localized string similar to DLS Files. /// @@ -448,7 +457,7 @@ public static string MenuOpenAlphaDream { } /// - /// Looks up a localized string similar to Open DSE Folder. + /// Looks up a localized string similar to Open DSE Files. /// public static string MenuOpenDSE { get { @@ -474,6 +483,24 @@ public static string MenuOpenSDAT { } } + /// + /// Looks up a localized string similar to Open SMD Folder. + /// + public static string MenuOpenSMD { + get { + return ResourceManager.GetString("MenuOpenSMD", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open SWD File. + /// + public static string MenuOpenSWD { + get { + return ResourceManager.GetString("MenuOpenSWD", resourceCulture); + } + } + /// /// Looks up a localized string similar to Playlist. /// diff --git a/VG Music Studio - Core/Properties/Strings.resx b/VG Music Studio - Core/Properties/Strings.resx index 916279d..c48ae06 100644 --- a/VG Music Studio - Core/Properties/Strings.resx +++ b/VG Music Studio - Core/Properties/Strings.resx @@ -204,8 +204,8 @@ End Current Playlist - - Open DSE Folder + + Open SMD Folder Open GBA ROM (AlphaDream) @@ -369,4 +369,13 @@ songs|0_0|song|1_1|songs|2_*| + + Wave Data + + + Open DSE Files + + + Open SWD File + \ No newline at end of file diff --git a/VG Music Studio - Core/VG Music Studio - Core.csproj b/VG Music Studio - Core/VG Music Studio - Core.csproj index f82177d..4a7f6aa 100644 --- a/VG Music Studio - Core/VG Music Studio - Core.csproj +++ b/VG Music Studio - Core/VG Music Studio - Core.csproj @@ -11,10 +11,10 @@ - + Dependencies\DLS2.dll diff --git a/VG Music Studio - Core/Wii/WiiUtils.cs b/VG Music Studio - Core/Wii/WiiUtils.cs new file mode 100644 index 0000000..797775f --- /dev/null +++ b/VG Music Studio - Core/Wii/WiiUtils.cs @@ -0,0 +1,7 @@ +namespace Kermalis.VGMusicStudio.Core.Wii +{ + internal static class WiiUtils + { + public const int PPC_Broadway_Clock = 382_205_952; // (764.411904 / 2) = 382.205952 = 382,205,952 + } +} diff --git a/VG Music Studio - MIDI/VG Music Studio - MIDI.csproj b/VG Music Studio - MIDI/VG Music Studio - MIDI.csproj index ef1e119..f7acfdc 100644 --- a/VG Music Studio - MIDI/VG Music Studio - MIDI.csproj +++ b/VG Music Studio - MIDI/VG Music Studio - MIDI.csproj @@ -9,7 +9,7 @@ - + diff --git a/VG Music Studio - WinForms/API/Core/Interop/CoreErrorHelper.cs b/VG Music Studio - WinForms/API/Core/Interop/CoreErrorHelper.cs new file mode 100644 index 0000000..ddadfcb --- /dev/null +++ b/VG Music Studio - WinForms/API/Core/Interop/CoreErrorHelper.cs @@ -0,0 +1,96 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Internal +{ + /// HRESULT Wrapper + public enum HResult + { + /// S_OK + Ok = 0x0000, + + /// S_FALSE + False = 0x0001, + + /// E_INVALIDARG + InvalidArguments = unchecked((int)0x80070057), + + /// E_OUTOFMEMORY + OutOfMemory = unchecked((int)0x8007000E), + + /// E_NOINTERFACE + NoInterface = unchecked((int)0x80004002), + + /// E_FAIL + Fail = unchecked((int)0x80004005), + + /// E_ELEMENTNOTFOUND + ElementNotFound = unchecked((int)0x80070490), + + /// TYPE_E_ELEMENTNOTFOUND + TypeElementNotFound = unchecked((int)0x8002802B), + + /// NO_OBJECT + NoObject = unchecked((int)0x800401E5), + + /// Win32 Error code: ERROR_CANCELLED + Win32ErrorCanceled = 1223, + + /// ERROR_CANCELLED + Canceled = unchecked((int)0x800704C7), + + /// The requested resource is in use + ResourceInUse = unchecked((int)0x800700AA), + + /// The requested resources is read-only. + AccessDenied = unchecked((int)0x80030005) + } + + /// Provide Error Message Helper Methods. This is intended for Library Internal use only. + internal static class CoreErrorHelper + { + /// This is intended for Library Internal use only. + public const int Ignored = (int)HResult.Ok; + + /// This is intended for Library Internal use only. + private const int FacilityWin32 = 7; + + /// This is intended for Library Internal use only. + /// The error code. + /// True if the error code indicates failure. + public static bool Failed(HResult result) => !Succeeded(result); + + /// This is intended for Library Internal use only. + /// The error code. + /// True if the error code indicates failure. + public static bool Failed(int result) => !Succeeded(result); + + /// This is intended for Library Internal use only. + /// The Windows API error code. + /// The equivalent HRESULT. + public static int HResultFromWin32(int win32ErrorCode) + { + if (win32ErrorCode > 0) + { + win32ErrorCode = + (int)(((uint)win32ErrorCode & 0x0000FFFF) | (FacilityWin32 << 16) | 0x80000000); + } + return win32ErrorCode; + } + + /// This is intended for Library Internal use only. + /// The COM error code. + /// The Win32 error code. + /// Inticates that the Win32 error code corresponds to the COM error code. + public static bool Matches(int result, int win32ErrorCode) => (result == HResultFromWin32(win32ErrorCode)); + + /// This is intended for Library Internal use only. + /// The error code. + /// True if the error code indicates success. + public static bool Succeeded(int result) => result >= 0; + + /// This is intended for Library Internal use only. + /// The error code. + /// True if the error code indicates success. + public static bool Succeeded(HResult result) => Succeeded((int)result); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Core/Interop/CoreHelpers.cs b/VG Music Studio - WinForms/API/Core/Interop/CoreHelpers.cs new file mode 100644 index 0000000..abbe42f --- /dev/null +++ b/VG Music Studio - WinForms/API/Core/Interop/CoreHelpers.cs @@ -0,0 +1,86 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Resources; +using System; +using System.Globalization; +using System.Text; + +namespace Kermalis.VGMusicStudio.WinForms.API.Internal +{ + /// Common Helper methods + public static class CoreHelpers + { + /// Determines if the application is running on Vista + public static bool RunningOnVista => Environment.OSVersion.Version.Major >= 6; + + /// Determines if the application is running on Windows 7 + public static bool RunningOnWin7 => + // Verifies that OS version is 6.1 or greater, and the Platform is WinNT. + Environment.OSVersion.Platform == PlatformID.Win32NT && + Environment.OSVersion.Version.CompareTo(new Version(6, 1)) >= 0; + + /// Determines if the application is running on XP + public static bool RunningOnXP => Environment.OSVersion.Platform == PlatformID.Win32NT && + Environment.OSVersion.Version.Major >= 5; + + /// Get a string resource given a resource Id + /// The resource Id + /// + /// The string resource corresponding to the given resource Id. Returns null if the resource id is invalid or the string cannot be + /// retrieved for any other reason. + /// + public static string GetStringResource(string resourceId) + { + string[] parts; + string library; + int index; + + if (string.IsNullOrEmpty(resourceId)) { return string.Empty; } + + // Known folder "Recent" has a malformed resource id for its tooltip. This causes the resource id to parse into 3 parts instead + // of 2 parts if we don't fix. + resourceId = resourceId.Replace("shell32,dll", "shell32.dll"); + parts = resourceId.Split(new char[] { ',' }); + + library = parts[0]; + library = library.Replace(@"@", string.Empty); + library = Environment.ExpandEnvironmentVariables(library); + var handle = CoreNativeMethods.LoadLibrary(library); + + parts[1] = parts[1].Replace("-", string.Empty); + index = int.Parse(parts[1], CultureInfo.InvariantCulture); + + var stringValue = new StringBuilder(255); + var retval = CoreNativeMethods.LoadString(handle, index, stringValue, 255); + + return retval != 0 ? stringValue.ToString() : null; + } + + /// Throws PlatformNotSupportedException if the application is not running on Windows Vista + public static void ThrowIfNotVista() + { + if (!CoreHelpers.RunningOnVista) + { + throw new PlatformNotSupportedException(LocalizedMessages.CoreHelpersRunningOnVista); + } + } + + /// Throws PlatformNotSupportedException if the application is not running on Windows 7 + public static void ThrowIfNotWin7() + { + if (!CoreHelpers.RunningOnWin7) + { + throw new PlatformNotSupportedException(LocalizedMessages.CoreHelpersRunningOn7); + } + } + + /// Throws PlatformNotSupportedException if the application is not running on Windows XP + public static void ThrowIfNotXP() + { + if (!CoreHelpers.RunningOnXP) + { + throw new PlatformNotSupportedException(LocalizedMessages.CoreHelpersRunningOnXp); + } + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Core/Interop/CoreNativeMethods.cs b/VG Music Studio - WinForms/API/Core/Interop/CoreNativeMethods.cs new file mode 100644 index 0000000..736e401 --- /dev/null +++ b/VG Music Studio - WinForms/API/Core/Interop/CoreNativeMethods.cs @@ -0,0 +1,233 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Kermalis.VGMusicStudio.WinForms.API.Internal +{ + /// Wrappers for Native Methods and Structs. This type is intended for internal use only + internal static class CoreNativeMethods + { + // Disabled non-client rendering; window style is ignored. + internal const int DWMNCRP_DISABLED = 1; + + // Enabled non-client rendering; window style is ignored. + internal const int DWMNCRP_ENABLED = 2; + + // Enable/disable non-client rendering based on window style. + internal const int DWMNCRP_USEWINDOWSTYLE = 0; + + // Enable/disable non-client rendering Use DWMNCRP_* values. + internal const int DWMWA_NCRENDERING_ENABLED = 1; + + // Non-client rendering policy. + internal const int DWMWA_NCRENDERING_POLICY = 2; + + // Potentially enable/forcibly disable transitions 0 or 1. + internal const int DWMWA_TRANSITIONS_FORCEDISABLED = 3; + + internal const int EnterIdleMessage = 0x0121; + + // FormatMessage constants and structs. + internal const int FormatMessageFromSystem = 0x00001000; + + // App recovery and restart return codes + internal const uint ResultFailed = 0x80004005; + + internal const uint ResultFalse = 1; + + internal const uint ResultInvalidArgument = 0x80070057; + + internal const uint ResultNotFound = 0x80070490; + + internal const uint StatusAccessDenied = 0xC0000022; + + // Various important window messages + internal const int UserMessage = 0x0400; + + public delegate int WNDPROC(IntPtr hWnd, + uint uMessage, + IntPtr wParam, + IntPtr lParam); + + /// Gets the HiWord + /// The value to get the hi word from. + /// Size + /// The upper half of the dword. + public static int GetHiWord(long value, int size) => (short)(value >> size); + + /// Gets the LoWord + /// The value to get the low word from. + /// The lower half of the dword. + public static int GetLoWord(long value) => (short)(value & 0xFFFF); + + /// + /// Places (posts) a message in the message queue associated with the thread that created the specified window and returns without + /// waiting for the thread to process the message. + /// + /// + /// Handle to the window whose window procedure will receive the message. If this parameter is HWND_BROADCAST, the message is sent to + /// all top-level windows in the system, including disabled or invisible unowned windows, overlapped windows, and pop-up windows; but + /// the message is not sent to child windows. + /// + /// Specifies the message to be sent. + /// Specifies additional message-specific information. + /// Specifies additional message-specific information. + /// A return code specific to the message being sent. + [DllImport("user32.dll", CharSet = CharSet.Auto, PreserveSig = false, SetLastError = true)] + public static extern void PostMessage( + IntPtr windowHandle, + WindowMessage message, + IntPtr wparam, + IntPtr lparam + ); + + /// + /// Sends the specified message to a window or windows. The SendMessage function calls the window procedure for the specified window + /// and does not return until the window procedure has processed the message. + /// + /// + /// Handle to the window whose window procedure will receive the message. If this parameter is HWND_BROADCAST, the message is sent to + /// all top-level windows in the system, including disabled or invisible unowned windows, overlapped windows, and pop-up windows; but + /// the message is not sent to child windows. + /// + /// Specifies the message to be sent. + /// Specifies additional message-specific information. + /// Specifies additional message-specific information. + /// A return code specific to the message being sent. + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr SendMessage( + IntPtr windowHandle, + WindowMessage message, + IntPtr wparam, + IntPtr lparam + ); + + /// + /// Sends the specified message to a window or windows. The SendMessage function calls the window procedure for the specified window + /// and does not return until the window procedure has processed the message. + /// + /// + /// Handle to the window whose window procedure will receive the message. If this parameter is HWND_BROADCAST, the message is sent to + /// all top-level windows in the system, including disabled or invisible unowned windows, overlapped windows, and pop-up windows; but + /// the message is not sent to child windows. + /// + /// Specifies the message to be sent. + /// Specifies additional message-specific information. + /// Specifies additional message-specific information. + /// A return code specific to the message being sent. + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr SendMessage( + IntPtr windowHandle, + uint message, + IntPtr wparam, + IntPtr lparam + ); + + /// + /// Sends the specified message to a window or windows. The SendMessage function calls the window procedure for the specified window + /// and does not return until the window procedure has processed the message. + /// + /// + /// Handle to the window whose window procedure will receive the message. If this parameter is HWND_BROADCAST, the message is sent to + /// all top-level windows in the system, including disabled or invisible unowned windows, overlapped windows, and pop-up windows; but + /// the message is not sent to child windows. + /// + /// Specifies the message to be sent. + /// Specifies additional message-specific information. + /// Specifies additional message-specific information. + /// A return code specific to the message being sent. + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr SendMessage( + IntPtr windowHandle, + uint message, + IntPtr wparam, + [MarshalAs(UnmanagedType.LPWStr)] string lparam); + + /// + /// Sends the specified message to a window or windows. The SendMessage function calls the window procedure for the specified window + /// and does not return until the window procedure has processed the message. + /// + /// + /// Handle to the window whose window procedure will receive the message. If this parameter is HWND_BROADCAST, the message is sent to + /// all top-level windows in the system, including disabled or invisible unowned windows, overlapped windows, and pop-up windows; but + /// the message is not sent to child windows. + /// + /// Specifies the message to be sent. + /// Specifies additional message-specific information. + /// Specifies additional message-specific information. + /// A return code specific to the message being sent. + public static IntPtr SendMessage( + IntPtr windowHandle, + uint message, + int wparam, + string lparam) => SendMessage(windowHandle, message, (IntPtr)wparam, lparam); + + /// + /// Sends the specified message to a window or windows. The SendMessage function calls the window procedure for the specified window + /// and does not return until the window procedure has processed the message. + /// + /// + /// Handle to the window whose window procedure will receive the message. If this parameter is HWND_BROADCAST, the message is sent to + /// all top-level windows in the system, including disabled or invisible unowned windows, overlapped windows, and pop-up windows; but + /// the message is not sent to child windows. + /// + /// Specifies the message to be sent. + /// Specifies additional message-specific information. + /// Specifies additional message-specific information. + /// A return code specific to the message being sent. + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr SendMessage( + IntPtr windowHandle, + uint message, + ref int wparam, + [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lparam); + + [DllImport("gdi32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeleteObject(IntPtr graphicsObjectHandle); + + /// Destroys an icon and frees any memory the icon occupied. + /// Handle to the icon to be destroyed. The icon must not be in use. + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error + /// information, call GetLastError. + /// + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DestroyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true, EntryPoint = "DestroyWindow", CallingConvention = CallingConvention.StdCall)] + internal static extern int DestroyWindow(IntPtr handle); + + // Various helpers for forcing binding to proper version of Comctl32 (v6). + [DllImport("kernel32.dll", SetLastError = true, ThrowOnUnmappableChar = true, BestFitMapping = false)] + internal static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string fileName); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern int LoadString( + IntPtr instanceHandle, + int id, + StringBuilder buffer, + int bufferSize); + + [DllImport("Kernel32.dll", EntryPoint = "LocalFree")] + internal static extern IntPtr LocalFree(ref Guid guid); + + /// A Wrapper for a SIZE struct + [StructLayout(LayoutKind.Sequential)] + public struct Size + { + private int width; + private int height; + + /// Width + public int Width { get => width; set => width = value; } + + /// Height + public int Height { get => height; set => height = value; } + }; + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Core/Interop/WindowMessage.cs b/VG Music Studio - WinForms/API/Core/Interop/WindowMessage.cs new file mode 100644 index 0000000..a3f41b5 --- /dev/null +++ b/VG Music Studio - WinForms/API/Core/Interop/WindowMessage.cs @@ -0,0 +1,229 @@ +namespace Kermalis.VGMusicStudio.WinForms.API.Internal +{ + internal enum WindowMessage + { + Null = 0x00, + Create = 0x01, + Destroy = 0x02, + Move = 0x03, + Size = 0x05, + Activate = 0x06, + SetFocus = 0x07, + KillFocus = 0x08, + Enable = 0x0A, + SetRedraw = 0x0B, + SetText = 0x0C, + GetText = 0x0D, + GetTextLength = 0x0E, + Paint = 0x0F, + Close = 0x10, + QueryEndSession = 0x11, + Quit = 0x12, + QueryOpen = 0x13, + EraseBackground = 0x14, + SystemColorChange = 0x15, + EndSession = 0x16, + SystemError = 0x17, + ShowWindow = 0x18, + ControlColor = 0x19, + WinIniChange = 0x1A, + SettingChange = 0x1A, + DevModeChange = 0x1B, + ActivateApplication = 0x1C, + FontChange = 0x1D, + TimeChange = 0x1E, + CancelMode = 0x1F, + SetCursor = 0x20, + MouseActivate = 0x21, + ChildActivate = 0x22, + QueueSync = 0x23, + GetMinMaxInfo = 0x24, + PaintIcon = 0x26, + IconEraseBackground = 0x27, + NextDialogControl = 0x28, + SpoolerStatus = 0x2A, + DrawItem = 0x2B, + MeasureItem = 0x2C, + DeleteItem = 0x2D, + VKeyToItem = 0x2E, + CharToItem = 0x2F, + + SetFont = 0x30, + GetFont = 0x31, + SetHotkey = 0x32, + GetHotkey = 0x33, + QueryDragIcon = 0x37, + CompareItem = 0x39, + Compacting = 0x41, + WindowPositionChanging = 0x46, + WindowPositionChanged = 0x47, + Power = 0x48, + CopyData = 0x4A, + CancelJournal = 0x4B, + Notify = 0x4E, + InputLanguageChangeRequest = 0x50, + InputLanguageChange = 0x51, + TCard = 0x52, + Help = 0x53, + UserChanged = 0x54, + NotifyFormat = 0x55, + ContextMenu = 0x7B, + StyleChanging = 0x7C, + StyleChanged = 0x7D, + DisplayChange = 0x7E, + GetIcon = 0x7F, + SetIcon = 0x80, + + NCCreate = 0x81, + NCDestroy = 0x82, + NCCalculateSize = 0x83, + NCHitTest = 0x84, + NCPaint = 0x85, + NCActivate = 0x86, + GetDialogCode = 0x87, + NCMouseMove = 0xA0, + NCLeftButtonDown = 0xA1, + NCLeftButtonUp = 0xA2, + NCLeftButtonDoubleClick = 0xA3, + NCRightButtonDown = 0xA4, + NCRightButtonUp = 0xA5, + NCRightButtonDoubleClick = 0xA6, + NCMiddleButtonDown = 0xA7, + NCMiddleButtonUp = 0xA8, + NCMiddleButtonDoubleClick = 0xA9, + + KeyFirst = 0x100, + KeyDown = 0x100, + KeyUp = 0x101, + Char = 0x102, + DeadChar = 0x103, + SystemKeyDown = 0x104, + SystemKeyUp = 0x105, + SystemChar = 0x106, + SystemDeadChar = 0x107, + KeyLast = 0x108, + + IMEStartComposition = 0x10D, + IMEEndComposition = 0x10E, + IMEComposition = 0x10F, + IMEKeyLast = 0x10F, + + InitializeDialog = 0x110, + Command = 0x111, + SystemCommand = 0x112, + Timer = 0x113, + HorizontalScroll = 0x114, + VerticalScroll = 0x115, + InitializeMenu = 0x116, + InitializeMenuPopup = 0x117, + MenuSelect = 0x11F, + MenuChar = 0x120, + EnterIdle = 0x121, + + CTLColorMessageBox = 0x132, + CTLColorEdit = 0x133, + CTLColorListbox = 0x134, + CTLColorButton = 0x135, + CTLColorDialog = 0x136, + CTLColorScrollBar = 0x137, + CTLColorStatic = 0x138, + + MouseFirst = 0x200, + MouseMove = 0x200, + LeftButtonDown = 0x201, + LeftButtonUp = 0x202, + LeftButtonDoubleClick = 0x203, + RightButtonDown = 0x204, + RightButtonUp = 0x205, + RightButtonDoubleClick = 0x206, + MiddleButtonDown = 0x207, + MiddleButtonUp = 0x208, + MiddleButtonDoubleClick = 0x209, + MouseWheel = 0x20A, + MouseHorizontalWheel = 0x20E, + + ParentNotify = 0x210, + EnterMenuLoop = 0x211, + ExitMenuLoop = 0x212, + NextMenu = 0x213, + Sizing = 0x214, + CaptureChanged = 0x215, + Moving = 0x216, + PowerBroadcast = 0x218, + DeviceChange = 0x219, + + MDICreate = 0x220, + MDIDestroy = 0x221, + MDIActivate = 0x222, + MDIRestore = 0x223, + MDINext = 0x224, + MDIMaximize = 0x225, + MDITile = 0x226, + MDICascade = 0x227, + MDIIconArrange = 0x228, + MDIGetActive = 0x229, + MDISetMenu = 0x230, + EnterSizeMove = 0x231, + ExitSizeMove = 0x232, + DropFiles = 0x233, + MDIRefreshMenu = 0x234, + + IMESetContext = 0x281, + IMENotify = 0x282, + IMEControl = 0x283, + IMECompositionFull = 0x284, + IMESelect = 0x285, + IMEChar = 0x286, + IMEKeyDown = 0x290, + IMEKeyUp = 0x291, + + MouseHover = 0x2A1, + NCMouseLeave = 0x2A2, + MouseLeave = 0x2A3, + + Cut = 0x300, + Copy = 0x301, + Paste = 0x302, + Clear = 0x303, + Undo = 0x304, + + RenderFormat = 0x305, + RenderAllFormats = 0x306, + DestroyClipboard = 0x307, + DrawClipbard = 0x308, + PaintClipbard = 0x309, + VerticalScrollClipBoard = 0x30A, + SizeClipbard = 0x30B, + AskClipboardFormatname = 0x30C, + ChangeClipboardChain = 0x30D, + HorizontalScrollClipboard = 0x30E, + QueryNewPalette = 0x30F, + PaletteIsChanging = 0x310, + PaletteChanged = 0x311, + + Hotkey = 0x312, + Print = 0x317, + PrintClient = 0x318, + + HandHeldFirst = 0x358, + HandHeldlast = 0x35F, + PenWinFirst = 0x380, + PenWinLast = 0x38F, + CoalesceFirst = 0x390, + CoalesceLast = 0x39F, + DDE_First = 0x3E0, + DDE_Initiate = 0x3E0, + DDE_Terminate = 0x3E1, + DDE_Advise = 0x3E2, + DDE_Unadvise = 0x3E3, + DDE_Ack = 0x3E4, + DDE_Data = 0x3E5, + DDE_Request = 0x3E6, + DDE_Poke = 0x3E7, + DDE_Execute = 0x3E8, + DDE_Last = 0x3E8, + + User = 0x400, + App = 0x8000, + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Core/PropertySystem/PropVariant.cs b/VG Music Studio - WinForms/API/Core/PropertySystem/PropVariant.cs new file mode 100644 index 0000000..08efd0e --- /dev/null +++ b/VG Music Studio - WinForms/API/Core/PropertySystem/PropVariant.cs @@ -0,0 +1,754 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Internal +{ + /// Represents the OLE struct PROPVARIANT. This class is intended for internal use only. + /// + /// Originally sourced from http://blogs.msdn.com/adamroot/pages/interop-with-propvariants-in-net.aspx and modified to support additional + /// types including vectors and ability to set values + /// + [StructLayout(LayoutKind.Explicit)] + public sealed class PropVariant : IDisposable + { + [StructLayout(LayoutKind.Sequential)] + private struct Blob + { + public int Number; + public IntPtr Pointer; + } + + // A static dictionary of delegates to get data from array's contained within PropVariants + private static Dictionary> _vectorActions = null; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + private static Dictionary> GenerateVectorActions() + { + Dictionary> cache = new Dictionary> + { + { + typeof(short), + (pv, array, i) => + { + PropVariantNativeMethods.PropVariantGetInt16Elem(pv, i, out short val); + array.SetValue(val, i); + } + }, + + { + typeof(ushort), + (pv, array, i) => + { + PropVariantNativeMethods.PropVariantGetUInt16Elem(pv, i, out ushort val); + array.SetValue(val, i); + } + }, + + { + typeof(int), + (pv, array, i) => + { + PropVariantNativeMethods.PropVariantGetInt32Elem(pv, i, out int val); + array.SetValue(val, i); + } + }, + + { + typeof(uint), + (pv, array, i) => + { + PropVariantNativeMethods.PropVariantGetUInt32Elem(pv, i, out uint val); + array.SetValue(val, i); + } + }, + + { + typeof(long), + (pv, array, i) => + { + PropVariantNativeMethods.PropVariantGetInt64Elem(pv, i, out long val); + array.SetValue(val, i); + } + }, + + { + typeof(ulong), + (pv, array, i) => + { + PropVariantNativeMethods.PropVariantGetUInt64Elem(pv, i, out ulong val); + array.SetValue(val, i); + } + }, + + { + typeof(DateTime), + (pv, array, i) => + { + PropVariantNativeMethods.PropVariantGetFileTimeElem(pv, i, out System.Runtime.InteropServices.ComTypes.FILETIME val); + + long fileTime = GetFileTimeAsLong(ref val); + + array.SetValue(DateTime.FromFileTime(fileTime), i); + } + }, + + { + typeof(bool), + (pv, array, i) => + { + PropVariantNativeMethods.PropVariantGetBooleanElem(pv, i, out bool val); + array.SetValue(val, i); + } + }, + + { + typeof(double), + (pv, array, i) => + { + PropVariantNativeMethods.PropVariantGetDoubleElem(pv, i, out double val); + array.SetValue(val, i); + } + }, + + { + typeof(float), + (pv, array, i) => // float + { + float[] val = new float[1]; + Marshal.Copy(pv._blob.Pointer, val, (int)i, 1); + array.SetValue(val[0], (int)i); + } + }, + + { + typeof(decimal), + (pv, array, i) => + { + int[] val = new int[4]; + for (int a = 0; a < val.Length; a++) + { + val[a] = Marshal.ReadInt32(pv._blob.Pointer, + (int)i * sizeof(decimal) + a * sizeof(int)); //index * size + offset quarter + } + array.SetValue(new decimal(val), i); + } + }, + + { + typeof(string), + (pv, array, i) => + { + string val = string.Empty; + PropVariantNativeMethods.PropVariantGetStringElem(pv, i, ref val); + array.SetValue(val, i); + } + } + }; + + return cache; + } + + /// Attempts to create a PropVariant by finding an appropriate constructor. + /// Object from which PropVariant should be created. + public static PropVariant FromObject(object value) + { + if (value == null) + { + return new PropVariant(); + } + else + { + Func func = GetDynamicConstructor(value.GetType()); + return func(value); + } + } + + // A dictionary and lock to contain compiled expression trees for constructors + private static readonly Dictionary> _cache = new Dictionary>(); + + private static readonly object _padlock = new object(); + + // Retrieves a cached constructor expression. If no constructor has been cached, it attempts to find/add it. If it cannot be found an + // exception is thrown. This method looks for a public constructor with the same parameter type as the object. + private static Func GetDynamicConstructor(Type type) + { + lock (_padlock) + { + // initial check, if action is found, return it + if (!_cache.TryGetValue(type, out var action)) + { + // iterates through all constructors + System.Reflection.ConstructorInfo constructor = typeof(PropVariant) + .GetConstructor(new Type[] { type }); + + if (constructor == null) + { + // if the method was not found, throw. + throw new ArgumentException(LocalizedMessages.PropVariantTypeNotSupported); + } + else // if the method was found, create an expression to call it. + { + // create parameters to action + ParameterExpression arg = Expression.Parameter(typeof(object), "arg"); + + // create an expression to invoke the constructor with an argument cast to the correct type + NewExpression create = Expression.New(constructor, Expression.Convert(arg, type)); + + // compiles expression into an action delegate + action = Expression.Lambda>(create, arg).Compile(); + _cache.Add(type, action); + } + } + return action; + } + } + + [FieldOffset(0)] + private readonly decimal _decimal; + + // This is actually a VarEnum value, but the VarEnum type requires 4 bytes instead of the expected 2. + [FieldOffset(0)] + private ushort _valueType; + + // Reserved Fields + //[FieldOffset(2)] + //ushort _wReserved1; + //[FieldOffset(4)] + //ushort _wReserved2; + //[FieldOffset(6)] + //ushort _wReserved3; + + [FieldOffset(8)] + private readonly Blob _blob; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] + [FieldOffset(8)] + private IntPtr _ptr; + + [FieldOffset(8)] + private readonly int _int32; + + [FieldOffset(8)] + private readonly uint _uint32; + + [FieldOffset(8)] + private readonly byte _byte; + + [FieldOffset(8)] + private readonly sbyte _sbyte; + + [FieldOffset(8)] + private readonly short _short; + + [FieldOffset(8)] + private readonly ushort _ushort; + + [FieldOffset(8)] + private readonly long _long; + + [FieldOffset(8)] + private readonly ulong _ulong; + + [FieldOffset(8)] + private readonly double _double; + + [FieldOffset(8)] + private readonly float _float; + + /// Default constrcutor + public PropVariant() + { + // left empty + } + + /// Set a string value + public PropVariant(string value) + { + if (value == null) + { + throw new ArgumentException(LocalizedMessages.PropVariantNullString, "value"); + } + + _valueType = (ushort)VarEnum.VT_LPWSTR; + _ptr = Marshal.StringToCoTaskMemUni(value); + } + + /// Set a string vector + public PropVariant(string[] value) + { + if (value == null) { throw new ArgumentNullException("value"); } + + PropVariantNativeMethods.InitPropVariantFromStringVector(value, (uint)value.Length, this); + } + + /// Set a bool vector + public PropVariant(bool[] value) + { + if (value == null) { throw new ArgumentNullException("value"); } + + PropVariantNativeMethods.InitPropVariantFromBooleanVector(value, (uint)value.Length, this); + } + + /// Set a short vector + public PropVariant(short[] value) + { + if (value == null) { throw new ArgumentNullException("value"); } + + PropVariantNativeMethods.InitPropVariantFromInt16Vector(value, (uint)value.Length, this); + } + + /// Set a short vector + public PropVariant(ushort[] value) + { + if (value == null) { throw new ArgumentNullException("value"); } + + PropVariantNativeMethods.InitPropVariantFromUInt16Vector(value, (uint)value.Length, this); + } + + /// Set an int vector + public PropVariant(int[] value) + { + if (value == null) { throw new ArgumentNullException("value"); } + + PropVariantNativeMethods.InitPropVariantFromInt32Vector(value, (uint)value.Length, this); + } + + /// Set an uint vector + public PropVariant(uint[] value) + { + if (value == null) { throw new ArgumentNullException("value"); } + + PropVariantNativeMethods.InitPropVariantFromUInt32Vector(value, (uint)value.Length, this); + } + + /// Set a long vector + public PropVariant(long[] value) + { + if (value == null) { throw new ArgumentNullException("value"); } + + PropVariantNativeMethods.InitPropVariantFromInt64Vector(value, (uint)value.Length, this); + } + + /// Set a ulong vector + public PropVariant(ulong[] value) + { + if (value == null) { throw new ArgumentNullException("value"); } + + PropVariantNativeMethods.InitPropVariantFromUInt64Vector(value, (uint)value.Length, this); + } + + /// > Set a double vector + public PropVariant(double[] value) + { + if (value == null) { throw new ArgumentNullException("value"); } + + PropVariantNativeMethods.InitPropVariantFromDoubleVector(value, (uint)value.Length, this); + } + + /// Set a DateTime vector + public PropVariant(DateTime[] value) + { + if (value == null) { throw new ArgumentNullException("value"); } + System.Runtime.InteropServices.ComTypes.FILETIME[] fileTimeArr = + new System.Runtime.InteropServices.ComTypes.FILETIME[value.Length]; + + for (int i = 0; i < value.Length; i++) + { + fileTimeArr[i] = DateTimeToFileTime(value[i]); + } + + PropVariantNativeMethods.InitPropVariantFromFileTimeVector(fileTimeArr, (uint)fileTimeArr.Length, this); + } + + /// Set a bool value + public PropVariant(bool value) + { + _valueType = (ushort)VarEnum.VT_BOOL; + _int32 = (value == true) ? -1 : 0; + } + + /// Set a DateTime value + public PropVariant(DateTime value) + { + _valueType = (ushort)VarEnum.VT_FILETIME; + + System.Runtime.InteropServices.ComTypes.FILETIME ft = DateTimeToFileTime(value); + PropVariantNativeMethods.InitPropVariantFromFileTime(ref ft, this); + } + + /// Set a byte value + public PropVariant(byte value) + { + _valueType = (ushort)VarEnum.VT_UI1; + _byte = value; + } + + /// Set a sbyte value + public PropVariant(sbyte value) + { + _valueType = (ushort)VarEnum.VT_I1; + _sbyte = value; + } + + /// Set a short value + public PropVariant(short value) + { + _valueType = (ushort)VarEnum.VT_I2; + _short = value; + } + + /// Set an unsigned short value + public PropVariant(ushort value) + { + _valueType = (ushort)VarEnum.VT_UI2; + _ushort = value; + } + + /// Set an int value + public PropVariant(int value) + { + _valueType = (ushort)VarEnum.VT_I4; + _int32 = value; + } + + /// Set an unsigned int value + public PropVariant(uint value) + { + _valueType = (ushort)VarEnum.VT_UI4; + _uint32 = value; + } + + /// Set a decimal value + public PropVariant(decimal value) + { + _decimal = value; + + // It is critical that the value type be set after the decimal value, because they overlap. If valuetype is written first, its + // value will be lost when _decimal is written. + _valueType = (ushort)VarEnum.VT_DECIMAL; + } + + /// Create a PropVariant with a contained decimal array. + /// Decimal array to wrap. + public PropVariant(decimal[] value) + { + if (value == null) { throw new ArgumentNullException("value"); } + + _valueType = (ushort)(VarEnum.VT_DECIMAL | VarEnum.VT_VECTOR); + _int32 = value.Length; + + // allocate required memory for array with 128bit elements + _blob.Pointer = Marshal.AllocCoTaskMem(value.Length * sizeof(decimal)); + for (int i = 0; i < value.Length; i++) + { + int[] bits = decimal.GetBits(value[i]); + Marshal.Copy(bits, 0, _blob.Pointer, bits.Length); + } + } + + /// Create a PropVariant containing a float type. + public PropVariant(float value) + { + _valueType = (ushort)VarEnum.VT_R4; + + _float = value; + } + + /// Creates a PropVariant containing a float[] array. + public PropVariant(float[] value) + { + if (value == null) { throw new ArgumentNullException("value"); } + + _valueType = (ushort)(VarEnum.VT_R4 | VarEnum.VT_VECTOR); + _int32 = value.Length; + + _blob.Pointer = Marshal.AllocCoTaskMem(value.Length * sizeof(float)); + + Marshal.Copy(value, 0, _blob.Pointer, value.Length); + } + + /// Set a long + public PropVariant(long value) + { + _long = value; + _valueType = (ushort)VarEnum.VT_I8; + } + + /// Set a ulong + public PropVariant(ulong value) + { + _valueType = (ushort)VarEnum.VT_UI8; + _ulong = value; + } + + /// Set a double + public PropVariant(double value) + { + _valueType = (ushort)VarEnum.VT_R8; + _double = value; + } + + /// Set an IUnknown value + /// The new value to set. + internal void SetIUnknown(object value) + { + _valueType = (ushort)VarEnum.VT_UNKNOWN; + _ptr = Marshal.GetIUnknownForObject(value); + } + + /// Set a safe array value + /// The new value to set. + internal void SetSafeArray(Array array) + { + if (array == null) { throw new ArgumentNullException("array"); } + const ushort vtUnknown = 13; + IntPtr psa = PropVariantNativeMethods.SafeArrayCreateVector(vtUnknown, 0, (uint)array.Length); + + IntPtr pvData = PropVariantNativeMethods.SafeArrayAccessData(psa); + try // to remember to release lock on data + { + for (int i = 0; i < array.Length; ++i) + { + object obj = array.GetValue(i); + IntPtr punk = (obj != null) ? Marshal.GetIUnknownForObject(obj) : IntPtr.Zero; + Marshal.WriteIntPtr(pvData, i * IntPtr.Size, punk); + } + } + finally + { + PropVariantNativeMethods.SafeArrayUnaccessData(psa); + } + + _valueType = (ushort)VarEnum.VT_ARRAY | (ushort)VarEnum.VT_UNKNOWN; + _ptr = psa; + } + + /// Gets or sets the variant type. + public VarEnum VarType + { + get => (VarEnum)_valueType; + set => _valueType = (ushort)value; + } + + /// Checks if this has an empty or null value + /// + public bool IsNullOrEmpty => (_valueType == (ushort)VarEnum.VT_EMPTY || _valueType == (ushort)VarEnum.VT_NULL); + + /// Gets the variant value. + public object Value + { + get + { + switch ((VarEnum)_valueType) + { + case VarEnum.VT_I1: + return _sbyte; + + case VarEnum.VT_UI1: + return _byte; + + case VarEnum.VT_I2: + return _short; + + case VarEnum.VT_UI2: + return _ushort; + + case VarEnum.VT_I4: + case VarEnum.VT_INT: + return _int32; + + case VarEnum.VT_UI4: + case VarEnum.VT_UINT: + return _uint32; + + case VarEnum.VT_I8: + return _long; + + case VarEnum.VT_UI8: + return _ulong; + + case VarEnum.VT_R4: + return _float; + + case VarEnum.VT_R8: + return _double; + + case VarEnum.VT_BOOL: + return _int32 == -1; + + case VarEnum.VT_ERROR: + return _long; + + case VarEnum.VT_CY: + return _decimal; + + case VarEnum.VT_DATE: + return DateTime.FromOADate(_double); + + case VarEnum.VT_FILETIME: + return DateTime.FromFileTime(_long); + + case VarEnum.VT_BSTR: + return Marshal.PtrToStringBSTR(_ptr); + + case VarEnum.VT_BLOB: + return GetBlobData(); + + case VarEnum.VT_LPSTR: + return Marshal.PtrToStringAnsi(_ptr); + + case VarEnum.VT_LPWSTR: + return Marshal.PtrToStringUni(_ptr); + + case VarEnum.VT_UNKNOWN: + return Marshal.GetObjectForIUnknown(_ptr); + + case VarEnum.VT_DISPATCH: + return Marshal.GetObjectForIUnknown(_ptr); + + case VarEnum.VT_DECIMAL: + return _decimal; + + case VarEnum.VT_ARRAY | VarEnum.VT_UNKNOWN: + return CrackSingleDimSafeArray(_ptr); + + case (VarEnum.VT_VECTOR | VarEnum.VT_LPWSTR): + return GetVector(); + + case (VarEnum.VT_VECTOR | VarEnum.VT_I2): + return GetVector(); + + case (VarEnum.VT_VECTOR | VarEnum.VT_UI2): + return GetVector(); + + case (VarEnum.VT_VECTOR | VarEnum.VT_I4): + return GetVector(); + + case (VarEnum.VT_VECTOR | VarEnum.VT_UI4): + return GetVector(); + + case (VarEnum.VT_VECTOR | VarEnum.VT_I8): + return GetVector(); + + case (VarEnum.VT_VECTOR | VarEnum.VT_UI8): + return GetVector(); + + case (VarEnum.VT_VECTOR | VarEnum.VT_R4): + return GetVector(); + + case (VarEnum.VT_VECTOR | VarEnum.VT_R8): + return GetVector(); + + case (VarEnum.VT_VECTOR | VarEnum.VT_BOOL): + return GetVector(); + + case (VarEnum.VT_VECTOR | VarEnum.VT_FILETIME): + return GetVector(); + + case (VarEnum.VT_VECTOR | VarEnum.VT_DECIMAL): + return GetVector(); + + default: + // if the value cannot be marshaled + return null; + } + } + } + + private static long GetFileTimeAsLong(ref System.Runtime.InteropServices.ComTypes.FILETIME val) => (((long)val.dwHighDateTime) << 32) + val.dwLowDateTime; + + private static System.Runtime.InteropServices.ComTypes.FILETIME DateTimeToFileTime(DateTime value) + { + long hFT = value.ToFileTime(); + System.Runtime.InteropServices.ComTypes.FILETIME ft = + new System.Runtime.InteropServices.ComTypes.FILETIME + { + dwLowDateTime = (int)(hFT & 0xFFFFFFFF), + dwHighDateTime = (int)(hFT >> 32) + }; + return ft; + } + + private object GetBlobData() + { + byte[] blobData = new byte[_int32]; + + IntPtr pBlobData = _blob.Pointer; + Marshal.Copy(pBlobData, blobData, 0, _int32); + + return blobData; + } + + private Array GetVector() + { + int count = PropVariantNativeMethods.PropVariantGetElementCount(this); + if (count <= 0) { return null; } + + lock (_padlock) + { + if (_vectorActions == null) + { + _vectorActions = GenerateVectorActions(); + } + } + + if (!_vectorActions.TryGetValue(typeof(T), out var action)) + { + throw new InvalidCastException(LocalizedMessages.PropVariantUnsupportedType); + } + + Array array = new T[count]; + for (uint i = 0; i < count; i++) + { + action(this, array, i); + } + + return array; + } + + private static Array CrackSingleDimSafeArray(IntPtr psa) + { + uint cDims = PropVariantNativeMethods.SafeArrayGetDim(psa); + if (cDims != 1) + throw new ArgumentException(LocalizedMessages.PropVariantMultiDimArray, "psa"); + + int lBound = PropVariantNativeMethods.SafeArrayGetLBound(psa, 1U); + int uBound = PropVariantNativeMethods.SafeArrayGetUBound(psa, 1U); + + int n = uBound - lBound + 1; // uBound is inclusive + + object[] array = new object[n]; + for (int i = lBound; i <= uBound; ++i) + { + array[i] = PropVariantNativeMethods.SafeArrayGetElement(psa, ref i); + } + + return array; + } + + /// Disposes the object, calls the clear function. + public void Dispose() + { + PropVariantNativeMethods.PropVariantClear(this); + + GC.SuppressFinalize(this); + } + + /// Finalizer + ~PropVariant() + { + Dispose(); + } + + /// Provides an simple string representation of the contained data and type. + /// + public override string ToString() => string.Format(System.Globalization.CultureInfo.InvariantCulture, + "{0}: {1}", Value, VarType.ToString()); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Core/PropertySystem/PropVariantNativeMethods.cs b/VG Music Studio - WinForms/API/Core/PropertySystem/PropVariantNativeMethods.cs new file mode 100644 index 0000000..172762f --- /dev/null +++ b/VG Music Studio - WinForms/API/Core/PropertySystem/PropVariantNativeMethods.cs @@ -0,0 +1,107 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + internal static class PropVariantNativeMethods + { + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void InitPropVariantFromBooleanVector([In, MarshalAs(UnmanagedType.LPArray)] bool[] prgf, uint cElems, [Out] PropVariant ppropvar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void InitPropVariantFromDoubleVector([In, Out] double[] prgn, uint cElems, [Out] PropVariant propvar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void InitPropVariantFromFileTime([In] ref System.Runtime.InteropServices.ComTypes.FILETIME pftIn, [Out] PropVariant ppropvar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void InitPropVariantFromFileTimeVector([In, Out] System.Runtime.InteropServices.ComTypes.FILETIME[] prgft, uint cElems, [Out] PropVariant ppropvar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void InitPropVariantFromInt16Vector([In, Out] short[] prgn, uint cElems, [Out] PropVariant ppropvar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void InitPropVariantFromInt32Vector([In, Out] int[] prgn, uint cElems, [Out] PropVariant propVar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void InitPropVariantFromInt64Vector([In, Out] long[] prgn, uint cElems, [Out] PropVariant ppropvar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void InitPropVariantFromPropVariantVectorElem([In] PropVariant propvarIn, uint iElem, [Out] PropVariant ppropvar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void InitPropVariantFromStringVector([In, Out] string[] prgsz, uint cElems, [Out] PropVariant ppropvar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void InitPropVariantFromUInt16Vector([In, Out] ushort[] prgn, uint cElems, [Out] PropVariant ppropvar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void InitPropVariantFromUInt32Vector([In, Out] uint[] prgn, uint cElems, [Out] PropVariant ppropvar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void InitPropVariantFromUInt64Vector([In, Out] ulong[] prgn, uint cElems, [Out] PropVariant ppropvar); + + [DllImport("Ole32.dll", PreserveSig = false)] // returns hresult + internal static extern void PropVariantClear([In, Out] PropVariant pvar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void PropVariantGetBooleanElem([In] PropVariant propVar, [In]uint iElem, [Out, MarshalAs(UnmanagedType.Bool)] out bool pfVal); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void PropVariantGetDoubleElem([In] PropVariant propVar, [In] uint iElem, [Out] out double pnVal); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.I4)] + internal static extern int PropVariantGetElementCount([In] PropVariant propVar); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void PropVariantGetFileTimeElem([In] PropVariant propVar, [In] uint iElem, [Out, MarshalAs(UnmanagedType.Struct)] out System.Runtime.InteropServices.ComTypes.FILETIME pftVal); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void PropVariantGetInt16Elem([In] PropVariant propVar, [In] uint iElem, [Out] out short pnVal); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void PropVariantGetInt32Elem([In] PropVariant propVar, [In] uint iElem, [Out] out int pnVal); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void PropVariantGetInt64Elem([In] PropVariant propVar, [In] uint iElem, [Out] out long pnVal); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void PropVariantGetStringElem([In] PropVariant propVar, [In] uint iElem, [MarshalAs(UnmanagedType.LPWStr)] ref string ppszVal); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void PropVariantGetUInt16Elem([In] PropVariant propVar, [In] uint iElem, [Out] out ushort pnVal); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void PropVariantGetUInt32Elem([In] PropVariant propVar, [In] uint iElem, [Out] out uint pnVal); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = false)] + internal static extern void PropVariantGetUInt64Elem([In] PropVariant propVar, [In] uint iElem, [Out] out ulong pnVal); + + [DllImport("OleAut32.dll", PreserveSig = false)] // returns hresult + internal static extern IntPtr SafeArrayAccessData(IntPtr psa); + + [DllImport("OleAut32.dll", PreserveSig = true)] // psa is actually returned, not hresult + internal static extern IntPtr SafeArrayCreateVector(ushort vt, int lowerBound, uint cElems); + + [DllImport("OleAut32.dll", PreserveSig = true)] // retuns uint32 + internal static extern uint SafeArrayGetDim(IntPtr psa); + + // This decl for SafeArrayGetElement is only valid for cDims==1! + [DllImport("OleAut32.dll", PreserveSig = false)] // returns hresult + [return: MarshalAs(UnmanagedType.IUnknown)] + internal static extern object SafeArrayGetElement(IntPtr psa, ref int rgIndices); + + [DllImport("OleAut32.dll", PreserveSig = false)] // returns hresult + internal static extern int SafeArrayGetLBound(IntPtr psa, uint nDim); + + [DllImport("OleAut32.dll", PreserveSig = false)] // returns hresult + internal static extern int SafeArrayGetUBound(IntPtr psa, uint nDim); + + [DllImport("OleAut32.dll", PreserveSig = false)] // returns hresult + internal static extern void SafeArrayUnaccessData(IntPtr psa); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Core/PropertySystem/PropertyKey.cs b/VG Music Studio - WinForms/API/Core/PropertySystem/PropertyKey.cs new file mode 100644 index 0000000..e7ffe0c --- /dev/null +++ b/VG Music Studio - WinForms/API/Core/PropertySystem/PropertyKey.cs @@ -0,0 +1,82 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Resources; +using System; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + /// Defines a unique key for a Shell Property + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct PropertyKey : IEquatable + { + private Guid formatId; + private readonly int propertyId; + + /// A unique GUID for the property + public Guid FormatId => formatId; + + /// Property identifier (PID) + public int PropertyId => propertyId; + + /// PropertyKey Constructor + /// A unique GUID for the property + /// Property identifier (PID) + public PropertyKey(Guid formatId, int propertyId) + { + this.formatId = formatId; + this.propertyId = propertyId; + } + + /// PropertyKey Constructor + /// A string represenstion of a GUID for the property + /// Property identifier (PID) + public PropertyKey(string formatId, int propertyId) + { + this.formatId = new Guid(formatId); + this.propertyId = propertyId; + } + + /// Returns whether this object is equal to another. This is vital for performance of value types. + /// The object to compare against. + /// Equality result. + public bool Equals(PropertyKey other) => other.Equals((object)this); + + /// Returns the hash code of the object. This is vital for performance of value types. + /// + public override int GetHashCode() => formatId.GetHashCode() ^ propertyId; + + /// Returns whether this object is equal to another. This is vital for performance of value types. + /// The object to compare against. + /// Equality result. + public override bool Equals(object obj) + { + if (obj == null) + return false; + + if (!(obj is PropertyKey)) + return false; + + var other = (PropertyKey)obj; + return other.formatId.Equals(formatId) && (other.propertyId == propertyId); + } + + /// Implements the == (equality) operator. + /// First property key to compare. + /// Second property key to compare. + /// true if object a equals object b. false otherwise. + public static bool operator ==(PropertyKey propKey1, PropertyKey propKey2) => propKey1.Equals(propKey2); + + /// Implements the != (inequality) operator. + /// First property key to compare + /// Second property key to compare. + /// true if object a does not equal object b. false otherwise. + public static bool operator !=(PropertyKey propKey1, PropertyKey propKey2) => !propKey1.Equals(propKey2); + + /// Override ToString() to provide a user friendly string representation + /// String representing the property key + public override string ToString() => string.Format(System.Globalization.CultureInfo.InvariantCulture, + LocalizedMessages.PropertyKeyFormatString, + formatId.ToString("B"), propertyId); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Core/Resources/LocalizedMessages.Designer.cs b/VG Music Studio - WinForms/API/Core/Resources/LocalizedMessages.Designer.cs new file mode 100644 index 0000000..c408155 --- /dev/null +++ b/VG Music Studio - WinForms/API/Core/Resources/LocalizedMessages.Designer.cs @@ -0,0 +1,684 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Kermalis.VGMusicStudio.WinForms.API.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class LocalizedMessages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal LocalizedMessages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Kermalis.VGMusicStudio.WinForms.API.Resources.LocalizedMessages", typeof(LocalizedMessages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Failed to register application for restart due to bad parameters.. + /// + internal static string ApplicationRecoverFailedToRegisterForRestartBadParameters { + get { + return ResourceManager.GetString("ApplicationRecoverFailedToRegisterForRestartBadParameters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Application was not registered for recovery due to bad parameters.. + /// + internal static string ApplicationRecoveryBadParameters { + get { + return ResourceManager.GetString("ApplicationRecoveryBadParameters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Application failed to register for recovery.. + /// + internal static string ApplicationRecoveryFailedToRegister { + get { + return ResourceManager.GetString("ApplicationRecoveryFailedToRegister", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Application failed to registered for restart.. + /// + internal static string ApplicationRecoveryFailedToRegisterForRestart { + get { + return ResourceManager.GetString("ApplicationRecoveryFailedToRegisterForRestart", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unregister for recovery failed.. + /// + internal static string ApplicationRecoveryFailedToUnregister { + get { + return ResourceManager.GetString("ApplicationRecoveryFailedToUnregister", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unregister for restart failed.. + /// + internal static string ApplicationRecoveryFailedToUnregisterForRestart { + get { + return ResourceManager.GetString("ApplicationRecoveryFailedToUnregisterForRestart", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This method must be called from the registered callback method.. + /// + internal static string ApplicationRecoveryMustBeCalledFromCallback { + get { + return ResourceManager.GetString("ApplicationRecoveryMustBeCalledFromCallback", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ACOnline: {1}{0}Max Charge: {2} mWh{0}Current Charge: {3} mWh{0}Discharge Rate: {4} mWh{0}Estimated Time Remaining: {5}{0}Suggested Critical Battery Charge: {6} mWh{0}Suggested Battery Warning Charge: {7} mWh{0}. + /// + internal static string BatteryStateStringRepresentation { + get { + return ResourceManager.GetString("BatteryStateStringRepresentation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cancelable cannot be changed while dialog is showing.. + /// + internal static string CancelableCannotBeChanged { + get { + return ResourceManager.GetString("CancelableCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dialog caption cannot be changed while dialog is showing.. + /// + internal static string CaptionCannotBeChanged { + get { + return ResourceManager.GetString("CaptionCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to CheckBox text cannot be changed while dialog is showing.. + /// + internal static string CheckBoxCannotBeChanged { + get { + return ResourceManager.GetString("CheckBoxCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Collapsed control text cannot be changed while dialog is showing.. + /// + internal static string CollapsedTextCannotBeChanged { + get { + return ResourceManager.GetString("CollapsedTextCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only supported on Windows 7 or newer.. + /// + internal static string CoreHelpersRunningOn7 { + get { + return ResourceManager.GetString("CoreHelpersRunningOn7", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only supported on Windows Vista or newer.. + /// + internal static string CoreHelpersRunningOnVista { + get { + return ResourceManager.GetString("CoreHelpersRunningOnVista", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only supported on Windows XP or newer.. + /// + internal static string CoreHelpersRunningOnXp { + get { + return ResourceManager.GetString("CoreHelpersRunningOnXp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Default Button cannot be changed while dialog is showing.. + /// + internal static string DefaultButtonCannotBeChanged { + get { + return ResourceManager.GetString("DefaultButtonCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dialog cannot have more than one control with the same name.. + /// + internal static string DialogCollectionCannotHaveDuplicateNames { + get { + return ResourceManager.GetString("DialogCollectionCannotHaveDuplicateNames", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dialog control must be removed from current collections first.. + /// + internal static string DialogCollectionControlAlreadyHosted { + get { + return ResourceManager.GetString("DialogCollectionControlAlreadyHosted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Control name cannot be null or zero length.. + /// + internal static string DialogCollectionControlNameNull { + get { + return ResourceManager.GetString("DialogCollectionControlNameNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Modifying controls collection while dialog is showing is not supported.. + /// + internal static string DialogCollectionModifyShowingDialog { + get { + return ResourceManager.GetString("DialogCollectionModifyShowingDialog", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dialog control name cannot be empty or null.. + /// + internal static string DialogControlNameCannotBeEmpty { + get { + return ResourceManager.GetString("DialogControlNameCannotBeEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dialog controls cannot be renamed.. + /// + internal static string DialogControlsCannotBeRenamed { + get { + return ResourceManager.GetString("DialogControlsCannotBeRenamed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Application. + /// + internal static string DialogDefaultCaption { + get { + return ResourceManager.GetString("DialogDefaultCaption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to . + /// + internal static string DialogDefaultContent { + get { + return ResourceManager.GetString("DialogDefaultContent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to . + /// + internal static string DialogDefaultMainInstruction { + get { + return ResourceManager.GetString("DialogDefaultMainInstruction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expanded information mode cannot be changed while dialog is showing.. + /// + internal static string ExpandedDetailsCannotBeChanged { + get { + return ResourceManager.GetString("ExpandedDetailsCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expanded control label cannot be changed while dialog is showing.. + /// + internal static string ExpandedLabelCannotBeChanged { + get { + return ResourceManager.GetString("ExpandedLabelCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expanding state of the dialog cannot be changed while dialog is showing.. + /// + internal static string ExpandingStateCannotBeChanged { + get { + return ResourceManager.GetString("ExpandingStateCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Hyperlinks cannot be enabled/disabled while dialog is showing.. + /// + internal static string HyperlinksCannotBetSet { + get { + return ResourceManager.GetString("HyperlinksCannotBetSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reference path is invalid.. + /// + internal static string InvalidReferencePath { + get { + return ResourceManager.GetString("InvalidReferencePath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified event handler has not been registered.. + /// + internal static string MessageManagerHandlerNotRegistered { + get { + return ResourceManager.GetString("MessageManagerHandlerNotRegistered", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error has occurred in dialog configuration.. + /// + internal static string NativeTaskDialogConfigurationError { + get { + return ResourceManager.GetString("NativeTaskDialogConfigurationError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid arguments to Win32 call.. + /// + internal static string NativeTaskDialogInternalErrorArgs { + get { + return ResourceManager.GetString("NativeTaskDialogInternalErrorArgs", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dialog contents too complex.. + /// + internal static string NativeTaskDialogInternalErrorComplex { + get { + return ResourceManager.GetString("NativeTaskDialogInternalErrorComplex", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An unexpected internal error occurred in the Win32 call: {0:x}. + /// + internal static string NativeTaskDialogInternalErrorUnexpected { + get { + return ResourceManager.GetString("NativeTaskDialogInternalErrorUnexpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to TaskDialog feature needs to load version 6 of comctl32.dll but a different version is current loaded in memory.. + /// + internal static string NativeTaskDialogVersionError { + get { + return ResourceManager.GetString("NativeTaskDialogVersionError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dialog owner cannot be changed while dialog is showing.. + /// + internal static string OwnerCannotBeChanged { + get { + return ResourceManager.GetString("OwnerCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SetThreadExecutionState call failed.. + /// + internal static string PowerExecutionStateFailed { + get { + return ResourceManager.GetString("PowerExecutionStateFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The caller had insufficient access rights to get the system battery state.. + /// + internal static string PowerInsufficientAccessBatteryState { + get { + return ResourceManager.GetString("PowerInsufficientAccessBatteryState", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The caller had insufficient access rights to get the system power capabilities.. + /// + internal static string PowerInsufficientAccessCapabilities { + get { + return ResourceManager.GetString("PowerInsufficientAccessCapabilities", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to get active power scheme.. + /// + internal static string PowerManagerActiveSchemeFailed { + get { + return ResourceManager.GetString("PowerManagerActiveSchemeFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Battery is not present on this system.. + /// + internal static string PowerManagerBatteryNotPresent { + get { + return ResourceManager.GetString("PowerManagerBatteryNotPresent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Progress bar cannot be changed while dialog is showing.. + /// + internal static string ProgressBarCannotBeChanged { + get { + return ResourceManager.GetString("ProgressBarCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Progress bar cannot be hosted in multiple dialogs.. + /// + internal static string ProgressBarCannotBeHostedInMultipleDialogs { + get { + return ResourceManager.GetString("ProgressBarCannotBeHostedInMultipleDialogs", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}, {1}. + /// + internal static string PropertyKeyFormatString { + get { + return ResourceManager.GetString("PropertyKeyFormatString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to initialize PropVariant.. + /// + internal static string PropVariantInitializationError { + get { + return ResourceManager.GetString("PropVariantInitializationError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Multi-dimensional SafeArrays not supported.. + /// + internal static string PropVariantMultiDimArray { + get { + return ResourceManager.GetString("PropVariantMultiDimArray", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to String argument cannot be null or empty.. + /// + internal static string PropVariantNullString { + get { + return ResourceManager.GetString("PropVariantNullString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This Value type is not supported.. + /// + internal static string PropVariantTypeNotSupported { + get { + return ResourceManager.GetString("PropVariantTypeNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot be cast to unsupported type.. + /// + internal static string PropVariantUnsupportedType { + get { + return ResourceManager.GetString("PropVariantUnsupportedType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to delegate: {0}, state: {1}, ping: {2}. + /// + internal static string RecoverySettingsFormatString { + get { + return ResourceManager.GetString("RecoverySettingsFormatString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to command: {0} restrictions: {1}. + /// + internal static string RestartSettingsFormatString { + get { + return ResourceManager.GetString("RestartSettingsFormatString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to StandardButtons cannot be changed while dialog is showing.. + /// + internal static string StandardButtonsCannotBeChanged { + get { + return ResourceManager.GetString("StandardButtonsCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Startup location cannot be changed while dialog is showing.. + /// + internal static string StartupLocationCannotBeChanged { + get { + return ResourceManager.GetString("StartupLocationCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Bad button ID in closing event.. + /// + internal static string TaskDialogBadButtonId { + get { + return ResourceManager.GetString("TaskDialogBadButtonId", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Button text must be non-empty.. + /// + internal static string TaskDialogButtonTextEmpty { + get { + return ResourceManager.GetString("TaskDialogButtonTextEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Check box text must be provided to enable the dialog check box.. + /// + internal static string TaskDialogCheckBoxTextRequiredToEnableCheckBox { + get { + return ResourceManager.GetString("TaskDialogCheckBoxTextRequiredToEnableCheckBox", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attempting to close a non-showing dialog.. + /// + internal static string TaskDialogCloseNonShowing { + get { + return ResourceManager.GetString("TaskDialogCloseNonShowing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Application. + /// + internal static string TaskDialogDefaultCaption { + get { + return ResourceManager.GetString("TaskDialogDefaultCaption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to . + /// + internal static string TaskDialogDefaultContent { + get { + return ResourceManager.GetString("TaskDialogDefaultContent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to . + /// + internal static string TaskDialogDefaultMainInstruction { + get { + return ResourceManager.GetString("TaskDialogDefaultMainInstruction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot have more than one default button of a given type.. + /// + internal static string TaskDialogOnlyOneDefaultControl { + get { + return ResourceManager.GetString("TaskDialogOnlyOneDefaultControl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maximum value provided must be greater than the minimum value.. + /// + internal static string TaskDialogProgressBarMaxValueGreaterThanMin { + get { + return ResourceManager.GetString("TaskDialogProgressBarMaxValueGreaterThanMin", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Minimum value provided must be a positive number.. + /// + internal static string TaskDialogProgressBarMinValueGreaterThanZero { + get { + return ResourceManager.GetString("TaskDialogProgressBarMinValueGreaterThanZero", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Minimum value provided must less than the maximum value.. + /// + internal static string TaskDialogProgressBarMinValueLessThanMax { + get { + return ResourceManager.GetString("TaskDialogProgressBarMinValueLessThanMax", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value provided must be greater than equal to the minimum value and less than the maximum value.. + /// + internal static string TaskDialogProgressBarValueInRange { + get { + return ResourceManager.GetString("TaskDialogProgressBarValueInRange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dialog cannot display both non-standard buttons and standard buttons.. + /// + internal static string TaskDialogSupportedButtonsAndButtons { + get { + return ResourceManager.GetString("TaskDialogSupportedButtonsAndButtons", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dialog cannot display both non-standard buttons and command links.. + /// + internal static string TaskDialogSupportedButtonsAndLinks { + get { + return ResourceManager.GetString("TaskDialogSupportedButtonsAndLinks", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unknown dialog control type.. + /// + internal static string TaskDialogUnkownControl { + get { + return ResourceManager.GetString("TaskDialogUnkownControl", resourceCulture); + } + } + } +} diff --git a/VG Music Studio - WinForms/API/Core/Resources/LocalizedMessages.resx b/VG Music Studio - WinForms/API/Core/Resources/LocalizedMessages.resx new file mode 100644 index 0000000..075c057 --- /dev/null +++ b/VG Music Studio - WinForms/API/Core/Resources/LocalizedMessages.resx @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Failed to register application for restart due to bad parameters. + + + Application was not registered for recovery due to bad parameters. + + + Application failed to register for recovery. + + + Application failed to registered for restart. + + + Unregister for recovery failed. + + + Unregister for restart failed. + + + This method must be called from the registered callback method. + + + ACOnline: {1}{0}Max Charge: {2} mWh{0}Current Charge: {3} mWh{0}Discharge Rate: {4} mWh{0}Estimated Time Remaining: {5}{0}Suggested Critical Battery Charge: {6} mWh{0}Suggested Battery Warning Charge: {7} mWh{0} + + + Cancelable cannot be changed while dialog is showing. + + + Dialog caption cannot be changed while dialog is showing. + + + CheckBox text cannot be changed while dialog is showing. + + + Collapsed control text cannot be changed while dialog is showing. + + + Only supported on Windows 7 or newer. + + + Only supported on Windows Vista or newer. + + + Only supported on Windows XP or newer. + + + Dialog cannot have more than one control with the same name. + + + Dialog control must be removed from current collections first. + + + Control name cannot be null or zero length. + + + Modifying controls collection while dialog is showing is not supported. + + + Dialog control name cannot be empty or null. + + + Dialog controls cannot be renamed. + + + Application + + + + + + + + + Expanded information mode cannot be changed while dialog is showing. + + + Expanded control label cannot be changed while dialog is showing. + + + Expanding state of the dialog cannot be changed while dialog is showing. + + + Hyperlinks cannot be enabled/disabled while dialog is showing. + + + Reference path is invalid. + + + The specified event handler has not been registered. + + + An error has occurred in dialog configuration. + + + Invalid arguments to Win32 call. + + + Dialog contents too complex. + + + An unexpected internal error occurred in the Win32 call: {0:x} + + + TaskDialog feature needs to load version 6 of comctl32.dll but a different version is current loaded in memory. + + + Dialog owner cannot be changed while dialog is showing. + + + SetThreadExecutionState call failed. + + + The caller had insufficient access rights to get the system battery state. + + + The caller had insufficient access rights to get the system power capabilities. + + + Battery is not present on this system. + + + Progress bar cannot be changed while dialog is showing. + + + Progress bar cannot be hosted in multiple dialogs. + + + {0}, {1} + + + Multi-dimensional SafeArrays not supported. + + + String argument cannot be null or empty. + + + This Value type is not supported. + + + delegate: {0}, state: {1}, ping: {2} + + + command: {0} restrictions: {1} + + + StandardButtons cannot be changed while dialog is showing. + + + Startup location cannot be changed while dialog is showing. + + + Bad button ID in closing event. + + + Button text must be non-empty. + + + Check box text must be provided to enable the dialog check box. + + + Attempting to close a non-showing dialog. + + + Application + + + + + + + + + Cannot have more than one default button of a given type. + + + Maximum value provided must be greater than the minimum value. + + + Minimum value provided must be a positive number. + + + Minimum value provided must less than the maximum value. + + + Value provided must be greater than equal to the minimum value and less than the maximum value. + + + Dialog cannot display both non-standard buttons and standard buttons. + + + Dialog cannot display both non-standard buttons and command links. + + + Unknown dialog control type. + + + Unable to initialize PropVariant. + + + Failed to get active power scheme. + + + Cannot be cast to unsupported type. + + + Default Button cannot be changed while dialog is showing. + + \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/DefaultShellImageSizes.cs b/VG Music Studio - WinForms/API/Shell/Common/DefaultShellImageSizes.cs new file mode 100644 index 0000000..49b54ef --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/DefaultShellImageSizes.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Defines the read-only properties for default shell icon sizes. + public static class DefaultIconSize + { + /// The extra-large size property for a 256x256 pixel Shell Icon. + public static readonly System.Windows.Size ExtraLarge = new System.Windows.Size(256, 256); + + /// The large size property for a 48x48 pixel Shell Icon. + public static readonly System.Windows.Size Large = new System.Windows.Size(48, 48); + + /// The maximum size for a Shell Icon, 256x256 pixels. + public static readonly System.Windows.Size Maximum = new System.Windows.Size(256, 256); + + /// The medium size property for a 32x32 pixel Shell Icon. + public static readonly System.Windows.Size Medium = new System.Windows.Size(32, 32); + + /// The small size property for a 16x16 pixel Shell Icon. + public static readonly System.Windows.Size Small = new System.Windows.Size(16, 16); + } + + /// Defines the read-only properties for default shell thumbnail sizes. + public static class DefaultThumbnailSize + { + /// Gets the extra-large size property for a 1024x1024 pixel Shell Thumbnail. + public static readonly System.Windows.Size ExtraLarge = new System.Windows.Size(1024, 1024); + + /// Gets the large size property for a 256x256 pixel Shell Thumbnail. + public static readonly System.Windows.Size Large = new System.Windows.Size(256, 256); + + /// Maximum size for the Shell Thumbnail, 1024x1024 pixels. + public static readonly System.Windows.Size Maximum = new System.Windows.Size(1024, 1024); + + /// Gets the medium size property for a 96x96 pixel Shell Thumbnail. + public static readonly System.Windows.Size Medium = new System.Windows.Size(96, 96); + + /// Gets the small size property for a 32x32 pixel Shell Thumbnail. + public static readonly System.Windows.Size Small = new System.Windows.Size(32, 32); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/IconReference.cs b/VG Music Studio - WinForms/API/Shell/Common/IconReference.cs new file mode 100644 index 0000000..54955a1 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/IconReference.cs @@ -0,0 +1,130 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Resources; +using System; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// A refence to an icon resource + public struct IconReference + { + private static readonly char[] commaSeparator = new char[] { ',' }; + private string moduleName; + private string referencePath; + + /// Overloaded constructor takes in the module name and resource id for the icon reference. + /// String specifying the name of an executable file, DLL, or icon file + /// Zero-based index of the icon + public IconReference(string moduleName, int resourceId) + : this() + { + if (string.IsNullOrEmpty(moduleName)) + { + throw new ArgumentNullException("moduleName"); + } + + this.moduleName = moduleName; + ResourceId = resourceId; + referencePath = string.Format(System.Globalization.CultureInfo.InvariantCulture, + "{0},{1}", moduleName, resourceId); + } + + /// Overloaded constructor takes in the module name and resource id separated by a comma. + /// Reference path for the icon consiting of the module name and resource id. + public IconReference(string refPath) + : this() + { + if (string.IsNullOrEmpty(refPath)) + { + throw new ArgumentNullException("refPath"); + } + + var refParams = refPath.Split(commaSeparator); + + if (refParams.Length != 2 || string.IsNullOrEmpty(refParams[0]) || string.IsNullOrEmpty(refParams[1])) + { + throw new ArgumentException(LocalizedMessages.InvalidReferencePath, "refPath"); + } + + moduleName = refParams[0]; + ResourceId = int.Parse(refParams[1], System.Globalization.CultureInfo.InvariantCulture); + + referencePath = refPath; + } + + /// String specifying the name of an executable file, DLL, or icon file + public string ModuleName + { + get => moduleName; + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentNullException("value"); + } + moduleName = value; + } + } + + /// Reference to a specific icon within a EXE, DLL or icon file. + public string ReferencePath + { + get => referencePath; + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentNullException("value"); + } + + var refParams = value.Split(commaSeparator); + + if (refParams.Length != 2 || string.IsNullOrEmpty(refParams[0]) || string.IsNullOrEmpty(refParams[1])) + { + throw new ArgumentException(LocalizedMessages.InvalidReferencePath, "value"); + } + + ModuleName = refParams[0]; + ResourceId = int.Parse(refParams[1], System.Globalization.CultureInfo.InvariantCulture); + + referencePath = value; + } + } + + /// Zero-based index of the icon + public int ResourceId { get; set; } + + /// Implements the != (unequality) operator. + /// First object to compare. + /// Second object to compare. + /// True if icon1 does not equals icon1; false otherwise. + public static bool operator !=(IconReference icon1, IconReference icon2) => !(icon1 == icon2); + + /// Implements the == (equality) operator. + /// First object to compare. + /// Second object to compare. + /// True if icon1 equals icon1; false otherwise. + public static bool operator ==(IconReference icon1, IconReference icon2) => (icon1.moduleName == icon2.moduleName) && + (icon1.referencePath == icon2.referencePath) && + (icon1.ResourceId == icon2.ResourceId); + + /// Determines if this object is equal to another. + /// The object to compare + /// Returns true if the objects are equal; false otherwise. + public override bool Equals(object obj) + { + if (obj == null || !(obj is IconReference)) { return false; } + return (this == (IconReference)obj); + } + + /// Generates a nearly unique hashcode for this structure. + /// A hash code. + public override int GetHashCode() + { + var hash = moduleName.GetHashCode(); + hash = hash * 31 + referencePath.GetHashCode(); + hash = hash * 31 + ResourceId.GetHashCode(); + return hash; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/NativePoint.cs b/VG Music Studio - WinForms/API/Shell/Common/NativePoint.cs new file mode 100644 index 0000000..bc71a4b --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/NativePoint.cs @@ -0,0 +1,52 @@ +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// A wrapper for the native POINT structure. + [StructLayout(LayoutKind.Sequential)] + public struct NativePoint + { + /// Initialize the NativePoint + /// The x coordinate of the point. + /// The y coordinate of the point. + public NativePoint(int x, int y) + : this() + { + X = x; + Y = y; + } + + /// The X coordinate of the point + public int X { get; set; } + + /// The Y coordinate of the point + public int Y { get; set; } + + /// Determines if two NativePoints are equal. + /// First NativePoint + /// Second NativePoint + /// True if first NativePoint is equal to the second; false otherwise. + public static bool operator ==(NativePoint first, NativePoint second) => first.X == second.X + && first.Y == second.Y; + + /// Determines if two NativePoints are not equal. + /// First NativePoint + /// Second NativePoint + /// True if first NativePoint is not equal to the second; false otherwise. + public static bool operator !=(NativePoint first, NativePoint second) => !(first == second); + + /// Determines if this NativePoint is equal to another. + /// Another NativePoint to compare + /// True if this NativePoint is equal obj; false otherwise. + public override bool Equals(object obj) => (obj != null && obj is NativePoint) ? this == (NativePoint)obj : false; + + /// Gets a hash code for the NativePoint. + /// Hash code for the NativePoint + public override int GetHashCode() + { + var hash = X.GetHashCode(); + hash = hash * 31 + Y.GetHashCode(); + return hash; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/NativeRect.cs b/VG Music Studio - WinForms/API/Shell/Common/NativeRect.cs new file mode 100644 index 0000000..4e8ba26 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/NativeRect.cs @@ -0,0 +1,72 @@ +using Kermalis.VGMusicStudio.WinForms.API.Shell.Interop; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// A wrapper for a RECT struct + [StructLayout(LayoutKind.Sequential)] + public struct NativeRect + { + /// Position of left edge + public int Left { get; set; } + + /// Position of top edge + public int Top { get; set; } + + /// Position of right edge + public int Right { get; set; } + + /// Position of bottom edge + public int Bottom { get; set; } + + /// Creates a new NativeRect initialized with supplied values. + /// Position of left edge + /// Position of top edge + /// Position of right edge + /// Position of bottom edge + public NativeRect(int left, int top, int right, int bottom) + : this() + { + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } + + /// Determines if two NativeRects are equal. + /// First NativeRect + /// Second NativeRect + /// True if first NativeRect is equal to second; false otherwise. + public static bool operator ==(NativeRect first, NativeRect second) => first.Left == second.Left + && first.Top == second.Top + && first.Right == second.Right + && first.Bottom == second.Bottom; + + /// Determines if two NativeRects are not equal + /// First NativeRect + /// Second NativeRect + /// True if first is not equal to second; false otherwise. + public static bool operator !=(NativeRect first, NativeRect second) => !(first == second); + + /// Determines if the NativeRect is equal to another Rect. + /// Another NativeRect to compare + /// True if this NativeRect is equal to the one provided; false otherwise. + public override bool Equals(object obj) => (obj != null && obj is NativeRect) ? this == (NativeRect)obj : false; + + /// Creates a hash code for the NativeRect + /// Returns hash code for this NativeRect + public override int GetHashCode() + { + var hash = Left.GetHashCode(); + hash = hash * 31 + Top.GetHashCode(); + hash = hash * 31 + Right.GetHashCode(); + hash = hash * 31 + Bottom.GetHashCode(); + return hash; + } + + /// Performs a conversion from to . + /// The RECT. + /// The result of the conversion. + internal static NativeRect FromRECT(RECT r) => new NativeRect(r.left, r.top, r.right, r.bottom); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellEnums.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellEnums.cs new file mode 100644 index 0000000..30150b6 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellEnums.cs @@ -0,0 +1,360 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// One of the values that indicates how the ShellObject DisplayName should look. + public enum DisplayNameType + { + /// Returns the display name relative to the desktop. + Default = 0x00000000, + + /// Returns the parsing name relative to the parent folder. + RelativeToParent = unchecked((int)0x80018001), + + /// Returns the path relative to the parent folder in a friendly format as displayed in an address bar. + RelativeToParentAddressBar = unchecked((int)0x8007c001), + + /// Returns the parsing name relative to the desktop. + RelativeToDesktop = unchecked((int)0x80028000), + + /// Returns the editing name relative to the parent folder. + RelativeToParentEditing = unchecked((int)0x80031001), + + /// Returns the editing name relative to the desktop. + RelativeToDesktopEditing = unchecked((int)0x8004c000), + + /// Returns the display name relative to the file system path. + FileSystemPath = unchecked((int)0x80058000), + + /// Returns the display name relative to a URL. + Url = unchecked((int)0x80068000), + } + + /// CommonFileDialog AddPlace locations + public enum FileDialogAddPlaceLocation + { + /// At the bottom of the Favorites or Places list. + Bottom = 0x00000000, + + /// At the top of the Favorites or Places list. + Top = 0x00000001, + } + + /// Used to describe the view mode. + public enum FolderLogicalViewMode + { + /// The view is not specified. + Unspecified = -1, + + /// This should have the same affect as Unspecified. + None = 0, + + /// The minimum valid enumeration value. Used for validation purposes only. + First = 1, + + /// Details view. + Details = 1, + + /// Tiles view. + Tiles = 2, + + /// Icons view. + Icons = 3, + + /// Windows 7 and later. List view. + List = 4, + + /// Windows 7 and later. Content view. + Content = 5, + + /// The maximum valid enumeration value. Used for validation purposes only. + Last = 5 + } + + /// Available Library folder types + public enum LibraryFolderType + { + /// General Items + Generic = 0, + + /// Documents + Documents, + + /// Music + Music, + + /// Pictures + Pictures, + + /// Videos + Videos + } + + /// + /// Used by IQueryParserManager::SetOption to set parsing options. This can be used to specify schemas and localization options. + /// + public enum QueryParserManagerOption + { + /// + /// A VT_LPWSTR containing the name of the file that contains the schema binary. The default value is StructuredQuerySchema.bin for + /// the SystemIndex catalog and StructuredQuerySchemaTrivial.bin for the trivial catalog. + /// + SchemaBinaryName = 0, + + /// + /// Either a VT_BOOL or a VT_LPWSTR. If the value is a VT_BOOL and is FALSE, a pre-localized schema will not be used. If the value is + /// a VT_BOOL and is TRUE, IQueryParserManager will use the pre-localized schema binary in "%ALLUSERSPROFILE%\Microsoft\Windows". If + /// the value is a VT_LPWSTR, the value should contain the full path of the folder in which the pre-localized schema binary can be + /// found. The default value is VT_BOOL with TRUE. + /// + PreLocalizedSchemaBinaryPath = 1, + + /// + /// A VT_LPWSTR containing the full path to the folder that contains the unlocalized schema binary. The default value is "%SYSTEMROOT%\System32". + /// + UnlocalizedSchemaBinaryPath = 2, + + /// + /// A VT_LPWSTR containing the full path to the folder that contains the localized schema binary that can be read and written to as + /// needed. The default value is "%LOCALAPPDATA%\Microsoft\Windows". + /// + LocalizedSchemaBinaryPath = 3, + + /// + /// A VT_BOOL. If TRUE, then the paths for pre-localized and localized binaries have "\(LCID)" appended to them, where language code + /// identifier (LCID) is the decimal locale ID for the localized language. The default is TRUE. + /// + AppendLCIDToLocalizedPath = 4, + + /// + /// A VT_UNKNOWN with an object supporting ISchemaLocalizerSupport. This object will be used instead of the default localizer support object. + /// + LocalizerSupport = 5 + } + + /// + /// Provides a set of flags to be used with to indicate the operation in + /// methods. + /// + public enum SearchConditionOperation + { + /// An implicit comparison between the value of the property and the value of the constant. + Implicit = 0, + + /// The value of the property and the value of the constant must be equal. + Equal = 1, + + /// The value of the property and the value of the constant must not be equal. + NotEqual = 2, + + /// The value of the property must be less than the value of the constant. + LessThan = 3, + + /// The value of the property must be greater than the value of the constant. + GreaterThan = 4, + + /// The value of the property must be less than or equal to the value of the constant. + LessThanOrEqual = 5, + + /// The value of the property must be greater than or equal to the value of the constant. + GreaterThanOrEqual = 6, + + /// The value of the property must begin with the value of the constant. + ValueStartsWith = 7, + + /// The value of the property must end with the value of the constant. + ValueEndsWith = 8, + + /// The value of the property must contain the value of the constant. + ValueContains = 9, + + /// The value of the property must not contain the value of the constant. + ValueNotContains = 10, + + /// + /// The value of the property must match the value of the constant, where '?' matches any single character and '*' matches any + /// sequence of characters. + /// + DosWildcards = 11, + + /// The value of the property must contain a word that is the value of the constant. + WordEqual = 12, + + /// The value of the property must contain a word that begins with the value of the constant. + WordStartsWith = 13, + + /// The application is free to interpret this in any suitable way. + ApplicationSpecific = 14 + } + + /// Set of flags to be used with . + public enum SearchConditionType + { + /// Indicates that the values of the subterms are combined by "AND". + And = 0, + + /// Indicates that the values of the subterms are combined by "OR". + Or = 1, + + /// Indicates a "NOT" comparison of subterms. + Not = 2, + + /// Indicates that the node is a comparison between a property and a constant value using a . + Leaf = 3, + } + + /// The direction in which the items are sorted. + public enum SortDirection + { + /// A default value for sort direction, this value should not be used; instead use Descending or Ascending. + Default = 0, + + /// + /// The items are sorted in descending order. Whether the sort is alphabetical, numerical, and so on, is determined by the data type + /// of the column indicated in propkey. + /// + Descending = -1, + + /// + /// The items are sorted in ascending order. Whether the sort is alphabetical, numerical, and so on, is determined by the data type + /// of the column indicated in propkey. + /// + Ascending = 1, + } + + /// Provides a set of flags to be used with IQueryParser::SetMultiOption to indicate individual options. + public enum StructuredQueryMultipleOption + { + /// + /// The key should be property name P. The value should be a VT_UNKNOWN with an IEnumVARIANT which has two values: a VT_BSTR that is + /// another property name Q and a VT_I4 that is a CONDITION_OPERATION cop. A predicate with property name P, some operation and a + /// value V will then be replaced by a predicate with property name Q, operation cop and value V before further processing happens. + /// + VirtualProperty, + + /// + /// The key should be a value type name V. The value should be a VT_LPWSTR with a property name P. A predicate with no property name + /// and a value of type V (or any subtype of V) will then use property P. + /// + DefaultProperty, + + /// + /// The key should be a value type name V. The value should be a VT_UNKNOWN with a IConditionGenerator G. The GenerateForLeaf method + /// of G will then be applied to any predicate with value type V and if it returns a query expression, that will be used. If it + /// returns NULL, normal processing will be used instead. + /// + GeneratorForType, + + /// + /// The key should be a property name P. The value should be a VT_VECTOR|VT_LPWSTR, where each string is a property name. The count + /// must be at least one. This "map" will be added to those of the loaded schema and used during resolution. A second call with the + /// same key will replace the current map. If the value is VT_NULL, the map will be removed. + /// + MapProperty, + } + + /// Provides a set of flags to be used with IQueryParser::SetOption and IQueryParser::GetOption to indicate individual options. + public enum StructuredQuerySingleOption + { + /// The value should be VT_LPWSTR and the path to a file containing a schema binary. + Schema, + + /// + /// The value must be VT_EMPTY (the default) or a VT_UI4 that is an LCID. It is used as the locale of contents (not keywords) in the + /// query to be searched for, when no other information is available. The default value is the current keyboard locale. Retrieving + /// the value always returns a VT_UI4. + /// + Locale, + + /// + /// This option is used to override the default word breaker used when identifying keywords in queries. The default word breaker is + /// chosen according to the language of the keywords (cf. SQSO_LANGUAGE_KEYWORDS below). When setting this option, the value should + /// be VT_EMPTY for using the default word breaker, or a VT_UNKNOWN with an object supporting the IWordBreaker interface. Retrieving + /// the option always returns a VT_UNKNOWN with an object supporting the IWordBreaker interface. + /// + WordBreaker, + + /// + /// The value should be VT_EMPTY or VT_BOOL with VARIANT_TRUE to allow natural query syntax (the default) or VT_BOOL with + /// VARIANT_FALSE to allow only advanced query syntax. Retrieving the option always returns a VT_BOOL. This option is now deprecated, + /// use SQSO_SYNTAX. + /// + NaturalSyntax, + + /// + /// The value should be VT_BOOL with VARIANT_TRUE to generate query expressions as if each word in the query had a star appended to + /// it (unless followed by punctuation other than a parenthesis), or VT_EMPTY or VT_BOOL with VARIANT_FALSE to use the words as they + /// are (the default). A word-wheeling application will generally want to set this option to true. Retrieving the option always + /// returns a VT_BOOL. + /// + AutomaticWildcard, + + /// Reserved. The value should be VT_EMPTY (the default) or VT_I4. Retrieving the option always returns a VT_I4. + TraceLevel, + + /// The value must be a VT_UI4 that is a LANGID. It defaults to the default user UI language. + LanguageKeywords, + + /// The value must be a VT_UI4 that is a STRUCTURED_QUERY_SYNTAX value. It defaults to SQS_NATURAL_QUERY_SYNTAX. + Syntax, + + /// + /// The value must be a VT_BLOB that is a copy of a TIME_ZONE_INFORMATION structure. It defaults to the current time zone. + /// + TimeZone, + + /// + /// This setting decides what connector should be assumed between conditions when none is specified. The value must be a VT_UI4 that + /// is a CONDITION_TYPE. Only CT_AND_CONDITION and CT_OR_CONDITION are valid. It defaults to CT_AND_CONDITION. + /// + ImplicitConnector, + + /// + /// This setting decides whether there are special requirements on the case of connector keywords (such as AND or OR). The value must + /// be a VT_UI4 that is a CASE_REQUIREMENT value. It defaults to CASE_REQUIREMENT_UPPER_IF_AQS. + /// + ConnectorCase, + } + + /// Flags controlling the appearance of a window + public enum WindowShowCommand + { + /// Hides the window and activates another window. + Hide = 0, + + /// Activates and displays the window (including restoring it to its original size and position). + Normal = 1, + + /// Minimizes the window. + Minimized = 2, + + /// Maximizes the window. + Maximized = 3, + + /// Similar to , except that the window is not activated. + ShowNoActivate = 4, + + /// Activates the window and displays it in its current size and position. + Show = 5, + + /// Minimizes the window and activates the next top-level window. + Minimize = 6, + + /// Minimizes the window and does not activate it. + ShowMinimizedNoActivate = 7, + + /// Similar to , except that the window is not activated. + ShowNA = 8, + + /// Activates and displays the window, restoring it to its original size and position. + Restore = 9, + + /// Sets the show state based on the initial value specified when the process was created. + Default = 10, + + /// + /// Minimizes a window, even if the thread owning the window is not responding. Use this only to minimize windows from a different thread. + /// + ForceMinimize = 11 + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellException.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellException.cs new file mode 100644 index 0000000..f1362d4 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellException.cs @@ -0,0 +1,58 @@ +using System; +using System.Runtime.InteropServices; +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// An exception thrown when an error occurs while dealing with ShellObjects. + [Serializable] + public class ShellException : ExternalException + { + /// Default constructor. + public ShellException() { } + + /// Initializes an excpetion with a custom message. + /// Custom message + public ShellException(string message) : base(message) { } + + /// Initializes an exception with custom message and inner exception. + /// Custom message + /// The original exception that preceded this exception + public ShellException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// Initializes an exception with custom message and error code. + /// Custom message + /// HResult error code + public ShellException(string message, int errorCode) : base(message, errorCode) { } + + /// Initializes an exception with custom message and inner exception. + /// HRESULT of an operation + public ShellException(int errorCode) + : base(LocalizedMessages.ShellExceptionDefaultText, errorCode) + { + } + + /// Initializes a new exception using an HResult + /// HResult error + internal ShellException(HResult result) : this((int)result) { } + + /// Initializes an exception with custom message and error code. + /// + /// + internal ShellException(string message, HResult errorCode) : this(message, (int)errorCode) { } + + /// Initializes an exception from serialization info and a context. + /// + /// + protected ShellException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellFile.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellFile.cs new file mode 100644 index 0000000..f25d91d --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellFile.cs @@ -0,0 +1,38 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using System.IO; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// A file in the Shell Namespace + public class ShellFile : ShellObject + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + internal ShellFile(string path) + { + // Get the absolute path + var absPath = ShellHelper.GetAbsolutePath(path); + + // Make sure this is valid + if (!File.Exists(absPath)) + { + throw new FileNotFoundException( + string.Format(System.Globalization.CultureInfo.InvariantCulture, + LocalizedMessages.FilePathNotExist, path)); + } + + ParsingName = absPath; + } + + internal ShellFile(IShellItem2 shellItem) => nativeShellItem = shellItem; + + /// The path for this file + public virtual string Path => ParsingName; + + /// Constructs a new ShellFile object given a file path + /// The file or folder path + /// ShellFile object created using given file path. + public static ShellFile FromFilePath(string path) => new ShellFile(path); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellFileSystemFolder.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellFileSystemFolder.cs new file mode 100644 index 0000000..06e2a9c --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellFileSystemFolder.cs @@ -0,0 +1,50 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using System.IO; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// A folder in the Shell Namespace + public class ShellFileSystemFolder : ShellFolder + { + internal ShellFileSystemFolder() + { + // Empty + } + + internal ShellFileSystemFolder(IShellItem2 shellItem) => nativeShellItem = shellItem; + + /// The path for this Folder + public virtual string Path => ParsingName; + + /// Constructs a new ShellFileSystemFolder object given a folder path + /// The folder path + /// ShellFileSystemFolder created from the given folder path. + public static ShellFileSystemFolder FromFolderPath(string path) + { + // Get the absolute path + var absPath = ShellHelper.GetAbsolutePath(path); + + // Make sure this is valid + if (!Directory.Exists(absPath)) + { + throw new DirectoryNotFoundException( + string.Format(System.Globalization.CultureInfo.InvariantCulture, + LocalizedMessages.FilePathNotExist, path)); + } + + var folder = new ShellFileSystemFolder(); + try + { + folder.ParsingName = absPath; + return folder; + } + catch + { + folder.Dispose(); + throw; + } + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellFolder.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellFolder.cs new file mode 100644 index 0000000..b3e8cb1 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellFolder.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Represents the base class for all types of folders (filesystem and non filesystem) + public abstract class ShellFolder : ShellContainer + { + // empty + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellFolderItems.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellFolderItems.cs new file mode 100644 index 0000000..f1dbaf0 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellFolderItems.cs @@ -0,0 +1,77 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + internal class ShellFolderItems : IEnumerator + { + private readonly ShellContainer nativeShellFolder; + private ShellObject currentItem; + private IEnumIDList nativeEnumIdList; + + internal ShellFolderItems(ShellContainer nativeShellFolder) + { + this.nativeShellFolder = nativeShellFolder; + + var hr = nativeShellFolder.NativeShellFolder.EnumObjects( + IntPtr.Zero, + ShellNativeMethods.ShellFolderEnumerationOptions.Folders | ShellNativeMethods.ShellFolderEnumerationOptions.NonFolders, + out nativeEnumIdList); + + if (!CoreErrorHelper.Succeeded(hr)) + { + if (hr == HResult.Canceled) + { + throw new System.IO.FileNotFoundException(); + } + else + { + throw new ShellException(hr); + } + } + } + + public ShellObject Current => currentItem; + + object IEnumerator.Current => currentItem; + + public void Dispose() + { + if (nativeEnumIdList != null) + { + Marshal.ReleaseComObject(nativeEnumIdList); + nativeEnumIdList = null; + } + } + + /// + /// + public bool MoveNext() + { + if (nativeEnumIdList == null) { return false; } + + uint itemsRequested = 1; + var hr = nativeEnumIdList.Next(itemsRequested, out var item, out var numItemsReturned); + + if (numItemsReturned < itemsRequested || hr != HResult.Ok) { return false; } + + currentItem = ShellObjectFactory.Create(item, nativeShellFolder); + + return true; + } + + /// + public void Reset() + { + if (nativeEnumIdList != null) + { + nativeEnumIdList.Reset(); + } + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellHelper.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellHelper.cs new file mode 100644 index 0000000..2f258d0 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellHelper.cs @@ -0,0 +1,82 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem; +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// A helper class for Shell Objects + internal static class ShellHelper + { + internal static PropertyKey ItemTypePropertyKey = new PropertyKey(new Guid("28636AA6-953D-11D2-B5D6-00C04FD918D0"), 11); + + internal static string GetAbsolutePath(string path) + { + if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) + { + return path; + } + return Path.GetFullPath((path)); + } + + internal static string GetItemType(IShellItem2 shellItem) + { + if (shellItem != null) + { + var hr = shellItem.GetString(ref ItemTypePropertyKey, out var itemType); + if (hr == HResult.Ok) { return itemType; } + } + + return null; + } + + internal static string GetParsingName(IShellItem shellItem) + { + if (shellItem == null) { return null; } + + string path = null; + + var pszPath = IntPtr.Zero; + var hr = shellItem.GetDisplayName(ShellNativeMethods.ShellItemDesignNameOptions.DesktopAbsoluteParsing, out pszPath); + + if (hr != HResult.Ok && hr != HResult.InvalidArguments) + { + throw new ShellException(LocalizedMessages.ShellHelperGetParsingNameFailed, hr); + } + + if (pszPath != IntPtr.Zero) + { + path = Marshal.PtrToStringAuto(pszPath); + Marshal.FreeCoTaskMem(pszPath); + pszPath = IntPtr.Zero; + } + + return path; + } + + internal static IntPtr PidlFromParsingName(string name) + { + var retCode = ShellNativeMethods.SHParseDisplayName( + name, IntPtr.Zero, out var pidl, 0, + out var sfgao); + + return (CoreErrorHelper.Succeeded(retCode) ? pidl : IntPtr.Zero); + } + + internal static IntPtr PidlFromShellItem(IShellItem nativeShellItem) + { + var unknown = Marshal.GetIUnknownForObject(nativeShellItem); + return PidlFromUnknown(unknown); + } + + internal static IntPtr PidlFromUnknown(IntPtr unknown) + { + var retCode = ShellNativeMethods.SHGetIDListFromObject(unknown, out var pidl); + return (CoreErrorHelper.Succeeded(retCode) ? pidl : IntPtr.Zero); + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellLibrary.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellLibrary.cs new file mode 100644 index 0000000..eaf8ffc --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellLibrary.cs @@ -0,0 +1,694 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// A Shell Library in the Shell Namespace + public sealed class ShellLibrary : ShellContainer, IList + { + internal const string FileExtension = ".library-ms"; + + private static readonly Guid[] FolderTypesGuids = + { + new Guid(ShellKFIDGuid.GenericLibrary), + new Guid(ShellKFIDGuid.DocumentsLibrary), + new Guid(ShellKFIDGuid.MusicLibrary), + new Guid(ShellKFIDGuid.PicturesLibrary), + new Guid(ShellKFIDGuid.VideosLibrary) + }; + + private readonly IKnownFolder knownFolder; + private INativeShellLibrary nativeShellLibrary; + + /// Creates a shell library in the Libraries Known Folder, using the given shell library name. + /// The name of this library + /// Allow overwriting an existing library; if one exists with the same name + public ShellLibrary(string libraryName, bool overwrite) + : this() + { + if (string.IsNullOrEmpty(libraryName)) + { + throw new ArgumentException(LocalizedMessages.ShellLibraryEmptyName, "libraryName"); + } + + Name = libraryName; + var guid = new Guid(ShellKFIDGuid.Libraries); + + var flags = overwrite ? + ShellNativeMethods.LibrarySaveOptions.OverrideExisting : + ShellNativeMethods.LibrarySaveOptions.FailIfThere; + + nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); + nativeShellLibrary.SaveInKnownFolder(ref guid, libraryName, flags, out nativeShellItem); + } + + /// Creates a shell library in a given Known Folder, using the given shell library name. + /// The name of this library + /// The known folder + /// Override an existing library with the same name + public ShellLibrary(string libraryName, IKnownFolder sourceKnownFolder, bool overwrite) + : this() + { + if (string.IsNullOrEmpty(libraryName)) + { + throw new ArgumentException(LocalizedMessages.ShellLibraryEmptyName, "libraryName"); + } + + knownFolder = sourceKnownFolder; + + Name = libraryName; + var guid = knownFolder.FolderId; + + var flags = overwrite ? + ShellNativeMethods.LibrarySaveOptions.OverrideExisting : + ShellNativeMethods.LibrarySaveOptions.FailIfThere; + + nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); + nativeShellLibrary.SaveInKnownFolder(ref guid, libraryName, flags, out nativeShellItem); + } + + /// Creates a shell library in a given local folder, using the given shell library name. + /// The name of this library + /// The path to the local folder + /// Override an existing library with the same name + public ShellLibrary(string libraryName, string folderPath, bool overwrite) + : this() + { + if (string.IsNullOrEmpty(libraryName)) + { + throw new ArgumentException(LocalizedMessages.ShellLibraryEmptyName, "libraryName"); + } + + if (!Directory.Exists(folderPath)) + { + throw new DirectoryNotFoundException(LocalizedMessages.ShellLibraryFolderNotFound); + } + + Name = libraryName; + + var flags = overwrite ? + ShellNativeMethods.LibrarySaveOptions.OverrideExisting : + ShellNativeMethods.LibrarySaveOptions.FailIfThere; + + var guid = new Guid(ShellIIDGuid.IShellItem); + + ShellNativeMethods.SHCreateItemFromParsingName(folderPath, IntPtr.Zero, ref guid, out + IShellItem shellItemIn); + + nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); + nativeShellLibrary.Save(shellItemIn, libraryName, flags, out nativeShellItem); + } + + private ShellLibrary() => CoreHelpers.ThrowIfNotWin7(); + + //Construct the ShellLibrary object from a native Shell Library + private ShellLibrary(INativeShellLibrary nativeShellLibrary) + : this() => this.nativeShellLibrary = nativeShellLibrary; + + /// Creates a shell library in the Libraries Known Folder, using the given IKnownFolder + /// KnownFolder from which to create the new Shell Library + /// If true , opens the library in read-only mode. + private ShellLibrary(IKnownFolder sourceKnownFolder, bool isReadOnly) + : this() + { + Debug.Assert(sourceKnownFolder != null); + + // Keep a reference locally + knownFolder = sourceKnownFolder; + + nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); + + var flags = isReadOnly ? + AccessModes.Read : + AccessModes.ReadWrite; + + // Get the IShellItem2 + base.nativeShellItem = ((ShellObject)sourceKnownFolder).NativeShellItem2; + + var guid = sourceKnownFolder.FolderId; + + // Load the library from the IShellItem2 + try + { + nativeShellLibrary.LoadLibraryFromKnownFolder(ref guid, flags); + } + catch (InvalidCastException) + { + throw new ArgumentException(LocalizedMessages.ShellLibraryInvalidLibrary, "sourceKnownFolder"); + } + catch (NotImplementedException) + { + throw new ArgumentException(LocalizedMessages.ShellLibraryInvalidLibrary, "sourceKnownFolder"); + } + } + + /// Release resources + ~ShellLibrary() + { + Dispose(false); + } + + /// Indicates whether this feature is supported on the current platform. + public static new bool IsPlatformSupported => + // We need Windows 7 onwards ... + CoreHelpers.RunningOnWin7; + + /// Get a the known folder FOLDERID_Libraries + public static IKnownFolder LibrariesKnownFolder + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return KnownFolderHelper.FromKnownFolderId(new Guid(ShellKFIDGuid.Libraries)); + } + } + + /// + /// By default, this folder is the first location added to the library. The default save folder is both the default folder where + /// files can be saved, and also where the library XML file will be saved, if no other path is specified + /// + public string DefaultSaveFolder + { + get + { + var guid = new Guid(ShellIIDGuid.IShellItem); + + nativeShellLibrary.GetDefaultSaveFolder( + ShellNativeMethods.DefaultSaveFolderType.Detect, + ref guid, + out var saveFolderItem); + + return ShellHelper.GetParsingName(saveFolderItem); + } + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentNullException("value"); + } + + if (!Directory.Exists(value)) + { + throw new DirectoryNotFoundException(LocalizedMessages.ShellLibraryDefaultSaveFolderNotFound); + } + + var fullPath = new DirectoryInfo(value).FullName; + + var guid = new Guid(ShellIIDGuid.IShellItem); + + ShellNativeMethods.SHCreateItemFromParsingName(fullPath, IntPtr.Zero, ref guid, out IShellItem saveFolderItem); + + nativeShellLibrary.SetDefaultSaveFolder( + ShellNativeMethods.DefaultSaveFolderType.Detect, + saveFolderItem); + + nativeShellLibrary.Commit(); + } + } + + /// The Resource Reference to the icon. + public IconReference IconResourceId + { + get + { + nativeShellLibrary.GetIcon(out var iconRef); + return new IconReference(iconRef); + } + + set + { + nativeShellLibrary.SetIcon(value.ReferencePath); + nativeShellLibrary.Commit(); + } + } + + /// Whether the library will be pinned to the Explorer Navigation Pane + public bool IsPinnedToNavigationPane + { + get + { + var flags = ShellNativeMethods.LibraryOptions.PinnedToNavigationPane; + + nativeShellLibrary.GetOptions(out flags); + + return ( + (flags & ShellNativeMethods.LibraryOptions.PinnedToNavigationPane) == + ShellNativeMethods.LibraryOptions.PinnedToNavigationPane); + } + set + { + var flags = ShellNativeMethods.LibraryOptions.Default; + + if (value) + { + flags |= ShellNativeMethods.LibraryOptions.PinnedToNavigationPane; + } + else + { + flags &= ~ShellNativeMethods.LibraryOptions.PinnedToNavigationPane; + } + + nativeShellLibrary.SetOptions(ShellNativeMethods.LibraryOptions.PinnedToNavigationPane, flags); + nativeShellLibrary.Commit(); + } + } + + /// Indicates whether this list is read-only or not. + public bool IsReadOnly => false; + + /// One of predefined Library types + /// Will throw if no Library Type is set + public LibraryFolderType LibraryType + { + get + { + nativeShellLibrary.GetFolderType(out var folderTypeGuid); + + return GetFolderTypefromGuid(folderTypeGuid); + } + + set + { + var guid = FolderTypesGuids[(int)value]; + nativeShellLibrary.SetFolderType(ref guid); + nativeShellLibrary.Commit(); + } + } + + /// The Guid of the Library type + /// Will throw if no Library Type is set + public Guid LibraryTypeId + { + get + { + nativeShellLibrary.GetFolderType(out var folderTypeGuid); + + return folderTypeGuid; + } + } + + /// The name of the library, every library must have a name + /// Will throw if no Icon is set + public override string Name + { + get + { + if (base.Name == null && NativeShellItem != null) + { + base.Name = System.IO.Path.GetFileNameWithoutExtension(ShellHelper.GetParsingName(NativeShellItem)); + } + + return base.Name; + } + } + + /// The count of the items in the list. + public int Count => ItemsList.Count; + + internal override IShellItem NativeShellItem => NativeShellItem2; + + internal override IShellItem2 NativeShellItem2 => nativeShellItem; + + private List ItemsList => GetFolders(); + + /// Retrieves the folder at the specified index + /// The index of the folder to retrieve. + /// A folder. + public ShellFileSystemFolder this[int index] + { + get => ItemsList[index]; + set => + // Index related options are not supported by IShellLibrary doesn't support them. + throw new NotImplementedException(); + } + + /// Load the library using a number of options + /// The name of the library + /// If true, loads the library in read-only mode. + /// A ShellLibrary Object + public static ShellLibrary Load(string libraryName, bool isReadOnly) + { + CoreHelpers.ThrowIfNotWin7(); + + var kf = KnownFolders.Libraries; + var librariesFolderPath = (kf != null) ? kf.Path : string.Empty; + + var guid = new Guid(ShellIIDGuid.IShellItem); + var shellItemPath = System.IO.Path.Combine(librariesFolderPath, libraryName + FileExtension); + var hr = ShellNativeMethods.SHCreateItemFromParsingName(shellItemPath, IntPtr.Zero, ref guid, out IShellItem nativeShellItem); + + if (!CoreErrorHelper.Succeeded(hr)) + throw new ShellException(hr); + + var nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); + var flags = isReadOnly ? + AccessModes.Read : + AccessModes.ReadWrite; + nativeShellLibrary.LoadLibraryFromItem(nativeShellItem, flags); + + var library = new ShellLibrary(nativeShellLibrary); + try + { + library.nativeShellItem = (IShellItem2)nativeShellItem; + library.Name = libraryName; + + return library; + } + catch + { + library.Dispose(); + throw; + } + } + + /// Load the library using a number of options + /// The name of the library. + /// The path to the library. + /// If true, opens the library in read-only mode. + /// A ShellLibrary Object + public static ShellLibrary Load(string libraryName, string folderPath, bool isReadOnly) + { + CoreHelpers.ThrowIfNotWin7(); + + // Create the shell item path + var shellItemPath = System.IO.Path.Combine(folderPath, libraryName + FileExtension); + var item = ShellFile.FromFilePath(shellItemPath); + + var nativeShellItem = item.NativeShellItem; + var nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); + var flags = isReadOnly ? + AccessModes.Read : + AccessModes.ReadWrite; + nativeShellLibrary.LoadLibraryFromItem(nativeShellItem, flags); + + var library = new ShellLibrary(nativeShellLibrary); + try + { + library.nativeShellItem = (IShellItem2)nativeShellItem; + library.Name = libraryName; + + return library; + } + catch + { + library.Dispose(); + throw; + } + } + + /// Load the library using a number of options + /// A known folder. + /// If true, opens the library in read-only mode. + /// A ShellLibrary Object + public static ShellLibrary Load(IKnownFolder sourceKnownFolder, bool isReadOnly) + { + CoreHelpers.ThrowIfNotWin7(); + return new ShellLibrary(sourceKnownFolder, isReadOnly); + } + + /// Shows the library management dialog which enables users to mange the library folders and default save location. + /// The name of the library + /// The path to the library. + /// The parent window,or IntPtr.Zero for no parent + /// A title for the library management dialog, or null to use the library name as the title + /// An optional help string to display for the library management dialog + /// If true, do not show warning dialogs about locations that cannot be indexed + /// If the library is already open in read-write mode, the dialog will not save the changes. + public static void ShowManageLibraryUI(string libraryName, string folderPath, IntPtr windowHandle, string title, string instruction, bool allowAllLocations) + { + // this method is not safe for MTA consumption and will blow Access Violations if called from an MTA thread so we wrap this call + // up into a Worker thread that performs all operations in a single threaded apartment + using (var shellLibrary = ShellLibrary.Load(libraryName, folderPath, true)) + { + ShowManageLibraryUI(shellLibrary, windowHandle, title, instruction, allowAllLocations); + } + } + + /// Shows the library management dialog which enables users to mange the library folders and default save location. + /// The name of the library + /// The parent window,or IntPtr.Zero for no parent + /// A title for the library management dialog, or null to use the library name as the title + /// An optional help string to display for the library management dialog + /// If true, do not show warning dialogs about locations that cannot be indexed + /// If the library is already open in read-write mode, the dialog will not save the changes. + public static void ShowManageLibraryUI(string libraryName, IntPtr windowHandle, string title, string instruction, bool allowAllLocations) + { + // this method is not safe for MTA consumption and will blow Access Violations if called from an MTA thread so we wrap this call + // up into a Worker thread that performs all operations in a single threaded apartment + using (var shellLibrary = ShellLibrary.Load(libraryName, true)) + { + ShowManageLibraryUI(shellLibrary, windowHandle, title, instruction, allowAllLocations); + } + } + + /// Shows the library management dialog which enables users to mange the library folders and default save location. + /// A known folder. + /// The parent window,or IntPtr.Zero for no parent + /// A title for the library management dialog, or null to use the library name as the title + /// An optional help string to display for the library management dialog + /// If true, do not show warning dialogs about locations that cannot be indexed + /// If the library is already open in read-write mode, the dialog will not save the changes. + public static void ShowManageLibraryUI(IKnownFolder sourceKnownFolder, IntPtr windowHandle, string title, string instruction, bool allowAllLocations) + { + // this method is not safe for MTA consumption and will blow Access Violations if called from an MTA thread so we wrap this call + // up into a Worker thread that performs all operations in a single threaded apartment + using (var shellLibrary = ShellLibrary.Load(sourceKnownFolder, true)) + { + ShowManageLibraryUI(shellLibrary, windowHandle, title, instruction, allowAllLocations); + } + } + + /// Add a new FileSystemFolder or SearchConnector + /// The folder to add to the library. + public void Add(ShellFileSystemFolder item) + { + if (item == null) { throw new ArgumentNullException("item"); } + + nativeShellLibrary.AddFolder(item.NativeShellItem); + nativeShellLibrary.Commit(); + } + + /// Add an existing folder to this library + /// The path to the folder to be added to the library. + public void Add(string folderPath) + { + if (!Directory.Exists(folderPath)) + { + throw new DirectoryNotFoundException(LocalizedMessages.ShellLibraryFolderNotFound); + } + + Add(ShellFileSystemFolder.FromFolderPath(folderPath)); + } + + /// Clear all items of this Library + public void Clear() + { + var list = ItemsList; + foreach (var folder in list) + { + nativeShellLibrary.RemoveFolder(folder.NativeShellItem); + } + + nativeShellLibrary.Commit(); + } + + /// Close the library, and release its associated file system resources + public void Close() => Dispose(); + + /// Determines if an item with the specified path exists in the collection. + /// The path of the item. + /// true if the item exists in the collection. + public bool Contains(string fullPath) + { + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentNullException("fullPath"); + } + + return ItemsList.Any(folder => string.Equals(fullPath, folder.Path, StringComparison.OrdinalIgnoreCase)); + } + + /// Determines if a folder exists in the collection. + /// The folder. + /// true, if the folder exists in the collection. + public bool Contains(ShellFileSystemFolder item) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + return ItemsList.Any(folder => string.Equals(item.Path, folder.Path, StringComparison.OrdinalIgnoreCase)); + } + + /// Retrieves the collection enumerator. + /// The enumerator. + public new IEnumerator GetEnumerator() => ItemsList.GetEnumerator(); + + /// + /// Searches for the specified FileSystemFolder and returns the zero-based index of the first occurrence within Library list. + /// + /// The item to search for. + /// The index of the item in the collection, or -1 if the item does not exist. + public int IndexOf(ShellFileSystemFolder item) => ItemsList.IndexOf(item); + + /// Remove a folder or search connector + /// The item to remove. + /// true if the item was removed. + public bool Remove(ShellFileSystemFolder item) + { + if (item == null) { throw new ArgumentNullException("item"); } + + try + { + nativeShellLibrary.RemoveFolder(item.NativeShellItem); + nativeShellLibrary.Commit(); + } + catch (COMException) + { + return false; + } + + return true; + } + + /// Remove a folder or search connector + /// The path of the item to remove. + /// true if the item was removed. + public bool Remove(string folderPath) + { + var item = ShellFileSystemFolder.FromFolderPath(folderPath); + return Remove(item); + } + + /// Copies the collection to an array. + /// The array to copy to. + /// The index in the array at which to start the copy. + void ICollection.CopyTo(ShellFileSystemFolder[] array, int arrayIndex) => throw new NotImplementedException(); + + /// Retrieves the collection enumerator. + /// The enumerator. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => ItemsList.GetEnumerator(); + + /// Inserts a FileSystemFolder at the specified index. + /// The index to insert at. + /// The FileSystemFolder to insert. + void IList.Insert(int index, ShellFileSystemFolder item) => + // Index related options are not supported by IShellLibrary doesn't support them. + throw new NotImplementedException(); + + /// Removes an item at the specified index. + /// The index to remove. + void IList.RemoveAt(int index) => + // Index related options are not supported by IShellLibrary doesn't support them. + throw new NotImplementedException(); + + /// Load the library using a number of options + /// IShellItem + /// read-only flag + /// A ShellLibrary Object + internal static ShellLibrary FromShellItem(IShellItem nativeShellItem, bool isReadOnly) + { + CoreHelpers.ThrowIfNotWin7(); + + var nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); + + var flags = isReadOnly ? + AccessModes.Read : + AccessModes.ReadWrite; + + nativeShellLibrary.LoadLibraryFromItem(nativeShellItem, flags); + + var library = new ShellLibrary(nativeShellLibrary) + { + nativeShellItem = (IShellItem2)nativeShellItem + }; + + return library; + } + + /// Release resources + /// Indicates that this was called from Dispose(), rather than from the finalizer. + protected override void Dispose(bool disposing) + { + if (nativeShellLibrary != null) + { + Marshal.ReleaseComObject(nativeShellLibrary); + nativeShellLibrary = null; + } + + base.Dispose(disposing); + } + + private static LibraryFolderType GetFolderTypefromGuid(Guid folderTypeGuid) + { + for (var i = 0; i < FolderTypesGuids.Length; i++) + { + if (folderTypeGuid.Equals(FolderTypesGuids[i])) + { + return (LibraryFolderType)i; + } + } + throw new ArgumentOutOfRangeException("folderTypeGuid", LocalizedMessages.ShellLibraryInvalidFolderType); + } + + private static void ShowManageLibraryUI(ShellLibrary shellLibrary, IntPtr windowHandle, string title, string instruction, bool allowAllLocations) + { + var hr = 0; + + var staWorker = new Thread(() => + { + hr = ShellNativeMethods.SHShowManageLibraryUI( + shellLibrary.NativeShellItem, + windowHandle, + title, + instruction, + allowAllLocations ? + ShellNativeMethods.LibraryManageDialogOptions.NonIndexableLocationWarning : + ShellNativeMethods.LibraryManageDialogOptions.Default); + }); + + staWorker.SetApartmentState(ApartmentState.STA); + staWorker.Start(); + staWorker.Join(); + + if (!CoreErrorHelper.Succeeded(hr)) { throw new ShellException(hr); } + } + + private List GetFolders() + { + var list = new List(); + + var shellItemArrayGuid = new Guid(ShellIIDGuid.IShellItemArray); + + var hr = nativeShellLibrary.GetFolders(ShellNativeMethods.LibraryFolderFilter.AllItems, ref shellItemArrayGuid, out var itemArray); + + if (!CoreErrorHelper.Succeeded(hr)) { return list; } + + itemArray.GetCount(out var count); + + for (uint i = 0; i < count; ++i) + { + itemArray.GetItemAt(i, out var shellItem); + list.Add(new ShellFileSystemFolder(shellItem as IShellItem2)); + } + + if (itemArray != null) + { + Marshal.ReleaseComObject(itemArray); + itemArray = null; + } + + return list; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellLink.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellLink.cs new file mode 100644 index 0000000..98538b8 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellLink.cs @@ -0,0 +1,112 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Represents a link to existing FileSystem or Virtual item. + public class ShellLink : ShellObject + { + /// Path for this file e.g. c:\Windows\file.txt, + private string _internalPath; + + private string internalArguments; + + private string internalComments; + + private string internalTargetLocation; + + internal ShellLink(IShellItem2 shellItem) => nativeShellItem = shellItem; + + /// Gets the arguments associated with this link. + public string Arguments + { + get + { + if (string.IsNullOrEmpty(internalArguments) && NativeShellItem2 != null) + { + internalArguments = Properties.System.Link.Arguments.Value; + } + + return internalArguments; + } + } + + /// Gets the comments associated with this link. + public string Comments + { + get + { + if (string.IsNullOrEmpty(internalComments) && NativeShellItem2 != null) + { + internalComments = Properties.System.Comment.Value; + } + + return internalComments; + } + } + + /// The path for this link + public virtual string Path + { + get + { + if (_internalPath == null && NativeShellItem != null) + { + _internalPath = base.ParsingName; + } + return _internalPath; + } + protected set => _internalPath = value; + } + + /// Gets the location to which this link points to. + public string TargetLocation + { + get + { + if (string.IsNullOrEmpty(internalTargetLocation) && NativeShellItem2 != null) + { + internalTargetLocation = Properties.System.Link.TargetParsingPath.Value; + } + return internalTargetLocation; + } + set + { + if (value == null) { return; } + + internalTargetLocation = value; + + if (NativeShellItem2 != null) + { + Properties.System.Link.TargetParsingPath.Value = internalTargetLocation; + } + } + } + + /// Gets the ShellObject to which this link points to. + public ShellObject TargetShellObject => ShellObjectFactory.Create(TargetLocation); + + /// Gets or sets the link's title + public string Title + { + get + { + if (NativeShellItem2 != null) { return Properties.System.Title.Value; } + return null; + } + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + if (NativeShellItem2 != null) + { + Properties.System.Title.Value = value; + } + } + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellNonFileSystemFolder.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellNonFileSystemFolder.cs new file mode 100644 index 0000000..a373bd2 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellNonFileSystemFolder.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Represents a Non FileSystem folder (e.g. My Computer, Control Panel) + public class ShellNonFileSystemFolder : ShellFolder + { + internal ShellNonFileSystemFolder() + { + // Empty + } + + internal ShellNonFileSystemFolder(IShellItem2 shellItem) => nativeShellItem = shellItem; + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellNonFileSystemItem.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellNonFileSystemItem.cs new file mode 100644 index 0000000..369db84 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellNonFileSystemItem.cs @@ -0,0 +1,10 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Represents a non filesystem item (e.g. virtual items inside Control Panel) + public class ShellNonFileSystemItem : ShellObject + { + internal ShellNonFileSystemItem(IShellItem2 shellItem) => nativeShellItem = shellItem; + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellObject.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellObject.cs new file mode 100644 index 0000000..e9d25ae --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellObject.cs @@ -0,0 +1,396 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem; +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// The base class for all Shell objects in Shell Namespace. + public abstract class ShellObject : IDisposable, IEquatable + { + /// Internal member to keep track of the native IShellItem2 + internal IShellItem2 nativeShellItem; + + /// A friendly name for this object that' suitable for display + private string _internalName; + + /// Parsing name for this Object e.g. c:\Windows\file.txt, or ::{Some Guid} + private string _internalParsingName; + + /// PID List (PIDL) for this object + private IntPtr _internalPIDL = IntPtr.Zero; + + private int? hashValue; + + private ShellObject parentShellObject; + + private ShellProperties properties; + + private ShellThumbnail thumbnail; + + internal ShellObject() + { + } + + internal ShellObject(IShellItem2 shellItem) => nativeShellItem = shellItem; + + /// Implement the finalizer. + ~ShellObject() + { + Dispose(false); + } + + /// Indicates whether this feature is supported on the current platform. + public static bool IsPlatformSupported => + // We need Windows Vista onwards ... + CoreHelpers.RunningOnVista; + + /// Gets a value that determines if this ShellObject is a file system object. + public bool IsFileSystemObject + { + get + { + try + { + NativeShellItem.GetAttributes(ShellNativeMethods.ShellFileGetAttributesOptions.FileSystem, out var sfgao); + return (sfgao & ShellNativeMethods.ShellFileGetAttributesOptions.FileSystem) != 0; + } + catch (FileNotFoundException) + { + return false; + } + catch (NullReferenceException) + { + // NativeShellItem is null + return false; + } + } + } + + /// Gets a value that determines if this ShellObject is a link or shortcut. + public bool IsLink + { + get + { + try + { + NativeShellItem.GetAttributes(ShellNativeMethods.ShellFileGetAttributesOptions.Link, out var sfgao); + return (sfgao & ShellNativeMethods.ShellFileGetAttributesOptions.Link) != 0; + } + catch (FileNotFoundException) + { + return false; + } + catch (NullReferenceException) + { + // NativeShellItem is null + return false; + } + } + } + + /// Gets the normal display for this ShellItem. + public virtual string Name + { + get + { + if (_internalName == null && NativeShellItem != null) + { + var pszString = IntPtr.Zero; + var hr = NativeShellItem.GetDisplayName(ShellNativeMethods.ShellItemDesignNameOptions.Normal, out pszString); + if (hr == HResult.Ok && pszString != IntPtr.Zero) + { + _internalName = Marshal.PtrToStringAuto(pszString); + + // Free the string + Marshal.FreeCoTaskMem(pszString); + } + } + return _internalName; + } + + protected set => _internalName = value; + } + + /// Gets the parent ShellObject. Returns null if the object has no parent, i.e. if this object is the Desktop folder. + public ShellObject Parent + { + get + { + if (parentShellObject == null && NativeShellItem2 != null) + { + var hr = NativeShellItem2.GetParent(out var parentShellItem); + + if (hr == HResult.Ok && parentShellItem != null) + { + parentShellObject = ShellObjectFactory.Create(parentShellItem); + } + else if (hr == HResult.NoObject) + { + // Should return null if the parent is desktop + return null; + } + else + { + throw new ShellException(hr); + } + } + + return parentShellObject; + } + } + + /// Gets the parsing name for this ShellItem. + public virtual string ParsingName + { + get + { + if (_internalParsingName == null && nativeShellItem != null) + { + _internalParsingName = ShellHelper.GetParsingName(nativeShellItem); + } + return _internalParsingName ?? string.Empty; + } + protected set => _internalParsingName = value; + } + + /// Gets an object that allows the manipulation of ShellProperties for this shell item. + public ShellProperties Properties + { + get + { + if (properties == null) + { + properties = new ShellProperties(this); + } + return properties; + } + } + + /// Gets the thumbnail of the ShellObject. + public ShellThumbnail Thumbnail + { + get + { + if (thumbnail == null) { thumbnail = new ShellThumbnail(this); } + return thumbnail; + } + } + + /// + /// Gets access to the native IPropertyStore (if one is already created for this item and still valid. This is usually done by the + /// ShellPropertyWriter class. The reference will be set to null when the writer has been closed/commited). + /// + internal IPropertyStore NativePropertyStore { get; set; } + + /// Return the native ShellFolder object + internal virtual IShellItem NativeShellItem => NativeShellItem2; + + /// Return the native ShellFolder object as newer IShellItem2 + /// + /// If the native object cannot be created. The ErrorCode member will contain the external error code. + /// + internal virtual IShellItem2 NativeShellItem2 + { + get + { + if (nativeShellItem == null && ParsingName != null) + { + var guid = new Guid(ShellIIDGuid.IShellItem2); + var retCode = ShellNativeMethods.SHCreateItemFromParsingName(ParsingName, IntPtr.Zero, ref guid, out nativeShellItem); + + if (nativeShellItem == null || !CoreErrorHelper.Succeeded(retCode)) + { + throw new ShellException(LocalizedMessages.ShellObjectCreationFailed, Marshal.GetExceptionForHR(retCode)); + } + } + return nativeShellItem; + } + } + + /// Gets the PID List (PIDL) for this ShellItem. + internal virtual IntPtr PIDL + { + get + { + // Get teh PIDL for the ShellItem + if (_internalPIDL == IntPtr.Zero && NativeShellItem != null) + { + _internalPIDL = ShellHelper.PidlFromShellItem(NativeShellItem); + } + + return _internalPIDL; + } + set => _internalPIDL = value; + } + + /// + /// Creates a ShellObject subclass given a parsing name. For file system items, this method will only accept absolute paths. + /// + /// The parsing name of the object. + /// A newly constructed ShellObject object. + public static ShellObject FromParsingName(string parsingName) => ShellObjectFactory.Create(parsingName); + + /// Implements the != (inequality) operator. + /// First object to compare. + /// Second object to compare. + /// True if leftShellObject does not equal leftShellObject; false otherwise. + public static bool operator !=(ShellObject leftShellObject, ShellObject rightShellObject) => !(leftShellObject == rightShellObject); + + /// Implements the == (equality) operator. + /// First object to compare. + /// Second object to compare. + /// True if leftShellObject equals rightShellObject; false otherwise. + public static bool operator ==(ShellObject leftShellObject, ShellObject rightShellObject) + { + if ((object)leftShellObject == null) + { + return ((object)rightShellObject == null); + } + return leftShellObject.Equals(rightShellObject); + } + + /// Release the native objects. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// Determines if two ShellObjects are identical. + /// The ShellObject to comare this one to. + /// True if the ShellObjects are equal, false otherwise. + public bool Equals(ShellObject other) + { + var areEqual = false; + + if (other != null) + { + var ifirst = NativeShellItem; + var isecond = other.NativeShellItem; + if (ifirst != null && isecond != null) + { + var hr = ifirst.Compare( + isecond, SICHINTF.SICHINT_ALLFIELDS, out var result); + + areEqual = (hr == HResult.Ok) && (result == 0); + } + } + + return areEqual; + } + + /// Returns whether this object is equal to another. + /// The object to compare against. + /// Equality result. + public override bool Equals(object obj) => Equals(obj as ShellObject); + + /// + /// Returns the display name of the ShellFolder object. DisplayNameType represents one of the values that indicates how the name + /// should look. See for a list of possible values. + /// + /// A disaply name type. + /// A string. + public virtual string GetDisplayName(DisplayNameType displayNameType) + { + string returnValue = null; + NativeShellItem2?.GetDisplayName((ShellNativeMethods.ShellItemDesignNameOptions)displayNameType, out returnValue); + return returnValue; + } + + /// Returns the hash code of the object. + /// + public override int GetHashCode() + { + if (!hashValue.HasValue) + { + var size = ShellNativeMethods.ILGetSize(PIDL); + if (size != 0) + { + var pidlData = new byte[size]; + Marshal.Copy(PIDL, pidlData, 0, (int)size); + + // Using FNV-1a hash algorithm because a cryptographically secure algorithm is not required for this use + const int p = 16777619; + int hash = -2128831035; + + for (int i = 0; i < pidlData.Length; i++) + hash = (hash ^ pidlData[i]) * p; + + hashValue = hash; + } + else + { + hashValue = 0; + } + } + return hashValue.Value; + } + + /// Overrides object.ToString() + /// A string representation of the object. + public override string ToString() => Name; + + /// + /// Updates the native shell item that maps to this shell object. This is necessary when the shell item changes after the shell + /// object has been created. Without this method call, the retrieval of properties will return stale data. + /// + /// Bind context object + public void Update(IBindCtx bindContext) + { + var hr = HResult.Ok; + + if (NativeShellItem2 != null) + { + hr = NativeShellItem2.Update(bindContext); + } + + if (CoreErrorHelper.Failed(hr)) + { + throw new ShellException(hr); + } + } + + /// Release the native and managed objects + /// Indicates that this is being called from Dispose(), rather than the finalizer. + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _internalName = null; + _internalParsingName = null; + properties = null; + thumbnail = null; + parentShellObject = null; + } + + if (properties != null) + { + properties.Dispose(); + } + + if (_internalPIDL != IntPtr.Zero) + { + ShellNativeMethods.ILFree(_internalPIDL); + _internalPIDL = IntPtr.Zero; + } + + if (nativeShellItem != null) + { + Marshal.ReleaseComObject(nativeShellItem); + nativeShellItem = null; + } + + if (NativePropertyStore != null) + { + Marshal.ReleaseComObject(NativePropertyStore); + NativePropertyStore = null; + } + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellObjectContainer.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellObjectContainer.cs new file mode 100644 index 0000000..6f50b0c --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellObjectContainer.cs @@ -0,0 +1,92 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// + /// Represents the base class for all types of Shell "containers". Any class deriving from this class can contain other ShellObjects + /// (e.g. ShellFolder, FileSystemKnownFolder, ShellLibrary, etc) + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "This will complicate the class hierarchy and naming convention used in the Shell area")] + public abstract class ShellContainer : ShellObject, IEnumerable, IDisposable + { + private IShellFolder desktopFolderEnumeration; + private IShellFolder nativeShellFolder; + + internal ShellContainer() + { + } + + internal ShellContainer(IShellItem2 shellItem) : base(shellItem) + { + } + + internal IShellFolder NativeShellFolder + { + get + { + if (nativeShellFolder == null) + { + var guid = new Guid(ShellIIDGuid.IShellFolder); + var handler = new Guid(ShellBHIDGuid.ShellFolderObject); + + var hr = NativeShellItem.BindToHandler( + IntPtr.Zero, ref handler, ref guid, out nativeShellFolder); + + if (CoreErrorHelper.Failed(hr)) + { + var str = ShellHelper.GetParsingName(NativeShellItem); + if (str != null && str != Environment.GetFolderPath(Environment.SpecialFolder.Desktop)) + { + throw new ShellException(hr); + } + } + } + + return nativeShellFolder; + } + } + + /// Enumerates through contents of the ShellObjectContainer + /// Enumerated contents + public IEnumerator GetEnumerator() + { + if (NativeShellFolder == null) + { + if (desktopFolderEnumeration == null) + { + ShellNativeMethods.SHGetDesktopFolder(out desktopFolderEnumeration); + } + + nativeShellFolder = desktopFolderEnumeration; + } + + return new ShellFolderItems(this); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => new ShellFolderItems(this); + + /// Release resources + /// True indicates that this is being called from Dispose(), rather than the finalizer. + protected override void Dispose(bool disposing) + { + if (nativeShellFolder != null) + { + Marshal.ReleaseComObject(nativeShellFolder); + nativeShellFolder = null; + } + + if (desktopFolderEnumeration != null) + { + Marshal.ReleaseComObject(desktopFolderEnumeration); + desktopFolderEnumeration = null; + } + + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellObjectFactory.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellObjectFactory.cs new file mode 100644 index 0000000..50fd41b --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellObjectFactory.cs @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + internal static class ShellObjectFactory + { + /// Creates a ShellObject given a native IShellItem interface + /// + /// A newly constructed ShellObject object + internal static ShellObject Create(IShellItem nativeShellItem) + { + // Sanity check + Debug.Assert(nativeShellItem != null, "nativeShellItem should not be null"); + + // Need to make sure we're running on Vista or higher + if (!CoreHelpers.RunningOnVista) + { + throw new PlatformNotSupportedException(LocalizedMessages.ShellObjectFactoryPlatformNotSupported); + } + + // A lot of APIs need IShellItem2, so just keep a copy of it here + var nativeShellItem2 = nativeShellItem as IShellItem2; + + // Get the System.ItemType property + var itemType = ShellHelper.GetItemType(nativeShellItem2); + + if (!string.IsNullOrEmpty(itemType)) { itemType = itemType.ToLowerInvariant(); } + + // Get some IShellItem attributes + nativeShellItem2.GetAttributes(ShellNativeMethods.ShellFileGetAttributesOptions.FileSystem | ShellNativeMethods.ShellFileGetAttributesOptions.Folder, out var sfgao); + + // Is this item a FileSystem item? + var isFileSystem = (sfgao & ShellNativeMethods.ShellFileGetAttributesOptions.FileSystem) != 0; + + // Is this item a Folder? + var isFolder = (sfgao & ShellNativeMethods.ShellFileGetAttributesOptions.Folder) != 0; + + // Shell Library + ShellLibrary shellLibrary = null; + + // Create the right type of ShellObject based on the above information + + // 1. First check if this is a Shell Link + if (StringComparer.OrdinalIgnoreCase.Equals(itemType, ".lnk")) + { + return new ShellLink(nativeShellItem2); + } + // 2. Check if this is a container or a single item (entity) + else if (isFolder) + { + // 3. If this is a folder, check for types: Shell Library, Shell Folder or Search Container + if (itemType == ".library-ms" && (shellLibrary = ShellLibrary.FromShellItem(nativeShellItem2, true)) != null) + { + return shellLibrary; // we already created this above while checking for Library + } + else if (itemType == ".searchconnector-ms") + { + return new ShellSearchConnector(nativeShellItem2); + } + else if (itemType == ".search-ms") + { + return new ShellSavedSearchCollection(nativeShellItem2); + } + + // 4. It's a ShellFolder + if (isFileSystem) + { + // 5. Is it a (File-System / Non-Virtual) Known Folder + if (!IsVirtualKnownFolder(nativeShellItem2)) + { //needs to check if it is a known folder and not virtual + var kf = new FileSystemKnownFolder(nativeShellItem2); + return kf; + } + + return new ShellFileSystemFolder(nativeShellItem2); + } + + // 5. Is it a (Non File-System / Virtual) Known Folder + if (IsVirtualKnownFolder(nativeShellItem2)) + { //needs to check if known folder is virtual + var kf = new NonFileSystemKnownFolder(nativeShellItem2); + return kf; + } + + return new ShellNonFileSystemFolder(nativeShellItem2); + } + + // 6. If this is an entity (single item), check if its filesystem or not + if (isFileSystem) { return new ShellFile(nativeShellItem2); } + + return new ShellNonFileSystemItem(nativeShellItem2); + } + + /// Creates a ShellObject given a parsing name + /// + /// A newly constructed ShellObject object + internal static ShellObject Create(string parsingName) + { + if (string.IsNullOrEmpty(parsingName)) + { + throw new ArgumentNullException("parsingName"); + } + + // Create a native shellitem from our path + var guid = new Guid(ShellIIDGuid.IShellItem2); + var retCode = ShellNativeMethods.SHCreateItemFromParsingName(parsingName, IntPtr.Zero, ref guid, out + // Create a native shellitem from our path + IShellItem2 nativeShellItem); + + if (!CoreErrorHelper.Succeeded(retCode)) + { + throw new ShellException(LocalizedMessages.ShellObjectFactoryUnableToCreateItem, Marshal.GetExceptionForHR(retCode)); + } + return ShellObjectFactory.Create(nativeShellItem); + } + + /// Constructs a new Shell object from IDList pointer + /// + /// + internal static ShellObject Create(IntPtr idListPtr) + { + // Throw exception if not running on Win7 or newer. + CoreHelpers.ThrowIfNotVista(); + + var guid = new Guid(ShellIIDGuid.IShellItem2); + + var retCode = ShellNativeMethods.SHCreateItemFromIDList(idListPtr, ref guid, out var nativeShellItem); + + if (!CoreErrorHelper.Succeeded(retCode)) { return null; } + return ShellObjectFactory.Create(nativeShellItem); + } + + /// Constructs a new Shell object from IDList pointer + /// + /// + /// + internal static ShellObject Create(IntPtr idListPtr, ShellContainer parent) + { + var retCode = ShellNativeMethods.SHCreateShellItem( + IntPtr.Zero, + parent.NativeShellFolder, + idListPtr, out var nativeShellItem); + + if (!CoreErrorHelper.Succeeded(retCode)) { return null; } + + return ShellObjectFactory.Create(nativeShellItem); + } + + // This is a work around for the STA thread bug. This will execute the call on a non-sta thread, then return the result + private static bool IsVirtualKnownFolder(IShellItem2 nativeShellItem2) + { + var pidl = IntPtr.Zero; + try + { + IKnownFolderNative nativeFolder = null; + var definition = new KnownFoldersSafeNativeMethods.NativeFolderDefinition(); + + // We found a bug where the enumeration of shell folders was not reliable when called from a STA thread - it would return + // different results the first time vs the other times. + // + // This is a work around. We call FindFolderFromIDList on a worker MTA thread instead of the main STA thread. + // + // Ultimately, it would be a very good idea to replace the 'getting shell object' logic to get a list of pidl's in 1 step, + // then look up their information in a 2nd, rather than looking them up as we get them. This would replace the need for the + // work around. + var padlock = new object(); + lock (padlock) + { + var unknown = Marshal.GetIUnknownForObject(nativeShellItem2); + + ThreadPool.QueueUserWorkItem(obj => + { + lock (padlock) + { + pidl = ShellHelper.PidlFromUnknown(unknown); + + new KnownFolderManagerClass().FindFolderFromIDList(pidl, out nativeFolder); + + if (nativeFolder != null) + { + nativeFolder.GetFolderDefinition(out definition); + } + + Monitor.Pulse(padlock); + } + }); + + Monitor.Wait(padlock); + } + + return nativeFolder != null && definition.category == FolderCategory.Virtual; + } + finally + { + ShellNativeMethods.ILFree(pidl); + } + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellSavedSearchCollection.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellSavedSearchCollection.cs new file mode 100644 index 0000000..259adf0 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellSavedSearchCollection.cs @@ -0,0 +1,13 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Represents a saved search + public class ShellSavedSearchCollection : ShellSearchCollection + { + internal ShellSavedSearchCollection(IShellItem2 shellItem) + : base(shellItem) => CoreHelpers.ThrowIfNotVista(); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellSearchCollection.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellSearchCollection.cs new file mode 100644 index 0000000..e771711 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellSearchCollection.cs @@ -0,0 +1,16 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Represents the base class for all search-related classes. + public class ShellSearchCollection : ShellContainer + { + internal ShellSearchCollection() + { + } + + internal ShellSearchCollection(IShellItem2 shellItem) : base(shellItem) + { + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellSearchConnector.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellSearchConnector.cs new file mode 100644 index 0000000..675d3a9 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellSearchConnector.cs @@ -0,0 +1,20 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// A Serch Connector folder in the Shell Namespace + public sealed class ShellSearchConnector : ShellSearchCollection + { + internal ShellSearchConnector() => CoreHelpers.ThrowIfNotWin7(); + + internal ShellSearchConnector(IShellItem2 shellItem) + : this() => nativeShellItem = shellItem; + + /// Indicates whether this feature is supported on the current platform. + public new static bool IsPlatformSupported => + // We need Windows 7 onwards ... + CoreHelpers.RunningOnWin7; + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellThumbnail.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellThumbnail.cs new file mode 100644 index 0000000..22fdaf2 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellThumbnail.cs @@ -0,0 +1,249 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Interop; +using System.Windows.Media.Imaging; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Represents a thumbnail or an icon for a ShellObject. + public class ShellThumbnail + { + /// Native shellItem + private readonly IShellItem shellItemNative; + + /// Internal member to keep track of the current size + private System.Windows.Size currentSize = new System.Windows.Size(256, 256); + + private ShellThumbnailFormatOption formatOption = ShellThumbnailFormatOption.Default; + + /// Internal constructor that takes in a parent ShellObject. + /// + internal ShellThumbnail(ShellObject shellObject) + { + if (shellObject == null || shellObject.NativeShellItem == null) + { + throw new ArgumentNullException("shellObject"); + } + + shellItemNative = shellObject.NativeShellItem; + } + + /// Gets or sets a value that determines if the user can manually stretch the returned image. The default value is false. + /// + /// For example, if the caller passes in 80x80 a 96x96 thumbnail could be returned. This could be used as a performance optimization + /// if the caller will need to stretch the image themselves anyway. Note that the Shell implementation performs a GDI stretch blit. + /// If the caller wants a higher quality image stretch, they should pass this flag and do it themselves. + /// + public bool AllowBiggerSize { get; set; } + + /// + /// Gets the thumbnail or icon image in format. Null is returned if the + /// ShellObject does not have a thumbnail or icon image. + /// + public BitmapSource BitmapSource => GetBitmapSource(CurrentSize); + + /// + /// Gets or sets the default size of the thumbnail or icon. The default is 32x32 pixels for icons and 256x256 pixels for thumbnails. + /// + /// + /// If the size specified is larger than the maximum size of 1024x1024 for thumbnails and 256x256 for icons, an + /// is thrown. + /// + public System.Windows.Size CurrentSize + { + get => currentSize; + set + { + // Check for 0; negative number check not required as System.Windows.Size only allows positive numbers. + if (value.Height == 0 || value.Width == 0) + { + throw new System.ArgumentOutOfRangeException("value", LocalizedMessages.ShellThumbnailSizeCannotBe0); + } + + var size = (FormatOption == ShellThumbnailFormatOption.IconOnly) ? + DefaultIconSize.Maximum : DefaultThumbnailSize.Maximum; + + if (value.Height > size.Height || value.Width > size.Width) + { + throw new System.ArgumentOutOfRangeException("value", + string.Format(System.Globalization.CultureInfo.InvariantCulture, + LocalizedMessages.ShellThumbnailCurrentSizeRange, size.ToString())); + } + + currentSize = value; + } + } + + /// Gets the thumbnail or icon in extra large size and format. + public Bitmap ExtraLargeBitmap => GetBitmap(DefaultIconSize.ExtraLarge, DefaultThumbnailSize.ExtraLarge); + + /// Gets the thumbnail or icon in Extra Large size and format. + public BitmapSource ExtraLargeBitmapSource => GetBitmapSource(DefaultIconSize.ExtraLarge, DefaultThumbnailSize.ExtraLarge); + + /// Gets the thumbnail or icon in Extra Large size and format. + public Icon ExtraLargeIcon => Icon.FromHandle(ExtraLargeBitmap.GetHicon()); + + /// + /// Gets or sets a value that determines if the current format option is thumbnail or icon, thumbnail only, or icon only. The default + /// is thumbnail or icon. + /// + public ShellThumbnailFormatOption FormatOption + { + get => formatOption; + set + { + formatOption = value; + + // Do a similar check as we did in CurrentSize property setter, If our mode is IconOnly, then our max is defined by + // DefaultIconSize.Maximum. We should make sure our CurrentSize is within this max range + if (FormatOption == ShellThumbnailFormatOption.IconOnly + && (CurrentSize.Height > DefaultIconSize.Maximum.Height || CurrentSize.Width > DefaultIconSize.Maximum.Width)) + { + CurrentSize = DefaultIconSize.Maximum; + } + } + } + + /// + /// Gets the thumbnail or icon image in format. Null is returned if the ShellObject does not have a + /// thumbnail or icon image. + /// + public Icon Icon => Icon.FromHandle(Bitmap.GetHicon()); + + /// Gets the thumbnail or icon in large size and format. + public Bitmap LargeBitmap => GetBitmap(DefaultIconSize.Large, DefaultThumbnailSize.Large); + + /// Gets the thumbnail or icon in large size and format. + public BitmapSource LargeBitmapSource => GetBitmapSource(DefaultIconSize.Large, DefaultThumbnailSize.Large); + + /// Gets the thumbnail or icon in Large size and format. + public Icon LargeIcon => Icon.FromHandle(LargeBitmap.GetHicon()); + + /// Gets the thumbnail or icon in Medium size and format. + public Bitmap MediumBitmap => GetBitmap(DefaultIconSize.Medium, DefaultThumbnailSize.Medium); + + /// Gets the thumbnail or icon in medium size and format. + public BitmapSource MediumBitmapSource => GetBitmapSource(DefaultIconSize.Medium, DefaultThumbnailSize.Medium); + + /// Gets the thumbnail or icon in Medium size and format. + public Icon MediumIcon => Icon.FromHandle(MediumBitmap.GetHicon()); + + /// + /// Gets or sets a value that determines if the current retrieval option is cache or extract, cache only, or from memory only. The + /// default is cache or extract. + /// + public ShellThumbnailRetrievalOption RetrievalOption { get; set; } + + /// Gets the thumbnail or icon in small size and format. + public Bitmap SmallBitmap => GetBitmap(DefaultIconSize.Small, DefaultThumbnailSize.Small); + + /// Gets the thumbnail or icon in small size and format. + public BitmapSource SmallBitmapSource => GetBitmapSource(DefaultIconSize.Small, DefaultThumbnailSize.Small); + + /// Gets the thumbnail or icon in small size and format. + public Icon SmallIcon => Icon.FromHandle(SmallBitmap.GetHicon()); + + /// + /// Gets the thumbnail or icon image in format. Null is returned if the ShellObject does not have + /// a thumbnail or icon image. + /// + public Bitmap Bitmap => GetBitmap(CurrentSize); + + private ShellNativeMethods.SIIGBF CalculateFlags() + { + ShellNativeMethods.SIIGBF flags = 0x0000; + + if (AllowBiggerSize) + { + flags |= ShellNativeMethods.SIIGBF.BiggerSizeOk; + } + + if (RetrievalOption == ShellThumbnailRetrievalOption.CacheOnly) + { + flags |= ShellNativeMethods.SIIGBF.InCacheOnly; + } + else if (RetrievalOption == ShellThumbnailRetrievalOption.MemoryOnly) + { + flags |= ShellNativeMethods.SIIGBF.MemoryOnly; + } + + if (FormatOption == ShellThumbnailFormatOption.IconOnly) + { + flags |= ShellNativeMethods.SIIGBF.IconOnly; + } + else if (FormatOption == ShellThumbnailFormatOption.ThumbnailOnly) + { + flags |= ShellNativeMethods.SIIGBF.ThumbnailOnly; + } + + return flags; + } + + private Bitmap GetBitmap(System.Windows.Size iconOnlySize, System.Windows.Size thumbnailSize) => GetBitmap(FormatOption == ShellThumbnailFormatOption.IconOnly ? iconOnlySize : thumbnailSize); + + private Bitmap GetBitmap(System.Windows.Size size) + { + var hBitmap = GetHBitmap(size); + + // return a System.Drawing.Bitmap from the hBitmap + var returnValue = Bitmap.FromHbitmap(hBitmap); + + // delete HBitmap to avoid memory leaks + ShellNativeMethods.DeleteObject(hBitmap); + + return returnValue; + } + + private BitmapSource GetBitmapSource(System.Windows.Size iconOnlySize, System.Windows.Size thumbnailSize) => GetBitmapSource(FormatOption == ShellThumbnailFormatOption.IconOnly ? iconOnlySize : thumbnailSize); + + private BitmapSource GetBitmapSource(System.Windows.Size size) + { + var hBitmap = GetHBitmap(size); + + // return a System.Media.Imaging.BitmapSource Use interop to create a BitmapSource from hBitmap. + var returnValue = Imaging.CreateBitmapSourceFromHBitmap( + hBitmap, + IntPtr.Zero, + System.Windows.Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions()); + + // delete HBitmap to avoid memory leaks + ShellNativeMethods.DeleteObject(hBitmap); + + return returnValue; + } + + private IntPtr GetHBitmap(System.Windows.Size size) + { + var hbitmap = IntPtr.Zero; + + // Create a size structure to pass to the native method + var nativeSIZE = new CoreNativeMethods.Size + { + Width = Convert.ToInt32(size.Width), + Height = Convert.ToInt32(size.Height) + }; + + // Use IShellItemImageFactory to get an icon Options passed in: Resize to fit + var hr = ((IShellItemImageFactory)shellItemNative).GetImage(nativeSIZE, CalculateFlags(), out hbitmap); + + if (hr == HResult.Ok) { return hbitmap; } + else if ((uint)hr == 0x8004B200 && FormatOption == ShellThumbnailFormatOption.ThumbnailOnly) + { + // Thumbnail was requested, but this ShellItem doesn't have a thumbnail. + throw new InvalidOperationException(LocalizedMessages.ShellThumbnailDoesNotHaveThumbnail, Marshal.GetExceptionForHR((int)hr)); + } + else if ((uint)hr == 0x80040154) // REGDB_E_CLASSNOTREG + { + throw new NotSupportedException(LocalizedMessages.ShellThumbnailNoHandler, Marshal.GetExceptionForHR((int)hr)); + } + + throw new ShellException(hr); + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/ShellThumbnailEnums.cs b/VG Music Studio - WinForms/API/Shell/Common/ShellThumbnailEnums.cs new file mode 100644 index 0000000..1d65167 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/ShellThumbnailEnums.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Represents the format options for the thumbnails and icons. + public enum ShellThumbnailFormatOption + { + /// + /// The default behavior loads a thumbnail. An HBITMAP for the icon of the item is retrieved if there is no thumbnail for the current + /// Shell Item. + /// + Default, + + /// + /// The ThumbnailOnly behavior returns only the thumbnails, never the icon. Note that not all items have thumbnails so + /// ShellThumbnailFormatOption.ThumbnailOnly can fail in these cases. + /// + ThumbnailOnly = ShellNativeMethods.SIIGBF.ThumbnailOnly, + + /// The IconOnly behavior returns only the icon, never the thumbnail. + IconOnly = ShellNativeMethods.SIIGBF.IconOnly, + } + + /// + /// Represents the different retrieval options for the thumbnail or icon, such as extracting the thumbnail or icon from a file, from the + /// cache only, or from memory only. + /// + public enum ShellThumbnailRetrievalOption + { + /// + /// The default behavior loads a thumbnail. If there is no thumbnail for the current ShellItem, the icon is retrieved. The thumbnail + /// or icon is extracted if it is not currently cached. + /// + Default, + + /// + /// The CacheOnly behavior returns a cached thumbnail if it is available. Allows access to the disk, but only to retrieve a cached + /// item. If no cached thumbnail is available, a cached per-instance icon is returned but a thumbnail or icon is not extracted. + /// + CacheOnly = ShellNativeMethods.SIIGBF.InCacheOnly, + + /// + /// The MemoryOnly behavior returns the item only if it is in memory. The disk is not accessed even if the item is cached. Note that + /// this only returns an already-cached icon and can fall back to a per-class icon if an item has a per-instance icon that has not + /// been cached yet. Retrieving a thumbnail, even if it is cached, always requires the disk to be accessed, so this method should not + /// be called from the user interface (UI) thread without passing ShellThumbnailCacheOptions.MemoryOnly. + /// + MemoryOnly = ShellNativeMethods.SIIGBF.MemoryOnly, + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Common/SortColumn.cs b/VG Music Studio - WinForms/API/Shell/Common/SortColumn.cs new file mode 100644 index 0000000..128a768 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Common/SortColumn.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Stores information about how to sort a column that is displayed in the folder view. + [StructLayout(LayoutKind.Sequential)] + public struct SortColumn + { + /// Creates a sort column with the specified direction for the given property. + /// Property key for the property that the user will sort. + /// The direction in which the items are sorted. + public SortColumn(PropertyKey propertyKey, SortDirection direction) + : this() + { + this.propertyKey = propertyKey; + this.direction = direction; + } + + /// + /// The ID of the column by which the user will sort. A PropertyKey structure. For example, for the "Name" column, the property key + /// is PKEY_ItemNameDisplay or . + /// + public PropertyKey PropertyKey { get => propertyKey; set => propertyKey = value; } + + private PropertyKey propertyKey; + + /// The direction in which the items are sorted. + public SortDirection Direction { get => direction; set => direction = value; } + + private SortDirection direction; + + /// Implements the == (equality) operator. + /// First object to compare. + /// Second object to compare. + /// True if col1 equals col2; false otherwise. + public static bool operator ==(SortColumn col1, SortColumn col2) => (col1.direction == col2.direction) && + (col1.propertyKey == col2.propertyKey); + + /// Implements the != (unequality) operator. + /// First object to compare. + /// Second object to compare. + /// True if col1 does not equals col1; false otherwise. + public static bool operator !=(SortColumn col1, SortColumn col2) => !(col1 == col2); + + /// Determines if this object is equal to another. + /// The object to compare + /// Returns true if the objects are equal; false otherwise. + public override bool Equals(object obj) + { + if (obj == null || obj.GetType() != typeof(SortColumn)) { return false; } + return (this == (SortColumn)obj); + } + + /// Generates a nearly unique hashcode for this structure. + /// A hash code. + public override int GetHashCode() + { + var hash = direction.GetHashCode(); + hash = hash * 31 + propertyKey.GetHashCode(); + return hash; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellCOMClasses.cs b/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellCOMClasses.cs new file mode 100644 index 0000000..0a0342c --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellCOMClasses.cs @@ -0,0 +1,22 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + [ComImport, + Guid(ShellIIDGuid.IShellLibrary), + CoClass(typeof(ShellLibraryCoClass))] + internal interface INativeShellLibrary : IShellLibrary + { + } + + [ComImport, + ClassInterface(ClassInterfaceType.None), + TypeLibType(TypeLibTypeFlags.FCanCreate), + Guid(ShellCLSIDGuid.ShellLibrary)] + internal class ShellLibraryCoClass + { + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellCOMGuids.cs b/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellCOMGuids.cs new file mode 100644 index 0000000..0c80934 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellCOMGuids.cs @@ -0,0 +1,91 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + internal static class ShellBHIDGuid + { + internal const string ShellFolderObject = "3981e224-f559-11d3-8e3a-00c04f6837d5"; + } + + internal static class ShellCLSIDGuid + { + internal const string ConditionFactory = "E03E85B0-7BE3-4000-BA98-6C13DE9FA486"; + + // CLSID GUID strings for relevant coclasses. + internal const string FileOpenDialog = "DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7"; + + internal const string FileSaveDialog = "C0B4E2F3-BA21-4773-8DBA-335EC946EB8B"; + internal const string KnownFolderManager = "4DF0C730-DF9D-4AE3-9153-AA6B82E9795A"; + internal const string QueryParserManager = "5088B39A-29B4-4d9d-8245-4EE289222F66"; + internal const string SearchFolderItemFactory = "14010e02-bbbd-41f0-88e3-eda371216584"; + internal const string ShellLibrary = "D9B3211D-E57F-4426-AAEF-30A806ADD397"; + } + + internal static class ShellIIDGuid + { + internal const string CShellLink = "00021401-0000-0000-C000-000000000046"; + + internal const string ICondition = "0FC988D4-C935-4b97-A973-46282EA175C8"; + + internal const string IConditionFactory = "A5EFE073-B16F-474f-9F3E-9F8B497A3E08"; + + internal const string IEnumIDList = "000214F2-0000-0000-C000-000000000046"; + + internal const string IEnumUnknown = "00000100-0000-0000-C000-000000000046"; + + internal const string IFileDialog = "42F85136-DB7E-439C-85F1-E4075D135FC8"; + + internal const string IFileDialogControlEvents = "36116642-D713-4B97-9B83-7484A9D00433"; + + internal const string IFileDialogCustomize = "E6FDD21A-163F-4975-9C8C-A69F1BA37034"; + + internal const string IFileDialogEvents = "973510DB-7D7F-452B-8975-74A85828D354"; + + internal const string IFileOpenDialog = "D57C7288-D4AD-4768-BE02-9D969532D960"; + + internal const string IFileSaveDialog = "84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB"; + + // IID GUID strings for relevant Shell COM interfaces. + internal const string IModalWindow = "B4DB1657-70D7-485E-8E3E-6FCB5A5C1802"; + + internal const string IPersist = "0000010c-0000-0000-C000-000000000046"; + internal const string IPersistStream = "00000109-0000-0000-C000-000000000046"; + internal const string IPropertyDescription = "6F79D558-3E96-4549-A1D1-7D75D2288814"; + internal const string IPropertyDescription2 = "57D2EDED-5062-400E-B107-5DAE79FE57A6"; + internal const string IPropertyDescriptionList = "1F9FC1D0-C39B-4B26-817F-011967D3440E"; + internal const string IPropertyEnumType = "11E1FBF9-2D56-4A6B-8DB3-7CD193A471F2"; + internal const string IPropertyEnumType2 = "9B6E051C-5DDD-4321-9070-FE2ACB55E794"; + internal const string IPropertyEnumTypeList = "A99400F4-3D84-4557-94BA-1242FB2CC9A6"; + internal const string IPropertyStore = "886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"; + internal const string IPropertyStoreCache = "3017056d-9a91-4e90-937d-746c72abbf4f"; + internal const string IPropertyStoreCapabilities = "c8e2d566-186e-4d49-bf41-6909ead56acc"; + internal const string IQueryParser = "2EBDEE67-3505-43f8-9946-EA44ABC8E5B0"; + internal const string IQueryParserManager = "A879E3C4-AF77-44fb-8F37-EBD1487CF920"; + internal const string IQuerySolution = "D6EBC66B-8921-4193-AFDD-A1789FB7FF57"; + internal const string IRichChunk = "4FDEF69C-DBC9-454e-9910-B34F3C64B510"; + internal const string ISearchFolderItemFactory = "a0ffbc28-5482-4366-be27-3e81e78e06c2"; + internal const string ISharedBitmap = "091162a4-bc96-411f-aae8-c5122cd03363"; + internal const string IShellFolder = "000214E6-0000-0000-C000-000000000046"; + internal const string IShellFolder2 = "93F2F68C-1D1B-11D3-A30E-00C04F79ABD1"; + internal const string IShellItem = "43826D1E-E718-42EE-BC55-A1E261C37BFE"; + internal const string IShellItem2 = "7E9FB0D3-919F-4307-AB2E-9B1860310C93"; + internal const string IShellItemArray = "B63EA76D-1F85-456F-A19C-48159EFA858B"; + internal const string IShellLibrary = "11A66EFA-382E-451A-9234-1E0E12EF3085"; + internal const string IShellLinkW = "000214F9-0000-0000-C000-000000000046"; + internal const string IThumbnailCache = "F676C15D-596A-4ce2-8234-33996F445DB1"; + } + + internal static class ShellKFIDGuid + { + internal const string ComputerFolder = "0AC0837C-BBF8-452A-850D-79D08E667CA7"; + internal const string Documents = "FDD39AD0-238F-46AF-ADB4-6C85480369C7"; + internal const string DocumentsLibrary = "7d49d726-3c21-4f05-99aa-fdc2c9474656"; + internal const string Favorites = "1777F761-68AD-4D8A-87BD-30B759FA33DD"; + internal const string GenericLibrary = "5c4f28b5-f869-4e84-8e60-f11db97c5cc7"; + internal const string Libraries = "1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE"; + internal const string MusicLibrary = "94d6ddcc-4a68-4175-a374-bd584a510b78"; + internal const string PicturesLibrary = "b3690e58-e961-423b-b687-386ebfd83239"; + internal const string Profile = "5E6C858F-0E22-4760-9AFE-EA3317B67173"; + internal const string VideosLibrary = "5fa96407-7e77-483c-ac93-691d05850de8"; + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellCOMInterfaces.cs b/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellCOMInterfaces.cs new file mode 100644 index 0000000..8a363ae --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellCOMInterfaces.cs @@ -0,0 +1,871 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem; +using Kermalis.VGMusicStudio.WinForms.API.Taskbar; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + internal enum SICHINTF + { + SICHINT_DISPLAY = 0x00000000, + SICHINT_CANONICAL = 0x10000000, + SICHINT_TEST_FILESYSPATH_IF_NOT_EQUAL = 0x20000000, + SICHINT_ALLFIELDS = unchecked((int)0x80000000) + } + + // Disable warning if a method declaration hides another inherited from a parent COM interface To successfully import a COM interface, + // all inherited methods need to be declared again with the exception of those already declared in "IUnknown" +#pragma warning disable 108 + + [ComImport(), + Guid(ShellIIDGuid.ICondition), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface ICondition : IPersistStream + { + // Summary: Retrieves the class identifier (CLSID) of an object. + // + // Parameters: pClassID: When this method returns, contains a reference to the CLSID. This parameter is passed uninitialized. + [PreserveSig] + void GetClassID(out Guid pClassID); + + // Summary: Checks an object for changes since it was last saved to its current file. + // + // Returns: S_OK if the file has changed since it was last saved; S_FALSE if the file has not changed since it was last saved. + [PreserveSig] + HResult IsDirty(); + + [PreserveSig] + HResult Load([In, MarshalAs(UnmanagedType.Interface)] IStream stm); + + [PreserveSig] + HResult Save([In, MarshalAs(UnmanagedType.Interface)] IStream stm, bool fRemember); + + [PreserveSig] + HResult GetSizeMax(out ulong cbSize); + + // For any node, return what kind of node it is. + [PreserveSig] + HResult GetConditionType([Out()] out SearchConditionType pNodeType); + + // riid must be IID_IEnumUnknown, IID_IEnumVARIANT or IID_IObjectArray, or in the case of a negation node IID_ICondition. If this is + // a leaf node, E_FAIL will be returned. If this is a negation node, then if riid is IID_ICondition, *ppv will be set to a single + // ICondition, otherwise an enumeration of one. If this is a conjunction or a disjunction, *ppv will be set to an enumeration of the subconditions. + [PreserveSig] + HResult GetSubConditions([In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out object ppv); + + // If this is not a leaf node, E_FAIL will be returned. Retrieve the property name, operation and value from the leaf node. Any one + // of ppszPropertyName, pcop and ppropvar may be NULL. + [PreserveSig] + HResult GetComparisonInfo( + [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszPropertyName, + [Out] out SearchConditionOperation pcop, + [Out] PropVariant ppropvar); + + // If this is not a leaf node, E_FAIL will be returned. + // *ppszValueTypeName will be set to the semantic type of the value, or to NULL if this is not meaningful. + [PreserveSig] + HResult GetValueType([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszValueTypeName); + + // If this is not a leaf node, E_FAIL will be returned. If the value of the leaf node is VT_EMPTY, *ppszNormalization will be set to + // an empty string. If the value is a string (VT_LPWSTR, VT_BSTR or VT_LPSTR), then *ppszNormalization will be set to a + // character-normalized form of the value. Otherwise, *ppszNormalization will be set to some (character-normalized) string + // representation of the value. + [PreserveSig] + HResult GetValueNormalization([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszNormalization); + + // Return information about what parts of the input produced the property, the operation and the value. Any one of ppPropertyTerm, + // ppOperationTerm and ppValueTerm may be NULL. For a leaf node returned by the parser, the position information of each IRichChunk + // identifies the tokens that contributed the property/operation/value, the string value is the corresponding part of the input + // string, and the PROPVARIANT is VT_EMPTY. + [PreserveSig] + HResult GetInputTerms([Out] out IRichChunk ppPropertyTerm, [Out] out IRichChunk ppOperationTerm, [Out] out IRichChunk ppValueTerm); + + // Make a deep copy of this ICondition. + [PreserveSig] + HResult Clone([Out()] out ICondition ppc); + }; + + [ComImport, + Guid(ShellIIDGuid.IConditionFactory), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IConditionFactory + { + [PreserveSig] + HResult MakeNot([In] ICondition pcSub, [In] bool fSimplify, [Out] out ICondition ppcResult); + + [PreserveSig] + HResult MakeAndOr([In] SearchConditionType ct, [In] IEnumUnknown peuSubs, [In] bool fSimplify, [Out] out ICondition ppcResult); + + [PreserveSig] + HResult MakeLeaf( + [In, MarshalAs(UnmanagedType.LPWStr)] string pszPropertyName, + [In] SearchConditionOperation cop, + [In, MarshalAs(UnmanagedType.LPWStr)] string pszValueType, + [In] PropVariant ppropvar, + IRichChunk richChunk1, + IRichChunk richChunk2, + IRichChunk richChunk3, + [In] bool fExpand, + [Out] out ICondition ppcResult); + + [PreserveSig] + HResult Resolve(/*[In] ICondition pc, [In] STRUCTURED_QUERY_RESOLVE_OPTION sqro, [In] ref SYSTEMTIME pstReferenceTime, [Out] out ICondition ppcResolved*/); + }; + + [ComImport, + Guid("24264891-E80B-4fd3-B7CE-4FF2FAE8931F"), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IEntity + { + // TODO + } + + [ComImport, + Guid(ShellIIDGuid.IEnumIDList), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IEnumIDList + { + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult Next(uint celt, out IntPtr rgelt, out uint pceltFetched); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult Skip([In] uint celt); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult Reset(); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult Clone([MarshalAs(UnmanagedType.Interface)] out IEnumIDList ppenum); + } + + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid(ShellIIDGuid.IEnumUnknown)] + internal interface IEnumUnknown + { + [PreserveSig] + HResult Next(uint requestedNumber, ref IntPtr buffer, ref uint fetchedNumber); + + [PreserveSig] + HResult Skip(uint number); + + [PreserveSig] + HResult Reset(); + + [PreserveSig] + HResult Clone(out IEnumUnknown result); + } + + [ComImport(), + Guid(ShellIIDGuid.IModalWindow), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IModalWindow + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), + PreserveSig] + int Show([In] IntPtr parent); + } + + [ComImport, + Guid(ShellIIDGuid.IConditionFactory), + CoClass(typeof(ConditionFactoryCoClass))] + internal interface INativeConditionFactory : IConditionFactory + { + } + + [ComImport, + Guid(ShellIIDGuid.IQueryParserManager), + CoClass(typeof(QueryParserManagerCoClass))] + internal interface INativeQueryParserManager : IQueryParserManager + { + } + + [ComImport, + Guid(ShellIIDGuid.ISearchFolderItemFactory), + CoClass(typeof(SearchFolderItemFactoryCoClass))] + internal interface INativeSearchFolderItemFactory : ISearchFolderItemFactory + { + } + + // Summary: Provides the managed definition of the IPersistStream interface, with functionality from IPersist. + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("00000109-0000-0000-C000-000000000046")] + internal interface IPersistStream + { + // Summary: Retrieves the class identifier (CLSID) of an object. + // + // Parameters: pClassID: When this method returns, contains a reference to the CLSID. This parameter is passed uninitialized. + [PreserveSig] + void GetClassID(out Guid pClassID); + + // Summary: Checks an object for changes since it was last saved to its current file. + // + // Returns: S_OK if the file has changed since it was last saved; S_FALSE if the file has not changed since it was last saved. + [PreserveSig] + HResult IsDirty(); + + [PreserveSig] + HResult Load([In, MarshalAs(UnmanagedType.Interface)] IStream stm); + + [PreserveSig] + HResult Save([In, MarshalAs(UnmanagedType.Interface)] IStream stm, bool fRemember); + + [PreserveSig] + HResult GetSizeMax(out ulong cbSize); + } + + [ComImport, + Guid(ShellIIDGuid.IQueryParser), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IQueryParser + { + // Parse parses an input string, producing a query solution. pCustomProperties should be an enumeration of IRichChunk objects, one + // for each custom property the application has recognized. pCustomProperties may be NULL, equivalent to an empty enumeration. For + // each IRichChunk, the position information identifies the character span of the custom property, the string value should be the + // name of an actual property, and the PROPVARIANT is completely ignored. + [PreserveSig] + HResult Parse([In, MarshalAs(UnmanagedType.LPWStr)] string pszInputString, [In] IEnumUnknown pCustomProperties, [Out] out IQuerySolution ppSolution); + + // Set a single option. See STRUCTURED_QUERY_SINGLE_OPTION above. + [PreserveSig] + HResult SetOption([In] StructuredQuerySingleOption option, [In] PropVariant pOptionValue); + + [PreserveSig] + HResult GetOption([In] StructuredQuerySingleOption option, [Out] PropVariant pOptionValue); + + // Set a multi option. See STRUCTURED_QUERY_MULTIOPTION above. + [PreserveSig] + HResult SetMultiOption([In] StructuredQueryMultipleOption option, [In, MarshalAs(UnmanagedType.LPWStr)] string pszOptionKey, [In] PropVariant pOptionValue); + + // Get a schema provider for browsing the currently loaded schema. + [PreserveSig] + HResult GetSchemaProvider([Out] out /*ISchemaProvider*/ IntPtr ppSchemaProvider); + + // Restate a condition as a query string according to the currently selected syntax. The parameter fUseEnglish is reserved for future + // use; must be FALSE. + [PreserveSig] + HResult RestateToString([In] ICondition pCondition, [In] bool fUseEnglish, [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszQueryString); + + // Parse a condition for a given property. It can be anything that would go after 'PROPERTY:' in an AQS expession. + [PreserveSig] + HResult ParsePropertyValue([In, MarshalAs(UnmanagedType.LPWStr)] string pszPropertyName, [In, MarshalAs(UnmanagedType.LPWStr)] string pszInputString, [Out] out IQuerySolution ppSolution); + + // Restate a condition for a given property. If the condition contains a leaf with any other property name, or no property name at + // all, E_INVALIDARG will be returned. + [PreserveSig] + HResult RestatePropertyValueToString([In] ICondition pCondition, [In] bool fUseEnglish, [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszPropertyName, [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszQueryString); + } + + [ComImport, + Guid(ShellIIDGuid.IQueryParserManager), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IQueryParserManager + { + // Create a query parser loaded with the schema for a certain catalog localize to a certain language, and initialized with standard + // defaults. One valid value for riid is IID_IQueryParser. + [PreserveSig] + HResult CreateLoadedParser([In, MarshalAs(UnmanagedType.LPWStr)] string pszCatalog, [In] ushort langidForKeywords, [In] ref Guid riid, [Out] out IQueryParser ppQueryParser); + + // In addition to setting AQS/NQS and automatic wildcard for the given query parser, this sets up standard named entity handlers and + // sets the keyboard locale as locale for word breaking. + [PreserveSig] + HResult InitializeOptions([In] bool fUnderstandNQS, [In] bool fAutoWildCard, [In] IQueryParser pQueryParser); + + // Change one of the settings for the query parser manager, such as the name of the schema binary, or the location of the localized + // and unlocalized schema binaries. By default, the settings point to the schema binaries used by Windows Shell. + [PreserveSig] + HResult SetOption([In] QueryParserManagerOption option, [In] PropVariant pOptionValue); + }; + + [ComImport, + Guid(ShellIIDGuid.IQuerySolution), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IQuerySolution : IConditionFactory + { + [PreserveSig] + HResult MakeNot([In] ICondition pcSub, [In] bool fSimplify, [Out] out ICondition ppcResult); + + [PreserveSig] + HResult MakeAndOr([In] SearchConditionType ct, [In] IEnumUnknown peuSubs, [In] bool fSimplify, [Out] out ICondition ppcResult); + + [PreserveSig] + HResult MakeLeaf( + [In, MarshalAs(UnmanagedType.LPWStr)] string pszPropertyName, + [In] SearchConditionOperation cop, + [In, MarshalAs(UnmanagedType.LPWStr)] string pszValueType, + [In] PropVariant ppropvar, + IRichChunk richChunk1, + IRichChunk richChunk2, + IRichChunk richChunk3, + [In] bool fExpand, + [Out] out ICondition ppcResult); + + [PreserveSig] + HResult Resolve(/*[In] ICondition pc, [In] int sqro, [In] ref SYSTEMTIME pstReferenceTime, [Out] out ICondition ppcResolved*/); + + // Retrieve the condition tree and the "main type" of the solution. ppQueryNode and ppMainType may be NULL. + [PreserveSig] + HResult GetQuery([Out, MarshalAs(UnmanagedType.Interface)] out ICondition ppQueryNode, [Out, MarshalAs(UnmanagedType.Interface)] out IEntity ppMainType); + + // Identify parts of the input string not accounted for. Each parse error is represented by an IRichChunk where the position + // information reflect token counts, the string is NULL and the value is a VT_I4 where lVal is from the ParseErrorType enumeration. + // The valid values for riid are IID_IEnumUnknown and IID_IEnumVARIANT. + [PreserveSig] + HResult GetErrors([In] ref Guid riid, [Out] out /* void** */ IntPtr ppParseErrors); + + // Report the query string, how it was tokenized and what LCID and word breaker were used (for recognizing keywords). + // ppszInputString, ppTokens, pLocale and ppWordBreaker may be NULL. + [PreserveSig] + HResult GetLexicalData([MarshalAs(UnmanagedType.LPWStr)] out string ppszInputString, [Out] /* ITokenCollection** */ out IntPtr ppTokens, [Out] out uint plcid, [Out] /* IUnknown** */ out IntPtr ppWordBreaker); + } + + [ComImport, + Guid(ShellIIDGuid.IRichChunk), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IRichChunk + { + // The position *pFirstPos is zero-based. Any one of pFirstPos, pLength, ppsz and pValue may be NULL. + [PreserveSig] + HResult GetData(/*[out, annotation("__out_opt")] ULONG* pFirstPos, [out, annotation("__out_opt")] ULONG* pLength, [out, annotation("__deref_opt_out_opt")] LPWSTR* ppsz, [out, annotation("__out_opt")] PROPVARIANT* pValue*/); + } + + [ComImport, + Guid(ShellIIDGuid.ISearchFolderItemFactory), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface ISearchFolderItemFactory + { + [PreserveSig] + HResult SetDisplayName([In, MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName); + + [PreserveSig] + HResult SetFolderTypeID([In] Guid ftid); + + [PreserveSig] + HResult SetFolderLogicalViewMode([In] FolderLogicalViewMode flvm); + + [PreserveSig] + HResult SetIconSize([In] int iIconSize); + + [PreserveSig] + HResult SetVisibleColumns([In] uint cVisibleColumns, [In, MarshalAs(UnmanagedType.LPArray)] PropertyKey[] rgKey); + + [PreserveSig] + HResult SetSortColumns([In] uint cSortColumns, [In, MarshalAs(UnmanagedType.LPArray)] SortColumn[] rgSortColumns); + + [PreserveSig] + HResult SetGroupColumn([In] ref PropertyKey keyGroup); + + [PreserveSig] + HResult SetStacks([In] uint cStackKeys, [In, MarshalAs(UnmanagedType.LPArray)] PropertyKey[] rgStackKeys); + + [PreserveSig] + HResult SetScope([In, MarshalAs(UnmanagedType.Interface)] IShellItemArray ppv); + + [PreserveSig] + HResult SetCondition([In] ICondition pCondition); + + [PreserveSig] + int GetShellItem(ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IShellItem ppv); + + [PreserveSig] + HResult GetIDList([Out] IntPtr ppidl); + }; + + [ComImport, + Guid(ShellIIDGuid.ISharedBitmap), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface ISharedBitmap + { + void GetSharedBitmap([Out] out IntPtr phbm); + + void GetSize([Out] out CoreNativeMethods.Size pSize); + + void GetFormat([Out] out ThumbnailAlphaType pat); + + void InitializeBitmap([In] IntPtr hbm, [In] ThumbnailAlphaType wtsAT); + + void Detach([Out] out IntPtr phbm); + } + + [ComImport, + Guid(ShellIIDGuid.IShellFolder), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown), + ComConversionLoss] + internal interface IShellFolder + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void ParseDisplayName(IntPtr hwnd, [In, MarshalAs(UnmanagedType.Interface)] IBindCtx pbc, [In, MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName, [In, Out] ref uint pchEaten, [Out] IntPtr ppidl, [In, Out] ref uint pdwAttributes); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult EnumObjects([In] IntPtr hwnd, [In] ShellNativeMethods.ShellFolderEnumerationOptions grfFlags, [MarshalAs(UnmanagedType.Interface)] out IEnumIDList ppenumIDList); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult BindToObject([In] IntPtr pidl, /*[In, MarshalAs(UnmanagedType.Interface)] IBindCtx*/ IntPtr pbc, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IShellFolder ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void BindToStorage([In] ref IntPtr pidl, [In, MarshalAs(UnmanagedType.Interface)] IBindCtx pbc, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void CompareIDs([In] IntPtr lParam, [In] ref IntPtr pidl1, [In] ref IntPtr pidl2); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void CreateViewObject([In] IntPtr hwndOwner, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAttributesOf([In] uint cidl, [In] IntPtr apidl, [In, Out] ref uint rgfInOut); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetUIObjectOf([In] IntPtr hwndOwner, [In] uint cidl, [In] IntPtr apidl, [In] ref Guid riid, [In, Out] ref uint rgfReserved, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDisplayNameOf([In] ref IntPtr pidl, [In] uint uFlags, out IntPtr pName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetNameOf([In] IntPtr hwnd, [In] ref IntPtr pidl, [In, MarshalAs(UnmanagedType.LPWStr)] string pszName, [In] uint uFlags, [Out] IntPtr ppidlOut); + } + + [ComImport, + Guid(ShellIIDGuid.IShellFolder2), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown), + ComConversionLoss] + internal interface IShellFolder2 : IShellFolder + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void ParseDisplayName([In] IntPtr hwnd, [In, MarshalAs(UnmanagedType.Interface)] IBindCtx pbc, [In, MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName, [In, Out] ref uint pchEaten, [Out] IntPtr ppidl, [In, Out] ref uint pdwAttributes); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void EnumObjects([In] IntPtr hwnd, [In] ShellNativeMethods.ShellFolderEnumerationOptions grfFlags, [MarshalAs(UnmanagedType.Interface)] out IEnumIDList ppenumIDList); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void BindToObject([In] IntPtr pidl, /*[In, MarshalAs(UnmanagedType.Interface)] IBindCtx*/ IntPtr pbc, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IShellFolder ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void BindToStorage([In] ref IntPtr pidl, [In, MarshalAs(UnmanagedType.Interface)] IBindCtx pbc, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void CompareIDs([In] IntPtr lParam, [In] ref IntPtr pidl1, [In] ref IntPtr pidl2); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void CreateViewObject([In] IntPtr hwndOwner, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAttributesOf([In] uint cidl, [In] IntPtr apidl, [In, Out] ref uint rgfInOut); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetUIObjectOf([In] IntPtr hwndOwner, [In] uint cidl, [In] IntPtr apidl, [In] ref Guid riid, [In, Out] ref uint rgfReserved, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDisplayNameOf([In] ref IntPtr pidl, [In] uint uFlags, out IntPtr pName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetNameOf([In] IntPtr hwnd, [In] ref IntPtr pidl, [In, MarshalAs(UnmanagedType.LPWStr)] string pszName, [In] uint uFlags, [Out] IntPtr ppidlOut); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDefaultSearchGUID(out Guid pguid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void EnumSearches([Out] out IntPtr ppenum); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDefaultColumn([In] uint dwRes, out uint pSort, out uint pDisplay); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDefaultColumnState([In] uint iColumn, out uint pcsFlags); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDetailsEx([In] ref IntPtr pidl, [In] ref PropertyKey pscid, [MarshalAs(UnmanagedType.Struct)] out object pv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDetailsOf([In] ref IntPtr pidl, [In] uint iColumn, out IntPtr psd); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void MapColumnToSCID([In] uint iColumn, out PropertyKey pscid); + } + + [ComImport, + Guid(ShellIIDGuid.IShellItem), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellItem + { + // Not supported: IBindCtx. + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult BindToHandler( + [In] IntPtr pbc, + [In] ref Guid bhid, + [In] ref Guid riid, + [Out, MarshalAs(UnmanagedType.Interface)] out IShellFolder ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetDisplayName( + [In] ShellNativeMethods.ShellItemDesignNameOptions sigdnName, + out IntPtr ppszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAttributes([In] ShellNativeMethods.ShellFileGetAttributesOptions sfgaoMask, out ShellNativeMethods.ShellFileGetAttributesOptions psfgaoAttribs); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult Compare( + [In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, + [In] SICHINTF hint, + out int piOrder); + } + + [ComImport, + Guid(ShellIIDGuid.IShellItem2), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellItem2 : IShellItem + { + // Not supported: IBindCtx. + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult BindToHandler( + [In] IntPtr pbc, + [In] ref Guid bhid, + [In] ref Guid riid, + [Out, MarshalAs(UnmanagedType.Interface)] out IShellFolder ppv); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetDisplayName( + [In] ShellNativeMethods.ShellItemDesignNameOptions sigdnName, + [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAttributes([In] ShellNativeMethods.ShellFileGetAttributesOptions sfgaoMask, out ShellNativeMethods.ShellFileGetAttributesOptions psfgaoAttribs); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Compare( + [In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, + [In] uint hint, + out int piOrder); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig] + int GetPropertyStore( + [In] ShellNativeMethods.GetPropertyStoreOptions Flags, + [In] ref Guid riid, + [Out, MarshalAs(UnmanagedType.Interface)] out IPropertyStore ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPropertyStoreWithCreateObject([In] ShellNativeMethods.GetPropertyStoreOptions Flags, [In, MarshalAs(UnmanagedType.IUnknown)] object punkCreateObject, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPropertyStoreForKeys([In] ref PropertyKey rgKeys, [In] uint cKeys, [In] ShellNativeMethods.GetPropertyStoreOptions Flags, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.IUnknown)] out IPropertyStore ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPropertyDescriptionList([In] ref PropertyKey keyType, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult Update([In, MarshalAs(UnmanagedType.Interface)] IBindCtx pbc); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetProperty([In] ref PropertyKey key, [Out] PropVariant ppropvar); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetCLSID([In] ref PropertyKey key, out Guid pclsid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetFileTime([In] ref PropertyKey key, out System.Runtime.InteropServices.ComTypes.FILETIME pft); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetInt32([In] ref PropertyKey key, out int pi); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetString([In] ref PropertyKey key, [MarshalAs(UnmanagedType.LPWStr)] out string ppsz); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetUInt32([In] ref PropertyKey key, out uint pui); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetUInt64([In] ref PropertyKey key, out ulong pull); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetBool([In] ref PropertyKey key, out int pf); + } + + [ComImport, + Guid(ShellIIDGuid.IShellItemArray), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellItemArray + { + // Not supported: IBindCtx. + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult BindToHandler( + [In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, + [In] ref Guid rbhid, + [In] ref Guid riid, + out IntPtr ppvOut); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetPropertyStore( + [In] int Flags, + [In] ref Guid riid, + out IntPtr ppv); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetPropertyDescriptionList( + [In] ref PropertyKey keyType, + [In] ref Guid riid, + out IntPtr ppv); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetAttributes( + [In] ShellNativeMethods.ShellItemAttributeOptions dwAttribFlags, + [In] ShellNativeMethods.ShellFileGetAttributesOptions sfgaoMask, + out ShellNativeMethods.ShellFileGetAttributesOptions psfgaoAttribs); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetCount(out uint pdwNumItems); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetItemAt( + [In] uint dwIndex, + [MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + // Not supported: IEnumShellItems (will use GetCount and GetItemAt instead). + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult EnumItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenumShellItems); + } + + [ComImportAttribute()] + [GuidAttribute("bcc18b79-ba16-442f-80c4-8a59c30c463b")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellItemImageFactory + { + [PreserveSig] + HResult GetImage( + [In, MarshalAs(UnmanagedType.Struct)] CoreNativeMethods.Size size, + [In] ShellNativeMethods.SIIGBF flags, + [Out] out IntPtr phbm); + } + + [ComImport, + Guid(ShellIIDGuid.IShellLibrary), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellLibrary + { + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult LoadLibraryFromItem( + [In, MarshalAs(UnmanagedType.Interface)] IShellItem library, + [In] AccessModes grfMode); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void LoadLibraryFromKnownFolder( + [In] ref Guid knownfidLibrary, + [In] AccessModes grfMode); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void AddFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem location); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void RemoveFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem location); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetFolders( + [In] ShellNativeMethods.LibraryFolderFilter lff, + [In] ref Guid riid, + [MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void ResolveFolder( + [In, MarshalAs(UnmanagedType.Interface)] IShellItem folderToResolve, + [In] uint timeout, + [In] ref Guid riid, + [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDefaultSaveFolder( + [In] ShellNativeMethods.DefaultSaveFolderType dsft, + [In] ref Guid riid, + [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetDefaultSaveFolder( + [In] ShellNativeMethods.DefaultSaveFolderType dsft, + [In, MarshalAs(UnmanagedType.Interface)] IShellItem si); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetOptions( + out ShellNativeMethods.LibraryOptions lofOptions); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetOptions( + [In] ShellNativeMethods.LibraryOptions lofMask, + [In] ShellNativeMethods.LibraryOptions lofOptions); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetFolderType(out Guid ftid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetFolderType([In] ref Guid ftid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetIcon([MarshalAs(UnmanagedType.LPWStr)] out string icon); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetIcon([In, MarshalAs(UnmanagedType.LPWStr)] string icon); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Commit(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Save( + [In, MarshalAs(UnmanagedType.Interface)] IShellItem folderToSaveIn, + [In, MarshalAs(UnmanagedType.LPWStr)] string libraryName, + [In] ShellNativeMethods.LibrarySaveOptions lsf, + [MarshalAs(UnmanagedType.Interface)] out IShellItem2 savedTo); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SaveInKnownFolder( + [In] ref Guid kfidToSaveIn, + [In, MarshalAs(UnmanagedType.LPWStr)] string libraryName, + [In] ShellNativeMethods.LibrarySaveOptions lsf, + [MarshalAs(UnmanagedType.Interface)] out IShellItem2 savedTo); + }; + + [ComImport, + Guid(ShellIIDGuid.IShellLinkW), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellLinkW + { + void GetPath( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, + int cchMaxPath, + //ref _WIN32_FIND_DATAW pfd, + IntPtr pfd, + uint fFlags); + + void GetIDList(out IntPtr ppidl); + + void SetIDList(IntPtr pidl); + + void GetDescription( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, + int cchMaxName); + + void SetDescription( + [MarshalAs(UnmanagedType.LPWStr)] string pszName); + + void GetWorkingDirectory( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, + int cchMaxPath + ); + + void SetWorkingDirectory( + [MarshalAs(UnmanagedType.LPWStr)] string pszDir); + + void GetArguments( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, + int cchMaxPath); + + void SetArguments( + [MarshalAs(UnmanagedType.LPWStr)] string pszArgs); + + void GetHotKey(out short wHotKey); + + void SetHotKey(short wHotKey); + + void GetShowCmd(out uint iShowCmd); + + void SetShowCmd(uint iShowCmd); + + void GetIconLocation( + [Out(), MarshalAs(UnmanagedType.LPWStr)] out StringBuilder pszIconPath, + int cchIconPath, + out int iIcon); + + void SetIconLocation( + [MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, + int iIcon); + + void SetRelativePath( + [MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, + uint dwReserved); + + void Resolve(IntPtr hwnd, uint fFlags); + + void SetPath( + [MarshalAs(UnmanagedType.LPWStr)] string pszFile); + } + + [ComImport, + Guid(ShellIIDGuid.IThumbnailCache), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IThumbnailCache + { + void GetThumbnail([In] IShellItem pShellItem, + [In] uint cxyRequestedThumbSize, + [In] ShellNativeMethods.ThumbnailOptions flags, + [Out] out ISharedBitmap ppvThumb, + [Out] out ShellNativeMethods.ThumbnailCacheOptions pOutFlags, + [Out] ShellNativeMethods.ThumbnailId pThumbnailID); + + void GetThumbnailByID([In] ShellNativeMethods.ThumbnailId thumbnailID, + [In] uint cxyRequestedThumbSize, + [Out] out ISharedBitmap ppvThumb, + [Out] out ShellNativeMethods.ThumbnailCacheOptions pOutFlags); + } + + [ComImport, + ClassInterface(ClassInterfaceType.None), + TypeLibType(TypeLibTypeFlags.FCanCreate), + Guid(ShellCLSIDGuid.ConditionFactory)] + internal class ConditionFactoryCoClass + { + } + + [ComImport, + Guid(ShellIIDGuid.CShellLink), + ClassInterface(ClassInterfaceType.None)] + internal class CShellLink { } + + [ComImport, + ClassInterface(ClassInterfaceType.None), + TypeLibType(TypeLibTypeFlags.FCanCreate), + Guid(ShellCLSIDGuid.QueryParserManager)] + internal class QueryParserManagerCoClass + { + } + + [ComImport, + ClassInterface(ClassInterfaceType.None), + TypeLibType(TypeLibTypeFlags.FCanCreate), + Guid(ShellCLSIDGuid.SearchFolderItemFactory)] + internal class SearchFolderItemFactoryCoClass + { + } + +#pragma warning restore 108 +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellNativeMethods.cs b/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellNativeMethods.cs new file mode 100644 index 0000000..75034b3 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellNativeMethods.cs @@ -0,0 +1,595 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + internal static class ShellNativeMethods + { + internal const int CommandLink = 0x0000000E; + + internal const uint GetNote = 0x0000160A; + + internal const uint GetNoteLength = 0x0000160B; + + internal const int InPlaceStringTruncated = 0x00401A0; + + internal const int MaxPath = 260; + + internal const uint SetNote = 0x00001609; + + internal const uint SetShield = 0x0000160C; + + internal enum ControlState + { + Inactive = 0x00000000, + Enable = 0x00000001, + Visible = 0x00000002 + } + + internal enum DefaultSaveFolderType + { + Detect = 1, + Private = 2, + Public = 3 + }; + + internal enum FileDialogAddPlacement + { + Bottom = 0x00000000, + Top = 0x00000001, + } + + internal enum FileDialogEventOverwriteResponse + { + Default = 0x00000000, + Accept = 0x00000001, + Refuse = 0x00000002 + } + + internal enum FileDialogEventShareViolationResponse + { + Default = 0x00000000, + Accept = 0x00000001, + Refuse = 0x00000002 + } + + [Flags] + internal enum FileOpenOptions + { + OverwritePrompt = 0x00000002, + StrictFileTypes = 0x00000004, + NoChangeDirectory = 0x00000008, + PickFolders = 0x00000020, + + // Ensure that items returned are filesystem items. + ForceFilesystem = 0x00000040, + + // Allow choosing items that have no storage. + AllNonStorageItems = 0x00000080, + + NoValidate = 0x00000100, + AllowMultiSelect = 0x00000200, + PathMustExist = 0x00000800, + FileMustExist = 0x00001000, + CreatePrompt = 0x00002000, + ShareAware = 0x00004000, + NoReadOnlyReturn = 0x00008000, + NoTestFileCreate = 0x00010000, + HideMruPlaces = 0x00020000, + HidePinnedPlaces = 0x00040000, + NoDereferenceLinks = 0x00100000, + DontAddToRecent = 0x02000000, + ForceShowHidden = 0x10000000, + DefaultNoMiniMode = 0x20000000 + } + + /// + /// Indicate flags that modify the property store object retrieved by methods that create a property store, such as + /// IShellItem2::GetPropertyStore or IPropertyStoreFactory::GetPropertyStore. + /// + [Flags] + internal enum GetPropertyStoreOptions + { + /// + /// Meaning to a calling process: Return a read-only property store that contains all properties. Slow items (offline files) are + /// not opened. Combination with other flags: Can be overridden by other flags. + /// + Default = 0, + + /// + /// Meaning to a calling process: Include only properties directly from the property handler, which opens the file on the disk, + /// network, or device. Meaning to a file + /// folder: Only include properties directly from the handler. + /// + /// Meaning to other folders: When delegating to a file folder, pass this flag on to the file folder; do not do any multiplexing + /// (MUX). When not delegating to a file folder, ignore this flag instead of returning a failure code. + /// + /// Combination with other flags: Cannot be combined with GPS_TEMPORARY, GPS_FASTPROPERTIESONLY, or GPS_BESTEFFORT. + /// + HandlePropertiesOnly = 0x1, + + /// + /// Meaning to a calling process: Can write properties to the item. + /// Note: The store may contain fewer properties than a read-only store. + /// + /// Meaning to a file folder: ReadWrite. + /// + /// Meaning to other folders: ReadWrite. Note: When using default MUX, return a single unmultiplexed store because the default + /// MUX does not support ReadWrite. + /// + /// Combination with other flags: Cannot be combined with GPS_TEMPORARY, GPS_FASTPROPERTIESONLY, GPS_BESTEFFORT, or + /// GPS_DELAYCREATION. Implies GPS_HANDLERPROPERTIESONLY. + /// + ReadWrite = 0x2, + + /// + /// Meaning to a calling process: Provides a writable store, with no initial properties, that exists for the lifetime of the + /// Shell item instance; basically, a property bag attached to the item instance. + /// + /// Meaning to a file folder: Not applicable. Handled by the Shell item. + /// + /// Meaning to other folders: Not applicable. Handled by the Shell item. + /// + /// Combination with other flags: Cannot be combined with any other flag. Implies GPS_READWRITE + /// + Temporary = 0x4, + + /// + /// Meaning to a calling process: Provides a store that does not involve reading from the disk or network. Note: Some values may + /// be different, or missing, compared to a store without this flag. + /// + /// Meaning to a file folder: Include the "innate" and "fallback" stores only. Do not load the handler. + /// + /// Meaning to other folders: Include only properties that are available in memory or can be computed very quickly (no properties + /// from disk, network, or peripheral IO devices). This is normally only data sources from the IDLIST. When delegating to other + /// folders, pass this flag on to them. + /// + /// Combination with other flags: Cannot be combined with GPS_TEMPORARY, GPS_READWRITE, GPS_HANDLERPROPERTIESONLY, or GPS_DELAYCREATION. + /// + FastPropertiesOnly = 0x8, + + /// + /// Meaning to a calling process: Open a slow item (offline file) if necessary. Meaning to a file folder: Retrieve a file from + /// offline storage, if necessary. + /// Note: Without this flag, the handler is not created for offline files. + /// + /// Meaning to other folders: Do not return any properties that are very slow. + /// + /// Combination with other flags: Cannot be combined with GPS_TEMPORARY or GPS_FASTPROPERTIESONLY. + /// + OpensLowItem = 0x10, + + /// + /// Meaning to a calling process: Delay memory-intensive operations, such as file access, until a property is requested that + /// requires such access. + /// + /// Meaning to a file folder: Do not create the handler until needed; for example, either GetCount/GetAt or GetValue, where the + /// innate store does not satisfy the request. + /// Note: GetValue might fail due to file access problems. + /// + /// Meaning to other folders: If the folder has memory-intensive properties, such as delegating to a file folder or network + /// access, it can optimize performance by supporting IDelayedPropertyStoreFactory and splitting up its properties into a fast + /// and a slow store. It can then use delayed MUX to recombine them. + /// + /// Combination with other flags: Cannot be combined with GPS_TEMPORARY or GPS_READWRITE + /// + DelayCreation = 0x20, + + /// + /// Meaning to a calling process: Succeed at getting the store, even if some properties are not returned. Note: Some values may + /// be different, or missing, compared to a store without this flag. + /// + /// Meaning to a file folder: Succeed and return a store, even if the handler or innate store has an error during creation. Only + /// fail if substores fail. + /// + /// Meaning to other folders: Succeed on getting the store, even if some properties are not returned. + /// + /// Combination with other flags: Cannot be combined with GPS_TEMPORARY, GPS_READWRITE, or GPS_HANDLERPROPERTIESONLY. + /// + BestEffort = 0x40, + + /// Mask for valid GETPROPERTYSTOREFLAGS values. + MaskValid = 0xff, + } + + internal enum LibraryFolderFilter + { + ForceFileSystem = 1, + StorageItems = 2, + AllItems = 3 + }; + + internal enum LibraryManageDialogOptions + { + Default = 0, + NonIndexableLocationWarning = 1 + }; + + [Flags] + internal enum LibraryOptions + { + Default = 0, + PinnedToNavigationPane = 0x1, + MaskAll = 0x1 + }; + + internal enum LibrarySaveOptions + { + FailIfThere = 0, + OverrideExisting = 1, + MakeUniqueName = 2 + }; + + [Flags] + internal enum ShellChangeNotifyEventSource + { + InterruptLevel = 0x0001, + ShellLevel = 0x0002, + RecursiveInterrupt = 0x1000, + NewDelivery = 0x8000 + } + + [Flags] + internal enum ShellFileGetAttributesOptions + { + /// The specified items can be copied. + CanCopy = 0x00000001, + + /// The specified items can be moved. + CanMove = 0x00000002, + + /// + /// Shortcuts can be created for the specified items. This flag has the same value as DROPEFFECT. The normal use of this flag is + /// to add a Create Shortcut item to the shortcut menu that is displayed during drag-and-drop operations. However, SFGAO_CANLINK + /// also adds a Create Shortcut item to the Microsoft Windows Explorer's File menu and to normal shortcut menus. If this item is + /// selected, your application's IContextMenu::InvokeCommand is invoked with the lpVerb member of the CMINVOKECOMMANDINFO + /// structure set to "link." Your application is responsible for creating the link. + /// + CanLink = 0x00000004, + + /// The specified items can be bound to an IStorage interface through IShellFolder::BindToObject. + Storage = 0x00000008, + + /// The specified items can be renamed. + CanRename = 0x00000010, + + /// The specified items can be deleted. + CanDelete = 0x00000020, + + /// The specified items have property sheets. + HasPropertySheet = 0x00000040, + + /// The specified items are drop targets. + DropTarget = 0x00000100, + + /// This flag is a mask for the capability flags. + CapabilityMask = 0x00000177, + + /// Windows 7 and later. The specified items are system items. + System = 0x00001000, + + /// The specified items are encrypted. + Encrypted = 0x00002000, + + /// + /// Indicates that accessing the object = through IStream or other storage interfaces, is a slow operation. Applications should + /// avoid accessing items flagged with SFGAO_ISSLOW. + /// + IsSlow = 0x00004000, + + /// The specified items are ghosted icons. + Ghosted = 0x00008000, + + /// The specified items are shortcuts. + Link = 0x00010000, + + /// The specified folder objects are shared. + Share = 0x00020000, + + /// + /// The specified items are read-only. In the case of folders, this means that new items cannot be created in those folders. + /// + ReadOnly = 0x00040000, + + /// + /// The item is hidden and should not be displayed unless the Show hidden files and folders option is enabled in Folder Settings. + /// + Hidden = 0x00080000, + + /// This flag is a mask for the display attributes. + DisplayAttributeMask = 0x000FC000, + + /// The specified folders contain one or more file system folders. + FileSystemAncestor = 0x10000000, + + /// The specified items are folders. + Folder = 0x20000000, + + /// + /// The specified folders or file objects are part of the file system that is, they are files, directories, or root directories). + /// + FileSystem = 0x40000000, + + /// The specified folders have subfolders = and are, therefore, expandable in the left pane of Windows Explorer). + HasSubFolder = unchecked((int)0x80000000), + + /// This flag is a mask for the contents attributes. + ContentsMask = unchecked((int)0x80000000), + + /// + /// When specified as input, SFGAO_VALIDATE instructs the folder to validate that the items pointed to by the contents of apidl + /// exist. If one or more of those items do not exist, IShellFolder::GetAttributesOf returns a failure code. When used with the + /// file system folder, SFGAO_VALIDATE instructs the folder to discard cached properties retrieved by clients of + /// IShellFolder2::GetDetailsEx that may have accumulated for the specified items. + /// + Validate = 0x01000000, + + /// The specified items are on removable media or are themselves removable devices. + Removable = 0x02000000, + + /// The specified items are compressed. + Compressed = 0x04000000, + + /// The specified items can be browsed in place. + Browsable = 0x08000000, + + /// The items are nonenumerated items. + Nonenumerated = 0x00100000, + + /// The objects contain new content. + NewContent = 0x00200000, + + /// It is possible to create monikers for the specified file objects or folders. + CanMoniker = 0x00400000, + + /// Not supported. + HasStorage = 0x00400000, + + /// + /// Indicates that the item has a stream associated with it that can be accessed by a call to IShellFolder::BindToObject with + /// IID_IStream in the riid parameter. + /// + Stream = 0x00400000, + + /// + /// Children of this item are accessible through IStream or IStorage. Those children are flagged with SFGAO_STORAGE or SFGAO_STREAM. + /// + StorageAncestor = 0x00800000, + + /// This flag is a mask for the storage capability attributes. + StorageCapabilityMask = 0x70C50008, + + /// + /// Mask used by PKEY_SFGAOFlags to remove certain values that are considered to cause slow calculations or lack context. Equal + /// to SFGAO_VALIDATE | SFGAO_ISSLOW | SFGAO_HASSUBFOLDER. + /// + PkeyMask = unchecked((int)0x81044000), + } + + [Flags] + internal enum ShellFolderEnumerationOptions : ushort + { + CheckingForChildren = 0x0010, + Folders = 0x0020, + NonFolders = 0x0040, + IncludeHidden = 0x0080, + InitializeOnFirstNext = 0x0100, + NetPrinterSearch = 0x0200, + Shareable = 0x0400, + Storage = 0x0800, + NavigationEnum = 0x1000, + FastItems = 0x2000, + FlatList = 0x4000, + EnableAsync = 0x8000 + } + + internal enum ShellItemAttributeOptions + { + // if multiple items and the attirbutes together. + And = 0x00000001, + + // if multiple items or the attributes together. + Or = 0x00000002, + + // Call GetAttributes directly on the ShellFolder for multiple attributes. + AppCompat = 0x00000003, + + // A mask for SIATTRIBFLAGS_AND, SIATTRIBFLAGS_OR, and SIATTRIBFLAGS_APPCOMPAT. Callers normally do not use this value. + Mask = 0x00000003, + + // Windows 7 and later. Examine all items in the array to compute the attributes. Note that this can result in poor performance + // over large arrays and therefore it should be used only when needed. Cases in which you pass this flag should be extremely rare. + AllItems = 0x00004000 + } + + internal enum ShellItemDesignNameOptions + { + Normal = 0x00000000, // SIGDN_NORMAL + ParentRelativeParsing = unchecked((int)0x80018001), // SIGDN_INFOLDER | SIGDN_FORPARSING + DesktopAbsoluteParsing = unchecked((int)0x80028000), // SIGDN_FORPARSING + ParentRelativeEditing = unchecked((int)0x80031001), // SIGDN_INFOLDER | SIGDN_FOREDITING + DesktopAbsoluteEditing = unchecked((int)0x8004c000), // SIGDN_FORPARSING | SIGDN_FORADDRESSBAR + FileSystemPath = unchecked((int)0x80058000), // SIGDN_FORPARSING + Url = unchecked((int)0x80068000), // SIGDN_FORPARSING + ParentRelativeForAddressBar = unchecked((int)0x8007c001), // SIGDN_INFOLDER | SIGDN_FORPARSING | SIGDN_FORADDRESSBAR + ParentRelative = unchecked((int)0x80080001) // SIGDN_INFOLDER + } + + [Flags] + internal enum SIIGBF + { + ResizeToFit = 0x00, + BiggerSizeOk = 0x01, + MemoryOnly = 0x02, + IconOnly = 0x04, + ThumbnailOnly = 0x08, + InCacheOnly = 0x10, + } + + [Flags] + internal enum ThumbnailCacheOptions + { + Default = 0x00000000, + LowQuality = 0x00000001, + Cached = 0x00000002, + } + + [Flags] + internal enum ThumbnailOptions + { + Extract = 0x00000000, + InCacheOnly = 0x00000001, + FastExtract = 0x00000002, + ForceExtraction = 0x00000004, + SlowReclaim = 0x00000008, + ExtractDoNotCache = 0x00000020 + } + + [DllImport("shell32.dll", CharSet = CharSet.None)] + public static extern void ILFree(IntPtr pidl); + + [DllImport("gdi32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeleteObject(IntPtr hObject); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern uint ILGetSize(IntPtr pidl); + + [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int PathParseIconLocation( + [MarshalAs(UnmanagedType.LPWStr)] ref string pszIconFile); + + [DllImport("shell32.dll")] + internal static extern IntPtr SHChangeNotification_Lock( + IntPtr windowHandle, + int processId, + out IntPtr pidl, + out uint lEvent); + + [DllImport("shell32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SHChangeNotification_Unlock(IntPtr hLock); + + [DllImport("shell32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SHChangeNotifyDeregister(uint hNotify); + + [DllImport("shell32.dll")] + internal static extern uint SHChangeNotifyRegister( + IntPtr windowHandle, + ShellChangeNotifyEventSource sources, + ShellObjectChangeTypes events, + uint message, + int entries, + ref SHChangeNotifyEntry changeNotifyEntry); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int SHCreateItemFromIDList( + /*PCIDLIST_ABSOLUTE*/ IntPtr pidl, + ref Guid riid, + [MarshalAs(UnmanagedType.Interface)] out IShellItem2 ppv); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int SHCreateItemFromParsingName( + [MarshalAs(UnmanagedType.LPWStr)] string path, + // The following parameter is not used - binding context. + IntPtr pbc, + ref Guid riid, + [MarshalAs(UnmanagedType.Interface)] out IShellItem2 shellItem); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int SHCreateItemFromParsingName( + [MarshalAs(UnmanagedType.LPWStr)] string path, + // The following parameter is not used - binding context. + IntPtr pbc, + ref Guid riid, + [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int SHCreateShellItem( + IntPtr pidlParent, + [In, MarshalAs(UnmanagedType.Interface)] IShellFolder psfParent, + IntPtr pidl, + [MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi + ); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int SHCreateShellItemArrayFromDataObject( + System.Runtime.InteropServices.ComTypes.IDataObject pdo, + ref Guid riid, + [MarshalAs(UnmanagedType.Interface)] out IShellItemArray iShellItemArray); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int SHGetDesktopFolder( + [MarshalAs(UnmanagedType.Interface)] out IShellFolder ppshf + ); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int SHGetIDListFromObject(IntPtr iUnknown, + out IntPtr ppidl + ); + + [DllImport("shell32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SHGetPathFromIDListW(IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszPath); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int SHParseDisplayName( + [MarshalAs(UnmanagedType.LPWStr)] string pszName, + IntPtr pbc, + out IntPtr ppidl, + ShellFileGetAttributesOptions sfgaoIn, + out ShellFileGetAttributesOptions psfgaoOut + ); + + [DllImport("Shell32", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + internal static extern int SHShowManageLibraryUI( + [In, MarshalAs(UnmanagedType.Interface)] IShellItem library, + [In] IntPtr hwndOwner, + [In] string title, + [In] string instruction, + [In] LibraryManageDialogOptions lmdOptions); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct FilterSpec + { + [MarshalAs(UnmanagedType.LPWStr)] + internal string Name; + + [MarshalAs(UnmanagedType.LPWStr)] + internal string Spec; + + internal FilterSpec(string name, string spec) + { + Name = name; + Spec = spec; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SHChangeNotifyEntry + { + internal IntPtr pIdl; + + [MarshalAs(UnmanagedType.Bool)] + internal bool recursively; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ShellNotifyStruct + { + internal IntPtr item1; + internal IntPtr item2; + }; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct ThumbnailId + { + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 16)] + private readonly byte rgbKey; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellNativeStructs.cs b/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellNativeStructs.cs new file mode 100644 index 0000000..d456b4d --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/Common/ShellNativeStructs.cs @@ -0,0 +1,80 @@ +using System; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// + /// The STGM constants are flags that indicate conditions for creating and deleting the object and access modes for the object. + /// + /// You can combine these flags, but you can only choose one flag from each group of related flags. Typically one flag from each of the + /// access and sharing groups must be specified for all functions and methods which use these constants. + /// + [Flags] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Justification = "Follows native api.")] + public enum AccessModes + { + /// Indicates that, in direct mode, each change to a storage or stream element is written as it occurs. + Direct = 0x00000000, + + /// Indicates that, in transacted mode, changes are buffered and written only if an explicit commit operation is called. + Transacted = 0x00010000, + + /// Provides a faster implementation of a compound file in a limited, but frequently used, case. + Simple = 0x08000000, + + /// Indicates that the object is read-only, meaning that modifications cannot be made. + Read = 0x00000000, + + /// Enables you to save changes to the object, but does not permit access to its data. + Write = 0x00000001, + + /// Enables access and modification of object data. + ReadWrite = 0x00000002, + + /// Specifies that subsequent openings of the object are not denied read or write access. + ShareDenyNone = 0x00000040, + + /// Prevents others from subsequently opening the object in Read mode. + ShareDenyRead = 0x00000030, + + /// Prevents others from subsequently opening the object for Write or ReadWrite access. + ShareDenyWrite = 0x00000020, + + /// Prevents others from subsequently opening the object in any mode. + ShareExclusive = 0x00000010, + + /// Opens the storage object with exclusive access to the most recently committed version. + Priority = 0x00040000, + + /// + /// Indicates that the underlying file is to be automatically destroyed when the root storage object is released. This feature is + /// most useful for creating temporary files. + /// + DeleteOnRelease = 0x04000000, + + /// + /// Indicates that, in transacted mode, a temporary scratch file is usually used to save modifications until the Commit method is + /// called. Specifying NoScratch permits the unused portion of the original file to be used as work space instead of creating a new + /// file for that purpose. + /// + NoScratch = 0x00100000, + + /// Indicates that an existing storage object or stream should be removed before the new object replaces it. + Create = 0x00001000, + + /// Creates the new object while preserving existing data in a stream named "Contents". + Convert = 0x00020000, + + /// Causes the create operation to fail if an existing object with the specified name exists. + FailIfThere = 0x00000000, + + /// + /// This flag is used when opening a storage object with Transacted and without ShareExclusive or ShareDenyWrite. In this case, + /// specifying NoSnapshot prevents the system-provided implementation from creating a snapshot copy of the file. Instead, changes to + /// the file are written to the end of the file. + /// + NoSnapshot = 0x00200000, + + /// Supports direct mode for single-writer, multireader file operations. + DirectSingleWriterMultipleReader = 0x00400000 + }; +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/Common/WindowUtilities.cs b/VG Music Studio - WinForms/API/Shell/Interop/Common/WindowUtilities.cs new file mode 100644 index 0000000..0303ada --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/Common/WindowUtilities.cs @@ -0,0 +1,209 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +/* Unmerged change from project 'Shell (net452)' +Before: +using System; +After: +using Microsoft.WindowsAPICodePack.Shell.Interop; +*/ + +/* Unmerged change from project 'Shell (net462)' +Before: +using System; +After: +using Microsoft.WindowsAPICodePack.Shell.Interop; +*/ + +/* Unmerged change from project 'Shell (net472)' +Before: +using System; +After: +using Microsoft.WindowsAPICodePack.Shell.Interop; +*/ + +using Kermalis.VGMusicStudio.WinForms.API.Taskbar; +using System; + +/* Unmerged change from project 'Shell (net452)' +Before: +using Microsoft.WindowsAPICodePack.Shell.Interop; +After: +using System; +*/ + +/* Unmerged change from project 'Shell (net462)' +Before: +using Microsoft.WindowsAPICodePack.Shell.Interop; +After: +using System; +*/ + +/* Unmerged change from project 'Shell (net472)' +Before: +using Microsoft.WindowsAPICodePack.Shell.Interop; +After: +using System; +*/ + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + [Flags] + internal enum WindowStyles + { + /// The window has a thin-line border. + Border = 0x00800000, + + /// The window has a title bar (includes the WS_BORDER style). + Caption = 0x00C00000, + + /// + /// The window is a child window. A window with this style cannot have a menu bar. This style cannot be used with the WS_POPUP style. + /// + Child = 0x40000000, + + /// Same as the WS_CHILD style. + ChildWindow = 0x40000000, + + /// + /// Excludes the area occupied by child windows when drawing occurs within the parent window. This style is used when creating the + /// parent window. + /// + ClipChildren = 0x02000000, + + /// + /// Clips child windows relative to each other; that is, when a particular child window receives a WM_PAINT message, the + /// WS_CLIPSIBLINGS style clips all other overlapping child windows out of the region of the child window to be updated. If + /// WS_CLIPSIBLINGS is not specified and child windows overlap, it is possible, when drawing within the client area of a child + /// window, to draw within the client area of a neighboring child window. + /// + ClipSiblings = 0x04000000, + + /// + /// The window is initially disabled. A disabled window cannot receive input from the user. To change this after a window has been + /// created, use the EnableWindow function. + /// + Disabled = 0x08000000, + + /// + /// The window has a border of a style typically used with dialog boxes. A window with this style cannot have a title bar. + /// + DialogFrame = 0x0040000, + + /// + /// The window is the first control of a group of controls. The group consists of this first control and all controls defined after + /// it, up to the next control with the WS_GROUP style. The first control in each group usually has the WS_TABSTOP style so that the + /// user can move from group to group. The user can subsequently change the keyboard focus from one control in the group to the next + /// control in the group by using the direction keys. + /// + /// You can turn this style on and off to change dialog box navigation. To change this style after a window has been created, use the + /// SetWindowLong function. + /// + Group = 0x00020000, + + /// The window has a horizontal scroll bar. + HorizontalScroll = 0x00100000, + + /// The window is initially minimized. Same as the WS_MINIMIZE style. + Iconic = 0x20000000, + + /// The window is initially maximized. + Maximize = 0x01000000, + + /// + /// The window has a maximize button. Cannot be combined with the WS_EX_CONTEXTHELP style. The WS_SYSMENU style must also be specifie + /// + MaximizeBox = 0x00010000, + + /// The window is initially minimized. Same as the WS_ICONIC style. + Minimize = 0x20000000, + + /// + /// The window has a minimize button. Cannot be combined with the WS_EX_CONTEXTHELP style. The WS_SYSMENU style must also be specified. + /// + MinimizeBox = 0x00020000, + + /// The window is an overlapped window. An overlapped window has a title bar and a border. Same as the WS_TILED style. + Overlapped = 0x00000000, + + /// The windows is a pop-up window. This style cannot be used with the WS_CHILD style. + Popup = unchecked((int)0x80000000), + + /// The window has a sizing border. Same as the WS_THICKFRAME style. + SizeBox = 0x00040000, + + /// The window has a window menu on its title bar. The WS_CAPTION style must also be specified. + SystemMenu = 0x00080000, + + /// + /// The window is a control that can receive the keyboard focus when the user presses the TAB key. Pressing the TAB key changes the + /// keyboard focus to the next control with the WS_TABSTOP style. + /// + /// You can turn this style on and off to change dialog box navigation. To change this style after a window has been created, use the + /// SetWindowLong function. For user-created windows and modeless dialogs to work with tab stops, alter the message loop to call the + /// IsDialogMessage function. + /// + Tabstop = 0x00010000, + + /// The window has a sizing border. Same as the WS_SIZEBOX style. + ThickFrame = 0x00040000, + + /// + /// The window is an overlapped window. An overlapped window has a title bar and a border. Same as the WS_OVERLAPPED style. + /// + Tiled = 0x00000000, + + /// + /// The window is initially visible. + /// + /// This style can be turned on and off by using the ShowWindow or SetWindowPos function. + /// + Visible = 0x10000000, + + /// The window has a vertical scroll bar. + VerticalScroll = 0x00200000, + + /// The window is an overlapped window. Same as the WS_OVERLAPPEDWINDOW style. + TiledWindowMask = Overlapped | Caption | SystemMenu | ThickFrame | MinimizeBox | MaximizeBox, + + /// + /// The window is a pop-up window. The WS_CAPTION and WS_POPUPWINDOW styles must be combined to make the window menu visible. + /// + PopupWindowMask = Popup | Border | SystemMenu, + + /// The window is an overlapped window. Same as the WS_TILEDWINDOW style. + OverlappedWindowMask = Overlapped | Caption | SystemMenu | ThickFrame | MinimizeBox | MaximizeBox, + } + + internal static class WindowUtilities + { + internal static System.Drawing.Size GetNonClientArea(IntPtr hwnd) + { + var c = new NativePoint(); + + TabbedThumbnailNativeMethods.ClientToScreen(hwnd, ref c); + + var r = new NativeRect(); + + TabbedThumbnailNativeMethods.GetWindowRect(hwnd, ref r); + + return new System.Drawing.Size(c.X - r.Left, c.Y - r.Top); + } + + internal static System.Drawing.Point GetParentOffsetOfChild(IntPtr hwnd, IntPtr hwndParent) + { + var childScreenCoord = new NativePoint(); + + TabbedThumbnailNativeMethods.ClientToScreen(hwnd, ref childScreenCoord); + + var parentScreenCoord = new NativePoint(); + + TabbedThumbnailNativeMethods.ClientToScreen(hwndParent, ref parentScreenCoord); + + var offset = new System.Drawing.Point( + childScreenCoord.X - parentScreenCoord.X, + childScreenCoord.Y - parentScreenCoord.Y); + + return offset; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/KnownFolders/KnownFoldersCOMGuids.cs b/VG Music Studio - WinForms/API/Shell/Interop/KnownFolders/KnownFoldersCOMGuids.cs new file mode 100644 index 0000000..261f8ce --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/KnownFolders/KnownFoldersCOMGuids.cs @@ -0,0 +1,26 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + internal static class KnownFoldersCLSIDGuid + { + // CLSID GUID strings for relevant coclasses. + internal const string KnownFolderManager = "4df0c730-df9d-4ae3-9153-aa6b82e9795a"; + } + + internal static class KnownFoldersIIDGuid + { + // IID GUID strings for relevant Shell COM interfaces. + internal const string IKnownFolder = "3AA7AF7E-9B36-420c-A8E3-F77D4674A488"; + + internal const string IKnownFolderManager = "8BE2D872-86AA-4d47-B776-32CCA40C7018"; + } + + internal static class KnownFoldersKFIDGuid + { + internal const string ComputerFolder = "0AC0837C-BBF8-452A-850D-79D08E667CA7"; + internal const string Documents = "FDD39AD0-238F-46AF-ADB4-6C85480369C7"; + internal const string Favorites = "1777F761-68AD-4D8A-87BD-30B759FA33DD"; + internal const string Profile = "5E6C858F-0E22-4760-9AFE-EA3317B67173"; + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/KnownFolders/KnownFoldersCOMInterfaces.cs b/VG Music Studio - WinForms/API/Shell/Interop/KnownFolders/KnownFoldersCOMInterfaces.cs new file mode 100644 index 0000000..4de6b8a --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/KnownFolders/KnownFoldersCOMInterfaces.cs @@ -0,0 +1,182 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + // Disable warning if a method declaration hides another inherited from a parent COM interface To successfully import a COM interface, + // all inherited methods need to be declared again with the exception of those already declared in "IUnknown" +#pragma warning disable 0108 + + [ComImport, + Guid(KnownFoldersIIDGuid.IKnownFolderManager), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IKnownFolderManager + { + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + void FolderIdFromCsidl(int csidl, + [Out] out Guid knownFolderID); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + void FolderIdToCsidl([In, MarshalAs(UnmanagedType.LPStruct)] Guid id, + [Out] out int csidl); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + void GetFolderIds([Out] out IntPtr folders, + [Out] out uint count); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + HResult GetFolder([In, MarshalAs(UnmanagedType.LPStruct)] Guid id, + [Out, MarshalAs(UnmanagedType.Interface)] out IKnownFolderNative knownFolder); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + void GetFolderByName(string canonicalName, + [Out, MarshalAs(UnmanagedType.Interface)] out IKnownFolderNative knownFolder); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + void RegisterFolder( + [In, MarshalAs(UnmanagedType.LPStruct)] Guid knownFolderGuid, + [In] ref KnownFoldersSafeNativeMethods.NativeFolderDefinition knownFolderDefinition); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + void UnregisterFolder( + [In, MarshalAs(UnmanagedType.LPStruct)] Guid knownFolderGuid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void FindFolderFromPath( + [In, MarshalAs(UnmanagedType.LPWStr)] string path, + [In] int mode, + [Out, MarshalAs(UnmanagedType.Interface)] out IKnownFolderNative knownFolder); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + HResult FindFolderFromIDList(IntPtr pidl, [Out, MarshalAs(UnmanagedType.Interface)] out IKnownFolderNative knownFolder); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + void Redirect(); + } + + [ComImport, + Guid(KnownFoldersIIDGuid.IKnownFolder), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IKnownFolderNative + { + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + Guid GetId(); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + FolderCategory GetCategory(); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + [PreserveSig] + HResult GetShellItem([In] int i, + ref Guid interfaceGuid, + [Out, MarshalAs(UnmanagedType.Interface)] out IShellItem2 shellItem); + + [return: MarshalAs(UnmanagedType.LPWStr)] + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + string GetPath([In] int option); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + void SetPath([In] int i, [In] string path); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + void GetIDList([In] int i, + [Out] out IntPtr itemIdentifierListPointer); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + Guid GetFolderType(); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + RedirectionCapability GetRedirectionCapabilities(); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + void GetFolderDefinition( + [Out, MarshalAs(UnmanagedType.Struct)] out KnownFoldersSafeNativeMethods.NativeFolderDefinition definition); + } + + [ComImport] + [Guid("4df0c730-df9d-4ae3-9153-aa6b82e9795a")] + internal class KnownFolderManagerClass : IKnownFolderManager + { + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + public extern virtual void FolderIdFromCsidl(int csidl, + [Out] out Guid knownFolderID); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + public extern virtual void FolderIdToCsidl( + [In, MarshalAs(UnmanagedType.LPStruct)] Guid id, + [Out] out int csidl); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + public extern virtual void GetFolderIds( + [Out] out IntPtr folders, + [Out] out uint count); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + public extern virtual HResult GetFolder( + [In, MarshalAs(UnmanagedType.LPStruct)] Guid id, + [Out, MarshalAs(UnmanagedType.Interface)] + out IKnownFolderNative knownFolder); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + public extern virtual void GetFolderByName( + string canonicalName, + [Out, MarshalAs(UnmanagedType.Interface)] out IKnownFolderNative knownFolder); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + public extern virtual void RegisterFolder( + [In, MarshalAs(UnmanagedType.LPStruct)] Guid knownFolderGuid, + [In] ref KnownFoldersSafeNativeMethods.NativeFolderDefinition knownFolderDefinition); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + public extern virtual void UnregisterFolder( + [In, MarshalAs(UnmanagedType.LPStruct)] Guid knownFolderGuid); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + public extern virtual void FindFolderFromPath( + [In, MarshalAs(UnmanagedType.LPWStr)] string path, + [In] int mode, + [Out, MarshalAs(UnmanagedType.Interface)] out IKnownFolderNative knownFolder); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + public extern virtual HResult FindFolderFromIDList(IntPtr pidl, [Out, MarshalAs(UnmanagedType.Interface)] out IKnownFolderNative knownFolder); + + [MethodImpl(MethodImplOptions.InternalCall, + MethodCodeType = MethodCodeType.Runtime)] + public extern virtual void Redirect(); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/KnownFolders/KnownFoldersNativeMethods.cs b/VG Music Studio - WinForms/API/Shell/Interop/KnownFolders/KnownFoldersNativeMethods.cs new file mode 100644 index 0000000..659fd6e --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/KnownFolders/KnownFoldersNativeMethods.cs @@ -0,0 +1,34 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// + /// Internal class that contains interop declarations for functions that are considered benign but that are performance critical. + /// + /// Functions that are benign but not performance critical should be located in the NativeMethods class. + [SuppressUnmanagedCodeSecurity] + internal static class KnownFoldersSafeNativeMethods + { + [StructLayout(LayoutKind.Sequential)] + internal struct NativeFolderDefinition + { + internal FolderCategory category; + internal IntPtr name; + internal IntPtr description; + internal Guid parentId; + internal IntPtr relativePath; + internal IntPtr parsingName; + internal IntPtr tooltip; + internal IntPtr localizedName; + internal IntPtr icon; + internal IntPtr security; + internal uint attributes; + internal DefinitionOptions definitionOptions; + internal Guid folderTypeId; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/PropertySystem/PropertySystemCOMInterfaces.cs b/VG Music Studio - WinForms/API/Shell/Interop/PropertySystem/PropertySystemCOMInterfaces.cs new file mode 100644 index 0000000..f248235 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/PropertySystem/PropertySystemCOMInterfaces.cs @@ -0,0 +1,347 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + // Disable warning if a method declaration hides another inherited from a parent COM interface To successfully import a COM interface, + // all inherited methods need to be declared again with the exception of those already declared in "IUnknown" +#pragma warning disable 108 + + [ComImport, + Guid(ShellIIDGuid.IPropertyDescription), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IPropertyDescription + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPropertyKey(out PropertyKey pkey); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetCanonicalName([MarshalAs(UnmanagedType.LPWStr)] out string ppszName); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetPropertyType(out VarEnum pvartype); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), + PreserveSig] + HResult GetDisplayName(out IntPtr ppszName); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetEditInvitation(out IntPtr ppszInvite); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetTypeFlags([In] PropertyTypeOptions mask, out PropertyTypeOptions ppdtFlags); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetViewFlags(out PropertyViewOptions ppdvFlags); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetDefaultColumnWidth(out uint pcxChars); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetDisplayType(out PropertyDisplayType pdisplaytype); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetColumnState(out PropertyColumnStateOptions pcsFlags); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetGroupingRange(out PropertyGroupingRange pgr); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetRelativeDescriptionType(out PropertySystemNativeMethods.RelativeDescriptionType prdt); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetRelativeDescription([In] PropVariant propvar1, [In] PropVariant propvar2, [MarshalAs(UnmanagedType.LPWStr)] out string ppszDesc1, [MarshalAs(UnmanagedType.LPWStr)] out string ppszDesc2); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetSortDescription(out PropertySortDescription psd); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetSortDescriptionLabel([In] bool fDescending, out IntPtr ppszDescription); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetAggregationType(out PropertyAggregationType paggtype); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetConditionType(out PropertyConditionType pcontype, out PropertyConditionOperation popDefault); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetEnumTypeList([In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IPropertyEnumTypeList ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void CoerceToCanonicalValue([In, Out] PropVariant propvar); + + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] // Note: this method signature may be wrong, but it is not used. + HResult FormatForDisplay([In] PropVariant propvar, [In] ref PropertyDescriptionFormatOptions pdfFlags, [MarshalAs(UnmanagedType.LPWStr)] out string ppszDisplay); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult IsValueCanonical([In] PropVariant propvar); + } + + [ComImport, + Guid(ShellIIDGuid.IPropertyDescription2), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IPropertyDescription2 : IPropertyDescription + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPropertyKey(out PropertyKey pkey); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetCanonicalName([MarshalAs(UnmanagedType.LPWStr)] out string ppszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPropertyType(out VarEnum pvartype); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string ppszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetEditInvitation([MarshalAs(UnmanagedType.LPWStr)] out string ppszInvite); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetTypeFlags([In] PropertyTypeOptions mask, out PropertyTypeOptions ppdtFlags); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetViewFlags(out PropertyViewOptions ppdvFlags); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDefaultColumnWidth(out uint pcxChars); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDisplayType(out PropertyDisplayType pdisplaytype); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetColumnState(out uint pcsFlags); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetGroupingRange(out PropertyGroupingRange pgr); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetRelativeDescriptionType(out PropertySystemNativeMethods.RelativeDescriptionType prdt); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetRelativeDescription( + [In] PropVariant propvar1, + [In] PropVariant propvar2, + [MarshalAs(UnmanagedType.LPWStr)] out string ppszDesc1, + [MarshalAs(UnmanagedType.LPWStr)] out string ppszDesc2); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetSortDescription(out PropertySortDescription psd); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetSortDescriptionLabel([In] int fDescending, [MarshalAs(UnmanagedType.LPWStr)] out string ppszDescription); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAggregationType(out PropertyAggregationType paggtype); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetConditionType( + out PropertyConditionType pcontype, + out PropertyConditionOperation popDefault); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetEnumTypeList([In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void CoerceToCanonicalValue([In, Out] PropVariant ppropvar); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void FormatForDisplay([In] PropVariant propvar, [In] ref PropertyDescriptionFormatOptions pdfFlags, [MarshalAs(UnmanagedType.LPWStr)] out string ppszDisplay); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult IsValueCanonical([In] PropVariant propvar); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetImageReferenceForValue( + [In] PropVariant propvar, + [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszImageRes); + } + + [ComImport, + Guid(ShellIIDGuid.IPropertyDescriptionList), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IPropertyDescriptionList + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetCount(out uint pcElem); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAt([In] uint iElem, [In] ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IPropertyDescription ppv); + } + + [ComImport, + Guid(ShellIIDGuid.IPropertyEnumType), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IPropertyEnumType + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetEnumType([Out] out PropEnumType penumtype); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetValue([Out] PropVariant ppropvar); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetRangeMinValue([Out] PropVariant ppropvar); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetRangeSetValue([Out] PropVariant ppropvar); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDisplayText([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszDisplay); + } + + [ComImport, + Guid(ShellIIDGuid.IPropertyEnumType2), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IPropertyEnumType2 : IPropertyEnumType + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetEnumType([Out] out PropEnumType penumtype); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetValue([Out] PropVariant ppropvar); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetRangeMinValue([Out] PropVariant ppropvar); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetRangeSetValue([Out] PropVariant ppropvar); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDisplayText([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszDisplay); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetImageReference([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszImageRes); + } + + [ComImport, + Guid(ShellIIDGuid.IPropertyEnumTypeList), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IPropertyEnumTypeList + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetCount([Out] out uint pctypes); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAt( + [In] uint itype, + [In] ref Guid riid, // riid may be IID_IPropertyEnumType + [Out, MarshalAs(UnmanagedType.Interface)] out IPropertyEnumType ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetConditionAt( + [In] uint index, + [In] ref Guid riid, + out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void FindMatchingIndex( + [In] PropVariant propvarCmp, + [Out] out uint pnIndex); + } + + /// A property store + [ComImport] + [Guid(ShellIIDGuid.IPropertyStore)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IPropertyStore + { + /// Gets the number of properties contained in the property store. + /// + /// + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetCount([Out] out uint propertyCount); + + /// Get a property key located at a specific index. + /// + /// + /// + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetAt([In] uint propertyIndex, out PropertyKey key); + + /// Gets the value of a property from the store + /// + /// + /// + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetValue([In] ref PropertyKey key, [Out] PropVariant pv); + + /// Sets the value of a property in the store + /// + /// + /// + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig] + HResult SetValue([In] ref PropertyKey key, [In] PropVariant pv); + + /// Commits the changes. + /// + [PreserveSig] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult Commit(); + } + + /// An in-memory property store cache + [ComImport] + [Guid(ShellIIDGuid.IPropertyStoreCache)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IPropertyStoreCache + { + /// Gets the state of a property stored in the cache + /// + /// + /// + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetState(ref PropertyKey key, [Out] out PropertyStoreCacheState state); + + /// Gets the valeu and state of a property in the cache + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult GetValueAndState(ref PropertyKey propKey, [Out] PropVariant pv, [Out] out PropertyStoreCacheState state); + + /// Sets the state of a property in the cache. + /// + /// + /// + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult SetState(ref PropertyKey propKey, PropertyStoreCacheState state); + + /// Sets the value and state in the cache. + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + HResult SetValueAndState(ref PropertyKey propKey, [In] PropVariant pv, PropertyStoreCacheState state); + } + + [ComImport] + [Guid(ShellIIDGuid.IPropertyStoreCapabilities)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IPropertyStoreCapabilities + { + HResult IsPropertyWritable([In]ref PropertyKey propertyKey); + } + +#pragma warning restore 108 +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/PropertySystem/PropertySystemNativeMethods.cs b/VG Music Studio - WinForms/API/Shell/Interop/PropertySystem/PropertySystemNativeMethods.cs new file mode 100644 index 0000000..afcf164 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/PropertySystem/PropertySystemNativeMethods.cs @@ -0,0 +1,52 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + internal static class PropertySystemNativeMethods + { + internal enum RelativeDescriptionType + { + General, + Date, + Size, + Count, + Revision, + Length, + Duration, + Speed, + Rate, + Rating, + Priority + } + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int PSGetNameFromPropertyKey( + ref PropertyKey propkey, + [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszCanonicalName + ); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern HResult PSGetPropertyDescription( + ref PropertyKey propkey, + ref Guid riid, + [Out, MarshalAs(UnmanagedType.Interface)] out IPropertyDescription ppv + ); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int PSGetPropertyDescriptionListFromString( + [In, MarshalAs(UnmanagedType.LPWStr)] string pszPropList, + [In] ref Guid riid, + out IPropertyDescriptionList ppv + ); + + [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int PSGetPropertyKeyFromName( + [In, MarshalAs(UnmanagedType.LPWStr)] string pszCanonicalName, + out PropertyKey propkey + ); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/ShellExtensions/HandlerNativeMethods.cs b/VG Music Studio - WinForms/API/Shell/Interop/ShellExtensions/HandlerNativeMethods.cs new file mode 100644 index 0000000..76cc918 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/ShellExtensions/HandlerNativeMethods.cs @@ -0,0 +1,191 @@ +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.Interop +{ + /// Provides means by which to intiailze with a file. + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("b7d14566-0509-4cce-a71f-0a554233bd9b")] + public interface IInitializeWithFile + { + /// Initializes with a file. + /// + /// + void Initialize([MarshalAs(UnmanagedType.LPWStr)] string filePath, AccessModes fileMode); + } + + /// Provides means by which to initialize with a ShellObject + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("7f73be3f-fb79-493c-a6c7-7ee14e245841")] + public interface IInitializeWithItem + { + /// Initializes with ShellItem + /// + /// + void Initialize([In, MarshalAs(UnmanagedType.IUnknown)] object shellItem, AccessModes accessMode); + } + + /// Provides means by which to initialize with a stream. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + [ComVisible(true)] + [Guid("b824b49d-22ac-4161-ac8a-9916e8fa3f7f")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IInitializeWithStream + { + /// Initializes with a stream. + /// + /// + void Initialize(IStream stream, AccessModes fileMode); + } + + /// ComVisible interface for native IThumbnailProvider + [ComVisible(true)] + [Guid("e357fccd-a995-4576-b01f-234630154e96")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IThumbnailProvider + { + /// Gets a pointer to a bitmap to display as a thumbnail + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")] + void GetThumbnail(uint squareLength, out IntPtr bitmapHandle, out uint bitmapType); + } + + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("fc4801a3-2ba9-11cf-a229-00aa003d7352")] + internal interface IObjectWithSite + { + void SetSite([In, MarshalAs(UnmanagedType.IUnknown)] object pUnkSite); + + void GetSite(ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvSite); + } + + [ComImport] + [Guid("00000114-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IOleWindow + { + void GetWindow(out IntPtr phwnd); + + void ContextSensitiveHelp([MarshalAs(UnmanagedType.Bool)] bool fEnterMode); + } + + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("8895b1c6-b41f-4c1c-a562-0d564250836f")] + internal interface IPreviewHandler + { + void SetWindow(IntPtr hwnd, ref RECT rect); + + void SetRect(ref RECT rect); + + void DoPreview(); + + void Unload(); + + void SetFocus(); + + void QueryFocus(out IntPtr phwnd); + + [PreserveSig] + HResult TranslateAccelerator(ref MSG pmsg); + } + + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("fec87aaf-35f9-447a-adb7-20234491401a")] + internal interface IPreviewHandlerFrame + { + void GetWindowContext(IntPtr pinfo); + + [PreserveSig] + HResult TranslateAccelerator(ref MSG pmsg); + }; + + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("8327b13c-b63f-4b24-9b8a-d010dcc3f599")] + internal interface IPreviewHandlerVisuals + { + void SetBackgroundColor(COLORREF color); + + void SetFont(ref LogFont plf); + + void SetTextColor(COLORREF color); + } + + [StructLayout(LayoutKind.Sequential)] + internal struct COLORREF + { + public uint Dword; + + public Color Color => Color.FromArgb( + (int)(0x000000FFU & Dword), + (int)(0x0000FF00U & Dword) >> 8, + (int)(0x00FF0000U & Dword) >> 16); + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MSG + { + public IntPtr hwnd; + public int message; + public IntPtr wParam; + public IntPtr lParam; + public int time; + public int pt_x; + public int pt_y; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct RECT + { + public readonly int left; + public readonly int top; + public readonly int right; + public readonly int bottom; + + public Rectangle ToRectangle() => Rectangle.FromLTRB(left, top, right, bottom); + } + + internal static class HandlerNativeMethods + { + internal static readonly Guid PreviewHandlerGuid = new Guid("{8895b1c6-b41f-4c1c-a562-0d564250836f}"); + + internal static readonly Guid ThumbnailProviderGuid = new Guid("{e357fccd-a995-4576-b01f-234630154e96}"); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + internal static extern IntPtr GetFocus(); + + [DllImport("user32.dll")] + internal static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal class LogFont + { + internal int height; + internal int width; + internal int escapement; + internal int orientation; + internal int weight; + internal byte italic; + internal byte underline; + internal byte strikeOut; + internal byte charSet; + internal byte outPrecision; + internal byte clipPrecision; + internal byte quality; + internal byte pitchAndFamily; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + internal string lfFaceName = string.Empty; + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/ShellObjectWatcher/ShellObjectWatcherNativeMethods.cs b/VG Music Studio - WinForms/API/Shell/Interop/ShellObjectWatcher/ShellObjectWatcherNativeMethods.cs new file mode 100644 index 0000000..1585797 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/ShellObjectWatcher/ShellObjectWatcherNativeMethods.cs @@ -0,0 +1,161 @@ +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.Interop +{ + /// Wraps the native Windows MSG structure. + [StructLayout(LayoutKind.Sequential)] + public struct Message + { + private readonly IntPtr windowHandle; + private readonly uint msg; + private readonly IntPtr wparam; + private readonly IntPtr lparam; + private readonly int time; + private NativePoint point; + + /// Creates a new instance of the Message struct + /// Window handle + /// Message + /// WParam + /// LParam + /// Time + /// Point + internal Message(IntPtr windowHandle, uint msg, IntPtr wparam, IntPtr lparam, int time, NativePoint point) + : this() + { + this.windowHandle = windowHandle; + this.msg = msg; + this.wparam = wparam; + this.lparam = lparam; + this.time = time; + this.point = point; + } + + /// Gets the LParam + public IntPtr LParam => lparam; + + /// Gets the window message + public uint Msg => msg; + + /// Gets the point + public NativePoint Point => point; + + /// Gets the time + public int Time => time; + + /// Gets the window handle + public IntPtr WindowHandle => windowHandle; + + /// Gets the WParam + public IntPtr WParam => wparam; + + /// Determines if two messages are not equal. + /// First message + /// Second message + /// True if first and second message are not equal; false otherwise. + public static bool operator !=(Message first, Message second) => !(first == second); + + /// Determines if two messages are equal. + /// First message + /// Second message + /// True if first and second message are equal; false otherwise. + public static bool operator ==(Message first, Message second) => first.WindowHandle == second.WindowHandle + && first.Msg == second.Msg + && first.WParam == second.WParam + && first.LParam == second.LParam + && first.Time == second.Time + && first.Point == second.Point; + + /// Determines if this message is equal to another. + /// Another message + /// True if this message is equal argument; false otherwise. + public override bool Equals(object obj) => (obj != null && obj is Message) ? this == (Message)obj : false; + + /// Gets a hash code for the message. + /// Hash code for this message. + public override int GetHashCode() + { + var hash = WindowHandle.GetHashCode(); + hash = hash * 31 + Msg.GetHashCode(); + hash = hash * 31 + WParam.GetHashCode(); + hash = hash * 31 + LParam.GetHashCode(); + hash = hash * 31 + Time.GetHashCode(); + hash = hash * 31 + Point.GetHashCode(); + return hash; + } + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct WindowClassEx + { + internal uint Size; + internal uint Style; + + internal ShellObjectWatcherNativeMethods.WndProcDelegate WndProc; + + internal int ExtraClassBytes; + internal int ExtraWindowBytes; + internal IntPtr InstanceHandle; + internal IntPtr IconHandle; + internal IntPtr CursorHandle; + internal IntPtr BackgroundBrushHandle; + + internal string MenuName; + internal string ClassName; + + internal IntPtr SmallIconHandle; + } + + internal static class ShellObjectWatcherNativeMethods + { + public delegate int WndProcDelegate(IntPtr hwnd, uint msg, IntPtr wparam, IntPtr lparam); + + [DllImport("Ole32.dll")] + public static extern HResult CreateBindCtx( + int reserved, // must be 0 + [Out] out IBindCtx bindCtx); + + [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr CreateWindowEx( + int extendedStyle, + [MarshalAs(UnmanagedType.LPWStr)] + string className, //string className, //optional + [MarshalAs(UnmanagedType.LPWStr)] + string windowName, //window name + int style, + int x, + int y, + int width, + int height, + IntPtr parentHandle, + IntPtr menuHandle, + IntPtr instanceHandle, + IntPtr additionalData); + + [DllImport("User32.dll")] + public static extern int DefWindowProc( + IntPtr hwnd, + uint msg, + IntPtr wparam, + IntPtr lparam); + + [DllImport("User32.dll")] + public static extern void DispatchMessage([In] ref Message message); + + [DllImport("User32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetMessage( + [Out] out Message message, + IntPtr windowHandle, + uint filterMinMessage, + uint filterMaxMessage); + + [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern uint RegisterClassEx( + ref WindowClassEx windowClass + ); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/Taskbar/TabbedThumbnailNativeMethods.cs b/VG Music Studio - WinForms/API/Shell/Interop/Taskbar/TabbedThumbnailNativeMethods.cs new file mode 100644 index 0000000..88b0824 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/Taskbar/TabbedThumbnailNativeMethods.cs @@ -0,0 +1,222 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +/* Unmerged change from project 'Shell (net452)' +Before: +using System; +using System.Runtime.InteropServices; +After: +using Microsoft.WindowsAPICodePack.Shell; +using Microsoft.WindowsAPICodePack.Shell.Interop; +*/ + +/* Unmerged change from project 'Shell (net462)' +Before: +using System; +using System.Runtime.InteropServices; +After: +using Microsoft.WindowsAPICodePack.Shell; +using Microsoft.WindowsAPICodePack.Shell.Interop; +*/ + +/* Unmerged change from project 'Shell (net472)' +Before: +using System; +using System.Runtime.InteropServices; +After: +using Microsoft.WindowsAPICodePack.Shell; +using Microsoft.WindowsAPICodePack.Shell.Interop; +*/ + +using Kermalis.VGMusicStudio.WinForms.API.Shell; +using System; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + internal static class TabbedThumbnailNativeMethods + { + internal const int DisplayFrame = 0x00000001; + + internal const int ForceIconicRepresentation = 7; + internal const int HasIconicBitmap = 10; + + internal const uint MsgfltAdd = 1; + internal const uint MsgfltRemove = 2; + internal const int ScClose = 0xF060; + internal const int ScMaximize = 0xF030; + internal const int ScMinimize = 0xF020; + internal const uint WaActive = 1; + internal const uint WaClickActive = 2; + internal const uint WmDwmSendIconicLivePreviewBitmap = 0x0326; + internal const uint WmDwmSendIconicThumbnail = 0x0323; + + [DllImport("user32.dll", SetLastError = true)] + internal static extern IntPtr ChangeWindowMessageFilter(uint message, uint dwFlag); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ClientToScreen( + IntPtr hwnd, + ref NativePoint point); + + [DllImport("dwmapi.dll")] + internal static extern int DwmInvalidateIconicBitmaps(IntPtr hwnd); + + [DllImport("dwmapi.dll")] + internal static extern int DwmSetIconicLivePreviewBitmap( + IntPtr hwnd, + IntPtr hbitmap, + ref NativePoint ptClient, + uint flags); + + [DllImport("dwmapi.dll")] + internal static extern int DwmSetIconicLivePreviewBitmap( + IntPtr hwnd, IntPtr hbitmap, IntPtr ptClient, uint flags); + + [DllImport("dwmapi.dll")] + internal static extern int DwmSetIconicThumbnail( + IntPtr hwnd, IntPtr hbitmap, uint flags); + + [DllImport("dwmapi.dll", PreserveSig = true)] + internal static extern int DwmSetWindowAttribute( + IntPtr hwnd, + //DWMWA_* values. + uint dwAttributeToSet, + IntPtr pvAttributeValue, + uint cbAttribute); + + /// + /// Call this method to either enable custom previews on the taskbar (second argument as true) or to disable (second argument as + /// false). If called with True, the method will call DwmSetWindowAttribute for the specific window handle and let DWM know that we + /// will be providing a custom bitmap for the thumbnail as well as Aero peek. + /// + /// + /// + internal static void EnableCustomWindowPreview(IntPtr hwnd, bool enable) + { + var t = Marshal.AllocHGlobal(4); + Marshal.WriteInt32(t, enable ? 1 : 0); + + try + { + var rc = DwmSetWindowAttribute(hwnd, HasIconicBitmap, t, 4); + if (rc != 0) + { + throw Marshal.GetExceptionForHR(rc); + } + + rc = DwmSetWindowAttribute(hwnd, ForceIconicRepresentation, t, 4); + if (rc != 0) + { + throw Marshal.GetExceptionForHR(rc); + } + } + finally + { + Marshal.FreeHGlobal(t); + } + } + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool GetClientRect(IntPtr hwnd, ref NativeRect rect); + + internal static bool GetClientSize(IntPtr hwnd, out System.Drawing.Size size) + { + var rect = new NativeRect(); + if (!GetClientRect(hwnd, ref rect)) + { + size = new System.Drawing.Size(-1, -1); + return false; + } + size = new System.Drawing.Size(rect.Right, rect.Bottom); + return true; + } + + [DllImport("user32.dll")] + internal static extern IntPtr GetWindowDC(IntPtr hwnd); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool GetWindowRect(IntPtr hwnd, ref NativeRect rect); + + [DllImport("user32.dll")] + internal static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc); + + /// Sets the specified iconic thumbnail for the specified window. This is typically done in response to a DWM message. + /// The window handle. + /// The thumbnail bitmap. + internal static void SetIconicThumbnail(IntPtr hwnd, IntPtr hBitmap) + { + var rc = DwmSetIconicThumbnail( + hwnd, + hBitmap, + DisplayFrame); + if (rc != 0) + { + throw Marshal.GetExceptionForHR(rc); + } + } + + /// + /// Sets the specified peek (live preview) bitmap for the specified window. This is typically done in response to a DWM message. + /// + /// The window handle. + /// The thumbnail bitmap. + /// Whether to display a standard window frame around the bitmap. + internal static void SetPeekBitmap(IntPtr hwnd, IntPtr bitmap, bool displayFrame) + { + var rc = DwmSetIconicLivePreviewBitmap( + hwnd, + bitmap, + IntPtr.Zero, + displayFrame ? DisplayFrame : (uint)0); + if (rc != 0) + { + throw Marshal.GetExceptionForHR(rc); + } + } + + /// + /// Sets the specified peek (live preview) bitmap for the specified window. This is typically done in response to a DWM message. + /// + /// The window handle. + /// The thumbnail bitmap. + /// + /// The client area offset at which to display the specified bitmap. The rest of the parent window will be displayed as "remembered" + /// by the DWM. + /// + /// Whether to display a standard window frame around the bitmap. + internal static void SetPeekBitmap(IntPtr hwnd, IntPtr bitmap, System.Drawing.Point offset, bool displayFrame) + { + var nativePoint = new NativePoint(offset.X, offset.Y); + var rc = DwmSetIconicLivePreviewBitmap( + hwnd, + bitmap, + ref nativePoint, + displayFrame ? DisplayFrame : (uint)0); + + if (rc != 0) + { + var e = Marshal.GetExceptionForHR(rc); + + if (e is ArgumentException) + { + // Ignore argument exception as it's not really recommended to be throwing exception when rendering the peek bitmap. If + // it's some other kind of exception, then throw it. + } + else + { + throw e; + } + } + } + + [DllImport("gdi32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool StretchBlt( + IntPtr hDestDC, int destX, int destY, int destWidth, int destHeight, + IntPtr hSrcDC, int srcX, int srcY, int srcWidth, int srcHeight, + uint operation); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/Taskbar/TaskbarCOMInterfaces.cs b/VG Music Studio - WinForms/API/Shell/Interop/Taskbar/TaskbarCOMInterfaces.cs new file mode 100644 index 0000000..141c09e --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/Taskbar/TaskbarCOMInterfaces.cs @@ -0,0 +1,182 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + [ComImportAttribute()] + [GuidAttribute("6332DEBF-87B5-4670-90C0-5E57B408A49E")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface ICustomDestinationList + { + void SetAppID( + [MarshalAs(UnmanagedType.LPWStr)] string pszAppID); + + [PreserveSig] + HResult BeginList( + out uint cMaxSlots, + ref Guid riid, + [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + + [PreserveSig] + HResult AppendCategory( + [MarshalAs(UnmanagedType.LPWStr)] string pszCategory, + [MarshalAs(UnmanagedType.Interface)] IObjectArray poa); + + void AppendKnownCategory( + [MarshalAs(UnmanagedType.I4)] KnownDestinationCategory category); + + [PreserveSig] + HResult AddUserTasks( + [MarshalAs(UnmanagedType.Interface)] IObjectArray poa); + + void CommitList(); + + void GetRemovedDestinations( + ref Guid riid, + [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + + void DeleteList( + [MarshalAs(UnmanagedType.LPWStr)] string pszAppID); + + void AbortList(); + } + + [ComImportAttribute()] + [GuidAttribute("92CA9DCD-5622-4BBA-A805-5E9F541BD8C9")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IObjectArray + { + void GetCount(out uint cObjects); + + void GetAt( + uint iIndex, + ref Guid riid, + [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + } + + [ComImportAttribute()] + [GuidAttribute("5632B1A4-E38A-400A-928A-D4CD63230295")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IObjectCollection + { + // IObjectArray + [PreserveSig] + void GetCount(out uint cObjects); + + [PreserveSig] + void GetAt( + uint iIndex, + ref Guid riid, + [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + + // IObjectCollection + void AddObject( + [MarshalAs(UnmanagedType.Interface)] object pvObject); + + void AddFromArray( + [MarshalAs(UnmanagedType.Interface)] IObjectArray poaSource); + + void RemoveObject(uint uiIndex); + + void Clear(); + } + + [ComImportAttribute()] + [GuidAttribute("c43dc798-95d1-4bea-9030-bb99e2983a1a")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface ITaskbarList4 + { + // ITaskbarList + [PreserveSig] + void HrInit(); + + [PreserveSig] + void AddTab(IntPtr hwnd); + + [PreserveSig] + void DeleteTab(IntPtr hwnd); + + [PreserveSig] + void ActivateTab(IntPtr hwnd); + + [PreserveSig] + void SetActiveAlt(IntPtr hwnd); + + // ITaskbarList2 + [PreserveSig] + void MarkFullscreenWindow( + IntPtr hwnd, + [MarshalAs(UnmanagedType.Bool)] bool fFullscreen); + + // ITaskbarList3 + [PreserveSig] + void SetProgressValue(IntPtr hwnd, ulong ullCompleted, ulong ullTotal); + + [PreserveSig] + void SetProgressState(IntPtr hwnd, TaskbarProgressBarStatus tbpFlags); + + [PreserveSig] + void RegisterTab(IntPtr hwndTab, IntPtr hwndMDI); + + [PreserveSig] + void UnregisterTab(IntPtr hwndTab); + + [PreserveSig] + void SetTabOrder(IntPtr hwndTab, IntPtr hwndInsertBefore); + + [PreserveSig] + void SetTabActive(IntPtr hwndTab, IntPtr hwndInsertBefore, uint dwReserved); + + [PreserveSig] + HResult ThumbBarAddButtons( + IntPtr hwnd, + uint cButtons, + [MarshalAs(UnmanagedType.LPArray)] ThumbButton[] pButtons); + + [PreserveSig] + HResult ThumbBarUpdateButtons( + IntPtr hwnd, + uint cButtons, + [MarshalAs(UnmanagedType.LPArray)] ThumbButton[] pButtons); + + [PreserveSig] + void ThumbBarSetImageList(IntPtr hwnd, IntPtr himl); + + [PreserveSig] + void SetOverlayIcon( + IntPtr hwnd, + IntPtr hIcon, + [MarshalAs(UnmanagedType.LPWStr)] string pszDescription); + + [PreserveSig] + void SetThumbnailTooltip( + IntPtr hwnd, + [MarshalAs(UnmanagedType.LPWStr)] string pszTip); + + [PreserveSig] + void SetThumbnailClip( + IntPtr hwnd, + IntPtr prcClip); + + // ITaskbarList4 + void SetTabProperties(IntPtr hwndTab, SetTabPropertiesOption stpFlags); + } + + [GuidAttribute("77F10CF0-3DB5-4966-B520-B7C54FD35ED6")] + [ClassInterfaceAttribute(ClassInterfaceType.None)] + [ComImportAttribute()] + internal class CDestinationList { } + + [GuidAttribute("2D3468C1-36A7-43B6-AC24-D3F02FD9607A")] + [ClassInterfaceAttribute(ClassInterfaceType.None)] + [ComImportAttribute()] + internal class CEnumerableObjectCollection { } + + [GuidAttribute("56FDF344-FD6D-11d0-958A-006097C9A090")] + [ClassInterfaceAttribute(ClassInterfaceType.None)] + [ComImportAttribute()] + internal class CTaskbarList { } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Interop/Taskbar/TaskbarNativeMethods.cs b/VG Music Studio - WinForms/API/Shell/Interop/Taskbar/TaskbarNativeMethods.cs new file mode 100644 index 0000000..592d346 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Interop/Taskbar/TaskbarNativeMethods.cs @@ -0,0 +1,183 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell; +using Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// Thumbnail Alpha Types + public enum ThumbnailAlphaType + { + /// Let the system decide. + Unknown = 0, + + /// No transparency + NoAlphaChannel = 1, + + /// Has transparency + HasAlphaChannel = 2, + } + + internal enum KnownDestinationCategory + { + Frequent = 1, + Recent + } + + internal enum SetTabPropertiesOption + { + None = 0x0, + UseAppThumbnailAlways = 0x1, + UseAppThumbnailWhenActive = 0x2, + UseAppPeekAlways = 0x4, + UseAppPeekWhenActive = 0x8 + } + + internal enum ShellAddToRecentDocs + { + Pidl = 0x1, + PathA = 0x2, + PathW = 0x3, + AppIdInfo = 0x4, // indicates the data type is a pointer to a SHARDAPPIDINFO structure + AppIdInfoIdList = 0x5, // indicates the data type is a pointer to a SHARDAPPIDINFOIDLIST structure + Link = 0x6, // indicates the data type is a pointer to an IShellLink instance + AppIdInfoLink = 0x7, // indicates the data type is a pointer to a SHARDAPPIDINFOLINK structure + } + + internal enum TaskbarActiveTabSetting + { + UseMdiThumbnail = 0x1, + UseMdiLivePreview = 0x2 + } + + internal enum TaskbarProgressBarStatus + { + NoProgress = 0, + Indeterminate = 0x1, + Normal = 0x2, + Error = 0x4, + Paused = 0x8 + } + + internal enum ThumbButtonMask + { + Bitmap = 0x1, + Icon = 0x2, + Tooltip = 0x4, + THB_FLAGS = 0x8 + } + + [Flags] + internal enum ThumbButtonOptions + { + Enabled = 0x00000000, + Disabled = 0x00000001, + DismissOnClick = 0x00000002, + NoBackground = 0x00000004, + Hidden = 0x00000008, + NonInteractive = 0x00000010 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct ThumbButton + { + /// WPARAM value for a THUMBBUTTON being clicked. + internal const int Clicked = 0x1800; + + [MarshalAs(UnmanagedType.U4)] + internal ThumbButtonMask Mask; + + internal uint Id; + internal uint Bitmap; + internal IntPtr Icon; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + internal string Tip; + + [MarshalAs(UnmanagedType.U4)] + internal ThumbButtonOptions Flags; + } + + internal static class TaskbarNativeMethods + { + internal const int WmCommand = 0x0111; + + internal const uint WmDwmSendIconicLivePreviewBitmap = 0x0326; + + internal const uint WmDwmSendIconThumbnail = 0x0323; + + // Register Window Message used by Shell to notify that the corresponding taskbar button has been added to the taskbar. + internal static readonly uint WmTaskbarButtonCreated = RegisterWindowMessage("TaskbarButtonCreated"); + + [DllImport("shell32.dll")] + public static extern int SHGetPropertyStoreForWindow( + IntPtr hwnd, + ref Guid iid /*IID_IPropertyStore*/, + [Out(), MarshalAs(UnmanagedType.Interface)]out IPropertyStore propertyStore); + + [DllImport("shell32.dll")] + internal static extern void GetCurrentProcessExplicitAppUserModelID( + [Out(), MarshalAs(UnmanagedType.LPWStr)] out string AppID); + + internal static IPropertyStore GetWindowPropertyStore(IntPtr hwnd) + { + var guid = new Guid(ShellIIDGuid.IPropertyStore); + var rc = SHGetPropertyStoreForWindow( + hwnd, + ref guid, + out var propStore); + if (rc != 0) + { + throw Marshal.GetExceptionForHR(rc); + } + return propStore; + } + + [DllImport("user32.dll", EntryPoint = "RegisterWindowMessage", SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern uint RegisterWindowMessage([MarshalAs(UnmanagedType.LPWStr)] string lpString); + + [DllImport("shell32.dll")] + internal static extern void SetCurrentProcessExplicitAppUserModelID( + [MarshalAs(UnmanagedType.LPWStr)] string AppID); + + /// Sets the window's application id by its window handle. + /// The window handle. + /// The application id. + internal static void SetWindowAppId(IntPtr hwnd, string appId) => SetWindowProperty(hwnd, SystemProperties.System.AppUserModel.ID, appId); + + internal static void SetWindowProperty(IntPtr hwnd, PropertyKey propkey, string value) + { + // Get the IPropertyStore for the given window handle + var propStore = GetWindowPropertyStore(hwnd); + + // Set the value + using (var pv = new PropVariant(value)) + { + var result = propStore.SetValue(ref propkey, pv); + if (!CoreErrorHelper.Succeeded(result)) + { + throw new ShellException(result); + } + } + + // Dispose the IPropertyStore and PropVariant + Marshal.ReleaseComObject(propStore); + } + + [DllImport("shell32.dll")] + internal static extern void SHAddToRecentDocs( + ShellAddToRecentDocs flags, + [MarshalAs(UnmanagedType.LPWStr)] string path); + + internal static void SHAddToRecentDocs(string path) => SHAddToRecentDocs(ShellAddToRecentDocs.PathW, path); + + internal static class TaskbarGuids + { + internal static Guid IObjectArray = new Guid("92CA9DCD-5622-4BBA-A805-5E9F541BD8C9"); + internal static Guid IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/KnownFolders/DefinitionOptions.cs b/VG Music Studio - WinForms/API/Shell/KnownFolders/DefinitionOptions.cs new file mode 100644 index 0000000..4c772d9 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/KnownFolders/DefinitionOptions.cs @@ -0,0 +1,23 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Specifies behaviors for known folders. + [Flags] + public enum DefinitionOptions + { + /// No behaviors are defined. + None = 0x0, + + /// Prevents a per-user known folder from being redirected to a network location. + LocalRedirectOnly = 0x2, + + /// The known folder can be roamed through PC-to-PC synchronization. + Roamable = 0x4, + + /// Creates the known folder when the user first logs on. + Precreate = 0x8 + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/KnownFolders/FileSystemKnownFolder.cs b/VG Music Studio - WinForms/API/Shell/KnownFolders/FileSystemKnownFolder.cs new file mode 100644 index 0000000..78cffcb --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/KnownFolders/FileSystemKnownFolder.cs @@ -0,0 +1,163 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Represents a registered file system Known Folder + public class FileSystemKnownFolder : ShellFileSystemFolder, IKnownFolder, IDisposable + { + private IKnownFolderNative knownFolderNative; + private KnownFolderSettings knownFolderSettings; + + internal FileSystemKnownFolder(IShellItem2 shellItem) : base(shellItem) + { + } + + internal FileSystemKnownFolder(IKnownFolderNative kf) + { + Debug.Assert(kf != null); + knownFolderNative = kf; + + // Set the native shell item and set it on the base class (ShellObject) + var guid = new Guid(ShellIIDGuid.IShellItem2); + knownFolderNative.GetShellItem(0, ref guid, out nativeShellItem); + } + + /// Gets this known folder's canonical name. + /// A object. + public string CanonicalName => KnownFolderSettings.CanonicalName; + + /// Gets the category designation for this known folder. + /// A value. + public FolderCategory Category => KnownFolderSettings.Category; + + /// Gets an value that describes this known folder's behaviors. + /// A value. + public DefinitionOptions DefinitionOptions => KnownFolderSettings.DefinitionOptions; + + /// Gets this known folder's description. + /// A object. + public string Description => KnownFolderSettings.Description; + + /// Gets this known folder's file attributes, such as "read-only". + /// A value. + public System.IO.FileAttributes FileAttributes => KnownFolderSettings.FileAttributes; + + /// Gets the unique identifier for this known folder. + /// A value. + public Guid FolderId => KnownFolderSettings.FolderId; + + /// Gets a string representation of this known folder's type. + /// A object. + public string FolderType => KnownFolderSettings.FolderType; + + /// Gets the unique identifier for this known folder's type. + /// A value. + public Guid FolderTypeId => KnownFolderSettings.FolderTypeId; + + /// Gets this known folder's localized name. + /// A object. + public string LocalizedName => KnownFolderSettings.LocalizedName; + + /// Gets the resource identifier for this known folder's localized name. + /// A object. + public string LocalizedNameResourceId => KnownFolderSettings.LocalizedNameResourceId; + + /// Gets the unique identifier for this known folder's parent folder. + /// A value. + public Guid ParentId => KnownFolderSettings.ParentId; + + /// Gets this known folder's parsing name. + /// A object. + public override string ParsingName => base.ParsingName; + + /// Gets the path for this known folder. + /// A object. + public override string Path => KnownFolderSettings.Path; + + /// Gets a value that indicates whether this known folder's path exists on the computer. + /// A bool value. + /// + /// If this property value is false, the folder might be a virtual folder ( property will be + /// for virtual folders) + /// + public bool PathExists => KnownFolderSettings.PathExists; + + /// + /// Gets a value that states whether this known folder can have its path set to a new value, including any restrictions on the redirection. + /// + /// A value. + public RedirectionCapability Redirection => KnownFolderSettings.Redirection; + + /// Gets this known folder's relative path. + /// A object. + public string RelativePath => KnownFolderSettings.RelativePath; + + /// Gets this known folder's security attributes. + /// A object. + public string Security => KnownFolderSettings.Security; + + /// Gets this known folder's tool tip text. + /// A object. + public string Tooltip => KnownFolderSettings.Tooltip; + + /// Gets the resource identifier for this known folder's tool tip text. + /// A object. + public string TooltipResourceId => KnownFolderSettings.TooltipResourceId; + + private KnownFolderSettings KnownFolderSettings + { + get + { + if (knownFolderNative == null) + { + // We need to get the PIDL either from the NativeShellItem, or from base class's property (if someone already set it on + // us). Need to use the PIDL to get the native IKnownFolder interface. + + // Get the PIDL for the ShellItem + if (nativeShellItem != null && base.PIDL == IntPtr.Zero) + { + base.PIDL = ShellHelper.PidlFromShellItem(nativeShellItem); + } + + // If we have a valid PIDL, get the native IKnownFolder + if (base.PIDL != IntPtr.Zero) + { + knownFolderNative = KnownFolderHelper.FromPIDL(base.PIDL); + } + + Debug.Assert(knownFolderNative != null); + } + + // If this is the first time this property is being called, get the native Folder Defination (KnownFolder properties) + if (knownFolderSettings == null) + { + knownFolderSettings = new KnownFolderSettings(knownFolderNative); + } + + return knownFolderSettings; + } + } + + /// Release resources + /// Indicates that this mothod is being called from Dispose() rather than the finalizer. + protected override void Dispose(bool disposing) + { + if (disposing) + { + knownFolderSettings = null; + } + + if (knownFolderNative != null) + { + Marshal.ReleaseComObject(knownFolderNative); + knownFolderNative = null; + } + + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/KnownFolders/FolderCategory.cs b/VG Music Studio - WinForms/API/Shell/KnownFolders/FolderCategory.cs new file mode 100644 index 0000000..41202a1 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/KnownFolders/FolderCategory.cs @@ -0,0 +1,36 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Specifies the categories for known folders. + public enum FolderCategory + { + /// The folder category is not specified. + None = 0x00, + + /// + /// The folder is a virtual folder. Virtual folders are not part of the file system. For example, Control Panel and Printers are + /// virtual folders. A number of properties such as folder path and redirection do not apply to this category. + /// + Virtual = 0x1, + + /// + /// The folder is fixed. Fixed file system folders are not managed by the Shell and are usually given a permanent path when the + /// system is installed. For example, the Windows and Program Files folders are fixed folders. A number of properties such as + /// redirection do not apply to this category. + /// + Fixed = 0x2, + + /// + /// The folder is a common folder. Common folders are used for sharing data and settings accessible by all users of a system. For + /// example, all users share a common Documents folder as well as their per-user Documents folder. + /// + Common = 0x3, + + /// + /// Each user has their own copy of the folder. Per-user folders are those stored under each user's profile and accessible only by + /// that user. + /// + PerUser = 0x4 + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/KnownFolders/FolderProperties.cs b/VG Music Studio - WinForms/API/Shell/KnownFolders/FolderProperties.cs new file mode 100644 index 0000000..be73c02 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/KnownFolders/FolderProperties.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Runtime.InteropServices; +using System.Windows.Media.Imaging; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// + /// Structure used internally to store property values for a known folder. This structure holds the information returned in the + /// FOLDER_DEFINITION structure, and resources referenced by fields in NativeFolderDefinition, such as icon and tool tip. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct FolderProperties + { + internal string name; + internal FolderCategory category; + internal string canonicalName; + internal string description; + internal Guid parentId; + internal string parent; + internal string relativePath; + internal string parsingName; + internal string tooltipResourceId; + internal string tooltip; + internal string localizedName; + internal string localizedNameResourceId; + internal string iconResourceId; + internal BitmapSource icon; + internal DefinitionOptions definitionOptions; + internal System.IO.FileAttributes fileAttributes; + internal Guid folderTypeId; + internal string folderType; + internal Guid folderId; + internal string path; + internal bool pathExists; + internal RedirectionCapability redirection; + internal string security; + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/KnownFolders/FolderTypes.cs b/VG Music Studio - WinForms/API/Shell/KnownFolders/FolderTypes.cs new file mode 100644 index 0000000..e15ae47 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/KnownFolders/FolderTypes.cs @@ -0,0 +1,173 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using System; +using System.Collections.Generic; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// The FolderTypes values represent a view template applied to a folder, usually based on its intended use and contents. + internal static class FolderTypes + { + /// + /// Windows 7 and later. A folder that contains communication-related files such as e-mails, calendar information, and contact information. + /// + internal static Guid Communications = new Guid( + 0x91475fe5, 0x586b, 0x4eba, 0x8d, 0x75, 0xd1, 0x74, 0x34, 0xb8, 0xcd, 0xf6); + + /// The folder is a compressed archive, such as a compressed file with a .zip file name extension. + internal static Guid CompressedFolder = new Guid( + 0x80213e82, 0xbcfd, 0x4c4f, 0x88, 0x17, 0xbb, 0x27, 0x60, 0x12, 0x67, 0xa9); + + /// An e-mail-related folder that contains contact information. + internal static Guid Contacts = new Guid( + 0xde2b70ec, 0x9bf7, 0x4a93, 0xbd, 0x3d, 0x24, 0x3f, 0x78, 0x81, 0xd4, 0x92); + + /// The Control Panel in category view. This is a virtual folder. + internal static Guid ControlPanelCategory = new Guid( + 0xde4f0660, 0xfa10, 0x4b8f, 0xa4, 0x94, 0x06, 0x8b, 0x20, 0xb2, 0x23, 0x07); + + /// The Control Panel in classic view. This is a virtual folder. + internal static Guid ControlPanelClassic = new Guid( + 0x0c3794f3, 0xb545, 0x43aa, 0xa3, 0x29, 0xc3, 0x74, 0x30, 0xc5, 0x8d, 0x2a); + + /// The folder contains document files. These can be of mixed format—.doc, .txt, and others. + internal static Guid Documents = new Guid( + 0x7d49d726, 0x3c21, 0x4f05, 0x99, 0xaa, 0xfd, 0xc2, 0xc9, 0x47, 0x46, 0x56); + + /// The folder is the Games folder found in the Start menu. + internal static Guid Games = new Guid( + 0xb689b0d0, 0x76d3, 0x4cbb, 0x87, 0xf7, 0x58, 0x5d, 0x0e, 0x0c, 0xe0, 0x70); + + /// Windows 7 and later. The folder is a library, but of no specified type. + internal static Guid GenericLibrary = new Guid( + 0x5f4eab9a, 0x6833, 0x4f61, 0x89, 0x9d, 0x31, 0xcf, 0x46, 0x97, 0x9d, 0x49); + + /// Windows 7 and later. The folder contains search results, but they are of mixed or no specific type. + internal static Guid GenericSearchResults = new Guid( + 0x7fde1a1e, 0x8b31, 0x49a5, 0x93, 0xb8, 0x6b, 0xe1, 0x4c, 0xfa, 0x49, 0x43); + + /// + /// The folder is invalid. There are several things that can cause this judgement: hard disk errors, file system errors, and + /// compression errors among them. + /// + internal static Guid Invalid = new Guid( + 0x57807898, 0x8c4f, 0x4462, 0xbb, 0x63, 0x71, 0x04, 0x23, 0x80, 0xb1, 0x09); + + /// A default library view without a more specific template. This value is not supported in Windows 7 and later systems. + internal static Guid Library = new Guid( + 0x4badfc68, 0xc4ac, 0x4716, 0xa0, 0xa0, 0x4d, 0x5d, 0xaa, 0x6b, 0x0f, 0x3e); + + /// Windows 7 and later. The folder contains audio files, such as .mp3 and .wma files. + internal static Guid Music = new Guid( + 0xaf9c03d6, 0x7db9, 0x4a15, 0x94, 0x64, 0x13, 0xbf, 0x9f, 0xb6, 0x9a, 0x2a); + + /// A list of music files displayed in Icons view. This value is not supported in Windows 7 and later systems. + internal static Guid MusicIcons = new Guid( + 0x0b7467fb, 0x84ba, 0x4aae, 0xa0, 0x9b, 0x15, 0xb7, 0x10, 0x97, 0xaf, 0x9e); + + /// The Network Explorer folder. + internal static Guid NetworkExplorer = new Guid( + 0x25cc242b, 0x9a7c, 0x4f51, 0x80, 0xe0, 0x7a, 0x29, 0x28, 0xfe, 0xbe, 0x42); + + /// No particular content type has been detected or specified. This value is not supported in Windows 7 and later systems. + internal static Guid NotSpecified = new Guid( + 0x5c4f28b5, 0xf869, 0x4e84, 0x8e, 0x60, 0xf1, 0x1d, 0xb9, 0x7c, 0x5c, 0xc7); + + /// Windows 7 and later. The folder contains federated search OpenSearch results. + internal static Guid OpenSearch = new Guid( + 0x8faf9629, 0x1980, 0x46ff, 0x80, 0x23, 0x9d, 0xce, 0xab, 0x9c, 0x3e, 0xe3); + + /// Windows 7 and later. The homegroup view. + internal static Guid OtherUsers = new Guid( + 0xb337fd00, 0x9dd5, 0x4635, 0xa6, 0xd4, 0xda, 0x33, 0xfd, 0x10, 0x2b, 0x7a); + + /// Image files, such as .jpg, .tif, or .png files. + internal static Guid Pictures = new Guid( + 0xb3690e58, 0xe961, 0x423b, 0xb6, 0x87, 0x38, 0x6e, 0xbf, 0xd8, 0x32, 0x39); + + /// Printers that have been added to the system. This is a virtual folder. + internal static Guid Printers = new Guid( + 0x2c7bbec6, 0xc844, 0x4a0a, 0x91, 0xfa, 0xce, 0xf6, 0xf5, 0x9c, 0xfd, 0xa1); + + /// Windows 7 and later. The folder contains recorded television broadcasts. + internal static Guid RecordedTV = new Guid( + 0x5557a28f, 0x5da6, 0x4f83, 0x88, 0x09, 0xc2, 0xc9, 0x8a, 0x11, 0xa6, 0xfa); + + /// The Recycle Bin. This is a virtual folder. + internal static Guid RecycleBin = new Guid( + 0xd6d9e004, 0xcd87, 0x442b, 0x9d, 0x57, 0x5e, 0x0a, 0xeb, 0x4f, 0x6f, 0x72); + + /// Windows 7 and later. The folder contains saved game states. + internal static Guid SavedGames = new Guid( + 0xd0363307, 0x28cb, 0x4106, 0x9f, 0x23, 0x29, 0x56, 0xe3, 0xe5, 0xe0, 0xe7); + + /// Windows 7 and later. Before you search. + internal static Guid SearchConnector = new Guid( + 0x982725ee, 0x6f47, 0x479e, 0xb4, 0x47, 0x81, 0x2b, 0xfa, 0x7d, 0x2e, 0x8f); + + /// Windows 7 and later. A user's Searches folder, normally found at C:\Users\username\Searches. + internal static Guid Searches = new Guid( + 0x0b0ba2e3, 0x405f, 0x415e, 0xa6, 0xee, 0xca, 0xd6, 0x25, 0x20, 0x78, 0x53); + + /// The software explorer window used by the Add or Remove Programs control panel icon. + internal static Guid SoftwareExplorer = new Guid( + 0xd674391b, 0x52d9, 0x4e07, 0x83, 0x4e, 0x67, 0xc9, 0x86, 0x10, 0xf3, 0x9d); + + /// The folder is the FOLDERID_UsersFiles folder. + internal static Guid UserFiles = new Guid( + 0xcd0fc69b, 0x71e2, 0x46e5, 0x96, 0x90, 0x5b, 0xcd, 0x9f, 0x57, 0xaa, 0xb3); + + /// Windows 7 and later. The view shown when the user clicks the Windows Explorer button on the taskbar. + internal static Guid UsersLibraries = new Guid( + 0xc4d98f09, 0x6124, 0x4fe0, 0x99, 0x42, 0x82, 0x64, 0x16, 0x8, 0x2d, 0xa9); + + /// Windows 7 and later. The folder contains video files. These can be of mixed format—.wmv, .mov, and others. + internal static Guid Videos = new Guid( + 0x5fa96407, 0x7e77, 0x483c, 0xac, 0x93, 0x69, 0x1d, 0x05, 0x85, 0x0d, 0xe8); + + private static readonly Dictionary types; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static FolderTypes() + { + types = new Dictionary + { + // Review: These Localized messages could probably be a reflected value of the field's name. + { NotSpecified, LocalizedMessages.FolderTypeNotSpecified }, + { Invalid, LocalizedMessages.FolderTypeInvalid }, + { Communications, LocalizedMessages.FolderTypeCommunications }, + { CompressedFolder, LocalizedMessages.FolderTypeCompressedFolder }, + { Contacts, LocalizedMessages.FolderTypeContacts }, + { ControlPanelCategory, LocalizedMessages.FolderTypeCategory }, + { ControlPanelClassic, LocalizedMessages.FolderTypeClassic }, + { Documents, LocalizedMessages.FolderTypeDocuments }, + { Games, LocalizedMessages.FolderTypeGames }, + { GenericSearchResults, LocalizedMessages.FolderTypeSearchResults }, + { GenericLibrary, LocalizedMessages.FolderTypeGenericLibrary }, + { Library, LocalizedMessages.FolderTypeLibrary }, + { Music, LocalizedMessages.FolderTypeMusic }, + { MusicIcons, LocalizedMessages.FolderTypeMusicIcons }, + { NetworkExplorer, LocalizedMessages.FolderTypeNetworkExplorer }, + { OtherUsers, LocalizedMessages.FolderTypeOtherUsers }, + { OpenSearch, LocalizedMessages.FolderTypeOpenSearch }, + { Pictures, LocalizedMessages.FolderTypePictures }, + { Printers, LocalizedMessages.FolderTypePrinters }, + { RecycleBin, LocalizedMessages.FolderTypeRecycleBin }, + { RecordedTV, LocalizedMessages.FolderTypeRecordedTV }, + { SoftwareExplorer, LocalizedMessages.FolderTypeSoftwareExplorer }, + { SavedGames, LocalizedMessages.FolderTypeSavedGames }, + { SearchConnector, LocalizedMessages.FolderTypeSearchConnector }, + { Searches, LocalizedMessages.FolderTypeSearches }, + { UsersLibraries, LocalizedMessages.FolderTypeUserLibraries }, + { UserFiles, LocalizedMessages.FolderTypeUserFiles }, + { Videos, LocalizedMessages.FolderTypeVideos } + }; + } + + internal static string GetFolderType(Guid typeId) + { + return types.TryGetValue(typeId, out var type) ? type : string.Empty; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/KnownFolders/FoldersIdentifiers.cs b/VG Music Studio - WinForms/API/Shell/KnownFolders/FoldersIdentifiers.cs new file mode 100644 index 0000000..878074f --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/KnownFolders/FoldersIdentifiers.cs @@ -0,0 +1,370 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Contains the GUID identifiers for well-known folders. + internal static class FolderIdentifiers + { + /// Get Programs + internal static Guid AddNewPrograms = new Guid(0xde61d971, 0x5ebc, 0x4f02, 0xa3, 0xa9, 0x6c, 0x82, 0x89, 0x5e, 0x5c, 0x04); + + /// Administrative Tools + internal static Guid AdminTools = new Guid(0x724EF170, 0xA42D, 0x4FEF, 0x9F, 0x26, 0xB6, 0x0E, 0x84, 0x6F, 0xBA, 0x4F); + + /// Installed Updates + internal static Guid AppUpdates = new Guid(0xa305ce99, 0xf527, 0x492b, 0x8b, 0x1a, 0x7e, 0x76, 0xfa, 0x98, 0xd6, 0xe4); + + /// Temporary Burn Folder + internal static Guid CDBurning = new Guid(0x9E52AB10, 0xF80D, 0x49DF, 0xAC, 0xB8, 0x43, 0x30, 0xF5, 0x68, 0x78, 0x55); + + /// Programs and Features + internal static Guid ChangeRemovePrograms = new Guid(0xdf7266ac, 0x9274, 0x4867, 0x8d, 0x55, 0x3b, 0xd6, 0x61, 0xde, 0x87, 0x2d); + + /// Administrative Tools + internal static Guid CommonAdminTools = new Guid(0xD0384E7D, 0xBAC3, 0x4797, 0x8F, 0x14, 0xCB, 0xA2, 0x29, 0xB3, 0x92, 0xB5); + + /// OEM Links + internal static Guid CommonOEMLinks = new Guid(0xC1BAE2D0, 0x10DF, 0x4334, 0xBE, 0xDD, 0x7A, 0xA2, 0x0B, 0x22, 0x7A, 0x9D); + + /// Programs + internal static Guid CommonPrograms = new Guid(0x0139D44E, 0x6AFE, 0x49F2, 0x86, 0x90, 0x3D, 0xAF, 0xCA, 0xE6, 0xFF, 0xB8); + + /// Start Menu + internal static Guid CommonStartMenu = new Guid(0xA4115719, 0xD62E, 0x491D, 0xAA, 0x7C, 0xE7, 0x4B, 0x8B, 0xE3, 0xB0, 0x67); + + /// Startup + internal static Guid CommonStartup = new Guid(0x82A5EA35, 0xD9CD, 0x47C5, 0x96, 0x29, 0xE1, 0x5D, 0x2F, 0x71, 0x4E, 0x6E); + + /// Templates + internal static Guid CommonTemplates = new Guid(0xB94237E7, 0x57AC, 0x4347, 0x91, 0x51, 0xB0, 0x8C, 0x6C, 0x32, 0xD1, 0xF7); + + /// Computer + internal static Guid Computer = new Guid(0x0AC0837C, 0xBBF8, 0x452A, 0x85, 0x0D, 0x79, 0xD0, 0x8E, 0x66, 0x7C, 0xA7); + + /// Conflicts + internal static Guid Conflict = new Guid(0x4bfefb45, 0x347d, 0x4006, 0xa5, 0xbe, 0xac, 0x0c, 0xb0, 0x56, 0x71, 0x92); + + /// Network Connections + internal static Guid Connections = new Guid(0x6F0CD92B, 0x2E97, 0x45D1, 0x88, 0xFF, 0xB0, 0xD1, 0x86, 0xB8, 0xDE, 0xDD); + + /// Contacts + internal static Guid Contacts = new Guid(0x56784854, 0xc6cb, 0x462b, 0x81, 0x69, 0x88, 0xe3, 0x50, 0xac, 0xb8, 0x82); + + /// Control Panel + internal static Guid ControlPanel = new Guid(0x82A74AEB, 0xAEB4, 0x465C, 0xA0, 0x14, 0xD0, 0x97, 0xEE, 0x34, 0x6D, 0x63); + + /// Cookies + internal static Guid Cookies = new Guid(0x2B0F765D, 0xC0E9, 0x4171, 0x90, 0x8E, 0x08, 0xA6, 0x11, 0xB8, 0x4F, 0xF6); + + /// Desktop + internal static Guid Desktop = new Guid(0xB4BFCC3A, 0xDB2C, 0x424C, 0xB0, 0x29, 0x7F, 0xE9, 0x9A, 0x87, 0xC6, 0x41); + + /// DeviceMetadataStore + internal static Guid DeviceMetadataStore = new Guid(0x5ce4a5e9, 0xe4eb, 0x479d, 0xb8, 0x9f, 0x13, 0x0c, 0x02, 0x88, 0x61, 0x55); + + /// Documents + internal static Guid Documents = new Guid(0xFDD39AD0, 0x238F, 0x46AF, 0xAD, 0xB4, 0x6C, 0x85, 0x48, 0x03, 0x69, 0xC7); + + /// DocumentsLibrary + internal static Guid DocumentsLibrary = new Guid(0x7b0db17d, 0x9cd2, 0x4a93, 0x97, 0x33, 0x46, 0xcc, 0x89, 0x02, 0x2e, 0x7c); + + /// Downloads + internal static Guid Downloads = new Guid(0x374de290, 0x123f, 0x4565, 0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b); + + /// Favorites + internal static Guid Favorites = new Guid(0x1777F761, 0x68AD, 0x4D8A, 0x87, 0xBD, 0x30, 0xB7, 0x59, 0xFA, 0x33, 0xDD); + + /// Fonts + internal static Guid Fonts = new Guid(0xFD228CB7, 0xAE11, 0x4AE3, 0x86, 0x4C, 0x16, 0xF3, 0x91, 0x0A, 0xB8, 0xFE); + + /// Games + internal static Guid Games = new Guid(0xcac52c1a, 0xb53d, 0x4edc, 0x92, 0xd7, 0x6b, 0x2e, 0x8a, 0xc1, 0x94, 0x34); + + /// GameExplorer + internal static Guid GameTasks = new Guid(0x54fae61, 0x4dd8, 0x4787, 0x80, 0xb6, 0x9, 0x2, 0x20, 0xc4, 0xb7, 0x0); + + /// History + internal static Guid History = new Guid(0xD9DC8A3B, 0xB784, 0x432E, 0xA7, 0x81, 0x5A, 0x11, 0x30, 0xA7, 0x59, 0x63); + + /// ImplicitAppShortcuts + internal static Guid ImplicitAppShortcuts = new Guid(0xbcb5256f, 0x79f6, 0x4cee, 0xb7, 0x25, 0xdc, 0x34, 0xe4, 0x2, 0xfd, 0x46); + + /// Internet Explorer + internal static Guid Internet = new Guid(0x4D9F7874, 0x4E0C, 0x4904, 0x96, 0x7B, 0x40, 0xB0, 0xD2, 0x0C, 0x3E, 0x4B); + + /// Temporary Internet Files + internal static Guid InternetCache = new Guid(0x352481E8, 0x33BE, 0x4251, 0xBA, 0x85, 0x60, 0x07, 0xCA, 0xED, 0xCF, 0x9D); + + /// Libraries + internal static Guid Libraries = new Guid(0x1b3ea5dc, 0xb587, 0x4786, 0xb4, 0xef, 0xbd, 0x1d, 0xc3, 0x32, 0xae, 0xae); + + /// Links + internal static Guid Links = new Guid(0xbfb9d5e0, 0xc6a9, 0x404c, 0xb2, 0xb2, 0xae, 0x6d, 0xb6, 0xaf, 0x49, 0x68); + + /// Local + internal static Guid LocalAppData = new Guid(0xF1B32785, 0x6FBA, 0x4FCF, 0x9D, 0x55, 0x7B, 0x8E, 0x7F, 0x15, 0x70, 0x91); + + /// LocalLow + internal static Guid LocalAppDataLow = new Guid(0xA520A1A4, 0x1780, 0x4FF6, 0xBD, 0x18, 0x16, 0x73, 0x43, 0xC5, 0xAF, 0x16); + + /// None + internal static Guid LocalizedResourcesDir = new Guid(0x2A00375E, 0x224C, 0x49DE, 0xB8, 0xD1, 0x44, 0x0D, 0xF7, 0xEF, 0x3D, 0xDC); + + /// Music + internal static Guid Music = new Guid(0x4BD8D571, 0x6D19, 0x48D3, 0xBE, 0x97, 0x42, 0x22, 0x20, 0x08, 0x0E, 0x43); + + /// MusicLibrary + internal static Guid MusicLibrary = new Guid(0x2112ab0a, 0xc86a, 0x4ffe, 0xa3, 0x68, 0xd, 0xe9, 0x6e, 0x47, 0x1, 0x2e); + + /// Network Shortcuts + internal static Guid NetHood = new Guid(0xC5ABBF53, 0xE17F, 0x4121, 0x89, 0x00, 0x86, 0x62, 0x6F, 0xC2, 0xC9, 0x73); + + /// Network + internal static Guid Network = new Guid(0xD20BEEC4, 0x5CA8, 0x4905, 0xAE, 0x3B, 0xBF, 0x25, 0x1E, 0xA0, 0x9B, 0x53); + + /// Original Images + internal static Guid OriginalImages = new Guid(0x2C36C0AA, 0x5812, 0x4b87, 0xbf, 0xd0, 0x4c, 0xd0, 0xdf, 0xb1, 0x9b, 0x39); + + /// OtherUsers + internal static Guid OtherUsers = new Guid(0x52528a6b, 0xb9e3, 0x4add, 0xb6, 0xd, 0x58, 0x8c, 0x2d, 0xba, 0x84, 0x2d); + + /// Slide Shows + internal static Guid PhotoAlbums = new Guid(0x69D2CF90, 0xFC33, 0x4FB7, 0x9A, 0x0C, 0xEB, 0xB0, 0xF0, 0xFC, 0xB4, 0x3C); + + /// Pictures + internal static Guid Pictures = new Guid(0x33E28130, 0x4E1E, 0x4676, 0x83, 0x5A, 0x98, 0x39, 0x5C, 0x3B, 0xC3, 0xBB); + + /// PicturesLibrary + internal static Guid PicturesLibrary = new Guid(0xa990ae9f, 0xa03b, 0x4e80, 0x94, 0xbc, 0x99, 0x12, 0xd7, 0x50, 0x41, 0x4); + + /// Playlists + internal static Guid Playlists = new Guid(0xDE92C1C7, 0x837F, 0x4F69, 0xA3, 0xBB, 0x86, 0xE6, 0x31, 0x20, 0x4A, 0x23); + + /// Printers + internal static Guid Printers = new Guid(0x76FC4E2D, 0xD6AD, 0x4519, 0xA6, 0x63, 0x37, 0xBD, 0x56, 0x06, 0x81, 0x85); + + /// Printer Shortcuts + internal static Guid PrintHood = new Guid(0x9274BD8D, 0xCFD1, 0x41C3, 0xB3, 0x5E, 0xB1, 0x3F, 0x55, 0xA7, 0x58, 0xF4); + + /// The user's username (%USERNAME%) + internal static Guid Profile = new Guid(0x5E6C858F, 0x0E22, 0x4760, 0x9A, 0xFE, 0xEA, 0x33, 0x17, 0xB6, 0x71, 0x73); + + /// ProgramData + internal static Guid ProgramData = new Guid(0x62AB5D82, 0xFDC1, 0x4DC3, 0xA9, 0xDD, 0x07, 0x0D, 0x1D, 0x49, 0x5D, 0x97); + + /// Program Files + internal static Guid ProgramFiles = new Guid(0x905e63b6, 0xc1bf, 0x494e, 0xb2, 0x9c, 0x65, 0xb7, 0x32, 0xd3, 0xd2, 0x1a); + + /// Common Files + internal static Guid ProgramFilesCommon = new Guid(0xF7F1ED05, 0x9F6D, 0x47A2, 0xAA, 0xAE, 0x29, 0xD3, 0x17, 0xC6, 0xF0, 0x66); + + /// Common Files + internal static Guid ProgramFilesCommonX64 = new Guid(0x6365d5a7, 0xf0d, 0x45e5, 0x87, 0xf6, 0xd, 0xa5, 0x6b, 0x6a, 0x4f, 0x7d); + + /// Common Files + internal static Guid ProgramFilesCommonX86 = new Guid(0xDE974D24, 0xD9C6, 0x4D3E, 0xBF, 0x91, 0xF4, 0x45, 0x51, 0x20, 0xB9, 0x17); + + /// Program Files + internal static Guid ProgramFilesX64 = new Guid(0x6d809377, 0x6af0, 0x444b, 0x89, 0x57, 0xa3, 0x77, 0x3f, 0x02, 0x20, 0x0e); + + /// Program Files + internal static Guid ProgramFilesX86 = new Guid(0x7C5A40EF, 0xA0FB, 0x4BFC, 0x87, 0x4A, 0xC0, 0xF2, 0xE0, 0xB9, 0xFA, 0x8E); + + /// Programs + internal static Guid Programs = new Guid(0xA77F5D77, 0x2E2B, 0x44C3, 0xA6, 0xA2, 0xAB, 0xA6, 0x01, 0x05, 0x4A, 0x51); + + /// Public + internal static Guid Public = new Guid(0xDFDF76A2, 0xC82A, 0x4D63, 0x90, 0x6A, 0x56, 0x44, 0xAC, 0x45, 0x73, 0x85); + + /// Public Desktop + internal static Guid PublicDesktop = new Guid(0xC4AA340D, 0xF20F, 0x4863, 0xAF, 0xEF, 0xF8, 0x7E, 0xF2, 0xE6, 0xBA, 0x25); + + /// Public Documents + internal static Guid PublicDocuments = new Guid(0xED4824AF, 0xDCE4, 0x45A8, 0x81, 0xE2, 0xFC, 0x79, 0x65, 0x08, 0x36, 0x34); + + /// Public Downloads + internal static Guid PublicDownloads = new Guid(0x3d644c9b, 0x1fb8, 0x4f30, 0x9b, 0x45, 0xf6, 0x70, 0x23, 0x5f, 0x79, 0xc0); + + /// GameExplorer + internal static Guid PublicGameTasks = new Guid(0xdebf2536, 0xe1a8, 0x4c59, 0xb6, 0xa2, 0x41, 0x45, 0x86, 0x47, 0x6a, 0xea); + + /// Public Music + internal static Guid PublicMusic = new Guid(0x3214FAB5, 0x9757, 0x4298, 0xBB, 0x61, 0x92, 0xA9, 0xDE, 0xAA, 0x44, 0xFF); + + /// Public Pictures + internal static Guid PublicPictures = new Guid(0xB6EBFB86, 0x6907, 0x413C, 0x9A, 0xF7, 0x4F, 0xC2, 0xAB, 0xF0, 0x7C, 0xC5); + + /// PublicRingtones + internal static Guid PublicRingtones = new Guid(0xE555AB60, 0x153B, 0x4D17, 0x9F, 0x04, 0xA5, 0xFE, 0x99, 0xFC, 0x15, 0xEC); + + /// Public Videos + internal static Guid PublicVideos = new Guid(0x2400183A, 0x6185, 0x49FB, 0xA2, 0xD8, 0x4A, 0x39, 0x2A, 0x60, 0x2B, 0xA3); + + /// Quick Launch + internal static Guid QuickLaunch = new Guid(0x52a4f021, 0x7b75, 0x48a9, 0x9f, 0x6b, 0x4b, 0x87, 0xa2, 0x10, 0xbc, 0x8f); + + /// Recent Items + internal static Guid Recent = new Guid(0xAE50C081, 0xEBD2, 0x438A, 0x86, 0x55, 0x8A, 0x09, 0x2E, 0x34, 0x98, 0x7A); + + /// Recorded TV + internal static Guid RecordedTV = new Guid(0xbd85e001, 0x112e, 0x431e, 0x98, 0x3b, 0x7b, 0x15, 0xac, 0x09, 0xff, 0xf1); + + /// RecordedTVLibrary + internal static Guid RecordedTVLibrary = new Guid(0x1a6fdba2, 0xf42d, 0x4358, 0xa7, 0x98, 0xb7, 0x4d, 0x74, 0x59, 0x26, 0xc5); + + /// Recycle Bin + internal static Guid RecycleBin = new Guid(0xB7534046, 0x3ECB, 0x4C18, 0xBE, 0x4E, 0x64, 0xCD, 0x4C, 0xB7, 0xD6, 0xAC); + + /// Resources + internal static Guid ResourceDir = new Guid(0x8AD10C31, 0x2ADB, 0x4296, 0xA8, 0xF7, 0xE4, 0x70, 0x12, 0x32, 0xC9, 0x72); + + /// Ringtones + internal static Guid Ringtones = new Guid(0xC870044B, 0xF49E, 0x4126, 0xA9, 0xC3, 0xB5, 0x2A, 0x1F, 0xF4, 0x11, 0xE8); + + /// Roaming + internal static Guid RoamingAppData = new Guid(0x3EB685DB, 0x65F9, 0x4CF6, 0xA0, 0x3A, 0xE3, 0xEF, 0x65, 0x72, 0x9F, 0x3D); + + /// Sample Music + internal static Guid SampleMusic = new Guid(0xB250C668, 0xF57D, 0x4EE1, 0xA6, 0x3C, 0x29, 0x0E, 0xE7, 0xD1, 0xAA, 0x1F); + + /// Sample Pictures + internal static Guid SamplePictures = new Guid(0xC4900540, 0x2379, 0x4C75, 0x84, 0x4B, 0x64, 0xE6, 0xFA, 0xF8, 0x71, 0x6B); + + /// Sample Playlists + internal static Guid SamplePlaylists = new Guid(0x15CA69B3, 0x30EE, 0x49C1, 0xAC, 0xE1, 0x6B, 0x5E, 0xC3, 0x72, 0xAF, 0xB5); + + /// Sample Videos + internal static Guid SampleVideos = new Guid(0x859EAD94, 0x2E85, 0x48AD, 0xA7, 0x1A, 0x09, 0x69, 0xCB, 0x56, 0xA6, 0xCD); + + /// Saved Games + internal static Guid SavedGames = new Guid(0x4c5c32ff, 0xbb9d, 0x43b0, 0xb5, 0xb4, 0x2d, 0x72, 0xe5, 0x4e, 0xaa, 0xa4); + + /// Searches + internal static Guid SavedSearches = new Guid(0x7d1d3a04, 0xdebb, 0x4115, 0x95, 0xcf, 0x2f, 0x29, 0xda, 0x29, 0x20, 0xda); + + /// Offline Files + internal static Guid SearchCsc = new Guid(0xee32e446, 0x31ca, 0x4aba, 0x81, 0x4f, 0xa5, 0xeb, 0xd2, 0xfd, 0x6d, 0x5e); + + /// Search home + internal static Guid SearchHome = new Guid(0x190337d1, 0xb8ca, 0x4121, 0xa6, 0x39, 0x6d, 0x47, 0x2d, 0x16, 0x97, 0x2a); + + /// Microsoft Office Outlook + internal static Guid SearchMapi = new Guid(0x98ec0e18, 0x2098, 0x4d44, 0x86, 0x44, 0x66, 0x97, 0x93, 0x15, 0xa2, 0x81); + + /// SendTo + internal static Guid SendTo = new Guid(0x8983036C, 0x27C0, 0x404B, 0x8F, 0x08, 0x10, 0x2D, 0x10, 0xDC, 0xFD, 0x74); + + /// Gadgets + internal static Guid SidebarDefaultParts = new Guid(0x7b396e54, 0x9ec5, 0x4300, 0xbe, 0xa, 0x24, 0x82, 0xeb, 0xae, 0x1a, 0x26); + + /// Gadgets + internal static Guid SidebarParts = new Guid(0xa75d362e, 0x50fc, 0x4fb7, 0xac, 0x2c, 0xa8, 0xbe, 0xaa, 0x31, 0x44, 0x93); + + /// Start Menu + internal static Guid StartMenu = new Guid(0x625B53C3, 0xAB48, 0x4EC1, 0xBA, 0x1F, 0xA1, 0xEF, 0x41, 0x46, 0xFC, 0x19); + + /// Startup + internal static Guid Startup = new Guid(0xB97D20BB, 0xF46A, 0x4C97, 0xBA, 0x10, 0x5E, 0x36, 0x08, 0x43, 0x08, 0x54); + + /// Sync Center + internal static Guid SyncManager = new Guid(0x43668BF8, 0xC14E, 0x49B2, 0x97, 0xC9, 0x74, 0x77, 0x84, 0xD7, 0x84, 0xB7); + + /// Sync Results + internal static Guid SyncResults = new Guid(0x289a9a43, 0xbe44, 0x4057, 0xa4, 0x1b, 0x58, 0x7a, 0x76, 0xd7, 0xe7, 0xf9); + + /// Sync Setup + internal static Guid SyncSetup = new Guid(0xf214138, 0xb1d3, 0x4a90, 0xbb, 0xa9, 0x27, 0xcb, 0xc0, 0xc5, 0x38, 0x9a); + + /// System32 + internal static Guid System = new Guid(0x1AC14E77, 0x02E7, 0x4E5D, 0xB7, 0x44, 0x2E, 0xB1, 0xAE, 0x51, 0x98, 0xB7); + + /// System32 + internal static Guid SystemX86 = new Guid(0xD65231B0, 0xB2F1, 0x4857, 0xA4, 0xCE, 0xA8, 0xE7, 0xC6, 0xEA, 0x7D, 0x27); + + /// Templates + internal static Guid Templates = new Guid(0xA63293E8, 0x664E, 0x48DB, 0xA0, 0x79, 0xDF, 0x75, 0x9E, 0x05, 0x09, 0xF7); + + /// Tree property value folder + internal static Guid TreeProperties = new Guid(0x5b3749ad, 0xb49f, 0x49c1, 0x83, 0xeb, 0x15, 0x37, 0x0f, 0xbd, 0x48, 0x82); + + /// UserPinned + internal static Guid UserPinned = new Guid(0x9e3995ab, 0x1f9c, 0x4f13, 0xb8, 0x27, 0x48, 0xb2, 0x4b, 0x6c, 0x71, 0x74); + + /// Users + internal static Guid UserProfiles = new Guid(0x0762D272, 0xC50A, 0x4BB0, 0xA3, 0x82, 0x69, 0x7D, 0xCD, 0x72, 0x9B, 0x80); + + /// UserProgramFiles + internal static Guid UserProgramFiles = new Guid(0x5cd7aee2, 0x2219, 0x4a67, 0xb8, 0x5d, 0x6c, 0x9c, 0xe1, 0x56, 0x60, 0xcb); + + /// UserProgramFilesCommon + internal static Guid UserProgramFilesCommon = new Guid(0xbcbd3057, 0xca5c, 0x4622, 0xb4, 0x2d, 0xbc, 0x56, 0xdb, 0x0a, 0xe5, 0x16); + + /// The user's full name (for instance, Jean Philippe Bagel) entered when the user account was created. + internal static Guid UsersFiles = new Guid(0xf3ce0f7c, 0x4901, 0x4acc, 0x86, 0x48, 0xd5, 0xd4, 0x4b, 0x04, 0xef, 0x8f); + + /// UsersLibraries + internal static Guid UsersLibraries = new Guid(0xa302545d, 0xdeff, 0x464b, 0xab, 0xe8, 0x61, 0xc8, 0x64, 0x8d, 0x93, 0x9b); + + /// Videos + internal static Guid Videos = new Guid(0x18989B1D, 0x99B5, 0x455B, 0x84, 0x1C, 0xAB, 0x7C, 0x74, 0xE4, 0xDD, 0xFC); + + /// VideosLibrary + internal static Guid VideosLibrary = new Guid(0x491e922f, 0x5643, 0x4af4, 0xa7, 0xeb, 0x4e, 0x7a, 0x13, 0x8d, 0x81, 0x74); + + /// Windows + internal static Guid Windows = new Guid(0xF38BF404, 0x1D43, 0x42F2, 0x93, 0x05, 0x67, 0xDE, 0x0B, 0x28, 0xFC, 0x23); + + private static readonly Dictionary folders; + + static FolderIdentifiers() + { + folders = new Dictionary(); + var folderIDs = typeof(FolderIdentifiers); + + var fields = folderIDs.GetFields(BindingFlags.NonPublic | BindingFlags.Static); + foreach (var f in fields) + { + // Ignore dictionary field. + if (f.FieldType == typeof(Guid)) + { + var id = (Guid)f.GetValue(null); + var name = f.Name; + folders.Add(id, name); + } + } + } + + /// Returns a sorted list of name, guid pairs for all known folders. + /// + internal static SortedList GetAllFolders() + { + // Make a copy of the dictionary because the Keys and Values collections are mutable. + ICollection keys = folders.Keys; + + var slist = new SortedList(); + foreach (var g in keys) + { + slist.Add(folders[g], g); + } + + return slist; + } + + /// Returns the friendly name for a specified folder. + /// The Guid identifier for a known folder. + /// A value. + internal static string NameForGuid(Guid folderId) + { + if (!folders.TryGetValue(folderId, out var folder)) + { + throw new ArgumentException(LocalizedMessages.FolderIdsUnknownGuid, "folderId"); + } + return folder; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/KnownFolders/IKnownFolder.cs b/VG Music Studio - WinForms/API/Shell/KnownFolders/IKnownFolder.cs new file mode 100644 index 0000000..db3158e --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/KnownFolders/IKnownFolder.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Collections.Generic; +using System.IO; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Represents a registered or known folder in the system. + public interface IKnownFolder : IDisposable, IEnumerable + { + /// Gets this known folder's canonical name. + string CanonicalName { get; } + + /// Gets the category designation for this known folder. + FolderCategory Category { get; } + + /// Gets an value that describes this known folder's behaviors. + DefinitionOptions DefinitionOptions { get; } + + /// Gets this known folder's description. + string Description { get; } + + /// Gets this known folder's file attributes, such as "read-only". + FileAttributes FileAttributes { get; } + + /// Gets the unique identifier for this known folder. + Guid FolderId { get; } + + /// Gets a string representation of this known folder's type. + string FolderType { get; } + + /// Gets the unique identifier for this known folder's type. + Guid FolderTypeId { get; } + + /// Gets this known folder's localized name. + string LocalizedName { get; } + + /// Gets the resource identifier for this known folder's localized name. + string LocalizedNameResourceId { get; } + + /// Gets the unique identifier for this known folder's parent folder. + Guid ParentId { get; } + + /// Gets this known folder's parsing name. + string ParsingName { get; } + + /// Gets the path for this known folder. + string Path { get; } + + /// Gets a value that indicates whether this known folder's path exists on the computer. + /// + /// If this property value is false, the folder might be a virtual folder ( property will be + /// for virtual folders) + /// + bool PathExists { get; } + + /// + /// Gets a value that states whether this known folder can have its path set to a new value, including any restrictions on the redirection. + /// + RedirectionCapability Redirection { get; } + + /// Gets this known folder's relative path. + string RelativePath { get; } + + /// Gets this known folder's security attributes. + string Security { get; } + + /// Gets this known folder's tool tip text. + string Tooltip { get; } + + /// Gets the resource identifier for this known folder's tool tip text. + string TooltipResourceId { get; } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/KnownFolders/KnownFolderHelper.cs b/VG Music Studio - WinForms/API/Shell/KnownFolders/KnownFolderHelper.cs new file mode 100644 index 0000000..9a51da0 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/KnownFolders/KnownFolderHelper.cs @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Diagnostics; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Creates the helper class for known folders. + public static class KnownFolderHelper + { + /// Returns the known folder given its canonical name. + /// A non-localized canonical name for the known folder, such as MyComputer. + /// A known folder representing the specified name. + /// + /// Thrown if the given canonical name is invalid or if the KnownFolder could not be created. + /// + public static IKnownFolder FromCanonicalName(string canonicalName) + { + var knownFolderManager = (IKnownFolderManager)new KnownFolderManagerClass(); + + knownFolderManager.GetFolderByName(canonicalName, out var knownFolderNative); + var kf = KnownFolderHelper.GetKnownFolder(knownFolderNative); + + if (kf == null) + { + throw new ArgumentException(LocalizedMessages.ShellInvalidCanonicalName, "canonicalName"); + } + return kf; + } + + /// Returns a known folder given a globally unique identifier. + /// A GUID for the requested known folder. + /// A known folder representing the specified name. + /// Thrown if the given Known Folder ID is invalid. + public static IKnownFolder FromKnownFolderId(Guid knownFolderId) + { + var knownFolderManager = new KnownFolderManagerClass(); + + var hr = knownFolderManager.GetFolder(knownFolderId, out var knownFolderNative); + if (hr != HResult.Ok) { throw new ShellException(hr); } + + var kf = GetKnownFolder(knownFolderNative); + if (kf == null) + { + throw new ArgumentException(LocalizedMessages.KnownFolderInvalidGuid, "knownFolderId"); + } + return kf; + } + + /// + /// Returns a known folder given its shell namespace parsing name, such as ::{645FF040-5081-101B-9F08-00AA002F954E} for the + /// Recycle Bin. + /// + /// The parsing name (or path) for the requested known folder. + /// A known folder representing the specified name. + /// Thrown if the given parsing name is invalid. + public static IKnownFolder FromParsingName(string parsingName) + { + if (parsingName == null) + { + throw new ArgumentNullException("parsingName"); + } + + var pidl = IntPtr.Zero; + var pidl2 = IntPtr.Zero; + + try + { + pidl = ShellHelper.PidlFromParsingName(parsingName); + + if (pidl == IntPtr.Zero) + { + throw new ArgumentException(LocalizedMessages.KnownFolderParsingName, "parsingName"); + } + + // It's probably a special folder, try to get it + var knownFolderNative = KnownFolderHelper.FromPIDL(pidl); + if (knownFolderNative != null) + { + var kf = KnownFolderHelper.GetKnownFolder(knownFolderNative); + if (kf == null) + { + throw new ArgumentException(LocalizedMessages.KnownFolderParsingName, "parsingName"); + } + return kf; + } + + // No physical storage was found for this known folder We'll try again with a different name + + // try one more time with a trailing \0 + pidl2 = ShellHelper.PidlFromParsingName(parsingName.PadRight(1, '\0')); + + if (pidl2 == IntPtr.Zero) + { + throw new ArgumentException(LocalizedMessages.KnownFolderParsingName, "parsingName"); + } + + var kf2 = KnownFolderHelper.GetKnownFolder(KnownFolderHelper.FromPIDL(pidl)); + if (kf2 == null) + { + throw new ArgumentException(LocalizedMessages.KnownFolderParsingName, "parsingName"); + } + + return kf2; + } + finally + { + ShellNativeMethods.ILFree(pidl); + ShellNativeMethods.ILFree(pidl2); + } + } + + /// + /// Returns a known folder given its shell path, such as C:\users\public\documents or + /// ::{645FF040-5081-101B-9F08-00AA002F954E} for the Recycle Bin. + /// + /// The path for the requested known folder; either a physical path or a virtual path. + /// A known folder representing the specified name. + public static IKnownFolder FromPath(string path) => KnownFolderHelper.FromParsingName(path); + + /// Returns a known folder given a globally unique identifier. + /// A GUID for the requested known folder. + /// A known folder representing the specified name. Returns null if Known Folder is not found or could not be created. + internal static IKnownFolder FromKnownFolderIdInternal(Guid knownFolderId) + { + var knownFolderManager = (IKnownFolderManager)new KnownFolderManagerClass(); + + var hr = knownFolderManager.GetFolder(knownFolderId, out var knownFolderNative); + + return (hr == HResult.Ok) ? GetKnownFolder(knownFolderNative) : null; + } + + /// Returns the native known folder (IKnownFolderNative) given a PID list + /// + /// + internal static IKnownFolderNative FromPIDL(IntPtr pidl) + { + var knownFolderManager = new KnownFolderManagerClass(); + + var hr = knownFolderManager.FindFolderFromIDList(pidl, out var knownFolder); + + return (hr == HResult.Ok) ? knownFolder : null; + } + + /// + /// Given a native KnownFolder (IKnownFolderNative), create the right type of IKnownFolder object (FileSystemKnownFolder or NonFileSystemKnownFolder) + /// + /// Native Known Folder + /// + private static IKnownFolder GetKnownFolder(IKnownFolderNative knownFolderNative) + { + Debug.Assert(knownFolderNative != null, "Native IKnownFolder should not be null."); + + // Get the native IShellItem2 from the native IKnownFolder + var guid = new Guid(ShellIIDGuid.IShellItem2); + var hr = knownFolderNative.GetShellItem(0, ref guid, out var shellItem); + + if (!CoreErrorHelper.Succeeded(hr)) { return null; } + + var isFileSystem = false; + + // If we have a valid IShellItem, try to get the FileSystem attribute. + if (shellItem != null) + { + shellItem.GetAttributes(ShellNativeMethods.ShellFileGetAttributesOptions.FileSystem, out var sfgao); + + // Is this item a FileSystem item? + isFileSystem = (sfgao & ShellNativeMethods.ShellFileGetAttributesOptions.FileSystem) != 0; + } + + // If it's FileSystem, create a FileSystemKnownFolder, else NonFileSystemKnownFolder + if (isFileSystem) + { + var kf = new FileSystemKnownFolder(knownFolderNative); + return kf; + } + + var knownFsFolder = new NonFileSystemKnownFolder(knownFolderNative); + return knownFsFolder; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/KnownFolders/KnownFolderSettings.cs b/VG Music Studio - WinForms/API/Shell/KnownFolders/KnownFolderSettings.cs new file mode 100644 index 0000000..e1c86ad --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/KnownFolders/KnownFolderSettings.cs @@ -0,0 +1,180 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Internal class to represent the KnownFolder settings/properties + internal class KnownFolderSettings + { + private FolderProperties knownFolderProperties; + + internal KnownFolderSettings(IKnownFolderNative knownFolderNative) => GetFolderProperties(knownFolderNative); + + /// Gets this known folder's canonical name. + /// A object. + public string CanonicalName => knownFolderProperties.canonicalName; + + /// Gets the category designation for this known folder. + /// A value. + public FolderCategory Category => knownFolderProperties.category; + + /// Gets an value that describes this known folder's behaviors. + /// A value. + public DefinitionOptions DefinitionOptions => knownFolderProperties.definitionOptions; + + /// Gets this known folder's description. + /// A object. + public string Description => knownFolderProperties.description; + + /// Gets the unique identifier for this known folder. + /// A value. + public Guid FolderId => knownFolderProperties.folderId; + + /// Gets a string representation of this known folder's type. + /// A object. + public string FolderType => knownFolderProperties.folderType; + + /// Gets the unique identifier for this known folder's type. + /// A value. + public Guid FolderTypeId => knownFolderProperties.folderTypeId; + + /// Gets this known folder's localized name. + /// A object. + public string LocalizedName => knownFolderProperties.localizedName; + + /// Gets the resource identifier for this known folder's localized name. + /// A object. + public string LocalizedNameResourceId => knownFolderProperties.localizedNameResourceId; + + /// Gets the unique identifier for this known folder's parent folder. + /// A value. + public Guid ParentId => knownFolderProperties.parentId; + + /// Gets the path for this known folder. + /// A object. + public string Path => knownFolderProperties.path; + + /// Gets a value that indicates whether this known folder's path exists on the computer. + /// A bool value. + /// + /// If this property value is false, the folder might be a virtual folder ( property will be + /// for virtual folders) + /// + public bool PathExists => knownFolderProperties.pathExists; + + /// + /// Gets a value that states whether this known folder can have its path set to a new value, including any restrictions on the redirection. + /// + /// A value. + public RedirectionCapability Redirection => knownFolderProperties.redirection; + + /// Gets this known folder's relative path. + /// A object. + public string RelativePath => knownFolderProperties.relativePath; + + /// Gets this known folder's security attributes. + /// A object. + public string Security => knownFolderProperties.security; + + /// Gets this known folder's tool tip text. + /// A object. + public string Tooltip => knownFolderProperties.tooltip; + + /// Gets the resource identifier for this known folder's tool tip text. + /// A object. + public string TooltipResourceId => knownFolderProperties.tooltipResourceId; + + /// Gets this known folder's file attributes, such as "read-only". + /// A value. + public System.IO.FileAttributes FileAttributes => knownFolderProperties.fileAttributes; + + /// Populates a structure that contains this known folder's properties. + private void GetFolderProperties(IKnownFolderNative knownFolderNative) + { + Debug.Assert(knownFolderNative != null); + + knownFolderNative.GetFolderDefinition(out var nativeFolderDefinition); + + try + { + knownFolderProperties.category = nativeFolderDefinition.category; + knownFolderProperties.canonicalName = Marshal.PtrToStringUni(nativeFolderDefinition.name); + knownFolderProperties.description = Marshal.PtrToStringUni(nativeFolderDefinition.description); + knownFolderProperties.parentId = nativeFolderDefinition.parentId; + knownFolderProperties.relativePath = Marshal.PtrToStringUni(nativeFolderDefinition.relativePath); + knownFolderProperties.parsingName = Marshal.PtrToStringUni(nativeFolderDefinition.parsingName); + knownFolderProperties.tooltipResourceId = Marshal.PtrToStringUni(nativeFolderDefinition.tooltip); + knownFolderProperties.localizedNameResourceId = Marshal.PtrToStringUni(nativeFolderDefinition.localizedName); + knownFolderProperties.iconResourceId = Marshal.PtrToStringUni(nativeFolderDefinition.icon); + knownFolderProperties.security = Marshal.PtrToStringUni(nativeFolderDefinition.security); + knownFolderProperties.fileAttributes = (System.IO.FileAttributes)nativeFolderDefinition.attributes; + knownFolderProperties.definitionOptions = nativeFolderDefinition.definitionOptions; + knownFolderProperties.folderTypeId = nativeFolderDefinition.folderTypeId; + knownFolderProperties.folderType = FolderTypes.GetFolderType(knownFolderProperties.folderTypeId); + + knownFolderProperties.path = GetPath(out var pathExists, knownFolderNative); + knownFolderProperties.pathExists = pathExists; + + knownFolderProperties.redirection = knownFolderNative.GetRedirectionCapabilities(); + + // Turn tooltip, localized name and icon resource IDs into the actual resources. + knownFolderProperties.tooltip = CoreHelpers.GetStringResource(knownFolderProperties.tooltipResourceId); + knownFolderProperties.localizedName = CoreHelpers.GetStringResource(knownFolderProperties.localizedNameResourceId); + + knownFolderProperties.folderId = knownFolderNative.GetId(); + } + finally + { + // Clean up memory. + Marshal.FreeCoTaskMem(nativeFolderDefinition.name); + Marshal.FreeCoTaskMem(nativeFolderDefinition.description); + Marshal.FreeCoTaskMem(nativeFolderDefinition.relativePath); + Marshal.FreeCoTaskMem(nativeFolderDefinition.parsingName); + Marshal.FreeCoTaskMem(nativeFolderDefinition.tooltip); + Marshal.FreeCoTaskMem(nativeFolderDefinition.localizedName); + Marshal.FreeCoTaskMem(nativeFolderDefinition.icon); + Marshal.FreeCoTaskMem(nativeFolderDefinition.security); + } + } + + /// Gets the path of this this known folder. + /// + /// Returns false if the folder is virtual, or a boolean value that indicates whether this known folder exists. + /// + /// Native IKnownFolder reference + /// A containing the path, or if this known folder does not exist. + private string GetPath(out bool fileExists, IKnownFolderNative knownFolderNative) + { + Debug.Assert(knownFolderNative != null); + + var kfPath = string.Empty; + fileExists = true; + + // Virtual folders do not have path. + if (knownFolderProperties.category == FolderCategory.Virtual) + { + fileExists = false; + return kfPath; + } + + try + { + kfPath = knownFolderNative.GetPath(0); + } + catch (System.IO.FileNotFoundException) + { + fileExists = false; + } + catch (System.IO.DirectoryNotFoundException) + { + fileExists = false; + } + + return kfPath; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/KnownFolders/KnownFolders.cs b/VG Music Studio - WinForms/API/Shell/KnownFolders/KnownFolders.cs new file mode 100644 index 0000000..9f8d207 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/KnownFolders/KnownFolders.cs @@ -0,0 +1,576 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Defines properties for known folders that identify the path of standard known folders. + public static class KnownFolders + { + /// Gets a strongly-typed read-only collection of all the registered known folders. + public static ICollection All => GetAllFolders(); + + /// Gets the metadata for the CommonOEMLinks folder. + /// An object. + public static IKnownFolder CommonOemLinks => GetKnownFolder(FolderIdentifiers.CommonOEMLinks); + + /// Gets the metadata for the DeviceMetadataStore folder. + public static IKnownFolder DeviceMetadataStore + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.DeviceMetadataStore); + } + } + + /// Gets the metadata for the DocumentsLibrary folder. + public static IKnownFolder DocumentsLibrary + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.DocumentsLibrary); + } + } + + /// Gets the metadata for the ImplicitAppShortcuts folder. + public static IKnownFolder ImplicitAppShortcuts + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.ImplicitAppShortcuts); + } + } + + /// Gets the metadata for the Libraries folder. + public static IKnownFolder Libraries + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.Libraries); + } + } + + /// Gets the metadata for the MusicLibrary folder. + public static IKnownFolder MusicLibrary + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.MusicLibrary); + } + } + + /// Gets the metadata for the OtherUsers folder. + public static IKnownFolder OtherUsers + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.OtherUsers); + } + } + + /// Gets the metadata for the PicturesLibrary folder. + public static IKnownFolder PicturesLibrary + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.PicturesLibrary); + } + } + + /// Gets the metadata for the PublicRingtones folder. + public static IKnownFolder PublicRingtones + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.PublicRingtones); + } + } + + /// Gets the metadata for the RecordedTVLibrary folder. + public static IKnownFolder RecordedTVLibrary + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.RecordedTVLibrary); + } + } + + /// Gets the metadata for the Ringtones folder. + public static IKnownFolder Ringtones + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.Ringtones); + } + } + + /// + ///Gets the metadata for the UserPinned folder. + /// + public static IKnownFolder UserPinned + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.UserPinned); + } + } + + /// Gets the metadata for the UserProgramFiles folder. + public static IKnownFolder UserProgramFiles + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.UserProgramFiles); + } + } + + /// Gets the metadata for the UserProgramFilesCommon folder. + public static IKnownFolder UserProgramFilesCommon + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.UserProgramFilesCommon); + } + } + + /// Gets the metadata for the UsersLibraries folder. + public static IKnownFolder UsersLibraries + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.UsersLibraries); + } + } + + /// Gets the metadata for the VideosLibrary folder. + public static IKnownFolder VideosLibrary + { + get + { + CoreHelpers.ThrowIfNotWin7(); + return GetKnownFolder(FolderIdentifiers.VideosLibrary); + } + } + + /// Gets the metadata for the AddNewPrograms folder. + /// An object. + public static IKnownFolder AddNewPrograms => GetKnownFolder(FolderIdentifiers.AddNewPrograms); + + /// Gets the metadata for the AdminTools folder. + /// An object. + public static IKnownFolder AdminTools => GetKnownFolder(FolderIdentifiers.AdminTools); + + /// Gets the metadata for the AppUpdates folder. + /// An object. + public static IKnownFolder AppUpdates => GetKnownFolder(FolderIdentifiers.AppUpdates); + + /// Gets the metadata for the CDBurning folder. + /// An object. + public static IKnownFolder CDBurning => GetKnownFolder(FolderIdentifiers.CDBurning); + + /// Gets the metadata for the ChangeRemovePrograms folder. + /// An object. + public static IKnownFolder ChangeRemovePrograms => GetKnownFolder(FolderIdentifiers.ChangeRemovePrograms); + + /// Gets the metadata for the CommonAdminTools folder. + /// An object. + public static IKnownFolder CommonAdminTools => GetKnownFolder(FolderIdentifiers.CommonAdminTools); + + /// Gets the metadata for the CommonPrograms folder. + /// An object. + public static IKnownFolder CommonPrograms => GetKnownFolder(FolderIdentifiers.CommonPrograms); + + /// Gets the metadata for the CommonStartMenu folder. + /// An object. + public static IKnownFolder CommonStartMenu => GetKnownFolder(FolderIdentifiers.CommonStartMenu); + + /// Gets the metadata for the CommonStartup folder. + /// An object. + public static IKnownFolder CommonStartup => GetKnownFolder(FolderIdentifiers.CommonStartup); + + /// Gets the metadata for the CommonTemplates folder. + /// An object. + public static IKnownFolder CommonTemplates => GetKnownFolder(FolderIdentifiers.CommonTemplates); + + /// Gets the metadata for the Computer folder. + /// An object. + public static IKnownFolder Computer => GetKnownFolder( + FolderIdentifiers.Computer); + + /// Gets the metadata for the Conflict folder. + /// An object. + public static IKnownFolder Conflict => GetKnownFolder( + FolderIdentifiers.Conflict); + + /// Gets the metadata for the Connections folder. + /// An object. + public static IKnownFolder Connections => GetKnownFolder( + FolderIdentifiers.Connections); + + /// Gets the metadata for the Contacts folder. + /// An object. + public static IKnownFolder Contacts => GetKnownFolder(FolderIdentifiers.Contacts); + + /// Gets the metadata for the ControlPanel folder. + /// An object. + public static IKnownFolder ControlPanel => GetKnownFolder( + FolderIdentifiers.ControlPanel); + + /// Gets the metadata for the Cookies folder. + /// An object. + public static IKnownFolder Cookies => GetKnownFolder(FolderIdentifiers.Cookies); + + /// Gets the metadata for the Desktop folder. + /// An object. + public static IKnownFolder Desktop => GetKnownFolder( + FolderIdentifiers.Desktop); + + /// Gets the metadata for the per-user Documents folder. + /// An object. + public static IKnownFolder Documents => GetKnownFolder(FolderIdentifiers.Documents); + + /// Gets the metadata for the per-user Downloads folder. + /// An object. + public static IKnownFolder Downloads => GetKnownFolder(FolderIdentifiers.Downloads); + + /// Gets the metadata for the per-user Favorites folder. + /// An object. + public static IKnownFolder Favorites => GetKnownFolder(FolderIdentifiers.Favorites); + + /// Gets the metadata for the Fonts folder. + /// An object. + public static IKnownFolder Fonts => GetKnownFolder(FolderIdentifiers.Fonts); + + /// Gets the metadata for the Games folder. + /// An object. + public static IKnownFolder Games => GetKnownFolder(FolderIdentifiers.Games); + + /// Gets the metadata for the GameTasks folder. + /// An object. + public static IKnownFolder GameTasks => GetKnownFolder(FolderIdentifiers.GameTasks); + + /// Gets the metadata for the History folder. + /// An object. + public static IKnownFolder History => GetKnownFolder(FolderIdentifiers.History); + + /// Gets the metadata for the Internet folder. + /// An object. + public static IKnownFolder Internet => GetKnownFolder( + FolderIdentifiers.Internet); + + /// Gets the metadata for the InternetCache folder. + /// An object. + public static IKnownFolder InternetCache => GetKnownFolder(FolderIdentifiers.InternetCache); + + /// Gets the metadata for the per-user Links folder. + /// An object. + public static IKnownFolder Links => GetKnownFolder(FolderIdentifiers.Links); + + /// Gets the metadata for the per-user LocalAppData folder. + /// An object. + public static IKnownFolder LocalAppData => GetKnownFolder(FolderIdentifiers.LocalAppData); + + /// Gets the metadata for the LocalAppDataLow folder. + /// An object. + public static IKnownFolder LocalAppDataLow => GetKnownFolder(FolderIdentifiers.LocalAppDataLow); + + /// Gets the metadata for the LocalizedResourcesDir folder. + /// An object. + public static IKnownFolder LocalizedResourcesDir => GetKnownFolder(FolderIdentifiers.LocalizedResourcesDir); + + /// Gets the metadata for the per-user Music folder. + /// An object. + public static IKnownFolder Music => GetKnownFolder(FolderIdentifiers.Music); + + /// Gets the metadata for the NetHood folder. + /// An object. + public static IKnownFolder NetHood => GetKnownFolder(FolderIdentifiers.NetHood); + + /// Gets the metadata for the Network folder. + /// An object. + public static IKnownFolder Network => GetKnownFolder( + FolderIdentifiers.Network); + + /// Gets the metadata for the OriginalImages folder. + /// An object. + public static IKnownFolder OriginalImages => GetKnownFolder(FolderIdentifiers.OriginalImages); + + /// Gets the metadata for the PhotoAlbums folder. + /// An object. + public static IKnownFolder PhotoAlbums => GetKnownFolder(FolderIdentifiers.PhotoAlbums); + + /// Gets the metadata for the per-user Pictures folder. + /// An object. + public static IKnownFolder Pictures => GetKnownFolder(FolderIdentifiers.Pictures); + + /// Gets the metadata for the Playlists folder. + /// An object. + public static IKnownFolder Playlists => GetKnownFolder(FolderIdentifiers.Playlists); + + /// Gets the metadata for the Printers folder. + /// An object. + public static IKnownFolder Printers => GetKnownFolder( + FolderIdentifiers.Printers); + + /// Gets the metadata for the PrintHood folder. + /// An object. + public static IKnownFolder PrintHood => GetKnownFolder(FolderIdentifiers.PrintHood); + + /// Gets the metadata for the Profile folder. + /// An object. + public static IKnownFolder Profile => GetKnownFolder(FolderIdentifiers.Profile); + + /// Gets the metadata for the ProgramData folder. + /// An object. + public static IKnownFolder ProgramData => GetKnownFolder(FolderIdentifiers.ProgramData); + + /// Gets the metadata for the ProgramFiles folder. + /// An object. + public static IKnownFolder ProgramFiles => GetKnownFolder(FolderIdentifiers.ProgramFiles); + + /// Gets the metadata for the ProgramFilesCommon folder. + /// An object. + public static IKnownFolder ProgramFilesCommon => GetKnownFolder(FolderIdentifiers.ProgramFilesCommon); + + /// Gets the metadata for the ProgramFilesCommonX64 folder. + /// An object. + public static IKnownFolder ProgramFilesCommonX64 => GetKnownFolder(FolderIdentifiers.ProgramFilesCommonX64); + + /// Gets the metadata for the ProgramFilesCommonX86 folder. + /// An object. + public static IKnownFolder ProgramFilesCommonX86 => GetKnownFolder(FolderIdentifiers.ProgramFilesCommonX86); + + /// Gets the metadata for the ProgramsFilesX64 folder. + /// An object. + public static IKnownFolder ProgramFilesX64 => GetKnownFolder(FolderIdentifiers.ProgramFilesX64); + + /// Gets the metadata for the ProgramFilesX86 folder. + /// An object. + public static IKnownFolder ProgramFilesX86 => GetKnownFolder(FolderIdentifiers.ProgramFilesX86); + + /// Gets the metadata for the Programs folder. + /// An object. + public static IKnownFolder Programs => GetKnownFolder(FolderIdentifiers.Programs); + + /// Gets the metadata for the Public folder. + /// An object. + public static IKnownFolder Public => GetKnownFolder(FolderIdentifiers.Public); + + /// Gets the metadata for the PublicDesktop folder. + /// An object. + public static IKnownFolder PublicDesktop => GetKnownFolder(FolderIdentifiers.PublicDesktop); + + /// Gets the metadata for the PublicDocuments folder. + /// An object. + public static IKnownFolder PublicDocuments => GetKnownFolder(FolderIdentifiers.PublicDocuments); + + /// Gets the metadata for the PublicDownloads folder. + /// An object. + public static IKnownFolder PublicDownloads => GetKnownFolder(FolderIdentifiers.PublicDownloads); + + /// Gets the metadata for the PublicGameTasks folder. + /// An object. + public static IKnownFolder PublicGameTasks => GetKnownFolder(FolderIdentifiers.PublicGameTasks); + + /// Gets the metadata for the PublicMusic folder. + /// An object. + public static IKnownFolder PublicMusic => GetKnownFolder(FolderIdentifiers.PublicMusic); + + /// Gets the metadata for the PublicPictures folder. + /// An object. + public static IKnownFolder PublicPictures => GetKnownFolder(FolderIdentifiers.PublicPictures); + + /// Gets the metadata for the PublicVideos folder. + /// An object. + public static IKnownFolder PublicVideos => GetKnownFolder(FolderIdentifiers.PublicVideos); + + /// Gets the metadata for the per-user QuickLaunch folder. + /// An object. + public static IKnownFolder QuickLaunch => GetKnownFolder(FolderIdentifiers.QuickLaunch); + + /// Gets the metadata for the per-user Recent folder. + /// An object. + public static IKnownFolder Recent => GetKnownFolder(FolderIdentifiers.Recent); + + /// Gets the metadata for the RecordedTV folder. + /// An object. + /// This folder is not used. + public static IKnownFolder RecordedTV => GetKnownFolder(FolderIdentifiers.RecordedTV); + + /// Gets the metadata for the RecycleBin folder. + /// An object. + public static IKnownFolder RecycleBin => GetKnownFolder( + FolderIdentifiers.RecycleBin); + + /// Gets the metadata for the ResourceDir folder. + /// An object. + public static IKnownFolder ResourceDir => GetKnownFolder(FolderIdentifiers.ResourceDir); + + /// Gets the metadata for the RoamingAppData folder. + /// An object. + public static IKnownFolder RoamingAppData => GetKnownFolder(FolderIdentifiers.RoamingAppData); + + /// Gets the metadata for the SampleMusic folder. + /// An object. + public static IKnownFolder SampleMusic => GetKnownFolder(FolderIdentifiers.SampleMusic); + + /// Gets the metadata for the SamplePictures folder. + /// An object. + public static IKnownFolder SamplePictures => GetKnownFolder(FolderIdentifiers.SamplePictures); + + /// Gets the metadata for the SamplePlaylists folder. + /// An object. + public static IKnownFolder SamplePlaylists => GetKnownFolder(FolderIdentifiers.SamplePlaylists); + + /// Gets the metadata for the SampleVideos folder. + /// An object. + public static IKnownFolder SampleVideos => GetKnownFolder(FolderIdentifiers.SampleVideos); + + /// Gets the metadata for the per-user SavedGames folder. + /// An object. + public static IKnownFolder SavedGames => GetKnownFolder(FolderIdentifiers.SavedGames); + + /// Gets the metadata for the per-user SavedSearches folder. + /// An object. + public static IKnownFolder SavedSearches => GetKnownFolder(FolderIdentifiers.SavedSearches); + + /// Gets the metadata for the SearchCsc folder. + /// An object. + public static IKnownFolder SearchCsc => GetKnownFolder(FolderIdentifiers.SearchCsc); + + /// Gets the metadata for the SearchHome folder. + /// An object. + public static IKnownFolder SearchHome => GetKnownFolder(FolderIdentifiers.SearchHome); + + /// Gets the metadata for the SearchMapi folder. + /// An object. + public static IKnownFolder SearchMapi => GetKnownFolder(FolderIdentifiers.SearchMapi); + + /// Gets the metadata for the per-user SendTo folder. + /// An object. + public static IKnownFolder SendTo => GetKnownFolder(FolderIdentifiers.SendTo); + + /// Gets the metadata for the SidebarDefaultParts folder. + /// An object. + public static IKnownFolder SidebarDefaultParts => GetKnownFolder(FolderIdentifiers.SidebarDefaultParts); + + /// Gets the metadata for the SidebarParts folder. + /// An object. + public static IKnownFolder SidebarParts => GetKnownFolder(FolderIdentifiers.SidebarParts); + + /// Gets the metadata for the per-user StartMenu folder. + /// An object. + public static IKnownFolder StartMenu => GetKnownFolder(FolderIdentifiers.StartMenu); + + /// Gets the metadata for the Startup folder. + /// An object. + public static IKnownFolder Startup => GetKnownFolder(FolderIdentifiers.Startup); + + /// Gets the metadata for the SyncManager folder. + /// An object. + public static IKnownFolder SyncManager => GetKnownFolder( + FolderIdentifiers.SyncManager); + + /// Gets the metadata for the SyncResults folder. + /// An object. + public static IKnownFolder SyncResults => GetKnownFolder( + FolderIdentifiers.SyncResults); + + /// Gets the metadata for the SyncSetup folder. + /// An object. + public static IKnownFolder SyncSetup => GetKnownFolder( + FolderIdentifiers.SyncSetup); + + /// Gets the metadata for the System folder. + /// An object. + public static IKnownFolder System => GetKnownFolder(FolderIdentifiers.System); + + /// Gets the metadata for the SystemX86 folder. + /// An object. + public static IKnownFolder SystemX86 => GetKnownFolder(FolderIdentifiers.SystemX86); + + /// Gets the metadata for the Templates folder. + /// An object. + public static IKnownFolder Templates => GetKnownFolder(FolderIdentifiers.Templates); + + /// Gets the metadata for the TreeProperties folder. + /// An object. + public static IKnownFolder TreeProperties => GetKnownFolder(FolderIdentifiers.TreeProperties); + + /// Gets the metadata for the UserProfiles folder. + /// An object. + public static IKnownFolder UserProfiles => GetKnownFolder(FolderIdentifiers.UserProfiles); + + /// Gets the metadata for the UsersFiles folder. + /// An object. + public static IKnownFolder UsersFiles => GetKnownFolder(FolderIdentifiers.UsersFiles); + + /// Gets the metadata for the Videos folder. + /// An object. + public static IKnownFolder Videos => GetKnownFolder(FolderIdentifiers.Videos); + + /// Gets the metadata for the Windows folder. + /// An object. + public static IKnownFolder Windows => GetKnownFolder(FolderIdentifiers.Windows); + + private static ReadOnlyCollection GetAllFolders() + { + // Should this method be thread-safe?? (It'll take a while to get a list of all the known folders, create the managed wrapper and + // return the read-only collection. + + IList foldersList = new List(); + var folders = IntPtr.Zero; + + try + { + var knownFolderManager = new KnownFolderManagerClass(); + knownFolderManager.GetFolderIds(out folders, out var count); + + if (count > 0 && folders != IntPtr.Zero) + { + // Loop through all the KnownFolderID elements + for (var i = 0; i < count; i++) + { + // Read the current pointer + var current = new IntPtr(folders.ToInt64() + (Marshal.SizeOf(typeof(Guid)) * i)); + + // Convert to Guid + var knownFolderID = (Guid)Marshal.PtrToStructure(current, typeof(Guid)); + + var kf = KnownFolderHelper.FromKnownFolderIdInternal(knownFolderID); + + // Add to our collection if it's not null (some folders might not exist on the system or we could have an exception + // that resulted in the null return from above method call + if (kf != null) { foldersList.Add(kf); } + } + } + } + finally + { + if (folders != IntPtr.Zero) { Marshal.FreeCoTaskMem(folders); } + } + + return new ReadOnlyCollection(foldersList); + } + + private static IKnownFolder GetKnownFolder(Guid guid) => KnownFolderHelper.FromKnownFolderId(guid); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/KnownFolders/NonFileSystemKnownFolder.cs b/VG Music Studio - WinForms/API/Shell/KnownFolders/NonFileSystemKnownFolder.cs new file mode 100644 index 0000000..1521ecf --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/KnownFolders/NonFileSystemKnownFolder.cs @@ -0,0 +1,163 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Represents a registered non file system Known Folder + public class NonFileSystemKnownFolder : ShellNonFileSystemFolder, IKnownFolder, IDisposable + { + private IKnownFolderNative knownFolderNative; + private KnownFolderSettings knownFolderSettings; + + internal NonFileSystemKnownFolder(IShellItem2 shellItem) : base(shellItem) + { + } + + internal NonFileSystemKnownFolder(IKnownFolderNative kf) + { + Debug.Assert(kf != null); + knownFolderNative = kf; + + // Set the native shell item and set it on the base class (ShellObject) + var guid = new Guid(ShellIIDGuid.IShellItem2); + knownFolderNative.GetShellItem(0, ref guid, out nativeShellItem); + } + + /// Gets this known folder's canonical name. + /// A object. + public string CanonicalName => KnownFolderSettings.CanonicalName; + + /// Gets the category designation for this known folder. + /// A value. + public FolderCategory Category => KnownFolderSettings.Category; + + /// Gets an value that describes this known folder's behaviors. + /// A value. + public DefinitionOptions DefinitionOptions => KnownFolderSettings.DefinitionOptions; + + /// Gets this known folder's description. + /// A object. + public string Description => KnownFolderSettings.Description; + + /// Gets this known folder's file attributes, such as "read-only". + /// A value. + public System.IO.FileAttributes FileAttributes => KnownFolderSettings.FileAttributes; + + /// Gets the unique identifier for this known folder. + /// A value. + public Guid FolderId => KnownFolderSettings.FolderId; + + /// Gets a string representation of this known folder's type. + /// A object. + public string FolderType => KnownFolderSettings.FolderType; + + /// Gets the unique identifier for this known folder's type. + /// A value. + public Guid FolderTypeId => KnownFolderSettings.FolderTypeId; + + /// Gets this known folder's localized name. + /// A object. + public string LocalizedName => KnownFolderSettings.LocalizedName; + + /// Gets the resource identifier for this known folder's localized name. + /// A object. + public string LocalizedNameResourceId => KnownFolderSettings.LocalizedNameResourceId; + + /// Gets the unique identifier for this known folder's parent folder. + /// A value. + public Guid ParentId => KnownFolderSettings.ParentId; + + /// Gets this known folder's parsing name. + /// A object. + public override string ParsingName => base.ParsingName; + + /// Gets the path for this known folder. + /// A object. + public string Path => KnownFolderSettings.Path; + + /// Gets a value that indicates whether this known folder's path exists on the computer. + /// A bool value. + /// + /// If this property value is false, the folder might be a virtual folder ( property will be + /// for virtual folders) + /// + public bool PathExists => KnownFolderSettings.PathExists; + + /// + /// Gets a value that states whether this known folder can have its path set to a new value, including any restrictions on the redirection. + /// + /// A value. + public RedirectionCapability Redirection => KnownFolderSettings.Redirection; + + /// Gets this known folder's relative path. + /// A object. + public string RelativePath => KnownFolderSettings.RelativePath; + + /// Gets this known folder's security attributes. + /// A object. + public string Security => KnownFolderSettings.Security; + + /// Gets this known folder's tool tip text. + /// A object. + public string Tooltip => KnownFolderSettings.Tooltip; + + /// Gets the resource identifier for this known folder's tool tip text. + /// A object. + public string TooltipResourceId => KnownFolderSettings.TooltipResourceId; + + private KnownFolderSettings KnownFolderSettings + { + get + { + if (knownFolderNative == null) + { + // We need to get the PIDL either from the NativeShellItem, or from base class's property (if someone already set it on + // us). Need to use the PIDL to get the native IKnownFolder interface. + + // Get teh PIDL for the ShellItem + if (nativeShellItem != null && base.PIDL == IntPtr.Zero) + { + base.PIDL = ShellHelper.PidlFromShellItem(nativeShellItem); + } + + // If we have a valid PIDL, get the native IKnownFolder + if (base.PIDL != IntPtr.Zero) + { + knownFolderNative = KnownFolderHelper.FromPIDL(base.PIDL); + } + + Debug.Assert(knownFolderNative != null); + } + + // If this is the first time this property is being called, get the native Folder Defination (KnownFolder properties) + if (knownFolderSettings == null) + { + knownFolderSettings = new KnownFolderSettings(knownFolderNative); + } + + return knownFolderSettings; + } + } + + /// Release resources + /// Indicates that this mothod is being called from Dispose() rather than the finalizer. + protected override void Dispose(bool disposing) + { + if (disposing) + { + knownFolderSettings = null; + } + + if (knownFolderNative != null) + { + Marshal.ReleaseComObject(knownFolderNative); + knownFolderNative = null; + } + + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/KnownFolders/RedirectionCapabilities.cs b/VG Music Studio - WinForms/API/Shell/KnownFolders/RedirectionCapabilities.cs new file mode 100644 index 0000000..34e5d9f --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/KnownFolders/RedirectionCapabilities.cs @@ -0,0 +1,32 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// Specifies the redirection capabilities for known folders. + public enum RedirectionCapability + { + /// Redirection capability is unknown. + None = 0x00, + + /// The known folder can be redirected. + AllowAll = 0xff, + + /// + /// The known folder can be redirected. Currently, redirection exists only for common and user folders; fixed and virtual folders + /// cannot be redirected. + /// + Redirectable = 0x1, + + /// Redirection is not allowed. + DenyAll = 0xfff00, + + /// The folder cannot be redirected because it is already redirected by group policy. + DenyPolicyRedirected = 0x100, + + /// The folder cannot be redirected because the policy prohibits redirecting this folder. + DenyPolicy = 0x200, + + /// The folder cannot be redirected because the calling application does not have sufficient permissions. + DenyPermissions = 0x400 + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/IShellProperty.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/IShellProperty.cs new file mode 100644 index 0000000..8cbf2a7 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/IShellProperty.cs @@ -0,0 +1,66 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +/* Unmerged change from project 'Shell (net452)' +Before: +using System; +using Microsoft.WindowsAPICodePack.Shell; +After: +using Microsoft.WindowsAPICodePack.Shell; +using System; +*/ + +/* Unmerged change from project 'Shell (net462)' +Before: +using System; +using Microsoft.WindowsAPICodePack.Shell; +After: +using Microsoft.WindowsAPICodePack.Shell; +using System; +*/ + +/* Unmerged change from project 'Shell (net472)' +Before: +using System; +using Microsoft.WindowsAPICodePack.Shell; +After: +using Microsoft.WindowsAPICodePack.Shell; +using System; +*/ + +using System; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + /// Defines the properties used by a Shell Property. + public interface IShellProperty + { + /// Gets the case-sensitive name of the property as it is known to the system, regardless of its localized name. + string CanonicalName { get; } + + /// Get the property description object. + ShellPropertyDescription Description { get; } + + /// + /// Gets the image reference path and icon index associated with a property value. This API is only available in Windows 7. + /// + IconReference IconReference { get; } + + /// Gets the property key that identifies this property. + PropertyKey PropertyKey { get; } + + /// Gets the value for this property using the generic Object type. + /// + /// To obtain a specific type for this value, use the more strongly-typed Property<T> class. You can only set a value + /// for this type using the Property<T> class. + /// + object ValueAsObject { get; } + + /// Gets the System.Type value for this property. + Type ValueType { get; } + + /// Gets a formatted, Unicode string representation of a property value. + /// One or more PropertyDescriptionFormat flags chosen to produce the desired display format. + /// The formatted value as a string. + string FormatForDisplay(PropertyDescriptionFormatOptions format); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/PropertySystemException.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/PropertySystemException.cs new file mode 100644 index 0000000..6e9b1af --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/PropertySystemException.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + /// An exception thrown when an error occurs while dealing with the Property System API. + [Serializable] + public class PropertySystemException : ExternalException + { + /// Default constructor. + public PropertySystemException() { } + + /// Initializes an excpetion with a custom message. + /// + public PropertySystemException(string message) : base(message) { } + + /// Initializes an exception with custom message and inner exception. + /// + /// + public PropertySystemException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// Initializes an exception with custom message and error code. + /// + /// + public PropertySystemException(string message, int errorCode) : base(message, errorCode) { } + + /// Initializes an exception from serialization info and a context. + /// + /// + protected PropertySystemException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellProperties.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellProperties.cs new file mode 100644 index 0000000..69f489e --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellProperties.cs @@ -0,0 +1,165 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +/* Unmerged change from project 'Shell (net452)' +Before: +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.WindowsAPICodePack.Internal; +After: +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.Runtime.Internal; +using System; +using System.Runtime.InteropServices; +using MS.WindowsAPICodePack.InteropServices.ComTypes; +*/ + +/* Unmerged change from project 'Shell (net462)' +Before: +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.WindowsAPICodePack.Internal; +After: +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.Runtime.Internal; +using System; +using System.Runtime.InteropServices; +using MS.WindowsAPICodePack.InteropServices.ComTypes; +*/ + +/* Unmerged change from project 'Shell (net472)' +Before: +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.WindowsAPICodePack.Internal; +After: +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.Runtime.Internal; +using System; +using System.Runtime.InteropServices; +using MS.WindowsAPICodePack.InteropServices.ComTypes; +*/ + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + /// + /// Defines a partial class that implements helper methods for retrieving Shell properties using a canonical name, property key, or a + /// strongly-typed property. Also provides access to all the strongly-typed system properties and default properties collections. + /// + public partial class ShellProperties : IDisposable + { + private ShellPropertyCollection defaultPropertyCollection; + private PropertySystem propertySystem; + + internal ShellProperties(ShellObject parent) => ParentShellObject = parent; + + /// Gets the collection of all the default properties for this item. + public ShellPropertyCollection DefaultPropertyCollection + { + get + { + if (defaultPropertyCollection == null) + { + defaultPropertyCollection = new ShellPropertyCollection(ParentShellObject); + } + + return defaultPropertyCollection; + } + } + + /// Gets all the properties for the system through an accessor. + public PropertySystem System + { + get + { + if (propertySystem == null) + { + propertySystem = new PropertySystem(ParentShellObject); + } + + return propertySystem; + } + } + + private ShellObject ParentShellObject { get; set; } + + /// Cleans up memory + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// Returns a property available in the default property collection using the given property key. + /// The property key. + /// An IShellProperty. + public IShellProperty GetProperty(PropertyKey key) => CreateTypedProperty(key); + + /// Returns a property available in the default property collection using the given canonical name. + /// The canonical name. + /// An IShellProperty. + public IShellProperty GetProperty(string canonicalName) => CreateTypedProperty(canonicalName); + + /// Returns a strongly typed property available in the default property collection using the given property key. + /// The type of property to retrieve. + /// The property key. + /// A strongly-typed ShellProperty for the given property key. + public ShellProperty GetProperty(PropertyKey key) => CreateTypedProperty(key) as ShellProperty; + + /// Returns a strongly typed property available in the default property collection using the given canonical name. + /// The type of property to retrieve. + /// The canonical name. + /// A strongly-typed ShellProperty for the given canonical name. + public ShellProperty GetProperty(string canonicalName) => CreateTypedProperty(canonicalName) as ShellProperty; + + /// Returns the shell property writer used when writing multiple properties. + /// A ShellPropertyWriter. + /// + /// Use the Using pattern with the returned ShellPropertyWriter or manually call the Close method on the writer to commit the changes + /// and dispose the writer + /// + public ShellPropertyWriter GetPropertyWriter() => new ShellPropertyWriter(ParentShellObject); + + internal IShellProperty CreateTypedProperty(PropertyKey propKey) + { + var desc = ShellPropertyDescriptionsCache.Cache.GetPropertyDescription(propKey); + return new ShellProperty(propKey, desc, ParentShellObject); + } + + internal IShellProperty CreateTypedProperty(PropertyKey propKey) => ShellPropertyFactory.CreateShellProperty(propKey, ParentShellObject); + + internal IShellProperty CreateTypedProperty(string canonicalName) + { + // Otherwise, call the native PropertyStore method + + var result = PropertySystemNativeMethods.PSGetPropertyKeyFromName(canonicalName, out var propKey); + + if (!CoreErrorHelper.Succeeded(result)) + { + throw new ArgumentException( + LocalizedMessages.ShellInvalidCanonicalName, + Marshal.GetExceptionForHR(result)); + } + return CreateTypedProperty(propKey); + } + + /// Cleans up memory + protected virtual void Dispose(bool disposed) + { + if (disposed && defaultPropertyCollection != null) + { + defaultPropertyCollection.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellProperty.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellProperty.cs new file mode 100644 index 0000000..c0223b1 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellProperty.cs @@ -0,0 +1,363 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + /// + /// Defines a strongly-typed property object. All writable property objects must be of this type to be able to call the value setter. + /// + /// The type of this property's value. Because a property value can be empty, only nullable types are allowed. + public class ShellProperty : IShellProperty + { + private readonly ShellPropertyDescription description = null; + private int? imageReferenceIconIndex; + private string imageReferencePath = null; + private PropertyKey propertyKey; + + /// Constructs a new Property object + /// + /// + /// + internal ShellProperty( + PropertyKey propertyKey, + ShellPropertyDescription description, + ShellObject parent) + { + this.propertyKey = propertyKey; + this.description = description; + ParentShellObject = parent; + AllowSetTruncatedValue = false; + } + + /// Constructs a new Property object + /// + /// + /// + internal ShellProperty( + PropertyKey propertyKey, + ShellPropertyDescription description, + IPropertyStore propertyStore) + { + this.propertyKey = propertyKey; + this.description = description; + NativePropertyStore = propertyStore; + AllowSetTruncatedValue = false; + } + + /// Gets or sets a value that determines if a value can be truncated. The default for this property is false. + /// + /// An will be thrown if this property is not set to true, and a property value was set but + /// later truncated. + /// + public bool AllowSetTruncatedValue { get; set; } + + /// Get the property description object. + public ShellPropertyDescription Description => description; + + /// Gets the image reference path and icon index associated with a property value (Windows 7 only). + public IconReference IconReference + { + get + { + if (!CoreHelpers.RunningOnWin7) + { + throw new PlatformNotSupportedException(LocalizedMessages.ShellPropertyWindows7); + } + + GetImageReference(); + var index = (imageReferenceIconIndex.HasValue ? imageReferenceIconIndex.Value : -1); + + return new IconReference(imageReferencePath, index); + } + } + + /// Gets the property key identifying this property. + public PropertyKey PropertyKey => propertyKey; + + /// + /// Gets or sets the strongly-typed value of this property. The value of the property is cleared if the value is set to null. + /// + /// + /// If the property value cannot be retrieved or updated in the Property System + /// + /// If the type of this property is not supported; e.g. writing a binary object. + /// + /// Thrown if is false, and either a string value was truncated or a numeric value was rounded. + /// + public T Value + { + get + { + // Make sure we load the correct type + Debug.Assert(ValueType == ShellPropertyFactory.VarEnumToSystemType(Description.VarEnumType)); + + using (var propVar = new PropVariant()) + { + if (ParentShellObject.NativePropertyStore != null) + { + // If there is a valid property store for this shell object, then use it. + ParentShellObject.NativePropertyStore.GetValue(ref propertyKey, propVar); + } + else if (ParentShellObject != null) + { + // Use IShellItem2.GetProperty instead of creating a new property store The file might be locked. This is probably + // quicker, and sufficient for what we need + ParentShellObject.NativeShellItem2.GetProperty(ref propertyKey, propVar); + } + else if (NativePropertyStore != null) + { + NativePropertyStore.GetValue(ref propertyKey, propVar); + } + + //Get the value + return propVar.Value != null ? (T)propVar.Value : default(T); + } + } + set + { + // Make sure we use the correct type + Debug.Assert(ValueType == ShellPropertyFactory.VarEnumToSystemType(Description.VarEnumType)); + + if (typeof(T) != ValueType) + { + throw new NotSupportedException( + string.Format(System.Globalization.CultureInfo.InvariantCulture, + LocalizedMessages.ShellPropertyWrongType, ValueType.Name)); + } + + if (value is Nullable) + { + var t = typeof(T); + var pi = t.GetProperty("HasValue"); + if (pi != null) + { + var hasValue = (bool)pi.GetValue(value, null); + if (!hasValue) + { + ClearValue(); + return; + } + } + } + else if (value == null) + { + ClearValue(); + return; + } + + if (ParentShellObject != null) + { + using (var propertyWriter = ParentShellObject.Properties.GetPropertyWriter()) + { + propertyWriter.WriteProperty(this, value, AllowSetTruncatedValue); + } + } + else if (NativePropertyStore != null) + { + throw new InvalidOperationException(LocalizedMessages.ShellPropertyCannotSetProperty); + } + } + } + + /// + /// Gets the value for this property using the generic Object type. To obtain a specific type for this value, use the more type + /// strong Property<T> class. Also, you can only set a value for this type using Property<T> + /// + public object ValueAsObject + { + get + { + using (var propVar = new PropVariant()) + { + if (ParentShellObject != null) + { + var store = ShellPropertyCollection.CreateDefaultPropertyStore(ParentShellObject); + + store.GetValue(ref propertyKey, propVar); + + Marshal.ReleaseComObject(store); + store = null; + } + else if (NativePropertyStore != null) + { + NativePropertyStore.GetValue(ref propertyKey, propVar); + } + + return propVar != null ? propVar.Value : null; + } + } + } + + /// Gets the associated runtime type. + public Type ValueType + { + get + { + // The type for this object need to match that of the description + Debug.Assert(Description.ValueType == typeof(T)); + + return Description.ValueType; + } + } + + /// Gets the case-sensitive name of a property as it is known to the system, regardless of its localized name. + public string CanonicalName => Description.CanonicalName; + + private IPropertyStore NativePropertyStore { get; set; } + private ShellObject ParentShellObject { get; set; } + + /// Clears the value of the property. + public void ClearValue() + { + using (var propVar = new PropVariant()) + { + StorePropVariantValue(propVar); + } + } + + /// Returns a formatted, Unicode string representation of a property value. + /// One or more of the PropertyDescriptionFormat flags that indicate the desired format. + /// The formatted value as a string, or null if this property cannot be formatted for display. + public string FormatForDisplay(PropertyDescriptionFormatOptions format) + { + if (Description == null || Description.NativePropertyDescription == null) + { + // We cannot do anything without a property description + return null; + } + + var store = ShellPropertyCollection.CreateDefaultPropertyStore(ParentShellObject); + + using (var propVar = new PropVariant()) + { + store.GetValue(ref propertyKey, propVar); + + // Release the Propertystore + Marshal.ReleaseComObject(store); + store = null; + + var hr = Description.NativePropertyDescription.FormatForDisplay(propVar, ref format, out var formattedString); + + // Sometimes, the value cannot be displayed properly, such as for blobs or if we get argument exception + if (!CoreErrorHelper.Succeeded(hr)) + throw new ShellException(hr); + + return formattedString; + } + } + + /// Returns a formatted, Unicode string representation of a property value. + /// One or more of the PropertyDescriptionFormat flags that indicate the desired format. + /// The formatted value as a string, or null if this property cannot be formatted for display. + /// True if the method successfully locates the formatted string; otherwise False. + public bool TryFormatForDisplay(PropertyDescriptionFormatOptions format, out string formattedString) + { + if (Description == null || Description.NativePropertyDescription == null) + { + // We cannot do anything without a property description + formattedString = null; + return false; + } + + var store = ShellPropertyCollection.CreateDefaultPropertyStore(ParentShellObject); + + using (var propVar = new PropVariant()) + { + store.GetValue(ref propertyKey, propVar); + + // Release the Propertystore + Marshal.ReleaseComObject(store); + store = null; + + var hr = Description.NativePropertyDescription.FormatForDisplay(propVar, ref format, out formattedString); + + // Sometimes, the value cannot be displayed properly, such as for blobs or if we get argument exception + if (!CoreErrorHelper.Succeeded(hr)) + { + formattedString = null; + return false; + } + return true; + } + } + + private void GetImageReference() + { + var store = ShellPropertyCollection.CreateDefaultPropertyStore(ParentShellObject); + + using (var propVar = new PropVariant()) + { + store.GetValue(ref propertyKey, propVar); + + Marshal.ReleaseComObject(store); + store = null; + + ((IPropertyDescription2)Description.NativePropertyDescription).GetImageReferenceForValue( + propVar, out var refPath); + + if (refPath == null) { return; } + + var index = ShellNativeMethods.PathParseIconLocation(ref refPath); + if (refPath != null) + { + imageReferencePath = refPath; + imageReferenceIconIndex = index; + } + } + } + + private void StorePropVariantValue(PropVariant propVar) + { + var guid = new Guid(ShellIIDGuid.IPropertyStore); + IPropertyStore writablePropStore = null; + try + { + var hr = ParentShellObject.NativeShellItem2.GetPropertyStore( + ShellNativeMethods.GetPropertyStoreOptions.ReadWrite, + ref guid, + out writablePropStore); + + if (!CoreErrorHelper.Succeeded(hr)) + { + throw new PropertySystemException(LocalizedMessages.ShellPropertyUnableToGetWritableProperty, + Marshal.GetExceptionForHR(hr)); + } + + var result = writablePropStore.SetValue(ref propertyKey, propVar); + + if (!AllowSetTruncatedValue && (int)result == ShellNativeMethods.InPlaceStringTruncated) + { + throw new ArgumentOutOfRangeException("propVar", LocalizedMessages.ShellPropertyValueTruncated); + } + + if (!CoreErrorHelper.Succeeded(result)) + { + throw new PropertySystemException(LocalizedMessages.ShellPropertySetValue, Marshal.GetExceptionForHR((int)result)); + } + + writablePropStore.Commit(); + } + catch (InvalidComObjectException e) + { + throw new PropertySystemException(LocalizedMessages.ShellPropertyUnableToGetWritableProperty, e); + } + catch (InvalidCastException) + { + throw new PropertySystemException(LocalizedMessages.ShellPropertyUnableToGetWritableProperty); + } + finally + { + if (writablePropStore != null) + { + Marshal.ReleaseComObject(writablePropStore); + writablePropStore = null; + } + } + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyCollection.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyCollection.cs new file mode 100644 index 0000000..41e6121 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyCollection.cs @@ -0,0 +1,264 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +/* Unmerged change from project 'Shell (net452)' +Before: +using System; +After: +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.WindowsAPICodePack.Internal; +using System; +*/ + +/* Unmerged change from project 'Shell (net462)' +Before: +using System; +After: +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.WindowsAPICodePack.Internal; +using System; +*/ + +/* Unmerged change from project 'Shell (net472)' +Before: +using System; +After: +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.WindowsAPICodePack.Internal; +using System; +*/ + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Collections.Generic; + +/* Unmerged change from project 'Shell (net452)' +Before: +using System.Runtime.InteropServices.ComTypes; +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.WindowsAPICodePack.Internal; +After: +using System.Runtime.InteropServices.ComTypes; +*/ + +/* Unmerged change from project 'Shell (net462)' +Before: +using System.Runtime.InteropServices.ComTypes; +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.WindowsAPICodePack.Internal; +After: +using System.Runtime.InteropServices.ComTypes; +*/ + +/* Unmerged change from project 'Shell (net472)' +Before: +using System.Runtime.InteropServices.ComTypes; +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.WindowsAPICodePack.Internal; +After: +using System.Runtime.InteropServices.ComTypes; +*/ + +using System.Collections.ObjectModel; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + /// Creates a readonly collection of IProperty objects. + public class ShellPropertyCollection : ReadOnlyCollection, IDisposable + { + /// Creates a new Property collection given an IShellItem2 native interface + /// Parent ShellObject + public ShellPropertyCollection(ShellObject parent) + : base(new List()) + { + ParentShellObject = parent; + IPropertyStore nativePropertyStore = null; + try + { + nativePropertyStore = CreateDefaultPropertyStore(ParentShellObject); + AddProperties(nativePropertyStore); + } + catch + { + if (parent != null) + { + parent.Dispose(); + } + throw; + } + finally + { + if (nativePropertyStore != null) + { + Marshal.ReleaseComObject(nativePropertyStore); + nativePropertyStore = null; + } + } + } + + /// Creates a new ShellPropertyCollection object with the specified file or folder path. + /// The path to the file or folder. + public ShellPropertyCollection(string path) : this(ShellObjectFactory.Create(path)) { } + + /// Creates a new Property collection given an IPropertyStore object + /// IPropertyStore + internal ShellPropertyCollection(IPropertyStore nativePropertyStore) + : base(new List()) + { + NativePropertyStore = nativePropertyStore; + AddProperties(nativePropertyStore); + } + + /// Implement the finalizer. + ~ShellPropertyCollection() + { + Dispose(false); + } + + private IPropertyStore NativePropertyStore { get; set; } + private ShellObject ParentShellObject { get; set; } + + /// Gets the property associated with the supplied canonical name string. The canonical name property is case-sensitive. + /// The canonical name. + /// The property associated with the canonical name, if found. + /// Throws IndexOutOfRangeException if no matching property is found. + public IShellProperty this[string canonicalName] + { + get + { + if (string.IsNullOrEmpty(canonicalName)) + { + throw new ArgumentException(LocalizedMessages.PropertyCollectionNullCanonicalName, "canonicalName"); + } + + var prop = Items.FirstOrDefault(p => p.CanonicalName == canonicalName); + if (prop == null) + { + throw new IndexOutOfRangeException(LocalizedMessages.PropertyCollectionCanonicalInvalidIndex); + } + return prop; + } + } + + /// Gets a property associated with the supplied property key. + /// The property key. + /// The property associated with the property key, if found. + /// Throws IndexOutOfRangeException if no matching property is found. + public IShellProperty this[PropertyKey key] + { + get + { + var prop = Items.FirstOrDefault(p => p.PropertyKey == key); + if (prop != null) return prop; + + throw new IndexOutOfRangeException(LocalizedMessages.PropertyCollectionInvalidIndex); + } + } + + /// Checks if a property with the given canonical name is available. + /// The canonical name of the property. + /// True if available, false otherwise. + public bool Contains(string canonicalName) + { + if (string.IsNullOrEmpty(canonicalName)) + { + throw new ArgumentException(LocalizedMessages.PropertyCollectionNullCanonicalName, "canonicalName"); + } + + return Items.Any(p => p.CanonicalName == canonicalName); + } + + public bool Contains(PropertyKey key) => Items.Any(p => p.PropertyKey == key); + + /// Release the native objects. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + internal static IPropertyStore CreateDefaultPropertyStore(ShellObject shellObj) + { + var guid = new Guid(ShellIIDGuid.IPropertyStore); + var hr = shellObj.NativeShellItem2.GetPropertyStore( + ShellNativeMethods.GetPropertyStoreOptions.BestEffort, + ref guid, + out var nativePropertyStore); + + // throw on failure + if (nativePropertyStore == null || !CoreErrorHelper.Succeeded(hr)) + { + throw new ShellException(hr); + } + + return nativePropertyStore; + } + + // TODO - ShellProperties.cs also has a similar class that is used for creating a ShellObject specific IShellProperty. These 2 + // methods should be combined or moved to a common location. + internal static IShellProperty CreateTypedProperty(PropertyKey propKey, IPropertyStore NativePropertyStore) => ShellPropertyFactory.CreateShellProperty(propKey, NativePropertyStore); + + /// Release the native and managed objects + /// Indicates that this is being called from Dispose(), rather than the finalizer. + protected virtual void Dispose(bool disposing) + { + if (NativePropertyStore != null) + { + Marshal.ReleaseComObject(NativePropertyStore); + NativePropertyStore = null; + } + } + + private void AddProperties(IPropertyStore nativePropertyStore) + { + // Populate the property collection + nativePropertyStore.GetCount(out var propertyCount); + for (uint i = 0; i < propertyCount; i++) + { + nativePropertyStore.GetAt(i, out var propKey); + + if (ParentShellObject != null) + { + Items.Add(ParentShellObject.Properties.CreateTypedProperty(propKey)); + } + else + { + Items.Add(CreateTypedProperty(propKey, NativePropertyStore)); + } + } + } + + /// Checks if a property with the given property key is available. + /// The property key. + /// True if available, false otherwise. + + /* Unmerged change from project 'Shell (net452)' + Before: + public bool Contains(PropertyKey key) + { + return Items.Any(p => p.PropertyKey == key); + After: + public bool Contains(PropertyKey key) => Items.Any(p => p.PropertyKey == key); + */ + + /* Unmerged change from project 'Shell (net462)' + Before: + public bool Contains(PropertyKey key) + { + return Items.Any(p => p.PropertyKey == key); + After: + public bool Contains(PropertyKey key) => Items.Any(p => p.PropertyKey == key); + */ + + /* Unmerged change from project 'Shell (net472)' + Before: + public bool Contains(PropertyKey key) + { + return Items.Any(p => p.PropertyKey == key); + After: + public bool Contains(PropertyKey key) => Items.Any(p => p.PropertyKey == key); + */ + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyDescription.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyDescription.cs new file mode 100644 index 0000000..2d6154e --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyDescription.cs @@ -0,0 +1,486 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +/* Unmerged change from project 'Shell (net452)' +Before: +using System; +After: +using MS.WindowsAPICodePack.Internal; +using System; +*/ + +/* Unmerged change from project 'Shell (net462)' +Before: +using System; +After: +using MS.WindowsAPICodePack.Internal; +using System; +*/ + +/* Unmerged change from project 'Shell (net472)' +Before: +using System; +After: +using MS.WindowsAPICodePack.Internal; +using System; +*/ + +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + /// Defines the shell property description information for a property. + public class ShellPropertyDescription : IDisposable + { + private PropertyAggregationType? aggregationTypes; + private string canonicalName; + private PropertyColumnStateOptions? columnState; + private PropertyConditionOperation? conditionOperation; + private PropertyConditionType? conditionType; + private uint? defaultColumWidth; + private string displayName; + private PropertyDisplayType? displayType; + private string editInvitation; + private PropertyGroupingRange? groupingRange; + private IPropertyDescription nativePropertyDescription; + private ReadOnlyCollection propertyEnumTypes; + private PropertyKey propertyKey; + private PropertyTypeOptions? propertyTypeFlags; + private PropertyViewOptions? propertyViewFlags; + private PropertySortDescription? sortDescription; + private Type valueType; + private VarEnum? varEnumType = null; + + internal ShellPropertyDescription(PropertyKey key) => propertyKey = key; + + /// Release the native objects + ~ShellPropertyDescription() + { + Dispose(false); + } + + /// + /// Gets a value that describes how the property values are displayed when multiple items are selected in the user interface (UI). + /// + public PropertyAggregationType AggregationTypes + { + get + { + if (NativePropertyDescription != null && aggregationTypes == null) + { + var hr = NativePropertyDescription.GetAggregationType(out var tempAggregationTypes); + + if (CoreErrorHelper.Succeeded(hr)) + { + aggregationTypes = tempAggregationTypes; + } + } + + return aggregationTypes.HasValue ? aggregationTypes.Value : default(PropertyAggregationType); + } + } + + /// Gets the case-sensitive name of a property as it is known to the system, regardless of its localized name. + public string CanonicalName + { + get + { + if (canonicalName == null) + { + PropertySystemNativeMethods.PSGetNameFromPropertyKey(ref propertyKey, out canonicalName); + } + + return canonicalName; + } + } + + /// + /// Gets the column state flag, which describes how the property should be treated by interfaces or APIs that use this flag. + /// + public PropertyColumnStateOptions ColumnState + { + get + { + // If default/first value, try to get it again, otherwise used the cached one. + if (NativePropertyDescription != null && columnState == null) + { + var hr = NativePropertyDescription.GetColumnState(out var state); + + if (CoreErrorHelper.Succeeded(hr)) + { + columnState = state; + } + } + + return columnState.HasValue ? columnState.Value : default(PropertyColumnStateOptions); + } + } + + /// + /// Gets the default condition operation to use when displaying the property in the query builder user interface (UI). This + /// influences the list of predicate conditions (for example, equals, less than, and contains) that are shown for this property. + /// + /// + /// For more information, see the conditionType attribute of the typeInfo element in the property's .propdesc file. + /// + public PropertyConditionOperation ConditionOperation + { + get + { + // If default/first value, try to get it again, otherwise used the cached one. + if (NativePropertyDescription != null && conditionOperation == null) + { + var hr = NativePropertyDescription.GetConditionType(out var tempConditionType, out var tempConditionOperation); + + if (CoreErrorHelper.Succeeded(hr)) + { + conditionOperation = tempConditionOperation; + conditionType = tempConditionType; + } + } + + return conditionOperation.HasValue ? conditionOperation.Value : default(PropertyConditionOperation); + } + } + + /// + /// Gets the condition type to use when displaying the property in the query builder user interface (UI). This influences the list of + /// predicate conditions (for example, equals, less than, and + /// contains) that are shown for this property. + /// + /// + /// For more information, see the conditionType attribute of the typeInfo element in the property's .propdesc file. + /// + public PropertyConditionType ConditionType + { + get + { + // If default/first value, try to get it again, otherwise used the cached one. + if (NativePropertyDescription != null && conditionType == null) + { + var hr = NativePropertyDescription.GetConditionType(out var tempConditionType, out var tempConditionOperation); + + if (CoreErrorHelper.Succeeded(hr)) + { + conditionOperation = tempConditionOperation; + conditionType = tempConditionType; + } + } + + return conditionType.HasValue ? conditionType.Value : default(PropertyConditionType); + } + } + + /// Gets the default user interface (UI) column width for this property. + public uint DefaultColumWidth + { + get + { + if (NativePropertyDescription != null && !defaultColumWidth.HasValue) + { + var hr = NativePropertyDescription.GetDefaultColumnWidth(out var tempDefaultColumWidth); + + if (CoreErrorHelper.Succeeded(hr)) + { + defaultColumWidth = tempDefaultColumWidth; + } + } + + return defaultColumWidth.HasValue ? defaultColumWidth.Value : default(uint); + } + } + + /// Gets the display name of the property as it is shown in any user interface (UI). + public string DisplayName + { + get + { + if (NativePropertyDescription != null && displayName == null) + { + var dispNameptr = IntPtr.Zero; + + var hr = NativePropertyDescription.GetDisplayName(out dispNameptr); + + if (CoreErrorHelper.Succeeded(hr) && dispNameptr != IntPtr.Zero) + { + displayName = Marshal.PtrToStringUni(dispNameptr); + + // Free the string + Marshal.FreeCoTaskMem(dispNameptr); + } + } + + return displayName; + } + } + + /// Gets the current data type used to display the property. + public PropertyDisplayType DisplayType + { + get + { + if (NativePropertyDescription != null && displayType == null) + { + var hr = NativePropertyDescription.GetDisplayType(out var tempDisplayType); + + if (CoreErrorHelper.Succeeded(hr)) + { + displayType = tempDisplayType; + } + } + + return displayType.HasValue ? displayType.Value : default(PropertyDisplayType); + } + } + + /// Gets the text used in edit controls hosted in various dialog boxes. + public string EditInvitation + { + get + { + if (NativePropertyDescription != null && editInvitation == null) + { + // EditInvitation can be empty, so ignore the HR value, but don't throw an exception + var ptr = IntPtr.Zero; + + var hr = NativePropertyDescription.GetEditInvitation(out ptr); + + if (CoreErrorHelper.Succeeded(hr) && ptr != IntPtr.Zero) + { + editInvitation = Marshal.PtrToStringUni(ptr); + // Free the string + Marshal.FreeCoTaskMem(ptr); + } + } + + return editInvitation; + } + } + + /// Gets the method used when a view is grouped by this property. + /// + /// The information retrieved by this method comes from the groupingRange attribute of the typeInfo element in the + /// property's .propdesc file. + /// + public PropertyGroupingRange GroupingRange + { + get + { + // If default/first value, try to get it again, otherwise used the cached one. + if (NativePropertyDescription != null && groupingRange == null) + { + var hr = NativePropertyDescription.GetGroupingRange(out var tempGroupingRange); + + if (CoreErrorHelper.Succeeded(hr)) + { + groupingRange = tempGroupingRange; + } + } + + return groupingRange.HasValue ? groupingRange.Value : default(PropertyGroupingRange); + } + } + + /// Gets a value that determines if the native property description is present on the system. + public bool HasSystemDescription => NativePropertyDescription != null; + + /// Gets a list of the possible values for this property. + public ReadOnlyCollection PropertyEnumTypes + { + get + { + if (NativePropertyDescription != null && propertyEnumTypes == null) + { + var propEnumTypeList = new List(); + + var guid = new Guid(ShellIIDGuid.IPropertyEnumTypeList); + var hr = NativePropertyDescription.GetEnumTypeList(ref guid, out var nativeList); + + if (nativeList != null && CoreErrorHelper.Succeeded(hr)) + { + nativeList.GetCount(out var count); + guid = new Guid(ShellIIDGuid.IPropertyEnumType); + + for (uint i = 0; i < count; i++) + { + nativeList.GetAt(i, ref guid, out var nativeEnumType); + propEnumTypeList.Add(new ShellPropertyEnumType(nativeEnumType)); + } + } + + propertyEnumTypes = new ReadOnlyCollection(propEnumTypeList); + } + + return propertyEnumTypes; + } + } + + /// Gets the property key identifying the underlying property. + public PropertyKey PropertyKey => propertyKey; + + /// Gets the current sort description flags for the property, which indicate the particular wordings of sort offerings. + /// + /// The settings retrieved by this method are set through the sortDescription attribute of the labelInfo element in the + /// property's .propdesc file. + /// + public PropertySortDescription SortDescription + { + get + { + // If default/first value, try to get it again, otherwise used the cached one. + if (NativePropertyDescription != null && sortDescription == null) + { + var hr = NativePropertyDescription.GetSortDescription(out var tempSortDescription); + + if (CoreErrorHelper.Succeeded(hr)) + { + sortDescription = tempSortDescription; + } + } + + return sortDescription.HasValue ? sortDescription.Value : default(PropertySortDescription); + } + } + + /// Gets a set of flags that describe the uses and capabilities of the property. + public PropertyTypeOptions TypeFlags + { + get + { + if (NativePropertyDescription != null && propertyTypeFlags == null) + { + var hr = NativePropertyDescription.GetTypeFlags(PropertyTypeOptions.MaskAll, out var tempFlags); + + propertyTypeFlags = CoreErrorHelper.Succeeded(hr) ? tempFlags : default(PropertyTypeOptions); + } + + return propertyTypeFlags.HasValue ? propertyTypeFlags.Value : default(PropertyTypeOptions); + } + } + + /// Gets the .NET system type for a value of this property, or null if the value is empty. + public Type ValueType + { + get + { + if (valueType == null) + { + valueType = ShellPropertyFactory.VarEnumToSystemType(VarEnumType); + } + + return valueType; + } + } + + /// Gets the VarEnum OLE type for this property. + public VarEnum VarEnumType + { + get + { + if (NativePropertyDescription != null && varEnumType == null) + { + var hr = NativePropertyDescription.GetPropertyType(out var tempType); + + if (CoreErrorHelper.Succeeded(hr)) + { + varEnumType = tempType; + } + } + + return varEnumType.HasValue ? varEnumType.Value : default(VarEnum); + } + } + + /// Gets the current set of flags governing the property's view. + public PropertyViewOptions ViewFlags + { + get + { + if (NativePropertyDescription != null && propertyViewFlags == null) + { + var hr = NativePropertyDescription.GetViewFlags(out var tempFlags); + + propertyViewFlags = CoreErrorHelper.Succeeded(hr) ? tempFlags : default(PropertyViewOptions); + } + + return propertyViewFlags.HasValue ? propertyViewFlags.Value : default(PropertyViewOptions); + } + } + + /// Get the native property description COM interface + internal IPropertyDescription NativePropertyDescription + { + get + { + if (nativePropertyDescription == null) + { + var guid = new Guid(ShellIIDGuid.IPropertyDescription); + PropertySystemNativeMethods.PSGetPropertyDescription(ref propertyKey, ref guid, out nativePropertyDescription); + } + + return nativePropertyDescription; + } + } + + /// Release the native objects + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// Gets the localized display string that describes the current sort order. + /// + /// Indicates the sort order should reference the string "Z on top"; otherwise, the sort order should reference the string "A on top". + /// + /// The sort description for this property. + /// + /// The string retrieved by this method is determined by flags set in the sortDescription attribute of the labelInfo + /// element in the property's .propdesc file. + /// + public string GetSortDescriptionLabel(bool descending) + { + var ptr = IntPtr.Zero; + var label = string.Empty; + + if (NativePropertyDescription != null) + { + var hr = NativePropertyDescription.GetSortDescriptionLabel(descending, out ptr); + + if (CoreErrorHelper.Succeeded(hr) && ptr != IntPtr.Zero) + { + label = Marshal.PtrToStringUni(ptr); + // Free the string + Marshal.FreeCoTaskMem(ptr); + } + } + + return label; + } + + /// Release the native objects + /// Indicates that this is being called from Dispose(), rather than the finalizer. + protected virtual void Dispose(bool disposing) + { + if (nativePropertyDescription != null) + { + Marshal.ReleaseComObject(nativePropertyDescription); + nativePropertyDescription = null; + } + + if (disposing) + { + // and the managed ones + canonicalName = null; + displayName = null; + editInvitation = null; + defaultColumWidth = null; + valueType = null; + propertyEnumTypes = null; + } + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyDescriptionsCache.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyDescriptionsCache.cs new file mode 100644 index 0000000..2de7da9 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyDescriptionsCache.cs @@ -0,0 +1,36 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System.Collections.Generic; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + internal class ShellPropertyDescriptionsCache + { + private static ShellPropertyDescriptionsCache cacheInstance; + + private readonly IDictionary propsDictionary; + + private ShellPropertyDescriptionsCache() => propsDictionary = new Dictionary(); + + public static ShellPropertyDescriptionsCache Cache + { + get + { + if (cacheInstance == null) + { + cacheInstance = new ShellPropertyDescriptionsCache(); + } + return cacheInstance; + } + } + + public ShellPropertyDescription GetPropertyDescription(PropertyKey key) + { + if (!propsDictionary.ContainsKey(key)) + { + propsDictionary.Add(key, new ShellPropertyDescription(key)); + } + return propsDictionary[key]; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyEnumType.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyEnumType.cs new file mode 100644 index 0000000..7d9dbc8 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyEnumType.cs @@ -0,0 +1,100 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + /// Defines the enumeration values for a property type. + public class ShellPropertyEnumType + { + private string displayText; + private PropEnumType? enumType; + private object minValue, setValue, enumerationValue; + + internal ShellPropertyEnumType(IPropertyEnumType nativePropertyEnumType) => NativePropertyEnumType = nativePropertyEnumType; + + /// Gets display text from an enumeration information structure. + public string DisplayText + { + get + { + if (displayText == null) + { + NativePropertyEnumType.GetDisplayText(out displayText); + } + return displayText; + } + } + + /// Gets an enumeration type from an enumeration information structure. + public PropEnumType EnumType + { + get + { + if (!enumType.HasValue) + { + NativePropertyEnumType.GetEnumType(out var tempEnumType); + enumType = tempEnumType; + } + return enumType.Value; + } + } + + /// Gets a minimum value from an enumeration information structure. + public object RangeMinValue + { + get + { + if (minValue == null) + { + using (var propVar = new PropVariant()) + { + NativePropertyEnumType.GetRangeMinValue(propVar); + minValue = propVar.Value; + } + } + return minValue; + } + } + + /// Gets a set value from an enumeration information structure. + public object RangeSetValue + { + get + { + if (setValue == null) + { + using (var propVar = new PropVariant()) + { + NativePropertyEnumType.GetRangeSetValue(propVar); + setValue = propVar.Value; + } + } + return setValue; + } + } + + /// Gets a value from an enumeration information structure. + public object RangeValue + { + get + { + if (enumerationValue == null) + { + using (var propVar = new PropVariant()) + { + NativePropertyEnumType.GetValue(propVar); + enumerationValue = propVar.Value; + } + } + return enumerationValue; + } + } + + private IPropertyEnumType NativePropertyEnumType + { + set; + get; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyEnums.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyEnums.cs new file mode 100644 index 0000000..6be20e3 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyEnums.cs @@ -0,0 +1,463 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + /// Property Enumeration Types + public enum PropEnumType + { + /// Use DisplayText and either RangeMinValue or RangeSetValue. + DiscreteValue = 0, + + /// Use DisplayText and either RangeMinValue or RangeSetValue + RangedValue = 1, + + /// Use DisplayText + DefaultValue = 2, + + /// Use Value or RangeMinValue + EndRange = 3 + }; + + /// Property Aggregation Type + public enum PropertyAggregationType + { + /// The string "Multiple Values" is displayed. + Default = 0, + + /// The first value in the selection is displayed. + First = 1, + + /// + /// The sum of the selected values is displayed. This flag is never returned for data types VT_LPWSTR, VT_BOOL, and VT_FILETIME. + /// + Sum = 2, + + /// + /// The numerical average of the selected values is displayed. This flag is never returned for data types VT_LPWSTR, VT_BOOL, and VT_FILETIME. + /// + Average = 3, + + /// + /// The date range of the selected values is displayed. This flag is only returned for values of the VT_FILETIME data type. + /// + DateRange = 4, + + /// + /// A concatenated string of all the values is displayed. The order of individual values in the string is undefined. The concatenated + /// string omits duplicate values; if a value occurs more than once, it only appears a single time in the concatenated string. + /// + Union = 5, + + /// The highest of the selected values is displayed. + Max = 6, + + /// The lowest of the selected values is displayed. + Min = 7 + } + + /// Describes how a property should be treated for display purposes. + [Flags] + public enum PropertyColumnStateOptions + { + /// Default value + None = 0x00000000, + + /// The value is displayed as a string. + StringType = 0x00000001, + + /// The value is displayed as an integer. + IntegerType = 0x00000002, + + /// The value is displayed as a date/time. + DateType = 0x00000003, + + /// A mask for display type values StringType, IntegerType, and DateType. + TypeMask = 0x0000000f, + + /// The column should be on by default in Details view. + OnByDefault = 0x00000010, + + /// Will be slow to compute. Perform on a background thread. + Slow = 0x00000020, + + /// Provided by a handler, not the folder. + Extended = 0x00000040, + + /// Not displayed in the context menu, but is listed in the More... dialog. + SecondaryUI = 0x00000080, + + /// Not displayed in the user interface (UI). + Hidden = 0x00000100, + + /// VarCmp produces same result as IShellFolder::CompareIDs. + PreferVariantCompare = 0x00000200, + + /// PSFormatForDisplay produces same result as IShellFolder::CompareIDs. + PreferFormatForDisplay = 0x00000400, + + /// Do not sort folders separately. + NoSortByFolders = 0x00000800, + + /// Only displayed in the UI. + ViewOnly = 0x00010000, + + /// Marks columns with values that should be read in a batch. + BatchRead = 0x00020000, + + /// Grouping is disabled for this column. + NoGroupBy = 0x00040000, + + /// Can't resize the column. + FixedWidth = 0x00001000, + + /// The width is the same in all dots per inch (dpi)s. + NoDpiScale = 0x00002000, + + /// Fixed width and height ratio. + FixedRatio = 0x00004000, + + /// Filters out new display flags. + DisplayMask = 0x0000F000, + } + + /// Provides a set of flags to be used with IConditionFactory, ICondition, and IConditionGenerator to indicate the operation. + public enum PropertyConditionOperation + { + /// The implicit comparison between the value of the property and the value of the constant. + Implicit, + + /// The value of the property and the value of the constant must be equal. + Equal, + + /// The value of the property and the value of the constant must not be equal. + NotEqual, + + /// The value of the property must be less than the value of the constant. + LessThan, + + /// The value of the property must be greater than the value of the constant. + GreaterThan, + + /// The value of the property must be less than or equal to the value of the constant. + LessThanOrEqual, + + /// The value of the property must be greater than or equal to the value of the constant. + GreaterThanOrEqual, + + /// The value of the property must begin with the value of the constant. + ValueStartsWith, + + /// The value of the property must end with the value of the constant. + ValueEndsWith, + + /// The value of the property must contain the value of the constant. + ValueContains, + + /// The value of the property must not contain the value of the constant. + ValueNotContains, + + /// + /// The value of the property must match the value of the constant, where '?' matches any single character and '*' matches any + /// sequence of characters. + /// + DOSWildCards, + + /// The value of the property must contain a word that is the value of the constant. + WordEqual, + + /// The value of the property must contain a word that begins with the value of the constant. + WordStartsWith, + + /// The application is free to interpret this in any suitable way. + ApplicationSpecific, + } + + /// Specifies the condition type to use when displaying the property in the query builder user interface (UI). + public enum PropertyConditionType + { + /// The default condition type. + None = 0, + + /// The string type. + String = 1, + + /// The size type. + Size = 2, + + /// The date/time type. + DateTime = 3, + + /// The Boolean type. + Boolean = 4, + + /// The number type. + Number = 5, + } + + /// Delineates the format of a property string. + /// + /// Typically use one, or a bitwise combination of these flags, to specify the format. Some flags are mutually exclusive, so combinations + /// like ShortTime | LongTime | HideTime are not allowed. + /// + [Flags] + public enum PropertyDescriptionFormatOptions + { + /// The format settings specified in the property's .propdesc file. + None = 0, + + /// The value preceded with the property's display name. + /// + /// This flag is ignored when the hideLabelPrefix attribute of the labelInfo element in the property's .propinfo file + /// is set to true. + /// + PrefixName = 0x1, + + /// The string treated as a file name. + FileName = 0x2, + + /// The sizes displayed in kilobytes (KB), regardless of size. + /// This flag applies to properties of Integer types and aligns the values in the column. + AlwaysKB = 0x4, + + /// Reserved. + RightToLeft = 0x8, + + /// The time displayed as 'hh:mm am/pm'. + ShortTime = 0x10, + + /// The time displayed as 'hh:mm:ss am/pm'. + LongTime = 0x20, + + /// The time portion of date/time hidden. + HideTime = 64, + + /// The date displayed as 'MM/DD/YY'. For example, '3/21/04'. + ShortDate = 0x80, + + /// The date displayed as 'DayOfWeek Month day, year'. For example, 'Monday, March 21, 2004'. + LongDate = 0x100, + + /// The date portion of date/time hidden. + HideDate = 0x200, + + /// The friendly date descriptions, such as "Yesterday". + RelativeDate = 0x400, + + /// The text displayed in a text box as a cue for the user, such as 'Enter your name'. + /// + /// The invitation text is returned if formatting failed or the value was empty. Invitation text is text displayed in a text box as a + /// cue for the user, Formatting can fail if the data entered is not of an expected type, such as putting alpha characters in a phone + /// number field. + /// + UseEditInvitation = 0x800, + + /// + /// This flag requires UseEditInvitation to also be specified. When the formatting flags are ReadOnly | UseEditInvitation and the + /// algorithm would have shown invitation text, a string is returned that indicates the value is "Unknown" instead of the invitation text. + /// + ReadOnly = 0x1000, + + /// + /// The detection of the reading order is not automatic. Useful when converting to ANSI to omit the Unicode reading order characters. + /// + NoAutoReadingOrder = 0x2000, + + /// Smart display of DateTime values + SmartDateTime = 0x4000 + } + + /// Specifies the display types for a property. + public enum PropertyDisplayType + { + /// The String Display. This is the default if the property doesn't specify a display type. + String = 0, + + /// The Number Display. + Number = 1, + + /// The Boolean Display. + Boolean = 2, + + /// The DateTime Display. + DateTime = 3, + + /// The Enumerated Display. + Enumerated = 4 + } + + /// Specifies the property description grouping ranges. + public enum PropertyGroupingRange + { + /// The individual values. + Discrete = 0, + + /// The static alphanumeric ranges. + Alphanumeric = 1, + + /// The static size ranges. + Size = 2, + + /// The dynamically-created ranges. + Dynamic = 3, + + /// The month and year groups. + Date = 4, + + /// The percent groups. + Percent = 5, + + /// The enumerated groups. + Enumerated = 6, + } + + /// Describes the particular wordings of sort offerings. + /// Note that the strings shown are English versions only; localized strings are used for other locales. + public enum PropertySortDescription + { + /// The default ascending or descending property sort, "Sort going up", "Sort going down". + General, + + /// The alphabetical sort, "A on top", "Z on top". + AToZ, + + /// The numerical sort, "Lowest on top", "Highest on top". + LowestToHighest, + + /// The size sort, "Smallest on top", "Largest on top". + SmallestToBiggest, + + /// The chronological sort, "Oldest on top", "Newest on top". + OldestToNewest, + } + + /// Property store cache state + public enum PropertyStoreCacheState + { + /// Contained in file, not updated. + Normal = 0, + + /// Not contained in file. + NotInSource = 1, + + /// Contained in file, has been updated since file was consumed. + Dirty = 2 + } + + /// Describes the attributes of the typeInfo element in the property's .propdesc file. + [Flags] + public enum PropertyTypeOptions + { + /// The property uses the default values for all attributes. + None = 0x00000000, + + /// The property can have multiple values. + /// + /// These values are stored as a VT_VECTOR in the PROPVARIANT structure. This value is set by the multipleValues attribute of the + /// typeInfo element in the property's .propdesc file. + /// + MultipleValues = 0x00000001, + + /// This property cannot be written to. + /// This value is set by the isInnate attribute of the typeInfo element in the property's .propdesc file. + IsInnate = 0x00000002, + + /// The property is a group heading. + /// This value is set by the isGroup attribute of the typeInfo element in the property's .propdesc file. + IsGroup = 0x00000004, + + /// The user can group by this property. + /// This value is set by the canGroupBy attribute of the typeInfo element in the property's .propdesc file. + CanGroupBy = 0x00000008, + + /// The user can stack by this property. + /// This value is set by the canStackBy attribute of the typeInfo element in the property's .propdesc file. + CanStackBy = 0x00000010, + + /// This property contains a hierarchy. + /// This value is set by the isTreeProperty attribute of the typeInfo element in the property's .propdesc file. + IsTreeProperty = 0x00000020, + + /// Include this property in any full text query that is performed. + /// This value is set by the includeInFullTextQuery attribute of the typeInfo element in the property's .propdesc file. + IncludeInFullTextQuery = 0x00000040, + + /// This property is meant to be viewed by the user. + /// + /// This influences whether the property shows up in the "Choose Columns" dialog, for example. This value is set by the isViewable + /// attribute of the typeInfo element in the property's .propdesc file. + /// + IsViewable = 0x00000080, + + /// This property is included in the list of properties that can be queried. + /// + /// A queryable property must also be viewable. This influences whether the property shows up in the query builder UI. This value is + /// set by the isQueryable attribute of the typeInfo element in the property's .propdesc file. + /// + IsQueryable = 0x00000100, + + /// + /// Used with an innate property (that is, a value calculated from other property values) to indicate that it can be deleted. + /// + /// + /// Windows Vista with Service Pack 1 (SP1) and later. This value is used by the Remove Properties user interface (UI) to determine + /// whether to display a check box next to an property that allows that property to be selected for removal. Note that a property + /// that is not innate can always be purged regardless of the presence or absence of this flag. + /// + CanBePurged = 0x00000200, + + /// This property is owned by the system. + IsSystemProperty = unchecked((int)0x80000000), + + /// A mask used to retrieve all flags. + MaskAll = unchecked((int)0x800001FF), + } + + /// Associates property names with property description list strings. + [Flags] + public enum PropertyViewOptions + { + /// The property is shown by default. + None = 0x00000000, + + /// The property is centered. + CenterAlign = 0x00000001, + + /// The property is right aligned. + RightAlign = 0x00000002, + + /// The property is shown as the beginning of the next collection of properties in the view. + BeginNewGroup = 0x00000004, + + /// The remainder of the view area is filled with the content of this property. + FillArea = 0x00000008, + + /// The property is reverse sorted if it is a property in a list of sorted properties. + SortDescending = 0x00000010, + + /// The property is only shown if it is present. + ShowOnlyIfPresent = 0x00000020, + + /// The property is shown by default in a view (where applicable). + ShowByDefault = 0x00000040, + + /// The property is shown by default in primary column selection user interface (UI). + ShowInPrimaryList = 0x00000080, + + /// The property is shown by default in secondary column selection UI. + ShowInSecondaryList = 0x00000100, + + /// The label is hidden if the view is normally inclined to show the label. + HideLabel = 0x00000200, + + /// The property is not displayed as a column in the UI. + Hidden = 0x00000800, + + /// The property is wrapped to the next row. + CanWrap = 0x00001000, + + /// A mask used to retrieve all flags. + MaskAll = 0x000003ff, + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyFactory.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyFactory.cs new file mode 100644 index 0000000..c9f6963 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyFactory.cs @@ -0,0 +1,195 @@ +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + /// Factory class for creating typed ShellProperties. Generates/caches expressions to create generic ShellProperties. + internal static class ShellPropertyFactory + { + // Constructor cache. It takes object as the third param so a single function will suffice for both constructors. + private static readonly Dictionary> _storeCache + = new Dictionary>(); + + /// Creates a generic ShellProperty. + /// PropertyKey + /// Shell object from which to get property + /// ShellProperty matching type of value in property. + public static IShellProperty CreateShellProperty(PropertyKey propKey, ShellObject shellObject) => GenericCreateShellProperty(propKey, shellObject); + + /// Creates a generic ShellProperty. + /// PropertyKey + /// IPropertyStore from which to get property + /// ShellProperty matching type of value in property. + public static IShellProperty CreateShellProperty(PropertyKey propKey, IPropertyStore store) => GenericCreateShellProperty(propKey, store); + + /// Converts VarEnum to its associated .net Type. + /// VarEnum value + /// Associated .net equivelent. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + public static Type VarEnumToSystemType(VarEnum VarEnumType) + { + switch (VarEnumType) + { + case (VarEnum.VT_EMPTY): + case (VarEnum.VT_NULL): + return typeof(object); + + case (VarEnum.VT_UI1): + return typeof(byte?); + + case (VarEnum.VT_I2): + return typeof(short?); + + case (VarEnum.VT_UI2): + return typeof(ushort?); + + case (VarEnum.VT_I4): + return typeof(int?); + + case (VarEnum.VT_UI4): + return typeof(uint?); + + case (VarEnum.VT_I8): + return typeof(long?); + + case (VarEnum.VT_UI8): + return typeof(ulong?); + + case (VarEnum.VT_R8): + return typeof(double?); + + case (VarEnum.VT_BOOL): + return typeof(bool?); + + case (VarEnum.VT_FILETIME): + return typeof(DateTime?); + + case (VarEnum.VT_CLSID): + return typeof(IntPtr?); + + case (VarEnum.VT_CF): + return typeof(IntPtr?); + + case (VarEnum.VT_BLOB): + return typeof(byte[]); + + case (VarEnum.VT_LPWSTR): + return typeof(string); + + case (VarEnum.VT_UNKNOWN): + return typeof(IntPtr?); + + case (VarEnum.VT_STREAM): + return typeof(IStream); + + case (VarEnum.VT_VECTOR | VarEnum.VT_UI1): + return typeof(byte[]); + + case (VarEnum.VT_VECTOR | VarEnum.VT_I2): + return typeof(short[]); + + case (VarEnum.VT_VECTOR | VarEnum.VT_UI2): + return typeof(ushort[]); + + case (VarEnum.VT_VECTOR | VarEnum.VT_I4): + return typeof(int[]); + + case (VarEnum.VT_VECTOR | VarEnum.VT_UI4): + return typeof(uint[]); + + case (VarEnum.VT_VECTOR | VarEnum.VT_I8): + return typeof(long[]); + + case (VarEnum.VT_VECTOR | VarEnum.VT_UI8): + return typeof(ulong[]); + + case (VarEnum.VT_VECTOR | VarEnum.VT_R8): + return typeof(double[]); + + case (VarEnum.VT_VECTOR | VarEnum.VT_BOOL): + return typeof(bool[]); + + case (VarEnum.VT_VECTOR | VarEnum.VT_FILETIME): + return typeof(DateTime[]); + + case (VarEnum.VT_VECTOR | VarEnum.VT_CLSID): + return typeof(IntPtr[]); + + case (VarEnum.VT_VECTOR | VarEnum.VT_CF): + return typeof(IntPtr[]); + + case (VarEnum.VT_VECTOR | VarEnum.VT_LPWSTR): + return typeof(string[]); + + default: + return typeof(object); + } + } + + // Creates an expression for the specific constructor of the given type. + private static Func ExpressConstructor(Type type, Type[] argTypes) + { + var typeHash = GetTypeHash(argTypes); + + // Finds the correct constructor by matching the hash of the types. + var ctorInfo = type.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) + .FirstOrDefault(x => typeHash == GetTypeHash(x.GetParameters().Select(a => a.ParameterType))); + + if (ctorInfo == null) + { + throw new ArgumentException(LocalizedMessages.ShellPropertyFactoryConstructorNotFound, "type"); + } + + var key = Expression.Parameter(argTypes[0], "propKey"); + var desc = Expression.Parameter(argTypes[1], "desc"); + var third = Expression.Parameter(typeof(object), "third"); //needs to be object to avoid casting later + + var create = Expression.New(ctorInfo, key, desc, + Expression.Convert(third, argTypes[2])); + + return Expression.Lambda>( + create, key, desc, third).Compile(); + } + + private static IShellProperty GenericCreateShellProperty(PropertyKey propKey, T thirdArg) + { + var thirdType = (thirdArg is ShellObject) ? typeof(ShellObject) : typeof(T); + + var propDesc = ShellPropertyDescriptionsCache.Cache.GetPropertyDescription(propKey); + + // Get the generic type + var type = typeof(ShellProperty<>).MakeGenericType(VarEnumToSystemType(propDesc.VarEnumType)); + + // The hash for the function is based off the generic type and which type (constructor) we're using. + var hash = GetTypeHash(type, thirdType); + + if (!_storeCache.TryGetValue(hash, out var ctor)) + { + Type[] argTypes = { typeof(PropertyKey), typeof(ShellPropertyDescription), thirdType }; + ctor = ExpressConstructor(type, argTypes); + _storeCache.Add(hash, ctor); + } + + return ctor(propKey, propDesc, thirdArg); + } + + private static int GetTypeHash(params Type[] types) => GetTypeHash((IEnumerable)types); + + // Creates a hash code, unique to the number and order of types. + private static int GetTypeHash(IEnumerable types) + { + var hash = 0; + foreach (var type in types) + { + hash = hash * 31 + type.GetHashCode(); + } + return hash; + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyWriter.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyWriter.cs new file mode 100644 index 0000000..fbcddca --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/ShellPropertyWriter.cs @@ -0,0 +1,203 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + /// Creates a property writer capable of setting multiple properties for a given ShellObject. + public class ShellPropertyWriter : IDisposable + { + // Reference to our writable PropertyStore + internal IPropertyStore writablePropStore; + + private ShellObject parentShellObject; + + internal ShellPropertyWriter(ShellObject parent) + { + ParentShellObject = parent; + + // Open the property store for this shell object... + var guid = new Guid(ShellIIDGuid.IPropertyStore); + + try + { + var hr = ParentShellObject.NativeShellItem2.GetPropertyStore( + ShellNativeMethods.GetPropertyStoreOptions.ReadWrite, + ref guid, + out writablePropStore); + + if (!CoreErrorHelper.Succeeded(hr)) + { + throw new PropertySystemException(LocalizedMessages.ShellPropertyUnableToGetWritableProperty, + Marshal.GetExceptionForHR(hr)); + } + else + { + // If we succeed in creating a valid property store for this ShellObject, then set it on the parent shell object for + // others to use. Once this writer is closed/commited, we will set the + if (ParentShellObject.NativePropertyStore == null) + { + ParentShellObject.NativePropertyStore = writablePropStore; + } + } + } + catch (InvalidComObjectException e) + { + throw new PropertySystemException(LocalizedMessages.ShellPropertyUnableToGetWritableProperty, e); + } + catch (InvalidCastException) + { + throw new PropertySystemException(LocalizedMessages.ShellPropertyUnableToGetWritableProperty); + } + } + + /// + ~ShellPropertyWriter() + { + Dispose(false); + } + + /// Reference to parent ShellObject (associated with this writer) + protected ShellObject ParentShellObject + { + get => parentShellObject; + private set => parentShellObject = value; + } + + /// Call this method to commit the writes (calls to WriteProperty method) and dispose off the writer. + public void Close() + { + // Close the property writer (commit, etc) + if (writablePropStore != null) + { + writablePropStore.Commit(); + + Marshal.ReleaseComObject(writablePropStore); + writablePropStore = null; + } + + ParentShellObject.NativePropertyStore = null; + } + + /// Release the native objects. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// Writes the given property key and value. + /// The property key. + /// The value associated with the key. + public void WriteProperty(PropertyKey key, object value) => WriteProperty(key, value, true); + + /// Writes the given property key and value. To allow truncation of the given value, set allowTruncatedValue to true. + /// The property key. + /// The value associated with the key. + /// True to allow truncation (default); otherwise False. + /// If the writable property store is already closed. + /// + /// If AllowTruncatedValue is set to false and while setting the value on the property it had to be truncated in a string or rounded + /// in a numeric value. + /// + public void WriteProperty(PropertyKey key, object value, bool allowTruncatedValue) + { + if (writablePropStore == null) + throw new InvalidOperationException("Writeable store has been closed."); + + using (var propVar = PropVariant.FromObject(value)) + { + var result = writablePropStore.SetValue(ref key, propVar); + + if (!allowTruncatedValue && ((int)result == ShellNativeMethods.InPlaceStringTruncated)) + { + // At this point we can't revert back the commit so don't commit, close the property store and throw an exception to let + // the user know. + Marshal.ReleaseComObject(writablePropStore); + writablePropStore = null; + + throw new ArgumentOutOfRangeException("value", LocalizedMessages.ShellPropertyValueTruncated); + } + + if (!CoreErrorHelper.Succeeded(result)) + { + throw new PropertySystemException(LocalizedMessages.ShellPropertySetValue, Marshal.GetExceptionForHR((int)result)); + } + } + } + + /// Writes the specified property given the canonical name and a value. + /// The canonical name. + /// The property value. + public void WriteProperty(string canonicalName, object value) => WriteProperty(canonicalName, value, true); + + /// + /// Writes the specified property given the canonical name and a value. To allow truncation of the given value, set + /// allowTruncatedValue to true. + /// + /// The canonical name. + /// The property value. + /// True to allow truncation (default); otherwise False. + /// If the given canonical name is not valid. + public void WriteProperty(string canonicalName, object value, bool allowTruncatedValue) + { + // Get the PropertyKey using the canonicalName passed in + + var result = PropertySystemNativeMethods.PSGetPropertyKeyFromName(canonicalName, out var propKey); + + if (!CoreErrorHelper.Succeeded(result)) + { + throw new ArgumentException( + LocalizedMessages.ShellInvalidCanonicalName, + Marshal.GetExceptionForHR(result)); + } + + WriteProperty(propKey, value, allowTruncatedValue); + } + + /// Writes the specified property using an IShellProperty and a value. + /// The property name. + /// The property value. + public void WriteProperty(IShellProperty shellProperty, object value) => WriteProperty(shellProperty, value, true); + + /// + /// Writes the specified property given an IShellProperty and a value. To allow truncation of the given value, set + /// allowTruncatedValue to true. + /// + /// The property name. + /// The property value. + /// True to allow truncation (default); otherwise False. + public void WriteProperty(IShellProperty shellProperty, object value, bool allowTruncatedValue) + { + if (shellProperty == null) { throw new ArgumentNullException("shellProperty"); } + WriteProperty(shellProperty.PropertyKey, value, allowTruncatedValue); + } + + /// Writes the specified property using a strongly-typed ShellProperty and a value. + /// The type of the property name. + /// The property name. + /// The property value. + public void WriteProperty(ShellProperty shellProperty, T value) => WriteProperty(shellProperty, value, true); + + /// + /// Writes the specified property given a strongly-typed ShellProperty and a value. To allow truncation of the given value, set + /// allowTruncatedValue to true. + /// + /// The type of the property name. + /// The property name. + /// The property value. + /// True to allow truncation (default); otherwise False. + public void WriteProperty(ShellProperty shellProperty, T value, bool allowTruncatedValue) + { + if (shellProperty == null) { throw new ArgumentNullException("shellProperty"); } + WriteProperty(shellProperty.PropertyKey, value, allowTruncatedValue); + } + + /// Release the native and managed objects. + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) => Close(); + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/StronglyTypedProperties.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/StronglyTypedProperties.cs new file mode 100644 index 0000000..91149c2 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/StronglyTypedProperties.cs @@ -0,0 +1,16025 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Collections; +using System.Runtime.InteropServices.ComTypes; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + + /// + /// Base class for all the strongly-typed properties + /// + public abstract class PropertyStoreItems + { + // Left empty for base class + } + + // TODO: FIX THIS!!! + public partial class ShellProperties + { + + /// + /// .System Properties + /// + public class PropertySystem : PropertyStoreItems + { + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystem(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.AcquisitionID -- PKEY_AcquisitionID + /// Description: Hash to determine acquisition session. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {65A98875-3C80-40AB-ABBC-EFDAF77DBEE2}, 100 + /// + public ShellProperty AcquisitionID + { + get + { + var key = SystemProperties.System.AcquisitionID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ApplicationName -- PKEY_ApplicationName + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 18 (PIDSI_APPNAME) + /// + public ShellProperty ApplicationName + { + get + { + var key = SystemProperties.System.ApplicationName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Author -- PKEY_Author + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 4 (PIDSI_AUTHOR) + /// + public ShellProperty Author + { + get + { + var key = SystemProperties.System.Author; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Capacity -- PKEY_Capacity + /// Description: The amount of total space in bytes. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_Volume) {9B174B35-40FF-11D2-A27E-00C04FC30871}, 3 (PID_VOLUME_CAPACITY) (Filesystem Volume Properties) + /// + public ShellProperty Capacity + { + get + { + var key = SystemProperties.System.Capacity; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Category -- PKEY_Category + /// Description: Legacy code treats this as VT_LPSTR. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 2 (PIDDSI_CATEGORY) + /// + public ShellProperty Category + { + get + { + var key = SystemProperties.System.Category; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Comment -- PKEY_Comment + /// Description: Comments. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 6 (PIDSI_COMMENTS) + /// + public ShellProperty Comment + { + get + { + var key = SystemProperties.System.Comment; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Company -- PKEY_Company + /// Description: The company or publisher. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 15 (PIDDSI_COMPANY) + /// + public ShellProperty Company + { + get + { + var key = SystemProperties.System.Company; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ComputerName -- PKEY_ComputerName + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 5 (PID_COMPUTERNAME) + /// + public ShellProperty ComputerName + { + get + { + var key = SystemProperties.System.ComputerName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ContainedItems -- PKEY_ContainedItems + /// Description: The list of type of items, this item contains. For example, this item contains urls, attachments etc. + ///This is represented as a vector array of GUIDs where each GUID represents certain type. + /// + /// Type: Multivalue Guid -- VT_VECTOR | VT_CLSID (For variants: VT_ARRAY | VT_CLSID) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 29 + /// + public ShellProperty ContainedItems + { + get + { + var key = SystemProperties.System.ContainedItems; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ContentStatus -- PKEY_ContentStatus + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 27 + /// + public ShellProperty ContentStatus + { + get + { + var key = SystemProperties.System.ContentStatus; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ContentType -- PKEY_ContentType + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 26 + /// + public ShellProperty ContentType + { + get + { + var key = SystemProperties.System.ContentType; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Copyright -- PKEY_Copyright + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 11 (PIDMSI_COPYRIGHT) + /// + public ShellProperty Copyright + { + get + { + var key = SystemProperties.System.Copyright; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DateAccessed -- PKEY_DateAccessed + /// Description: The time of the last access to the item. The Indexing Service friendly name is 'access'. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 16 (PID_STG_ACCESSTIME) + /// + public ShellProperty DateAccessed + { + get + { + var key = SystemProperties.System.DateAccessed; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DateAcquired -- PKEY_DateAcquired + /// Description: The time the file entered the system via acquisition. This is not the same as System.DateImported. + ///Examples are when pictures are acquired from a camera, or when music is purchased online. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {2CBAA8F5-D81F-47CA-B17A-F8D822300131}, 100 + /// + public ShellProperty DateAcquired + { + get + { + var key = SystemProperties.System.DateAcquired; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DateArchived -- PKEY_DateArchived + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {43F8D7B7-A444-4F87-9383-52271C9B915C}, 100 + /// + public ShellProperty DateArchived + { + get + { + var key = SystemProperties.System.DateArchived; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DateCompleted -- PKEY_DateCompleted + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {72FAB781-ACDA-43E5-B155-B2434F85E678}, 100 + /// + public ShellProperty DateCompleted + { + get + { + var key = SystemProperties.System.DateCompleted; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DateCreated -- PKEY_DateCreated + /// Description: The date and time the item was created. The Indexing Service friendly name is 'create'. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 15 (PID_STG_CREATETIME) + /// + public ShellProperty DateCreated + { + get + { + var key = SystemProperties.System.DateCreated; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DateImported -- PKEY_DateImported + /// Description: The time the file is imported into a separate database. This is not the same as System.DateAcquired. (Eg, 2003:05:22 13:55:04) + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 18258 + /// + public ShellProperty DateImported + { + get + { + var key = SystemProperties.System.DateImported; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DateModified -- PKEY_DateModified + /// Description: The date and time of the last write to the item. The Indexing Service friendly name is 'write'. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 14 (PID_STG_WRITETIME) + /// + public ShellProperty DateModified + { + get + { + var key = SystemProperties.System.DateModified; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DescriptionID -- PKEY_DescriptionID + /// Description: The contents of a SHDESCRIPTIONID structure as a buffer of bytes. + /// + /// Type: Buffer -- VT_VECTOR | VT_UI1 (For variants: VT_ARRAY | VT_UI1) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 2 (PID_DESCRIPTIONID) + /// + public ShellProperty DescriptionID + { + get + { + var key = SystemProperties.System.DescriptionID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DueDate -- PKEY_DueDate + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {3F8472B5-E0AF-4DB2-8071-C53FE76AE7CE}, 100 + /// + public ShellProperty DueDate + { + get + { + var key = SystemProperties.System.DueDate; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.EndDate -- PKEY_EndDate + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {C75FAA05-96FD-49E7-9CB4-9F601082D553}, 100 + /// + public ShellProperty EndDate + { + get + { + var key = SystemProperties.System.EndDate; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FileAllocationSize -- PKEY_FileAllocationSize + /// Description: + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 18 (PID_STG_ALLOCSIZE) + /// + public ShellProperty FileAllocationSize + { + get + { + var key = SystemProperties.System.FileAllocationSize; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FileAttributes -- PKEY_FileAttributes + /// Description: This is the WIN32_FIND_DATA dwFileAttributes for the file-based item. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 13 (PID_STG_ATTRIBUTES) + /// + public ShellProperty FileAttributes + { + get + { + var key = SystemProperties.System.FileAttributes; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FileCount -- PKEY_FileCount + /// Description: + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 12 + /// + public ShellProperty FileCount + { + get + { + var key = SystemProperties.System.FileCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FileDescription -- PKEY_FileDescription + /// Description: This is a user-friendly description of the file. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSFMTID_VERSION) {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 3 (PIDVSI_FileDescription) + /// + public ShellProperty FileDescription + { + get + { + var key = SystemProperties.System.FileDescription; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FileExtension -- PKEY_FileExtension + /// Description: This is the file extension of the file based item, including the leading period. + /// + ///If System.FileName is VT_EMPTY, then this property should be too. Otherwise, it should be derived + ///appropriately by the data source from System.FileName. If System.FileName does not have a file + ///extension, this value should be VT_EMPTY. + /// + ///To obtain the type of any item (including an item that is not a file), use System.ItemType. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" ".txt" + /// "\\server\share\mydir\goodnews.doc" ".doc" + /// "\\server\share\numbers.xls" ".xls" + /// "\\server\share\folder" VT_EMPTY + /// "c:\foo\MyFolder" VT_EMPTY + /// [desktop] VT_EMPTY + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E4F10A3C-49E6-405D-8288-A23BD4EEAA6C}, 100 + /// + public ShellProperty FileExtension + { + get + { + var key = SystemProperties.System.FileExtension; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FileFRN -- PKEY_FileFRN + /// Description: This is the unique file ID, also known as the File Reference Number. For a given file, this is the same value + ///as is found in the structure variable FILE_ID_BOTH_DIR_INFO.FileId, via GetFileInformationByHandleEx(). + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 21 (PID_STG_FRN) + /// + public ShellProperty FileFRN + { + get + { + var key = SystemProperties.System.FileFRN; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FileName -- PKEY_FileName + /// Description: This is the file name (including extension) of the file. + /// + ///It is possible that the item might not exist on a filesystem (ie, it may not be opened + ///using CreateFile). Nonetheless, if the item is represented as a file from the logical sense + ///(and its name follows standard Win32 file-naming syntax), then the data source should emit this property. + /// + ///If an item is not a file, then the value for this property is VT_EMPTY. See + ///System.ItemNameDisplay. + /// + ///This has the same value as System.ParsingName for items that are provided by the Shell's file folder. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "hello.txt" + /// "\\server\share\mydir\goodnews.doc" "goodnews.doc" + /// "\\server\share\numbers.xls" "numbers.xls" + /// "c:\foo\MyFolder" "MyFolder" + /// (email message) VT_EMPTY + /// (song on portable device) "song.wma" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {41CF5AE0-F75A-4806-BD87-59C7D9248EB9}, 100 + /// + public ShellProperty FileName + { + get + { + var key = SystemProperties.System.FileName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FileOwner -- PKEY_FileOwner + /// Description: This is the owner of the file, according to the file system. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Misc) {9B174B34-40FF-11D2-A27E-00C04FC30871}, 4 (PID_MISC_OWNER) + /// + public ShellProperty FileOwner + { + get + { + var key = SystemProperties.System.FileOwner; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FileVersion -- PKEY_FileVersion + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSFMTID_VERSION) {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 4 (PIDVSI_FileVersion) + /// + public ShellProperty FileVersion + { + get + { + var key = SystemProperties.System.FileVersion; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FindData -- PKEY_FindData + /// Description: WIN32_FIND_DATAW in buffer of bytes. + /// + /// Type: Buffer -- VT_VECTOR | VT_UI1 (For variants: VT_ARRAY | VT_UI1) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 0 (PID_FINDDATA) + /// + public ShellProperty FindData + { + get + { + var key = SystemProperties.System.FindData; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FlagColor -- PKEY_FlagColor + /// Description: + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {67DF94DE-0CA7-4D6F-B792-053A3E4F03CF}, 100 + /// + public ShellProperty FlagColor + { + get + { + var key = SystemProperties.System.FlagColor; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FlagColorText -- PKEY_FlagColorText + /// Description: This is the user-friendly form of System.FlagColor. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {45EAE747-8E2A-40AE-8CBF-CA52ABA6152A}, 100 + /// + public ShellProperty FlagColorText + { + get + { + var key = SystemProperties.System.FlagColorText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FlagStatus -- PKEY_FlagStatus + /// Description: Status of Flag. Values: (0=none 1=white 2=Red). cdoPR_FLAG_STATUS + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 12 + /// + public ShellProperty FlagStatus + { + get + { + var key = SystemProperties.System.FlagStatus; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FlagStatusText -- PKEY_FlagStatusText + /// Description: This is the user-friendly form of System.FlagStatus. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DC54FD2E-189D-4871-AA01-08C2F57A4ABC}, 100 + /// + public ShellProperty FlagStatusText + { + get + { + var key = SystemProperties.System.FlagStatusText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FreeSpace -- PKEY_FreeSpace + /// Description: The amount of free space in bytes. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_Volume) {9B174B35-40FF-11D2-A27E-00C04FC30871}, 2 (PID_VOLUME_FREE) (Filesystem Volume Properties) + /// + public ShellProperty FreeSpace + { + get + { + var key = SystemProperties.System.FreeSpace; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.FullText -- PKEY_FullText + /// Description: This PKEY is used to specify search terms that should be applied as broadly as possible, + ///across all valid properties for the data source(s) being searched. It should not be + ///emitted from a data source. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {1E3EE840-BC2B-476C-8237-2ACD1A839B22}, 6 + /// + public ShellProperty FullText + { + get + { + var key = SystemProperties.System.FullText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Identity -- PKEY_Identity + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A26F4AFC-7346-4299-BE47-EB1AE613139F}, 100 + /// + public ShellProperty IdentityProperty + { + get + { + var key = SystemProperties.System.IdentityProperty; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ImageParsingName -- PKEY_ImageParsingName + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D7750EE0-C6A4-48EC-B53E-B87B52E6D073}, 100 + /// + public ShellProperty ImageParsingName + { + get + { + var key = SystemProperties.System.ImageParsingName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Importance -- PKEY_Importance + /// Description: + /// Type: Int32 -- VT_I4 + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 11 + /// + public ShellProperty Importance + { + get + { + var key = SystemProperties.System.Importance; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ImportanceText -- PKEY_ImportanceText + /// Description: This is the user-friendly form of System.Importance. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A3B29791-7713-4E1D-BB40-17DB85F01831}, 100 + /// + public ShellProperty ImportanceText + { + get + { + var key = SystemProperties.System.ImportanceText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.InfoTipText -- PKEY_InfoTipText + /// Description: The text (with formatted property values) to show in the infotip. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 17 + /// + public ShellProperty InfoTipText + { + get + { + var key = SystemProperties.System.InfoTipText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.InternalName -- PKEY_InternalName + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSFMTID_VERSION) {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 5 (PIDVSI_InternalName) + /// + public ShellProperty InternalName + { + get + { + var key = SystemProperties.System.InternalName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsAttachment -- PKEY_IsAttachment + /// Description: Identifies if this item is an attachment. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {F23F425C-71A1-4FA8-922F-678EA4A60408}, 100 + /// + public ShellProperty IsAttachment + { + get + { + var key = SystemProperties.System.IsAttachment; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsDefaultNonOwnerSaveLocation -- PKEY_IsDefaultNonOwnerSaveLocation + /// Description: Identifies the default save location for a library for non-owners of the library + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}, 5 + /// + public ShellProperty IsDefaultNonOwnerSaveLocation + { + get + { + var key = SystemProperties.System.IsDefaultNonOwnerSaveLocation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsDefaultSaveLocation -- PKEY_IsDefaultSaveLocation + /// Description: Identifies the default save location for a library for the owner of the library + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}, 3 + /// + public ShellProperty IsDefaultSaveLocation + { + get + { + var key = SystemProperties.System.IsDefaultSaveLocation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsDeleted -- PKEY_IsDeleted + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {5CDA5FC8-33EE-4FF3-9094-AE7BD8868C4D}, 100 + /// + public ShellProperty IsDeleted + { + get + { + var key = SystemProperties.System.IsDeleted; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsEncrypted -- PKEY_IsEncrypted + /// Description: Is the item encrypted? + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {90E5E14E-648B-4826-B2AA-ACAF790E3513}, 10 + /// + public ShellProperty IsEncrypted + { + get + { + var key = SystemProperties.System.IsEncrypted; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsFlagged -- PKEY_IsFlagged + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {5DA84765-E3FF-4278-86B0-A27967FBDD03}, 100 + /// + public ShellProperty IsFlagged + { + get + { + var key = SystemProperties.System.IsFlagged; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsFlaggedComplete -- PKEY_IsFlaggedComplete + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {A6F360D2-55F9-48DE-B909-620E090A647C}, 100 + /// + public ShellProperty IsFlaggedComplete + { + get + { + var key = SystemProperties.System.IsFlaggedComplete; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsIncomplete -- PKEY_IsIncomplete + /// Description: Identifies if the message was not completely received for some error condition. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {346C8BD1-2E6A-4C45-89A4-61B78E8E700F}, 100 + /// + public ShellProperty IsIncomplete + { + get + { + var key = SystemProperties.System.IsIncomplete; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsLocationSupported -- PKEY_IsLocationSupported + /// Description: A bool value to know if a location is supported (locally indexable, or remotely indexed). + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}, 8 + /// + public ShellProperty IsLocationSupported + { + get + { + var key = SystemProperties.System.IsLocationSupported; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsPinnedToNameSpaceTree -- PKEY_IsPinnedToNameSpaceTree + /// Description: A bool value to know if a shell folder is pinned to the navigation pane + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}, 2 + /// + public ShellProperty IsPinnedToNamespaceTree + { + get + { + var key = SystemProperties.System.IsPinnedToNamespaceTree; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsRead -- PKEY_IsRead + /// Description: Has the item been read? + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 10 + /// + public ShellProperty IsRead + { + get + { + var key = SystemProperties.System.IsRead; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsSearchOnlyItem -- PKEY_IsSearchOnlyItem + /// Description: Identifies if a location or a library is search only + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}, 4 + /// + public ShellProperty IsSearchOnlyItem + { + get + { + var key = SystemProperties.System.IsSearchOnlyItem; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsSendToTarget -- PKEY_IsSendToTarget + /// Description: Provided by certain shell folders. Return TRUE if the folder is a valid Send To target. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 33 + /// + public ShellProperty IsSendToTarget + { + get + { + var key = SystemProperties.System.IsSendToTarget; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IsShared -- PKEY_IsShared + /// Description: Is this item shared? This only checks for ACLs that are not inherited. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {EF884C5B-2BFE-41BB-AAE5-76EEDF4F9902}, 100 + /// + public ShellProperty IsShared + { + get + { + var key = SystemProperties.System.IsShared; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemAuthors -- PKEY_ItemAuthors + /// Description: This is the generic list of authors associated with an item. + /// + ///For example, the artist name for a track is the item author. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D0A04F0A-462A-48A4-BB2F-3706E88DBD7D}, 100 + /// + public ShellProperty ItemAuthors + { + get + { + var key = SystemProperties.System.ItemAuthors; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemClassType -- PKEY_ItemClassType + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {048658AD-2DB8-41A4-BBB6-AC1EF1207EB1}, 100 + /// + public ShellProperty ItemClassType + { + get + { + var key = SystemProperties.System.ItemClassType; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemDate -- PKEY_ItemDate + /// Description: This is the main date for an item. The date of interest. + /// + ///For example, for photos this maps to System.Photo.DateTaken. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {F7DB74B4-4287-4103-AFBA-F1B13DCD75CF}, 100 + /// + public ShellProperty ItemDate + { + get + { + var key = SystemProperties.System.ItemDate; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemFolderNameDisplay -- PKEY_ItemFolderNameDisplay + /// Description: This is the user-friendly display name of the parent folder of an item. + /// + ///If System.ItemFolderPathDisplay is VT_EMPTY, then this property should be too. Otherwise, it + ///should be derived appropriately by the data source from System.ItemFolderPathDisplay. + /// + ///If the folder is a file folder, the value will be localized if a localized name is available. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "bar" + /// "\\server\share\mydir\goodnews.doc" "mydir" + /// "\\server\share\numbers.xls" "share" + /// "c:\foo\MyFolder" "foo" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "Inbox" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 2 (PID_STG_DIRECTORY) + /// + public ShellProperty ItemFolderNameDisplay + { + get + { + var key = SystemProperties.System.ItemFolderNameDisplay; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemFolderPathDisplay -- PKEY_ItemFolderPathDisplay + /// Description: This is the user-friendly display path of the parent folder of an item. + /// + ///If System.ItemPathDisplay is VT_EMPTY, then this property should be too. Otherwise, it should + ///be derived appropriately by the data source from System.ItemPathDisplay. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "c:\foo\bar" + /// "\\server\share\mydir\goodnews.doc" "\\server\share\mydir" + /// "\\server\share\numbers.xls" "\\server\share" + /// "c:\foo\MyFolder" "c:\foo" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "/Mailbox Account/Inbox" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 6 + /// + public ShellProperty ItemFolderPathDisplay + { + get + { + var key = SystemProperties.System.ItemFolderPathDisplay; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemFolderPathDisplayNarrow -- PKEY_ItemFolderPathDisplayNarrow + /// Description: This is the user-friendly display path of the parent folder of an item. The format of the string + ///should be tailored such that the folder name comes first, to optimize for a narrow viewing column. + /// + ///If the folder is a file folder, the value includes localized names if they are present. + /// + ///If System.ItemFolderPathDisplay is VT_EMPTY, then this property should be too. Otherwise, it should + ///be derived appropriately by the data source from System.ItemFolderPathDisplay. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "bar (c:\foo)" + /// "\\server\share\mydir\goodnews.doc" "mydir (\\server\share)" + /// "\\server\share\numbers.xls" "share (\\server)" + /// "c:\foo\MyFolder" "foo (c:\)" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "Inbox (/Mailbox Account)" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DABD30ED-0043-4789-A7F8-D013A4736622}, 100 + /// + public ShellProperty ItemFolderPathDisplayNarrow + { + get + { + var key = SystemProperties.System.ItemFolderPathDisplayNarrow; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemName -- PKEY_ItemName + /// Description: This is the base-name of the System.ItemNameDisplay. + /// + ///If the item is a file this property + ///includes the extension in all cases, and will be localized if a localized name is available. + /// + ///If the item is a message, then the value of this property does not include the forwarding or + ///reply prefixes (see System.ItemNamePrefix). + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6B8DA074-3B5C-43BC-886F-0A2CDCE00B6F}, 100 + /// + public ShellProperty ItemName + { + get + { + var key = SystemProperties.System.ItemName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemNameDisplay -- PKEY_ItemNameDisplay + /// Description: This is the display name in "most complete" form. This is the best effort unique representation + ///of the name of an item that makes sense for end users to read. It is the concatentation of + ///System.ItemNamePrefix and System.ItemName. + /// + ///If the item is a file this property + ///includes the extension in all cases, and will be localized if a localized name is available. + /// + ///There are acceptable cases when System.FileName is not VT_EMPTY, yet the value of this property + ///is completely different. Email messages are a key example. If the item is an email message, + ///the item name is likely the subject. In that case, the value must be the concatenation of the + ///System.ItemNamePrefix and System.ItemName. Since the value of System.ItemNamePrefix excludes + ///any trailing whitespace, the concatenation must include a whitespace when generating System.ItemNameDisplay. + /// + ///Note that this property is not guaranteed to be unique, but the idea is to promote the most likely + ///candidate that can be unique and also makes sense for end users. For example, for documents, you + ///might think about using System.Title as the System.ItemNameDisplay, but in practice the title of + ///the documents may not be useful or unique enough to be of value as the sole System.ItemNameDisplay. + ///Instead, providing the value of System.FileName as the value of System.ItemNameDisplay is a better + ///candidate. In Windows Mail, the emails are stored in the file system as .eml files and the + ///System.FileName for those files are not human-friendly as they contain GUIDs. In this example, + ///promoting System.Subject as System.ItemNameDisplay makes more sense. + /// + ///Compatibility notes: + /// + ///Shell folder implementations on Vista: use PKEY_ItemNameDisplay for the name column when + ///you want Explorer to call ISF::GetDisplayNameOf(SHGDN_NORMAL) to get the value of the name. Use + ///another PKEY (like PKEY_ItemName) when you want Explorer to call either the folder's property store or + ///ISF2::GetDetailsEx in order to get the value of the name. + /// + ///Shell folder implementations on XP: the first column needs to be the name column, and Explorer + ///will call ISF::GetDisplayNameOf to get the value of the name. The PKEY/SCID does not matter. + /// + ///Example values: + /// + /// File: "hello.txt" + /// Message: "Re: Let's talk about Tom's argyle socks!" + /// Device folder: "song.wma" + /// Folder: "Documents" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 10 (PID_STG_NAME) + /// + public ShellProperty ItemNameDisplay + { + get + { + var key = SystemProperties.System.ItemNameDisplay; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemNamePrefix -- PKEY_ItemNamePrefix + /// Description: This is the prefix of an item, used for email messages. + ///where the subject begins with "Re:" which is the prefix. + /// + ///If the item is a file, then the value of this property is VT_EMPTY. + /// + ///If the item is a message, then the value of this property is the forwarding or reply + ///prefixes (including delimiting colon, but no whitespace), or VT_EMPTY if there is no prefix. + /// + ///Example values: + /// + ///System.ItemNamePrefix System.ItemName System.ItemNameDisplay + ///--------------------- ------------------- ---------------------- + ///VT_EMPTY "Great day" "Great day" + ///"Re:" "Great day" "Re: Great day" + ///"Fwd: " "Monthly budget" "Fwd: Monthly budget" + ///VT_EMPTY "accounts.xls" "accounts.xls" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D7313FF1-A77A-401C-8C99-3DBDD68ADD36}, 100 + /// + public ShellProperty ItemNamePrefix + { + get + { + var key = SystemProperties.System.ItemNamePrefix; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemParticipants -- PKEY_ItemParticipants + /// Description: This is the generic list of people associated with an item and who contributed + ///to the item. + /// + ///For example, this is the combination of people in the To list, Cc list and + ///sender of an email message. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D4D0AA16-9948-41A4-AA85-D97FF9646993}, 100 + /// + public ShellProperty ItemParticipants + { + get + { + var key = SystemProperties.System.ItemParticipants; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemPathDisplay -- PKEY_ItemPathDisplay + /// Description: This is the user-friendly display path to the item. + /// + ///If the item is a file or folder this property + ///includes the extension in all cases, and will be localized if a localized name is available. + /// + ///For other items,this is the user-friendly equivalent, assuming the item exists in hierarchical storage. + /// + ///Unlike System.ItemUrl, this property value does not include the URL scheme. + /// + ///To parse an item path, use System.ItemUrl or System.ParsingPath. To reference shell + ///namespace items using shell APIs, use System.ParsingPath. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "c:\foo\bar\hello.txt" + /// "\\server\share\mydir\goodnews.doc" "\\server\share\mydir\goodnews.doc" + /// "\\server\share\numbers.xls" "\\server\share\numbers.xls" + /// "c:\foo\MyFolder" "c:\foo\MyFolder" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "/Mailbox Account/Inbox/'Re: Hello!'" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 7 + /// + public ShellProperty ItemPathDisplay + { + get + { + var key = SystemProperties.System.ItemPathDisplay; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemPathDisplayNarrow -- PKEY_ItemPathDisplayNarrow + /// Description: This is the user-friendly display path to the item. The format of the string should be + ///tailored such that the name comes first, to optimize for a narrow viewing column. + /// + ///If the item is a file, the value excludes the file extension, and includes localized names if they are present. + ///If the item is a message, the value includes the System.ItemNamePrefix. + /// + ///To parse an item path, use System.ItemUrl or System.ParsingPath. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "hello (c:\foo\bar)" + /// "\\server\share\mydir\goodnews.doc" "goodnews (\\server\share\mydir)" + /// "\\server\share\folder" "folder (\\server\share)" + /// "c:\foo\MyFolder" "MyFolder (c:\foo)" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "Re: Hello! (/Mailbox Account/Inbox)" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 8 + /// + public ShellProperty ItemPathDisplayNarrow + { + get + { + var key = SystemProperties.System.ItemPathDisplayNarrow; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemType -- PKEY_ItemType + /// Description: This is the canonical type of the item and is intended to be programmatically + ///parsed. + /// + ///If there is no canonical type, the value is VT_EMPTY. + /// + ///If the item is a file (ie, System.FileName is not VT_EMPTY), the value is the same as + ///System.FileExtension. + /// + ///Use System.ItemTypeText when you want to display the type to end users in a view. (If + /// the item is a file, passing the System.ItemType value to PSFormatForDisplay will + /// result in the same value as System.ItemTypeText.) + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" ".txt" + /// "\\server\share\mydir\goodnews.doc" ".doc" + /// "\\server\share\folder" "Directory" + /// "c:\foo\MyFolder" "Directory" + /// [desktop] "Folder" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "MAPI/IPM.Message" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 11 + /// + public ShellProperty ItemType + { + get + { + var key = SystemProperties.System.ItemType; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemTypeText -- PKEY_ItemTypeText + /// Description: This is the user friendly type name of the item. This is not intended to be + ///programmatically parsed. + /// + ///If System.ItemType is VT_EMPTY, the value of this property is also VT_EMPTY. + /// + ///If the item is a file, the value of this property is the same as if you passed the + ///file's System.ItemType value to PSFormatForDisplay. + /// + ///This property should not be confused with System.Kind, where System.Kind is a high-level + ///user friendly kind name. For example, for a document, System.Kind = "Document" and + ///System.Item.Type = ".doc" and System.Item.TypeText = "Microsoft Word Document" + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "Text File" + /// "\\server\share\mydir\goodnews.doc" "Microsoft Word Document" + /// "\\server\share\folder" "File Folder" + /// "c:\foo\MyFolder" "File Folder" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "Outlook E-Mail Message" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 4 (PID_STG_STORAGETYPE) + /// + public ShellProperty ItemTypeText + { + get + { + var key = SystemProperties.System.ItemTypeText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ItemUrl -- PKEY_ItemUrl + /// Description: This always represents a well formed URL that points to the item. + /// + ///To reference shell namespace items using shell APIs, use System.ParsingPath. + /// + ///Example values: + /// + /// Files: "file:///c:/foo/bar/hello.txt" + /// "csc://{GUID}/..." + /// Messages: "mapi://..." + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Query) {49691C90-7E17-101A-A91C-08002B2ECDA9}, 9 (DISPID_QUERY_VIRTUALPATH) + /// + public ShellProperty ItemUrl + { + get + { + var key = SystemProperties.System.ItemUrl; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Keywords -- PKEY_Keywords + /// Description: The keywords for the item. Also referred to as tags. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 5 (PIDSI_KEYWORDS) + /// + public ShellProperty Keywords + { + get + { + var key = SystemProperties.System.Keywords; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Kind -- PKEY_Kind + /// Description: System.Kind is used to map extensions to various .Search folders. + ///Extensions are mapped to Kinds at HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\KindMap + ///The list of kinds is not extensible. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {1E3EE840-BC2B-476C-8237-2ACD1A839B22}, 3 + /// + public ShellProperty Kind + { + get + { + var key = SystemProperties.System.Kind; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.KindText -- PKEY_KindText + /// Description: This is the user-friendly form of System.Kind. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F04BEF95-C585-4197-A2B7-DF46FDC9EE6D}, 100 + /// + public ShellProperty KindText + { + get + { + var key = SystemProperties.System.KindText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Language -- PKEY_Language + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 28 + /// + public ShellProperty Language + { + get + { + var key = SystemProperties.System.Language; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.MileageInformation -- PKEY_MileageInformation + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FDF84370-031A-4ADD-9E91-0D775F1C6605}, 100 + /// + public ShellProperty MileageInformation + { + get + { + var key = SystemProperties.System.MileageInformation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.MIMEType -- PKEY_MIMEType + /// Description: The MIME type. Eg, for EML files: 'message/rfc822'. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0B63E350-9CCC-11D0-BCDB-00805FCCCE04}, 5 + /// + public ShellProperty MIMEType + { + get + { + var key = SystemProperties.System.MIMEType; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.NamespaceCLSID -- PKEY_NamespaceCLSID + /// Description: The CLSID of the name space extension for an item, the object that implements IShellFolder for this item + /// + /// Type: Guid -- VT_CLSID + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 6 + /// + public ShellProperty NamespaceClsid + { + get + { + var key = SystemProperties.System.NamespaceClsid; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Null -- PKEY_Null + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {00000000-0000-0000-0000-000000000000}, 0 + /// + public ShellProperty Null + { + get + { + var key = SystemProperties.System.Null; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.OfflineAvailability -- PKEY_OfflineAvailability + /// Description: + /// Type: UInt32 -- VT_UI4 + /// FormatID: {A94688B6-7D9F-4570-A648-E3DFC0AB2B3F}, 100 + /// + public ShellProperty OfflineAvailability + { + get + { + var key = SystemProperties.System.OfflineAvailability; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.OfflineStatus -- PKEY_OfflineStatus + /// Description: + /// Type: UInt32 -- VT_UI4 + /// FormatID: {6D24888F-4718-4BDA-AFED-EA0FB4386CD8}, 100 + /// + public ShellProperty OfflineStatus + { + get + { + var key = SystemProperties.System.OfflineStatus; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.OriginalFileName -- PKEY_OriginalFileName + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSFMTID_VERSION) {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 6 + /// + public ShellProperty OriginalFileName + { + get + { + var key = SystemProperties.System.OriginalFileName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.OwnerSID -- PKEY_OwnerSID + /// Description: SID of the user that owns the library. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}, 6 + /// + public ShellProperty OwnerSid + { + get + { + var key = SystemProperties.System.OwnerSid; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ParentalRating -- PKEY_ParentalRating + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 21 (PIDMSI_PARENTAL_RATING) + /// + public ShellProperty ParentalRating + { + get + { + var key = SystemProperties.System.ParentalRating; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ParentalRatingReason -- PKEY_ParentalRatingReason + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {10984E0A-F9F2-4321-B7EF-BAF195AF4319}, 100 + /// + public ShellProperty ParentalRatingReason + { + get + { + var key = SystemProperties.System.ParentalRatingReason; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ParentalRatingsOrganization -- PKEY_ParentalRatingsOrganization + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A7FE0840-1344-46F0-8D37-52ED712A4BF9}, 100 + /// + public ShellProperty ParentalRatingsOrganization + { + get + { + var key = SystemProperties.System.ParentalRatingsOrganization; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ParsingBindContext -- PKEY_ParsingBindContext + /// Description: used to get the IBindCtx for an item for parsing + /// + /// Type: Any -- VT_NULL Legacy code may treat this as VT_UNKNOWN. + /// FormatID: {DFB9A04D-362F-4CA3-B30B-0254B17B5B84}, 100 + /// + public ShellProperty ParsingBindContext + { + get + { + var key = SystemProperties.System.ParsingBindContext; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ParsingName -- PKEY_ParsingName + /// Description: The shell namespace name of an item relative to a parent folder. This name may be passed to + ///IShellFolder::ParseDisplayName() of the parent shell folder. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 24 + /// + public ShellProperty ParsingName + { + get + { + var key = SystemProperties.System.ParsingName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ParsingPath -- PKEY_ParsingPath + /// Description: This is the shell namespace path to the item. This path may be passed to + ///SHParseDisplayName to parse the path to the correct shell folder. + /// + ///If the item is a file, the value is identical to System.ItemPathDisplay. + /// + ///If the item cannot be accessed through the shell namespace, this value is VT_EMPTY. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 30 + /// + public ShellProperty ParsingPath + { + get + { + var key = SystemProperties.System.ParsingPath; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PerceivedType -- PKEY_PerceivedType + /// Description: The perceived type of a shell item, based upon its canonical type. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 9 + /// + public ShellProperty PerceivedType + { + get + { + var key = SystemProperties.System.PerceivedType; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PercentFull -- PKEY_PercentFull + /// Description: The amount filled as a percentage, multiplied by 100 (ie, the valid range is 0 through 100). + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_Volume) {9B174B35-40FF-11D2-A27E-00C04FC30871}, 5 (Filesystem Volume Properties) + /// + public ShellProperty PercentFull + { + get + { + var key = SystemProperties.System.PercentFull; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Priority -- PKEY_Priority + /// Description: + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {9C1FCF74-2D97-41BA-B4AE-CB2E3661A6E4}, 5 + /// + public ShellProperty Priority + { + get + { + var key = SystemProperties.System.Priority; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PriorityText -- PKEY_PriorityText + /// Description: This is the user-friendly form of System.Priority. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D98BE98B-B86B-4095-BF52-9D23B2E0A752}, 100 + /// + public ShellProperty PriorityText + { + get + { + var key = SystemProperties.System.PriorityText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Project -- PKEY_Project + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {39A7F922-477C-48DE-8BC8-B28441E342E3}, 100 + /// + public ShellProperty Project + { + get + { + var key = SystemProperties.System.Project; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ProviderItemID -- PKEY_ProviderItemID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F21D9941-81F0-471A-ADEE-4E74B49217ED}, 100 + /// + public ShellProperty ProviderItemID + { + get + { + var key = SystemProperties.System.ProviderItemID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Rating -- PKEY_Rating + /// Description: Indicates the users preference rating of an item on a scale of 1-99 (1-12 = One Star, + ///13-37 = Two Stars, 38-62 = Three Stars, 63-87 = Four Stars, 88-99 = Five Stars). + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 9 (PIDMSI_RATING) + /// + public ShellProperty Rating + { + get + { + var key = SystemProperties.System.Rating; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RatingText -- PKEY_RatingText + /// Description: This is the user-friendly form of System.Rating. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {90197CA7-FD8F-4E8C-9DA3-B57E1E609295}, 100 + /// + public ShellProperty RatingText + { + get + { + var key = SystemProperties.System.RatingText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sensitivity -- PKEY_Sensitivity + /// Description: + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {F8D3F6AC-4874-42CB-BE59-AB454B30716A}, 100 + /// + public ShellProperty Sensitivity + { + get + { + var key = SystemProperties.System.Sensitivity; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.SensitivityText -- PKEY_SensitivityText + /// Description: This is the user-friendly form of System.Sensitivity. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D0C7F054-3F72-4725-8527-129A577CB269}, 100 + /// + public ShellProperty SensitivityText + { + get + { + var key = SystemProperties.System.SensitivityText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.SFGAOFlags -- PKEY_SFGAOFlags + /// Description: IShellFolder::GetAttributesOf flags, with SFGAO_PKEYSFGAOMASK attributes masked out. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 25 + /// + public ShellProperty SFGAOFlags + { + get + { + var key = SystemProperties.System.SFGAOFlags; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.SharedWith -- PKEY_SharedWith + /// Description: Who is the item shared with? + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {EF884C5B-2BFE-41BB-AAE5-76EEDF4F9902}, 200 + /// + public ShellProperty SharedWith + { + get + { + var key = SystemProperties.System.SharedWith; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ShareUserRating -- PKEY_ShareUserRating + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 12 (PIDMSI_SHARE_USER_RATING) + /// + public ShellProperty ShareUserRating + { + get + { + var key = SystemProperties.System.ShareUserRating; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.SharingStatus -- PKEY_SharingStatus + /// Description: What is the item's sharing status (not shared, shared, everyone (homegroup or everyone), or private)? + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {EF884C5B-2BFE-41BB-AAE5-76EEDF4F9902}, 300 + /// + public ShellProperty SharingStatus + { + get + { + var key = SystemProperties.System.SharingStatus; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.SimpleRating -- PKEY_SimpleRating + /// Description: Indicates the users preference rating of an item on a scale of 0-5 (0=unrated, 1=One Star, 2=Two Stars, 3=Three Stars, + ///4=Four Stars, 5=Five Stars) + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {A09F084E-AD41-489F-8076-AA5BE3082BCA}, 100 + /// + public ShellProperty SimpleRating + { + get + { + var key = SystemProperties.System.SimpleRating; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Size -- PKEY_Size + /// Description: + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 12 (PID_STG_SIZE) + /// + public ShellProperty Size + { + get + { + var key = SystemProperties.System.Size; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.SoftwareUsed -- PKEY_SoftwareUsed + /// Description: PropertyTagSoftwareUsed + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 305 + /// + public ShellProperty SoftwareUsed + { + get + { + var key = SystemProperties.System.SoftwareUsed; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.SourceItem -- PKEY_SourceItem + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {668CDFA5-7A1B-4323-AE4B-E527393A1D81}, 100 + /// + public ShellProperty SourceItem + { + get + { + var key = SystemProperties.System.SourceItem; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.StartDate -- PKEY_StartDate + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {48FD6EC8-8A12-4CDF-A03E-4EC5A511EDDE}, 100 + /// + public ShellProperty StartDate + { + get + { + var key = SystemProperties.System.StartDate; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Status -- PKEY_Status + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_IntSite) {000214A1-0000-0000-C000-000000000046}, 9 + /// + public ShellProperty Status + { + get + { + var key = SystemProperties.System.Status; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Subject -- PKEY_Subject + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 3 (PIDSI_SUBJECT) + /// + public ShellProperty Subject + { + get + { + var key = SystemProperties.System.Subject; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Thumbnail -- PKEY_Thumbnail + /// Description: A data that represents the thumbnail in VT_CF format. + /// + /// Type: Clipboard -- VT_CF + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 17 (PIDSI_THUMBNAIL) + /// + public ShellProperty Thumbnail + { + get + { + var key = SystemProperties.System.Thumbnail; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ThumbnailCacheId -- PKEY_ThumbnailCacheId + /// Description: Unique value that can be used as a key to cache thumbnails. The value changes when the name, volume, or data modified + ///of an item changes. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: {446D16B1-8DAD-4870-A748-402EA43D788C}, 100 + /// + public ShellProperty ThumbnailCacheId + { + get + { + var key = SystemProperties.System.ThumbnailCacheId; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.ThumbnailStream -- PKEY_ThumbnailStream + /// Description: Data that represents the thumbnail in VT_STREAM format that GDI+/WindowsCodecs supports (jpg, png, etc). + /// + /// Type: Stream -- VT_STREAM + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 27 + /// + public ShellProperty ThumbnailStream + { + get + { + var key = SystemProperties.System.ThumbnailStream; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Title -- PKEY_Title + /// Description: Title of item. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 2 (PIDSI_TITLE) + /// + public ShellProperty Title + { + get + { + var key = SystemProperties.System.Title; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.TotalFileSize -- PKEY_TotalFileSize + /// Description: + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 14 + /// + public ShellProperty TotalFileSize + { + get + { + var key = SystemProperties.System.TotalFileSize; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Trademarks -- PKEY_Trademarks + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSFMTID_VERSION) {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 9 (PIDVSI_Trademarks) + /// + public ShellProperty Trademarks + { + get + { + var key = SystemProperties.System.Trademarks; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + private PropertySystemAppUserModel internalPropertySystemAppUserModel; + /// + /// System.AppUserModel Properties + /// + public PropertySystemAppUserModel AppUserModel + { + get + { + if (internalPropertySystemAppUserModel == null) + { + internalPropertySystemAppUserModel = new PropertySystemAppUserModel(shellObjectParent); + } + + return internalPropertySystemAppUserModel; + } + } + private PropertySystemAudio internalPropertySystemAudio; + /// + /// System.Audio Properties + /// + public PropertySystemAudio Audio + { + get + { + if (internalPropertySystemAudio == null) + { + internalPropertySystemAudio = new PropertySystemAudio(shellObjectParent); + } + + return internalPropertySystemAudio; + } + } + private PropertySystemCalendar internalPropertySystemCalendar; + /// + /// System.Calendar Properties + /// + public PropertySystemCalendar Calendar + { + get + { + if (internalPropertySystemCalendar == null) + { + internalPropertySystemCalendar = new PropertySystemCalendar(shellObjectParent); + } + + return internalPropertySystemCalendar; + } + } + private PropertySystemCommunication internalPropertySystemCommunication; + /// + /// System.Communication Properties + /// + public PropertySystemCommunication Communication + { + get + { + if (internalPropertySystemCommunication == null) + { + internalPropertySystemCommunication = new PropertySystemCommunication(shellObjectParent); + } + + return internalPropertySystemCommunication; + } + } + private PropertySystemComputer internalPropertySystemComputer; + /// + /// System.Computer Properties + /// + public PropertySystemComputer Computer + { + get + { + if (internalPropertySystemComputer == null) + { + internalPropertySystemComputer = new PropertySystemComputer(shellObjectParent); + } + + return internalPropertySystemComputer; + } + } + private PropertySystemContact internalPropertySystemContact; + /// + /// System.Contact Properties + /// + public PropertySystemContact Contact + { + get + { + if (internalPropertySystemContact == null) + { + internalPropertySystemContact = new PropertySystemContact(shellObjectParent); + } + + return internalPropertySystemContact; + } + } + private PropertySystemDevice internalPropertySystemDevice; + /// + /// System.Device Properties + /// + public PropertySystemDevice Device + { + get + { + if (internalPropertySystemDevice == null) + { + internalPropertySystemDevice = new PropertySystemDevice(shellObjectParent); + } + + return internalPropertySystemDevice; + } + } + private PropertySystemDeviceInterface internalPropertySystemDeviceInterface; + /// + /// System.DeviceInterface Properties + /// + public PropertySystemDeviceInterface DeviceInterface + { + get + { + if (internalPropertySystemDeviceInterface == null) + { + internalPropertySystemDeviceInterface = new PropertySystemDeviceInterface(shellObjectParent); + } + + return internalPropertySystemDeviceInterface; + } + } + private PropertySystemDevices internalPropertySystemDevices; + /// + /// System.Devices Properties + /// + public PropertySystemDevices Devices + { + get + { + if (internalPropertySystemDevices == null) + { + internalPropertySystemDevices = new PropertySystemDevices(shellObjectParent); + } + + return internalPropertySystemDevices; + } + } + private PropertySystemDocument internalPropertySystemDocument; + /// + /// System.Document Properties + /// + public PropertySystemDocument Document + { + get + { + if (internalPropertySystemDocument == null) + { + internalPropertySystemDocument = new PropertySystemDocument(shellObjectParent); + } + + return internalPropertySystemDocument; + } + } + private PropertySystemDRM internalPropertySystemDRM; + /// + /// System.DRM Properties + /// + public PropertySystemDRM DRM + { + get + { + if (internalPropertySystemDRM == null) + { + internalPropertySystemDRM = new PropertySystemDRM(shellObjectParent); + } + + return internalPropertySystemDRM; + } + } + private PropertySystemGPS internalPropertySystemGPS; + /// + /// System.GPS Properties + /// + public PropertySystemGPS GPS + { + get + { + if (internalPropertySystemGPS == null) + { + internalPropertySystemGPS = new PropertySystemGPS(shellObjectParent); + } + + return internalPropertySystemGPS; + } + } + private PropertySystemIdentity internalPropertySystemIdentity; + /// + /// System.Identity Properties + /// + public PropertySystemIdentity Identity + { + get + { + if (internalPropertySystemIdentity == null) + { + internalPropertySystemIdentity = new PropertySystemIdentity(shellObjectParent); + } + + return internalPropertySystemIdentity; + } + } + private PropertySystemIdentityProvider internalPropertySystemIdentityProvider; + /// + /// System.IdentityProvider Properties + /// + public PropertySystemIdentityProvider IdentityProvider + { + get + { + if (internalPropertySystemIdentityProvider == null) + { + internalPropertySystemIdentityProvider = new PropertySystemIdentityProvider(shellObjectParent); + } + + return internalPropertySystemIdentityProvider; + } + } + private PropertySystemImage internalPropertySystemImage; + /// + /// System.Image Properties + /// + public PropertySystemImage Image + { + get + { + if (internalPropertySystemImage == null) + { + internalPropertySystemImage = new PropertySystemImage(shellObjectParent); + } + + return internalPropertySystemImage; + } + } + private PropertySystemJournal internalPropertySystemJournal; + /// + /// System.Journal Properties + /// + public PropertySystemJournal Journal + { + get + { + if (internalPropertySystemJournal == null) + { + internalPropertySystemJournal = new PropertySystemJournal(shellObjectParent); + } + + return internalPropertySystemJournal; + } + } + private PropertySystemLayoutPattern internalPropertySystemLayoutPattern; + /// + /// System.LayoutPattern Properties + /// + public PropertySystemLayoutPattern LayoutPattern + { + get + { + if (internalPropertySystemLayoutPattern == null) + { + internalPropertySystemLayoutPattern = new PropertySystemLayoutPattern(shellObjectParent); + } + + return internalPropertySystemLayoutPattern; + } + } + private PropertySystemLink internalPropertySystemLink; + /// + /// System.Link Properties + /// + public PropertySystemLink Link + { + get + { + if (internalPropertySystemLink == null) + { + internalPropertySystemLink = new PropertySystemLink(shellObjectParent); + } + + return internalPropertySystemLink; + } + } + private PropertySystemMedia internalPropertySystemMedia; + /// + /// System.Media Properties + /// + public PropertySystemMedia Media + { + get + { + if (internalPropertySystemMedia == null) + { + internalPropertySystemMedia = new PropertySystemMedia(shellObjectParent); + } + + return internalPropertySystemMedia; + } + } + private PropertySystemMessage internalPropertySystemMessage; + /// + /// System.Message Properties + /// + public PropertySystemMessage Message + { + get + { + if (internalPropertySystemMessage == null) + { + internalPropertySystemMessage = new PropertySystemMessage(shellObjectParent); + } + + return internalPropertySystemMessage; + } + } + private PropertySystemMusic internalPropertySystemMusic; + /// + /// System.Music Properties + /// + public PropertySystemMusic Music + { + get + { + if (internalPropertySystemMusic == null) + { + internalPropertySystemMusic = new PropertySystemMusic(shellObjectParent); + } + + return internalPropertySystemMusic; + } + } + private PropertySystemNote internalPropertySystemNote; + /// + /// System.Note Properties + /// + public PropertySystemNote Note + { + get + { + if (internalPropertySystemNote == null) + { + internalPropertySystemNote = new PropertySystemNote(shellObjectParent); + } + + return internalPropertySystemNote; + } + } + private PropertySystemPhoto internalPropertySystemPhoto; + /// + /// System.Photo Properties + /// + public PropertySystemPhoto Photo + { + get + { + if (internalPropertySystemPhoto == null) + { + internalPropertySystemPhoto = new PropertySystemPhoto(shellObjectParent); + } + + return internalPropertySystemPhoto; + } + } + private PropertySystemPropGroup internalPropertySystemPropGroup; + /// + /// System.PropGroup Properties + /// + public PropertySystemPropGroup PropGroup + { + get + { + if (internalPropertySystemPropGroup == null) + { + internalPropertySystemPropGroup = new PropertySystemPropGroup(shellObjectParent); + } + + return internalPropertySystemPropGroup; + } + } + private PropertySystemPropList internalPropertySystemPropList; + /// + /// System.PropList Properties + /// + public PropertySystemPropList PropList + { + get + { + if (internalPropertySystemPropList == null) + { + internalPropertySystemPropList = new PropertySystemPropList(shellObjectParent); + } + + return internalPropertySystemPropList; + } + } + private PropertySystemRecordedTV internalPropertySystemRecordedTV; + /// + /// System.RecordedTV Properties + /// + public PropertySystemRecordedTV RecordedTV + { + get + { + if (internalPropertySystemRecordedTV == null) + { + internalPropertySystemRecordedTV = new PropertySystemRecordedTV(shellObjectParent); + } + + return internalPropertySystemRecordedTV; + } + } + private PropertySystemSearch internalPropertySystemSearch; + /// + /// System.Search Properties + /// + public PropertySystemSearch Search + { + get + { + if (internalPropertySystemSearch == null) + { + internalPropertySystemSearch = new PropertySystemSearch(shellObjectParent); + } + + return internalPropertySystemSearch; + } + } + private PropertySystemShell internalPropertySystemShell; + /// + /// System.Shell Properties + /// + public PropertySystemShell Shell + { + get + { + if (internalPropertySystemShell == null) + { + internalPropertySystemShell = new PropertySystemShell(shellObjectParent); + } + + return internalPropertySystemShell; + } + } + private PropertySystemSoftware internalPropertySystemSoftware; + /// + /// System.Software Properties + /// + public PropertySystemSoftware Software + { + get + { + if (internalPropertySystemSoftware == null) + { + internalPropertySystemSoftware = new PropertySystemSoftware(shellObjectParent); + } + + return internalPropertySystemSoftware; + } + } + private PropertySystemSync internalPropertySystemSync; + /// + /// System.Sync Properties + /// + public PropertySystemSync Sync + { + get + { + if (internalPropertySystemSync == null) + { + internalPropertySystemSync = new PropertySystemSync(shellObjectParent); + } + + return internalPropertySystemSync; + } + } + private PropertySystemTask internalPropertySystemTask; + /// + /// System.Task Properties + /// + public PropertySystemTask Task + { + get + { + if (internalPropertySystemTask == null) + { + internalPropertySystemTask = new PropertySystemTask(shellObjectParent); + } + + return internalPropertySystemTask; + } + } + private PropertySystemVideo internalPropertySystemVideo; + /// + /// System.Video Properties + /// + public PropertySystemVideo Video + { + get + { + if (internalPropertySystemVideo == null) + { + internalPropertySystemVideo = new PropertySystemVideo(shellObjectParent); + } + + return internalPropertySystemVideo; + } + } + private PropertySystemVolume internalPropertySystemVolume; + /// + /// System.Volume Properties + /// + public PropertySystemVolume Volume + { + get + { + if (internalPropertySystemVolume == null) + { + internalPropertySystemVolume = new PropertySystemVolume(shellObjectParent); + } + + return internalPropertySystemVolume; + } + } + + } + + /// + /// System.AppUserModel Properties + /// + public class PropertySystemAppUserModel : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemAppUserModel(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.AppUserModel.ExcludeFromShowInNewInstall -- PKEY_AppUserModel_ExcludeFromShowInNewInstall + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 8 + /// + public ShellProperty ExcludeFromShowInNewInstall + { + get + { + var key = SystemProperties.System.AppUserModel.ExcludeFromShowInNewInstall; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.AppUserModel.ID -- PKEY_AppUserModel_ID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 5 + /// + public ShellProperty ID + { + get + { + var key = SystemProperties.System.AppUserModel.ID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.AppUserModel.IsDestListSeparator -- PKEY_AppUserModel_IsDestListSeparator + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 6 + /// + public ShellProperty IsDestinationListSeparator + { + get + { + var key = SystemProperties.System.AppUserModel.IsDestinationListSeparator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.AppUserModel.PreventPinning -- PKEY_AppUserModel_PreventPinning + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 9 + /// + public ShellProperty PreventPinning + { + get + { + var key = SystemProperties.System.AppUserModel.PreventPinning; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.AppUserModel.RelaunchCommand -- PKEY_AppUserModel_RelaunchCommand + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 2 + /// + public ShellProperty RelaunchCommand + { + get + { + var key = SystemProperties.System.AppUserModel.RelaunchCommand; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.AppUserModel.RelaunchDisplayNameResource -- PKEY_AppUserModel_RelaunchDisplayNameResource + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 4 + /// + public ShellProperty RelaunchDisplayNameResource + { + get + { + var key = SystemProperties.System.AppUserModel.RelaunchDisplayNameResource; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.AppUserModel.RelaunchIconResource -- PKEY_AppUserModel_RelaunchIconResource + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 3 + /// + public ShellProperty RelaunchIconResource + { + get + { + var key = SystemProperties.System.AppUserModel.RelaunchIconResource; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Audio Properties + /// + public class PropertySystemAudio : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemAudio(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Audio.ChannelCount -- PKEY_Audio_ChannelCount + /// Description: Indicates the channel count for the audio file. Values: 1 (mono), 2 (stereo). + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 7 (PIDASI_CHANNEL_COUNT) + /// + public ShellProperty ChannelCount + { + get + { + var key = SystemProperties.System.Audio.ChannelCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Audio.Compression -- PKEY_Audio_Compression + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 10 (PIDASI_COMPRESSION) + /// + public ShellProperty Compression + { + get + { + var key = SystemProperties.System.Audio.Compression; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Audio.EncodingBitrate -- PKEY_Audio_EncodingBitrate + /// Description: Indicates the average data rate in Hz for the audio file in "bits per second". + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 4 (PIDASI_AVG_DATA_RATE) + /// + public ShellProperty EncodingBitrate + { + get + { + var key = SystemProperties.System.Audio.EncodingBitrate; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Audio.Format -- PKEY_Audio_Format + /// Description: Indicates the format of the audio file. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) Legacy code may treat this as VT_BSTR. + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 2 (PIDASI_FORMAT) + /// + public ShellProperty Format + { + get + { + var key = SystemProperties.System.Audio.Format; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Audio.IsVariableBitRate -- PKEY_Audio_IsVariableBitRate + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {E6822FEE-8C17-4D62-823C-8E9CFCBD1D5C}, 100 + /// + public ShellProperty IsVariableBitrate + { + get + { + var key = SystemProperties.System.Audio.IsVariableBitrate; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Audio.PeakValue -- PKEY_Audio_PeakValue + /// Description: + /// Type: UInt32 -- VT_UI4 + /// FormatID: {2579E5D0-1116-4084-BD9A-9B4F7CB4DF5E}, 100 + /// + public ShellProperty PeakValue + { + get + { + var key = SystemProperties.System.Audio.PeakValue; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Audio.SampleRate -- PKEY_Audio_SampleRate + /// Description: Indicates the audio sample rate for the audio file in "samples per second". + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 5 (PIDASI_SAMPLE_RATE) + /// + public ShellProperty SampleRate + { + get + { + var key = SystemProperties.System.Audio.SampleRate; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Audio.SampleSize -- PKEY_Audio_SampleSize + /// Description: Indicates the audio sample size for the audio file in "bits per sample". + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 6 (PIDASI_SAMPLE_SIZE) + /// + public ShellProperty SampleSize + { + get + { + var key = SystemProperties.System.Audio.SampleSize; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Audio.StreamName -- PKEY_Audio_StreamName + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 9 (PIDASI_STREAM_NAME) + /// + public ShellProperty StreamName + { + get + { + var key = SystemProperties.System.Audio.StreamName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Audio.StreamNumber -- PKEY_Audio_StreamNumber + /// Description: + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 8 (PIDASI_STREAM_NUMBER) + /// + public ShellProperty StreamNumber + { + get + { + var key = SystemProperties.System.Audio.StreamNumber; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Calendar Properties + /// + public class PropertySystemCalendar : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemCalendar(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Calendar.Duration -- PKEY_Calendar_Duration + /// Description: The duration as specified in a string. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {293CA35A-09AA-4DD2-B180-1FE245728A52}, 100 + /// + public ShellProperty Duration + { + get + { + var key = SystemProperties.System.Calendar.Duration; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.IsOnline -- PKEY_Calendar_IsOnline + /// Description: Identifies if the event is an online event. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {BFEE9149-E3E2-49A7-A862-C05988145CEC}, 100 + /// + public ShellProperty IsOnline + { + get + { + var key = SystemProperties.System.Calendar.IsOnline; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.IsRecurring -- PKEY_Calendar_IsRecurring + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {315B9C8D-80A9-4EF9-AE16-8E746DA51D70}, 100 + /// + public ShellProperty IsRecurring + { + get + { + var key = SystemProperties.System.Calendar.IsRecurring; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.Location -- PKEY_Calendar_Location + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F6272D18-CECC-40B1-B26A-3911717AA7BD}, 100 + /// + public ShellProperty Location + { + get + { + var key = SystemProperties.System.Calendar.Location; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.OptionalAttendeeAddresses -- PKEY_Calendar_OptionalAttendeeAddresses + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D55BAE5A-3892-417A-A649-C6AC5AAAEAB3}, 100 + /// + public ShellProperty OptionalAttendeeAddresses + { + get + { + var key = SystemProperties.System.Calendar.OptionalAttendeeAddresses; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.OptionalAttendeeNames -- PKEY_Calendar_OptionalAttendeeNames + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {09429607-582D-437F-84C3-DE93A2B24C3C}, 100 + /// + public ShellProperty OptionalAttendeeNames + { + get + { + var key = SystemProperties.System.Calendar.OptionalAttendeeNames; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.OrganizerAddress -- PKEY_Calendar_OrganizerAddress + /// Description: Address of the organizer organizing the event. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {744C8242-4DF5-456C-AB9E-014EFB9021E3}, 100 + /// + public ShellProperty OrganizerAddress + { + get + { + var key = SystemProperties.System.Calendar.OrganizerAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.OrganizerName -- PKEY_Calendar_OrganizerName + /// Description: Name of the organizer organizing the event. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {AAA660F9-9865-458E-B484-01BC7FE3973E}, 100 + /// + public ShellProperty OrganizerName + { + get + { + var key = SystemProperties.System.Calendar.OrganizerName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.ReminderTime -- PKEY_Calendar_ReminderTime + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {72FC5BA4-24F9-4011-9F3F-ADD27AFAD818}, 100 + /// + public ShellProperty ReminderTime + { + get + { + var key = SystemProperties.System.Calendar.ReminderTime; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.RequiredAttendeeAddresses -- PKEY_Calendar_RequiredAttendeeAddresses + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {0BA7D6C3-568D-4159-AB91-781A91FB71E5}, 100 + /// + public ShellProperty RequiredAttendeeAddresses + { + get + { + var key = SystemProperties.System.Calendar.RequiredAttendeeAddresses; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.RequiredAttendeeNames -- PKEY_Calendar_RequiredAttendeeNames + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {B33AF30B-F552-4584-936C-CB93E5CDA29F}, 100 + /// + public ShellProperty RequiredAttendeeNames + { + get + { + var key = SystemProperties.System.Calendar.RequiredAttendeeNames; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.Resources -- PKEY_Calendar_Resources + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {00F58A38-C54B-4C40-8696-97235980EAE1}, 100 + /// + public ShellProperty Resources + { + get + { + var key = SystemProperties.System.Calendar.Resources; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.ResponseStatus -- PKEY_Calendar_ResponseStatus + /// Description: This property stores the status of the user responses to meetings in her calendar. + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {188C1F91-3C40-4132-9EC5-D8B03B72A8A2}, 100 + /// + public ShellProperty ResponseStatus + { + get + { + var key = SystemProperties.System.Calendar.ResponseStatus; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.ShowTimeAs -- PKEY_Calendar_ShowTimeAs + /// Description: + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {5BF396D4-5EB2-466F-BDE9-2FB3F2361D6E}, 100 + /// + public ShellProperty ShowTimeAs + { + get + { + var key = SystemProperties.System.Calendar.ShowTimeAs; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Calendar.ShowTimeAsText -- PKEY_Calendar_ShowTimeAsText + /// Description: This is the user-friendly form of System.Calendar.ShowTimeAs. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {53DA57CF-62C0-45C4-81DE-7610BCEFD7F5}, 100 + /// + public ShellProperty ShowTimeAsText + { + get + { + var key = SystemProperties.System.Calendar.ShowTimeAsText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Communication Properties + /// + public class PropertySystemCommunication : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemCommunication(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Communication.AccountName -- PKEY_Communication_AccountName + /// Description: Account Name + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 9 + /// + public ShellProperty AccountName + { + get + { + var key = SystemProperties.System.Communication.AccountName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Communication.DateItemExpires -- PKEY_Communication_DateItemExpires + /// Description: Date the item expires due to the retention policy. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {428040AC-A177-4C8A-9760-F6F761227F9A}, 100 + /// + public ShellProperty DateItemExpires + { + get + { + var key = SystemProperties.System.Communication.DateItemExpires; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Communication.FollowupIconIndex -- PKEY_Communication_FollowupIconIndex + /// Description: This is the icon index used on messages marked for followup. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {83A6347E-6FE4-4F40-BA9C-C4865240D1F4}, 100 + /// + public ShellProperty FollowUpIconIndex + { + get + { + var key = SystemProperties.System.Communication.FollowUpIconIndex; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Communication.HeaderItem -- PKEY_Communication_HeaderItem + /// Description: This property will be true if the item is a header item which means the item hasn't been fully downloaded. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {C9C34F84-2241-4401-B607-BD20ED75AE7F}, 100 + /// + public ShellProperty HeaderItem + { + get + { + var key = SystemProperties.System.Communication.HeaderItem; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Communication.PolicyTag -- PKEY_Communication_PolicyTag + /// Description: This a string used to identify the retention policy applied to the item. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {EC0B4191-AB0B-4C66-90B6-C6637CDEBBAB}, 100 + /// + public ShellProperty PolicyTag + { + get + { + var key = SystemProperties.System.Communication.PolicyTag; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Communication.SecurityFlags -- PKEY_Communication_SecurityFlags + /// Description: Security flags associated with the item to know if the item is encrypted, signed or DRM enabled. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {8619A4B6-9F4D-4429-8C0F-B996CA59E335}, 100 + /// + public ShellProperty SecurityFlags + { + get + { + var key = SystemProperties.System.Communication.SecurityFlags; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Communication.Suffix -- PKEY_Communication_Suffix + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {807B653A-9E91-43EF-8F97-11CE04EE20C5}, 100 + /// + public ShellProperty Suffix + { + get + { + var key = SystemProperties.System.Communication.Suffix; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Communication.TaskStatus -- PKEY_Communication_TaskStatus + /// Description: + /// Type: UInt16 -- VT_UI2 + /// FormatID: {BE1A72C6-9A1D-46B7-AFE7-AFAF8CEF4999}, 100 + /// + public ShellProperty TaskStatus + { + get + { + var key = SystemProperties.System.Communication.TaskStatus; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Communication.TaskStatusText -- PKEY_Communication_TaskStatusText + /// Description: This is the user-friendly form of System.Communication.TaskStatus. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A6744477-C237-475B-A075-54F34498292A}, 100 + /// + public ShellProperty TaskStatusText + { + get + { + var key = SystemProperties.System.Communication.TaskStatusText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Computer Properties + /// + public class PropertySystemComputer : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemComputer(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Computer.DecoratedFreeSpace -- PKEY_Computer_DecoratedFreeSpace + /// Description: Free space and total space: "%s free of %s" + /// + /// Type: Multivalue UInt64 -- VT_VECTOR | VT_UI8 (For variants: VT_ARRAY | VT_UI8) + /// FormatID: (FMTID_Volume) {9B174B35-40FF-11D2-A27E-00C04FC30871}, 7 (Filesystem Volume Properties) + /// + public ShellProperty DecoratedFreeSpace + { + get + { + var key = SystemProperties.System.Computer.DecoratedFreeSpace; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Contact Properties + /// + public class PropertySystemContact : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemContact(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Contact.Anniversary -- PKEY_Contact_Anniversary + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {9AD5BADB-CEA7-4470-A03D-B84E51B9949E}, 100 + /// + public ShellProperty Anniversary + { + get + { + var key = SystemProperties.System.Contact.Anniversary; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.AssistantName -- PKEY_Contact_AssistantName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CD102C9C-5540-4A88-A6F6-64E4981C8CD1}, 100 + /// + public ShellProperty AssistantName + { + get + { + var key = SystemProperties.System.Contact.AssistantName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.AssistantTelephone -- PKEY_Contact_AssistantTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9A93244D-A7AD-4FF8-9B99-45EE4CC09AF6}, 100 + /// + public ShellProperty AssistantTelephone + { + get + { + var key = SystemProperties.System.Contact.AssistantTelephone; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.Birthday -- PKEY_Contact_Birthday + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 47 + /// + public ShellProperty Birthday + { + get + { + var key = SystemProperties.System.Contact.Birthday; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.BusinessAddress -- PKEY_Contact_BusinessAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {730FB6DD-CF7C-426B-A03F-BD166CC9EE24}, 100 + /// + public ShellProperty BusinessAddress + { + get + { + var key = SystemProperties.System.Contact.BusinessAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.BusinessAddressCity -- PKEY_Contact_BusinessAddressCity + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {402B5934-EC5A-48C3-93E6-85E86A2D934E}, 100 + /// + public ShellProperty BusinessAddressCity + { + get + { + var key = SystemProperties.System.Contact.BusinessAddressCity; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.BusinessAddressCountry -- PKEY_Contact_BusinessAddressCountry + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {B0B87314-FCF6-4FEB-8DFF-A50DA6AF561C}, 100 + /// + public ShellProperty BusinessAddressCountry + { + get + { + var key = SystemProperties.System.Contact.BusinessAddressCountry; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.BusinessAddressPostalCode -- PKEY_Contact_BusinessAddressPostalCode + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E1D4A09E-D758-4CD1-B6EC-34A8B5A73F80}, 100 + /// + public ShellProperty BusinessAddressPostalCode + { + get + { + var key = SystemProperties.System.Contact.BusinessAddressPostalCode; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.BusinessAddressPostOfficeBox -- PKEY_Contact_BusinessAddressPostOfficeBox + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {BC4E71CE-17F9-48D5-BEE9-021DF0EA5409}, 100 + /// + public ShellProperty BusinessAddressPostOfficeBox + { + get + { + var key = SystemProperties.System.Contact.BusinessAddressPostOfficeBox; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.BusinessAddressState -- PKEY_Contact_BusinessAddressState + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {446F787F-10C4-41CB-A6C4-4D0343551597}, 100 + /// + public ShellProperty BusinessAddressState + { + get + { + var key = SystemProperties.System.Contact.BusinessAddressState; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.BusinessAddressStreet -- PKEY_Contact_BusinessAddressStreet + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DDD1460F-C0BF-4553-8CE4-10433C908FB0}, 100 + /// + public ShellProperty BusinessAddressStreet + { + get + { + var key = SystemProperties.System.Contact.BusinessAddressStreet; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.BusinessFaxNumber -- PKEY_Contact_BusinessFaxNumber + /// Description: Business fax number of the contact. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {91EFF6F3-2E27-42CA-933E-7C999FBE310B}, 100 + /// + public ShellProperty BusinessFaxNumber + { + get + { + var key = SystemProperties.System.Contact.BusinessFaxNumber; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.BusinessHomePage -- PKEY_Contact_BusinessHomePage + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {56310920-2491-4919-99CE-EADB06FAFDB2}, 100 + /// + public ShellProperty BusinessHomepage + { + get + { + var key = SystemProperties.System.Contact.BusinessHomepage; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.BusinessTelephone -- PKEY_Contact_BusinessTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6A15E5A0-0A1E-4CD7-BB8C-D2F1B0C929BC}, 100 + /// + public ShellProperty BusinessTelephone + { + get + { + var key = SystemProperties.System.Contact.BusinessTelephone; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.CallbackTelephone -- PKEY_Contact_CallbackTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {BF53D1C3-49E0-4F7F-8567-5A821D8AC542}, 100 + /// + public ShellProperty CallbackTelephone + { + get + { + var key = SystemProperties.System.Contact.CallbackTelephone; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.CarTelephone -- PKEY_Contact_CarTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {8FDC6DEA-B929-412B-BA90-397A257465FE}, 100 + /// + public ShellProperty CarTelephone + { + get + { + var key = SystemProperties.System.Contact.CarTelephone; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.Children -- PKEY_Contact_Children + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D4729704-8EF1-43EF-9024-2BD381187FD5}, 100 + /// + public ShellProperty Children + { + get + { + var key = SystemProperties.System.Contact.Children; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.CompanyMainTelephone -- PKEY_Contact_CompanyMainTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {8589E481-6040-473D-B171-7FA89C2708ED}, 100 + /// + public ShellProperty CompanyMainTelephone + { + get + { + var key = SystemProperties.System.Contact.CompanyMainTelephone; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.Department -- PKEY_Contact_Department + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FC9F7306-FF8F-4D49-9FB6-3FFE5C0951EC}, 100 + /// + public ShellProperty Department + { + get + { + var key = SystemProperties.System.Contact.Department; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.EmailAddress -- PKEY_Contact_EmailAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F8FA7FA3-D12B-4785-8A4E-691A94F7A3E7}, 100 + /// + public ShellProperty EmailAddress + { + get + { + var key = SystemProperties.System.Contact.EmailAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.EmailAddress2 -- PKEY_Contact_EmailAddress2 + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {38965063-EDC8-4268-8491-B7723172CF29}, 100 + /// + public ShellProperty EmailAddress2 + { + get + { + var key = SystemProperties.System.Contact.EmailAddress2; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.EmailAddress3 -- PKEY_Contact_EmailAddress3 + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {644D37B4-E1B3-4BAD-B099-7E7C04966ACA}, 100 + /// + public ShellProperty EmailAddress3 + { + get + { + var key = SystemProperties.System.Contact.EmailAddress3; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.EmailAddresses -- PKEY_Contact_EmailAddresses + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {84D8F337-981D-44B3-9615-C7596DBA17E3}, 100 + /// + public ShellProperty EmailAddresses + { + get + { + var key = SystemProperties.System.Contact.EmailAddresses; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.EmailName -- PKEY_Contact_EmailName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CC6F4F24-6083-4BD4-8754-674D0DE87AB8}, 100 + /// + public ShellProperty EmailName + { + get + { + var key = SystemProperties.System.Contact.EmailName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.FileAsName -- PKEY_Contact_FileAsName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F1A24AA7-9CA7-40F6-89EC-97DEF9FFE8DB}, 100 + /// + public ShellProperty FileAsName + { + get + { + var key = SystemProperties.System.Contact.FileAsName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.FirstName -- PKEY_Contact_FirstName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {14977844-6B49-4AAD-A714-A4513BF60460}, 100 + /// + public ShellProperty FirstName + { + get + { + var key = SystemProperties.System.Contact.FirstName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.FullName -- PKEY_Contact_FullName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {635E9051-50A5-4BA2-B9DB-4ED056C77296}, 100 + /// + public ShellProperty FullName + { + get + { + var key = SystemProperties.System.Contact.FullName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.Gender -- PKEY_Contact_Gender + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {3C8CEE58-D4F0-4CF9-B756-4E5D24447BCD}, 100 + /// + public ShellProperty Gender + { + get + { + var key = SystemProperties.System.Contact.Gender; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.GenderValue -- PKEY_Contact_GenderValue + /// Description: + /// Type: UInt16 -- VT_UI2 + /// FormatID: {3C8CEE58-D4F0-4CF9-B756-4E5D24447BCD}, 101 + /// + public ShellProperty GenderValue + { + get + { + var key = SystemProperties.System.Contact.GenderValue; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.Hobbies -- PKEY_Contact_Hobbies + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {5DC2253F-5E11-4ADF-9CFE-910DD01E3E70}, 100 + /// + public ShellProperty Hobbies + { + get + { + var key = SystemProperties.System.Contact.Hobbies; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.HomeAddress -- PKEY_Contact_HomeAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {98F98354-617A-46B8-8560-5B1B64BF1F89}, 100 + /// + public ShellProperty HomeAddress + { + get + { + var key = SystemProperties.System.Contact.HomeAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.HomeAddressCity -- PKEY_Contact_HomeAddressCity + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 65 + /// + public ShellProperty HomeAddressCity + { + get + { + var key = SystemProperties.System.Contact.HomeAddressCity; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.HomeAddressCountry -- PKEY_Contact_HomeAddressCountry + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {08A65AA1-F4C9-43DD-9DDF-A33D8E7EAD85}, 100 + /// + public ShellProperty HomeAddressCountry + { + get + { + var key = SystemProperties.System.Contact.HomeAddressCountry; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.HomeAddressPostalCode -- PKEY_Contact_HomeAddressPostalCode + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {8AFCC170-8A46-4B53-9EEE-90BAE7151E62}, 100 + /// + public ShellProperty HomeAddressPostalCode + { + get + { + var key = SystemProperties.System.Contact.HomeAddressPostalCode; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.HomeAddressPostOfficeBox -- PKEY_Contact_HomeAddressPostOfficeBox + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7B9F6399-0A3F-4B12-89BD-4ADC51C918AF}, 100 + /// + public ShellProperty HomeAddressPostOfficeBox + { + get + { + var key = SystemProperties.System.Contact.HomeAddressPostOfficeBox; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.HomeAddressState -- PKEY_Contact_HomeAddressState + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C89A23D0-7D6D-4EB8-87D4-776A82D493E5}, 100 + /// + public ShellProperty HomeAddressState + { + get + { + var key = SystemProperties.System.Contact.HomeAddressState; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.HomeAddressStreet -- PKEY_Contact_HomeAddressStreet + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0ADEF160-DB3F-4308-9A21-06237B16FA2A}, 100 + /// + public ShellProperty HomeAddressStreet + { + get + { + var key = SystemProperties.System.Contact.HomeAddressStreet; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.HomeFaxNumber -- PKEY_Contact_HomeFaxNumber + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {660E04D6-81AB-4977-A09F-82313113AB26}, 100 + /// + public ShellProperty HomeFaxNumber + { + get + { + var key = SystemProperties.System.Contact.HomeFaxNumber; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.HomeTelephone -- PKEY_Contact_HomeTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 20 + /// + public ShellProperty HomeTelephone + { + get + { + var key = SystemProperties.System.Contact.HomeTelephone; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.IMAddress -- PKEY_Contact_IMAddress + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D68DBD8A-3374-4B81-9972-3EC30682DB3D}, 100 + /// + public ShellProperty IMAddress + { + get + { + var key = SystemProperties.System.Contact.IMAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.Initials -- PKEY_Contact_Initials + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F3D8F40D-50CB-44A2-9718-40CB9119495D}, 100 + /// + public ShellProperty Initials + { + get + { + var key = SystemProperties.System.Contact.Initials; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.JobTitle -- PKEY_Contact_JobTitle + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 6 + /// + public ShellProperty JobTitle + { + get + { + var key = SystemProperties.System.Contact.JobTitle; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.Label -- PKEY_Contact_Label + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {97B0AD89-DF49-49CC-834E-660974FD755B}, 100 + /// + public ShellProperty Label + { + get + { + var key = SystemProperties.System.Contact.Label; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.LastName -- PKEY_Contact_LastName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {8F367200-C270-457C-B1D4-E07C5BCD90C7}, 100 + /// + public ShellProperty LastName + { + get + { + var key = SystemProperties.System.Contact.LastName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.MailingAddress -- PKEY_Contact_MailingAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C0AC206A-827E-4650-95AE-77E2BB74FCC9}, 100 + /// + public ShellProperty MailingAddress + { + get + { + var key = SystemProperties.System.Contact.MailingAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.MiddleName -- PKEY_Contact_MiddleName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 71 + /// + public ShellProperty MiddleName + { + get + { + var key = SystemProperties.System.Contact.MiddleName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.MobileTelephone -- PKEY_Contact_MobileTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 35 + /// + public ShellProperty MobileTelephone + { + get + { + var key = SystemProperties.System.Contact.MobileTelephone; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.NickName -- PKEY_Contact_NickName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 74 + /// + public ShellProperty Nickname + { + get + { + var key = SystemProperties.System.Contact.Nickname; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.OfficeLocation -- PKEY_Contact_OfficeLocation + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 7 + /// + public ShellProperty OfficeLocation + { + get + { + var key = SystemProperties.System.Contact.OfficeLocation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.OtherAddress -- PKEY_Contact_OtherAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {508161FA-313B-43D5-83A1-C1ACCF68622C}, 100 + /// + public ShellProperty OtherAddress + { + get + { + var key = SystemProperties.System.Contact.OtherAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.OtherAddressCity -- PKEY_Contact_OtherAddressCity + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6E682923-7F7B-4F0C-A337-CFCA296687BF}, 100 + /// + public ShellProperty OtherAddressCity + { + get + { + var key = SystemProperties.System.Contact.OtherAddressCity; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.OtherAddressCountry -- PKEY_Contact_OtherAddressCountry + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {8F167568-0AAE-4322-8ED9-6055B7B0E398}, 100 + /// + public ShellProperty OtherAddressCountry + { + get + { + var key = SystemProperties.System.Contact.OtherAddressCountry; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.OtherAddressPostalCode -- PKEY_Contact_OtherAddressPostalCode + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {95C656C1-2ABF-4148-9ED3-9EC602E3B7CD}, 100 + /// + public ShellProperty OtherAddressPostalCode + { + get + { + var key = SystemProperties.System.Contact.OtherAddressPostalCode; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.OtherAddressPostOfficeBox -- PKEY_Contact_OtherAddressPostOfficeBox + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {8B26EA41-058F-43F6-AECC-4035681CE977}, 100 + /// + public ShellProperty OtherAddressPostOfficeBox + { + get + { + var key = SystemProperties.System.Contact.OtherAddressPostOfficeBox; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.OtherAddressState -- PKEY_Contact_OtherAddressState + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {71B377D6-E570-425F-A170-809FAE73E54E}, 100 + /// + public ShellProperty OtherAddressState + { + get + { + var key = SystemProperties.System.Contact.OtherAddressState; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.OtherAddressStreet -- PKEY_Contact_OtherAddressStreet + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FF962609-B7D6-4999-862D-95180D529AEA}, 100 + /// + public ShellProperty OtherAddressStreet + { + get + { + var key = SystemProperties.System.Contact.OtherAddressStreet; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.PagerTelephone -- PKEY_Contact_PagerTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D6304E01-F8F5-4F45-8B15-D024A6296789}, 100 + /// + public ShellProperty PagerTelephone + { + get + { + var key = SystemProperties.System.Contact.PagerTelephone; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.PersonalTitle -- PKEY_Contact_PersonalTitle + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 69 + /// + public ShellProperty PersonalTitle + { + get + { + var key = SystemProperties.System.Contact.PersonalTitle; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.PrimaryAddressCity -- PKEY_Contact_PrimaryAddressCity + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C8EA94F0-A9E3-4969-A94B-9C62A95324E0}, 100 + /// + public ShellProperty PrimaryAddressCity + { + get + { + var key = SystemProperties.System.Contact.PrimaryAddressCity; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.PrimaryAddressCountry -- PKEY_Contact_PrimaryAddressCountry + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E53D799D-0F3F-466E-B2FF-74634A3CB7A4}, 100 + /// + public ShellProperty PrimaryAddressCountry + { + get + { + var key = SystemProperties.System.Contact.PrimaryAddressCountry; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.PrimaryAddressPostalCode -- PKEY_Contact_PrimaryAddressPostalCode + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {18BBD425-ECFD-46EF-B612-7B4A6034EDA0}, 100 + /// + public ShellProperty PrimaryAddressPostalCode + { + get + { + var key = SystemProperties.System.Contact.PrimaryAddressPostalCode; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.PrimaryAddressPostOfficeBox -- PKEY_Contact_PrimaryAddressPostOfficeBox + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DE5EF3C7-46E1-484E-9999-62C5308394C1}, 100 + /// + public ShellProperty PrimaryAddressPostOfficeBox + { + get + { + var key = SystemProperties.System.Contact.PrimaryAddressPostOfficeBox; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.PrimaryAddressState -- PKEY_Contact_PrimaryAddressState + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F1176DFE-7138-4640-8B4C-AE375DC70A6D}, 100 + /// + public ShellProperty PrimaryAddressState + { + get + { + var key = SystemProperties.System.Contact.PrimaryAddressState; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.PrimaryAddressStreet -- PKEY_Contact_PrimaryAddressStreet + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {63C25B20-96BE-488F-8788-C09C407AD812}, 100 + /// + public ShellProperty PrimaryAddressStreet + { + get + { + var key = SystemProperties.System.Contact.PrimaryAddressStreet; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.PrimaryEmailAddress -- PKEY_Contact_PrimaryEmailAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 48 + /// + public ShellProperty PrimaryEmailAddress + { + get + { + var key = SystemProperties.System.Contact.PrimaryEmailAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.PrimaryTelephone -- PKEY_Contact_PrimaryTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 25 + /// + public ShellProperty PrimaryTelephone + { + get + { + var key = SystemProperties.System.Contact.PrimaryTelephone; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.Profession -- PKEY_Contact_Profession + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7268AF55-1CE4-4F6E-A41F-B6E4EF10E4A9}, 100 + /// + public ShellProperty Profession + { + get + { + var key = SystemProperties.System.Contact.Profession; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.SpouseName -- PKEY_Contact_SpouseName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9D2408B6-3167-422B-82B0-F583B7A7CFE3}, 100 + /// + public ShellProperty SpouseName + { + get + { + var key = SystemProperties.System.Contact.SpouseName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.Suffix -- PKEY_Contact_Suffix + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 73 + /// + public ShellProperty Suffix + { + get + { + var key = SystemProperties.System.Contact.Suffix; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.TelexNumber -- PKEY_Contact_TelexNumber + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C554493C-C1F7-40C1-A76C-EF8C0614003E}, 100 + /// + public ShellProperty TelexNumber + { + get + { + var key = SystemProperties.System.Contact.TelexNumber; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.TTYTDDTelephone -- PKEY_Contact_TTYTDDTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {AAF16BAC-2B55-45E6-9F6D-415EB94910DF}, 100 + /// + public ShellProperty TTYTDDTelephone + { + get + { + var key = SystemProperties.System.Contact.TTYTDDTelephone; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.WebPage -- PKEY_Contact_WebPage + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 18 + /// + public ShellProperty Webpage + { + get + { + var key = SystemProperties.System.Contact.Webpage; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + private PropertyContactJA internalPropertyContactJA; + /// + /// Contact.JA Properties + /// + public PropertyContactJA JA + { + get + { + if (internalPropertyContactJA == null) + { + internalPropertyContactJA = new PropertyContactJA(shellObjectParent); + } + + return internalPropertyContactJA; + } + } + + } + + /// + /// Contact.JA Properties + /// + public class PropertyContactJA : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertyContactJA(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Contact.JA.CompanyNamePhonetic -- PKEY_Contact_JA_CompanyNamePhonetic + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {897B3694-FE9E-43E6-8066-260F590C0100}, 2 + /// + public ShellProperty CompanyNamePhonetic + { + get + { + var key = SystemProperties.System.Contact.JA.CompanyNamePhonetic; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.JA.FirstNamePhonetic -- PKEY_Contact_JA_FirstNamePhonetic + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {897B3694-FE9E-43E6-8066-260F590C0100}, 3 + /// + public ShellProperty FirstNamePhonetic + { + get + { + var key = SystemProperties.System.Contact.JA.FirstNamePhonetic; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Contact.JA.LastNamePhonetic -- PKEY_Contact_JA_LastNamePhonetic + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {897B3694-FE9E-43E6-8066-260F590C0100}, 4 + /// + public ShellProperty LastNamePhonetic + { + get + { + var key = SystemProperties.System.Contact.JA.LastNamePhonetic; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Device Properties + /// + public class PropertySystemDevice : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemDevice(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Device.PrinterURL -- PKEY_Device_PrinterURL + /// Description: Printer information Printer URL. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0B48F35A-BE6E-4F17-B108-3C4073D1669A}, 15 + /// + public ShellProperty PrinterUrl + { + get + { + var key = SystemProperties.System.Device.PrinterUrl; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.DeviceInterface Properties + /// + public class PropertySystemDeviceInterface : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemDeviceInterface(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.DeviceInterface.PrinterDriverDirectory -- PKEY_DeviceInterface_PrinterDriverDirectory + /// Description: Printer information Printer Driver Directory. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {847C66DE-B8D6-4AF9-ABC3-6F4F926BC039}, 14 + /// + public ShellProperty PrinterDriverDirectory + { + get + { + var key = SystemProperties.System.DeviceInterface.PrinterDriverDirectory; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DeviceInterface.PrinterDriverName -- PKEY_DeviceInterface_PrinterDriverName + /// Description: Printer information Driver Name. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {AFC47170-14F5-498C-8F30-B0D19BE449C6}, 11 + /// + public ShellProperty PrinterDriverName + { + get + { + var key = SystemProperties.System.DeviceInterface.PrinterDriverName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DeviceInterface.PrinterName -- PKEY_DeviceInterface_PrinterName + /// Description: Printer information Printer Name. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0A7B84EF-0C27-463F-84EF-06C5070001BE}, 10 + /// + public ShellProperty PrinterName + { + get + { + var key = SystemProperties.System.DeviceInterface.PrinterName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DeviceInterface.PrinterPortName -- PKEY_DeviceInterface_PrinterPortName + /// Description: Printer information Port Name. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {EEC7B761-6F94-41B1-949F-C729720DD13C}, 12 + /// + public ShellProperty PrinterPortName + { + get + { + var key = SystemProperties.System.DeviceInterface.PrinterPortName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Devices Properties + /// + public class PropertySystemDevices : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemDevices(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Devices.BatteryLife -- PKEY_Devices_BatteryLife + /// Description: Remaining battery life of the device as an integer between 0 and 100 percent. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 10 + /// + public ShellProperty BatteryLife + { + get + { + var key = SystemProperties.System.Devices.BatteryLife; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.BatteryPlusCharging -- PKEY_Devices_BatteryPlusCharging + /// Description: Remaining battery life of the device as an integer between 0 and 100 percent and the device's charging state. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 22 + /// + public ShellProperty BatteryPlusCharging + { + get + { + var key = SystemProperties.System.Devices.BatteryPlusCharging; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.BatteryPlusChargingText -- PKEY_Devices_BatteryPlusChargingText + /// Description: Remaining battery life of the device and the device's charging state as a string. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 23 + /// + public ShellProperty BatteryPlusChargingText + { + get + { + var key = SystemProperties.System.Devices.BatteryPlusChargingText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Category -- PKEY_Devices_Category_Desc_Singular + /// Description: Singular form of device category. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 91 + /// + public ShellProperty Category + { + get + { + var key = SystemProperties.System.Devices.Category; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.CategoryGroup -- PKEY_Devices_CategoryGroup_Desc + /// Description: Plural form of device category. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 94 + /// + public ShellProperty CategoryGroup + { + get + { + var key = SystemProperties.System.Devices.CategoryGroup; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.CategoryPlural -- PKEY_Devices_Category_Desc_Plural + /// Description: Plural form of device category. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 92 + /// + public ShellProperty CategoryPlural + { + get + { + var key = SystemProperties.System.Devices.CategoryPlural; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.ChargingState -- PKEY_Devices_ChargingState + /// Description: Boolean value representing if the device is currently charging. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 11 + /// + public ShellProperty ChargingState + { + get + { + var key = SystemProperties.System.Devices.ChargingState; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Connected -- PKEY_Devices_IsConnected + /// Description: Device connection state. If VARIANT_TRUE, indicates the device is currently connected to the computer. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 55 + /// + public ShellProperty Connected + { + get + { + var key = SystemProperties.System.Devices.Connected; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.ContainerId -- PKEY_Devices_ContainerId + /// Description: Device container ID. + /// + /// Type: Guid -- VT_CLSID + /// FormatID: {8C7ED206-3F8A-4827-B3AB-AE9E1FAEFC6C}, 2 + /// + public ShellProperty ContainerId + { + get + { + var key = SystemProperties.System.Devices.ContainerId; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.DefaultTooltip -- PKEY_Devices_DefaultTooltip + /// Description: Tooltip for default state + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {880F70A2-6082-47AC-8AAB-A739D1A300C3}, 153 + /// + public ShellProperty DefaultTooltip + { + get + { + var key = SystemProperties.System.Devices.DefaultTooltip; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.DeviceDescription1 -- PKEY_Devices_DeviceDescription1 + /// Description: First line of descriptive text about the device. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 81 + /// + public ShellProperty DeviceDescription1 + { + get + { + var key = SystemProperties.System.Devices.DeviceDescription1; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.DeviceDescription2 -- PKEY_Devices_DeviceDescription2 + /// Description: Second line of descriptive text about the device. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 82 + /// + public ShellProperty DeviceDescription2 + { + get + { + var key = SystemProperties.System.Devices.DeviceDescription2; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.DiscoveryMethod -- PKEY_Devices_DiscoveryMethod + /// Description: Device discovery method. This indicates on what transport or physical connection the device is discovered. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 52 + /// + public ShellProperty DiscoveryMethod + { + get + { + var key = SystemProperties.System.Devices.DiscoveryMethod; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.FriendlyName -- PKEY_Devices_FriendlyName + /// Description: Device friendly name. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {656A3BB3-ECC0-43FD-8477-4AE0404A96CD}, 12288 + /// + public ShellProperty FriendlyName + { + get + { + var key = SystemProperties.System.Devices.FriendlyName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.FunctionPaths -- PKEY_Devices_FunctionPaths + /// Description: Available functions for this device. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D08DD4C0-3A9E-462E-8290-7B636B2576B9}, 3 + /// + public ShellProperty FunctionPaths + { + get + { + var key = SystemProperties.System.Devices.FunctionPaths; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.InterfacePaths -- PKEY_Devices_InterfacePaths + /// Description: Available interfaces for this device. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D08DD4C0-3A9E-462E-8290-7B636B2576B9}, 2 + /// + public ShellProperty InterfacePaths + { + get + { + var key = SystemProperties.System.Devices.InterfacePaths; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.IsDefault -- PKEY_Devices_IsDefaultDevice + /// Description: If VARIANT_TRUE, the device is not working properly. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 86 + /// + public ShellProperty IsDefault + { + get + { + var key = SystemProperties.System.Devices.IsDefault; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.IsNetworkConnected -- PKEY_Devices_IsNetworkDevice + /// Description: If VARIANT_TRUE, the device is not working properly. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 85 + /// + public ShellProperty IsNetworkConnected + { + get + { + var key = SystemProperties.System.Devices.IsNetworkConnected; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.IsShared -- PKEY_Devices_IsSharedDevice + /// Description: If VARIANT_TRUE, the device is not working properly. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 84 + /// + public ShellProperty IsShared + { + get + { + var key = SystemProperties.System.Devices.IsShared; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.IsSoftwareInstalling -- PKEY_Devices_IsSoftwareInstalling + /// Description: If VARIANT_TRUE, the device installer is currently installing software. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {83DA6326-97A6-4088-9453-A1923F573B29}, 9 + /// + public ShellProperty IsSoftwareInstalling + { + get + { + var key = SystemProperties.System.Devices.IsSoftwareInstalling; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.LaunchDeviceStageFromExplorer -- PKEY_Devices_LaunchDeviceStageFromExplorer + /// Description: Indicates whether to launch Device Stage or not + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 77 + /// + public ShellProperty LaunchDeviceStageFromExplorer + { + get + { + var key = SystemProperties.System.Devices.LaunchDeviceStageFromExplorer; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.LocalMachine -- PKEY_Devices_IsLocalMachine + /// Description: If VARIANT_TRUE, the device in question is actually the computer. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 70 + /// + public ShellProperty LocalMachine + { + get + { + var key = SystemProperties.System.Devices.LocalMachine; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Manufacturer -- PKEY_Devices_Manufacturer + /// Description: Device manufacturer. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {656A3BB3-ECC0-43FD-8477-4AE0404A96CD}, 8192 + /// + public ShellProperty Manufacturer + { + get + { + var key = SystemProperties.System.Devices.Manufacturer; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.MissedCalls -- PKEY_Devices_MissedCalls + /// Description: Number of missed calls on the device. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 5 + /// + public ShellProperty MissedCalls + { + get + { + var key = SystemProperties.System.Devices.MissedCalls; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.ModelName -- PKEY_Devices_ModelName + /// Description: Model name of the device. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {656A3BB3-ECC0-43FD-8477-4AE0404A96CD}, 8194 + /// + public ShellProperty ModelName + { + get + { + var key = SystemProperties.System.Devices.ModelName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.ModelNumber -- PKEY_Devices_ModelNumber + /// Description: Model number of the device. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {656A3BB3-ECC0-43FD-8477-4AE0404A96CD}, 8195 + /// + public ShellProperty ModelNumber + { + get + { + var key = SystemProperties.System.Devices.ModelNumber; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.NetworkedTooltip -- PKEY_Devices_NetworkedTooltip + /// Description: Tooltip for connection state + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {880F70A2-6082-47AC-8AAB-A739D1A300C3}, 152 + /// + public ShellProperty NetworkedTooltip + { + get + { + var key = SystemProperties.System.Devices.NetworkedTooltip; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.NetworkName -- PKEY_Devices_NetworkName + /// Description: Name of the device's network. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 7 + /// + public ShellProperty NetworkName + { + get + { + var key = SystemProperties.System.Devices.NetworkName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.NetworkType -- PKEY_Devices_NetworkType + /// Description: String representing the type of the device's network. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 8 + /// + public ShellProperty NetworkType + { + get + { + var key = SystemProperties.System.Devices.NetworkType; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.NewPictures -- PKEY_Devices_NewPictures + /// Description: Number of new pictures on the device. + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 4 + /// + public ShellProperty NewPictures + { + get + { + var key = SystemProperties.System.Devices.NewPictures; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Notification -- PKEY_Devices_Notification + /// Description: Device Notification Property. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {06704B0C-E830-4C81-9178-91E4E95A80A0}, 3 + /// + public ShellProperty Notification + { + get + { + var key = SystemProperties.System.Devices.Notification; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.NotificationStore -- PKEY_Devices_NotificationStore + /// Description: Device Notification Store. + /// + /// Type: Object -- VT_UNKNOWN + /// FormatID: {06704B0C-E830-4C81-9178-91E4E95A80A0}, 2 + /// + public ShellProperty NotificationStore + { + get + { + var key = SystemProperties.System.Devices.NotificationStore; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.NotWorkingProperly -- PKEY_Devices_IsNotWorkingProperly + /// Description: If VARIANT_TRUE, the device is not working properly. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 83 + /// + public ShellProperty NotWorkingProperly + { + get + { + var key = SystemProperties.System.Devices.NotWorkingProperly; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Paired -- PKEY_Devices_IsPaired + /// Description: Device paired state. If VARIANT_TRUE, indicates the device is not paired with the computer. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 56 + /// + public ShellProperty Paired + { + get + { + var key = SystemProperties.System.Devices.Paired; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.PrimaryCategory -- PKEY_Devices_PrimaryCategory + /// Description: Primary category group for this device. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D08DD4C0-3A9E-462E-8290-7B636B2576B9}, 10 + /// + public ShellProperty PrimaryCategory + { + get + { + var key = SystemProperties.System.Devices.PrimaryCategory; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Roaming -- PKEY_Devices_Roaming + /// Description: Status indicator used to indicate if the device is roaming. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 9 + /// + public ShellProperty Roaming + { + get + { + var key = SystemProperties.System.Devices.Roaming; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.SafeRemovalRequired -- PKEY_Devices_SafeRemovalRequired + /// Description: Indicates if a device requires safe removal or not + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {AFD97640-86A3-4210-B67C-289C41AABE55}, 2 + /// + public ShellProperty SafeRemovalRequired + { + get + { + var key = SystemProperties.System.Devices.SafeRemovalRequired; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.SharedTooltip -- PKEY_Devices_SharedTooltip + /// Description: Tooltip for sharing state + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {880F70A2-6082-47AC-8AAB-A739D1A300C3}, 151 + /// + public ShellProperty SharedTooltip + { + get + { + var key = SystemProperties.System.Devices.SharedTooltip; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.SignalStrength -- PKEY_Devices_SignalStrength + /// Description: Device signal strength. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 2 + /// + public ShellProperty SignalStrength + { + get + { + var key = SystemProperties.System.Devices.SignalStrength; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Status1 -- PKEY_Devices_Status1 + /// Description: 1st line of device status. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D08DD4C0-3A9E-462E-8290-7B636B2576B9}, 257 + /// + public ShellProperty Status1 + { + get + { + var key = SystemProperties.System.Devices.Status1; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Status2 -- PKEY_Devices_Status2 + /// Description: 2nd line of device status. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D08DD4C0-3A9E-462E-8290-7B636B2576B9}, 258 + /// + public ShellProperty Status2 + { + get + { + var key = SystemProperties.System.Devices.Status2; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.StorageCapacity -- PKEY_Devices_StorageCapacity + /// Description: Total storage capacity of the device. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 12 + /// + public ShellProperty StorageCapacity + { + get + { + var key = SystemProperties.System.Devices.StorageCapacity; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.StorageFreeSpace -- PKEY_Devices_StorageFreeSpace + /// Description: Total free space of the storage of the device. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 13 + /// + public ShellProperty StorageFreeSpace + { + get + { + var key = SystemProperties.System.Devices.StorageFreeSpace; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.StorageFreeSpacePercent -- PKEY_Devices_StorageFreeSpacePercent + /// Description: Total free space of the storage of the device as a percentage. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 14 + /// + public ShellProperty StorageFreeSpacePercent + { + get + { + var key = SystemProperties.System.Devices.StorageFreeSpacePercent; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.TextMessages -- PKEY_Devices_TextMessages + /// Description: Number of unread text messages on the device. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 3 + /// + public ShellProperty TextMessages + { + get + { + var key = SystemProperties.System.Devices.TextMessages; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Voicemail -- PKEY_Devices_Voicemail + /// Description: Status indicator used to indicate if the device has voicemail. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 6 + /// + public ShellProperty Voicemail + { + get + { + var key = SystemProperties.System.Devices.Voicemail; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + private PropertyDevicesNotifications internalPropertyDevicesNotifications; + /// + /// Devices.Notifications Properties + /// + public PropertyDevicesNotifications Notifications + { + get + { + if (internalPropertyDevicesNotifications == null) + { + internalPropertyDevicesNotifications = new PropertyDevicesNotifications(shellObjectParent); + } + + return internalPropertyDevicesNotifications; + } + } + + } + + /// + /// Devices.Notifications Properties + /// + public class PropertyDevicesNotifications : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertyDevicesNotifications(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Devices.Notifications.LowBattery -- PKEY_Devices_Notification_LowBattery + /// Description: Device Low Battery Notification. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {C4C07F2B-8524-4E66-AE3A-A6235F103BEB}, 2 + /// + public ShellProperty LowBattery + { + get + { + var key = SystemProperties.System.Devices.Notifications.LowBattery; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Notifications.MissedCall -- PKEY_Devices_Notification_MissedCall + /// Description: Device Missed Call Notification. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {6614EF48-4EFE-4424-9EDA-C79F404EDF3E}, 2 + /// + public ShellProperty MissedCall + { + get + { + var key = SystemProperties.System.Devices.Notifications.MissedCall; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Notifications.NewMessage -- PKEY_Devices_Notification_NewMessage + /// Description: Device New Message Notification. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {2BE9260A-2012-4742-A555-F41B638B7DCB}, 2 + /// + public ShellProperty NewMessage + { + get + { + var key = SystemProperties.System.Devices.Notifications.NewMessage; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Notifications.NewVoicemail -- PKEY_Devices_Notification_NewVoicemail + /// Description: Device Voicemail Notification. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {59569556-0A08-4212-95B9-FAE2AD6413DB}, 2 + /// + public ShellProperty NewVoicemail + { + get + { + var key = SystemProperties.System.Devices.Notifications.NewVoicemail; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Notifications.StorageFull -- PKEY_Devices_Notification_StorageFull + /// Description: Device Storage Full Notification. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: {A0E00EE1-F0C7-4D41-B8E7-26A7BD8D38B0}, 2 + /// + public ShellProperty StorageFull + { + get + { + var key = SystemProperties.System.Devices.Notifications.StorageFull; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Devices.Notifications.StorageFullLinkText -- PKEY_Devices_Notification_StorageFullLinkText + /// Description: Link Text for the Device Storage Full Notification. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: {A0E00EE1-F0C7-4D41-B8E7-26A7BD8D38B0}, 3 + /// + public ShellProperty StorageFullLinkText + { + get + { + var key = SystemProperties.System.Devices.Notifications.StorageFullLinkText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Document Properties + /// + public class PropertySystemDocument : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemDocument(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Document.ByteCount -- PKEY_Document_ByteCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 4 (PIDDSI_BYTECOUNT) + /// + public ShellProperty ByteCount + { + get + { + var key = SystemProperties.System.Document.ByteCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.CharacterCount -- PKEY_Document_CharacterCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 16 (PIDSI_CHARCOUNT) + /// + public ShellProperty CharacterCount + { + get + { + var key = SystemProperties.System.Document.CharacterCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.ClientID -- PKEY_Document_ClientID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {276D7BB0-5B34-4FB0-AA4B-158ED12A1809}, 100 + /// + public ShellProperty ClientID + { + get + { + var key = SystemProperties.System.Document.ClientID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.Contributor -- PKEY_Document_Contributor + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {F334115E-DA1B-4509-9B3D-119504DC7ABB}, 100 + /// + public ShellProperty Contributor + { + get + { + var key = SystemProperties.System.Document.Contributor; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.DateCreated -- PKEY_Document_DateCreated + /// Description: This property is stored in the document, not obtained from the file system. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 12 (PIDSI_CREATE_DTM) + /// + public ShellProperty DateCreated + { + get + { + var key = SystemProperties.System.Document.DateCreated; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.DatePrinted -- PKEY_Document_DatePrinted + /// Description: Legacy name: "DocLastPrinted". + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 11 (PIDSI_LASTPRINTED) + /// + public ShellProperty DatePrinted + { + get + { + var key = SystemProperties.System.Document.DatePrinted; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.DateSaved -- PKEY_Document_DateSaved + /// Description: Legacy name: "DocLastSavedTm". + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 13 (PIDSI_LASTSAVE_DTM) + /// + public ShellProperty DateSaved + { + get + { + var key = SystemProperties.System.Document.DateSaved; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.Division -- PKEY_Document_Division + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {1E005EE6-BF27-428B-B01C-79676ACD2870}, 100 + /// + public ShellProperty Division + { + get + { + var key = SystemProperties.System.Document.Division; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.DocumentID -- PKEY_Document_DocumentID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E08805C8-E395-40DF-80D2-54F0D6C43154}, 100 + /// + public ShellProperty DocumentID + { + get + { + var key = SystemProperties.System.Document.DocumentID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.HiddenSlideCount -- PKEY_Document_HiddenSlideCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 9 (PIDDSI_HIDDENCOUNT) + /// + public ShellProperty HiddenSlideCount + { + get + { + var key = SystemProperties.System.Document.HiddenSlideCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.LastAuthor -- PKEY_Document_LastAuthor + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 8 (PIDSI_LASTAUTHOR) + /// + public ShellProperty LastAuthor + { + get + { + var key = SystemProperties.System.Document.LastAuthor; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.LineCount -- PKEY_Document_LineCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 5 (PIDDSI_LINECOUNT) + /// + public ShellProperty LineCount + { + get + { + var key = SystemProperties.System.Document.LineCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.Manager -- PKEY_Document_Manager + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 14 (PIDDSI_MANAGER) + /// + public ShellProperty Manager + { + get + { + var key = SystemProperties.System.Document.Manager; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.MultimediaClipCount -- PKEY_Document_MultimediaClipCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 10 (PIDDSI_MMCLIPCOUNT) + /// + public ShellProperty MultimediaClipCount + { + get + { + var key = SystemProperties.System.Document.MultimediaClipCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.NoteCount -- PKEY_Document_NoteCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 8 (PIDDSI_NOTECOUNT) + /// + public ShellProperty NoteCount + { + get + { + var key = SystemProperties.System.Document.NoteCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.PageCount -- PKEY_Document_PageCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 14 (PIDSI_PAGECOUNT) + /// + public ShellProperty PageCount + { + get + { + var key = SystemProperties.System.Document.PageCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.ParagraphCount -- PKEY_Document_ParagraphCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 6 (PIDDSI_PARCOUNT) + /// + public ShellProperty ParagraphCount + { + get + { + var key = SystemProperties.System.Document.ParagraphCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.PresentationFormat -- PKEY_Document_PresentationFormat + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 3 (PIDDSI_PRESFORMAT) + /// + public ShellProperty PresentationFormat + { + get + { + var key = SystemProperties.System.Document.PresentationFormat; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.RevisionNumber -- PKEY_Document_RevisionNumber + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 9 (PIDSI_REVNUMBER) + /// + public ShellProperty RevisionNumber + { + get + { + var key = SystemProperties.System.Document.RevisionNumber; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.Security -- PKEY_Document_Security + /// Description: Access control information, from SummaryInfo propset + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 19 + /// + public ShellProperty Security + { + get + { + var key = SystemProperties.System.Document.Security; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.SlideCount -- PKEY_Document_SlideCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 7 (PIDDSI_SLIDECOUNT) + /// + public ShellProperty SlideCount + { + get + { + var key = SystemProperties.System.Document.SlideCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.Template -- PKEY_Document_Template + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 7 (PIDSI_TEMPLATE) + /// + public ShellProperty Template + { + get + { + var key = SystemProperties.System.Document.Template; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.TotalEditingTime -- PKEY_Document_TotalEditingTime + /// Description: 100ns units, not milliseconds. VT_FILETIME for IPropertySetStorage handlers (legacy) + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 10 (PIDSI_EDITTIME) + /// + public ShellProperty TotalEditingTime + { + get + { + var key = SystemProperties.System.Document.TotalEditingTime; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.Version -- PKEY_Document_Version + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 29 + /// + public ShellProperty Version + { + get + { + var key = SystemProperties.System.Document.Version; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Document.WordCount -- PKEY_Document_WordCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 15 (PIDSI_WORDCOUNT) + /// + public ShellProperty WordCount + { + get + { + var key = SystemProperties.System.Document.WordCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.DRM Properties + /// + public class PropertySystemDRM : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemDRM(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.DRM.DatePlayExpires -- PKEY_DRM_DatePlayExpires + /// Description: Indicates when play expires for digital rights management. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_DRM) {AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}, 6 (PIDDRSI_PLAYEXPIRES) + /// + public ShellProperty DatePlayExpires + { + get + { + var key = SystemProperties.System.DRM.DatePlayExpires; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DRM.DatePlayStarts -- PKEY_DRM_DatePlayStarts + /// Description: Indicates when play starts for digital rights management. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_DRM) {AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}, 5 (PIDDRSI_PLAYSTARTS) + /// + public ShellProperty DatePlayStarts + { + get + { + var key = SystemProperties.System.DRM.DatePlayStarts; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DRM.Description -- PKEY_DRM_Description + /// Description: Displays the description for digital rights management. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DRM) {AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}, 3 (PIDDRSI_DESCRIPTION) + /// + public ShellProperty Description + { + get + { + var key = SystemProperties.System.DRM.Description; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DRM.IsProtected -- PKEY_DRM_IsProtected + /// Description: + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: (FMTID_DRM) {AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}, 2 (PIDDRSI_PROTECTED) + /// + public ShellProperty IsProtected + { + get + { + var key = SystemProperties.System.DRM.IsProtected; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.DRM.PlayCount -- PKEY_DRM_PlayCount + /// Description: Indicates the play count for digital rights management. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_DRM) {AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}, 4 (PIDDRSI_PLAYCOUNT) + /// + public ShellProperty PlayCount + { + get + { + var key = SystemProperties.System.DRM.PlayCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.GPS Properties + /// + public class PropertySystemGPS : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemGPS(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.GPS.Altitude -- PKEY_GPS_Altitude + /// Description: Indicates the altitude based on the reference in PKEY_GPS_AltitudeRef. Calculated from PKEY_GPS_AltitudeNumerator and + ///PKEY_GPS_AltitudeDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: {827EDB4F-5B73-44A7-891D-FDFFABEA35CA}, 100 + /// + public ShellProperty Altitude + { + get + { + var key = SystemProperties.System.GPS.Altitude; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.AltitudeDenominator -- PKEY_GPS_AltitudeDenominator + /// Description: Denominator of PKEY_GPS_Altitude + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {78342DCB-E358-4145-AE9A-6BFE4E0F9F51}, 100 + /// + public ShellProperty AltitudeDenominator + { + get + { + var key = SystemProperties.System.GPS.AltitudeDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.AltitudeNumerator -- PKEY_GPS_AltitudeNumerator + /// Description: Numerator of PKEY_GPS_Altitude + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {2DAD1EB7-816D-40D3-9EC3-C9773BE2AADE}, 100 + /// + public ShellProperty AltitudeNumerator + { + get + { + var key = SystemProperties.System.GPS.AltitudeNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.AltitudeRef -- PKEY_GPS_AltitudeRef + /// Description: Indicates the reference for the altitude property. (eg: above sea level, below sea level, absolute value) + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {46AC629D-75EA-4515-867F-6DC4321C5844}, 100 + /// + public ShellProperty AltitudeRef + { + get + { + var key = SystemProperties.System.GPS.AltitudeRef; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.AreaInformation -- PKEY_GPS_AreaInformation + /// Description: Represents the name of the GPS area + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {972E333E-AC7E-49F1-8ADF-A70D07A9BCAB}, 100 + /// + public ShellProperty AreaInformation + { + get + { + var key = SystemProperties.System.GPS.AreaInformation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.Date -- PKEY_GPS_Date + /// Description: Date and time of the GPS record + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {3602C812-0F3B-45F0-85AD-603468D69423}, 100 + /// + public ShellProperty Date + { + get + { + var key = SystemProperties.System.GPS.Date; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestBearing -- PKEY_GPS_DestBearing + /// Description: Indicates the bearing to the destination point. Calculated from PKEY_GPS_DestBearingNumerator and + ///PKEY_GPS_DestBearingDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {C66D4B3C-E888-47CC-B99F-9DCA3EE34DEA}, 100 + /// + public ShellProperty DestinationBearing + { + get + { + var key = SystemProperties.System.GPS.DestinationBearing; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestBearingDenominator -- PKEY_GPS_DestBearingDenominator + /// Description: Denominator of PKEY_GPS_DestBearing + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {7ABCF4F8-7C3F-4988-AC91-8D2C2E97ECA5}, 100 + /// + public ShellProperty DestinationBearingDenominator + { + get + { + var key = SystemProperties.System.GPS.DestinationBearingDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestBearingNumerator -- PKEY_GPS_DestBearingNumerator + /// Description: Numerator of PKEY_GPS_DestBearing + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {BA3B1DA9-86EE-4B5D-A2A4-A271A429F0CF}, 100 + /// + public ShellProperty DestinationBearingNumerator + { + get + { + var key = SystemProperties.System.GPS.DestinationBearingNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestBearingRef -- PKEY_GPS_DestBearingRef + /// Description: Indicates the reference used for the giving the bearing to the destination point. (eg: true direction, magnetic direction) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9AB84393-2A0F-4B75-BB22-7279786977CB}, 100 + /// + public ShellProperty DestinationBearingRef + { + get + { + var key = SystemProperties.System.GPS.DestinationBearingRef; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestDistance -- PKEY_GPS_DestDistance + /// Description: Indicates the distance to the destination point. Calculated from PKEY_GPS_DestDistanceNumerator and + ///PKEY_GPS_DestDistanceDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {A93EAE04-6804-4F24-AC81-09B266452118}, 100 + /// + public ShellProperty DestinationDistance + { + get + { + var key = SystemProperties.System.GPS.DestinationDistance; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestDistanceDenominator -- PKEY_GPS_DestDistanceDenominator + /// Description: Denominator of PKEY_GPS_DestDistance + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {9BC2C99B-AC71-4127-9D1C-2596D0D7DCB7}, 100 + /// + public ShellProperty DestinationDistanceDenominator + { + get + { + var key = SystemProperties.System.GPS.DestinationDistanceDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestDistanceNumerator -- PKEY_GPS_DestDistanceNumerator + /// Description: Numerator of PKEY_GPS_DestDistance + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {2BDA47DA-08C6-4FE1-80BC-A72FC517C5D0}, 100 + /// + public ShellProperty DestinationDistanceNumerator + { + get + { + var key = SystemProperties.System.GPS.DestinationDistanceNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestDistanceRef -- PKEY_GPS_DestDistanceRef + /// Description: Indicates the unit used to express the distance to the destination. (eg: kilometers, miles, knots) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {ED4DF2D3-8695-450B-856F-F5C1C53ACB66}, 100 + /// + public ShellProperty DestinationDistanceRef + { + get + { + var key = SystemProperties.System.GPS.DestinationDistanceRef; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestLatitude -- PKEY_GPS_DestLatitude + /// Description: Indicates the latitude of the destination point. This is an array of three values. Index 0 is the degrees, index 1 + ///is the minutes, index 2 is the seconds. Each is calculated from the values in PKEY_GPS_DestLatitudeNumerator and + ///PKEY_GPS_DestLatitudeDenominator. + /// + /// Type: Multivalue Double -- VT_VECTOR | VT_R8 (For variants: VT_ARRAY | VT_R8) + /// FormatID: {9D1D7CC5-5C39-451C-86B3-928E2D18CC47}, 100 + /// + public ShellProperty DestinationLatitude + { + get + { + var key = SystemProperties.System.GPS.DestinationLatitude; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestLatitudeDenominator -- PKEY_GPS_DestLatitudeDenominator + /// Description: Denominator of PKEY_GPS_DestLatitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {3A372292-7FCA-49A7-99D5-E47BB2D4E7AB}, 100 + /// + public ShellProperty DestinationLatitudeDenominator + { + get + { + var key = SystemProperties.System.GPS.DestinationLatitudeDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestLatitudeNumerator -- PKEY_GPS_DestLatitudeNumerator + /// Description: Numerator of PKEY_GPS_DestLatitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {ECF4B6F6-D5A6-433C-BB92-4076650FC890}, 100 + /// + public ShellProperty DestinationLatitudeNumerator + { + get + { + var key = SystemProperties.System.GPS.DestinationLatitudeNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestLatitudeRef -- PKEY_GPS_DestLatitudeRef + /// Description: Indicates whether the latitude destination point is north or south latitude + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CEA820B9-CE61-4885-A128-005D9087C192}, 100 + /// + public ShellProperty DestinationLatitudeRef + { + get + { + var key = SystemProperties.System.GPS.DestinationLatitudeRef; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestLongitude -- PKEY_GPS_DestLongitude + /// Description: Indicates the latitude of the destination point. This is an array of three values. Index 0 is the degrees, index 1 + ///is the minutes, index 2 is the seconds. Each is calculated from the values in PKEY_GPS_DestLongitudeNumerator and + ///PKEY_GPS_DestLongitudeDenominator. + /// + /// Type: Multivalue Double -- VT_VECTOR | VT_R8 (For variants: VT_ARRAY | VT_R8) + /// FormatID: {47A96261-CB4C-4807-8AD3-40B9D9DBC6BC}, 100 + /// + public ShellProperty DestinationLongitude + { + get + { + var key = SystemProperties.System.GPS.DestinationLongitude; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestLongitudeDenominator -- PKEY_GPS_DestLongitudeDenominator + /// Description: Denominator of PKEY_GPS_DestLongitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {425D69E5-48AD-4900-8D80-6EB6B8D0AC86}, 100 + /// + public ShellProperty DestinationLongitudeDenominator + { + get + { + var key = SystemProperties.System.GPS.DestinationLongitudeDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestLongitudeNumerator -- PKEY_GPS_DestLongitudeNumerator + /// Description: Numerator of PKEY_GPS_DestLongitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {A3250282-FB6D-48D5-9A89-DBCACE75CCCF}, 100 + /// + public ShellProperty DestinationLongitudeNumerator + { + get + { + var key = SystemProperties.System.GPS.DestinationLongitudeNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DestLongitudeRef -- PKEY_GPS_DestLongitudeRef + /// Description: Indicates whether the longitude destination point is east or west longitude + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {182C1EA6-7C1C-4083-AB4B-AC6C9F4ED128}, 100 + /// + public ShellProperty DestinationLongitudeRef + { + get + { + var key = SystemProperties.System.GPS.DestinationLongitudeRef; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.Differential -- PKEY_GPS_Differential + /// Description: Indicates whether differential correction was applied to the GPS receiver + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {AAF4EE25-BD3B-4DD7-BFC4-47F77BB00F6D}, 100 + /// + public ShellProperty Differential + { + get + { + var key = SystemProperties.System.GPS.Differential; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DOP -- PKEY_GPS_DOP + /// Description: Indicates the GPS DOP (data degree of precision). Calculated from PKEY_GPS_DOPNumerator and PKEY_GPS_DOPDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: {0CF8FB02-1837-42F1-A697-A7017AA289B9}, 100 + /// + public ShellProperty DOP + { + get + { + var key = SystemProperties.System.GPS.DOP; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DOPDenominator -- PKEY_GPS_DOPDenominator + /// Description: Denominator of PKEY_GPS_DOP + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {A0BE94C5-50BA-487B-BD35-0654BE8881ED}, 100 + /// + public ShellProperty DOPDenominator + { + get + { + var key = SystemProperties.System.GPS.DOPDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.DOPNumerator -- PKEY_GPS_DOPNumerator + /// Description: Numerator of PKEY_GPS_DOP + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {47166B16-364F-4AA0-9F31-E2AB3DF449C3}, 100 + /// + public ShellProperty DOPNumerator + { + get + { + var key = SystemProperties.System.GPS.DOPNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.ImgDirection -- PKEY_GPS_ImgDirection + /// Description: Indicates direction of the image when it was captured. Calculated from PKEY_GPS_ImgDirectionNumerator and + ///PKEY_GPS_ImgDirectionDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {16473C91-D017-4ED9-BA4D-B6BAA55DBCF8}, 100 + /// + public ShellProperty ImageDirection + { + get + { + var key = SystemProperties.System.GPS.ImageDirection; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.ImgDirectionDenominator -- PKEY_GPS_ImgDirectionDenominator + /// Description: Denominator of PKEY_GPS_ImgDirection + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {10B24595-41A2-4E20-93C2-5761C1395F32}, 100 + /// + public ShellProperty ImageDirectionDenominator + { + get + { + var key = SystemProperties.System.GPS.ImageDirectionDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.ImgDirectionNumerator -- PKEY_GPS_ImgDirectionNumerator + /// Description: Numerator of PKEY_GPS_ImgDirection + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {DC5877C7-225F-45F7-BAC7-E81334B6130A}, 100 + /// + public ShellProperty ImageDirectionNumerator + { + get + { + var key = SystemProperties.System.GPS.ImageDirectionNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.ImgDirectionRef -- PKEY_GPS_ImgDirectionRef + /// Description: Indicates reference for giving the direction of the image when it was captured. (eg: true direction, magnetic direction) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A4AAA5B7-1AD0-445F-811A-0F8F6E67F6B5}, 100 + /// + public ShellProperty ImageDirectionRef + { + get + { + var key = SystemProperties.System.GPS.ImageDirectionRef; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.Latitude -- PKEY_GPS_Latitude + /// Description: Indicates the latitude. This is an array of three values. Index 0 is the degrees, index 1 is the minutes, index 2 + ///is the seconds. Each is calculated from the values in PKEY_GPS_LatitudeNumerator and PKEY_GPS_LatitudeDenominator. + /// + /// Type: Multivalue Double -- VT_VECTOR | VT_R8 (For variants: VT_ARRAY | VT_R8) + /// FormatID: {8727CFFF-4868-4EC6-AD5B-81B98521D1AB}, 100 + /// + public ShellProperty Latitude + { + get + { + var key = SystemProperties.System.GPS.Latitude; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.LatitudeDenominator -- PKEY_GPS_LatitudeDenominator + /// Description: Denominator of PKEY_GPS_Latitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {16E634EE-2BFF-497B-BD8A-4341AD39EEB9}, 100 + /// + public ShellProperty LatitudeDenominator + { + get + { + var key = SystemProperties.System.GPS.LatitudeDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.LatitudeNumerator -- PKEY_GPS_LatitudeNumerator + /// Description: Numerator of PKEY_GPS_Latitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {7DDAAAD1-CCC8-41AE-B750-B2CB8031AEA2}, 100 + /// + public ShellProperty LatitudeNumerator + { + get + { + var key = SystemProperties.System.GPS.LatitudeNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.LatitudeRef -- PKEY_GPS_LatitudeRef + /// Description: Indicates whether latitude is north or south latitude + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {029C0252-5B86-46C7-ACA0-2769FFC8E3D4}, 100 + /// + public ShellProperty LatitudeRef + { + get + { + var key = SystemProperties.System.GPS.LatitudeRef; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.Longitude -- PKEY_GPS_Longitude + /// Description: Indicates the longitude. This is an array of three values. Index 0 is the degrees, index 1 is the minutes, index 2 + ///is the seconds. Each is calculated from the values in PKEY_GPS_LongitudeNumerator and PKEY_GPS_LongitudeDenominator. + /// + /// Type: Multivalue Double -- VT_VECTOR | VT_R8 (For variants: VT_ARRAY | VT_R8) + /// FormatID: {C4C4DBB2-B593-466B-BBDA-D03D27D5E43A}, 100 + /// + public ShellProperty Longitude + { + get + { + var key = SystemProperties.System.GPS.Longitude; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.LongitudeDenominator -- PKEY_GPS_LongitudeDenominator + /// Description: Denominator of PKEY_GPS_Longitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {BE6E176C-4534-4D2C-ACE5-31DEDAC1606B}, 100 + /// + public ShellProperty LongitudeDenominator + { + get + { + var key = SystemProperties.System.GPS.LongitudeDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.LongitudeNumerator -- PKEY_GPS_LongitudeNumerator + /// Description: Numerator of PKEY_GPS_Longitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {02B0F689-A914-4E45-821D-1DDA452ED2C4}, 100 + /// + public ShellProperty LongitudeNumerator + { + get + { + var key = SystemProperties.System.GPS.LongitudeNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.LongitudeRef -- PKEY_GPS_LongitudeRef + /// Description: Indicates whether longitude is east or west longitude + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {33DCF22B-28D5-464C-8035-1EE9EFD25278}, 100 + /// + public ShellProperty LongitudeRef + { + get + { + var key = SystemProperties.System.GPS.LongitudeRef; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.MapDatum -- PKEY_GPS_MapDatum + /// Description: Indicates the geodetic survey data used by the GPS receiver + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {2CA2DAE6-EDDC-407D-BEF1-773942ABFA95}, 100 + /// + public ShellProperty MapDatum + { + get + { + var key = SystemProperties.System.GPS.MapDatum; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.MeasureMode -- PKEY_GPS_MeasureMode + /// Description: Indicates the GPS measurement mode. (eg: 2-dimensional, 3-dimensional) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A015ED5D-AAEA-4D58-8A86-3C586920EA0B}, 100 + /// + public ShellProperty MeasureMode + { + get + { + var key = SystemProperties.System.GPS.MeasureMode; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.ProcessingMethod -- PKEY_GPS_ProcessingMethod + /// Description: Indicates the name of the method used for location finding + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {59D49E61-840F-4AA9-A939-E2099B7F6399}, 100 + /// + public ShellProperty ProcessingMethod + { + get + { + var key = SystemProperties.System.GPS.ProcessingMethod; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.Satellites -- PKEY_GPS_Satellites + /// Description: Indicates the GPS satellites used for measurements + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {467EE575-1F25-4557-AD4E-B8B58B0D9C15}, 100 + /// + public ShellProperty Satellites + { + get + { + var key = SystemProperties.System.GPS.Satellites; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.Speed -- PKEY_GPS_Speed + /// Description: Indicates the speed of the GPS receiver movement. Calculated from PKEY_GPS_SpeedNumerator and + ///PKEY_GPS_SpeedDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {DA5D0862-6E76-4E1B-BABD-70021BD25494}, 100 + /// + public ShellProperty Speed + { + get + { + var key = SystemProperties.System.GPS.Speed; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.SpeedDenominator -- PKEY_GPS_SpeedDenominator + /// Description: Denominator of PKEY_GPS_Speed + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {7D122D5A-AE5E-4335-8841-D71E7CE72F53}, 100 + /// + public ShellProperty SpeedDenominator + { + get + { + var key = SystemProperties.System.GPS.SpeedDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.SpeedNumerator -- PKEY_GPS_SpeedNumerator + /// Description: Numerator of PKEY_GPS_Speed + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {ACC9CE3D-C213-4942-8B48-6D0820F21C6D}, 100 + /// + public ShellProperty SpeedNumerator + { + get + { + var key = SystemProperties.System.GPS.SpeedNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.SpeedRef -- PKEY_GPS_SpeedRef + /// Description: Indicates the unit used to express the speed of the GPS receiver movement. (eg: kilometers per hour, + ///miles per hour, knots). + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {ECF7F4C9-544F-4D6D-9D98-8AD79ADAF453}, 100 + /// + public ShellProperty SpeedRef + { + get + { + var key = SystemProperties.System.GPS.SpeedRef; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.Status -- PKEY_GPS_Status + /// Description: Indicates the status of the GPS receiver when the image was recorded. (eg: measurement in progress, + ///measurement interoperability). + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {125491F4-818F-46B2-91B5-D537753617B2}, 100 + /// + public ShellProperty Status + { + get + { + var key = SystemProperties.System.GPS.Status; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.Track -- PKEY_GPS_Track + /// Description: Indicates the direction of the GPS receiver movement. Calculated from PKEY_GPS_TrackNumerator and + ///PKEY_GPS_TrackDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {76C09943-7C33-49E3-9E7E-CDBA872CFADA}, 100 + /// + public ShellProperty Track + { + get + { + var key = SystemProperties.System.GPS.Track; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.TrackDenominator -- PKEY_GPS_TrackDenominator + /// Description: Denominator of PKEY_GPS_Track + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {C8D1920C-01F6-40C0-AC86-2F3A4AD00770}, 100 + /// + public ShellProperty TrackDenominator + { + get + { + var key = SystemProperties.System.GPS.TrackDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.TrackNumerator -- PKEY_GPS_TrackNumerator + /// Description: Numerator of PKEY_GPS_Track + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {702926F4-44A6-43E1-AE71-45627116893B}, 100 + /// + public ShellProperty TrackNumerator + { + get + { + var key = SystemProperties.System.GPS.TrackNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.TrackRef -- PKEY_GPS_TrackRef + /// Description: Indicates reference for the direction of the GPS receiver movement. (eg: true direction, magnetic direction) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {35DBE6FE-44C3-4400-AAAE-D2C799C407E8}, 100 + /// + public ShellProperty TrackRef + { + get + { + var key = SystemProperties.System.GPS.TrackRef; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.GPS.VersionID -- PKEY_GPS_VersionID + /// Description: Indicates the version of the GPS information + /// + /// Type: Buffer -- VT_VECTOR | VT_UI1 (For variants: VT_ARRAY | VT_UI1) + /// FormatID: {22704DA4-C6B2-4A99-8E56-F16DF8C92599}, 100 + /// + public ShellProperty VersionID + { + get + { + var key = SystemProperties.System.GPS.VersionID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Identity Properties + /// + public class PropertySystemIdentity : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemIdentity(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Identity.Blob -- PKEY_Identity_Blob + /// Description: Blob used to import/export identities + /// + /// Type: Blob -- VT_BLOB + /// FormatID: {8C3B93A4-BAED-1A83-9A32-102EE313F6EB}, 100 + /// + public ShellProperty Blob + { + get + { + var key = SystemProperties.System.Identity.Blob; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Identity.DisplayName -- PKEY_Identity_DisplayName + /// Description: Display Name + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7D683FC9-D155-45A8-BB1F-89D19BCB792F}, 100 + /// + public ShellProperty DisplayName + { + get + { + var key = SystemProperties.System.Identity.DisplayName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Identity.IsMeIdentity -- PKEY_Identity_IsMeIdentity + /// Description: Is it Me Identity + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {A4108708-09DF-4377-9DFC-6D99986D5A67}, 100 + /// + public ShellProperty IsMeIdentity + { + get + { + var key = SystemProperties.System.Identity.IsMeIdentity; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Identity.PrimaryEmailAddress -- PKEY_Identity_PrimaryEmailAddress + /// Description: Primary Email Address + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FCC16823-BAED-4F24-9B32-A0982117F7FA}, 100 + /// + public ShellProperty PrimaryEmailAddress + { + get + { + var key = SystemProperties.System.Identity.PrimaryEmailAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Identity.ProviderID -- PKEY_Identity_ProviderID + /// Description: Provider ID + /// + /// Type: Guid -- VT_CLSID + /// FormatID: {74A7DE49-FA11-4D3D-A006-DB7E08675916}, 100 + /// + public ShellProperty ProviderID + { + get + { + var key = SystemProperties.System.Identity.ProviderID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Identity.UniqueID -- PKEY_Identity_UniqueID + /// Description: Unique ID + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E55FC3B0-2B60-4220-918E-B21E8BF16016}, 100 + /// + public ShellProperty UniqueID + { + get + { + var key = SystemProperties.System.Identity.UniqueID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Identity.UserName -- PKEY_Identity_UserName + /// Description: Identity User Name + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C4322503-78CA-49C6-9ACC-A68E2AFD7B6B}, 100 + /// + public ShellProperty UserName + { + get + { + var key = SystemProperties.System.Identity.UserName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.IdentityProvider Properties + /// + public class PropertySystemIdentityProvider : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemIdentityProvider(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.IdentityProvider.Name -- PKEY_IdentityProvider_Name + /// Description: Identity Provider Name + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {B96EFF7B-35CA-4A35-8607-29E3A54C46EA}, 100 + /// + public ShellProperty Name + { + get + { + var key = SystemProperties.System.IdentityProvider.Name; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.IdentityProvider.Picture -- PKEY_IdentityProvider_Picture + /// Description: Picture for the Identity Provider + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {2425166F-5642-4864-992F-98FD98F294C3}, 100 + /// + public ShellProperty Picture + { + get + { + var key = SystemProperties.System.IdentityProvider.Picture; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Image Properties + /// + public class PropertySystemImage : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemImage(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Image.BitDepth -- PKEY_Image_BitDepth + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 7 (PIDISI_BITDEPTH) + /// + public ShellProperty BitDepth + { + get + { + var key = SystemProperties.System.Image.BitDepth; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.ColorSpace -- PKEY_Image_ColorSpace + /// Description: PropertyTagExifColorSpace + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 40961 + /// + public ShellProperty ColorSpace + { + get + { + var key = SystemProperties.System.Image.ColorSpace; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.CompressedBitsPerPixel -- PKEY_Image_CompressedBitsPerPixel + /// Description: Calculated from PKEY_Image_CompressedBitsPerPixelNumerator and PKEY_Image_CompressedBitsPerPixelDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {364B6FA9-37AB-482A-BE2B-AE02F60D4318}, 100 + /// + public ShellProperty CompressedBitsPerPixel + { + get + { + var key = SystemProperties.System.Image.CompressedBitsPerPixel; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.CompressedBitsPerPixelDenominator -- PKEY_Image_CompressedBitsPerPixelDenominator + /// Description: Denominator of PKEY_Image_CompressedBitsPerPixel. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {1F8844E1-24AD-4508-9DFD-5326A415CE02}, 100 + /// + public ShellProperty CompressedBitsPerPixelDenominator + { + get + { + var key = SystemProperties.System.Image.CompressedBitsPerPixelDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.CompressedBitsPerPixelNumerator -- PKEY_Image_CompressedBitsPerPixelNumerator + /// Description: Numerator of PKEY_Image_CompressedBitsPerPixel. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {D21A7148-D32C-4624-8900-277210F79C0F}, 100 + /// + public ShellProperty CompressedBitsPerPixelNumerator + { + get + { + var key = SystemProperties.System.Image.CompressedBitsPerPixelNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.Compression -- PKEY_Image_Compression + /// Description: Indicates the image compression level. PropertyTagCompression. + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 259 + /// + public ShellProperty Compression + { + get + { + var key = SystemProperties.System.Image.Compression; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.CompressionText -- PKEY_Image_CompressionText + /// Description: This is the user-friendly form of System.Image.Compression. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {3F08E66F-2F44-4BB9-A682-AC35D2562322}, 100 + /// + public ShellProperty CompressionText + { + get + { + var key = SystemProperties.System.Image.CompressionText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.Dimensions -- PKEY_Image_Dimensions + /// Description: Indicates the dimensions of the image. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 13 (PIDISI_DIMENSIONS) + /// + public ShellProperty Dimensions + { + get + { + var key = SystemProperties.System.Image.Dimensions; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.HorizontalResolution -- PKEY_Image_HorizontalResolution + /// Description: + /// + /// Type: Double -- VT_R8 + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 5 (PIDISI_RESOLUTIONX) + /// + public ShellProperty HorizontalResolution + { + get + { + var key = SystemProperties.System.Image.HorizontalResolution; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.HorizontalSize -- PKEY_Image_HorizontalSize + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 3 (PIDISI_CX) + /// + public ShellProperty HorizontalSize + { + get + { + var key = SystemProperties.System.Image.HorizontalSize; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.ImageID -- PKEY_Image_ImageID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {10DABE05-32AA-4C29-BF1A-63E2D220587F}, 100 + /// + public ShellProperty ImageID + { + get + { + var key = SystemProperties.System.Image.ImageID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.ResolutionUnit -- PKEY_Image_ResolutionUnit + /// Description: + /// Type: Int16 -- VT_I2 + /// FormatID: {19B51FA6-1F92-4A5C-AB48-7DF0ABD67444}, 100 + /// + public ShellProperty ResolutionUnit + { + get + { + var key = SystemProperties.System.Image.ResolutionUnit; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.VerticalResolution -- PKEY_Image_VerticalResolution + /// Description: + /// + /// Type: Double -- VT_R8 + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 6 (PIDISI_RESOLUTIONY) + /// + public ShellProperty VerticalResolution + { + get + { + var key = SystemProperties.System.Image.VerticalResolution; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Image.VerticalSize -- PKEY_Image_VerticalSize + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 4 (PIDISI_CY) + /// + public ShellProperty VerticalSize + { + get + { + var key = SystemProperties.System.Image.VerticalSize; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Journal Properties + /// + public class PropertySystemJournal : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemJournal(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Journal.Contacts -- PKEY_Journal_Contacts + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {DEA7C82C-1D89-4A66-9427-A4E3DEBABCB1}, 100 + /// + public ShellProperty Contacts + { + get + { + var key = SystemProperties.System.Journal.Contacts; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Journal.EntryType -- PKEY_Journal_EntryType + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {95BEB1FC-326D-4644-B396-CD3ED90E6DDF}, 100 + /// + public ShellProperty EntryType + { + get + { + var key = SystemProperties.System.Journal.EntryType; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.LayoutPattern Properties + /// + public class PropertySystemLayoutPattern : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemLayoutPattern(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.LayoutPattern.ContentViewModeForBrowse -- PKEY_LayoutPattern_ContentViewModeForBrowse + /// Description: Specifies the layout pattern that the content view mode should apply for this item in the context of browsing. + ///Register the regvalue under the name of "ContentViewModeLayoutPatternForBrowse". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 500 + /// + public ShellProperty ContentViewModeForBrowse + { + get + { + var key = SystemProperties.System.LayoutPattern.ContentViewModeForBrowse; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.LayoutPattern.ContentViewModeForSearch -- PKEY_LayoutPattern_ContentViewModeForSearch + /// Description: Specifies the layout pattern that the content view mode should apply for this item in the context of searching. + ///Register the regvalue under the name of "ContentViewModeLayoutPatternForSearch". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 501 + /// + public ShellProperty ContentViewModeForSearch + { + get + { + var key = SystemProperties.System.LayoutPattern.ContentViewModeForSearch; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Link Properties + /// + public class PropertySystemLink : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemLink(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Link.Arguments -- PKEY_Link_Arguments + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {436F2667-14E2-4FEB-B30A-146C53B5B674}, 100 + /// + public ShellProperty Arguments + { + get + { + var key = SystemProperties.System.Link.Arguments; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Link.Comment -- PKEY_Link_Comment + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_LINK) {B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}, 5 + /// + public ShellProperty Comment + { + get + { + var key = SystemProperties.System.Link.Comment; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Link.DateVisited -- PKEY_Link_DateVisited + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {5CBF2787-48CF-4208-B90E-EE5E5D420294}, 23 (PKEYs relating to URLs. Used by IE History.) + /// + public ShellProperty DateVisited + { + get + { + var key = SystemProperties.System.Link.DateVisited; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Link.Description -- PKEY_Link_Description + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {5CBF2787-48CF-4208-B90E-EE5E5D420294}, 21 (PKEYs relating to URLs. Used by IE History.) + /// + public ShellProperty Description + { + get + { + var key = SystemProperties.System.Link.Description; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Link.Status -- PKEY_Link_Status + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (PSGUID_LINK) {B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}, 3 (PID_LINK_TARGET_TYPE) + /// + public ShellProperty Status + { + get + { + var key = SystemProperties.System.Link.Status; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Link.TargetExtension -- PKEY_Link_TargetExtension + /// Description: The file extension of the link target. See System.File.Extension + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {7A7D76F4-B630-4BD7-95FF-37CC51A975C9}, 2 + /// + public ShellProperty TargetExtension + { + get + { + var key = SystemProperties.System.Link.TargetExtension; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Link.TargetParsingPath -- PKEY_Link_TargetParsingPath + /// Description: This is the shell namespace path to the target of the link item. This path may be passed to + ///SHParseDisplayName to parse the path to the correct shell folder. + /// + ///If the target item is a file, the value is identical to System.ItemPathDisplay. + /// + ///If the target item cannot be accessed through the shell namespace, this value is VT_EMPTY. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_LINK) {B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}, 2 (PID_LINK_TARGET) + /// + public ShellProperty TargetParsingPath + { + get + { + var key = SystemProperties.System.Link.TargetParsingPath; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Link.TargetSFGAOFlags -- PKEY_Link_TargetSFGAOFlags + /// Description: IShellFolder::GetAttributesOf flags for the target of a link, with SFGAO_PKEYSFGAOMASK + ///attributes masked out. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_LINK) {B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}, 8 + /// + public ShellProperty TargetSFGAOFlags + { + get + { + var key = SystemProperties.System.Link.TargetSFGAOFlags; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Link.TargetSFGAOFlagsStrings -- PKEY_Link_TargetSFGAOFlagsStrings + /// Description: Expresses the SFGAO flags of a link as string values and is used as a query optimization. See + ///PKEY_Shell_SFGAOFlagsStrings for possible values of this. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D6942081-D53B-443D-AD47-5E059D9CD27A}, 3 + /// + public ShellProperty TargetSFGAOFlagsStrings + { + get + { + var key = SystemProperties.System.Link.TargetSFGAOFlagsStrings; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Link.TargetUrl -- PKEY_Link_TargetUrl + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {5CBF2787-48CF-4208-B90E-EE5E5D420294}, 2 (PKEYs relating to URLs. Used by IE History.) + /// + public ShellProperty TargetUrl + { + get + { + var key = SystemProperties.System.Link.TargetUrl; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Media Properties + /// + public class PropertySystemMedia : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemMedia(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Media.AuthorUrl -- PKEY_Media_AuthorUrl + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 32 (PIDMSI_AUTHOR_URL) + /// + public ShellProperty AuthorUrl + { + get + { + var key = SystemProperties.System.Media.AuthorUrl; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.AverageLevel -- PKEY_Media_AverageLevel + /// Description: + /// Type: UInt32 -- VT_UI4 + /// FormatID: {09EDD5B6-B301-43C5-9990-D00302EFFD46}, 100 + /// + public ShellProperty AverageLevel + { + get + { + var key = SystemProperties.System.Media.AverageLevel; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.ClassPrimaryID -- PKEY_Media_ClassPrimaryID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 13 (PIDMSI_CLASS_PRIMARY_ID) + /// + public ShellProperty ClassPrimaryID + { + get + { + var key = SystemProperties.System.Media.ClassPrimaryID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.ClassSecondaryID -- PKEY_Media_ClassSecondaryID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 14 (PIDMSI_CLASS_SECONDARY_ID) + /// + public ShellProperty ClassSecondaryID + { + get + { + var key = SystemProperties.System.Media.ClassSecondaryID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.CollectionGroupID -- PKEY_Media_CollectionGroupID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 24 (PIDMSI_COLLECTION_GROUP_ID) + /// + public ShellProperty CollectionGroupID + { + get + { + var key = SystemProperties.System.Media.CollectionGroupID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.CollectionID -- PKEY_Media_CollectionID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 25 (PIDMSI_COLLECTION_ID) + /// + public ShellProperty CollectionID + { + get + { + var key = SystemProperties.System.Media.CollectionID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.ContentDistributor -- PKEY_Media_ContentDistributor + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 18 (PIDMSI_CONTENTDISTRIBUTOR) + /// + public ShellProperty ContentDistributor + { + get + { + var key = SystemProperties.System.Media.ContentDistributor; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.ContentID -- PKEY_Media_ContentID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 26 (PIDMSI_CONTENT_ID) + /// + public ShellProperty ContentID + { + get + { + var key = SystemProperties.System.Media.ContentID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.CreatorApplication -- PKEY_Media_CreatorApplication + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 27 (PIDMSI_TOOL_NAME) + /// + public ShellProperty CreatorApplication + { + get + { + var key = SystemProperties.System.Media.CreatorApplication; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.CreatorApplicationVersion -- PKEY_Media_CreatorApplicationVersion + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 28 (PIDMSI_TOOL_VERSION) + /// + public ShellProperty CreatorApplicationVersion + { + get + { + var key = SystemProperties.System.Media.CreatorApplicationVersion; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.DateEncoded -- PKEY_Media_DateEncoded + /// Description: DateTime is in UTC (in the doc, not file system). + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {2E4B640D-5019-46D8-8881-55414CC5CAA0}, 100 + /// + public ShellProperty DateEncoded + { + get + { + var key = SystemProperties.System.Media.DateEncoded; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.DateReleased -- PKEY_Media_DateReleased + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DE41CC29-6971-4290-B472-F59F2E2F31E2}, 100 + /// + public ShellProperty DateReleased + { + get + { + var key = SystemProperties.System.Media.DateReleased; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.Duration -- PKEY_Media_Duration + /// Description: 100ns units, not milliseconds + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 3 (PIDASI_TIMELENGTH) + /// + public ShellProperty Duration + { + get + { + var key = SystemProperties.System.Media.Duration; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.DVDID -- PKEY_Media_DVDID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 15 (PIDMSI_DVDID) + /// + public ShellProperty DVDID + { + get + { + var key = SystemProperties.System.Media.DVDID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.EncodedBy -- PKEY_Media_EncodedBy + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 36 (PIDMSI_ENCODED_BY) + /// + public ShellProperty EncodedBy + { + get + { + var key = SystemProperties.System.Media.EncodedBy; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.EncodingSettings -- PKEY_Media_EncodingSettings + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 37 (PIDMSI_ENCODING_SETTINGS) + /// + public ShellProperty EncodingSettings + { + get + { + var key = SystemProperties.System.Media.EncodingSettings; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.FrameCount -- PKEY_Media_FrameCount + /// Description: Indicates the frame count for the image. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 12 (PIDISI_FRAMECOUNT) + /// + public ShellProperty FrameCount + { + get + { + var key = SystemProperties.System.Media.FrameCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.MCDI -- PKEY_Media_MCDI + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 16 (PIDMSI_MCDI) + /// + public ShellProperty MCDI + { + get + { + var key = SystemProperties.System.Media.MCDI; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.MetadataContentProvider -- PKEY_Media_MetadataContentProvider + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 17 (PIDMSI_PROVIDER) + /// + public ShellProperty MetadataContentProvider + { + get + { + var key = SystemProperties.System.Media.MetadataContentProvider; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.Producer -- PKEY_Media_Producer + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 22 (PIDMSI_PRODUCER) + /// + public ShellProperty Producer + { + get + { + var key = SystemProperties.System.Media.Producer; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.PromotionUrl -- PKEY_Media_PromotionUrl + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 33 (PIDMSI_PROMOTION_URL) + /// + public ShellProperty PromotionUrl + { + get + { + var key = SystemProperties.System.Media.PromotionUrl; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.ProtectionType -- PKEY_Media_ProtectionType + /// Description: If media is protected, how is it protected? + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 38 + /// + public ShellProperty ProtectionType + { + get + { + var key = SystemProperties.System.Media.ProtectionType; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.ProviderRating -- PKEY_Media_ProviderRating + /// Description: Rating (0 - 99) supplied by metadata provider + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 39 + /// + public ShellProperty ProviderRating + { + get + { + var key = SystemProperties.System.Media.ProviderRating; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.ProviderStyle -- PKEY_Media_ProviderStyle + /// Description: Style of music or video, supplied by metadata provider + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 40 + /// + public ShellProperty ProviderStyle + { + get + { + var key = SystemProperties.System.Media.ProviderStyle; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.Publisher -- PKEY_Media_Publisher + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 30 (PIDMSI_PUBLISHER) + /// + public ShellProperty Publisher + { + get + { + var key = SystemProperties.System.Media.Publisher; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.SubscriptionContentId -- PKEY_Media_SubscriptionContentId + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9AEBAE7A-9644-487D-A92C-657585ED751A}, 100 + /// + public ShellProperty SubscriptionContentId + { + get + { + var key = SystemProperties.System.Media.SubscriptionContentId; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.SubTitle -- PKEY_Media_SubTitle + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 38 (PIDSI_MUSIC_SUB_TITLE) + /// + public ShellProperty Subtitle + { + get + { + var key = SystemProperties.System.Media.Subtitle; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.UniqueFileIdentifier -- PKEY_Media_UniqueFileIdentifier + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 35 (PIDMSI_UNIQUE_FILE_IDENTIFIER) + /// + public ShellProperty UniqueFileIdentifier + { + get + { + var key = SystemProperties.System.Media.UniqueFileIdentifier; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.UserNoAutoInfo -- PKEY_Media_UserNoAutoInfo + /// Description: If true, do NOT alter this file's metadata. Set by user. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 41 + /// + public ShellProperty UserNoAutoInfo + { + get + { + var key = SystemProperties.System.Media.UserNoAutoInfo; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.UserWebUrl -- PKEY_Media_UserWebUrl + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 34 (PIDMSI_USER_WEB_URL) + /// + public ShellProperty UserWebUrl + { + get + { + var key = SystemProperties.System.Media.UserWebUrl; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.Writer -- PKEY_Media_Writer + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 23 (PIDMSI_WRITER) + /// + public ShellProperty Writer + { + get + { + var key = SystemProperties.System.Media.Writer; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Media.Year -- PKEY_Media_Year + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 5 (PIDSI_MUSIC_YEAR) + /// + public ShellProperty Year + { + get + { + var key = SystemProperties.System.Media.Year; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Message Properties + /// + public class PropertySystemMessage : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemMessage(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Message.AttachmentContents -- PKEY_Message_AttachmentContents + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {3143BF7C-80A8-4854-8880-E2E40189BDD0}, 100 + /// + public ShellProperty AttachmentContents + { + get + { + var key = SystemProperties.System.Message.AttachmentContents; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.AttachmentNames -- PKEY_Message_AttachmentNames + /// Description: The names of the attachments in a message + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 21 + /// + public ShellProperty AttachmentNames + { + get + { + var key = SystemProperties.System.Message.AttachmentNames; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.BccAddress -- PKEY_Message_BccAddress + /// Description: Addresses in Bcc: field + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 2 + /// + public ShellProperty BccAddress + { + get + { + var key = SystemProperties.System.Message.BccAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.BccName -- PKEY_Message_BccName + /// Description: person names in Bcc: field + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 3 + /// + public ShellProperty BccName + { + get + { + var key = SystemProperties.System.Message.BccName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.CcAddress -- PKEY_Message_CcAddress + /// Description: Addresses in Cc: field + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 4 + /// + public ShellProperty CcAddress + { + get + { + var key = SystemProperties.System.Message.CcAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.CcName -- PKEY_Message_CcName + /// Description: person names in Cc: field + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 5 + /// + public ShellProperty CcName + { + get + { + var key = SystemProperties.System.Message.CcName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.ConversationID -- PKEY_Message_ConversationID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DC8F80BD-AF1E-4289-85B6-3DFC1B493992}, 100 + /// + public ShellProperty ConversationID + { + get + { + var key = SystemProperties.System.Message.ConversationID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.ConversationIndex -- PKEY_Message_ConversationIndex + /// Description: + /// + /// Type: Buffer -- VT_VECTOR | VT_UI1 (For variants: VT_ARRAY | VT_UI1) + /// FormatID: {DC8F80BD-AF1E-4289-85B6-3DFC1B493992}, 101 + /// + public ShellProperty ConversationIndex + { + get + { + var key = SystemProperties.System.Message.ConversationIndex; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.DateReceived -- PKEY_Message_DateReceived + /// Description: Date and Time communication was received + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 20 + /// + public ShellProperty DateReceived + { + get + { + var key = SystemProperties.System.Message.DateReceived; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.DateSent -- PKEY_Message_DateSent + /// Description: Date and Time communication was sent + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 19 + /// + public ShellProperty DateSent + { + get + { + var key = SystemProperties.System.Message.DateSent; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.Flags -- PKEY_Message_Flags + /// Description: These are flags associated with email messages to know if a read receipt is pending, etc. + ///The values stored here by Outlook are defined for PR_MESSAGE_FLAGS on MSDN. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {A82D9EE7-CA67-4312-965E-226BCEA85023}, 100 + /// + public ShellProperty Flags + { + get + { + var key = SystemProperties.System.Message.Flags; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.FromAddress -- PKEY_Message_FromAddress + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 13 + /// + public ShellProperty FromAddress + { + get + { + var key = SystemProperties.System.Message.FromAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.FromName -- PKEY_Message_FromName + /// Description: Address in from field as person name + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 14 + /// + public ShellProperty FromName + { + get + { + var key = SystemProperties.System.Message.FromName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.HasAttachments -- PKEY_Message_HasAttachments + /// Description: + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {9C1FCF74-2D97-41BA-B4AE-CB2E3661A6E4}, 8 + /// + public ShellProperty HasAttachments + { + get + { + var key = SystemProperties.System.Message.HasAttachments; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.IsFwdOrReply -- PKEY_Message_IsFwdOrReply + /// Description: + /// Type: Int32 -- VT_I4 + /// FormatID: {9A9BC088-4F6D-469E-9919-E705412040F9}, 100 + /// + public ShellProperty IsFwdOrReply + { + get + { + var key = SystemProperties.System.Message.IsFwdOrReply; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.MessageClass -- PKEY_Message_MessageClass + /// Description: What type of outlook msg this is (meeting, task, mail, etc.) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CD9ED458-08CE-418F-A70E-F912C7BB9C5C}, 103 + /// + public ShellProperty MessageClass + { + get + { + var key = SystemProperties.System.Message.MessageClass; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.ProofInProgress -- PKEY_Message_ProofInProgress + /// Description: This property will be true if the message junk email proofing is still in progress. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {9098F33C-9A7D-48A8-8DE5-2E1227A64E91}, 100 + /// + public ShellProperty ProofInProgress + { + get + { + var key = SystemProperties.System.Message.ProofInProgress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.SenderAddress -- PKEY_Message_SenderAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0BE1C8E7-1981-4676-AE14-FDD78F05A6E7}, 100 + /// + public ShellProperty SenderAddress + { + get + { + var key = SystemProperties.System.Message.SenderAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.SenderName -- PKEY_Message_SenderName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0DA41CFA-D224-4A18-AE2F-596158DB4B3A}, 100 + /// + public ShellProperty SenderName + { + get + { + var key = SystemProperties.System.Message.SenderName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.Store -- PKEY_Message_Store + /// Description: The store (aka protocol handler) FILE, MAIL, OUTLOOKEXPRESS + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 15 + /// + public ShellProperty Store + { + get + { + var key = SystemProperties.System.Message.Store; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.ToAddress -- PKEY_Message_ToAddress + /// Description: Addresses in To: field + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 16 + /// + public ShellProperty ToAddress + { + get + { + var key = SystemProperties.System.Message.ToAddress; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.ToDoFlags -- PKEY_Message_ToDoFlags + /// Description: Flags associated with a message flagged to know if it's still active, if it was custom flagged, etc. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {1F856A9F-6900-4ABA-9505-2D5F1B4D66CB}, 100 + /// + public ShellProperty ToDoFlags + { + get + { + var key = SystemProperties.System.Message.ToDoFlags; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.ToDoTitle -- PKEY_Message_ToDoTitle + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {BCCC8A3C-8CEF-42E5-9B1C-C69079398BC7}, 100 + /// + public ShellProperty ToDoTitle + { + get + { + var key = SystemProperties.System.Message.ToDoTitle; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Message.ToName -- PKEY_Message_ToName + /// Description: Person names in To: field + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 17 + /// + public ShellProperty ToName + { + get + { + var key = SystemProperties.System.Message.ToName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Music Properties + /// + public class PropertySystemMusic : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemMusic(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Music.AlbumArtist -- PKEY_Music_AlbumArtist + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 13 (PIDSI_MUSIC_ALBUM_ARTIST) + /// + public ShellProperty AlbumArtist + { + get + { + var key = SystemProperties.System.Music.AlbumArtist; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.AlbumID -- PKEY_Music_AlbumID + /// Description: Concatenation of System.Music.AlbumArtist and System.Music.AlbumTitle, suitable for indexing and display. + ///Used to differentiate albums with the same title from different artists. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 100 + /// + public ShellProperty AlbumID + { + get + { + var key = SystemProperties.System.Music.AlbumID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.AlbumTitle -- PKEY_Music_AlbumTitle + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 4 (PIDSI_MUSIC_ALBUM) + /// + public ShellProperty AlbumTitle + { + get + { + var key = SystemProperties.System.Music.AlbumTitle; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.Artist -- PKEY_Music_Artist + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 2 (PIDSI_MUSIC_ARTIST) + /// + public ShellProperty Artist + { + get + { + var key = SystemProperties.System.Music.Artist; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.BeatsPerMinute -- PKEY_Music_BeatsPerMinute + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 35 (PIDSI_MUSIC_BEATS_PER_MINUTE) + /// + public ShellProperty BeatsPerMinute + { + get + { + var key = SystemProperties.System.Music.BeatsPerMinute; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.Composer -- PKEY_Music_Composer + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 19 (PIDMSI_COMPOSER) + /// + public ShellProperty Composer + { + get + { + var key = SystemProperties.System.Music.Composer; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.Conductor -- PKEY_Music_Conductor + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 36 (PIDSI_MUSIC_CONDUCTOR) + /// + public ShellProperty Conductor + { + get + { + var key = SystemProperties.System.Music.Conductor; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.ContentGroupDescription -- PKEY_Music_ContentGroupDescription + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 33 (PIDSI_MUSIC_CONTENT_GROUP_DESCRIPTION) + /// + public ShellProperty ContentGroupDescription + { + get + { + var key = SystemProperties.System.Music.ContentGroupDescription; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.DisplayArtist -- PKEY_Music_DisplayArtist + /// Description: This property returns the best representation of Album Artist for a given music file + ///based upon AlbumArtist, ContributingArtist and compilation info. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FD122953-FA93-4EF7-92C3-04C946B2F7C8}, 100 + /// + public ShellProperty DisplayArtist + { + get + { + var key = SystemProperties.System.Music.DisplayArtist; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.Genre -- PKEY_Music_Genre + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 11 (PIDSI_MUSIC_GENRE) + /// + public ShellProperty Genre + { + get + { + var key = SystemProperties.System.Music.Genre; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.InitialKey -- PKEY_Music_InitialKey + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 34 (PIDSI_MUSIC_INITIAL_KEY) + /// + public ShellProperty InitialKey + { + get + { + var key = SystemProperties.System.Music.InitialKey; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.IsCompilation -- PKEY_Music_IsCompilation + /// Description: Indicates whether the file is part of a compilation. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {C449D5CB-9EA4-4809-82E8-AF9D59DED6D1}, 100 + /// + public ShellProperty IsCompilation + { + get + { + var key = SystemProperties.System.Music.IsCompilation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.Lyrics -- PKEY_Music_Lyrics + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 12 (PIDSI_MUSIC_LYRICS) + /// + public ShellProperty Lyrics + { + get + { + var key = SystemProperties.System.Music.Lyrics; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.Mood -- PKEY_Music_Mood + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 39 (PIDSI_MUSIC_MOOD) + /// + public ShellProperty Mood + { + get + { + var key = SystemProperties.System.Music.Mood; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.PartOfSet -- PKEY_Music_PartOfSet + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 37 (PIDSI_MUSIC_PART_OF_SET) + /// + public ShellProperty PartOfSet + { + get + { + var key = SystemProperties.System.Music.PartOfSet; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.Period -- PKEY_Music_Period + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 31 (PIDMSI_PERIOD) + /// + public ShellProperty Period + { + get + { + var key = SystemProperties.System.Music.Period; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.SynchronizedLyrics -- PKEY_Music_SynchronizedLyrics + /// Description: + /// Type: Blob -- VT_BLOB + /// FormatID: {6B223B6A-162E-4AA9-B39F-05D678FC6D77}, 100 + /// + public ShellProperty SynchronizedLyrics + { + get + { + var key = SystemProperties.System.Music.SynchronizedLyrics; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Music.TrackNumber -- PKEY_Music_TrackNumber + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 7 (PIDSI_MUSIC_TRACK) + /// + public ShellProperty TrackNumber + { + get + { + var key = SystemProperties.System.Music.TrackNumber; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Note Properties + /// + public class PropertySystemNote : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemNote(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Note.Color -- PKEY_Note_Color + /// Description: + /// Type: UInt16 -- VT_UI2 + /// FormatID: {4776CAFA-BCE4-4CB1-A23E-265E76D8EB11}, 100 + /// + public ShellProperty Color + { + get + { + var key = SystemProperties.System.Note.Color; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Note.ColorText -- PKEY_Note_ColorText + /// Description: This is the user-friendly form of System.Note.Color. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {46B4E8DE-CDB2-440D-885C-1658EB65B914}, 100 + /// + public ShellProperty ColorText + { + get + { + var key = SystemProperties.System.Note.ColorText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Photo Properties + /// + public class PropertySystemPhoto : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemPhoto(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Photo.Aperture -- PKEY_Photo_Aperture + /// Description: PropertyTagExifAperture. Calculated from PKEY_Photo_ApertureNumerator and PKEY_Photo_ApertureDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37378 + /// + public ShellProperty Aperture + { + get + { + var key = SystemProperties.System.Photo.Aperture; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ApertureDenominator -- PKEY_Photo_ApertureDenominator + /// Description: Denominator of PKEY_Photo_Aperture + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {E1A9A38B-6685-46BD-875E-570DC7AD7320}, 100 + /// + public ShellProperty ApertureDenominator + { + get + { + var key = SystemProperties.System.Photo.ApertureDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ApertureNumerator -- PKEY_Photo_ApertureNumerator + /// Description: Numerator of PKEY_Photo_Aperture + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {0337ECEC-39FB-4581-A0BD-4C4CC51E9914}, 100 + /// + public ShellProperty ApertureNumerator + { + get + { + var key = SystemProperties.System.Photo.ApertureNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.Brightness -- PKEY_Photo_Brightness + /// Description: This is the brightness of the photo. + /// + ///Calculated from PKEY_Photo_BrightnessNumerator and PKEY_Photo_BrightnessDenominator. + /// + ///The units are "APEX", normally in the range of -99.99 to 99.99. If the numerator of + ///the recorded value is FFFFFFFF.H, "Unknown" should be indicated. + /// + /// Type: Double -- VT_R8 + /// FormatID: {1A701BF6-478C-4361-83AB-3701BB053C58}, 100 (PropertyTagExifBrightness) + /// + public ShellProperty Brightness + { + get + { + var key = SystemProperties.System.Photo.Brightness; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.BrightnessDenominator -- PKEY_Photo_BrightnessDenominator + /// Description: Denominator of PKEY_Photo_Brightness + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {6EBE6946-2321-440A-90F0-C043EFD32476}, 100 + /// + public ShellProperty BrightnessDenominator + { + get + { + var key = SystemProperties.System.Photo.BrightnessDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.BrightnessNumerator -- PKEY_Photo_BrightnessNumerator + /// Description: Numerator of PKEY_Photo_Brightness + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {9E7D118F-B314-45A0-8CFB-D654B917C9E9}, 100 + /// + public ShellProperty BrightnessNumerator + { + get + { + var key = SystemProperties.System.Photo.BrightnessNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.CameraManufacturer -- PKEY_Photo_CameraManufacturer + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 271 (PropertyTagEquipMake) + /// + public ShellProperty CameraManufacturer + { + get + { + var key = SystemProperties.System.Photo.CameraManufacturer; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.CameraModel -- PKEY_Photo_CameraModel + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 272 (PropertyTagEquipModel) + /// + public ShellProperty CameraModel + { + get + { + var key = SystemProperties.System.Photo.CameraModel; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.CameraSerialNumber -- PKEY_Photo_CameraSerialNumber + /// Description: Serial number of camera that produced this photo + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 273 + /// + public ShellProperty CameraSerialNumber + { + get + { + var key = SystemProperties.System.Photo.CameraSerialNumber; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.Contrast -- PKEY_Photo_Contrast + /// Description: This indicates the direction of contrast processing applied by the camera + ///when the image was shot. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {2A785BA9-8D23-4DED-82E6-60A350C86A10}, 100 + /// + public ShellProperty Contrast + { + get + { + var key = SystemProperties.System.Photo.Contrast; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ContrastText -- PKEY_Photo_ContrastText + /// Description: This is the user-friendly form of System.Photo.Contrast. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {59DDE9F2-5253-40EA-9A8B-479E96C6249A}, 100 + /// + public ShellProperty ContrastText + { + get + { + var key = SystemProperties.System.Photo.ContrastText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.DateTaken -- PKEY_Photo_DateTaken + /// Description: PropertyTagExifDTOrig + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 36867 + /// + public ShellProperty DateTaken + { + get + { + var key = SystemProperties.System.Photo.DateTaken; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.DigitalZoom -- PKEY_Photo_DigitalZoom + /// Description: PropertyTagExifDigitalZoom. Calculated from PKEY_Photo_DigitalZoomNumerator and PKEY_Photo_DigitalZoomDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: {F85BF840-A925-4BC2-B0C4-8E36B598679E}, 100 + /// + public ShellProperty DigitalZoom + { + get + { + var key = SystemProperties.System.Photo.DigitalZoom; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.DigitalZoomDenominator -- PKEY_Photo_DigitalZoomDenominator + /// Description: Denominator of PKEY_Photo_DigitalZoom + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {745BAF0E-E5C1-4CFB-8A1B-D031A0A52393}, 100 + /// + public ShellProperty DigitalZoomDenominator + { + get + { + var key = SystemProperties.System.Photo.DigitalZoomDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.DigitalZoomNumerator -- PKEY_Photo_DigitalZoomNumerator + /// Description: Numerator of PKEY_Photo_DigitalZoom + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {16CBB924-6500-473B-A5BE-F1599BCBE413}, 100 + /// + public ShellProperty DigitalZoomNumerator + { + get + { + var key = SystemProperties.System.Photo.DigitalZoomNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.Event -- PKEY_Photo_Event + /// Description: The event at which the photo was taken + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 18248 + /// + public ShellProperty Event + { + get + { + var key = SystemProperties.System.Photo.Event; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.EXIFVersion -- PKEY_Photo_EXIFVersion + /// Description: The EXIF version. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D35F743A-EB2E-47F2-A286-844132CB1427}, 100 + /// + public ShellProperty EXIFVersion + { + get + { + var key = SystemProperties.System.Photo.EXIFVersion; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ExposureBias -- PKEY_Photo_ExposureBias + /// Description: PropertyTagExifExposureBias. Calculated from PKEY_Photo_ExposureBiasNumerator and PKEY_Photo_ExposureBiasDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37380 + /// + public ShellProperty ExposureBias + { + get + { + var key = SystemProperties.System.Photo.ExposureBias; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ExposureBiasDenominator -- PKEY_Photo_ExposureBiasDenominator + /// Description: Denominator of PKEY_Photo_ExposureBias + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {AB205E50-04B7-461C-A18C-2F233836E627}, 100 + /// + public ShellProperty ExposureBiasDenominator + { + get + { + var key = SystemProperties.System.Photo.ExposureBiasDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ExposureBiasNumerator -- PKEY_Photo_ExposureBiasNumerator + /// Description: Numerator of PKEY_Photo_ExposureBias + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {738BF284-1D87-420B-92CF-5834BF6EF9ED}, 100 + /// + public ShellProperty ExposureBiasNumerator + { + get + { + var key = SystemProperties.System.Photo.ExposureBiasNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ExposureIndex -- PKEY_Photo_ExposureIndex + /// Description: PropertyTagExifExposureIndex. Calculated from PKEY_Photo_ExposureIndexNumerator and PKEY_Photo_ExposureIndexDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: {967B5AF8-995A-46ED-9E11-35B3C5B9782D}, 100 + /// + public ShellProperty ExposureIndex + { + get + { + var key = SystemProperties.System.Photo.ExposureIndex; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ExposureIndexDenominator -- PKEY_Photo_ExposureIndexDenominator + /// Description: Denominator of PKEY_Photo_ExposureIndex + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {93112F89-C28B-492F-8A9D-4BE2062CEE8A}, 100 + /// + public ShellProperty ExposureIndexDenominator + { + get + { + var key = SystemProperties.System.Photo.ExposureIndexDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ExposureIndexNumerator -- PKEY_Photo_ExposureIndexNumerator + /// Description: Numerator of PKEY_Photo_ExposureIndex + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {CDEDCF30-8919-44DF-8F4C-4EB2FFDB8D89}, 100 + /// + public ShellProperty ExposureIndexNumerator + { + get + { + var key = SystemProperties.System.Photo.ExposureIndexNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ExposureProgram -- PKEY_Photo_ExposureProgram + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 34850 (PropertyTagExifExposureProg) + /// + public ShellProperty ExposureProgram + { + get + { + var key = SystemProperties.System.Photo.ExposureProgram; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ExposureProgramText -- PKEY_Photo_ExposureProgramText + /// Description: This is the user-friendly form of System.Photo.ExposureProgram. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FEC690B7-5F30-4646-AE47-4CAAFBA884A3}, 100 + /// + public ShellProperty ExposureProgramText + { + get + { + var key = SystemProperties.System.Photo.ExposureProgramText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ExposureTime -- PKEY_Photo_ExposureTime + /// Description: PropertyTagExifExposureTime. Calculated from PKEY_Photo_ExposureTimeNumerator and PKEY_Photo_ExposureTimeDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 33434 + /// + public ShellProperty ExposureTime + { + get + { + var key = SystemProperties.System.Photo.ExposureTime; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ExposureTimeDenominator -- PKEY_Photo_ExposureTimeDenominator + /// Description: Denominator of PKEY_Photo_ExposureTime + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {55E98597-AD16-42E0-B624-21599A199838}, 100 + /// + public ShellProperty ExposureTimeDenominator + { + get + { + var key = SystemProperties.System.Photo.ExposureTimeDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ExposureTimeNumerator -- PKEY_Photo_ExposureTimeNumerator + /// Description: Numerator of PKEY_Photo_ExposureTime + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {257E44E2-9031-4323-AC38-85C552871B2E}, 100 + /// + public ShellProperty ExposureTimeNumerator + { + get + { + var key = SystemProperties.System.Photo.ExposureTimeNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.Flash -- PKEY_Photo_Flash + /// Description: PropertyTagExifFlash + /// + /// Type: Byte -- VT_UI1 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37385 + /// + public ShellProperty Flash + { + get + { + var key = SystemProperties.System.Photo.Flash; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FlashEnergy -- PKEY_Photo_FlashEnergy + /// Description: PropertyTagExifFlashEnergy. Calculated from PKEY_Photo_FlashEnergyNumerator and PKEY_Photo_FlashEnergyDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 41483 + /// + public ShellProperty FlashEnergy + { + get + { + var key = SystemProperties.System.Photo.FlashEnergy; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FlashEnergyDenominator -- PKEY_Photo_FlashEnergyDenominator + /// Description: Denominator of PKEY_Photo_FlashEnergy + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {D7B61C70-6323-49CD-A5FC-C84277162C97}, 100 + /// + public ShellProperty FlashEnergyDenominator + { + get + { + var key = SystemProperties.System.Photo.FlashEnergyDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FlashEnergyNumerator -- PKEY_Photo_FlashEnergyNumerator + /// Description: Numerator of PKEY_Photo_FlashEnergy + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {FCAD3D3D-0858-400F-AAA3-2F66CCE2A6BC}, 100 + /// + public ShellProperty FlashEnergyNumerator + { + get + { + var key = SystemProperties.System.Photo.FlashEnergyNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FlashManufacturer -- PKEY_Photo_FlashManufacturer + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {AABAF6C9-E0C5-4719-8585-57B103E584FE}, 100 + /// + public ShellProperty FlashManufacturer + { + get + { + var key = SystemProperties.System.Photo.FlashManufacturer; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FlashModel -- PKEY_Photo_FlashModel + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FE83BB35-4D1A-42E2-916B-06F3E1AF719E}, 100 + /// + public ShellProperty FlashModel + { + get + { + var key = SystemProperties.System.Photo.FlashModel; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FlashText -- PKEY_Photo_FlashText + /// Description: This is the user-friendly form of System.Photo.Flash. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6B8B68F6-200B-47EA-8D25-D8050F57339F}, 100 + /// + public ShellProperty FlashText + { + get + { + var key = SystemProperties.System.Photo.FlashText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FNumber -- PKEY_Photo_FNumber + /// Description: PropertyTagExifFNumber. Calculated from PKEY_Photo_FNumberNumerator and PKEY_Photo_FNumberDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 33437 + /// + public ShellProperty FNumber + { + get + { + var key = SystemProperties.System.Photo.FNumber; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FNumberDenominator -- PKEY_Photo_FNumberDenominator + /// Description: Denominator of PKEY_Photo_FNumber + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {E92A2496-223B-4463-A4E3-30EABBA79D80}, 100 + /// + public ShellProperty FNumberDenominator + { + get + { + var key = SystemProperties.System.Photo.FNumberDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FNumberNumerator -- PKEY_Photo_FNumberNumerator + /// Description: Numerator of PKEY_Photo_FNumber + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {1B97738A-FDFC-462F-9D93-1957E08BE90C}, 100 + /// + public ShellProperty FNumberNumerator + { + get + { + var key = SystemProperties.System.Photo.FNumberNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FocalLength -- PKEY_Photo_FocalLength + /// Description: PropertyTagExifFocalLength. Calculated from PKEY_Photo_FocalLengthNumerator and PKEY_Photo_FocalLengthDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37386 + /// + public ShellProperty FocalLength + { + get + { + var key = SystemProperties.System.Photo.FocalLength; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FocalLengthDenominator -- PKEY_Photo_FocalLengthDenominator + /// Description: Denominator of PKEY_Photo_FocalLength + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {305BC615-DCA1-44A5-9FD4-10C0BA79412E}, 100 + /// + public ShellProperty FocalLengthDenominator + { + get + { + var key = SystemProperties.System.Photo.FocalLengthDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FocalLengthInFilm -- PKEY_Photo_FocalLengthInFilm + /// Description: + /// Type: UInt16 -- VT_UI2 + /// FormatID: {A0E74609-B84D-4F49-B860-462BD9971F98}, 100 + /// + public ShellProperty FocalLengthInFilm + { + get + { + var key = SystemProperties.System.Photo.FocalLengthInFilm; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FocalLengthNumerator -- PKEY_Photo_FocalLengthNumerator + /// Description: Numerator of PKEY_Photo_FocalLength + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {776B6B3B-1E3D-4B0C-9A0E-8FBAF2A8492A}, 100 + /// + public ShellProperty FocalLengthNumerator + { + get + { + var key = SystemProperties.System.Photo.FocalLengthNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FocalPlaneXResolution -- PKEY_Photo_FocalPlaneXResolution + /// Description: PropertyTagExifFocalXRes. Calculated from PKEY_Photo_FocalPlaneXResolutionNumerator and + ///PKEY_Photo_FocalPlaneXResolutionDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {CFC08D97-C6F7-4484-89DD-EBEF4356FE76}, 100 + /// + public ShellProperty FocalPlaneXResolution + { + get + { + var key = SystemProperties.System.Photo.FocalPlaneXResolution; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FocalPlaneXResolutionDenominator -- PKEY_Photo_FocalPlaneXResolutionDenominator + /// Description: Denominator of PKEY_Photo_FocalPlaneXResolution + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {0933F3F5-4786-4F46-A8E8-D64DD37FA521}, 100 + /// + public ShellProperty FocalPlaneXResolutionDenominator + { + get + { + var key = SystemProperties.System.Photo.FocalPlaneXResolutionDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FocalPlaneXResolutionNumerator -- PKEY_Photo_FocalPlaneXResolutionNumerator + /// Description: Numerator of PKEY_Photo_FocalPlaneXResolution + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {DCCB10AF-B4E2-4B88-95F9-031B4D5AB490}, 100 + /// + public ShellProperty FocalPlaneXResolutionNumerator + { + get + { + var key = SystemProperties.System.Photo.FocalPlaneXResolutionNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FocalPlaneYResolution -- PKEY_Photo_FocalPlaneYResolution + /// Description: PropertyTagExifFocalYRes. Calculated from PKEY_Photo_FocalPlaneYResolutionNumerator and + ///PKEY_Photo_FocalPlaneYResolutionDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {4FFFE4D0-914F-4AC4-8D6F-C9C61DE169B1}, 100 + /// + public ShellProperty FocalPlaneYResolution + { + get + { + var key = SystemProperties.System.Photo.FocalPlaneYResolution; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FocalPlaneYResolutionDenominator -- PKEY_Photo_FocalPlaneYResolutionDenominator + /// Description: Denominator of PKEY_Photo_FocalPlaneYResolution + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {1D6179A6-A876-4031-B013-3347B2B64DC8}, 100 + /// + public ShellProperty FocalPlaneYResolutionDenominator + { + get + { + var key = SystemProperties.System.Photo.FocalPlaneYResolutionDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.FocalPlaneYResolutionNumerator -- PKEY_Photo_FocalPlaneYResolutionNumerator + /// Description: Numerator of PKEY_Photo_FocalPlaneYResolution + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {A2E541C5-4440-4BA8-867E-75CFC06828CD}, 100 + /// + public ShellProperty FocalPlaneYResolutionNumerator + { + get + { + var key = SystemProperties.System.Photo.FocalPlaneYResolutionNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.GainControl -- PKEY_Photo_GainControl + /// Description: This indicates the degree of overall image gain adjustment. + /// + ///Calculated from PKEY_Photo_GainControlNumerator and PKEY_Photo_GainControlDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {FA304789-00C7-4D80-904A-1E4DCC7265AA}, 100 (PropertyTagExifGainControl) + /// + public ShellProperty GainControl + { + get + { + var key = SystemProperties.System.Photo.GainControl; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.GainControlDenominator -- PKEY_Photo_GainControlDenominator + /// Description: Denominator of PKEY_Photo_GainControl + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {42864DFD-9DA4-4F77-BDED-4AAD7B256735}, 100 + /// + public ShellProperty GainControlDenominator + { + get + { + var key = SystemProperties.System.Photo.GainControlDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.GainControlNumerator -- PKEY_Photo_GainControlNumerator + /// Description: Numerator of PKEY_Photo_GainControl + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {8E8ECF7C-B7B8-4EB8-A63F-0EE715C96F9E}, 100 + /// + public ShellProperty GainControlNumerator + { + get + { + var key = SystemProperties.System.Photo.GainControlNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.GainControlText -- PKEY_Photo_GainControlText + /// Description: This is the user-friendly form of System.Photo.GainControl. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C06238B2-0BF9-4279-A723-25856715CB9D}, 100 + /// + public ShellProperty GainControlText + { + get + { + var key = SystemProperties.System.Photo.GainControlText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ISOSpeed -- PKEY_Photo_ISOSpeed + /// Description: PropertyTagExifISOSpeed + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 34855 + /// + public ShellProperty ISOSpeed + { + get + { + var key = SystemProperties.System.Photo.ISOSpeed; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.LensManufacturer -- PKEY_Photo_LensManufacturer + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E6DDCAF7-29C5-4F0A-9A68-D19412EC7090}, 100 + /// + public ShellProperty LensManufacturer + { + get + { + var key = SystemProperties.System.Photo.LensManufacturer; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.LensModel -- PKEY_Photo_LensModel + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E1277516-2B5F-4869-89B1-2E585BD38B7A}, 100 + /// + public ShellProperty LensModel + { + get + { + var key = SystemProperties.System.Photo.LensModel; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.LightSource -- PKEY_Photo_LightSource + /// Description: PropertyTagExifLightSource + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37384 + /// + public ShellProperty LightSource + { + get + { + var key = SystemProperties.System.Photo.LightSource; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.MakerNote -- PKEY_Photo_MakerNote + /// Description: + /// Type: Buffer -- VT_VECTOR | VT_UI1 (For variants: VT_ARRAY | VT_UI1) + /// FormatID: {FA303353-B659-4052-85E9-BCAC79549B84}, 100 + /// + public ShellProperty MakerNote + { + get + { + var key = SystemProperties.System.Photo.MakerNote; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.MakerNoteOffset -- PKEY_Photo_MakerNoteOffset + /// Description: + /// Type: UInt64 -- VT_UI8 + /// FormatID: {813F4124-34E6-4D17-AB3E-6B1F3C2247A1}, 100 + /// + public ShellProperty MakerNoteOffset + { + get + { + var key = SystemProperties.System.Photo.MakerNoteOffset; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.MaxAperture -- PKEY_Photo_MaxAperture + /// Description: Calculated from PKEY_Photo_MaxApertureNumerator and PKEY_Photo_MaxApertureDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: {08F6D7C2-E3F2-44FC-AF1E-5AA5C81A2D3E}, 100 + /// + public ShellProperty MaxAperture + { + get + { + var key = SystemProperties.System.Photo.MaxAperture; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.MaxApertureDenominator -- PKEY_Photo_MaxApertureDenominator + /// Description: Denominator of PKEY_Photo_MaxAperture + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {C77724D4-601F-46C5-9B89-C53F93BCEB77}, 100 + /// + public ShellProperty MaxApertureDenominator + { + get + { + var key = SystemProperties.System.Photo.MaxApertureDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.MaxApertureNumerator -- PKEY_Photo_MaxApertureNumerator + /// Description: Numerator of PKEY_Photo_MaxAperture + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {C107E191-A459-44C5-9AE6-B952AD4B906D}, 100 + /// + public ShellProperty MaxApertureNumerator + { + get + { + var key = SystemProperties.System.Photo.MaxApertureNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.MeteringMode -- PKEY_Photo_MeteringMode + /// Description: PropertyTagExifMeteringMode + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37383 + /// + public ShellProperty MeteringMode + { + get + { + var key = SystemProperties.System.Photo.MeteringMode; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.MeteringModeText -- PKEY_Photo_MeteringModeText + /// Description: This is the user-friendly form of System.Photo.MeteringMode. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F628FD8C-7BA8-465A-A65B-C5AA79263A9E}, 100 + /// + public ShellProperty MeteringModeText + { + get + { + var key = SystemProperties.System.Photo.MeteringModeText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.Orientation -- PKEY_Photo_Orientation + /// Description: This is the image orientation viewed in terms of rows and columns. + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 274 (PropertyTagOrientation) + /// + public ShellProperty Orientation + { + get + { + var key = SystemProperties.System.Photo.Orientation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.OrientationText -- PKEY_Photo_OrientationText + /// Description: This is the user-friendly form of System.Photo.Orientation. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A9EA193C-C511-498A-A06B-58E2776DCC28}, 100 + /// + public ShellProperty OrientationText + { + get + { + var key = SystemProperties.System.Photo.OrientationText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.PeopleNames -- PKEY_Photo_PeopleNames + /// Description: The people tags on an image. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: {E8309B6E-084C-49B4-B1FC-90A80331B638}, 100 + /// + public ShellProperty PeopleNames + { + get + { + var key = SystemProperties.System.Photo.PeopleNames; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.PhotometricInterpretation -- PKEY_Photo_PhotometricInterpretation + /// Description: This is the pixel composition. In JPEG compressed data, a JPEG marker is used + ///instead of this property. + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {341796F1-1DF9-4B1C-A564-91BDEFA43877}, 100 + /// + public ShellProperty PhotometricInterpretation + { + get + { + var key = SystemProperties.System.Photo.PhotometricInterpretation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.PhotometricInterpretationText -- PKEY_Photo_PhotometricInterpretationText + /// Description: This is the user-friendly form of System.Photo.PhotometricInterpretation. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {821437D6-9EAB-4765-A589-3B1CBBD22A61}, 100 + /// + public ShellProperty PhotometricInterpretationText + { + get + { + var key = SystemProperties.System.Photo.PhotometricInterpretationText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ProgramMode -- PKEY_Photo_ProgramMode + /// Description: This is the class of the program used by the camera to set exposure when the + ///picture is taken. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {6D217F6D-3F6A-4825-B470-5F03CA2FBE9B}, 100 + /// + public ShellProperty ProgramMode + { + get + { + var key = SystemProperties.System.Photo.ProgramMode; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ProgramModeText -- PKEY_Photo_ProgramModeText + /// Description: This is the user-friendly form of System.Photo.ProgramMode. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7FE3AA27-2648-42F3-89B0-454E5CB150C3}, 100 + /// + public ShellProperty ProgramModeText + { + get + { + var key = SystemProperties.System.Photo.ProgramModeText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.RelatedSoundFile -- PKEY_Photo_RelatedSoundFile + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {318A6B45-087F-4DC2-B8CC-05359551FC9E}, 100 + /// + public ShellProperty RelatedSoundFile + { + get + { + var key = SystemProperties.System.Photo.RelatedSoundFile; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.Saturation -- PKEY_Photo_Saturation + /// Description: This indicates the direction of saturation processing applied by the camera when + ///the image was shot. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {49237325-A95A-4F67-B211-816B2D45D2E0}, 100 + /// + public ShellProperty Saturation + { + get + { + var key = SystemProperties.System.Photo.Saturation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.SaturationText -- PKEY_Photo_SaturationText + /// Description: This is the user-friendly form of System.Photo.Saturation. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {61478C08-B600-4A84-BBE4-E99C45F0A072}, 100 + /// + public ShellProperty SaturationText + { + get + { + var key = SystemProperties.System.Photo.SaturationText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.Sharpness -- PKEY_Photo_Sharpness + /// Description: This indicates the direction of sharpness processing applied by the camera when + ///the image was shot. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {FC6976DB-8349-4970-AE97-B3C5316A08F0}, 100 + /// + public ShellProperty Sharpness + { + get + { + var key = SystemProperties.System.Photo.Sharpness; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.SharpnessText -- PKEY_Photo_SharpnessText + /// Description: This is the user-friendly form of System.Photo.Sharpness. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {51EC3F47-DD50-421D-8769-334F50424B1E}, 100 + /// + public ShellProperty SharpnessText + { + get + { + var key = SystemProperties.System.Photo.SharpnessText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ShutterSpeed -- PKEY_Photo_ShutterSpeed + /// Description: PropertyTagExifShutterSpeed. Calculated from PKEY_Photo_ShutterSpeedNumerator and PKEY_Photo_ShutterSpeedDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37377 + /// + public ShellProperty ShutterSpeed + { + get + { + var key = SystemProperties.System.Photo.ShutterSpeed; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ShutterSpeedDenominator -- PKEY_Photo_ShutterSpeedDenominator + /// Description: Denominator of PKEY_Photo_ShutterSpeed + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {E13D8975-81C7-4948-AE3F-37CAE11E8FF7}, 100 + /// + public ShellProperty ShutterSpeedDenominator + { + get + { + var key = SystemProperties.System.Photo.ShutterSpeedDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.ShutterSpeedNumerator -- PKEY_Photo_ShutterSpeedNumerator + /// Description: Numerator of PKEY_Photo_ShutterSpeed + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {16EA4042-D6F4-4BCA-8349-7C78D30FB333}, 100 + /// + public ShellProperty ShutterSpeedNumerator + { + get + { + var key = SystemProperties.System.Photo.ShutterSpeedNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.SubjectDistance -- PKEY_Photo_SubjectDistance + /// Description: PropertyTagExifSubjectDist. Calculated from PKEY_Photo_SubjectDistanceNumerator and PKEY_Photo_SubjectDistanceDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37382 + /// + public ShellProperty SubjectDistance + { + get + { + var key = SystemProperties.System.Photo.SubjectDistance; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.SubjectDistanceDenominator -- PKEY_Photo_SubjectDistanceDenominator + /// Description: Denominator of PKEY_Photo_SubjectDistance + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {0C840A88-B043-466D-9766-D4B26DA3FA77}, 100 + /// + public ShellProperty SubjectDistanceDenominator + { + get + { + var key = SystemProperties.System.Photo.SubjectDistanceDenominator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.SubjectDistanceNumerator -- PKEY_Photo_SubjectDistanceNumerator + /// Description: Numerator of PKEY_Photo_SubjectDistance + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {8AF4961C-F526-43E5-AA81-DB768219178D}, 100 + /// + public ShellProperty SubjectDistanceNumerator + { + get + { + var key = SystemProperties.System.Photo.SubjectDistanceNumerator; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.TagViewAggregate -- PKEY_Photo_TagViewAggregate + /// Description: A read-only aggregation of tag-like properties for use in building views. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: {B812F15D-C2D8-4BBF-BACD-79744346113F}, 100 + /// + public ShellProperty TagViewAggregate + { + get + { + var key = SystemProperties.System.Photo.TagViewAggregate; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.TranscodedForSync -- PKEY_Photo_TranscodedForSync + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {9A8EBB75-6458-4E82-BACB-35C0095B03BB}, 100 + /// + public ShellProperty TranscodedForSync + { + get + { + var key = SystemProperties.System.Photo.TranscodedForSync; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.WhiteBalance -- PKEY_Photo_WhiteBalance + /// Description: This indicates the white balance mode set when the image was shot. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {EE3D3D8A-5381-4CFA-B13B-AAF66B5F4EC9}, 100 + /// + public ShellProperty WhiteBalance + { + get + { + var key = SystemProperties.System.Photo.WhiteBalance; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Photo.WhiteBalanceText -- PKEY_Photo_WhiteBalanceText + /// Description: This is the user-friendly form of System.Photo.WhiteBalance. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6336B95E-C7A7-426D-86FD-7AE3D39C84B4}, 100 + /// + public ShellProperty WhiteBalanceText + { + get + { + var key = SystemProperties.System.Photo.WhiteBalanceText; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.PropGroup Properties + /// + public class PropertySystemPropGroup : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemPropGroup(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.PropGroup.Advanced -- PKEY_PropGroup_Advanced + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {900A403B-097B-4B95-8AE2-071FDAEEB118}, 100 + /// + public ShellProperty Advanced + { + get + { + var key = SystemProperties.System.PropGroup.Advanced; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.Audio -- PKEY_PropGroup_Audio + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {2804D469-788F-48AA-8570-71B9C187E138}, 100 + /// + public ShellProperty Audio + { + get + { + var key = SystemProperties.System.PropGroup.Audio; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.Calendar -- PKEY_PropGroup_Calendar + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {9973D2B5-BFD8-438A-BA94-5349B293181A}, 100 + /// + public ShellProperty Calendar + { + get + { + var key = SystemProperties.System.PropGroup.Calendar; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.Camera -- PKEY_PropGroup_Camera + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {DE00DE32-547E-4981-AD4B-542F2E9007D8}, 100 + /// + public ShellProperty Camera + { + get + { + var key = SystemProperties.System.PropGroup.Camera; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.Contact -- PKEY_PropGroup_Contact + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {DF975FD3-250A-4004-858F-34E29A3E37AA}, 100 + /// + public ShellProperty Contact + { + get + { + var key = SystemProperties.System.PropGroup.Contact; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.Content -- PKEY_PropGroup_Content + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {D0DAB0BA-368A-4050-A882-6C010FD19A4F}, 100 + /// + public ShellProperty Content + { + get + { + var key = SystemProperties.System.PropGroup.Content; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.Description -- PKEY_PropGroup_Description + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {8969B275-9475-4E00-A887-FF93B8B41E44}, 100 + /// + public ShellProperty Description + { + get + { + var key = SystemProperties.System.PropGroup.Description; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.FileSystem -- PKEY_PropGroup_FileSystem + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {E3A7D2C1-80FC-4B40-8F34-30EA111BDC2E}, 100 + /// + public ShellProperty FileSystem + { + get + { + var key = SystemProperties.System.PropGroup.FileSystem; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.General -- PKEY_PropGroup_General + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {CC301630-B192-4C22-B372-9F4C6D338E07}, 100 + /// + public ShellProperty General + { + get + { + var key = SystemProperties.System.PropGroup.General; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.GPS -- PKEY_PropGroup_GPS + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {F3713ADA-90E3-4E11-AAE5-FDC17685B9BE}, 100 + /// + public ShellProperty GPS + { + get + { + var key = SystemProperties.System.PropGroup.GPS; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.Image -- PKEY_PropGroup_Image + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {E3690A87-0FA8-4A2A-9A9F-FCE8827055AC}, 100 + /// + public ShellProperty Image + { + get + { + var key = SystemProperties.System.PropGroup.Image; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.Media -- PKEY_PropGroup_Media + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {61872CF7-6B5E-4B4B-AC2D-59DA84459248}, 100 + /// + public ShellProperty Media + { + get + { + var key = SystemProperties.System.PropGroup.Media; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.MediaAdvanced -- PKEY_PropGroup_MediaAdvanced + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {8859A284-DE7E-4642-99BA-D431D044B1EC}, 100 + /// + public ShellProperty MediaAdvanced + { + get + { + var key = SystemProperties.System.PropGroup.MediaAdvanced; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.Message -- PKEY_PropGroup_Message + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {7FD7259D-16B4-4135-9F97-7C96ECD2FA9E}, 100 + /// + public ShellProperty Message + { + get + { + var key = SystemProperties.System.PropGroup.Message; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.Music -- PKEY_PropGroup_Music + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {68DD6094-7216-40F1-A029-43FE7127043F}, 100 + /// + public ShellProperty Music + { + get + { + var key = SystemProperties.System.PropGroup.Music; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.Origin -- PKEY_PropGroup_Origin + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {2598D2FB-5569-4367-95DF-5CD3A177E1A5}, 100 + /// + public ShellProperty Origin + { + get + { + var key = SystemProperties.System.PropGroup.Origin; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.PhotoAdvanced -- PKEY_PropGroup_PhotoAdvanced + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {0CB2BF5A-9EE7-4A86-8222-F01E07FDADAF}, 100 + /// + public ShellProperty PhotoAdvanced + { + get + { + var key = SystemProperties.System.PropGroup.PhotoAdvanced; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.RecordedTV -- PKEY_PropGroup_RecordedTV + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {E7B33238-6584-4170-A5C0-AC25EFD9DA56}, 100 + /// + public ShellProperty RecordedTV + { + get + { + var key = SystemProperties.System.PropGroup.RecordedTV; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropGroup.Video -- PKEY_PropGroup_Video + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {BEBE0920-7671-4C54-A3EB-49FDDFC191EE}, 100 + /// + public ShellProperty Video + { + get + { + var key = SystemProperties.System.PropGroup.Video; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.PropList Properties + /// + public class PropertySystemPropList : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemPropList(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.PropList.ConflictPrompt -- PKEY_PropList_ConflictPrompt + /// Description: The list of properties to show in the file operation conflict resolution dialog. Properties with empty + ///values will not be displayed. Register under the regvalue of "ConflictPrompt". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 11 + /// + public ShellProperty ConflictPrompt + { + get + { + var key = SystemProperties.System.PropList.ConflictPrompt; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropList.ContentViewModeForBrowse -- PKEY_PropList_ContentViewModeForBrowse + /// Description: The list of properties to show in the content view mode of an item in the context of browsing. + ///Register the regvalue under the name of "ContentViewModeForBrowse". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 13 + /// + public ShellProperty ContentViewModeForBrowse + { + get + { + var key = SystemProperties.System.PropList.ContentViewModeForBrowse; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropList.ContentViewModeForSearch -- PKEY_PropList_ContentViewModeForSearch + /// Description: The list of properties to show in the content view mode of an item in the context of searching. + ///Register the regvalue under the name of "ContentViewModeForSearch". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 14 + /// + public ShellProperty ContentViewModeForSearch + { + get + { + var key = SystemProperties.System.PropList.ContentViewModeForSearch; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropList.ExtendedTileInfo -- PKEY_PropList_ExtendedTileInfo + /// Description: The list of properties to show in the listview on extended tiles. Register under the regvalue of + ///"ExtendedTileInfo". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 9 + /// + public ShellProperty ExtendedTileInfo + { + get + { + var key = SystemProperties.System.PropList.ExtendedTileInfo; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropList.FileOperationPrompt -- PKEY_PropList_FileOperationPrompt + /// Description: The list of properties to show in the file operation confirmation dialog. Properties with empty values + ///will not be displayed. If this list is not specified, then the InfoTip property list is used instead. + ///Register under the regvalue of "FileOperationPrompt". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 10 + /// + public ShellProperty FileOperationPrompt + { + get + { + var key = SystemProperties.System.PropList.FileOperationPrompt; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropList.FullDetails -- PKEY_PropList_FullDetails + /// Description: The list of all the properties to show in the details page. Property groups can be included in this list + ///in order to more easily organize the UI. Register under the regvalue of "FullDetails". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 2 + /// + public ShellProperty FullDetails + { + get + { + var key = SystemProperties.System.PropList.FullDetails; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropList.InfoTip -- PKEY_PropList_InfoTip + /// Description: The list of properties to show in the infotip. Properties with empty values will not be displayed. Register + ///under the regvalue of "InfoTip". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 4 (PID_PROPLIST_INFOTIP) + /// + public ShellProperty InfoTip + { + get + { + var key = SystemProperties.System.PropList.InfoTip; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropList.NonPersonal -- PKEY_PropList_NonPersonal + /// Description: The list of properties that are considered 'non-personal'. When told to remove all non-personal properties + ///from a given file, the system will leave these particular properties untouched. Register under the regvalue + ///of "NonPersonal". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {49D1091F-082E-493F-B23F-D2308AA9668C}, 100 + /// + public ShellProperty NonPersonal + { + get + { + var key = SystemProperties.System.PropList.NonPersonal; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropList.PreviewDetails -- PKEY_PropList_PreviewDetails + /// Description: The list of properties to display in the preview pane. Register under the regvalue of "PreviewDetails". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 8 + /// + public ShellProperty PreviewDetails + { + get + { + var key = SystemProperties.System.PropList.PreviewDetails; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropList.PreviewTitle -- PKEY_PropList_PreviewTitle + /// Description: The one or two properties to display in the preview pane title section. The optional second property is + ///displayed as a subtitle. Register under the regvalue of "PreviewTitle". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 6 + /// + public ShellProperty PreviewTitle + { + get + { + var key = SystemProperties.System.PropList.PreviewTitle; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropList.QuickTip -- PKEY_PropList_QuickTip + /// Description: The list of properties to show in the infotip when the item is on a slow network. Properties with empty + ///values will not be displayed. Register under the regvalue of "QuickTip". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 5 (PID_PROPLIST_QUICKTIP) + /// + public ShellProperty QuickTip + { + get + { + var key = SystemProperties.System.PropList.QuickTip; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropList.TileInfo -- PKEY_PropList_TileInfo + /// Description: The list of properties to show in the listview on tiles. Register under the regvalue of "TileInfo". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 3 (PID_PROPLIST_TILEINFO) + /// + public ShellProperty TileInfo + { + get + { + var key = SystemProperties.System.PropList.TileInfo; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.PropList.XPDetailsPanel -- PKEY_PropList_XPDetailsPanel + /// Description: The list of properties to display in the XP webview details panel. Obsolete. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_WebView) {F2275480-F782-4291-BD94-F13693513AEC}, 0 (PID_DISPLAY_PROPERTIES) + /// + public ShellProperty XPDetailsPanel + { + get + { + var key = SystemProperties.System.PropList.XPDetailsPanel; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.RecordedTV Properties + /// + public class PropertySystemRecordedTV : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemRecordedTV(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.RecordedTV.ChannelNumber -- PKEY_RecordedTV_ChannelNumber + /// Description: Example: 42 + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 7 + /// + public ShellProperty ChannelNumber + { + get + { + var key = SystemProperties.System.RecordedTV.ChannelNumber; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.Credits -- PKEY_RecordedTV_Credits + /// Description: Example: "Don Messick/Frank Welker/Casey Kasem/Heather North/Nicole Jaffe;;;" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 4 + /// + public ShellProperty Credits + { + get + { + var key = SystemProperties.System.RecordedTV.Credits; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.DateContentExpires -- PKEY_RecordedTV_DateContentExpires + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 15 + /// + public ShellProperty DateContentExpires + { + get + { + var key = SystemProperties.System.RecordedTV.DateContentExpires; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.EpisodeName -- PKEY_RecordedTV_EpisodeName + /// Description: Example: "Nowhere to Hyde" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 2 + /// + public ShellProperty EpisodeName + { + get + { + var key = SystemProperties.System.RecordedTV.EpisodeName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.IsATSCContent -- PKEY_RecordedTV_IsATSCContent + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 16 + /// + public ShellProperty IsATSCContent + { + get + { + var key = SystemProperties.System.RecordedTV.IsATSCContent; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.IsClosedCaptioningAvailable -- PKEY_RecordedTV_IsClosedCaptioningAvailable + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 12 + /// + public ShellProperty IsClosedCaptioningAvailable + { + get + { + var key = SystemProperties.System.RecordedTV.IsClosedCaptioningAvailable; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.IsDTVContent -- PKEY_RecordedTV_IsDTVContent + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 17 + /// + public ShellProperty IsDTVContent + { + get + { + var key = SystemProperties.System.RecordedTV.IsDTVContent; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.IsHDContent -- PKEY_RecordedTV_IsHDContent + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 18 + /// + public ShellProperty IsHDContent + { + get + { + var key = SystemProperties.System.RecordedTV.IsHDContent; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.IsRepeatBroadcast -- PKEY_RecordedTV_IsRepeatBroadcast + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 13 + /// + public ShellProperty IsRepeatBroadcast + { + get + { + var key = SystemProperties.System.RecordedTV.IsRepeatBroadcast; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.IsSAP -- PKEY_RecordedTV_IsSAP + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 14 + /// + public ShellProperty IsSAP + { + get + { + var key = SystemProperties.System.RecordedTV.IsSAP; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.NetworkAffiliation -- PKEY_RecordedTV_NetworkAffiliation + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {2C53C813-FB63-4E22-A1AB-0B331CA1E273}, 100 + /// + public ShellProperty NetworkAffiliation + { + get + { + var key = SystemProperties.System.RecordedTV.NetworkAffiliation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.OriginalBroadcastDate -- PKEY_RecordedTV_OriginalBroadcastDate + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {4684FE97-8765-4842-9C13-F006447B178C}, 100 + /// + public ShellProperty OriginalBroadcastDate + { + get + { + var key = SystemProperties.System.RecordedTV.OriginalBroadcastDate; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.ProgramDescription -- PKEY_RecordedTV_ProgramDescription + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 3 + /// + public ShellProperty ProgramDescription + { + get + { + var key = SystemProperties.System.RecordedTV.ProgramDescription; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.RecordingTime -- PKEY_RecordedTV_RecordingTime + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {A5477F61-7A82-4ECA-9DDE-98B69B2479B3}, 100 + /// + public ShellProperty RecordingTime + { + get + { + var key = SystemProperties.System.RecordedTV.RecordingTime; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.StationCallSign -- PKEY_RecordedTV_StationCallSign + /// Description: Example: "TOONP" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 5 + /// + public ShellProperty StationCallSign + { + get + { + var key = SystemProperties.System.RecordedTV.StationCallSign; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.RecordedTV.StationName -- PKEY_RecordedTV_StationName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {1B5439E7-EBA1-4AF8-BDD7-7AF1D4549493}, 100 + /// + public ShellProperty StationName + { + get + { + var key = SystemProperties.System.RecordedTV.StationName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Search Properties + /// + public class PropertySystemSearch : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemSearch(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Search.AutoSummary -- PKEY_Search_AutoSummary + /// Description: General Summary of the document. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {560C36C0-503A-11CF-BAA1-00004C752A9A}, 2 + /// + public ShellProperty AutoSummary + { + get + { + var key = SystemProperties.System.Search.AutoSummary; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.ContainerHash -- PKEY_Search_ContainerHash + /// Description: Hash code used to identify attachments to be deleted based on a common container url + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {BCEEE283-35DF-4D53-826A-F36A3EEFC6BE}, 100 + /// + public ShellProperty ContainerHash + { + get + { + var key = SystemProperties.System.Search.ContainerHash; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.Contents -- PKEY_Search_Contents + /// Description: The contents of the item. This property is for query restrictions only; it cannot be retrieved in a + ///query result. The Indexing Service friendly name is 'contents'. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 19 (PID_STG_CONTENTS) + /// + public ShellProperty Contents + { + get + { + var key = SystemProperties.System.Search.Contents; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.EntryID -- PKEY_Search_EntryID + /// Description: The entry ID for an item within a given catalog in the Windows Search Index. + ///This value may be recycled, and therefore is not considered unique over time. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_Query) {49691C90-7E17-101A-A91C-08002B2ECDA9}, 5 (PROPID_QUERY_WORKID) + /// + public ShellProperty EntryID + { + get + { + var key = SystemProperties.System.Search.EntryID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.ExtendedProperties -- PKEY_Search_ExtendedProperties + /// Description: + /// Type: Blob -- VT_BLOB + /// FormatID: {7B03B546-FA4F-4A52-A2FE-03D5311E5865}, 100 + /// + public ShellProperty ExtendedProperties + { + get + { + var key = SystemProperties.System.Search.ExtendedProperties; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.GatherTime -- PKEY_Search_GatherTime + /// Description: The Datetime that the Windows Search Gatherer process last pushed properties of this document to the Windows Search Gatherer Plugins. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {0B63E350-9CCC-11D0-BCDB-00805FCCCE04}, 8 + /// + public ShellProperty GatherTime + { + get + { + var key = SystemProperties.System.Search.GatherTime; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.HitCount -- PKEY_Search_HitCount + /// Description: When using CONTAINS over the Windows Search Index, this is the number of matches of the term. + ///If there are multiple CONTAINS, an AND computes the min number of hits and an OR the max number of hits. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_Query) {49691C90-7E17-101A-A91C-08002B2ECDA9}, 4 (PROPID_QUERY_HITCOUNT) + /// + public ShellProperty HitCount + { + get + { + var key = SystemProperties.System.Search.HitCount; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.IsClosedDirectory -- PKEY_Search_IsClosedDirectory + /// Description: If this property is emitted with a value of TRUE, then it indicates that this URL's last modified time applies to all of it's children, and if this URL is deleted then all of it's children are deleted as well. For example, this would be emitted as TRUE when emitting the URL of an email so that all attachments are tied to the last modified time of that email. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {0B63E343-9CCC-11D0-BCDB-00805FCCCE04}, 23 + /// + public ShellProperty IsClosedDirectory + { + get + { + var key = SystemProperties.System.Search.IsClosedDirectory; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.IsFullyContained -- PKEY_Search_IsFullyContained + /// Description: Any child URL of a URL which has System.Search.IsClosedDirectory=TRUE must emit System.Search.IsFullyContained=TRUE. This ensures that the URL is not deleted at the end of a crawl because it hasn't been visited (which is the normal mechanism for detecting deletes). For example an email attachment would emit this property + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {0B63E343-9CCC-11D0-BCDB-00805FCCCE04}, 24 + /// + public ShellProperty IsFullyContained + { + get + { + var key = SystemProperties.System.Search.IsFullyContained; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.QueryFocusedSummary -- PKEY_Search_QueryFocusedSummary + /// Description: Query Focused Summary of the document. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {560C36C0-503A-11CF-BAA1-00004C752A9A}, 3 + /// + public ShellProperty QueryFocusedSummary + { + get + { + var key = SystemProperties.System.Search.QueryFocusedSummary; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.QueryFocusedSummaryWithFallback -- PKEY_Search_QueryFocusedSummaryWithFallback + /// Description: Query Focused Summary of the document, if none is available it returns the AutoSummary. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {560C36C0-503A-11CF-BAA1-00004C752A9A}, 4 + /// + public ShellProperty QueryFocusedSummaryWithFallback + { + get + { + var key = SystemProperties.System.Search.QueryFocusedSummaryWithFallback; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.Rank -- PKEY_Search_Rank + /// Description: Relevance rank of row. Ranges from 0-1000. Larger numbers = better matches. Query-time only. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_Query) {49691C90-7E17-101A-A91C-08002B2ECDA9}, 3 (PROPID_QUERY_RANK) + /// + public ShellProperty Rank + { + get + { + var key = SystemProperties.System.Search.Rank; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.Store -- PKEY_Search_Store + /// Description: The identifier for the protocol handler that produced this item. (E.g. MAPI, CSC, FILE etc.) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A06992B3-8CAF-4ED7-A547-B259E32AC9FC}, 100 + /// + public ShellProperty Store + { + get + { + var key = SystemProperties.System.Search.Store; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.UrlToIndex -- PKEY_Search_UrlToIndex + /// Description: This property should be emitted by a container IFilter for each child URL within the container. The children will eventually be crawled by the indexer if they are within scope. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0B63E343-9CCC-11D0-BCDB-00805FCCCE04}, 2 + /// + public ShellProperty UrlToIndex + { + get + { + var key = SystemProperties.System.Search.UrlToIndex; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Search.UrlToIndexWithModificationTime -- PKEY_Search_UrlToIndexWithModificationTime + /// Description: This property is the same as System.Search.UrlToIndex except that it includes the time the URL was last modified. This is an optimization for the indexer as it doesn't have to call back into the protocol handler to ask for this information to determine if the content needs to be indexed again. The property is a vector with two elements, a VT_LPWSTR with the URL and a VT_FILETIME for the last modified time. + /// + /// Type: Multivalue Any -- VT_VECTOR | VT_NULL (For variants: VT_ARRAY | VT_NULL) + /// FormatID: {0B63E343-9CCC-11D0-BCDB-00805FCCCE04}, 12 + /// + public ShellProperty UrlToIndexWithModificationTime + { + get + { + var key = SystemProperties.System.Search.UrlToIndexWithModificationTime; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Shell Properties + /// + public class PropertySystemShell : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemShell(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Shell.OmitFromView -- PKEY_Shell_OmitFromView + /// Description: Set this to a string value of 'True' to omit this item from shell views + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DE35258C-C695-4CBC-B982-38B0AD24CED0}, 2 + /// + public ShellProperty OmitFromView + { + get + { + var key = SystemProperties.System.Shell.OmitFromView; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Shell.SFGAOFlagsStrings -- PKEY_Shell_SFGAOFlagsStrings + /// Description: Expresses the SFGAO flags as string values and is used as a query optimization. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D6942081-D53B-443D-AD47-5E059D9CD27A}, 2 + /// + public ShellProperty SFGAOFlagsStrings + { + get + { + var key = SystemProperties.System.Shell.SFGAOFlagsStrings; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Software Properties + /// + public class PropertySystemSoftware : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemSoftware(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Software.DateLastUsed -- PKEY_Software_DateLastUsed + /// Description: + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {841E4F90-FF59-4D16-8947-E81BBFFAB36D}, 16 + /// + public ShellProperty DateLastUsed + { + get + { + var key = SystemProperties.System.Software.DateLastUsed; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Software.ProductName -- PKEY_Software_ProductName + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSFMTID_VERSION) {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 7 + /// + public ShellProperty ProductName + { + get + { + var key = SystemProperties.System.Software.ProductName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Sync Properties + /// + public class PropertySystemSync : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemSync(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Sync.Comments -- PKEY_Sync_Comments + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 13 + /// + public ShellProperty Comments + { + get + { + var key = SystemProperties.System.Sync.Comments; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.ConflictDescription -- PKEY_Sync_ConflictDescription + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CE50C159-2FB8-41FD-BE68-D3E042E274BC}, 4 + /// + public ShellProperty ConflictDescription + { + get + { + var key = SystemProperties.System.Sync.ConflictDescription; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.ConflictFirstLocation -- PKEY_Sync_ConflictFirstLocation + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CE50C159-2FB8-41FD-BE68-D3E042E274BC}, 6 + /// + public ShellProperty ConflictFirstLocation + { + get + { + var key = SystemProperties.System.Sync.ConflictFirstLocation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.ConflictSecondLocation -- PKEY_Sync_ConflictSecondLocation + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CE50C159-2FB8-41FD-BE68-D3E042E274BC}, 7 + /// + public ShellProperty ConflictSecondLocation + { + get + { + var key = SystemProperties.System.Sync.ConflictSecondLocation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.HandlerCollectionID -- PKEY_Sync_HandlerCollectionID + /// Description: + /// Type: Guid -- VT_CLSID + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 2 + /// + public ShellProperty HandlerCollectionID + { + get + { + var key = SystemProperties.System.Sync.HandlerCollectionID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.HandlerID -- PKEY_Sync_HandlerID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 3 + /// + public ShellProperty HandlerID + { + get + { + var key = SystemProperties.System.Sync.HandlerID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.HandlerName -- PKEY_Sync_HandlerName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CE50C159-2FB8-41FD-BE68-D3E042E274BC}, 2 + /// + public ShellProperty HandlerName + { + get + { + var key = SystemProperties.System.Sync.HandlerName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.HandlerType -- PKEY_Sync_HandlerType + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 8 + /// + public ShellProperty HandlerType + { + get + { + var key = SystemProperties.System.Sync.HandlerType; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.HandlerTypeLabel -- PKEY_Sync_HandlerTypeLabel + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 9 + /// + public ShellProperty HandlerTypeLabel + { + get + { + var key = SystemProperties.System.Sync.HandlerTypeLabel; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.ItemID -- PKEY_Sync_ItemID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 6 + /// + public ShellProperty ItemID + { + get + { + var key = SystemProperties.System.Sync.ItemID; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.ItemName -- PKEY_Sync_ItemName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CE50C159-2FB8-41FD-BE68-D3E042E274BC}, 3 + /// + public ShellProperty ItemName + { + get + { + var key = SystemProperties.System.Sync.ItemName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.ProgressPercentage -- PKEY_Sync_ProgressPercentage + /// Description: An integer value between 0 and 100 representing the percentage completed. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 23 + /// + public ShellProperty ProgressPercentage + { + get + { + var key = SystemProperties.System.Sync.ProgressPercentage; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.State -- PKEY_Sync_State + /// Description: Sync state. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 24 + /// + public ShellProperty State + { + get + { + var key = SystemProperties.System.Sync.State; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Sync.Status -- PKEY_Sync_Status + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 10 + /// + public ShellProperty Status + { + get + { + var key = SystemProperties.System.Sync.Status; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Task Properties + /// + public class PropertySystemTask : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemTask(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Task.BillingInformation -- PKEY_Task_BillingInformation + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D37D52C6-261C-4303-82B3-08B926AC6F12}, 100 + /// + public ShellProperty BillingInformation + { + get + { + var key = SystemProperties.System.Task.BillingInformation; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Task.CompletionStatus -- PKEY_Task_CompletionStatus + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {084D8A0A-E6D5-40DE-BF1F-C8820E7C877C}, 100 + /// + public ShellProperty CompletionStatus + { + get + { + var key = SystemProperties.System.Task.CompletionStatus; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Task.Owner -- PKEY_Task_Owner + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {08C7CC5F-60F2-4494-AD75-55E3E0B5ADD0}, 100 + /// + public ShellProperty Owner + { + get + { + var key = SystemProperties.System.Task.Owner; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Video Properties + /// + public class PropertySystemVideo : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemVideo(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Video.Compression -- PKEY_Video_Compression + /// Description: Indicates the level of compression for the video stream. "Compression". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 10 (PIDVSI_COMPRESSION) + /// + public ShellProperty Compression + { + get + { + var key = SystemProperties.System.Video.Compression; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.Director -- PKEY_Video_Director + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 20 (PIDMSI_DIRECTOR) + /// + public ShellProperty Director + { + get + { + var key = SystemProperties.System.Video.Director; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.EncodingBitrate -- PKEY_Video_EncodingBitrate + /// Description: Indicates the data rate in "bits per second" for the video stream. "DataRate". + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 8 (PIDVSI_DATA_RATE) + /// + public ShellProperty EncodingBitrate + { + get + { + var key = SystemProperties.System.Video.EncodingBitrate; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.FourCC -- PKEY_Video_FourCC + /// Description: Indicates the 4CC for the video stream. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 44 + /// + public ShellProperty FourCC + { + get + { + var key = SystemProperties.System.Video.FourCC; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.FrameHeight -- PKEY_Video_FrameHeight + /// Description: Indicates the frame height for the video stream. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 4 + /// + public ShellProperty FrameHeight + { + get + { + var key = SystemProperties.System.Video.FrameHeight; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.FrameRate -- PKEY_Video_FrameRate + /// Description: Indicates the frame rate in "frames per millisecond" for the video stream. "FrameRate". + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 6 (PIDVSI_FRAME_RATE) + /// + public ShellProperty FrameRate + { + get + { + var key = SystemProperties.System.Video.FrameRate; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.FrameWidth -- PKEY_Video_FrameWidth + /// Description: Indicates the frame width for the video stream. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 3 + /// + public ShellProperty FrameWidth + { + get + { + var key = SystemProperties.System.Video.FrameWidth; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.HorizontalAspectRatio -- PKEY_Video_HorizontalAspectRatio + /// Description: Indicates the horizontal portion of the aspect ratio. The X portion of XX:YY, + ///like 16:9. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 42 + /// + public ShellProperty HorizontalAspectRatio + { + get + { + var key = SystemProperties.System.Video.HorizontalAspectRatio; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.SampleSize -- PKEY_Video_SampleSize + /// Description: Indicates the sample size in bits for the video stream. "SampleSize". + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 9 (PIDVSI_SAMPLE_SIZE) + /// + public ShellProperty SampleSize + { + get + { + var key = SystemProperties.System.Video.SampleSize; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.StreamName -- PKEY_Video_StreamName + /// Description: Indicates the name for the video stream. "StreamName". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 2 (PIDVSI_STREAM_NAME) + /// + public ShellProperty StreamName + { + get + { + var key = SystemProperties.System.Video.StreamName; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.StreamNumber -- PKEY_Video_StreamNumber + /// Description: "Stream Number". + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 11 (PIDVSI_STREAM_NUMBER) + /// + public ShellProperty StreamNumber + { + get + { + var key = SystemProperties.System.Video.StreamNumber; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.TotalBitrate -- PKEY_Video_TotalBitrate + /// Description: Indicates the total data rate in "bits per second" for all video and audio streams. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 43 (PIDVSI_TOTAL_BITRATE) + /// + public ShellProperty TotalBitrate + { + get + { + var key = SystemProperties.System.Video.TotalBitrate; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.TranscodedForSync -- PKEY_Video_TranscodedForSync + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 46 + /// + public ShellProperty TranscodedForSync + { + get + { + var key = SystemProperties.System.Video.TranscodedForSync; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Video.VerticalAspectRatio -- PKEY_Video_VerticalAspectRatio + /// Description: Indicates the vertical portion of the aspect ratio. The Y portion of + ///XX:YY, like 16:9. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 45 + /// + public ShellProperty VerticalAspectRatio + { + get + { + var key = SystemProperties.System.Video.VerticalAspectRatio; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + + /// + /// System.Volume Properties + /// + public class PropertySystemVolume : PropertyStoreItems + { + + + private readonly ShellObject shellObjectParent; + private readonly Hashtable hashtable = new Hashtable(); + + internal PropertySystemVolume(ShellObject parent) => shellObjectParent = parent; + + /// + /// Name: System.Volume.FileSystem -- PKEY_Volume_FileSystem + /// Description: Indicates the filesystem of the volume. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Volume) {9B174B35-40FF-11D2-A27E-00C04FC30871}, 4 (PID_VOLUME_FILESYSTEM) (Filesystem Volume Properties) + /// + public ShellProperty FileSystem + { + get + { + var key = SystemProperties.System.Volume.FileSystem; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Volume.IsMappedDrive -- PKEY_Volume_IsMappedDrive + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {149C0B69-2C2D-48FC-808F-D318D78C4636}, 2 + /// + public ShellProperty IsMappedDrive + { + get + { + var key = SystemProperties.System.Volume.IsMappedDrive; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + /// + /// Name: System.Volume.IsRoot -- PKEY_Volume_IsRoot + /// Description: + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: (FMTID_Volume) {9B174B35-40FF-11D2-A27E-00C04FC30871}, 10 (Filesystem Volume Properties) + /// + public ShellProperty IsRoot + { + get + { + var key = SystemProperties.System.Volume.IsRoot; + + if (!hashtable.ContainsKey(key)) + { + hashtable.Add(key, shellObjectParent.Properties.CreateTypedProperty(key)); + } + + return hashtable[key] as ShellProperty; + } + } + + } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/PropertySystem/SystemProperties.cs b/VG Music Studio - WinForms/API/Shell/PropertySystem/SystemProperties.cs new file mode 100644 index 0000000..7893849 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/PropertySystem/SystemProperties.cs @@ -0,0 +1,12302 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem +{ + + + /// + /// Provides easy access to all the system properties (property keys and their descriptions) + /// + public static class SystemProperties + { + + /// + /// Returns the property description for a given property key. + /// + /// Property key of the property whose description is required. + /// Property Description for a given property key + public static ShellPropertyDescription GetPropertyDescription(PropertyKey propertyKey) => ShellPropertyDescriptionsCache.Cache.GetPropertyDescription(propertyKey); + + + /// + /// Gets the property description for a given property's canonical name. + /// + /// Canonical name of the property whose description is required. + /// Property Description for a given property key + public static ShellPropertyDescription GetPropertyDescription(string canonicalName) + { + + var result = PropertySystemNativeMethods.PSGetPropertyKeyFromName(canonicalName, out var propKey); + + if (!CoreErrorHelper.Succeeded(result)) + { + throw new ArgumentException(LocalizedMessages.ShellInvalidCanonicalName, Marshal.GetExceptionForHR(result)); + } + return ShellPropertyDescriptionsCache.Cache.GetPropertyDescription(propKey); + } + + /// + /// System Properties + /// + public static class System + { + + + #region Properties + + /// + /// Name: System.AcquisitionID -- PKEY_AcquisitionID + /// Description: Hash to determine acquisition session. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {65A98875-3C80-40AB-ABBC-EFDAF77DBEE2}, 100 + /// + public static PropertyKey AcquisitionID + { + get + { + var key = new PropertyKey(new Guid("{65A98875-3C80-40AB-ABBC-EFDAF77DBEE2}"), 100); + return key; + } + } + + /// + /// Name: System.ApplicationName -- PKEY_ApplicationName + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 18 (PIDSI_APPNAME) + /// + public static PropertyKey ApplicationName + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 18); + + return key; + } + } + + /// + /// Name: System.Author -- PKEY_Author + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 4 (PIDSI_AUTHOR) + /// + public static PropertyKey Author + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 4); + + return key; + } + } + + /// + /// Name: System.Capacity -- PKEY_Capacity + /// Description: The amount of total space in bytes. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_Volume) {9B174B35-40FF-11D2-A27E-00C04FC30871}, 3 (PID_VOLUME_CAPACITY) (Filesystem Volume Properties) + /// + public static PropertyKey Capacity + { + get + { + var key = new PropertyKey(new Guid("{9B174B35-40FF-11D2-A27E-00C04FC30871}"), 3); + + return key; + } + } + + /// + /// Name: System.Category -- PKEY_Category + /// Description: Legacy code treats this as VT_LPSTR. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 2 (PIDDSI_CATEGORY) + /// + public static PropertyKey Category + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 2); + + return key; + } + } + + /// + /// Name: System.Comment -- PKEY_Comment + /// Description: Comments. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 6 (PIDSI_COMMENTS) + /// + public static PropertyKey Comment + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 6); + + return key; + } + } + + /// + /// Name: System.Company -- PKEY_Company + /// Description: The company or publisher. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 15 (PIDDSI_COMPANY) + /// + public static PropertyKey Company + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 15); + + return key; + } + } + + /// + /// Name: System.ComputerName -- PKEY_ComputerName + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 5 (PID_COMPUTERNAME) + /// + public static PropertyKey ComputerName + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 5); + + return key; + } + } + + /// + /// Name: System.ContainedItems -- PKEY_ContainedItems + /// Description: The list of type of items, this item contains. For example, this item contains urls, attachments etc. + ///This is represented as a vector array of GUIDs where each GUID represents certain type. + /// + /// Type: Multivalue Guid -- VT_VECTOR | VT_CLSID (For variants: VT_ARRAY | VT_CLSID) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 29 + /// + public static PropertyKey ContainedItems + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 29); + + return key; + } + } + + /// + /// Name: System.ContentStatus -- PKEY_ContentStatus + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 27 + /// + public static PropertyKey ContentStatus + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 27); + + return key; + } + } + + /// + /// Name: System.ContentType -- PKEY_ContentType + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 26 + /// + public static PropertyKey ContentType + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 26); + + return key; + } + } + + /// + /// Name: System.Copyright -- PKEY_Copyright + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 11 (PIDMSI_COPYRIGHT) + /// + public static PropertyKey Copyright + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 11); + + return key; + } + } + + /// + /// Name: System.DateAccessed -- PKEY_DateAccessed + /// Description: The time of the last access to the item. The Indexing Service friendly name is 'access'. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 16 (PID_STG_ACCESSTIME) + /// + public static PropertyKey DateAccessed + { + get + { + var key = new PropertyKey(new Guid("{B725F130-47EF-101A-A5F1-02608C9EEBAC}"), 16); + + return key; + } + } + + /// + /// Name: System.DateAcquired -- PKEY_DateAcquired + /// Description: The time the file entered the system via acquisition. This is not the same as System.DateImported. + ///Examples are when pictures are acquired from a camera, or when music is purchased online. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {2CBAA8F5-D81F-47CA-B17A-F8D822300131}, 100 + /// + public static PropertyKey DateAcquired + { + get + { + var key = new PropertyKey(new Guid("{2CBAA8F5-D81F-47CA-B17A-F8D822300131}"), 100); + + return key; + } + } + + /// + /// Name: System.DateArchived -- PKEY_DateArchived + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {43F8D7B7-A444-4F87-9383-52271C9B915C}, 100 + /// + public static PropertyKey DateArchived + { + get + { + var key = new PropertyKey(new Guid("{43F8D7B7-A444-4F87-9383-52271C9B915C}"), 100); + + return key; + } + } + + /// + /// Name: System.DateCompleted -- PKEY_DateCompleted + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {72FAB781-ACDA-43E5-B155-B2434F85E678}, 100 + /// + public static PropertyKey DateCompleted + { + get + { + var key = new PropertyKey(new Guid("{72FAB781-ACDA-43E5-B155-B2434F85E678}"), 100); + + return key; + } + } + + /// + /// Name: System.DateCreated -- PKEY_DateCreated + /// Description: The date and time the item was created. The Indexing Service friendly name is 'create'. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 15 (PID_STG_CREATETIME) + /// + public static PropertyKey DateCreated + { + get + { + var key = new PropertyKey(new Guid("{B725F130-47EF-101A-A5F1-02608C9EEBAC}"), 15); + + return key; + } + } + + /// + /// Name: System.DateImported -- PKEY_DateImported + /// Description: The time the file is imported into a separate database. This is not the same as System.DateAcquired. (Eg, 2003:05:22 13:55:04) + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 18258 + /// + public static PropertyKey DateImported + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 18258); + + return key; + } + } + + /// + /// Name: System.DateModified -- PKEY_DateModified + /// Description: The date and time of the last write to the item. The Indexing Service friendly name is 'write'. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 14 (PID_STG_WRITETIME) + /// + public static PropertyKey DateModified + { + get + { + var key = new PropertyKey(new Guid("{B725F130-47EF-101A-A5F1-02608C9EEBAC}"), 14); + + return key; + } + } + + /// + /// Name: System.DescriptionID -- PKEY_DescriptionID + /// Description: The contents of a SHDESCRIPTIONID structure as a buffer of bytes. + /// + /// Type: Buffer -- VT_VECTOR | VT_UI1 (For variants: VT_ARRAY | VT_UI1) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 2 (PID_DESCRIPTIONID) + /// + public static PropertyKey DescriptionID + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 2); + + return key; + } + } + + /// + /// Name: System.DueDate -- PKEY_DueDate + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {3F8472B5-E0AF-4DB2-8071-C53FE76AE7CE}, 100 + /// + public static PropertyKey DueDate + { + get + { + var key = new PropertyKey(new Guid("{3F8472B5-E0AF-4DB2-8071-C53FE76AE7CE}"), 100); + + return key; + } + } + + /// + /// Name: System.EndDate -- PKEY_EndDate + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {C75FAA05-96FD-49E7-9CB4-9F601082D553}, 100 + /// + public static PropertyKey EndDate + { + get + { + var key = new PropertyKey(new Guid("{C75FAA05-96FD-49E7-9CB4-9F601082D553}"), 100); + + return key; + } + } + + /// + /// Name: System.FileAllocationSize -- PKEY_FileAllocationSize + /// Description: + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 18 (PID_STG_ALLOCSIZE) + /// + public static PropertyKey FileAllocationSize + { + get + { + var key = new PropertyKey(new Guid("{B725F130-47EF-101A-A5F1-02608C9EEBAC}"), 18); + + return key; + } + } + + /// + /// Name: System.FileAttributes -- PKEY_FileAttributes + /// Description: This is the WIN32_FIND_DATA dwFileAttributes for the file-based item. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 13 (PID_STG_ATTRIBUTES) + /// + public static PropertyKey FileAttributes + { + get + { + var key = new PropertyKey(new Guid("{B725F130-47EF-101A-A5F1-02608C9EEBAC}"), 13); + + return key; + } + } + + /// + /// Name: System.FileCount -- PKEY_FileCount + /// Description: + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 12 + /// + public static PropertyKey FileCount + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 12); + + return key; + } + } + + /// + /// Name: System.FileDescription -- PKEY_FileDescription + /// Description: This is a user-friendly description of the file. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSFMTID_VERSION) {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 3 (PIDVSI_FileDescription) + /// + public static PropertyKey FileDescription + { + get + { + var key = new PropertyKey(new Guid("{0CEF7D53-FA64-11D1-A203-0000F81FEDEE}"), 3); + + return key; + } + } + + /// + /// Name: System.FileExtension -- PKEY_FileExtension + /// Description: This is the file extension of the file based item, including the leading period. + /// + ///If System.FileName is VT_EMPTY, then this property should be too. Otherwise, it should be derived + ///appropriately by the data source from System.FileName. If System.FileName does not have a file + ///extension, this value should be VT_EMPTY. + /// + ///To obtain the type of any item (including an item that is not a file), use System.ItemType. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" ".txt" + /// "\\server\share\mydir\goodnews.doc" ".doc" + /// "\\server\share\numbers.xls" ".xls" + /// "\\server\share\folder" VT_EMPTY + /// "c:\foo\MyFolder" VT_EMPTY + /// [desktop] VT_EMPTY + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E4F10A3C-49E6-405D-8288-A23BD4EEAA6C}, 100 + /// + public static PropertyKey FileExtension + { + get + { + var key = new PropertyKey(new Guid("{E4F10A3C-49E6-405D-8288-A23BD4EEAA6C}"), 100); + + return key; + } + } + + /// + /// Name: System.FileFRN -- PKEY_FileFRN + /// Description: This is the unique file ID, also known as the File Reference Number. For a given file, this is the same value + ///as is found in the structure variable FILE_ID_BOTH_DIR_INFO.FileId, via GetFileInformationByHandleEx(). + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 21 (PID_STG_FRN) + /// + public static PropertyKey FileFRN + { + get + { + var key = new PropertyKey(new Guid("{B725F130-47EF-101A-A5F1-02608C9EEBAC}"), 21); + + return key; + } + } + + /// + /// Name: System.FileName -- PKEY_FileName + /// Description: This is the file name (including extension) of the file. + /// + ///It is possible that the item might not exist on a filesystem (ie, it may not be opened + ///using CreateFile). Nonetheless, if the item is represented as a file from the logical sense + ///(and its name follows standard Win32 file-naming syntax), then the data source should emit this property. + /// + ///If an item is not a file, then the value for this property is VT_EMPTY. See + ///System.ItemNameDisplay. + /// + ///This has the same value as System.ParsingName for items that are provided by the Shell's file folder. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "hello.txt" + /// "\\server\share\mydir\goodnews.doc" "goodnews.doc" + /// "\\server\share\numbers.xls" "numbers.xls" + /// "c:\foo\MyFolder" "MyFolder" + /// (email message) VT_EMPTY + /// (song on portable device) "song.wma" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {41CF5AE0-F75A-4806-BD87-59C7D9248EB9}, 100 + /// + public static PropertyKey FileName + { + get + { + var key = new PropertyKey(new Guid("{41CF5AE0-F75A-4806-BD87-59C7D9248EB9}"), 100); + + return key; + } + } + + /// + /// Name: System.FileOwner -- PKEY_FileOwner + /// Description: This is the owner of the file, according to the file system. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Misc) {9B174B34-40FF-11D2-A27E-00C04FC30871}, 4 (PID_MISC_OWNER) + /// + public static PropertyKey FileOwner + { + get + { + var key = new PropertyKey(new Guid("{9B174B34-40FF-11D2-A27E-00C04FC30871}"), 4); + + return key; + } + } + + /// + /// Name: System.FileVersion -- PKEY_FileVersion + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSFMTID_VERSION) {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 4 (PIDVSI_FileVersion) + /// + public static PropertyKey FileVersion + { + get + { + var key = new PropertyKey(new Guid("{0CEF7D53-FA64-11D1-A203-0000F81FEDEE}"), 4); + + return key; + } + } + + /// + /// Name: System.FindData -- PKEY_FindData + /// Description: WIN32_FIND_DATAW in buffer of bytes. + /// + /// Type: Buffer -- VT_VECTOR | VT_UI1 (For variants: VT_ARRAY | VT_UI1) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 0 (PID_FINDDATA) + /// + public static PropertyKey FindData + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 0); + + return key; + } + } + + /// + /// Name: System.FlagColor -- PKEY_FlagColor + /// Description: + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {67DF94DE-0CA7-4D6F-B792-053A3E4F03CF}, 100 + /// + public static PropertyKey FlagColor + { + get + { + var key = new PropertyKey(new Guid("{67DF94DE-0CA7-4D6F-B792-053A3E4F03CF}"), 100); + + return key; + } + } + + /// + /// Name: System.FlagColorText -- PKEY_FlagColorText + /// Description: This is the user-friendly form of System.FlagColor. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {45EAE747-8E2A-40AE-8CBF-CA52ABA6152A}, 100 + /// + public static PropertyKey FlagColorText + { + get + { + var key = new PropertyKey(new Guid("{45EAE747-8E2A-40AE-8CBF-CA52ABA6152A}"), 100); + + return key; + } + } + + /// + /// Name: System.FlagStatus -- PKEY_FlagStatus + /// Description: Status of Flag. Values: (0=none 1=white 2=Red). cdoPR_FLAG_STATUS + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 12 + /// + public static PropertyKey FlagStatus + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 12); + + return key; + } + } + + /// + /// Name: System.FlagStatusText -- PKEY_FlagStatusText + /// Description: This is the user-friendly form of System.FlagStatus. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DC54FD2E-189D-4871-AA01-08C2F57A4ABC}, 100 + /// + public static PropertyKey FlagStatusText + { + get + { + var key = new PropertyKey(new Guid("{DC54FD2E-189D-4871-AA01-08C2F57A4ABC}"), 100); + + return key; + } + } + + /// + /// Name: System.FreeSpace -- PKEY_FreeSpace + /// Description: The amount of free space in bytes. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_Volume) {9B174B35-40FF-11D2-A27E-00C04FC30871}, 2 (PID_VOLUME_FREE) (Filesystem Volume Properties) + /// + public static PropertyKey FreeSpace + { + get + { + var key = new PropertyKey(new Guid("{9B174B35-40FF-11D2-A27E-00C04FC30871}"), 2); + + return key; + } + } + + /// + /// Name: System.FullText -- PKEY_FullText + /// Description: This PKEY is used to specify search terms that should be applied as broadly as possible, + ///across all valid properties for the data source(s) being searched. It should not be + ///emitted from a data source. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {1E3EE840-BC2B-476C-8237-2ACD1A839B22}, 6 + /// + public static PropertyKey FullText + { + get + { + var key = new PropertyKey(new Guid("{1E3EE840-BC2B-476C-8237-2ACD1A839B22}"), 6); + + return key; + } + } + + /// + /// Name: System.Identity -- PKEY_Identity + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A26F4AFC-7346-4299-BE47-EB1AE613139F}, 100 + /// + public static PropertyKey IdentityProperty + { + get + { + var key = new PropertyKey(new Guid("{A26F4AFC-7346-4299-BE47-EB1AE613139F}"), 100); + + return key; + } + } + + /// + /// Name: System.ImageParsingName -- PKEY_ImageParsingName + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D7750EE0-C6A4-48EC-B53E-B87B52E6D073}, 100 + /// + public static PropertyKey ImageParsingName + { + get + { + var key = new PropertyKey(new Guid("{D7750EE0-C6A4-48EC-B53E-B87B52E6D073}"), 100); + + return key; + } + } + + /// + /// Name: System.Importance -- PKEY_Importance + /// Description: + /// Type: Int32 -- VT_I4 + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 11 + /// + public static PropertyKey Importance + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 11); + + return key; + } + } + + /// + /// Name: System.ImportanceText -- PKEY_ImportanceText + /// Description: This is the user-friendly form of System.Importance. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A3B29791-7713-4E1D-BB40-17DB85F01831}, 100 + /// + public static PropertyKey ImportanceText + { + get + { + var key = new PropertyKey(new Guid("{A3B29791-7713-4E1D-BB40-17DB85F01831}"), 100); + + return key; + } + } + + /// + /// Name: System.InfoTipText -- PKEY_InfoTipText + /// Description: The text (with formatted property values) to show in the infotip. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 17 + /// + public static PropertyKey InfoTipText + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 17); + + return key; + } + } + + /// + /// Name: System.InternalName -- PKEY_InternalName + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSFMTID_VERSION) {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 5 (PIDVSI_InternalName) + /// + public static PropertyKey InternalName + { + get + { + var key = new PropertyKey(new Guid("{0CEF7D53-FA64-11D1-A203-0000F81FEDEE}"), 5); + + return key; + } + } + + /// + /// Name: System.IsAttachment -- PKEY_IsAttachment + /// Description: Identifies if this item is an attachment. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {F23F425C-71A1-4FA8-922F-678EA4A60408}, 100 + /// + public static PropertyKey IsAttachment + { + get + { + var key = new PropertyKey(new Guid("{F23F425C-71A1-4FA8-922F-678EA4A60408}"), 100); + + return key; + } + } + + /// + /// Name: System.IsDefaultNonOwnerSaveLocation -- PKEY_IsDefaultNonOwnerSaveLocation + /// Description: Identifies the default save location for a library for non-owners of the library + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}, 5 + /// + public static PropertyKey IsDefaultNonOwnerSaveLocation + { + get + { + var key = new PropertyKey(new Guid("{5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}"), 5); + + return key; + } + } + + /// + /// Name: System.IsDefaultSaveLocation -- PKEY_IsDefaultSaveLocation + /// Description: Identifies the default save location for a library for the owner of the library + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}, 3 + /// + public static PropertyKey IsDefaultSaveLocation + { + get + { + var key = new PropertyKey(new Guid("{5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}"), 3); + + return key; + } + } + + /// + /// Name: System.IsDeleted -- PKEY_IsDeleted + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {5CDA5FC8-33EE-4FF3-9094-AE7BD8868C4D}, 100 + /// + public static PropertyKey IsDeleted + { + get + { + var key = new PropertyKey(new Guid("{5CDA5FC8-33EE-4FF3-9094-AE7BD8868C4D}"), 100); + + return key; + } + } + + /// + /// Name: System.IsEncrypted -- PKEY_IsEncrypted + /// Description: Is the item encrypted? + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {90E5E14E-648B-4826-B2AA-ACAF790E3513}, 10 + /// + public static PropertyKey IsEncrypted + { + get + { + var key = new PropertyKey(new Guid("{90E5E14E-648B-4826-B2AA-ACAF790E3513}"), 10); + + return key; + } + } + + /// + /// Name: System.IsFlagged -- PKEY_IsFlagged + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {5DA84765-E3FF-4278-86B0-A27967FBDD03}, 100 + /// + public static PropertyKey IsFlagged + { + get + { + var key = new PropertyKey(new Guid("{5DA84765-E3FF-4278-86B0-A27967FBDD03}"), 100); + + return key; + } + } + + /// + /// Name: System.IsFlaggedComplete -- PKEY_IsFlaggedComplete + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {A6F360D2-55F9-48DE-B909-620E090A647C}, 100 + /// + public static PropertyKey IsFlaggedComplete + { + get + { + var key = new PropertyKey(new Guid("{A6F360D2-55F9-48DE-B909-620E090A647C}"), 100); + + return key; + } + } + + /// + /// Name: System.IsIncomplete -- PKEY_IsIncomplete + /// Description: Identifies if the message was not completely received for some error condition. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {346C8BD1-2E6A-4C45-89A4-61B78E8E700F}, 100 + /// + public static PropertyKey IsIncomplete + { + get + { + var key = new PropertyKey(new Guid("{346C8BD1-2E6A-4C45-89A4-61B78E8E700F}"), 100); + + return key; + } + } + + /// + /// Name: System.IsLocationSupported -- PKEY_IsLocationSupported + /// Description: A bool value to know if a location is supported (locally indexable, or remotely indexed). + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}, 8 + /// + public static PropertyKey IsLocationSupported + { + get + { + var key = new PropertyKey(new Guid("{5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}"), 8); + + return key; + } + } + + /// + /// Name: System.IsPinnedToNameSpaceTree -- PKEY_IsPinnedToNameSpaceTree + /// Description: A bool value to know if a shell folder is pinned to the navigation pane + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}, 2 + /// + public static PropertyKey IsPinnedToNamespaceTree + { + get + { + var key = new PropertyKey(new Guid("{5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}"), 2); + + return key; + } + } + + /// + /// Name: System.IsRead -- PKEY_IsRead + /// Description: Has the item been read? + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 10 + /// + public static PropertyKey IsRead + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 10); + + return key; + } + } + + /// + /// Name: System.IsSearchOnlyItem -- PKEY_IsSearchOnlyItem + /// Description: Identifies if a location or a library is search only + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}, 4 + /// + public static PropertyKey IsSearchOnlyItem + { + get + { + var key = new PropertyKey(new Guid("{5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}"), 4); + + return key; + } + } + + /// + /// Name: System.IsSendToTarget -- PKEY_IsSendToTarget + /// Description: Provided by certain shell folders. Return TRUE if the folder is a valid Send To target. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 33 + /// + public static PropertyKey IsSendToTarget + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 33); + + return key; + } + } + + /// + /// Name: System.IsShared -- PKEY_IsShared + /// Description: Is this item shared? This only checks for ACLs that are not inherited. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {EF884C5B-2BFE-41BB-AAE5-76EEDF4F9902}, 100 + /// + public static PropertyKey IsShared + { + get + { + var key = new PropertyKey(new Guid("{EF884C5B-2BFE-41BB-AAE5-76EEDF4F9902}"), 100); + + return key; + } + } + + /// + /// Name: System.ItemAuthors -- PKEY_ItemAuthors + /// Description: This is the generic list of authors associated with an item. + /// + ///For example, the artist name for a track is the item author. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D0A04F0A-462A-48A4-BB2F-3706E88DBD7D}, 100 + /// + public static PropertyKey ItemAuthors + { + get + { + var key = new PropertyKey(new Guid("{D0A04F0A-462A-48A4-BB2F-3706E88DBD7D}"), 100); + + return key; + } + } + + /// + /// Name: System.ItemClassType -- PKEY_ItemClassType + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {048658AD-2DB8-41A4-BBB6-AC1EF1207EB1}, 100 + /// + public static PropertyKey ItemClassType + { + get + { + var key = new PropertyKey(new Guid("{048658AD-2DB8-41A4-BBB6-AC1EF1207EB1}"), 100); + + return key; + } + } + + /// + /// Name: System.ItemDate -- PKEY_ItemDate + /// Description: This is the main date for an item. The date of interest. + /// + ///For example, for photos this maps to System.Photo.DateTaken. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {F7DB74B4-4287-4103-AFBA-F1B13DCD75CF}, 100 + /// + public static PropertyKey ItemDate + { + get + { + var key = new PropertyKey(new Guid("{F7DB74B4-4287-4103-AFBA-F1B13DCD75CF}"), 100); + + return key; + } + } + + /// + /// Name: System.ItemFolderNameDisplay -- PKEY_ItemFolderNameDisplay + /// Description: This is the user-friendly display name of the parent folder of an item. + /// + ///If System.ItemFolderPathDisplay is VT_EMPTY, then this property should be too. Otherwise, it + ///should be derived appropriately by the data source from System.ItemFolderPathDisplay. + /// + ///If the folder is a file folder, the value will be localized if a localized name is available. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "bar" + /// "\\server\share\mydir\goodnews.doc" "mydir" + /// "\\server\share\numbers.xls" "share" + /// "c:\foo\MyFolder" "foo" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "Inbox" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 2 (PID_STG_DIRECTORY) + /// + public static PropertyKey ItemFolderNameDisplay + { + get + { + var key = new PropertyKey(new Guid("{B725F130-47EF-101A-A5F1-02608C9EEBAC}"), 2); + + return key; + } + } + + /// + /// Name: System.ItemFolderPathDisplay -- PKEY_ItemFolderPathDisplay + /// Description: This is the user-friendly display path of the parent folder of an item. + /// + ///If System.ItemPathDisplay is VT_EMPTY, then this property should be too. Otherwise, it should + ///be derived appropriately by the data source from System.ItemPathDisplay. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "c:\foo\bar" + /// "\\server\share\mydir\goodnews.doc" "\\server\share\mydir" + /// "\\server\share\numbers.xls" "\\server\share" + /// "c:\foo\MyFolder" "c:\foo" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "/Mailbox Account/Inbox" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 6 + /// + public static PropertyKey ItemFolderPathDisplay + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 6); + + return key; + } + } + + /// + /// Name: System.ItemFolderPathDisplayNarrow -- PKEY_ItemFolderPathDisplayNarrow + /// Description: This is the user-friendly display path of the parent folder of an item. The format of the string + ///should be tailored such that the folder name comes first, to optimize for a narrow viewing column. + /// + ///If the folder is a file folder, the value includes localized names if they are present. + /// + ///If System.ItemFolderPathDisplay is VT_EMPTY, then this property should be too. Otherwise, it should + ///be derived appropriately by the data source from System.ItemFolderPathDisplay. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "bar (c:\foo)" + /// "\\server\share\mydir\goodnews.doc" "mydir (\\server\share)" + /// "\\server\share\numbers.xls" "share (\\server)" + /// "c:\foo\MyFolder" "foo (c:\)" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "Inbox (/Mailbox Account)" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DABD30ED-0043-4789-A7F8-D013A4736622}, 100 + /// + public static PropertyKey ItemFolderPathDisplayNarrow + { + get + { + var key = new PropertyKey(new Guid("{DABD30ED-0043-4789-A7F8-D013A4736622}"), 100); + + return key; + } + } + + /// + /// Name: System.ItemName -- PKEY_ItemName + /// Description: This is the base-name of the System.ItemNameDisplay. + /// + ///If the item is a file this property + ///includes the extension in all cases, and will be localized if a localized name is available. + /// + ///If the item is a message, then the value of this property does not include the forwarding or + ///reply prefixes (see System.ItemNamePrefix). + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6B8DA074-3B5C-43BC-886F-0A2CDCE00B6F}, 100 + /// + public static PropertyKey ItemName + { + get + { + var key = new PropertyKey(new Guid("{6B8DA074-3B5C-43BC-886F-0A2CDCE00B6F}"), 100); + + return key; + } + } + + /// + /// Name: System.ItemNameDisplay -- PKEY_ItemNameDisplay + /// Description: This is the display name in "most complete" form. This is the best effort unique representation + ///of the name of an item that makes sense for end users to read. It is the concatentation of + ///System.ItemNamePrefix and System.ItemName. + /// + ///If the item is a file this property + ///includes the extension in all cases, and will be localized if a localized name is available. + /// + ///There are acceptable cases when System.FileName is not VT_EMPTY, yet the value of this property + ///is completely different. Email messages are a key example. If the item is an email message, + ///the item name is likely the subject. In that case, the value must be the concatenation of the + ///System.ItemNamePrefix and System.ItemName. Since the value of System.ItemNamePrefix excludes + ///any trailing whitespace, the concatenation must include a whitespace when generating System.ItemNameDisplay. + /// + ///Note that this property is not guaranteed to be unique, but the idea is to promote the most likely + ///candidate that can be unique and also makes sense for end users. For example, for documents, you + ///might think about using System.Title as the System.ItemNameDisplay, but in practice the title of + ///the documents may not be useful or unique enough to be of value as the sole System.ItemNameDisplay. + ///Instead, providing the value of System.FileName as the value of System.ItemNameDisplay is a better + ///candidate. In Windows Mail, the emails are stored in the file system as .eml files and the + ///System.FileName for those files are not human-friendly as they contain GUIDs. In this example, + ///promoting System.Subject as System.ItemNameDisplay makes more sense. + /// + ///Compatibility notes: + /// + ///Shell folder implementations on Vista: use PKEY_ItemNameDisplay for the name column when + ///you want Explorer to call ISF::GetDisplayNameOf(SHGDN_NORMAL) to get the value of the name. Use + ///another PKEY (like PKEY_ItemName) when you want Explorer to call either the folder's property store or + ///ISF2::GetDetailsEx in order to get the value of the name. + /// + ///Shell folder implementations on XP: the first column needs to be the name column, and Explorer + ///will call ISF::GetDisplayNameOf to get the value of the name. The PKEY/SCID does not matter. + /// + ///Example values: + /// + /// File: "hello.txt" + /// Message: "Re: Let's talk about Tom's argyle socks!" + /// Device folder: "song.wma" + /// Folder: "Documents" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 10 (PID_STG_NAME) + /// + public static PropertyKey ItemNameDisplay + { + get + { + var key = new PropertyKey(new Guid("{B725F130-47EF-101A-A5F1-02608C9EEBAC}"), 10); + + return key; + } + } + + /// + /// Name: System.ItemNamePrefix -- PKEY_ItemNamePrefix + /// Description: This is the prefix of an item, used for email messages. + ///where the subject begins with "Re:" which is the prefix. + /// + ///If the item is a file, then the value of this property is VT_EMPTY. + /// + ///If the item is a message, then the value of this property is the forwarding or reply + ///prefixes (including delimiting colon, but no whitespace), or VT_EMPTY if there is no prefix. + /// + ///Example values: + /// + ///System.ItemNamePrefix System.ItemName System.ItemNameDisplay + ///--------------------- ------------------- ---------------------- + ///VT_EMPTY "Great day" "Great day" + ///"Re:" "Great day" "Re: Great day" + ///"Fwd: " "Monthly budget" "Fwd: Monthly budget" + ///VT_EMPTY "accounts.xls" "accounts.xls" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D7313FF1-A77A-401C-8C99-3DBDD68ADD36}, 100 + /// + public static PropertyKey ItemNamePrefix + { + get + { + var key = new PropertyKey(new Guid("{D7313FF1-A77A-401C-8C99-3DBDD68ADD36}"), 100); + + return key; + } + } + + /// + /// Name: System.ItemParticipants -- PKEY_ItemParticipants + /// Description: This is the generic list of people associated with an item and who contributed + ///to the item. + /// + ///For example, this is the combination of people in the To list, Cc list and + ///sender of an email message. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D4D0AA16-9948-41A4-AA85-D97FF9646993}, 100 + /// + public static PropertyKey ItemParticipants + { + get + { + var key = new PropertyKey(new Guid("{D4D0AA16-9948-41A4-AA85-D97FF9646993}"), 100); + + return key; + } + } + + /// + /// Name: System.ItemPathDisplay -- PKEY_ItemPathDisplay + /// Description: This is the user-friendly display path to the item. + /// + ///If the item is a file or folder this property + ///includes the extension in all cases, and will be localized if a localized name is available. + /// + ///For other items,this is the user-friendly equivalent, assuming the item exists in hierarchical storage. + /// + ///Unlike System.ItemUrl, this property value does not include the URL scheme. + /// + ///To parse an item path, use System.ItemUrl or System.ParsingPath. To reference shell + ///namespace items using shell APIs, use System.ParsingPath. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "c:\foo\bar\hello.txt" + /// "\\server\share\mydir\goodnews.doc" "\\server\share\mydir\goodnews.doc" + /// "\\server\share\numbers.xls" "\\server\share\numbers.xls" + /// "c:\foo\MyFolder" "c:\foo\MyFolder" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "/Mailbox Account/Inbox/'Re: Hello!'" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 7 + /// + public static PropertyKey ItemPathDisplay + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 7); + + return key; + } + } + + /// + /// Name: System.ItemPathDisplayNarrow -- PKEY_ItemPathDisplayNarrow + /// Description: This is the user-friendly display path to the item. The format of the string should be + ///tailored such that the name comes first, to optimize for a narrow viewing column. + /// + ///If the item is a file, the value excludes the file extension, and includes localized names if they are present. + ///If the item is a message, the value includes the System.ItemNamePrefix. + /// + ///To parse an item path, use System.ItemUrl or System.ParsingPath. + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "hello (c:\foo\bar)" + /// "\\server\share\mydir\goodnews.doc" "goodnews (\\server\share\mydir)" + /// "\\server\share\folder" "folder (\\server\share)" + /// "c:\foo\MyFolder" "MyFolder (c:\foo)" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "Re: Hello! (/Mailbox Account/Inbox)" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 8 + /// + public static PropertyKey ItemPathDisplayNarrow + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 8); + + return key; + } + } + + /// + /// Name: System.ItemType -- PKEY_ItemType + /// Description: This is the canonical type of the item and is intended to be programmatically + ///parsed. + /// + ///If there is no canonical type, the value is VT_EMPTY. + /// + ///If the item is a file (ie, System.FileName is not VT_EMPTY), the value is the same as + ///System.FileExtension. + /// + ///Use System.ItemTypeText when you want to display the type to end users in a view. (If + /// the item is a file, passing the System.ItemType value to PSFormatForDisplay will + /// result in the same value as System.ItemTypeText.) + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" ".txt" + /// "\\server\share\mydir\goodnews.doc" ".doc" + /// "\\server\share\folder" "Directory" + /// "c:\foo\MyFolder" "Directory" + /// [desktop] "Folder" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "MAPI/IPM.Message" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 11 + /// + public static PropertyKey ItemType + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 11); + + return key; + } + } + + /// + /// Name: System.ItemTypeText -- PKEY_ItemTypeText + /// Description: This is the user friendly type name of the item. This is not intended to be + ///programmatically parsed. + /// + ///If System.ItemType is VT_EMPTY, the value of this property is also VT_EMPTY. + /// + ///If the item is a file, the value of this property is the same as if you passed the + ///file's System.ItemType value to PSFormatForDisplay. + /// + ///This property should not be confused with System.Kind, where System.Kind is a high-level + ///user friendly kind name. For example, for a document, System.Kind = "Document" and + ///System.Item.Type = ".doc" and System.Item.TypeText = "Microsoft Word Document" + /// + ///Example values: + /// + /// If the path is... The property value is... + /// ----------------- ------------------------ + /// "c:\foo\bar\hello.txt" "Text File" + /// "\\server\share\mydir\goodnews.doc" "Microsoft Word Document" + /// "\\server\share\folder" "File Folder" + /// "c:\foo\MyFolder" "File Folder" + /// "/Mailbox Account/Inbox/'Re: Hello!'" "Outlook E-Mail Message" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 4 (PID_STG_STORAGETYPE) + /// + public static PropertyKey ItemTypeText + { + get + { + var key = new PropertyKey(new Guid("{B725F130-47EF-101A-A5F1-02608C9EEBAC}"), 4); + + return key; + } + } + + /// + /// Name: System.ItemUrl -- PKEY_ItemUrl + /// Description: This always represents a well formed URL that points to the item. + /// + ///To reference shell namespace items using shell APIs, use System.ParsingPath. + /// + ///Example values: + /// + /// Files: "file:///c:/foo/bar/hello.txt" + /// "csc://{GUID}/..." + /// Messages: "mapi://..." + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Query) {49691C90-7E17-101A-A91C-08002B2ECDA9}, 9 (DISPID_QUERY_VIRTUALPATH) + /// + public static PropertyKey ItemUrl + { + get + { + var key = new PropertyKey(new Guid("{49691C90-7E17-101A-A91C-08002B2ECDA9}"), 9); + + return key; + } + } + + /// + /// Name: System.Keywords -- PKEY_Keywords + /// Description: The keywords for the item. Also referred to as tags. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 5 (PIDSI_KEYWORDS) + /// + public static PropertyKey Keywords + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 5); + + return key; + } + } + + /// + /// Name: System.Kind -- PKEY_Kind + /// Description: System.Kind is used to map extensions to various .Search folders. + ///Extensions are mapped to Kinds at HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\KindMap + ///The list of kinds is not extensible. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {1E3EE840-BC2B-476C-8237-2ACD1A839B22}, 3 + /// + public static PropertyKey Kind + { + get + { + var key = new PropertyKey(new Guid("{1E3EE840-BC2B-476C-8237-2ACD1A839B22}"), 3); + + return key; + } + } + + /// + /// Name: System.KindText -- PKEY_KindText + /// Description: This is the user-friendly form of System.Kind. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F04BEF95-C585-4197-A2B7-DF46FDC9EE6D}, 100 + /// + public static PropertyKey KindText + { + get + { + var key = new PropertyKey(new Guid("{F04BEF95-C585-4197-A2B7-DF46FDC9EE6D}"), 100); + + return key; + } + } + + /// + /// Name: System.Language -- PKEY_Language + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 28 + /// + public static PropertyKey Language + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 28); + + return key; + } + } + + /// + /// Name: System.MileageInformation -- PKEY_MileageInformation + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FDF84370-031A-4ADD-9E91-0D775F1C6605}, 100 + /// + public static PropertyKey MileageInformation + { + get + { + var key = new PropertyKey(new Guid("{FDF84370-031A-4ADD-9E91-0D775F1C6605}"), 100); + + return key; + } + } + + /// + /// Name: System.MIMEType -- PKEY_MIMEType + /// Description: The MIME type. Eg, for EML files: 'message/rfc822'. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0B63E350-9CCC-11D0-BCDB-00805FCCCE04}, 5 + /// + public static PropertyKey MIMEType + { + get + { + var key = new PropertyKey(new Guid("{0B63E350-9CCC-11D0-BCDB-00805FCCCE04}"), 5); + + return key; + } + } + + /// + /// Name: System.NamespaceCLSID -- PKEY_NamespaceCLSID + /// Description: The CLSID of the name space extension for an item, the object that implements IShellFolder for this item + /// + /// Type: Guid -- VT_CLSID + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 6 + /// + public static PropertyKey NamespaceClsid + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 6); + + return key; + } + } + + /// + /// Name: System.Null -- PKEY_Null + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {00000000-0000-0000-0000-000000000000}, 0 + /// + public static PropertyKey Null + { + get + { + var key = new PropertyKey(new Guid("{00000000-0000-0000-0000-000000000000}"), 0); + + return key; + } + } + + /// + /// Name: System.OfflineAvailability -- PKEY_OfflineAvailability + /// Description: + /// Type: UInt32 -- VT_UI4 + /// FormatID: {A94688B6-7D9F-4570-A648-E3DFC0AB2B3F}, 100 + /// + public static PropertyKey OfflineAvailability + { + get + { + var key = new PropertyKey(new Guid("{A94688B6-7D9F-4570-A648-E3DFC0AB2B3F}"), 100); + + return key; + } + } + + /// + /// Name: System.OfflineStatus -- PKEY_OfflineStatus + /// Description: + /// Type: UInt32 -- VT_UI4 + /// FormatID: {6D24888F-4718-4BDA-AFED-EA0FB4386CD8}, 100 + /// + public static PropertyKey OfflineStatus + { + get + { + var key = new PropertyKey(new Guid("{6D24888F-4718-4BDA-AFED-EA0FB4386CD8}"), 100); + + return key; + } + } + + /// + /// Name: System.OriginalFileName -- PKEY_OriginalFileName + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSFMTID_VERSION) {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 6 + /// + public static PropertyKey OriginalFileName + { + get + { + var key = new PropertyKey(new Guid("{0CEF7D53-FA64-11D1-A203-0000F81FEDEE}"), 6); + + return key; + } + } + + /// + /// Name: System.OwnerSID -- PKEY_OwnerSID + /// Description: SID of the user that owns the library. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}, 6 + /// + public static PropertyKey OwnerSid + { + get + { + var key = new PropertyKey(new Guid("{5D76B67F-9B3D-44BB-B6AE-25DA4F638A67}"), 6); + + return key; + } + } + + /// + /// Name: System.ParentalRating -- PKEY_ParentalRating + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 21 (PIDMSI_PARENTAL_RATING) + /// + public static PropertyKey ParentalRating + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 21); + + return key; + } + } + + /// + /// Name: System.ParentalRatingReason -- PKEY_ParentalRatingReason + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {10984E0A-F9F2-4321-B7EF-BAF195AF4319}, 100 + /// + public static PropertyKey ParentalRatingReason + { + get + { + var key = new PropertyKey(new Guid("{10984E0A-F9F2-4321-B7EF-BAF195AF4319}"), 100); + + return key; + } + } + + /// + /// Name: System.ParentalRatingsOrganization -- PKEY_ParentalRatingsOrganization + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A7FE0840-1344-46F0-8D37-52ED712A4BF9}, 100 + /// + public static PropertyKey ParentalRatingsOrganization + { + get + { + var key = new PropertyKey(new Guid("{A7FE0840-1344-46F0-8D37-52ED712A4BF9}"), 100); + + return key; + } + } + + /// + /// Name: System.ParsingBindContext -- PKEY_ParsingBindContext + /// Description: used to get the IBindCtx for an item for parsing + /// + /// Type: Any -- VT_NULL Legacy code may treat this as VT_UNKNOWN. + /// FormatID: {DFB9A04D-362F-4CA3-B30B-0254B17B5B84}, 100 + /// + public static PropertyKey ParsingBindContext + { + get + { + var key = new PropertyKey(new Guid("{DFB9A04D-362F-4CA3-B30B-0254B17B5B84}"), 100); + + return key; + } + } + + /// + /// Name: System.ParsingName -- PKEY_ParsingName + /// Description: The shell namespace name of an item relative to a parent folder. This name may be passed to + ///IShellFolder::ParseDisplayName() of the parent shell folder. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 24 + /// + public static PropertyKey ParsingName + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 24); + + return key; + } + } + + /// + /// Name: System.ParsingPath -- PKEY_ParsingPath + /// Description: This is the shell namespace path to the item. This path may be passed to + ///SHParseDisplayName to parse the path to the correct shell folder. + /// + ///If the item is a file, the value is identical to System.ItemPathDisplay. + /// + ///If the item cannot be accessed through the shell namespace, this value is VT_EMPTY. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 30 + /// + public static PropertyKey ParsingPath + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 30); + + return key; + } + } + + /// + /// Name: System.PerceivedType -- PKEY_PerceivedType + /// Description: The perceived type of a shell item, based upon its canonical type. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 9 + /// + public static PropertyKey PerceivedType + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 9); + + return key; + } + } + + /// + /// Name: System.PercentFull -- PKEY_PercentFull + /// Description: The amount filled as a percentage, multiplied by 100 (ie, the valid range is 0 through 100). + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_Volume) {9B174B35-40FF-11D2-A27E-00C04FC30871}, 5 (Filesystem Volume Properties) + /// + public static PropertyKey PercentFull + { + get + { + var key = new PropertyKey(new Guid("{9B174B35-40FF-11D2-A27E-00C04FC30871}"), 5); + + return key; + } + } + + /// + /// Name: System.Priority -- PKEY_Priority + /// Description: + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {9C1FCF74-2D97-41BA-B4AE-CB2E3661A6E4}, 5 + /// + public static PropertyKey Priority + { + get + { + var key = new PropertyKey(new Guid("{9C1FCF74-2D97-41BA-B4AE-CB2E3661A6E4}"), 5); + + return key; + } + } + + /// + /// Name: System.PriorityText -- PKEY_PriorityText + /// Description: This is the user-friendly form of System.Priority. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D98BE98B-B86B-4095-BF52-9D23B2E0A752}, 100 + /// + public static PropertyKey PriorityText + { + get + { + var key = new PropertyKey(new Guid("{D98BE98B-B86B-4095-BF52-9D23B2E0A752}"), 100); + + return key; + } + } + + /// + /// Name: System.Project -- PKEY_Project + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {39A7F922-477C-48DE-8BC8-B28441E342E3}, 100 + /// + public static PropertyKey Project + { + get + { + var key = new PropertyKey(new Guid("{39A7F922-477C-48DE-8BC8-B28441E342E3}"), 100); + + return key; + } + } + + /// + /// Name: System.ProviderItemID -- PKEY_ProviderItemID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F21D9941-81F0-471A-ADEE-4E74B49217ED}, 100 + /// + public static PropertyKey ProviderItemID + { + get + { + var key = new PropertyKey(new Guid("{F21D9941-81F0-471A-ADEE-4E74B49217ED}"), 100); + + return key; + } + } + + /// + /// Name: System.Rating -- PKEY_Rating + /// Description: Indicates the users preference rating of an item on a scale of 1-99 (1-12 = One Star, + ///13-37 = Two Stars, 38-62 = Three Stars, 63-87 = Four Stars, 88-99 = Five Stars). + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 9 (PIDMSI_RATING) + /// + public static PropertyKey Rating + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 9); + + return key; + } + } + + /// + /// Name: System.RatingText -- PKEY_RatingText + /// Description: This is the user-friendly form of System.Rating. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {90197CA7-FD8F-4E8C-9DA3-B57E1E609295}, 100 + /// + public static PropertyKey RatingText + { + get + { + var key = new PropertyKey(new Guid("{90197CA7-FD8F-4E8C-9DA3-B57E1E609295}"), 100); + + return key; + } + } + + /// + /// Name: System.Sensitivity -- PKEY_Sensitivity + /// Description: + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {F8D3F6AC-4874-42CB-BE59-AB454B30716A}, 100 + /// + public static PropertyKey Sensitivity + { + get + { + var key = new PropertyKey(new Guid("{F8D3F6AC-4874-42CB-BE59-AB454B30716A}"), 100); + + return key; + } + } + + /// + /// Name: System.SensitivityText -- PKEY_SensitivityText + /// Description: This is the user-friendly form of System.Sensitivity. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D0C7F054-3F72-4725-8527-129A577CB269}, 100 + /// + public static PropertyKey SensitivityText + { + get + { + var key = new PropertyKey(new Guid("{D0C7F054-3F72-4725-8527-129A577CB269}"), 100); + + return key; + } + } + + /// + /// Name: System.SFGAOFlags -- PKEY_SFGAOFlags + /// Description: IShellFolder::GetAttributesOf flags, with SFGAO_PKEYSFGAOMASK attributes masked out. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 25 + /// + [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags")] + public static PropertyKey SFGAOFlags + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 25); + + return key; + } + } + + /// + /// Name: System.SharedWith -- PKEY_SharedWith + /// Description: Who is the item shared with? + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {EF884C5B-2BFE-41BB-AAE5-76EEDF4F9902}, 200 + /// + public static PropertyKey SharedWith + { + get + { + var key = new PropertyKey(new Guid("{EF884C5B-2BFE-41BB-AAE5-76EEDF4F9902}"), 200); + + return key; + } + } + + /// + /// Name: System.ShareUserRating -- PKEY_ShareUserRating + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 12 (PIDMSI_SHARE_USER_RATING) + /// + public static PropertyKey ShareUserRating + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 12); + + return key; + } + } + + /// + /// Name: System.SharingStatus -- PKEY_SharingStatus + /// Description: What is the item's sharing status (not shared, shared, everyone (homegroup or everyone), or private)? + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {EF884C5B-2BFE-41BB-AAE5-76EEDF4F9902}, 300 + /// + public static PropertyKey SharingStatus + { + get + { + var key = new PropertyKey(new Guid("{EF884C5B-2BFE-41BB-AAE5-76EEDF4F9902}"), 300); + + return key; + } + } + + /// + /// Name: System.SimpleRating -- PKEY_SimpleRating + /// Description: Indicates the users preference rating of an item on a scale of 0-5 (0=unrated, 1=One Star, 2=Two Stars, 3=Three Stars, + ///4=Four Stars, 5=Five Stars) + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {A09F084E-AD41-489F-8076-AA5BE3082BCA}, 100 + /// + public static PropertyKey SimpleRating + { + get + { + var key = new PropertyKey(new Guid("{A09F084E-AD41-489F-8076-AA5BE3082BCA}"), 100); + + return key; + } + } + + /// + /// Name: System.Size -- PKEY_Size + /// Description: + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 12 (PID_STG_SIZE) + /// + public static PropertyKey Size + { + get + { + var key = new PropertyKey(new Guid("{B725F130-47EF-101A-A5F1-02608C9EEBAC}"), 12); + + return key; + } + } + + /// + /// Name: System.SoftwareUsed -- PKEY_SoftwareUsed + /// Description: PropertyTagSoftwareUsed + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 305 + /// + public static PropertyKey SoftwareUsed + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 305); + + return key; + } + } + + /// + /// Name: System.SourceItem -- PKEY_SourceItem + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {668CDFA5-7A1B-4323-AE4B-E527393A1D81}, 100 + /// + public static PropertyKey SourceItem + { + get + { + var key = new PropertyKey(new Guid("{668CDFA5-7A1B-4323-AE4B-E527393A1D81}"), 100); + + return key; + } + } + + /// + /// Name: System.StartDate -- PKEY_StartDate + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {48FD6EC8-8A12-4CDF-A03E-4EC5A511EDDE}, 100 + /// + public static PropertyKey StartDate + { + get + { + var key = new PropertyKey(new Guid("{48FD6EC8-8A12-4CDF-A03E-4EC5A511EDDE}"), 100); + + return key; + } + } + + /// + /// Name: System.Status -- PKEY_Status + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_IntSite) {000214A1-0000-0000-C000-000000000046}, 9 + /// + public static PropertyKey Status + { + get + { + var key = new PropertyKey(new Guid("{000214A1-0000-0000-C000-000000000046}"), 9); + + return key; + } + } + + /// + /// Name: System.Subject -- PKEY_Subject + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 3 (PIDSI_SUBJECT) + /// + public static PropertyKey Subject + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 3); + + return key; + } + } + + /// + /// Name: System.Thumbnail -- PKEY_Thumbnail + /// Description: A data that represents the thumbnail in VT_CF format. + /// + /// Type: Clipboard -- VT_CF + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 17 (PIDSI_THUMBNAIL) + /// + public static PropertyKey Thumbnail + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 17); + + return key; + } + } + + /// + /// Name: System.ThumbnailCacheId -- PKEY_ThumbnailCacheId + /// Description: Unique value that can be used as a key to cache thumbnails. The value changes when the name, volume, or data modified + ///of an item changes. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: {446D16B1-8DAD-4870-A748-402EA43D788C}, 100 + /// + public static PropertyKey ThumbnailCacheId + { + get + { + var key = new PropertyKey(new Guid("{446D16B1-8DAD-4870-A748-402EA43D788C}"), 100); + + return key; + } + } + + /// + /// Name: System.ThumbnailStream -- PKEY_ThumbnailStream + /// Description: Data that represents the thumbnail in VT_STREAM format that GDI+/WindowsCodecs supports (jpg, png, etc). + /// + /// Type: Stream -- VT_STREAM + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 27 + /// + public static PropertyKey ThumbnailStream + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 27); + + return key; + } + } + + /// + /// Name: System.Title -- PKEY_Title + /// Description: Title of item. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 2 (PIDSI_TITLE) + /// + public static PropertyKey Title + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 2); + + return key; + } + } + + /// + /// Name: System.TotalFileSize -- PKEY_TotalFileSize + /// Description: + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_ShellDetails) {28636AA6-953D-11D2-B5D6-00C04FD918D0}, 14 + /// + public static PropertyKey TotalFileSize + { + get + { + var key = new PropertyKey(new Guid("{28636AA6-953D-11D2-B5D6-00C04FD918D0}"), 14); + + return key; + } + } + + /// + /// Name: System.Trademarks -- PKEY_Trademarks + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSFMTID_VERSION) {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 9 (PIDVSI_Trademarks) + /// + public static PropertyKey Trademarks + { + get + { + var key = new PropertyKey(new Guid("{0CEF7D53-FA64-11D1-A203-0000F81FEDEE}"), 9); + + return key; + } + } + #endregion + + + #region sub-classes + + /// + /// AppUserModel Properties + /// + public static class AppUserModel + { + + + #region Properties + + /// + /// Name: System.AppUserModel.ExcludeFromShowInNewInstall -- PKEY_AppUserModel_ExcludeFromShowInNewInstall + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 8 + /// + public static PropertyKey ExcludeFromShowInNewInstall + { + get + { + var key = new PropertyKey(new Guid("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}"), 8); + + return key; + } + } + + /// + /// Name: System.AppUserModel.ID -- PKEY_AppUserModel_ID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 5 + /// + public static PropertyKey ID + { + get + { + var key = new PropertyKey(new Guid("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}"), 5); + + return key; + } + } + + /// + /// Name: System.AppUserModel.IsDestListSeparator -- PKEY_AppUserModel_IsDestListSeparator + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 6 + /// + public static PropertyKey IsDestinationListSeparator + { + get + { + var key = new PropertyKey(new Guid("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}"), 6); + + return key; + } + } + + /// + /// Name: System.AppUserModel.PreventPinning -- PKEY_AppUserModel_PreventPinning + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 9 + /// + public static PropertyKey PreventPinning + { + get + { + var key = new PropertyKey(new Guid("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}"), 9); + + return key; + } + } + + /// + /// Name: System.AppUserModel.RelaunchCommand -- PKEY_AppUserModel_RelaunchCommand + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 2 + /// + public static PropertyKey RelaunchCommand + { + get + { + var key = new PropertyKey(new Guid("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}"), 2); + + return key; + } + } + + /// + /// Name: System.AppUserModel.RelaunchDisplayNameResource -- PKEY_AppUserModel_RelaunchDisplayNameResource + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 4 + /// + public static PropertyKey RelaunchDisplayNameResource + { + get + { + var key = new PropertyKey(new Guid("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}"), 4); + + return key; + } + } + + /// + /// Name: System.AppUserModel.RelaunchIconResource -- PKEY_AppUserModel_RelaunchIconResource + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 3 + /// + public static PropertyKey RelaunchIconResource + { + get + { + var key = new PropertyKey(new Guid("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}"), 3); + + return key; + } + } + #endregion + + } + + /// + /// Audio Properties + /// + public static class Audio + { + + + #region Properties + + /// + /// Name: System.Audio.ChannelCount -- PKEY_Audio_ChannelCount + /// Description: Indicates the channel count for the audio file. Values: 1 (mono), 2 (stereo). + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 7 (PIDASI_CHANNEL_COUNT) + /// + public static PropertyKey ChannelCount + { + get + { + var key = new PropertyKey(new Guid("{64440490-4C8B-11D1-8B70-080036B11A03}"), 7); + + return key; + } + } + + /// + /// Name: System.Audio.Compression -- PKEY_Audio_Compression + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 10 (PIDASI_COMPRESSION) + /// + public static PropertyKey Compression + { + get + { + var key = new PropertyKey(new Guid("{64440490-4C8B-11D1-8B70-080036B11A03}"), 10); + + return key; + } + } + + /// + /// Name: System.Audio.EncodingBitrate -- PKEY_Audio_EncodingBitrate + /// Description: Indicates the average data rate in Hz for the audio file in "bits per second". + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 4 (PIDASI_AVG_DATA_RATE) + /// + public static PropertyKey EncodingBitrate + { + get + { + var key = new PropertyKey(new Guid("{64440490-4C8B-11D1-8B70-080036B11A03}"), 4); + + return key; + } + } + + /// + /// Name: System.Audio.Format -- PKEY_Audio_Format + /// Description: Indicates the format of the audio file. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) Legacy code may treat this as VT_BSTR. + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 2 (PIDASI_FORMAT) + /// + public static PropertyKey Format + { + get + { + var key = new PropertyKey(new Guid("{64440490-4C8B-11D1-8B70-080036B11A03}"), 2); + + return key; + } + } + + /// + /// Name: System.Audio.IsVariableBitRate -- PKEY_Audio_IsVariableBitRate + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {E6822FEE-8C17-4D62-823C-8E9CFCBD1D5C}, 100 + /// + public static PropertyKey IsVariableBitrate + { + get + { + var key = new PropertyKey(new Guid("{E6822FEE-8C17-4D62-823C-8E9CFCBD1D5C}"), 100); + + return key; + } + } + + /// + /// Name: System.Audio.PeakValue -- PKEY_Audio_PeakValue + /// Description: + /// Type: UInt32 -- VT_UI4 + /// FormatID: {2579E5D0-1116-4084-BD9A-9B4F7CB4DF5E}, 100 + /// + public static PropertyKey PeakValue + { + get + { + var key = new PropertyKey(new Guid("{2579E5D0-1116-4084-BD9A-9B4F7CB4DF5E}"), 100); + + return key; + } + } + + /// + /// Name: System.Audio.SampleRate -- PKEY_Audio_SampleRate + /// Description: Indicates the audio sample rate for the audio file in "samples per second". + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 5 (PIDASI_SAMPLE_RATE) + /// + public static PropertyKey SampleRate + { + get + { + var key = new PropertyKey(new Guid("{64440490-4C8B-11D1-8B70-080036B11A03}"), 5); + + return key; + } + } + + /// + /// Name: System.Audio.SampleSize -- PKEY_Audio_SampleSize + /// Description: Indicates the audio sample size for the audio file in "bits per sample". + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 6 (PIDASI_SAMPLE_SIZE) + /// + public static PropertyKey SampleSize + { + get + { + var key = new PropertyKey(new Guid("{64440490-4C8B-11D1-8B70-080036B11A03}"), 6); + + return key; + } + } + + /// + /// Name: System.Audio.StreamName -- PKEY_Audio_StreamName + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 9 (PIDASI_STREAM_NAME) + /// + public static PropertyKey StreamName + { + get + { + var key = new PropertyKey(new Guid("{64440490-4C8B-11D1-8B70-080036B11A03}"), 9); + + return key; + } + } + + /// + /// Name: System.Audio.StreamNumber -- PKEY_Audio_StreamNumber + /// Description: + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 8 (PIDASI_STREAM_NUMBER) + /// + public static PropertyKey StreamNumber + { + get + { + var key = new PropertyKey(new Guid("{64440490-4C8B-11D1-8B70-080036B11A03}"), 8); + + return key; + } + } + #endregion + + + + } + + /// + /// Calendar Properties + /// + public static class Calendar + { + + + #region Properties + + /// + /// Name: System.Calendar.Duration -- PKEY_Calendar_Duration + /// Description: The duration as specified in a string. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {293CA35A-09AA-4DD2-B180-1FE245728A52}, 100 + /// + public static PropertyKey Duration + { + get + { + var key = new PropertyKey(new Guid("{293CA35A-09AA-4DD2-B180-1FE245728A52}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.IsOnline -- PKEY_Calendar_IsOnline + /// Description: Identifies if the event is an online event. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {BFEE9149-E3E2-49A7-A862-C05988145CEC}, 100 + /// + public static PropertyKey IsOnline + { + get + { + var key = new PropertyKey(new Guid("{BFEE9149-E3E2-49A7-A862-C05988145CEC}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.IsRecurring -- PKEY_Calendar_IsRecurring + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {315B9C8D-80A9-4EF9-AE16-8E746DA51D70}, 100 + /// + public static PropertyKey IsRecurring + { + get + { + var key = new PropertyKey(new Guid("{315B9C8D-80A9-4EF9-AE16-8E746DA51D70}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.Location -- PKEY_Calendar_Location + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F6272D18-CECC-40B1-B26A-3911717AA7BD}, 100 + /// + public static PropertyKey Location + { + get + { + var key = new PropertyKey(new Guid("{F6272D18-CECC-40B1-B26A-3911717AA7BD}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.OptionalAttendeeAddresses -- PKEY_Calendar_OptionalAttendeeAddresses + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D55BAE5A-3892-417A-A649-C6AC5AAAEAB3}, 100 + /// + public static PropertyKey OptionalAttendeeAddresses + { + get + { + var key = new PropertyKey(new Guid("{D55BAE5A-3892-417A-A649-C6AC5AAAEAB3}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.OptionalAttendeeNames -- PKEY_Calendar_OptionalAttendeeNames + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {09429607-582D-437F-84C3-DE93A2B24C3C}, 100 + /// + public static PropertyKey OptionalAttendeeNames + { + get + { + var key = new PropertyKey(new Guid("{09429607-582D-437F-84C3-DE93A2B24C3C}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.OrganizerAddress -- PKEY_Calendar_OrganizerAddress + /// Description: Address of the organizer organizing the event. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {744C8242-4DF5-456C-AB9E-014EFB9021E3}, 100 + /// + public static PropertyKey OrganizerAddress + { + get + { + var key = new PropertyKey(new Guid("{744C8242-4DF5-456C-AB9E-014EFB9021E3}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.OrganizerName -- PKEY_Calendar_OrganizerName + /// Description: Name of the organizer organizing the event. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {AAA660F9-9865-458E-B484-01BC7FE3973E}, 100 + /// + public static PropertyKey OrganizerName + { + get + { + var key = new PropertyKey(new Guid("{AAA660F9-9865-458E-B484-01BC7FE3973E}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.ReminderTime -- PKEY_Calendar_ReminderTime + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {72FC5BA4-24F9-4011-9F3F-ADD27AFAD818}, 100 + /// + public static PropertyKey ReminderTime + { + get + { + var key = new PropertyKey(new Guid("{72FC5BA4-24F9-4011-9F3F-ADD27AFAD818}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.RequiredAttendeeAddresses -- PKEY_Calendar_RequiredAttendeeAddresses + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {0BA7D6C3-568D-4159-AB91-781A91FB71E5}, 100 + /// + public static PropertyKey RequiredAttendeeAddresses + { + get + { + var key = new PropertyKey(new Guid("{0BA7D6C3-568D-4159-AB91-781A91FB71E5}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.RequiredAttendeeNames -- PKEY_Calendar_RequiredAttendeeNames + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {B33AF30B-F552-4584-936C-CB93E5CDA29F}, 100 + /// + public static PropertyKey RequiredAttendeeNames + { + get + { + var key = new PropertyKey(new Guid("{B33AF30B-F552-4584-936C-CB93E5CDA29F}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.Resources -- PKEY_Calendar_Resources + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {00F58A38-C54B-4C40-8696-97235980EAE1}, 100 + /// + public static PropertyKey Resources + { + get + { + var key = new PropertyKey(new Guid("{00F58A38-C54B-4C40-8696-97235980EAE1}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.ResponseStatus -- PKEY_Calendar_ResponseStatus + /// Description: This property stores the status of the user responses to meetings in her calendar. + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {188C1F91-3C40-4132-9EC5-D8B03B72A8A2}, 100 + /// + public static PropertyKey ResponseStatus + { + get + { + var key = new PropertyKey(new Guid("{188C1F91-3C40-4132-9EC5-D8B03B72A8A2}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.ShowTimeAs -- PKEY_Calendar_ShowTimeAs + /// Description: + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {5BF396D4-5EB2-466F-BDE9-2FB3F2361D6E}, 100 + /// + public static PropertyKey ShowTimeAs + { + get + { + var key = new PropertyKey(new Guid("{5BF396D4-5EB2-466F-BDE9-2FB3F2361D6E}"), 100); + + return key; + } + } + + /// + /// Name: System.Calendar.ShowTimeAsText -- PKEY_Calendar_ShowTimeAsText + /// Description: This is the user-friendly form of System.Calendar.ShowTimeAs. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {53DA57CF-62C0-45C4-81DE-7610BCEFD7F5}, 100 + /// + public static PropertyKey ShowTimeAsText + { + get + { + var key = new PropertyKey(new Guid("{53DA57CF-62C0-45C4-81DE-7610BCEFD7F5}"), 100); + + return key; + } + } + #endregion + + + + } + + /// + /// Communication Properties + /// + public static class Communication + { + + + #region Properties + + /// + /// Name: System.Communication.AccountName -- PKEY_Communication_AccountName + /// Description: Account Name + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 9 + /// + public static PropertyKey AccountName + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 9); + + return key; + } + } + + /// + /// Name: System.Communication.DateItemExpires -- PKEY_Communication_DateItemExpires + /// Description: Date the item expires due to the retention policy. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {428040AC-A177-4C8A-9760-F6F761227F9A}, 100 + /// + public static PropertyKey DateItemExpires + { + get + { + var key = new PropertyKey(new Guid("{428040AC-A177-4C8A-9760-F6F761227F9A}"), 100); + + return key; + } + } + + /// + /// Name: System.Communication.FollowupIconIndex -- PKEY_Communication_FollowupIconIndex + /// Description: This is the icon index used on messages marked for followup. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {83A6347E-6FE4-4F40-BA9C-C4865240D1F4}, 100 + /// + public static PropertyKey FollowUpIconIndex + { + get + { + var key = new PropertyKey(new Guid("{83A6347E-6FE4-4F40-BA9C-C4865240D1F4}"), 100); + + return key; + } + } + + /// + /// Name: System.Communication.HeaderItem -- PKEY_Communication_HeaderItem + /// Description: This property will be true if the item is a header item which means the item hasn't been fully downloaded. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {C9C34F84-2241-4401-B607-BD20ED75AE7F}, 100 + /// + public static PropertyKey HeaderItem + { + get + { + var key = new PropertyKey(new Guid("{C9C34F84-2241-4401-B607-BD20ED75AE7F}"), 100); + + return key; + } + } + + /// + /// Name: System.Communication.PolicyTag -- PKEY_Communication_PolicyTag + /// Description: This a string used to identify the retention policy applied to the item. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {EC0B4191-AB0B-4C66-90B6-C6637CDEBBAB}, 100 + /// + public static PropertyKey PolicyTag + { + get + { + var key = new PropertyKey(new Guid("{EC0B4191-AB0B-4C66-90B6-C6637CDEBBAB}"), 100); + + return key; + } + } + + /// + /// Name: System.Communication.SecurityFlags -- PKEY_Communication_SecurityFlags + /// Description: Security flags associated with the item to know if the item is encrypted, signed or DRM enabled. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {8619A4B6-9F4D-4429-8C0F-B996CA59E335}, 100 + /// + public static PropertyKey SecurityFlags + { + get + { + var key = new PropertyKey(new Guid("{8619A4B6-9F4D-4429-8C0F-B996CA59E335}"), 100); + + return key; + } + } + + /// + /// Name: System.Communication.Suffix -- PKEY_Communication_Suffix + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {807B653A-9E91-43EF-8F97-11CE04EE20C5}, 100 + /// + public static PropertyKey Suffix + { + get + { + var key = new PropertyKey(new Guid("{807B653A-9E91-43EF-8F97-11CE04EE20C5}"), 100); + + return key; + } + } + + /// + /// Name: System.Communication.TaskStatus -- PKEY_Communication_TaskStatus + /// Description: + /// Type: UInt16 -- VT_UI2 + /// FormatID: {BE1A72C6-9A1D-46B7-AFE7-AFAF8CEF4999}, 100 + /// + public static PropertyKey TaskStatus + { + get + { + var key = new PropertyKey(new Guid("{BE1A72C6-9A1D-46B7-AFE7-AFAF8CEF4999}"), 100); + + return key; + } + } + + /// + /// Name: System.Communication.TaskStatusText -- PKEY_Communication_TaskStatusText + /// Description: This is the user-friendly form of System.Communication.TaskStatus. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A6744477-C237-475B-A075-54F34498292A}, 100 + /// + public static PropertyKey TaskStatusText + { + get + { + var key = new PropertyKey(new Guid("{A6744477-C237-475B-A075-54F34498292A}"), 100); + + return key; + } + } + #endregion + + + + } + + /// + /// Computer Properties + /// + public static class Computer + { + + + #region Properties + + /// + /// Name: System.Computer.DecoratedFreeSpace -- PKEY_Computer_DecoratedFreeSpace + /// Description: Free space and total space: "%s free of %s" + /// + /// Type: Multivalue UInt64 -- VT_VECTOR | VT_UI8 (For variants: VT_ARRAY | VT_UI8) + /// FormatID: (FMTID_Volume) {9B174B35-40FF-11D2-A27E-00C04FC30871}, 7 (Filesystem Volume Properties) + /// + public static PropertyKey DecoratedFreeSpace + { + get + { + var key = new PropertyKey(new Guid("{9B174B35-40FF-11D2-A27E-00C04FC30871}"), 7); + + return key; + } + } + #endregion + + + + } + + /// + /// Contact Properties + /// + public static class Contact + { + + + #region Properties + + /// + /// Name: System.Contact.Anniversary -- PKEY_Contact_Anniversary + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {9AD5BADB-CEA7-4470-A03D-B84E51B9949E}, 100 + /// + public static PropertyKey Anniversary + { + get + { + var key = new PropertyKey(new Guid("{9AD5BADB-CEA7-4470-A03D-B84E51B9949E}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.AssistantName -- PKEY_Contact_AssistantName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CD102C9C-5540-4A88-A6F6-64E4981C8CD1}, 100 + /// + public static PropertyKey AssistantName + { + get + { + var key = new PropertyKey(new Guid("{CD102C9C-5540-4A88-A6F6-64E4981C8CD1}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.AssistantTelephone -- PKEY_Contact_AssistantTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9A93244D-A7AD-4FF8-9B99-45EE4CC09AF6}, 100 + /// + public static PropertyKey AssistantTelephone + { + get + { + var key = new PropertyKey(new Guid("{9A93244D-A7AD-4FF8-9B99-45EE4CC09AF6}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.Birthday -- PKEY_Contact_Birthday + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 47 + /// + public static PropertyKey Birthday + { + get + { + var key = new PropertyKey(new Guid("{176DC63C-2688-4E89-8143-A347800F25E9}"), 47); + + return key; + } + } + + /// + /// Name: System.Contact.BusinessAddress -- PKEY_Contact_BusinessAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {730FB6DD-CF7C-426B-A03F-BD166CC9EE24}, 100 + /// + public static PropertyKey BusinessAddress + { + get + { + var key = new PropertyKey(new Guid("{730FB6DD-CF7C-426B-A03F-BD166CC9EE24}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.BusinessAddressCity -- PKEY_Contact_BusinessAddressCity + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {402B5934-EC5A-48C3-93E6-85E86A2D934E}, 100 + /// + public static PropertyKey BusinessAddressCity + { + get + { + var key = new PropertyKey(new Guid("{402B5934-EC5A-48C3-93E6-85E86A2D934E}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.BusinessAddressCountry -- PKEY_Contact_BusinessAddressCountry + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {B0B87314-FCF6-4FEB-8DFF-A50DA6AF561C}, 100 + /// + public static PropertyKey BusinessAddressCountry + { + get + { + var key = new PropertyKey(new Guid("{B0B87314-FCF6-4FEB-8DFF-A50DA6AF561C}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.BusinessAddressPostalCode -- PKEY_Contact_BusinessAddressPostalCode + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E1D4A09E-D758-4CD1-B6EC-34A8B5A73F80}, 100 + /// + public static PropertyKey BusinessAddressPostalCode + { + get + { + var key = new PropertyKey(new Guid("{E1D4A09E-D758-4CD1-B6EC-34A8B5A73F80}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.BusinessAddressPostOfficeBox -- PKEY_Contact_BusinessAddressPostOfficeBox + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {BC4E71CE-17F9-48D5-BEE9-021DF0EA5409}, 100 + /// + public static PropertyKey BusinessAddressPostOfficeBox + { + get + { + var key = new PropertyKey(new Guid("{BC4E71CE-17F9-48D5-BEE9-021DF0EA5409}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.BusinessAddressState -- PKEY_Contact_BusinessAddressState + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {446F787F-10C4-41CB-A6C4-4D0343551597}, 100 + /// + public static PropertyKey BusinessAddressState + { + get + { + var key = new PropertyKey(new Guid("{446F787F-10C4-41CB-A6C4-4D0343551597}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.BusinessAddressStreet -- PKEY_Contact_BusinessAddressStreet + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DDD1460F-C0BF-4553-8CE4-10433C908FB0}, 100 + /// + public static PropertyKey BusinessAddressStreet + { + get + { + var key = new PropertyKey(new Guid("{DDD1460F-C0BF-4553-8CE4-10433C908FB0}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.BusinessFaxNumber -- PKEY_Contact_BusinessFaxNumber + /// Description: Business fax number of the contact. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {91EFF6F3-2E27-42CA-933E-7C999FBE310B}, 100 + /// + public static PropertyKey BusinessFaxNumber + { + get + { + var key = new PropertyKey(new Guid("{91EFF6F3-2E27-42CA-933E-7C999FBE310B}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.BusinessHomePage -- PKEY_Contact_BusinessHomePage + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {56310920-2491-4919-99CE-EADB06FAFDB2}, 100 + /// + public static PropertyKey BusinessHomepage + { + get + { + var key = new PropertyKey(new Guid("{56310920-2491-4919-99CE-EADB06FAFDB2}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.BusinessTelephone -- PKEY_Contact_BusinessTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6A15E5A0-0A1E-4CD7-BB8C-D2F1B0C929BC}, 100 + /// + public static PropertyKey BusinessTelephone + { + get + { + var key = new PropertyKey(new Guid("{6A15E5A0-0A1E-4CD7-BB8C-D2F1B0C929BC}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.CallbackTelephone -- PKEY_Contact_CallbackTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {BF53D1C3-49E0-4F7F-8567-5A821D8AC542}, 100 + /// + public static PropertyKey CallbackTelephone + { + get + { + var key = new PropertyKey(new Guid("{BF53D1C3-49E0-4F7F-8567-5A821D8AC542}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.CarTelephone -- PKEY_Contact_CarTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {8FDC6DEA-B929-412B-BA90-397A257465FE}, 100 + /// + public static PropertyKey CarTelephone + { + get + { + var key = new PropertyKey(new Guid("{8FDC6DEA-B929-412B-BA90-397A257465FE}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.Children -- PKEY_Contact_Children + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D4729704-8EF1-43EF-9024-2BD381187FD5}, 100 + /// + public static PropertyKey Children + { + get + { + var key = new PropertyKey(new Guid("{D4729704-8EF1-43EF-9024-2BD381187FD5}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.CompanyMainTelephone -- PKEY_Contact_CompanyMainTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {8589E481-6040-473D-B171-7FA89C2708ED}, 100 + /// + public static PropertyKey CompanyMainTelephone + { + get + { + var key = new PropertyKey(new Guid("{8589E481-6040-473D-B171-7FA89C2708ED}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.Department -- PKEY_Contact_Department + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FC9F7306-FF8F-4D49-9FB6-3FFE5C0951EC}, 100 + /// + public static PropertyKey Department + { + get + { + var key = new PropertyKey(new Guid("{FC9F7306-FF8F-4D49-9FB6-3FFE5C0951EC}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.EmailAddress -- PKEY_Contact_EmailAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F8FA7FA3-D12B-4785-8A4E-691A94F7A3E7}, 100 + /// + public static PropertyKey EmailAddress + { + get + { + var key = new PropertyKey(new Guid("{F8FA7FA3-D12B-4785-8A4E-691A94F7A3E7}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.EmailAddress2 -- PKEY_Contact_EmailAddress2 + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {38965063-EDC8-4268-8491-B7723172CF29}, 100 + /// + public static PropertyKey EmailAddress2 + { + get + { + var key = new PropertyKey(new Guid("{38965063-EDC8-4268-8491-B7723172CF29}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.EmailAddress3 -- PKEY_Contact_EmailAddress3 + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {644D37B4-E1B3-4BAD-B099-7E7C04966ACA}, 100 + /// + public static PropertyKey EmailAddress3 + { + get + { + var key = new PropertyKey(new Guid("{644D37B4-E1B3-4BAD-B099-7E7C04966ACA}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.EmailAddresses -- PKEY_Contact_EmailAddresses + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {84D8F337-981D-44B3-9615-C7596DBA17E3}, 100 + /// + public static PropertyKey EmailAddresses + { + get + { + var key = new PropertyKey(new Guid("{84D8F337-981D-44B3-9615-C7596DBA17E3}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.EmailName -- PKEY_Contact_EmailName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CC6F4F24-6083-4BD4-8754-674D0DE87AB8}, 100 + /// + public static PropertyKey EmailName + { + get + { + var key = new PropertyKey(new Guid("{CC6F4F24-6083-4BD4-8754-674D0DE87AB8}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.FileAsName -- PKEY_Contact_FileAsName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F1A24AA7-9CA7-40F6-89EC-97DEF9FFE8DB}, 100 + /// + public static PropertyKey FileAsName + { + get + { + var key = new PropertyKey(new Guid("{F1A24AA7-9CA7-40F6-89EC-97DEF9FFE8DB}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.FirstName -- PKEY_Contact_FirstName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {14977844-6B49-4AAD-A714-A4513BF60460}, 100 + /// + public static PropertyKey FirstName + { + get + { + var key = new PropertyKey(new Guid("{14977844-6B49-4AAD-A714-A4513BF60460}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.FullName -- PKEY_Contact_FullName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {635E9051-50A5-4BA2-B9DB-4ED056C77296}, 100 + /// + public static PropertyKey FullName + { + get + { + var key = new PropertyKey(new Guid("{635E9051-50A5-4BA2-B9DB-4ED056C77296}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.Gender -- PKEY_Contact_Gender + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {3C8CEE58-D4F0-4CF9-B756-4E5D24447BCD}, 100 + /// + public static PropertyKey Gender + { + get + { + var key = new PropertyKey(new Guid("{3C8CEE58-D4F0-4CF9-B756-4E5D24447BCD}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.GenderValue -- PKEY_Contact_GenderValue + /// Description: + /// Type: UInt16 -- VT_UI2 + /// FormatID: {3C8CEE58-D4F0-4CF9-B756-4E5D24447BCD}, 101 + /// + public static PropertyKey GenderValue + { + get + { + var key = new PropertyKey(new Guid("{3C8CEE58-D4F0-4CF9-B756-4E5D24447BCD}"), 101); + + return key; + } + } + + /// + /// Name: System.Contact.Hobbies -- PKEY_Contact_Hobbies + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {5DC2253F-5E11-4ADF-9CFE-910DD01E3E70}, 100 + /// + public static PropertyKey Hobbies + { + get + { + var key = new PropertyKey(new Guid("{5DC2253F-5E11-4ADF-9CFE-910DD01E3E70}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.HomeAddress -- PKEY_Contact_HomeAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {98F98354-617A-46B8-8560-5B1B64BF1F89}, 100 + /// + public static PropertyKey HomeAddress + { + get + { + var key = new PropertyKey(new Guid("{98F98354-617A-46B8-8560-5B1B64BF1F89}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.HomeAddressCity -- PKEY_Contact_HomeAddressCity + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 65 + /// + public static PropertyKey HomeAddressCity + { + get + { + var key = new PropertyKey(new Guid("{176DC63C-2688-4E89-8143-A347800F25E9}"), 65); + + return key; + } + } + + /// + /// Name: System.Contact.HomeAddressCountry -- PKEY_Contact_HomeAddressCountry + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {08A65AA1-F4C9-43DD-9DDF-A33D8E7EAD85}, 100 + /// + public static PropertyKey HomeAddressCountry + { + get + { + var key = new PropertyKey(new Guid("{08A65AA1-F4C9-43DD-9DDF-A33D8E7EAD85}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.HomeAddressPostalCode -- PKEY_Contact_HomeAddressPostalCode + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {8AFCC170-8A46-4B53-9EEE-90BAE7151E62}, 100 + /// + public static PropertyKey HomeAddressPostalCode + { + get + { + var key = new PropertyKey(new Guid("{8AFCC170-8A46-4B53-9EEE-90BAE7151E62}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.HomeAddressPostOfficeBox -- PKEY_Contact_HomeAddressPostOfficeBox + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7B9F6399-0A3F-4B12-89BD-4ADC51C918AF}, 100 + /// + public static PropertyKey HomeAddressPostOfficeBox + { + get + { + var key = new PropertyKey(new Guid("{7B9F6399-0A3F-4B12-89BD-4ADC51C918AF}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.HomeAddressState -- PKEY_Contact_HomeAddressState + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C89A23D0-7D6D-4EB8-87D4-776A82D493E5}, 100 + /// + public static PropertyKey HomeAddressState + { + get + { + var key = new PropertyKey(new Guid("{C89A23D0-7D6D-4EB8-87D4-776A82D493E5}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.HomeAddressStreet -- PKEY_Contact_HomeAddressStreet + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0ADEF160-DB3F-4308-9A21-06237B16FA2A}, 100 + /// + public static PropertyKey HomeAddressStreet + { + get + { + var key = new PropertyKey(new Guid("{0ADEF160-DB3F-4308-9A21-06237B16FA2A}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.HomeFaxNumber -- PKEY_Contact_HomeFaxNumber + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {660E04D6-81AB-4977-A09F-82313113AB26}, 100 + /// + public static PropertyKey HomeFaxNumber + { + get + { + var key = new PropertyKey(new Guid("{660E04D6-81AB-4977-A09F-82313113AB26}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.HomeTelephone -- PKEY_Contact_HomeTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 20 + /// + public static PropertyKey HomeTelephone + { + get + { + var key = new PropertyKey(new Guid("{176DC63C-2688-4E89-8143-A347800F25E9}"), 20); + + return key; + } + } + + /// + /// Name: System.Contact.IMAddress -- PKEY_Contact_IMAddress + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D68DBD8A-3374-4B81-9972-3EC30682DB3D}, 100 + /// + public static PropertyKey IMAddress + { + get + { + var key = new PropertyKey(new Guid("{D68DBD8A-3374-4B81-9972-3EC30682DB3D}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.Initials -- PKEY_Contact_Initials + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F3D8F40D-50CB-44A2-9718-40CB9119495D}, 100 + /// + public static PropertyKey Initials + { + get + { + var key = new PropertyKey(new Guid("{F3D8F40D-50CB-44A2-9718-40CB9119495D}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.JobTitle -- PKEY_Contact_JobTitle + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 6 + /// + public static PropertyKey JobTitle + { + get + { + var key = new PropertyKey(new Guid("{176DC63C-2688-4E89-8143-A347800F25E9}"), 6); + + return key; + } + } + + /// + /// Name: System.Contact.Label -- PKEY_Contact_Label + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {97B0AD89-DF49-49CC-834E-660974FD755B}, 100 + /// + public static PropertyKey Label + { + get + { + var key = new PropertyKey(new Guid("{97B0AD89-DF49-49CC-834E-660974FD755B}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.LastName -- PKEY_Contact_LastName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {8F367200-C270-457C-B1D4-E07C5BCD90C7}, 100 + /// + public static PropertyKey LastName + { + get + { + var key = new PropertyKey(new Guid("{8F367200-C270-457C-B1D4-E07C5BCD90C7}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.MailingAddress -- PKEY_Contact_MailingAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C0AC206A-827E-4650-95AE-77E2BB74FCC9}, 100 + /// + public static PropertyKey MailingAddress + { + get + { + var key = new PropertyKey(new Guid("{C0AC206A-827E-4650-95AE-77E2BB74FCC9}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.MiddleName -- PKEY_Contact_MiddleName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 71 + /// + public static PropertyKey MiddleName + { + get + { + var key = new PropertyKey(new Guid("{176DC63C-2688-4E89-8143-A347800F25E9}"), 71); + + return key; + } + } + + /// + /// Name: System.Contact.MobileTelephone -- PKEY_Contact_MobileTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 35 + /// + public static PropertyKey MobileTelephone + { + get + { + var key = new PropertyKey(new Guid("{176DC63C-2688-4E89-8143-A347800F25E9}"), 35); + + return key; + } + } + + /// + /// Name: System.Contact.NickName -- PKEY_Contact_NickName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 74 + /// + public static PropertyKey Nickname + { + get + { + var key = new PropertyKey(new Guid("{176DC63C-2688-4E89-8143-A347800F25E9}"), 74); + + return key; + } + } + + /// + /// Name: System.Contact.OfficeLocation -- PKEY_Contact_OfficeLocation + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 7 + /// + public static PropertyKey OfficeLocation + { + get + { + var key = new PropertyKey(new Guid("{176DC63C-2688-4E89-8143-A347800F25E9}"), 7); + + return key; + } + } + + /// + /// Name: System.Contact.OtherAddress -- PKEY_Contact_OtherAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {508161FA-313B-43D5-83A1-C1ACCF68622C}, 100 + /// + public static PropertyKey OtherAddress + { + get + { + var key = new PropertyKey(new Guid("{508161FA-313B-43D5-83A1-C1ACCF68622C}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.OtherAddressCity -- PKEY_Contact_OtherAddressCity + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6E682923-7F7B-4F0C-A337-CFCA296687BF}, 100 + /// + public static PropertyKey OtherAddressCity + { + get + { + var key = new PropertyKey(new Guid("{6E682923-7F7B-4F0C-A337-CFCA296687BF}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.OtherAddressCountry -- PKEY_Contact_OtherAddressCountry + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {8F167568-0AAE-4322-8ED9-6055B7B0E398}, 100 + /// + public static PropertyKey OtherAddressCountry + { + get + { + var key = new PropertyKey(new Guid("{8F167568-0AAE-4322-8ED9-6055B7B0E398}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.OtherAddressPostalCode -- PKEY_Contact_OtherAddressPostalCode + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {95C656C1-2ABF-4148-9ED3-9EC602E3B7CD}, 100 + /// + public static PropertyKey OtherAddressPostalCode + { + get + { + var key = new PropertyKey(new Guid("{95C656C1-2ABF-4148-9ED3-9EC602E3B7CD}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.OtherAddressPostOfficeBox -- PKEY_Contact_OtherAddressPostOfficeBox + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {8B26EA41-058F-43F6-AECC-4035681CE977}, 100 + /// + public static PropertyKey OtherAddressPostOfficeBox + { + get + { + var key = new PropertyKey(new Guid("{8B26EA41-058F-43F6-AECC-4035681CE977}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.OtherAddressState -- PKEY_Contact_OtherAddressState + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {71B377D6-E570-425F-A170-809FAE73E54E}, 100 + /// + public static PropertyKey OtherAddressState + { + get + { + var key = new PropertyKey(new Guid("{71B377D6-E570-425F-A170-809FAE73E54E}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.OtherAddressStreet -- PKEY_Contact_OtherAddressStreet + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FF962609-B7D6-4999-862D-95180D529AEA}, 100 + /// + public static PropertyKey OtherAddressStreet + { + get + { + var key = new PropertyKey(new Guid("{FF962609-B7D6-4999-862D-95180D529AEA}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.PagerTelephone -- PKEY_Contact_PagerTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D6304E01-F8F5-4F45-8B15-D024A6296789}, 100 + /// + public static PropertyKey PagerTelephone + { + get + { + var key = new PropertyKey(new Guid("{D6304E01-F8F5-4F45-8B15-D024A6296789}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.PersonalTitle -- PKEY_Contact_PersonalTitle + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 69 + /// + public static PropertyKey PersonalTitle + { + get + { + var key = new PropertyKey(new Guid("{176DC63C-2688-4E89-8143-A347800F25E9}"), 69); + + return key; + } + } + + /// + /// Name: System.Contact.PrimaryAddressCity -- PKEY_Contact_PrimaryAddressCity + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C8EA94F0-A9E3-4969-A94B-9C62A95324E0}, 100 + /// + public static PropertyKey PrimaryAddressCity + { + get + { + var key = new PropertyKey(new Guid("{C8EA94F0-A9E3-4969-A94B-9C62A95324E0}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.PrimaryAddressCountry -- PKEY_Contact_PrimaryAddressCountry + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E53D799D-0F3F-466E-B2FF-74634A3CB7A4}, 100 + /// + public static PropertyKey PrimaryAddressCountry + { + get + { + var key = new PropertyKey(new Guid("{E53D799D-0F3F-466E-B2FF-74634A3CB7A4}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.PrimaryAddressPostalCode -- PKEY_Contact_PrimaryAddressPostalCode + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {18BBD425-ECFD-46EF-B612-7B4A6034EDA0}, 100 + /// + public static PropertyKey PrimaryAddressPostalCode + { + get + { + var key = new PropertyKey(new Guid("{18BBD425-ECFD-46EF-B612-7B4A6034EDA0}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.PrimaryAddressPostOfficeBox -- PKEY_Contact_PrimaryAddressPostOfficeBox + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DE5EF3C7-46E1-484E-9999-62C5308394C1}, 100 + /// + public static PropertyKey PrimaryAddressPostOfficeBox + { + get + { + var key = new PropertyKey(new Guid("{DE5EF3C7-46E1-484E-9999-62C5308394C1}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.PrimaryAddressState -- PKEY_Contact_PrimaryAddressState + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F1176DFE-7138-4640-8B4C-AE375DC70A6D}, 100 + /// + public static PropertyKey PrimaryAddressState + { + get + { + var key = new PropertyKey(new Guid("{F1176DFE-7138-4640-8B4C-AE375DC70A6D}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.PrimaryAddressStreet -- PKEY_Contact_PrimaryAddressStreet + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {63C25B20-96BE-488F-8788-C09C407AD812}, 100 + /// + public static PropertyKey PrimaryAddressStreet + { + get + { + var key = new PropertyKey(new Guid("{63C25B20-96BE-488F-8788-C09C407AD812}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.PrimaryEmailAddress -- PKEY_Contact_PrimaryEmailAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 48 + /// + public static PropertyKey PrimaryEmailAddress + { + get + { + var key = new PropertyKey(new Guid("{176DC63C-2688-4E89-8143-A347800F25E9}"), 48); + + return key; + } + } + + /// + /// Name: System.Contact.PrimaryTelephone -- PKEY_Contact_PrimaryTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 25 + /// + public static PropertyKey PrimaryTelephone + { + get + { + var key = new PropertyKey(new Guid("{176DC63C-2688-4E89-8143-A347800F25E9}"), 25); + + return key; + } + } + + /// + /// Name: System.Contact.Profession -- PKEY_Contact_Profession + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7268AF55-1CE4-4F6E-A41F-B6E4EF10E4A9}, 100 + /// + public static PropertyKey Profession + { + get + { + var key = new PropertyKey(new Guid("{7268AF55-1CE4-4F6E-A41F-B6E4EF10E4A9}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.SpouseName -- PKEY_Contact_SpouseName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9D2408B6-3167-422B-82B0-F583B7A7CFE3}, 100 + /// + public static PropertyKey SpouseName + { + get + { + var key = new PropertyKey(new Guid("{9D2408B6-3167-422B-82B0-F583B7A7CFE3}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.Suffix -- PKEY_Contact_Suffix + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {176DC63C-2688-4E89-8143-A347800F25E9}, 73 + /// + public static PropertyKey Suffix + { + get + { + var key = new PropertyKey(new Guid("{176DC63C-2688-4E89-8143-A347800F25E9}"), 73); + + return key; + } + } + + /// + /// Name: System.Contact.TelexNumber -- PKEY_Contact_TelexNumber + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C554493C-C1F7-40C1-A76C-EF8C0614003E}, 100 + /// + public static PropertyKey TelexNumber + { + get + { + var key = new PropertyKey(new Guid("{C554493C-C1F7-40C1-A76C-EF8C0614003E}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.TTYTDDTelephone -- PKEY_Contact_TTYTDDTelephone + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {AAF16BAC-2B55-45E6-9F6D-415EB94910DF}, 100 + /// + public static PropertyKey TTYTDDTelephone + { + get + { + var key = new PropertyKey(new Guid("{AAF16BAC-2B55-45E6-9F6D-415EB94910DF}"), 100); + + return key; + } + } + + /// + /// Name: System.Contact.WebPage -- PKEY_Contact_WebPage + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 18 + /// + public static PropertyKey Webpage + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 18); + + return key; + } + } + #endregion + + + #region sub-classes + + /// + /// JA Properties + /// + public static class JA + { + + + #region Properties + + /// + /// Name: System.Contact.JA.CompanyNamePhonetic -- PKEY_Contact_JA_CompanyNamePhonetic + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {897B3694-FE9E-43E6-8066-260F590C0100}, 2 + /// + public static PropertyKey CompanyNamePhonetic + { + get + { + var key = new PropertyKey(new Guid("{897B3694-FE9E-43E6-8066-260F590C0100}"), 2); + + return key; + } + } + + /// + /// Name: System.Contact.JA.FirstNamePhonetic -- PKEY_Contact_JA_FirstNamePhonetic + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {897B3694-FE9E-43E6-8066-260F590C0100}, 3 + /// + public static PropertyKey FirstNamePhonetic + { + get + { + var key = new PropertyKey(new Guid("{897B3694-FE9E-43E6-8066-260F590C0100}"), 3); + + return key; + } + } + + /// + /// Name: System.Contact.JA.LastNamePhonetic -- PKEY_Contact_JA_LastNamePhonetic + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {897B3694-FE9E-43E6-8066-260F590C0100}, 4 + /// + public static PropertyKey LastNamePhonetic + { + get + { + var key = new PropertyKey(new Guid("{897B3694-FE9E-43E6-8066-260F590C0100}"), 4); + + return key; + } + } + #endregion + + } + #endregion + } + + /// + /// JA Properties + /// + public static class JA + { + + + #region Properties + + /// + /// Name: System.Contact.JA.CompanyNamePhonetic -- PKEY_Contact_JA_CompanyNamePhonetic + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {897B3694-FE9E-43E6-8066-260F590C0100}, 2 + /// + public static PropertyKey CompanyNamePhonetic + { + get + { + var key = new PropertyKey(new Guid("{897B3694-FE9E-43E6-8066-260F590C0100}"), 2); + + return key; + } + } + + /// + /// Name: System.Contact.JA.FirstNamePhonetic -- PKEY_Contact_JA_FirstNamePhonetic + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {897B3694-FE9E-43E6-8066-260F590C0100}, 3 + /// + public static PropertyKey FirstNamePhonetic + { + get + { + var key = new PropertyKey(new Guid("{897B3694-FE9E-43E6-8066-260F590C0100}"), 3); + + return key; + } + } + + /// + /// Name: System.Contact.JA.LastNamePhonetic -- PKEY_Contact_JA_LastNamePhonetic + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {897B3694-FE9E-43E6-8066-260F590C0100}, 4 + /// + public static PropertyKey LastNamePhonetic + { + get + { + var key = new PropertyKey(new Guid("{897B3694-FE9E-43E6-8066-260F590C0100}"), 4); + + return key; + } + } + #endregion + + + + } + + /// + /// Device Properties + /// + public static class Device + { + + + #region Properties + + /// + /// Name: System.Device.PrinterURL -- PKEY_Device_PrinterURL + /// Description: Printer information Printer URL. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0B48F35A-BE6E-4F17-B108-3C4073D1669A}, 15 + /// + public static PropertyKey PrinterUrl + { + get + { + var key = new PropertyKey(new Guid("{0B48F35A-BE6E-4F17-B108-3C4073D1669A}"), 15); + + return key; + } + } + #endregion + + + + } + + /// + /// DeviceInterface Properties + /// + public static class DeviceInterface + { + + + #region Properties + + /// + /// Name: System.DeviceInterface.PrinterDriverDirectory -- PKEY_DeviceInterface_PrinterDriverDirectory + /// Description: Printer information Printer Driver Directory. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {847C66DE-B8D6-4AF9-ABC3-6F4F926BC039}, 14 + /// + public static PropertyKey PrinterDriverDirectory + { + get + { + var key = new PropertyKey(new Guid("{847C66DE-B8D6-4AF9-ABC3-6F4F926BC039}"), 14); + + return key; + } + } + + /// + /// Name: System.DeviceInterface.PrinterDriverName -- PKEY_DeviceInterface_PrinterDriverName + /// Description: Printer information Driver Name. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {AFC47170-14F5-498C-8F30-B0D19BE449C6}, 11 + /// + public static PropertyKey PrinterDriverName + { + get + { + var key = new PropertyKey(new Guid("{AFC47170-14F5-498C-8F30-B0D19BE449C6}"), 11); + + return key; + } + } + + /// + /// Name: System.DeviceInterface.PrinterName -- PKEY_DeviceInterface_PrinterName + /// Description: Printer information Printer Name. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0A7B84EF-0C27-463F-84EF-06C5070001BE}, 10 + /// + public static PropertyKey PrinterName + { + get + { + var key = new PropertyKey(new Guid("{0A7B84EF-0C27-463F-84EF-06C5070001BE}"), 10); + + return key; + } + } + + /// + /// Name: System.DeviceInterface.PrinterPortName -- PKEY_DeviceInterface_PrinterPortName + /// Description: Printer information Port Name. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {EEC7B761-6F94-41B1-949F-C729720DD13C}, 12 + /// + public static PropertyKey PrinterPortName + { + get + { + var key = new PropertyKey(new Guid("{EEC7B761-6F94-41B1-949F-C729720DD13C}"), 12); + + return key; + } + } + #endregion + + + + } + + /// + /// Devices Properties + /// + public static class Devices + { + + + #region Properties + + /// + /// Name: System.Devices.BatteryLife -- PKEY_Devices_BatteryLife + /// Description: Remaining battery life of the device as an integer between 0 and 100 percent. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 10 + /// + public static PropertyKey BatteryLife + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 10); + + return key; + } + } + + /// + /// Name: System.Devices.BatteryPlusCharging -- PKEY_Devices_BatteryPlusCharging + /// Description: Remaining battery life of the device as an integer between 0 and 100 percent and the device's charging state. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 22 + /// + public static PropertyKey BatteryPlusCharging + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 22); + + return key; + } + } + + /// + /// Name: System.Devices.BatteryPlusChargingText -- PKEY_Devices_BatteryPlusChargingText + /// Description: Remaining battery life of the device and the device's charging state as a string. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 23 + /// + public static PropertyKey BatteryPlusChargingText + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 23); + + return key; + } + } + + /// + /// Name: System.Devices.Category -- PKEY_Devices_Category_Desc_Singular + /// Description: Singular form of device category. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 91 + /// + public static PropertyKey Category + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 91); + + return key; + } + } + + /// + /// Name: System.Devices.CategoryGroup -- PKEY_Devices_CategoryGroup_Desc + /// Description: Plural form of device category. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 94 + /// + public static PropertyKey CategoryGroup + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 94); + + return key; + } + } + + /// + /// Name: System.Devices.CategoryPlural -- PKEY_Devices_Category_Desc_Plural + /// Description: Plural form of device category. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 92 + /// + public static PropertyKey CategoryPlural + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 92); + + return key; + } + } + + /// + /// Name: System.Devices.ChargingState -- PKEY_Devices_ChargingState + /// Description: Boolean value representing if the device is currently charging. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 11 + /// + public static PropertyKey ChargingState + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 11); + + return key; + } + } + + /// + /// Name: System.Devices.Connected -- PKEY_Devices_IsConnected + /// Description: Device connection state. If VARIANT_TRUE, indicates the device is currently connected to the computer. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 55 + /// + public static PropertyKey Connected + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 55); + + return key; + } + } + + /// + /// Name: System.Devices.ContainerId -- PKEY_Devices_ContainerId + /// Description: Device container ID. + /// + /// Type: Guid -- VT_CLSID + /// FormatID: {8C7ED206-3F8A-4827-B3AB-AE9E1FAEFC6C}, 2 + /// + public static PropertyKey ContainerId + { + get + { + var key = new PropertyKey(new Guid("{8C7ED206-3F8A-4827-B3AB-AE9E1FAEFC6C}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.DefaultTooltip -- PKEY_Devices_DefaultTooltip + /// Description: Tooltip for default state + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {880F70A2-6082-47AC-8AAB-A739D1A300C3}, 153 + /// + public static PropertyKey DefaultTooltip + { + get + { + var key = new PropertyKey(new Guid("{880F70A2-6082-47AC-8AAB-A739D1A300C3}"), 153); + + return key; + } + } + + /// + /// Name: System.Devices.DeviceDescription1 -- PKEY_Devices_DeviceDescription1 + /// Description: First line of descriptive text about the device. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 81 + /// + public static PropertyKey DeviceDescription1 + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 81); + + return key; + } + } + + /// + /// Name: System.Devices.DeviceDescription2 -- PKEY_Devices_DeviceDescription2 + /// Description: Second line of descriptive text about the device. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 82 + /// + public static PropertyKey DeviceDescription2 + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 82); + + return key; + } + } + + /// + /// Name: System.Devices.DiscoveryMethod -- PKEY_Devices_DiscoveryMethod + /// Description: Device discovery method. This indicates on what transport or physical connection the device is discovered. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 52 + /// + public static PropertyKey DiscoveryMethod + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 52); + + return key; + } + } + + /// + /// Name: System.Devices.FriendlyName -- PKEY_Devices_FriendlyName + /// Description: Device friendly name. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {656A3BB3-ECC0-43FD-8477-4AE0404A96CD}, 12288 + /// + public static PropertyKey FriendlyName + { + get + { + var key = new PropertyKey(new Guid("{656A3BB3-ECC0-43FD-8477-4AE0404A96CD}"), 12288); + + return key; + } + } + + /// + /// Name: System.Devices.FunctionPaths -- PKEY_Devices_FunctionPaths + /// Description: Available functions for this device. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D08DD4C0-3A9E-462E-8290-7B636B2576B9}, 3 + /// + public static PropertyKey FunctionPaths + { + get + { + var key = new PropertyKey(new Guid("{D08DD4C0-3A9E-462E-8290-7B636B2576B9}"), 3); + + return key; + } + } + + /// + /// Name: System.Devices.InterfacePaths -- PKEY_Devices_InterfacePaths + /// Description: Available interfaces for this device. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D08DD4C0-3A9E-462E-8290-7B636B2576B9}, 2 + /// + public static PropertyKey InterfacePaths + { + get + { + var key = new PropertyKey(new Guid("{D08DD4C0-3A9E-462E-8290-7B636B2576B9}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.IsDefault -- PKEY_Devices_IsDefaultDevice + /// Description: If VARIANT_TRUE, the device is not working properly. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 86 + /// + public static PropertyKey IsDefault + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 86); + + return key; + } + } + + /// + /// Name: System.Devices.IsNetworkConnected -- PKEY_Devices_IsNetworkDevice + /// Description: If VARIANT_TRUE, the device is not working properly. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 85 + /// + public static PropertyKey IsNetworkConnected + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 85); + + return key; + } + } + + /// + /// Name: System.Devices.IsShared -- PKEY_Devices_IsSharedDevice + /// Description: If VARIANT_TRUE, the device is not working properly. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 84 + /// + public static PropertyKey IsShared + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 84); + + return key; + } + } + + /// + /// Name: System.Devices.IsSoftwareInstalling -- PKEY_Devices_IsSoftwareInstalling + /// Description: If VARIANT_TRUE, the device installer is currently installing software. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {83DA6326-97A6-4088-9453-A1923F573B29}, 9 + /// + public static PropertyKey IsSoftwareInstalling + { + get + { + var key = new PropertyKey(new Guid("{83DA6326-97A6-4088-9453-A1923F573B29}"), 9); + + return key; + } + } + + /// + /// Name: System.Devices.LaunchDeviceStageFromExplorer -- PKEY_Devices_LaunchDeviceStageFromExplorer + /// Description: Indicates whether to launch Device Stage or not + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 77 + /// + public static PropertyKey LaunchDeviceStageFromExplorer + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 77); + + return key; + } + } + + /// + /// Name: System.Devices.LocalMachine -- PKEY_Devices_IsLocalMachine + /// Description: If VARIANT_TRUE, the device in question is actually the computer. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 70 + /// + public static PropertyKey LocalMachine + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 70); + + return key; + } + } + + /// + /// Name: System.Devices.Manufacturer -- PKEY_Devices_Manufacturer + /// Description: Device manufacturer. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {656A3BB3-ECC0-43FD-8477-4AE0404A96CD}, 8192 + /// + public static PropertyKey Manufacturer + { + get + { + var key = new PropertyKey(new Guid("{656A3BB3-ECC0-43FD-8477-4AE0404A96CD}"), 8192); + + return key; + } + } + + /// + /// Name: System.Devices.MissedCalls -- PKEY_Devices_MissedCalls + /// Description: Number of missed calls on the device. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 5 + /// + public static PropertyKey MissedCalls + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 5); + + return key; + } + } + + /// + /// Name: System.Devices.ModelName -- PKEY_Devices_ModelName + /// Description: Model name of the device. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {656A3BB3-ECC0-43FD-8477-4AE0404A96CD}, 8194 + /// + public static PropertyKey ModelName + { + get + { + var key = new PropertyKey(new Guid("{656A3BB3-ECC0-43FD-8477-4AE0404A96CD}"), 8194); + + return key; + } + } + + /// + /// Name: System.Devices.ModelNumber -- PKEY_Devices_ModelNumber + /// Description: Model number of the device. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {656A3BB3-ECC0-43FD-8477-4AE0404A96CD}, 8195 + /// + public static PropertyKey ModelNumber + { + get + { + var key = new PropertyKey(new Guid("{656A3BB3-ECC0-43FD-8477-4AE0404A96CD}"), 8195); + + return key; + } + } + + /// + /// Name: System.Devices.NetworkedTooltip -- PKEY_Devices_NetworkedTooltip + /// Description: Tooltip for connection state + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {880F70A2-6082-47AC-8AAB-A739D1A300C3}, 152 + /// + public static PropertyKey NetworkedTooltip + { + get + { + var key = new PropertyKey(new Guid("{880F70A2-6082-47AC-8AAB-A739D1A300C3}"), 152); + + return key; + } + } + + /// + /// Name: System.Devices.NetworkName -- PKEY_Devices_NetworkName + /// Description: Name of the device's network. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 7 + /// + public static PropertyKey NetworkName + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 7); + + return key; + } + } + + /// + /// Name: System.Devices.NetworkType -- PKEY_Devices_NetworkType + /// Description: String representing the type of the device's network. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 8 + /// + public static PropertyKey NetworkType + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 8); + + return key; + } + } + + /// + /// Name: System.Devices.NewPictures -- PKEY_Devices_NewPictures + /// Description: Number of new pictures on the device. + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 4 + /// + public static PropertyKey NewPictures + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 4); + + return key; + } + } + + /// + /// Name: System.Devices.Notification -- PKEY_Devices_Notification + /// Description: Device Notification Property. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {06704B0C-E830-4C81-9178-91E4E95A80A0}, 3 + /// + public static PropertyKey Notification + { + get + { + var key = new PropertyKey(new Guid("{06704B0C-E830-4C81-9178-91E4E95A80A0}"), 3); + + return key; + } + } + + /// + /// Name: System.Devices.NotificationStore -- PKEY_Devices_NotificationStore + /// Description: Device Notification Store. + /// + /// Type: Object -- VT_UNKNOWN + /// FormatID: {06704B0C-E830-4C81-9178-91E4E95A80A0}, 2 + /// + public static PropertyKey NotificationStore + { + get + { + var key = new PropertyKey(new Guid("{06704B0C-E830-4C81-9178-91E4E95A80A0}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.NotWorkingProperly -- PKEY_Devices_IsNotWorkingProperly + /// Description: If VARIANT_TRUE, the device is not working properly. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 83 + /// + public static PropertyKey NotWorkingProperly + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 83); + + return key; + } + } + + /// + /// Name: System.Devices.Paired -- PKEY_Devices_IsPaired + /// Description: Device paired state. If VARIANT_TRUE, indicates the device is not paired with the computer. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {78C34FC8-104A-4ACA-9EA4-524D52996E57}, 56 + /// + public static PropertyKey Paired + { + get + { + var key = new PropertyKey(new Guid("{78C34FC8-104A-4ACA-9EA4-524D52996E57}"), 56); + + return key; + } + } + + /// + /// Name: System.Devices.PrimaryCategory -- PKEY_Devices_PrimaryCategory + /// Description: Primary category group for this device. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D08DD4C0-3A9E-462E-8290-7B636B2576B9}, 10 + /// + public static PropertyKey PrimaryCategory + { + get + { + var key = new PropertyKey(new Guid("{D08DD4C0-3A9E-462E-8290-7B636B2576B9}"), 10); + + return key; + } + } + + /// + /// Name: System.Devices.Roaming -- PKEY_Devices_Roaming + /// Description: Status indicator used to indicate if the device is roaming. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 9 + /// + public static PropertyKey Roaming + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 9); + + return key; + } + } + + /// + /// Name: System.Devices.SafeRemovalRequired -- PKEY_Devices_SafeRemovalRequired + /// Description: Indicates if a device requires safe removal or not + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {AFD97640-86A3-4210-B67C-289C41AABE55}, 2 + /// + public static PropertyKey SafeRemovalRequired + { + get + { + var key = new PropertyKey(new Guid("{AFD97640-86A3-4210-B67C-289C41AABE55}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.SharedTooltip -- PKEY_Devices_SharedTooltip + /// Description: Tooltip for sharing state + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {880F70A2-6082-47AC-8AAB-A739D1A300C3}, 151 + /// + public static PropertyKey SharedTooltip + { + get + { + var key = new PropertyKey(new Guid("{880F70A2-6082-47AC-8AAB-A739D1A300C3}"), 151); + + return key; + } + } + + /// + /// Name: System.Devices.SignalStrength -- PKEY_Devices_SignalStrength + /// Description: Device signal strength. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 2 + /// + public static PropertyKey SignalStrength + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.Status1 -- PKEY_Devices_Status1 + /// Description: 1st line of device status. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D08DD4C0-3A9E-462E-8290-7B636B2576B9}, 257 + /// + public static PropertyKey Status1 + { + get + { + var key = new PropertyKey(new Guid("{D08DD4C0-3A9E-462E-8290-7B636B2576B9}"), 257); + + return key; + } + } + + /// + /// Name: System.Devices.Status2 -- PKEY_Devices_Status2 + /// Description: 2nd line of device status. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D08DD4C0-3A9E-462E-8290-7B636B2576B9}, 258 + /// + public static PropertyKey Status2 + { + get + { + var key = new PropertyKey(new Guid("{D08DD4C0-3A9E-462E-8290-7B636B2576B9}"), 258); + + return key; + } + } + + /// + /// Name: System.Devices.StorageCapacity -- PKEY_Devices_StorageCapacity + /// Description: Total storage capacity of the device. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 12 + /// + public static PropertyKey StorageCapacity + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 12); + + return key; + } + } + + /// + /// Name: System.Devices.StorageFreeSpace -- PKEY_Devices_StorageFreeSpace + /// Description: Total free space of the storage of the device. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 13 + /// + public static PropertyKey StorageFreeSpace + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 13); + + return key; + } + } + + /// + /// Name: System.Devices.StorageFreeSpacePercent -- PKEY_Devices_StorageFreeSpacePercent + /// Description: Total free space of the storage of the device as a percentage. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 14 + /// + public static PropertyKey StorageFreeSpacePercent + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 14); + + return key; + } + } + + /// + /// Name: System.Devices.TextMessages -- PKEY_Devices_TextMessages + /// Description: Number of unread text messages on the device. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 3 + /// + public static PropertyKey TextMessages + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 3); + + return key; + } + } + + /// + /// Name: System.Devices.Voicemail -- PKEY_Devices_Voicemail + /// Description: Status indicator used to indicate if the device has voicemail. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {49CD1F76-5626-4B17-A4E8-18B4AA1A2213}, 6 + /// + public static PropertyKey Voicemail + { + get + { + var key = new PropertyKey(new Guid("{49CD1F76-5626-4B17-A4E8-18B4AA1A2213}"), 6); + + return key; + } + } + #endregion + + + #region sub-classes + + /// + /// Notifications Properties + /// + public static class Notifications + { + + + #region Properties + + /// + /// Name: System.Devices.Notifications.LowBattery -- PKEY_Devices_Notification_LowBattery + /// Description: Device Low Battery Notification. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {C4C07F2B-8524-4E66-AE3A-A6235F103BEB}, 2 + /// + public static PropertyKey LowBattery + { + get + { + var key = new PropertyKey(new Guid("{C4C07F2B-8524-4E66-AE3A-A6235F103BEB}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.Notifications.MissedCall -- PKEY_Devices_Notification_MissedCall + /// Description: Device Missed Call Notification. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {6614EF48-4EFE-4424-9EDA-C79F404EDF3E}, 2 + /// + public static PropertyKey MissedCall + { + get + { + var key = new PropertyKey(new Guid("{6614EF48-4EFE-4424-9EDA-C79F404EDF3E}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.Notifications.NewMessage -- PKEY_Devices_Notification_NewMessage + /// Description: Device New Message Notification. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {2BE9260A-2012-4742-A555-F41B638B7DCB}, 2 + /// + public static PropertyKey NewMessage + { + get + { + var key = new PropertyKey(new Guid("{2BE9260A-2012-4742-A555-F41B638B7DCB}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.Notifications.NewVoicemail -- PKEY_Devices_Notification_NewVoicemail + /// Description: Device Voicemail Notification. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {59569556-0A08-4212-95B9-FAE2AD6413DB}, 2 + /// + public static PropertyKey NewVoicemail + { + get + { + var key = new PropertyKey(new Guid("{59569556-0A08-4212-95B9-FAE2AD6413DB}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.Notifications.StorageFull -- PKEY_Devices_Notification_StorageFull + /// Description: Device Storage Full Notification. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: {A0E00EE1-F0C7-4D41-B8E7-26A7BD8D38B0}, 2 + /// + public static PropertyKey StorageFull + { + get + { + var key = new PropertyKey(new Guid("{A0E00EE1-F0C7-4D41-B8E7-26A7BD8D38B0}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.Notifications.StorageFullLinkText -- PKEY_Devices_Notification_StorageFullLinkText + /// Description: Link Text for the Device Storage Full Notification. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: {A0E00EE1-F0C7-4D41-B8E7-26A7BD8D38B0}, 3 + /// + public static PropertyKey StorageFullLinkText + { + get + { + var key = new PropertyKey(new Guid("{A0E00EE1-F0C7-4D41-B8E7-26A7BD8D38B0}"), 3); + + return key; + } + } + #endregion + + + + } + #endregion + } + + /// + /// Notifications Properties + /// + public static class Notifications + { + + + #region Properties + + /// + /// Name: System.Devices.Notifications.LowBattery -- PKEY_Devices_Notification_LowBattery + /// Description: Device Low Battery Notification. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {C4C07F2B-8524-4E66-AE3A-A6235F103BEB}, 2 + /// + public static PropertyKey LowBattery + { + get + { + var key = new PropertyKey(new Guid("{C4C07F2B-8524-4E66-AE3A-A6235F103BEB}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.Notifications.MissedCall -- PKEY_Devices_Notification_MissedCall + /// Description: Device Missed Call Notification. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {6614EF48-4EFE-4424-9EDA-C79F404EDF3E}, 2 + /// + public static PropertyKey MissedCall + { + get + { + var key = new PropertyKey(new Guid("{6614EF48-4EFE-4424-9EDA-C79F404EDF3E}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.Notifications.NewMessage -- PKEY_Devices_Notification_NewMessage + /// Description: Device New Message Notification. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {2BE9260A-2012-4742-A555-F41B638B7DCB}, 2 + /// + public static PropertyKey NewMessage + { + get + { + var key = new PropertyKey(new Guid("{2BE9260A-2012-4742-A555-F41B638B7DCB}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.Notifications.NewVoicemail -- PKEY_Devices_Notification_NewVoicemail + /// Description: Device Voicemail Notification. + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {59569556-0A08-4212-95B9-FAE2AD6413DB}, 2 + /// + public static PropertyKey NewVoicemail + { + get + { + var key = new PropertyKey(new Guid("{59569556-0A08-4212-95B9-FAE2AD6413DB}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.Notifications.StorageFull -- PKEY_Devices_Notification_StorageFull + /// Description: Device Storage Full Notification. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: {A0E00EE1-F0C7-4D41-B8E7-26A7BD8D38B0}, 2 + /// + public static PropertyKey StorageFull + { + get + { + var key = new PropertyKey(new Guid("{A0E00EE1-F0C7-4D41-B8E7-26A7BD8D38B0}"), 2); + + return key; + } + } + + /// + /// Name: System.Devices.Notifications.StorageFullLinkText -- PKEY_Devices_Notification_StorageFullLinkText + /// Description: Link Text for the Device Storage Full Notification. + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: {A0E00EE1-F0C7-4D41-B8E7-26A7BD8D38B0}, 3 + /// + public static PropertyKey StorageFullLinkText + { + get + { + var key = new PropertyKey(new Guid("{A0E00EE1-F0C7-4D41-B8E7-26A7BD8D38B0}"), 3); + + return key; + } + } + #endregion + + + + } + + /// + /// Document Properties + /// + public static class Document + { + + + #region Properties + + /// + /// Name: System.Document.ByteCount -- PKEY_Document_ByteCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 4 (PIDDSI_BYTECOUNT) + /// + public static PropertyKey ByteCount + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 4); + + return key; + } + } + + /// + /// Name: System.Document.CharacterCount -- PKEY_Document_CharacterCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 16 (PIDSI_CHARCOUNT) + /// + public static PropertyKey CharacterCount + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 16); + + return key; + } + } + + /// + /// Name: System.Document.ClientID -- PKEY_Document_ClientID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {276D7BB0-5B34-4FB0-AA4B-158ED12A1809}, 100 + /// + public static PropertyKey ClientID + { + get + { + var key = new PropertyKey(new Guid("{276D7BB0-5B34-4FB0-AA4B-158ED12A1809}"), 100); + + return key; + } + } + + /// + /// Name: System.Document.Contributor -- PKEY_Document_Contributor + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {F334115E-DA1B-4509-9B3D-119504DC7ABB}, 100 + /// + public static PropertyKey Contributor + { + get + { + var key = new PropertyKey(new Guid("{F334115E-DA1B-4509-9B3D-119504DC7ABB}"), 100); + + return key; + } + } + + /// + /// Name: System.Document.DateCreated -- PKEY_Document_DateCreated + /// Description: This property is stored in the document, not obtained from the file system. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 12 (PIDSI_CREATE_DTM) + /// + public static PropertyKey DateCreated + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 12); + + return key; + } + } + + /// + /// Name: System.Document.DatePrinted -- PKEY_Document_DatePrinted + /// Description: Legacy name: "DocLastPrinted". + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 11 (PIDSI_LASTPRINTED) + /// + public static PropertyKey DatePrinted + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 11); + + return key; + } + } + + /// + /// Name: System.Document.DateSaved -- PKEY_Document_DateSaved + /// Description: Legacy name: "DocLastSavedTm". + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 13 (PIDSI_LASTSAVE_DTM) + /// + public static PropertyKey DateSaved + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 13); + + return key; + } + } + + /// + /// Name: System.Document.Division -- PKEY_Document_Division + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {1E005EE6-BF27-428B-B01C-79676ACD2870}, 100 + /// + public static PropertyKey Division + { + get + { + var key = new PropertyKey(new Guid("{1E005EE6-BF27-428B-B01C-79676ACD2870}"), 100); + + return key; + } + } + + /// + /// Name: System.Document.DocumentID -- PKEY_Document_DocumentID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E08805C8-E395-40DF-80D2-54F0D6C43154}, 100 + /// + public static PropertyKey DocumentID + { + get + { + var key = new PropertyKey(new Guid("{E08805C8-E395-40DF-80D2-54F0D6C43154}"), 100); + + return key; + } + } + + /// + /// Name: System.Document.HiddenSlideCount -- PKEY_Document_HiddenSlideCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 9 (PIDDSI_HIDDENCOUNT) + /// + public static PropertyKey HiddenSlideCount + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 9); + + return key; + } + } + + /// + /// Name: System.Document.LastAuthor -- PKEY_Document_LastAuthor + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 8 (PIDSI_LASTAUTHOR) + /// + public static PropertyKey LastAuthor + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 8); + + return key; + } + } + + /// + /// Name: System.Document.LineCount -- PKEY_Document_LineCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 5 (PIDDSI_LINECOUNT) + /// + public static PropertyKey LineCount + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 5); + + return key; + } + } + + /// + /// Name: System.Document.Manager -- PKEY_Document_Manager + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 14 (PIDDSI_MANAGER) + /// + public static PropertyKey Manager + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 14); + + return key; + } + } + + /// + /// Name: System.Document.MultimediaClipCount -- PKEY_Document_MultimediaClipCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 10 (PIDDSI_MMCLIPCOUNT) + /// + public static PropertyKey MultimediaClipCount + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 10); + + return key; + } + } + + /// + /// Name: System.Document.NoteCount -- PKEY_Document_NoteCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 8 (PIDDSI_NOTECOUNT) + /// + public static PropertyKey NoteCount + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 8); + + return key; + } + } + + /// + /// Name: System.Document.PageCount -- PKEY_Document_PageCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 14 (PIDSI_PAGECOUNT) + /// + public static PropertyKey PageCount + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 14); + + return key; + } + } + + /// + /// Name: System.Document.ParagraphCount -- PKEY_Document_ParagraphCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 6 (PIDDSI_PARCOUNT) + /// + public static PropertyKey ParagraphCount + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 6); + + return key; + } + } + + /// + /// Name: System.Document.PresentationFormat -- PKEY_Document_PresentationFormat + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 3 (PIDDSI_PRESFORMAT) + /// + public static PropertyKey PresentationFormat + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 3); + + return key; + } + } + + /// + /// Name: System.Document.RevisionNumber -- PKEY_Document_RevisionNumber + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 9 (PIDSI_REVNUMBER) + /// + public static PropertyKey RevisionNumber + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 9); + + return key; + } + } + + /// + /// Name: System.Document.Security -- PKEY_Document_Security + /// Description: Access control information, from SummaryInfo propset + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 19 + /// + public static PropertyKey Security + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 19); + + return key; + } + } + + /// + /// Name: System.Document.SlideCount -- PKEY_Document_SlideCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 7 (PIDDSI_SLIDECOUNT) + /// + public static PropertyKey SlideCount + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 7); + + return key; + } + } + + /// + /// Name: System.Document.Template -- PKEY_Document_Template + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 7 (PIDSI_TEMPLATE) + /// + public static PropertyKey Template + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 7); + + return key; + } + } + + /// + /// Name: System.Document.TotalEditingTime -- PKEY_Document_TotalEditingTime + /// Description: 100ns units, not milliseconds. VT_FILETIME for IPropertySetStorage handlers (legacy) + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 10 (PIDSI_EDITTIME) + /// + public static PropertyKey TotalEditingTime + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 10); + + return key; + } + } + + /// + /// Name: System.Document.Version -- PKEY_Document_Version + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DocumentSummaryInformation) {D5CDD502-2E9C-101B-9397-08002B2CF9AE}, 29 + /// + public static PropertyKey Version + { + get + { + var key = new PropertyKey(new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"), 29); + + return key; + } + } + + /// + /// Name: System.Document.WordCount -- PKEY_Document_WordCount + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_SummaryInformation) {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, 15 (PIDSI_WORDCOUNT) + /// + public static PropertyKey WordCount + { + get + { + var key = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 15); + + return key; + } + } + #endregion + + + + } + + /// + /// DRM Properties + /// + public static class DRM + { + + + #region Properties + + /// + /// Name: System.DRM.DatePlayExpires -- PKEY_DRM_DatePlayExpires + /// Description: Indicates when play expires for digital rights management. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_DRM) {AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}, 6 (PIDDRSI_PLAYEXPIRES) + /// + public static PropertyKey DatePlayExpires + { + get + { + var key = new PropertyKey(new Guid("{AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}"), 6); + + return key; + } + } + + /// + /// Name: System.DRM.DatePlayStarts -- PKEY_DRM_DatePlayStarts + /// Description: Indicates when play starts for digital rights management. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_DRM) {AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}, 5 (PIDDRSI_PLAYSTARTS) + /// + public static PropertyKey DatePlayStarts + { + get + { + var key = new PropertyKey(new Guid("{AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}"), 5); + + return key; + } + } + + /// + /// Name: System.DRM.Description -- PKEY_DRM_Description + /// Description: Displays the description for digital rights management. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_DRM) {AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}, 3 (PIDDRSI_DESCRIPTION) + /// + public static PropertyKey Description + { + get + { + var key = new PropertyKey(new Guid("{AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}"), 3); + + return key; + } + } + + /// + /// Name: System.DRM.IsProtected -- PKEY_DRM_IsProtected + /// Description: + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: (FMTID_DRM) {AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}, 2 (PIDDRSI_PROTECTED) + /// + public static PropertyKey IsProtected + { + get + { + var key = new PropertyKey(new Guid("{AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}"), 2); + + return key; + } + } + + /// + /// Name: System.DRM.PlayCount -- PKEY_DRM_PlayCount + /// Description: Indicates the play count for digital rights management. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_DRM) {AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}, 4 (PIDDRSI_PLAYCOUNT) + /// + public static PropertyKey PlayCount + { + get + { + var key = new PropertyKey(new Guid("{AEAC19E4-89AE-4508-B9B7-BB867ABEE2ED}"), 4); + + return key; + } + } + #endregion + + + + } + + /// + /// GPS Properties + /// + public static class GPS + { + + + #region Properties + + /// + /// Name: System.GPS.Altitude -- PKEY_GPS_Altitude + /// Description: Indicates the altitude based on the reference in PKEY_GPS_AltitudeRef. Calculated from PKEY_GPS_AltitudeNumerator and + ///PKEY_GPS_AltitudeDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: {827EDB4F-5B73-44A7-891D-FDFFABEA35CA}, 100 + /// + public static PropertyKey Altitude + { + get + { + var key = new PropertyKey(new Guid("{827EDB4F-5B73-44A7-891D-FDFFABEA35CA}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.AltitudeDenominator -- PKEY_GPS_AltitudeDenominator + /// Description: Denominator of PKEY_GPS_Altitude + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {78342DCB-E358-4145-AE9A-6BFE4E0F9F51}, 100 + /// + public static PropertyKey AltitudeDenominator + { + get + { + var key = new PropertyKey(new Guid("{78342DCB-E358-4145-AE9A-6BFE4E0F9F51}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.AltitudeNumerator -- PKEY_GPS_AltitudeNumerator + /// Description: Numerator of PKEY_GPS_Altitude + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {2DAD1EB7-816D-40D3-9EC3-C9773BE2AADE}, 100 + /// + public static PropertyKey AltitudeNumerator + { + get + { + var key = new PropertyKey(new Guid("{2DAD1EB7-816D-40D3-9EC3-C9773BE2AADE}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.AltitudeRef -- PKEY_GPS_AltitudeRef + /// Description: Indicates the reference for the altitude property. (eg: above sea level, below sea level, absolute value) + /// + /// Type: Byte -- VT_UI1 + /// FormatID: {46AC629D-75EA-4515-867F-6DC4321C5844}, 100 + /// + public static PropertyKey AltitudeRef + { + get + { + var key = new PropertyKey(new Guid("{46AC629D-75EA-4515-867F-6DC4321C5844}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.AreaInformation -- PKEY_GPS_AreaInformation + /// Description: Represents the name of the GPS area + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {972E333E-AC7E-49F1-8ADF-A70D07A9BCAB}, 100 + /// + public static PropertyKey AreaInformation + { + get + { + var key = new PropertyKey(new Guid("{972E333E-AC7E-49F1-8ADF-A70D07A9BCAB}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.Date -- PKEY_GPS_Date + /// Description: Date and time of the GPS record + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {3602C812-0F3B-45F0-85AD-603468D69423}, 100 + /// + public static PropertyKey Date + { + get + { + var key = new PropertyKey(new Guid("{3602C812-0F3B-45F0-85AD-603468D69423}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestBearing -- PKEY_GPS_DestBearing + /// Description: Indicates the bearing to the destination point. Calculated from PKEY_GPS_DestBearingNumerator and + ///PKEY_GPS_DestBearingDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {C66D4B3C-E888-47CC-B99F-9DCA3EE34DEA}, 100 + /// + public static PropertyKey DestinationBearing + { + get + { + var key = new PropertyKey(new Guid("{C66D4B3C-E888-47CC-B99F-9DCA3EE34DEA}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestBearingDenominator -- PKEY_GPS_DestBearingDenominator + /// Description: Denominator of PKEY_GPS_DestBearing + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {7ABCF4F8-7C3F-4988-AC91-8D2C2E97ECA5}, 100 + /// + public static PropertyKey DestinationBearingDenominator + { + get + { + var key = new PropertyKey(new Guid("{7ABCF4F8-7C3F-4988-AC91-8D2C2E97ECA5}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestBearingNumerator -- PKEY_GPS_DestBearingNumerator + /// Description: Numerator of PKEY_GPS_DestBearing + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {BA3B1DA9-86EE-4B5D-A2A4-A271A429F0CF}, 100 + /// + public static PropertyKey DestinationBearingNumerator + { + get + { + var key = new PropertyKey(new Guid("{BA3B1DA9-86EE-4B5D-A2A4-A271A429F0CF}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestBearingRef -- PKEY_GPS_DestBearingRef + /// Description: Indicates the reference used for the giving the bearing to the destination point. (eg: true direction, magnetic direction) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9AB84393-2A0F-4B75-BB22-7279786977CB}, 100 + /// + public static PropertyKey DestinationBearingRef + { + get + { + var key = new PropertyKey(new Guid("{9AB84393-2A0F-4B75-BB22-7279786977CB}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestDistance -- PKEY_GPS_DestDistance + /// Description: Indicates the distance to the destination point. Calculated from PKEY_GPS_DestDistanceNumerator and + ///PKEY_GPS_DestDistanceDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {A93EAE04-6804-4F24-AC81-09B266452118}, 100 + /// + public static PropertyKey DestinationDistance + { + get + { + var key = new PropertyKey(new Guid("{A93EAE04-6804-4F24-AC81-09B266452118}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestDistanceDenominator -- PKEY_GPS_DestDistanceDenominator + /// Description: Denominator of PKEY_GPS_DestDistance + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {9BC2C99B-AC71-4127-9D1C-2596D0D7DCB7}, 100 + /// + public static PropertyKey DestinationDistanceDenominator + { + get + { + var key = new PropertyKey(new Guid("{9BC2C99B-AC71-4127-9D1C-2596D0D7DCB7}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestDistanceNumerator -- PKEY_GPS_DestDistanceNumerator + /// Description: Numerator of PKEY_GPS_DestDistance + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {2BDA47DA-08C6-4FE1-80BC-A72FC517C5D0}, 100 + /// + public static PropertyKey DestinationDistanceNumerator + { + get + { + var key = new PropertyKey(new Guid("{2BDA47DA-08C6-4FE1-80BC-A72FC517C5D0}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestDistanceRef -- PKEY_GPS_DestDistanceRef + /// Description: Indicates the unit used to express the distance to the destination. (eg: kilometers, miles, knots) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {ED4DF2D3-8695-450B-856F-F5C1C53ACB66}, 100 + /// + public static PropertyKey DestinationDistanceRef + { + get + { + var key = new PropertyKey(new Guid("{ED4DF2D3-8695-450B-856F-F5C1C53ACB66}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestLatitude -- PKEY_GPS_DestLatitude + /// Description: Indicates the latitude of the destination point. This is an array of three values. Index 0 is the degrees, index 1 + ///is the minutes, index 2 is the seconds. Each is calculated from the values in PKEY_GPS_DestLatitudeNumerator and + ///PKEY_GPS_DestLatitudeDenominator. + /// + /// Type: Multivalue Double -- VT_VECTOR | VT_R8 (For variants: VT_ARRAY | VT_R8) + /// FormatID: {9D1D7CC5-5C39-451C-86B3-928E2D18CC47}, 100 + /// + public static PropertyKey DestinationLatitude + { + get + { + var key = new PropertyKey(new Guid("{9D1D7CC5-5C39-451C-86B3-928E2D18CC47}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestLatitudeDenominator -- PKEY_GPS_DestLatitudeDenominator + /// Description: Denominator of PKEY_GPS_DestLatitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {3A372292-7FCA-49A7-99D5-E47BB2D4E7AB}, 100 + /// + public static PropertyKey DestinationLatitudeDenominator + { + get + { + var key = new PropertyKey(new Guid("{3A372292-7FCA-49A7-99D5-E47BB2D4E7AB}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestLatitudeNumerator -- PKEY_GPS_DestLatitudeNumerator + /// Description: Numerator of PKEY_GPS_DestLatitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {ECF4B6F6-D5A6-433C-BB92-4076650FC890}, 100 + /// + public static PropertyKey DestinationLatitudeNumerator + { + get + { + var key = new PropertyKey(new Guid("{ECF4B6F6-D5A6-433C-BB92-4076650FC890}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestLatitudeRef -- PKEY_GPS_DestLatitudeRef + /// Description: Indicates whether the latitude destination point is north or south latitude + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CEA820B9-CE61-4885-A128-005D9087C192}, 100 + /// + public static PropertyKey DestinationLatitudeRef + { + get + { + var key = new PropertyKey(new Guid("{CEA820B9-CE61-4885-A128-005D9087C192}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestLongitude -- PKEY_GPS_DestLongitude + /// Description: Indicates the latitude of the destination point. This is an array of three values. Index 0 is the degrees, index 1 + ///is the minutes, index 2 is the seconds. Each is calculated from the values in PKEY_GPS_DestLongitudeNumerator and + ///PKEY_GPS_DestLongitudeDenominator. + /// + /// Type: Multivalue Double -- VT_VECTOR | VT_R8 (For variants: VT_ARRAY | VT_R8) + /// FormatID: {47A96261-CB4C-4807-8AD3-40B9D9DBC6BC}, 100 + /// + public static PropertyKey DestinationLongitude + { + get + { + var key = new PropertyKey(new Guid("{47A96261-CB4C-4807-8AD3-40B9D9DBC6BC}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestLongitudeDenominator -- PKEY_GPS_DestLongitudeDenominator + /// Description: Denominator of PKEY_GPS_DestLongitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {425D69E5-48AD-4900-8D80-6EB6B8D0AC86}, 100 + /// + public static PropertyKey DestinationLongitudeDenominator + { + get + { + var key = new PropertyKey(new Guid("{425D69E5-48AD-4900-8D80-6EB6B8D0AC86}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestLongitudeNumerator -- PKEY_GPS_DestLongitudeNumerator + /// Description: Numerator of PKEY_GPS_DestLongitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {A3250282-FB6D-48D5-9A89-DBCACE75CCCF}, 100 + /// + public static PropertyKey DestinationLongitudeNumerator + { + get + { + var key = new PropertyKey(new Guid("{A3250282-FB6D-48D5-9A89-DBCACE75CCCF}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DestLongitudeRef -- PKEY_GPS_DestLongitudeRef + /// Description: Indicates whether the longitude destination point is east or west longitude + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {182C1EA6-7C1C-4083-AB4B-AC6C9F4ED128}, 100 + /// + public static PropertyKey DestinationLongitudeRef + { + get + { + var key = new PropertyKey(new Guid("{182C1EA6-7C1C-4083-AB4B-AC6C9F4ED128}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.Differential -- PKEY_GPS_Differential + /// Description: Indicates whether differential correction was applied to the GPS receiver + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {AAF4EE25-BD3B-4DD7-BFC4-47F77BB00F6D}, 100 + /// + public static PropertyKey Differential + { + get + { + var key = new PropertyKey(new Guid("{AAF4EE25-BD3B-4DD7-BFC4-47F77BB00F6D}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DOP -- PKEY_GPS_DOP + /// Description: Indicates the GPS DOP (data degree of precision). Calculated from PKEY_GPS_DOPNumerator and PKEY_GPS_DOPDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: {0CF8FB02-1837-42F1-A697-A7017AA289B9}, 100 + /// + public static PropertyKey DOP + { + get + { + var key = new PropertyKey(new Guid("{0CF8FB02-1837-42F1-A697-A7017AA289B9}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DOPDenominator -- PKEY_GPS_DOPDenominator + /// Description: Denominator of PKEY_GPS_DOP + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {A0BE94C5-50BA-487B-BD35-0654BE8881ED}, 100 + /// + public static PropertyKey DOPDenominator + { + get + { + var key = new PropertyKey(new Guid("{A0BE94C5-50BA-487B-BD35-0654BE8881ED}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.DOPNumerator -- PKEY_GPS_DOPNumerator + /// Description: Numerator of PKEY_GPS_DOP + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {47166B16-364F-4AA0-9F31-E2AB3DF449C3}, 100 + /// + public static PropertyKey DOPNumerator + { + get + { + var key = new PropertyKey(new Guid("{47166B16-364F-4AA0-9F31-E2AB3DF449C3}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.ImgDirection -- PKEY_GPS_ImgDirection + /// Description: Indicates direction of the image when it was captured. Calculated from PKEY_GPS_ImgDirectionNumerator and + ///PKEY_GPS_ImgDirectionDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {16473C91-D017-4ED9-BA4D-B6BAA55DBCF8}, 100 + /// + public static PropertyKey ImageDirection + { + get + { + var key = new PropertyKey(new Guid("{16473C91-D017-4ED9-BA4D-B6BAA55DBCF8}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.ImgDirectionDenominator -- PKEY_GPS_ImgDirectionDenominator + /// Description: Denominator of PKEY_GPS_ImgDirection + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {10B24595-41A2-4E20-93C2-5761C1395F32}, 100 + /// + public static PropertyKey ImageDirectionDenominator + { + get + { + var key = new PropertyKey(new Guid("{10B24595-41A2-4E20-93C2-5761C1395F32}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.ImgDirectionNumerator -- PKEY_GPS_ImgDirectionNumerator + /// Description: Numerator of PKEY_GPS_ImgDirection + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {DC5877C7-225F-45F7-BAC7-E81334B6130A}, 100 + /// + public static PropertyKey ImageDirectionNumerator + { + get + { + var key = new PropertyKey(new Guid("{DC5877C7-225F-45F7-BAC7-E81334B6130A}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.ImgDirectionRef -- PKEY_GPS_ImgDirectionRef + /// Description: Indicates reference for giving the direction of the image when it was captured. (eg: true direction, magnetic direction) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A4AAA5B7-1AD0-445F-811A-0F8F6E67F6B5}, 100 + /// + public static PropertyKey ImageDirectionRef + { + get + { + var key = new PropertyKey(new Guid("{A4AAA5B7-1AD0-445F-811A-0F8F6E67F6B5}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.Latitude -- PKEY_GPS_Latitude + /// Description: Indicates the latitude. This is an array of three values. Index 0 is the degrees, index 1 is the minutes, index 2 + ///is the seconds. Each is calculated from the values in PKEY_GPS_LatitudeNumerator and PKEY_GPS_LatitudeDenominator. + /// + /// Type: Multivalue Double -- VT_VECTOR | VT_R8 (For variants: VT_ARRAY | VT_R8) + /// FormatID: {8727CFFF-4868-4EC6-AD5B-81B98521D1AB}, 100 + /// + public static PropertyKey Latitude + { + get + { + var key = new PropertyKey(new Guid("{8727CFFF-4868-4EC6-AD5B-81B98521D1AB}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.LatitudeDenominator -- PKEY_GPS_LatitudeDenominator + /// Description: Denominator of PKEY_GPS_Latitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {16E634EE-2BFF-497B-BD8A-4341AD39EEB9}, 100 + /// + public static PropertyKey LatitudeDenominator + { + get + { + var key = new PropertyKey(new Guid("{16E634EE-2BFF-497B-BD8A-4341AD39EEB9}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.LatitudeNumerator -- PKEY_GPS_LatitudeNumerator + /// Description: Numerator of PKEY_GPS_Latitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {7DDAAAD1-CCC8-41AE-B750-B2CB8031AEA2}, 100 + /// + public static PropertyKey LatitudeNumerator + { + get + { + var key = new PropertyKey(new Guid("{7DDAAAD1-CCC8-41AE-B750-B2CB8031AEA2}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.LatitudeRef -- PKEY_GPS_LatitudeRef + /// Description: Indicates whether latitude is north or south latitude + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {029C0252-5B86-46C7-ACA0-2769FFC8E3D4}, 100 + /// + public static PropertyKey LatitudeRef + { + get + { + var key = new PropertyKey(new Guid("{029C0252-5B86-46C7-ACA0-2769FFC8E3D4}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.Longitude -- PKEY_GPS_Longitude + /// Description: Indicates the longitude. This is an array of three values. Index 0 is the degrees, index 1 is the minutes, index 2 + ///is the seconds. Each is calculated from the values in PKEY_GPS_LongitudeNumerator and PKEY_GPS_LongitudeDenominator. + /// + /// Type: Multivalue Double -- VT_VECTOR | VT_R8 (For variants: VT_ARRAY | VT_R8) + /// FormatID: {C4C4DBB2-B593-466B-BBDA-D03D27D5E43A}, 100 + /// + public static PropertyKey Longitude + { + get + { + var key = new PropertyKey(new Guid("{C4C4DBB2-B593-466B-BBDA-D03D27D5E43A}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.LongitudeDenominator -- PKEY_GPS_LongitudeDenominator + /// Description: Denominator of PKEY_GPS_Longitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {BE6E176C-4534-4D2C-ACE5-31DEDAC1606B}, 100 + /// + public static PropertyKey LongitudeDenominator + { + get + { + var key = new PropertyKey(new Guid("{BE6E176C-4534-4D2C-ACE5-31DEDAC1606B}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.LongitudeNumerator -- PKEY_GPS_LongitudeNumerator + /// Description: Numerator of PKEY_GPS_Longitude + /// + /// Type: Multivalue UInt32 -- VT_VECTOR | VT_UI4 (For variants: VT_ARRAY | VT_UI4) + /// FormatID: {02B0F689-A914-4E45-821D-1DDA452ED2C4}, 100 + /// + public static PropertyKey LongitudeNumerator + { + get + { + var key = new PropertyKey(new Guid("{02B0F689-A914-4E45-821D-1DDA452ED2C4}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.LongitudeRef -- PKEY_GPS_LongitudeRef + /// Description: Indicates whether longitude is east or west longitude + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {33DCF22B-28D5-464C-8035-1EE9EFD25278}, 100 + /// + public static PropertyKey LongitudeRef + { + get + { + var key = new PropertyKey(new Guid("{33DCF22B-28D5-464C-8035-1EE9EFD25278}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.MapDatum -- PKEY_GPS_MapDatum + /// Description: Indicates the geodetic survey data used by the GPS receiver + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {2CA2DAE6-EDDC-407D-BEF1-773942ABFA95}, 100 + /// + public static PropertyKey MapDatum + { + get + { + var key = new PropertyKey(new Guid("{2CA2DAE6-EDDC-407D-BEF1-773942ABFA95}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.MeasureMode -- PKEY_GPS_MeasureMode + /// Description: Indicates the GPS measurement mode. (eg: 2-dimensional, 3-dimensional) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A015ED5D-AAEA-4D58-8A86-3C586920EA0B}, 100 + /// + public static PropertyKey MeasureMode + { + get + { + var key = new PropertyKey(new Guid("{A015ED5D-AAEA-4D58-8A86-3C586920EA0B}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.ProcessingMethod -- PKEY_GPS_ProcessingMethod + /// Description: Indicates the name of the method used for location finding + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {59D49E61-840F-4AA9-A939-E2099B7F6399}, 100 + /// + public static PropertyKey ProcessingMethod + { + get + { + var key = new PropertyKey(new Guid("{59D49E61-840F-4AA9-A939-E2099B7F6399}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.Satellites -- PKEY_GPS_Satellites + /// Description: Indicates the GPS satellites used for measurements + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {467EE575-1F25-4557-AD4E-B8B58B0D9C15}, 100 + /// + public static PropertyKey Satellites + { + get + { + var key = new PropertyKey(new Guid("{467EE575-1F25-4557-AD4E-B8B58B0D9C15}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.Speed -- PKEY_GPS_Speed + /// Description: Indicates the speed of the GPS receiver movement. Calculated from PKEY_GPS_SpeedNumerator and + ///PKEY_GPS_SpeedDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {DA5D0862-6E76-4E1B-BABD-70021BD25494}, 100 + /// + public static PropertyKey Speed + { + get + { + var key = new PropertyKey(new Guid("{DA5D0862-6E76-4E1B-BABD-70021BD25494}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.SpeedDenominator -- PKEY_GPS_SpeedDenominator + /// Description: Denominator of PKEY_GPS_Speed + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {7D122D5A-AE5E-4335-8841-D71E7CE72F53}, 100 + /// + public static PropertyKey SpeedDenominator + { + get + { + var key = new PropertyKey(new Guid("{7D122D5A-AE5E-4335-8841-D71E7CE72F53}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.SpeedNumerator -- PKEY_GPS_SpeedNumerator + /// Description: Numerator of PKEY_GPS_Speed + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {ACC9CE3D-C213-4942-8B48-6D0820F21C6D}, 100 + /// + public static PropertyKey SpeedNumerator + { + get + { + var key = new PropertyKey(new Guid("{ACC9CE3D-C213-4942-8B48-6D0820F21C6D}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.SpeedRef -- PKEY_GPS_SpeedRef + /// Description: Indicates the unit used to express the speed of the GPS receiver movement. (eg: kilometers per hour, + ///miles per hour, knots). + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {ECF7F4C9-544F-4D6D-9D98-8AD79ADAF453}, 100 + /// + public static PropertyKey SpeedRef + { + get + { + var key = new PropertyKey(new Guid("{ECF7F4C9-544F-4D6D-9D98-8AD79ADAF453}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.Status -- PKEY_GPS_Status + /// Description: Indicates the status of the GPS receiver when the image was recorded. (eg: measurement in progress, + ///measurement interoperability). + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {125491F4-818F-46B2-91B5-D537753617B2}, 100 + /// + public static PropertyKey Status + { + get + { + var key = new PropertyKey(new Guid("{125491F4-818F-46B2-91B5-D537753617B2}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.Track -- PKEY_GPS_Track + /// Description: Indicates the direction of the GPS receiver movement. Calculated from PKEY_GPS_TrackNumerator and + ///PKEY_GPS_TrackDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {76C09943-7C33-49E3-9E7E-CDBA872CFADA}, 100 + /// + public static PropertyKey Track + { + get + { + var key = new PropertyKey(new Guid("{76C09943-7C33-49E3-9E7E-CDBA872CFADA}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.TrackDenominator -- PKEY_GPS_TrackDenominator + /// Description: Denominator of PKEY_GPS_Track + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {C8D1920C-01F6-40C0-AC86-2F3A4AD00770}, 100 + /// + public static PropertyKey TrackDenominator + { + get + { + var key = new PropertyKey(new Guid("{C8D1920C-01F6-40C0-AC86-2F3A4AD00770}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.TrackNumerator -- PKEY_GPS_TrackNumerator + /// Description: Numerator of PKEY_GPS_Track + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {702926F4-44A6-43E1-AE71-45627116893B}, 100 + /// + public static PropertyKey TrackNumerator + { + get + { + var key = new PropertyKey(new Guid("{702926F4-44A6-43E1-AE71-45627116893B}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.TrackRef -- PKEY_GPS_TrackRef + /// Description: Indicates reference for the direction of the GPS receiver movement. (eg: true direction, magnetic direction) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {35DBE6FE-44C3-4400-AAAE-D2C799C407E8}, 100 + /// + public static PropertyKey TrackRef + { + get + { + var key = new PropertyKey(new Guid("{35DBE6FE-44C3-4400-AAAE-D2C799C407E8}"), 100); + + return key; + } + } + + /// + /// Name: System.GPS.VersionID -- PKEY_GPS_VersionID + /// Description: Indicates the version of the GPS information + /// + /// Type: Buffer -- VT_VECTOR | VT_UI1 (For variants: VT_ARRAY | VT_UI1) + /// FormatID: {22704DA4-C6B2-4A99-8E56-F16DF8C92599}, 100 + /// + public static PropertyKey VersionID + { + get + { + var key = new PropertyKey(new Guid("{22704DA4-C6B2-4A99-8E56-F16DF8C92599}"), 100); + + return key; + } + } + #endregion + + + + } + + /// + /// Identity Properties + /// + public static class Identity + { + + + #region Properties + + /// + /// Name: System.Identity.Blob -- PKEY_Identity_Blob + /// Description: Blob used to import/export identities + /// + /// Type: Blob -- VT_BLOB + /// FormatID: {8C3B93A4-BAED-1A83-9A32-102EE313F6EB}, 100 + /// + public static PropertyKey Blob + { + get + { + var key = new PropertyKey(new Guid("{8C3B93A4-BAED-1A83-9A32-102EE313F6EB}"), 100); + + return key; + } + } + + /// + /// Name: System.Identity.DisplayName -- PKEY_Identity_DisplayName + /// Description: Display Name + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7D683FC9-D155-45A8-BB1F-89D19BCB792F}, 100 + /// + public static PropertyKey DisplayName + { + get + { + var key = new PropertyKey(new Guid("{7D683FC9-D155-45A8-BB1F-89D19BCB792F}"), 100); + + return key; + } + } + + /// + /// Name: System.Identity.IsMeIdentity -- PKEY_Identity_IsMeIdentity + /// Description: Is it Me Identity + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {A4108708-09DF-4377-9DFC-6D99986D5A67}, 100 + /// + public static PropertyKey IsMeIdentity + { + get + { + var key = new PropertyKey(new Guid("{A4108708-09DF-4377-9DFC-6D99986D5A67}"), 100); + + return key; + } + } + + /// + /// Name: System.Identity.PrimaryEmailAddress -- PKEY_Identity_PrimaryEmailAddress + /// Description: Primary Email Address + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FCC16823-BAED-4F24-9B32-A0982117F7FA}, 100 + /// + public static PropertyKey PrimaryEmailAddress + { + get + { + var key = new PropertyKey(new Guid("{FCC16823-BAED-4F24-9B32-A0982117F7FA}"), 100); + + return key; + } + } + + /// + /// Name: System.Identity.ProviderID -- PKEY_Identity_ProviderID + /// Description: Provider ID + /// + /// Type: Guid -- VT_CLSID + /// FormatID: {74A7DE49-FA11-4D3D-A006-DB7E08675916}, 100 + /// + public static PropertyKey ProviderID + { + get + { + var key = new PropertyKey(new Guid("{74A7DE49-FA11-4D3D-A006-DB7E08675916}"), 100); + + return key; + } + } + + /// + /// Name: System.Identity.UniqueID -- PKEY_Identity_UniqueID + /// Description: Unique ID + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E55FC3B0-2B60-4220-918E-B21E8BF16016}, 100 + /// + public static PropertyKey UniqueID + { + get + { + var key = new PropertyKey(new Guid("{E55FC3B0-2B60-4220-918E-B21E8BF16016}"), 100); + + return key; + } + } + + /// + /// Name: System.Identity.UserName -- PKEY_Identity_UserName + /// Description: Identity User Name + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C4322503-78CA-49C6-9ACC-A68E2AFD7B6B}, 100 + /// + public static PropertyKey UserName + { + get + { + var key = new PropertyKey(new Guid("{C4322503-78CA-49C6-9ACC-A68E2AFD7B6B}"), 100); + + return key; + } + } + #endregion + + + + } + + /// + /// IdentityProvider Properties + /// + public static class IdentityProvider + { + + + #region Properties + + /// + /// Name: System.IdentityProvider.Name -- PKEY_IdentityProvider_Name + /// Description: Identity Provider Name + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {B96EFF7B-35CA-4A35-8607-29E3A54C46EA}, 100 + /// + public static PropertyKey Name + { + get + { + var key = new PropertyKey(new Guid("{B96EFF7B-35CA-4A35-8607-29E3A54C46EA}"), 100); + + return key; + } + } + + /// + /// Name: System.IdentityProvider.Picture -- PKEY_IdentityProvider_Picture + /// Description: Picture for the Identity Provider + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {2425166F-5642-4864-992F-98FD98F294C3}, 100 + /// + public static PropertyKey Picture + { + get + { + var key = new PropertyKey(new Guid("{2425166F-5642-4864-992F-98FD98F294C3}"), 100); + + return key; + } + } + #endregion + + + + } + + /// + /// Image Properties + /// + public static class Image + { + + + #region Properties + + /// + /// Name: System.Image.BitDepth -- PKEY_Image_BitDepth + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 7 (PIDISI_BITDEPTH) + /// + public static PropertyKey BitDepth + { + get + { + var key = new PropertyKey(new Guid("{6444048F-4C8B-11D1-8B70-080036B11A03}"), 7); + + return key; + } + } + + /// + /// Name: System.Image.ColorSpace -- PKEY_Image_ColorSpace + /// Description: PropertyTagExifColorSpace + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 40961 + /// + public static PropertyKey ColorSpace + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 40961); + + return key; + } + } + + /// + /// Name: System.Image.CompressedBitsPerPixel -- PKEY_Image_CompressedBitsPerPixel + /// Description: Calculated from PKEY_Image_CompressedBitsPerPixelNumerator and PKEY_Image_CompressedBitsPerPixelDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {364B6FA9-37AB-482A-BE2B-AE02F60D4318}, 100 + /// + public static PropertyKey CompressedBitsPerPixel + { + get + { + var key = new PropertyKey(new Guid("{364B6FA9-37AB-482A-BE2B-AE02F60D4318}"), 100); + + return key; + } + } + + /// + /// Name: System.Image.CompressedBitsPerPixelDenominator -- PKEY_Image_CompressedBitsPerPixelDenominator + /// Description: Denominator of PKEY_Image_CompressedBitsPerPixel. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {1F8844E1-24AD-4508-9DFD-5326A415CE02}, 100 + /// + public static PropertyKey CompressedBitsPerPixelDenominator + { + get + { + var key = new PropertyKey(new Guid("{1F8844E1-24AD-4508-9DFD-5326A415CE02}"), 100); + + return key; + } + } + + /// + /// Name: System.Image.CompressedBitsPerPixelNumerator -- PKEY_Image_CompressedBitsPerPixelNumerator + /// Description: Numerator of PKEY_Image_CompressedBitsPerPixel. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {D21A7148-D32C-4624-8900-277210F79C0F}, 100 + /// + public static PropertyKey CompressedBitsPerPixelNumerator + { + get + { + var key = new PropertyKey(new Guid("{D21A7148-D32C-4624-8900-277210F79C0F}"), 100); + + return key; + } + } + + /// + /// Name: System.Image.Compression -- PKEY_Image_Compression + /// Description: Indicates the image compression level. PropertyTagCompression. + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 259 + /// + public static PropertyKey Compression + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 259); + + return key; + } + } + + /// + /// Name: System.Image.CompressionText -- PKEY_Image_CompressionText + /// Description: This is the user-friendly form of System.Image.Compression. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {3F08E66F-2F44-4BB9-A682-AC35D2562322}, 100 + /// + public static PropertyKey CompressionText + { + get + { + var key = new PropertyKey(new Guid("{3F08E66F-2F44-4BB9-A682-AC35D2562322}"), 100); + + return key; + } + } + + /// + /// Name: System.Image.Dimensions -- PKEY_Image_Dimensions + /// Description: Indicates the dimensions of the image. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 13 (PIDISI_DIMENSIONS) + /// + public static PropertyKey Dimensions + { + get + { + var key = new PropertyKey(new Guid("{6444048F-4C8B-11D1-8B70-080036B11A03}"), 13); + + return key; + } + } + + /// + /// Name: System.Image.HorizontalResolution -- PKEY_Image_HorizontalResolution + /// Description: + /// + /// Type: Double -- VT_R8 + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 5 (PIDISI_RESOLUTIONX) + /// + public static PropertyKey HorizontalResolution + { + get + { + var key = new PropertyKey(new Guid("{6444048F-4C8B-11D1-8B70-080036B11A03}"), 5); + + return key; + } + } + + /// + /// Name: System.Image.HorizontalSize -- PKEY_Image_HorizontalSize + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 3 (PIDISI_CX) + /// + public static PropertyKey HorizontalSize + { + get + { + var key = new PropertyKey(new Guid("{6444048F-4C8B-11D1-8B70-080036B11A03}"), 3); + + return key; + } + } + + /// + /// Name: System.Image.ImageID -- PKEY_Image_ImageID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {10DABE05-32AA-4C29-BF1A-63E2D220587F}, 100 + /// + public static PropertyKey ImageID + { + get + { + var key = new PropertyKey(new Guid("{10DABE05-32AA-4C29-BF1A-63E2D220587F}"), 100); + + return key; + } + } + + /// + /// Name: System.Image.ResolutionUnit -- PKEY_Image_ResolutionUnit + /// Description: + /// Type: Int16 -- VT_I2 + /// FormatID: {19B51FA6-1F92-4A5C-AB48-7DF0ABD67444}, 100 + /// + public static PropertyKey ResolutionUnit + { + get + { + var key = new PropertyKey(new Guid("{19B51FA6-1F92-4A5C-AB48-7DF0ABD67444}"), 100); + + return key; + } + } + + /// + /// Name: System.Image.VerticalResolution -- PKEY_Image_VerticalResolution + /// Description: + /// + /// Type: Double -- VT_R8 + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 6 (PIDISI_RESOLUTIONY) + /// + public static PropertyKey VerticalResolution + { + get + { + var key = new PropertyKey(new Guid("{6444048F-4C8B-11D1-8B70-080036B11A03}"), 6); + + return key; + } + } + + /// + /// Name: System.Image.VerticalSize -- PKEY_Image_VerticalSize + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 4 (PIDISI_CY) + /// + public static PropertyKey VerticalSize + { + get + { + var key = new PropertyKey(new Guid("{6444048F-4C8B-11D1-8B70-080036B11A03}"), 4); + + return key; + } + } + #endregion + + + + } + + /// + /// Journal Properties + /// + public static class Journal + { + + + #region Properties + + /// + /// Name: System.Journal.Contacts -- PKEY_Journal_Contacts + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {DEA7C82C-1D89-4A66-9427-A4E3DEBABCB1}, 100 + /// + public static PropertyKey Contacts + { + get + { + var key = new PropertyKey(new Guid("{DEA7C82C-1D89-4A66-9427-A4E3DEBABCB1}"), 100); + + return key; + } + } + + /// + /// Name: System.Journal.EntryType -- PKEY_Journal_EntryType + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {95BEB1FC-326D-4644-B396-CD3ED90E6DDF}, 100 + /// + public static PropertyKey EntryType + { + get + { + var key = new PropertyKey(new Guid("{95BEB1FC-326D-4644-B396-CD3ED90E6DDF}"), 100); + + return key; + } + } + #endregion + + + + } + + /// + /// LayoutPattern Properties + /// + public static class LayoutPattern + { + + + #region Properties + + /// + /// Name: System.LayoutPattern.ContentViewModeForBrowse -- PKEY_LayoutPattern_ContentViewModeForBrowse + /// Description: Specifies the layout pattern that the content view mode should apply for this item in the context of browsing. + ///Register the regvalue under the name of "ContentViewModeLayoutPatternForBrowse". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 500 + /// + public static PropertyKey ContentViewModeForBrowse + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 500); + + return key; + } + } + + /// + /// Name: System.LayoutPattern.ContentViewModeForSearch -- PKEY_LayoutPattern_ContentViewModeForSearch + /// Description: Specifies the layout pattern that the content view mode should apply for this item in the context of searching. + ///Register the regvalue under the name of "ContentViewModeLayoutPatternForSearch". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 501 + /// + public static PropertyKey ContentViewModeForSearch + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 501); + + return key; + } + } + #endregion + + + + } + + /// + /// Link Properties + /// + public static class Link + { + + + #region Properties + + /// + /// Name: System.Link.Arguments -- PKEY_Link_Arguments + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {436F2667-14E2-4FEB-B30A-146C53B5B674}, 100 + /// + public static PropertyKey Arguments + { + get + { + var key = new PropertyKey(new Guid("{436F2667-14E2-4FEB-B30A-146C53B5B674}"), 100); + + return key; + } + } + + /// + /// Name: System.Link.Comment -- PKEY_Link_Comment + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_LINK) {B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}, 5 + /// + public static PropertyKey Comment + { + get + { + var key = new PropertyKey(new Guid("{B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}"), 5); + + return key; + } + } + + /// + /// Name: System.Link.DateVisited -- PKEY_Link_DateVisited + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {5CBF2787-48CF-4208-B90E-EE5E5D420294}, 23 (PKEYs relating to URLs. Used by IE History.) + /// + public static PropertyKey DateVisited + { + get + { + var key = new PropertyKey(new Guid("{5CBF2787-48CF-4208-B90E-EE5E5D420294}"), 23); + + return key; + } + } + + /// + /// Name: System.Link.Description -- PKEY_Link_Description + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {5CBF2787-48CF-4208-B90E-EE5E5D420294}, 21 (PKEYs relating to URLs. Used by IE History.) + /// + public static PropertyKey Description + { + get + { + var key = new PropertyKey(new Guid("{5CBF2787-48CF-4208-B90E-EE5E5D420294}"), 21); + + return key; + } + } + + /// + /// Name: System.Link.Status -- PKEY_Link_Status + /// Description: + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (PSGUID_LINK) {B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}, 3 (PID_LINK_TARGET_TYPE) + /// + public static PropertyKey Status + { + get + { + var key = new PropertyKey(new Guid("{B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}"), 3); + + return key; + } + } + + /// + /// Name: System.Link.TargetExtension -- PKEY_Link_TargetExtension + /// Description: The file extension of the link target. See System.File.Extension + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {7A7D76F4-B630-4BD7-95FF-37CC51A975C9}, 2 + /// + public static PropertyKey TargetExtension + { + get + { + var key = new PropertyKey(new Guid("{7A7D76F4-B630-4BD7-95FF-37CC51A975C9}"), 2); + + return key; + } + } + + /// + /// Name: System.Link.TargetParsingPath -- PKEY_Link_TargetParsingPath + /// Description: This is the shell namespace path to the target of the link item. This path may be passed to + ///SHParseDisplayName to parse the path to the correct shell folder. + /// + ///If the target item is a file, the value is identical to System.ItemPathDisplay. + /// + ///If the target item cannot be accessed through the shell namespace, this value is VT_EMPTY. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_LINK) {B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}, 2 (PID_LINK_TARGET) + /// + public static PropertyKey TargetParsingPath + { + get + { + var key = new PropertyKey(new Guid("{B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}"), 2); + + return key; + } + } + + /// + /// Name: System.Link.TargetSFGAOFlags -- PKEY_Link_TargetSFGAOFlags + /// Description: IShellFolder::GetAttributesOf flags for the target of a link, with SFGAO_PKEYSFGAOMASK + ///attributes masked out. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_LINK) {B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}, 8 + /// + public static PropertyKey TargetSFGAOFlags + { + get + { + var key = new PropertyKey(new Guid("{B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}"), 8); + + return key; + } + } + + /// + /// Name: System.Link.TargetSFGAOFlagsStrings -- PKEY_Link_TargetSFGAOFlagsStrings + /// Description: Expresses the SFGAO flags of a link as string values and is used as a query optimization. See + ///PKEY_Shell_SFGAOFlagsStrings for possible values of this. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D6942081-D53B-443D-AD47-5E059D9CD27A}, 3 + /// + public static PropertyKey TargetSFGAOFlagsStrings + { + get + { + var key = new PropertyKey(new Guid("{D6942081-D53B-443D-AD47-5E059D9CD27A}"), 3); + + return key; + } + } + + /// + /// Name: System.Link.TargetUrl -- PKEY_Link_TargetUrl + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {5CBF2787-48CF-4208-B90E-EE5E5D420294}, 2 (PKEYs relating to URLs. Used by IE History.) + /// + public static PropertyKey TargetUrl + { + get + { + var key = new PropertyKey(new Guid("{5CBF2787-48CF-4208-B90E-EE5E5D420294}"), 2); + + return key; + } + } + #endregion + + + + } + + /// + /// Media Properties + /// + public static class Media + { + + + #region Properties + + /// + /// Name: System.Media.AuthorUrl -- PKEY_Media_AuthorUrl + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 32 (PIDMSI_AUTHOR_URL) + /// + public static PropertyKey AuthorUrl + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 32); + + return key; + } + } + + /// + /// Name: System.Media.AverageLevel -- PKEY_Media_AverageLevel + /// Description: + /// Type: UInt32 -- VT_UI4 + /// FormatID: {09EDD5B6-B301-43C5-9990-D00302EFFD46}, 100 + /// + public static PropertyKey AverageLevel + { + get + { + var key = new PropertyKey(new Guid("{09EDD5B6-B301-43C5-9990-D00302EFFD46}"), 100); + + return key; + } + } + + /// + /// Name: System.Media.ClassPrimaryID -- PKEY_Media_ClassPrimaryID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 13 (PIDMSI_CLASS_PRIMARY_ID) + /// + public static PropertyKey ClassPrimaryID + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 13); + + return key; + } + } + + /// + /// Name: System.Media.ClassSecondaryID -- PKEY_Media_ClassSecondaryID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 14 (PIDMSI_CLASS_SECONDARY_ID) + /// + public static PropertyKey ClassSecondaryID + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 14); + + return key; + } + } + + /// + /// Name: System.Media.CollectionGroupID -- PKEY_Media_CollectionGroupID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 24 (PIDMSI_COLLECTION_GROUP_ID) + /// + public static PropertyKey CollectionGroupID + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 24); + + return key; + } + } + + /// + /// Name: System.Media.CollectionID -- PKEY_Media_CollectionID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 25 (PIDMSI_COLLECTION_ID) + /// + public static PropertyKey CollectionID + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 25); + + return key; + } + } + + /// + /// Name: System.Media.ContentDistributor -- PKEY_Media_ContentDistributor + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 18 (PIDMSI_CONTENTDISTRIBUTOR) + /// + public static PropertyKey ContentDistributor + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 18); + + return key; + } + } + + /// + /// Name: System.Media.ContentID -- PKEY_Media_ContentID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 26 (PIDMSI_CONTENT_ID) + /// + public static PropertyKey ContentID + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 26); + + return key; + } + } + + /// + /// Name: System.Media.CreatorApplication -- PKEY_Media_CreatorApplication + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 27 (PIDMSI_TOOL_NAME) + /// + public static PropertyKey CreatorApplication + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 27); + + return key; + } + } + + /// + /// Name: System.Media.CreatorApplicationVersion -- PKEY_Media_CreatorApplicationVersion + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 28 (PIDMSI_TOOL_VERSION) + /// + public static PropertyKey CreatorApplicationVersion + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 28); + + return key; + } + } + + /// + /// Name: System.Media.DateEncoded -- PKEY_Media_DateEncoded + /// Description: DateTime is in UTC (in the doc, not file system). + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {2E4B640D-5019-46D8-8881-55414CC5CAA0}, 100 + /// + public static PropertyKey DateEncoded + { + get + { + var key = new PropertyKey(new Guid("{2E4B640D-5019-46D8-8881-55414CC5CAA0}"), 100); + + return key; + } + } + + /// + /// Name: System.Media.DateReleased -- PKEY_Media_DateReleased + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DE41CC29-6971-4290-B472-F59F2E2F31E2}, 100 + /// + public static PropertyKey DateReleased + { + get + { + var key = new PropertyKey(new Guid("{DE41CC29-6971-4290-B472-F59F2E2F31E2}"), 100); + + return key; + } + } + + /// + /// Name: System.Media.Duration -- PKEY_Media_Duration + /// Description: 100ns units, not milliseconds + /// + /// Type: UInt64 -- VT_UI8 + /// FormatID: (FMTID_AudioSummaryInformation) {64440490-4C8B-11D1-8B70-080036B11A03}, 3 (PIDASI_TIMELENGTH) + /// + public static PropertyKey Duration + { + get + { + var key = new PropertyKey(new Guid("{64440490-4C8B-11D1-8B70-080036B11A03}"), 3); + + return key; + } + } + + /// + /// Name: System.Media.DVDID -- PKEY_Media_DVDID + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 15 (PIDMSI_DVDID) + /// + public static PropertyKey DVDID + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 15); + + return key; + } + } + + /// + /// Name: System.Media.EncodedBy -- PKEY_Media_EncodedBy + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 36 (PIDMSI_ENCODED_BY) + /// + public static PropertyKey EncodedBy + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 36); + + return key; + } + } + + /// + /// Name: System.Media.EncodingSettings -- PKEY_Media_EncodingSettings + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 37 (PIDMSI_ENCODING_SETTINGS) + /// + public static PropertyKey EncodingSettings + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 37); + + return key; + } + } + + /// + /// Name: System.Media.FrameCount -- PKEY_Media_FrameCount + /// Description: Indicates the frame count for the image. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (PSGUID_IMAGESUMMARYINFORMATION) {6444048F-4C8B-11D1-8B70-080036B11A03}, 12 (PIDISI_FRAMECOUNT) + /// + public static PropertyKey FrameCount + { + get + { + var key = new PropertyKey(new Guid("{6444048F-4C8B-11D1-8B70-080036B11A03}"), 12); + + return key; + } + } + + /// + /// Name: System.Media.MCDI -- PKEY_Media_MCDI + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 16 (PIDMSI_MCDI) + /// + public static PropertyKey MCDI + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 16); + + return key; + } + } + + /// + /// Name: System.Media.MetadataContentProvider -- PKEY_Media_MetadataContentProvider + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 17 (PIDMSI_PROVIDER) + /// + public static PropertyKey MetadataContentProvider + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 17); + + return key; + } + } + + /// + /// Name: System.Media.Producer -- PKEY_Media_Producer + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 22 (PIDMSI_PRODUCER) + /// + public static PropertyKey Producer + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 22); + + return key; + } + } + + /// + /// Name: System.Media.PromotionUrl -- PKEY_Media_PromotionUrl + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 33 (PIDMSI_PROMOTION_URL) + /// + public static PropertyKey PromotionUrl + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 33); + + return key; + } + } + + /// + /// Name: System.Media.ProtectionType -- PKEY_Media_ProtectionType + /// Description: If media is protected, how is it protected? + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 38 + /// + public static PropertyKey ProtectionType + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 38); + + return key; + } + } + + /// + /// Name: System.Media.ProviderRating -- PKEY_Media_ProviderRating + /// Description: Rating (0 - 99) supplied by metadata provider + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 39 + /// + public static PropertyKey ProviderRating + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 39); + + return key; + } + } + + /// + /// Name: System.Media.ProviderStyle -- PKEY_Media_ProviderStyle + /// Description: Style of music or video, supplied by metadata provider + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 40 + /// + public static PropertyKey ProviderStyle + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 40); + + return key; + } + } + + /// + /// Name: System.Media.Publisher -- PKEY_Media_Publisher + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 30 (PIDMSI_PUBLISHER) + /// + public static PropertyKey Publisher + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 30); + + return key; + } + } + + /// + /// Name: System.Media.SubscriptionContentId -- PKEY_Media_SubscriptionContentId + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {9AEBAE7A-9644-487D-A92C-657585ED751A}, 100 + /// + public static PropertyKey SubscriptionContentId + { + get + { + var key = new PropertyKey(new Guid("{9AEBAE7A-9644-487D-A92C-657585ED751A}"), 100); + + return key; + } + } + + /// + /// Name: System.Media.SubTitle -- PKEY_Media_SubTitle + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 38 (PIDSI_MUSIC_SUB_TITLE) + /// + public static PropertyKey Subtitle + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 38); + + return key; + } + } + + /// + /// Name: System.Media.UniqueFileIdentifier -- PKEY_Media_UniqueFileIdentifier + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 35 (PIDMSI_UNIQUE_FILE_IDENTIFIER) + /// + public static PropertyKey UniqueFileIdentifier + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 35); + + return key; + } + } + + /// + /// Name: System.Media.UserNoAutoInfo -- PKEY_Media_UserNoAutoInfo + /// Description: If true, do NOT alter this file's metadata. Set by user. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 41 + /// + public static PropertyKey UserNoAutoInfo + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 41); + + return key; + } + } + + /// + /// Name: System.Media.UserWebUrl -- PKEY_Media_UserWebUrl + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 34 (PIDMSI_USER_WEB_URL) + /// + public static PropertyKey UserWebUrl + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 34); + + return key; + } + } + + /// + /// Name: System.Media.Writer -- PKEY_Media_Writer + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 23 (PIDMSI_WRITER) + /// + public static PropertyKey Writer + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 23); + + return key; + } + } + + /// + /// Name: System.Media.Year -- PKEY_Media_Year + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 5 (PIDSI_MUSIC_YEAR) + /// + public static PropertyKey Year + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 5); + + return key; + } + } + #endregion + + + + } + + /// + /// Message Properties + /// + public static class Message + { + + + #region Properties + + /// + /// Name: System.Message.AttachmentContents -- PKEY_Message_AttachmentContents + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {3143BF7C-80A8-4854-8880-E2E40189BDD0}, 100 + /// + public static PropertyKey AttachmentContents + { + get + { + var key = new PropertyKey(new Guid("{3143BF7C-80A8-4854-8880-E2E40189BDD0}"), 100); + + return key; + } + } + + /// + /// Name: System.Message.AttachmentNames -- PKEY_Message_AttachmentNames + /// Description: The names of the attachments in a message + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 21 + /// + public static PropertyKey AttachmentNames + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 21); + + return key; + } + } + + /// + /// Name: System.Message.BccAddress -- PKEY_Message_BccAddress + /// Description: Addresses in Bcc: field + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 2 + /// + public static PropertyKey BccAddress + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 2); + + return key; + } + } + + /// + /// Name: System.Message.BccName -- PKEY_Message_BccName + /// Description: person names in Bcc: field + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 3 + /// + public static PropertyKey BccName + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 3); + + return key; + } + } + + /// + /// Name: System.Message.CcAddress -- PKEY_Message_CcAddress + /// Description: Addresses in Cc: field + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 4 + /// + public static PropertyKey CcAddress + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 4); + + return key; + } + } + + /// + /// Name: System.Message.CcName -- PKEY_Message_CcName + /// Description: person names in Cc: field + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 5 + /// + public static PropertyKey CcName + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 5); + + return key; + } + } + + /// + /// Name: System.Message.ConversationID -- PKEY_Message_ConversationID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DC8F80BD-AF1E-4289-85B6-3DFC1B493992}, 100 + /// + public static PropertyKey ConversationID + { + get + { + var key = new PropertyKey(new Guid("{DC8F80BD-AF1E-4289-85B6-3DFC1B493992}"), 100); + + return key; + } + } + + /// + /// Name: System.Message.ConversationIndex -- PKEY_Message_ConversationIndex + /// Description: + /// + /// Type: Buffer -- VT_VECTOR | VT_UI1 (For variants: VT_ARRAY | VT_UI1) + /// FormatID: {DC8F80BD-AF1E-4289-85B6-3DFC1B493992}, 101 + /// + public static PropertyKey ConversationIndex + { + get + { + var key = new PropertyKey(new Guid("{DC8F80BD-AF1E-4289-85B6-3DFC1B493992}"), 101); + + return key; + } + } + + /// + /// Name: System.Message.DateReceived -- PKEY_Message_DateReceived + /// Description: Date and Time communication was received + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 20 + /// + public static PropertyKey DateReceived + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 20); + + return key; + } + } + + /// + /// Name: System.Message.DateSent -- PKEY_Message_DateSent + /// Description: Date and Time communication was sent + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 19 + /// + public static PropertyKey DateSent + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 19); + + return key; + } + } + + /// + /// Name: System.Message.Flags -- PKEY_Message_Flags + /// Description: These are flags associated with email messages to know if a read receipt is pending, etc. + ///The values stored here by Outlook are defined for PR_MESSAGE_FLAGS on MSDN. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {A82D9EE7-CA67-4312-965E-226BCEA85023}, 100 + /// + public static PropertyKey Flags + { + get + { + var key = new PropertyKey(new Guid("{A82D9EE7-CA67-4312-965E-226BCEA85023}"), 100); + + return key; + } + } + + /// + /// Name: System.Message.FromAddress -- PKEY_Message_FromAddress + /// Description: + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 13 + /// + public static PropertyKey FromAddress + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 13); + + return key; + } + } + + /// + /// Name: System.Message.FromName -- PKEY_Message_FromName + /// Description: Address in from field as person name + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 14 + /// + public static PropertyKey FromName + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 14); + + return key; + } + } + + /// + /// Name: System.Message.HasAttachments -- PKEY_Message_HasAttachments + /// Description: + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {9C1FCF74-2D97-41BA-B4AE-CB2E3661A6E4}, 8 + /// + public static PropertyKey HasAttachments + { + get + { + var key = new PropertyKey(new Guid("{9C1FCF74-2D97-41BA-B4AE-CB2E3661A6E4}"), 8); + + return key; + } + } + + /// + /// Name: System.Message.IsFwdOrReply -- PKEY_Message_IsFwdOrReply + /// Description: + /// Type: Int32 -- VT_I4 + /// FormatID: {9A9BC088-4F6D-469E-9919-E705412040F9}, 100 + /// + public static PropertyKey IsFwdOrReply + { + get + { + var key = new PropertyKey(new Guid("{9A9BC088-4F6D-469E-9919-E705412040F9}"), 100); + + return key; + } + } + + /// + /// Name: System.Message.MessageClass -- PKEY_Message_MessageClass + /// Description: What type of outlook msg this is (meeting, task, mail, etc.) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CD9ED458-08CE-418F-A70E-F912C7BB9C5C}, 103 + /// + public static PropertyKey MessageClass + { + get + { + var key = new PropertyKey(new Guid("{CD9ED458-08CE-418F-A70E-F912C7BB9C5C}"), 103); + + return key; + } + } + + /// + /// Name: System.Message.ProofInProgress -- PKEY_Message_ProofInProgress + /// Description: This property will be true if the message junk email proofing is still in progress. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {9098F33C-9A7D-48A8-8DE5-2E1227A64E91}, 100 + /// + public static PropertyKey ProofInProgress + { + get + { + var key = new PropertyKey(new Guid("{9098F33C-9A7D-48A8-8DE5-2E1227A64E91}"), 100); + + return key; + } + } + + /// + /// Name: System.Message.SenderAddress -- PKEY_Message_SenderAddress + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0BE1C8E7-1981-4676-AE14-FDD78F05A6E7}, 100 + /// + public static PropertyKey SenderAddress + { + get + { + var key = new PropertyKey(new Guid("{0BE1C8E7-1981-4676-AE14-FDD78F05A6E7}"), 100); + + return key; + } + } + + /// + /// Name: System.Message.SenderName -- PKEY_Message_SenderName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0DA41CFA-D224-4A18-AE2F-596158DB4B3A}, 100 + /// + public static PropertyKey SenderName + { + get + { + var key = new PropertyKey(new Guid("{0DA41CFA-D224-4A18-AE2F-596158DB4B3A}"), 100); + + return key; + } + } + + /// + /// Name: System.Message.Store -- PKEY_Message_Store + /// Description: The store (aka protocol handler) FILE, MAIL, OUTLOOKEXPRESS + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 15 + /// + public static PropertyKey Store + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 15); + + return key; + } + } + + /// + /// Name: System.Message.ToAddress -- PKEY_Message_ToAddress + /// Description: Addresses in To: field + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 16 + /// + public static PropertyKey ToAddress + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 16); + + return key; + } + } + + /// + /// Name: System.Message.ToDoFlags -- PKEY_Message_ToDoFlags + /// Description: Flags associated with a message flagged to know if it's still active, if it was custom flagged, etc. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {1F856A9F-6900-4ABA-9505-2D5F1B4D66CB}, 100 + /// + public static PropertyKey ToDoFlags + { + get + { + var key = new PropertyKey(new Guid("{1F856A9F-6900-4ABA-9505-2D5F1B4D66CB}"), 100); + + return key; + } + } + + /// + /// Name: System.Message.ToDoTitle -- PKEY_Message_ToDoTitle + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {BCCC8A3C-8CEF-42E5-9B1C-C69079398BC7}, 100 + /// + public static PropertyKey ToDoTitle + { + get + { + var key = new PropertyKey(new Guid("{BCCC8A3C-8CEF-42E5-9B1C-C69079398BC7}"), 100); + + return key; + } + } + + /// + /// Name: System.Message.ToName -- PKEY_Message_ToName + /// Description: Person names in To: field + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}, 17 + /// + public static PropertyKey ToName + { + get + { + var key = new PropertyKey(new Guid("{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD}"), 17); + + return key; + } + } + #endregion + + + + } + + /// + /// Music Properties + /// + public static class Music + { + + + #region Properties + + /// + /// Name: System.Music.AlbumArtist -- PKEY_Music_AlbumArtist + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 13 (PIDSI_MUSIC_ALBUM_ARTIST) + /// + public static PropertyKey AlbumArtist + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 13); + + return key; + } + } + + /// + /// Name: System.Music.AlbumID -- PKEY_Music_AlbumID + /// Description: Concatenation of System.Music.AlbumArtist and System.Music.AlbumTitle, suitable for indexing and display. + ///Used to differentiate albums with the same title from different artists. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 100 + /// + public static PropertyKey AlbumID + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 100); + + return key; + } + } + + /// + /// Name: System.Music.AlbumTitle -- PKEY_Music_AlbumTitle + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 4 (PIDSI_MUSIC_ALBUM) + /// + public static PropertyKey AlbumTitle + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 4); + + return key; + } + } + + /// + /// Name: System.Music.Artist -- PKEY_Music_Artist + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 2 (PIDSI_MUSIC_ARTIST) + /// + public static PropertyKey Artist + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 2); + + return key; + } + } + + /// + /// Name: System.Music.BeatsPerMinute -- PKEY_Music_BeatsPerMinute + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 35 (PIDSI_MUSIC_BEATS_PER_MINUTE) + /// + public static PropertyKey BeatsPerMinute + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 35); + + return key; + } + } + + /// + /// Name: System.Music.Composer -- PKEY_Music_Composer + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 19 (PIDMSI_COMPOSER) + /// + public static PropertyKey Composer + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 19); + + return key; + } + } + + /// + /// Name: System.Music.Conductor -- PKEY_Music_Conductor + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 36 (PIDSI_MUSIC_CONDUCTOR) + /// + public static PropertyKey Conductor + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 36); + + return key; + } + } + + /// + /// Name: System.Music.ContentGroupDescription -- PKEY_Music_ContentGroupDescription + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 33 (PIDSI_MUSIC_CONTENT_GROUP_DESCRIPTION) + /// + public static PropertyKey ContentGroupDescription + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 33); + + return key; + } + } + + /// + /// Name: System.Music.DisplayArtist -- PKEY_Music_DisplayArtist + /// Description: This property returns the best representation of Album Artist for a given music file + ///based upon AlbumArtist, ContributingArtist and compilation info. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FD122953-FA93-4EF7-92C3-04C946B2F7C8}, 100 + /// + public static PropertyKey DisplayArtist + { + get + { + var key = new PropertyKey(new Guid("{FD122953-FA93-4EF7-92C3-04C946B2F7C8}"), 100); + + return key; + } + } + + /// + /// Name: System.Music.Genre -- PKEY_Music_Genre + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 11 (PIDSI_MUSIC_GENRE) + /// + public static PropertyKey Genre + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 11); + + return key; + } + } + + /// + /// Name: System.Music.InitialKey -- PKEY_Music_InitialKey + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 34 (PIDSI_MUSIC_INITIAL_KEY) + /// + public static PropertyKey InitialKey + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 34); + + return key; + } + } + + /// + /// Name: System.Music.IsCompilation -- PKEY_Music_IsCompilation + /// Description: Indicates whether the file is part of a compilation. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {C449D5CB-9EA4-4809-82E8-AF9D59DED6D1}, 100 + /// + public static PropertyKey IsCompilation + { + get + { + var key = new PropertyKey(new Guid("{C449D5CB-9EA4-4809-82E8-AF9D59DED6D1}"), 100); + + return key; + } + } + + /// + /// Name: System.Music.Lyrics -- PKEY_Music_Lyrics + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 12 (PIDSI_MUSIC_LYRICS) + /// + public static PropertyKey Lyrics + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 12); + + return key; + } + } + + /// + /// Name: System.Music.Mood -- PKEY_Music_Mood + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 39 (PIDSI_MUSIC_MOOD) + /// + public static PropertyKey Mood + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 39); + + return key; + } + } + + /// + /// Name: System.Music.PartOfSet -- PKEY_Music_PartOfSet + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 37 (PIDSI_MUSIC_PART_OF_SET) + /// + public static PropertyKey PartOfSet + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 37); + + return key; + } + } + + /// + /// Name: System.Music.Period -- PKEY_Music_Period + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 31 (PIDMSI_PERIOD) + /// + public static PropertyKey Period + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 31); + + return key; + } + } + + /// + /// Name: System.Music.SynchronizedLyrics -- PKEY_Music_SynchronizedLyrics + /// Description: + /// Type: Blob -- VT_BLOB + /// FormatID: {6B223B6A-162E-4AA9-B39F-05D678FC6D77}, 100 + /// + public static PropertyKey SynchronizedLyrics + { + get + { + var key = new PropertyKey(new Guid("{6B223B6A-162E-4AA9-B39F-05D678FC6D77}"), 100); + + return key; + } + } + + /// + /// Name: System.Music.TrackNumber -- PKEY_Music_TrackNumber + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_MUSIC) {56A3372E-CE9C-11D2-9F0E-006097C686F6}, 7 (PIDSI_MUSIC_TRACK) + /// + public static PropertyKey TrackNumber + { + get + { + var key = new PropertyKey(new Guid("{56A3372E-CE9C-11D2-9F0E-006097C686F6}"), 7); + + return key; + } + } + #endregion + + + + } + + /// + /// Note Properties + /// + public static class Note + { + + + #region Properties + + /// + /// Name: System.Note.Color -- PKEY_Note_Color + /// Description: + /// Type: UInt16 -- VT_UI2 + /// FormatID: {4776CAFA-BCE4-4CB1-A23E-265E76D8EB11}, 100 + /// + public static PropertyKey Color + { + get + { + var key = new PropertyKey(new Guid("{4776CAFA-BCE4-4CB1-A23E-265E76D8EB11}"), 100); + + return key; + } + } + + /// + /// Name: System.Note.ColorText -- PKEY_Note_ColorText + /// Description: This is the user-friendly form of System.Note.Color. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {46B4E8DE-CDB2-440D-885C-1658EB65B914}, 100 + /// + public static PropertyKey ColorText + { + get + { + var key = new PropertyKey(new Guid("{46B4E8DE-CDB2-440D-885C-1658EB65B914}"), 100); + + return key; + } + } + #endregion + + + + } + + /// + /// Photo Properties + /// + public static class Photo + { + + + #region Properties + + /// + /// Name: System.Photo.Aperture -- PKEY_Photo_Aperture + /// Description: PropertyTagExifAperture. Calculated from PKEY_Photo_ApertureNumerator and PKEY_Photo_ApertureDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37378 + /// + public static PropertyKey Aperture + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 37378); + + return key; + } + } + + /// + /// Name: System.Photo.ApertureDenominator -- PKEY_Photo_ApertureDenominator + /// Description: Denominator of PKEY_Photo_Aperture + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {E1A9A38B-6685-46BD-875E-570DC7AD7320}, 100 + /// + public static PropertyKey ApertureDenominator + { + get + { + var key = new PropertyKey(new Guid("{E1A9A38B-6685-46BD-875E-570DC7AD7320}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ApertureNumerator -- PKEY_Photo_ApertureNumerator + /// Description: Numerator of PKEY_Photo_Aperture + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {0337ECEC-39FB-4581-A0BD-4C4CC51E9914}, 100 + /// + public static PropertyKey ApertureNumerator + { + get + { + var key = new PropertyKey(new Guid("{0337ECEC-39FB-4581-A0BD-4C4CC51E9914}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.Brightness -- PKEY_Photo_Brightness + /// Description: This is the brightness of the photo. + /// + ///Calculated from PKEY_Photo_BrightnessNumerator and PKEY_Photo_BrightnessDenominator. + /// + ///The units are "APEX", normally in the range of -99.99 to 99.99. If the numerator of + ///the recorded value is FFFFFFFF.H, "Unknown" should be indicated. + /// + /// Type: Double -- VT_R8 + /// FormatID: {1A701BF6-478C-4361-83AB-3701BB053C58}, 100 (PropertyTagExifBrightness) + /// + public static PropertyKey Brightness + { + get + { + var key = new PropertyKey(new Guid("{1A701BF6-478C-4361-83AB-3701BB053C58}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.BrightnessDenominator -- PKEY_Photo_BrightnessDenominator + /// Description: Denominator of PKEY_Photo_Brightness + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {6EBE6946-2321-440A-90F0-C043EFD32476}, 100 + /// + public static PropertyKey BrightnessDenominator + { + get + { + var key = new PropertyKey(new Guid("{6EBE6946-2321-440A-90F0-C043EFD32476}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.BrightnessNumerator -- PKEY_Photo_BrightnessNumerator + /// Description: Numerator of PKEY_Photo_Brightness + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {9E7D118F-B314-45A0-8CFB-D654B917C9E9}, 100 + /// + public static PropertyKey BrightnessNumerator + { + get + { + var key = new PropertyKey(new Guid("{9E7D118F-B314-45A0-8CFB-D654B917C9E9}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.CameraManufacturer -- PKEY_Photo_CameraManufacturer + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 271 (PropertyTagEquipMake) + /// + public static PropertyKey CameraManufacturer + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 271); + + return key; + } + } + + /// + /// Name: System.Photo.CameraModel -- PKEY_Photo_CameraModel + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 272 (PropertyTagEquipModel) + /// + public static PropertyKey CameraModel + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 272); + + return key; + } + } + + /// + /// Name: System.Photo.CameraSerialNumber -- PKEY_Photo_CameraSerialNumber + /// Description: Serial number of camera that produced this photo + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 273 + /// + public static PropertyKey CameraSerialNumber + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 273); + + return key; + } + } + + /// + /// Name: System.Photo.Contrast -- PKEY_Photo_Contrast + /// Description: This indicates the direction of contrast processing applied by the camera + ///when the image was shot. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {2A785BA9-8D23-4DED-82E6-60A350C86A10}, 100 + /// + public static PropertyKey Contrast + { + get + { + var key = new PropertyKey(new Guid("{2A785BA9-8D23-4DED-82E6-60A350C86A10}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ContrastText -- PKEY_Photo_ContrastText + /// Description: This is the user-friendly form of System.Photo.Contrast. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {59DDE9F2-5253-40EA-9A8B-479E96C6249A}, 100 + /// + public static PropertyKey ContrastText + { + get + { + var key = new PropertyKey(new Guid("{59DDE9F2-5253-40EA-9A8B-479E96C6249A}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.DateTaken -- PKEY_Photo_DateTaken + /// Description: PropertyTagExifDTOrig + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 36867 + /// + public static PropertyKey DateTaken + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 36867); + + return key; + } + } + + /// + /// Name: System.Photo.DigitalZoom -- PKEY_Photo_DigitalZoom + /// Description: PropertyTagExifDigitalZoom. Calculated from PKEY_Photo_DigitalZoomNumerator and PKEY_Photo_DigitalZoomDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: {F85BF840-A925-4BC2-B0C4-8E36B598679E}, 100 + /// + public static PropertyKey DigitalZoom + { + get + { + var key = new PropertyKey(new Guid("{F85BF840-A925-4BC2-B0C4-8E36B598679E}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.DigitalZoomDenominator -- PKEY_Photo_DigitalZoomDenominator + /// Description: Denominator of PKEY_Photo_DigitalZoom + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {745BAF0E-E5C1-4CFB-8A1B-D031A0A52393}, 100 + /// + public static PropertyKey DigitalZoomDenominator + { + get + { + var key = new PropertyKey(new Guid("{745BAF0E-E5C1-4CFB-8A1B-D031A0A52393}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.DigitalZoomNumerator -- PKEY_Photo_DigitalZoomNumerator + /// Description: Numerator of PKEY_Photo_DigitalZoom + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {16CBB924-6500-473B-A5BE-F1599BCBE413}, 100 + /// + public static PropertyKey DigitalZoomNumerator + { + get + { + var key = new PropertyKey(new Guid("{16CBB924-6500-473B-A5BE-F1599BCBE413}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.Event -- PKEY_Photo_Event + /// Description: The event at which the photo was taken + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 18248 + /// + public static PropertyKey Event + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 18248); + + return key; + } + } + + /// + /// Name: System.Photo.EXIFVersion -- PKEY_Photo_EXIFVersion + /// Description: The EXIF version. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D35F743A-EB2E-47F2-A286-844132CB1427}, 100 + /// + public static PropertyKey EXIFVersion + { + get + { + var key = new PropertyKey(new Guid("{D35F743A-EB2E-47F2-A286-844132CB1427}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ExposureBias -- PKEY_Photo_ExposureBias + /// Description: PropertyTagExifExposureBias. Calculated from PKEY_Photo_ExposureBiasNumerator and PKEY_Photo_ExposureBiasDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37380 + /// + public static PropertyKey ExposureBias + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 37380); + + return key; + } + } + + /// + /// Name: System.Photo.ExposureBiasDenominator -- PKEY_Photo_ExposureBiasDenominator + /// Description: Denominator of PKEY_Photo_ExposureBias + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {AB205E50-04B7-461C-A18C-2F233836E627}, 100 + /// + public static PropertyKey ExposureBiasDenominator + { + get + { + var key = new PropertyKey(new Guid("{AB205E50-04B7-461C-A18C-2F233836E627}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ExposureBiasNumerator -- PKEY_Photo_ExposureBiasNumerator + /// Description: Numerator of PKEY_Photo_ExposureBias + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {738BF284-1D87-420B-92CF-5834BF6EF9ED}, 100 + /// + public static PropertyKey ExposureBiasNumerator + { + get + { + var key = new PropertyKey(new Guid("{738BF284-1D87-420B-92CF-5834BF6EF9ED}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ExposureIndex -- PKEY_Photo_ExposureIndex + /// Description: PropertyTagExifExposureIndex. Calculated from PKEY_Photo_ExposureIndexNumerator and PKEY_Photo_ExposureIndexDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: {967B5AF8-995A-46ED-9E11-35B3C5B9782D}, 100 + /// + public static PropertyKey ExposureIndex + { + get + { + var key = new PropertyKey(new Guid("{967B5AF8-995A-46ED-9E11-35B3C5B9782D}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ExposureIndexDenominator -- PKEY_Photo_ExposureIndexDenominator + /// Description: Denominator of PKEY_Photo_ExposureIndex + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {93112F89-C28B-492F-8A9D-4BE2062CEE8A}, 100 + /// + public static PropertyKey ExposureIndexDenominator + { + get + { + var key = new PropertyKey(new Guid("{93112F89-C28B-492F-8A9D-4BE2062CEE8A}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ExposureIndexNumerator -- PKEY_Photo_ExposureIndexNumerator + /// Description: Numerator of PKEY_Photo_ExposureIndex + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {CDEDCF30-8919-44DF-8F4C-4EB2FFDB8D89}, 100 + /// + public static PropertyKey ExposureIndexNumerator + { + get + { + var key = new PropertyKey(new Guid("{CDEDCF30-8919-44DF-8F4C-4EB2FFDB8D89}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ExposureProgram -- PKEY_Photo_ExposureProgram + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 34850 (PropertyTagExifExposureProg) + /// + public static PropertyKey ExposureProgram + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 34850); + + return key; + } + } + + /// + /// Name: System.Photo.ExposureProgramText -- PKEY_Photo_ExposureProgramText + /// Description: This is the user-friendly form of System.Photo.ExposureProgram. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FEC690B7-5F30-4646-AE47-4CAAFBA884A3}, 100 + /// + public static PropertyKey ExposureProgramText + { + get + { + var key = new PropertyKey(new Guid("{FEC690B7-5F30-4646-AE47-4CAAFBA884A3}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ExposureTime -- PKEY_Photo_ExposureTime + /// Description: PropertyTagExifExposureTime. Calculated from PKEY_Photo_ExposureTimeNumerator and PKEY_Photo_ExposureTimeDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 33434 + /// + public static PropertyKey ExposureTime + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 33434); + + return key; + } + } + + /// + /// Name: System.Photo.ExposureTimeDenominator -- PKEY_Photo_ExposureTimeDenominator + /// Description: Denominator of PKEY_Photo_ExposureTime + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {55E98597-AD16-42E0-B624-21599A199838}, 100 + /// + public static PropertyKey ExposureTimeDenominator + { + get + { + var key = new PropertyKey(new Guid("{55E98597-AD16-42E0-B624-21599A199838}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ExposureTimeNumerator -- PKEY_Photo_ExposureTimeNumerator + /// Description: Numerator of PKEY_Photo_ExposureTime + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {257E44E2-9031-4323-AC38-85C552871B2E}, 100 + /// + public static PropertyKey ExposureTimeNumerator + { + get + { + var key = new PropertyKey(new Guid("{257E44E2-9031-4323-AC38-85C552871B2E}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.Flash -- PKEY_Photo_Flash + /// Description: PropertyTagExifFlash + /// + /// Type: Byte -- VT_UI1 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37385 + /// + public static PropertyKey Flash + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 37385); + + return key; + } + } + + /// + /// Name: System.Photo.FlashEnergy -- PKEY_Photo_FlashEnergy + /// Description: PropertyTagExifFlashEnergy. Calculated from PKEY_Photo_FlashEnergyNumerator and PKEY_Photo_FlashEnergyDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 41483 + /// + public static PropertyKey FlashEnergy + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 41483); + + return key; + } + } + + /// + /// Name: System.Photo.FlashEnergyDenominator -- PKEY_Photo_FlashEnergyDenominator + /// Description: Denominator of PKEY_Photo_FlashEnergy + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {D7B61C70-6323-49CD-A5FC-C84277162C97}, 100 + /// + public static PropertyKey FlashEnergyDenominator + { + get + { + var key = new PropertyKey(new Guid("{D7B61C70-6323-49CD-A5FC-C84277162C97}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FlashEnergyNumerator -- PKEY_Photo_FlashEnergyNumerator + /// Description: Numerator of PKEY_Photo_FlashEnergy + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {FCAD3D3D-0858-400F-AAA3-2F66CCE2A6BC}, 100 + /// + public static PropertyKey FlashEnergyNumerator + { + get + { + var key = new PropertyKey(new Guid("{FCAD3D3D-0858-400F-AAA3-2F66CCE2A6BC}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FlashManufacturer -- PKEY_Photo_FlashManufacturer + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {AABAF6C9-E0C5-4719-8585-57B103E584FE}, 100 + /// + public static PropertyKey FlashManufacturer + { + get + { + var key = new PropertyKey(new Guid("{AABAF6C9-E0C5-4719-8585-57B103E584FE}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FlashModel -- PKEY_Photo_FlashModel + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {FE83BB35-4D1A-42E2-916B-06F3E1AF719E}, 100 + /// + public static PropertyKey FlashModel + { + get + { + var key = new PropertyKey(new Guid("{FE83BB35-4D1A-42E2-916B-06F3E1AF719E}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FlashText -- PKEY_Photo_FlashText + /// Description: This is the user-friendly form of System.Photo.Flash. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6B8B68F6-200B-47EA-8D25-D8050F57339F}, 100 + /// + public static PropertyKey FlashText + { + get + { + var key = new PropertyKey(new Guid("{6B8B68F6-200B-47EA-8D25-D8050F57339F}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FNumber -- PKEY_Photo_FNumber + /// Description: PropertyTagExifFNumber. Calculated from PKEY_Photo_FNumberNumerator and PKEY_Photo_FNumberDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 33437 + /// + public static PropertyKey FNumber + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 33437); + + return key; + } + } + + /// + /// Name: System.Photo.FNumberDenominator -- PKEY_Photo_FNumberDenominator + /// Description: Denominator of PKEY_Photo_FNumber + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {E92A2496-223B-4463-A4E3-30EABBA79D80}, 100 + /// + public static PropertyKey FNumberDenominator + { + get + { + var key = new PropertyKey(new Guid("{E92A2496-223B-4463-A4E3-30EABBA79D80}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FNumberNumerator -- PKEY_Photo_FNumberNumerator + /// Description: Numerator of PKEY_Photo_FNumber + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {1B97738A-FDFC-462F-9D93-1957E08BE90C}, 100 + /// + public static PropertyKey FNumberNumerator + { + get + { + var key = new PropertyKey(new Guid("{1B97738A-FDFC-462F-9D93-1957E08BE90C}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FocalLength -- PKEY_Photo_FocalLength + /// Description: PropertyTagExifFocalLength. Calculated from PKEY_Photo_FocalLengthNumerator and PKEY_Photo_FocalLengthDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37386 + /// + public static PropertyKey FocalLength + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 37386); + + return key; + } + } + + /// + /// Name: System.Photo.FocalLengthDenominator -- PKEY_Photo_FocalLengthDenominator + /// Description: Denominator of PKEY_Photo_FocalLength + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {305BC615-DCA1-44A5-9FD4-10C0BA79412E}, 100 + /// + public static PropertyKey FocalLengthDenominator + { + get + { + var key = new PropertyKey(new Guid("{305BC615-DCA1-44A5-9FD4-10C0BA79412E}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FocalLengthInFilm -- PKEY_Photo_FocalLengthInFilm + /// Description: + /// Type: UInt16 -- VT_UI2 + /// FormatID: {A0E74609-B84D-4F49-B860-462BD9971F98}, 100 + /// + public static PropertyKey FocalLengthInFilm + { + get + { + var key = new PropertyKey(new Guid("{A0E74609-B84D-4F49-B860-462BD9971F98}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FocalLengthNumerator -- PKEY_Photo_FocalLengthNumerator + /// Description: Numerator of PKEY_Photo_FocalLength + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {776B6B3B-1E3D-4B0C-9A0E-8FBAF2A8492A}, 100 + /// + public static PropertyKey FocalLengthNumerator + { + get + { + var key = new PropertyKey(new Guid("{776B6B3B-1E3D-4B0C-9A0E-8FBAF2A8492A}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FocalPlaneXResolution -- PKEY_Photo_FocalPlaneXResolution + /// Description: PropertyTagExifFocalXRes. Calculated from PKEY_Photo_FocalPlaneXResolutionNumerator and + ///PKEY_Photo_FocalPlaneXResolutionDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {CFC08D97-C6F7-4484-89DD-EBEF4356FE76}, 100 + /// + public static PropertyKey FocalPlaneXResolution + { + get + { + var key = new PropertyKey(new Guid("{CFC08D97-C6F7-4484-89DD-EBEF4356FE76}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FocalPlaneXResolutionDenominator -- PKEY_Photo_FocalPlaneXResolutionDenominator + /// Description: Denominator of PKEY_Photo_FocalPlaneXResolution + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {0933F3F5-4786-4F46-A8E8-D64DD37FA521}, 100 + /// + public static PropertyKey FocalPlaneXResolutionDenominator + { + get + { + var key = new PropertyKey(new Guid("{0933F3F5-4786-4F46-A8E8-D64DD37FA521}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FocalPlaneXResolutionNumerator -- PKEY_Photo_FocalPlaneXResolutionNumerator + /// Description: Numerator of PKEY_Photo_FocalPlaneXResolution + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {DCCB10AF-B4E2-4B88-95F9-031B4D5AB490}, 100 + /// + public static PropertyKey FocalPlaneXResolutionNumerator + { + get + { + var key = new PropertyKey(new Guid("{DCCB10AF-B4E2-4B88-95F9-031B4D5AB490}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FocalPlaneYResolution -- PKEY_Photo_FocalPlaneYResolution + /// Description: PropertyTagExifFocalYRes. Calculated from PKEY_Photo_FocalPlaneYResolutionNumerator and + ///PKEY_Photo_FocalPlaneYResolutionDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {4FFFE4D0-914F-4AC4-8D6F-C9C61DE169B1}, 100 + /// + public static PropertyKey FocalPlaneYResolution + { + get + { + var key = new PropertyKey(new Guid("{4FFFE4D0-914F-4AC4-8D6F-C9C61DE169B1}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FocalPlaneYResolutionDenominator -- PKEY_Photo_FocalPlaneYResolutionDenominator + /// Description: Denominator of PKEY_Photo_FocalPlaneYResolution + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {1D6179A6-A876-4031-B013-3347B2B64DC8}, 100 + /// + public static PropertyKey FocalPlaneYResolutionDenominator + { + get + { + var key = new PropertyKey(new Guid("{1D6179A6-A876-4031-B013-3347B2B64DC8}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.FocalPlaneYResolutionNumerator -- PKEY_Photo_FocalPlaneYResolutionNumerator + /// Description: Numerator of PKEY_Photo_FocalPlaneYResolution + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {A2E541C5-4440-4BA8-867E-75CFC06828CD}, 100 + /// + public static PropertyKey FocalPlaneYResolutionNumerator + { + get + { + var key = new PropertyKey(new Guid("{A2E541C5-4440-4BA8-867E-75CFC06828CD}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.GainControl -- PKEY_Photo_GainControl + /// Description: This indicates the degree of overall image gain adjustment. + /// + ///Calculated from PKEY_Photo_GainControlNumerator and PKEY_Photo_GainControlDenominator. + /// + /// Type: Double -- VT_R8 + /// FormatID: {FA304789-00C7-4D80-904A-1E4DCC7265AA}, 100 (PropertyTagExifGainControl) + /// + public static PropertyKey GainControl + { + get + { + var key = new PropertyKey(new Guid("{FA304789-00C7-4D80-904A-1E4DCC7265AA}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.GainControlDenominator -- PKEY_Photo_GainControlDenominator + /// Description: Denominator of PKEY_Photo_GainControl + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {42864DFD-9DA4-4F77-BDED-4AAD7B256735}, 100 + /// + public static PropertyKey GainControlDenominator + { + get + { + var key = new PropertyKey(new Guid("{42864DFD-9DA4-4F77-BDED-4AAD7B256735}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.GainControlNumerator -- PKEY_Photo_GainControlNumerator + /// Description: Numerator of PKEY_Photo_GainControl + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {8E8ECF7C-B7B8-4EB8-A63F-0EE715C96F9E}, 100 + /// + public static PropertyKey GainControlNumerator + { + get + { + var key = new PropertyKey(new Guid("{8E8ECF7C-B7B8-4EB8-A63F-0EE715C96F9E}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.GainControlText -- PKEY_Photo_GainControlText + /// Description: This is the user-friendly form of System.Photo.GainControl. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C06238B2-0BF9-4279-A723-25856715CB9D}, 100 + /// + public static PropertyKey GainControlText + { + get + { + var key = new PropertyKey(new Guid("{C06238B2-0BF9-4279-A723-25856715CB9D}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ISOSpeed -- PKEY_Photo_ISOSpeed + /// Description: PropertyTagExifISOSpeed + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 34855 + /// + public static PropertyKey ISOSpeed + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 34855); + + return key; + } + } + + /// + /// Name: System.Photo.LensManufacturer -- PKEY_Photo_LensManufacturer + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E6DDCAF7-29C5-4F0A-9A68-D19412EC7090}, 100 + /// + public static PropertyKey LensManufacturer + { + get + { + var key = new PropertyKey(new Guid("{E6DDCAF7-29C5-4F0A-9A68-D19412EC7090}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.LensModel -- PKEY_Photo_LensModel + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {E1277516-2B5F-4869-89B1-2E585BD38B7A}, 100 + /// + public static PropertyKey LensModel + { + get + { + var key = new PropertyKey(new Guid("{E1277516-2B5F-4869-89B1-2E585BD38B7A}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.LightSource -- PKEY_Photo_LightSource + /// Description: PropertyTagExifLightSource + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37384 + /// + public static PropertyKey LightSource + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 37384); + + return key; + } + } + + /// + /// Name: System.Photo.MakerNote -- PKEY_Photo_MakerNote + /// Description: + /// Type: Buffer -- VT_VECTOR | VT_UI1 (For variants: VT_ARRAY | VT_UI1) + /// FormatID: {FA303353-B659-4052-85E9-BCAC79549B84}, 100 + /// + public static PropertyKey MakerNote + { + get + { + var key = new PropertyKey(new Guid("{FA303353-B659-4052-85E9-BCAC79549B84}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.MakerNoteOffset -- PKEY_Photo_MakerNoteOffset + /// Description: + /// Type: UInt64 -- VT_UI8 + /// FormatID: {813F4124-34E6-4D17-AB3E-6B1F3C2247A1}, 100 + /// + public static PropertyKey MakerNoteOffset + { + get + { + var key = new PropertyKey(new Guid("{813F4124-34E6-4D17-AB3E-6B1F3C2247A1}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.MaxAperture -- PKEY_Photo_MaxAperture + /// Description: Calculated from PKEY_Photo_MaxApertureNumerator and PKEY_Photo_MaxApertureDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: {08F6D7C2-E3F2-44FC-AF1E-5AA5C81A2D3E}, 100 + /// + public static PropertyKey MaxAperture + { + get + { + var key = new PropertyKey(new Guid("{08F6D7C2-E3F2-44FC-AF1E-5AA5C81A2D3E}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.MaxApertureDenominator -- PKEY_Photo_MaxApertureDenominator + /// Description: Denominator of PKEY_Photo_MaxAperture + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {C77724D4-601F-46C5-9B89-C53F93BCEB77}, 100 + /// + public static PropertyKey MaxApertureDenominator + { + get + { + var key = new PropertyKey(new Guid("{C77724D4-601F-46C5-9B89-C53F93BCEB77}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.MaxApertureNumerator -- PKEY_Photo_MaxApertureNumerator + /// Description: Numerator of PKEY_Photo_MaxAperture + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {C107E191-A459-44C5-9AE6-B952AD4B906D}, 100 + /// + public static PropertyKey MaxApertureNumerator + { + get + { + var key = new PropertyKey(new Guid("{C107E191-A459-44C5-9AE6-B952AD4B906D}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.MeteringMode -- PKEY_Photo_MeteringMode + /// Description: PropertyTagExifMeteringMode + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37383 + /// + public static PropertyKey MeteringMode + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 37383); + + return key; + } + } + + /// + /// Name: System.Photo.MeteringModeText -- PKEY_Photo_MeteringModeText + /// Description: This is the user-friendly form of System.Photo.MeteringMode. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {F628FD8C-7BA8-465A-A65B-C5AA79263A9E}, 100 + /// + public static PropertyKey MeteringModeText + { + get + { + var key = new PropertyKey(new Guid("{F628FD8C-7BA8-465A-A65B-C5AA79263A9E}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.Orientation -- PKEY_Photo_Orientation + /// Description: This is the image orientation viewed in terms of rows and columns. + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 274 (PropertyTagOrientation) + /// + public static PropertyKey Orientation + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 274); + + return key; + } + } + + /// + /// Name: System.Photo.OrientationText -- PKEY_Photo_OrientationText + /// Description: This is the user-friendly form of System.Photo.Orientation. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A9EA193C-C511-498A-A06B-58E2776DCC28}, 100 + /// + public static PropertyKey OrientationText + { + get + { + var key = new PropertyKey(new Guid("{A9EA193C-C511-498A-A06B-58E2776DCC28}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.PeopleNames -- PKEY_Photo_PeopleNames + /// Description: The people tags on an image. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: {E8309B6E-084C-49B4-B1FC-90A80331B638}, 100 + /// + public static PropertyKey PeopleNames + { + get + { + var key = new PropertyKey(new Guid("{E8309B6E-084C-49B4-B1FC-90A80331B638}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.PhotometricInterpretation -- PKEY_Photo_PhotometricInterpretation + /// Description: This is the pixel composition. In JPEG compressed data, a JPEG marker is used + ///instead of this property. + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: {341796F1-1DF9-4B1C-A564-91BDEFA43877}, 100 + /// + public static PropertyKey PhotometricInterpretation + { + get + { + var key = new PropertyKey(new Guid("{341796F1-1DF9-4B1C-A564-91BDEFA43877}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.PhotometricInterpretationText -- PKEY_Photo_PhotometricInterpretationText + /// Description: This is the user-friendly form of System.Photo.PhotometricInterpretation. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {821437D6-9EAB-4765-A589-3B1CBBD22A61}, 100 + /// + public static PropertyKey PhotometricInterpretationText + { + get + { + var key = new PropertyKey(new Guid("{821437D6-9EAB-4765-A589-3B1CBBD22A61}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ProgramMode -- PKEY_Photo_ProgramMode + /// Description: This is the class of the program used by the camera to set exposure when the + ///picture is taken. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {6D217F6D-3F6A-4825-B470-5F03CA2FBE9B}, 100 + /// + public static PropertyKey ProgramMode + { + get + { + var key = new PropertyKey(new Guid("{6D217F6D-3F6A-4825-B470-5F03CA2FBE9B}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ProgramModeText -- PKEY_Photo_ProgramModeText + /// Description: This is the user-friendly form of System.Photo.ProgramMode. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7FE3AA27-2648-42F3-89B0-454E5CB150C3}, 100 + /// + public static PropertyKey ProgramModeText + { + get + { + var key = new PropertyKey(new Guid("{7FE3AA27-2648-42F3-89B0-454E5CB150C3}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.RelatedSoundFile -- PKEY_Photo_RelatedSoundFile + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {318A6B45-087F-4DC2-B8CC-05359551FC9E}, 100 + /// + public static PropertyKey RelatedSoundFile + { + get + { + var key = new PropertyKey(new Guid("{318A6B45-087F-4DC2-B8CC-05359551FC9E}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.Saturation -- PKEY_Photo_Saturation + /// Description: This indicates the direction of saturation processing applied by the camera when + ///the image was shot. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {49237325-A95A-4F67-B211-816B2D45D2E0}, 100 + /// + public static PropertyKey Saturation + { + get + { + var key = new PropertyKey(new Guid("{49237325-A95A-4F67-B211-816B2D45D2E0}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.SaturationText -- PKEY_Photo_SaturationText + /// Description: This is the user-friendly form of System.Photo.Saturation. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {61478C08-B600-4A84-BBE4-E99C45F0A072}, 100 + /// + public static PropertyKey SaturationText + { + get + { + var key = new PropertyKey(new Guid("{61478C08-B600-4A84-BBE4-E99C45F0A072}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.Sharpness -- PKEY_Photo_Sharpness + /// Description: This indicates the direction of sharpness processing applied by the camera when + ///the image was shot. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {FC6976DB-8349-4970-AE97-B3C5316A08F0}, 100 + /// + public static PropertyKey Sharpness + { + get + { + var key = new PropertyKey(new Guid("{FC6976DB-8349-4970-AE97-B3C5316A08F0}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.SharpnessText -- PKEY_Photo_SharpnessText + /// Description: This is the user-friendly form of System.Photo.Sharpness. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {51EC3F47-DD50-421D-8769-334F50424B1E}, 100 + /// + public static PropertyKey SharpnessText + { + get + { + var key = new PropertyKey(new Guid("{51EC3F47-DD50-421D-8769-334F50424B1E}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ShutterSpeed -- PKEY_Photo_ShutterSpeed + /// Description: PropertyTagExifShutterSpeed. Calculated from PKEY_Photo_ShutterSpeedNumerator and PKEY_Photo_ShutterSpeedDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37377 + /// + public static PropertyKey ShutterSpeed + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 37377); + + return key; + } + } + + /// + /// Name: System.Photo.ShutterSpeedDenominator -- PKEY_Photo_ShutterSpeedDenominator + /// Description: Denominator of PKEY_Photo_ShutterSpeed + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {E13D8975-81C7-4948-AE3F-37CAE11E8FF7}, 100 + /// + public static PropertyKey ShutterSpeedDenominator + { + get + { + var key = new PropertyKey(new Guid("{E13D8975-81C7-4948-AE3F-37CAE11E8FF7}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.ShutterSpeedNumerator -- PKEY_Photo_ShutterSpeedNumerator + /// Description: Numerator of PKEY_Photo_ShutterSpeed + /// + /// Type: Int32 -- VT_I4 + /// FormatID: {16EA4042-D6F4-4BCA-8349-7C78D30FB333}, 100 + /// + public static PropertyKey ShutterSpeedNumerator + { + get + { + var key = new PropertyKey(new Guid("{16EA4042-D6F4-4BCA-8349-7C78D30FB333}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.SubjectDistance -- PKEY_Photo_SubjectDistance + /// Description: PropertyTagExifSubjectDist. Calculated from PKEY_Photo_SubjectDistanceNumerator and PKEY_Photo_SubjectDistanceDenominator + /// + /// Type: Double -- VT_R8 + /// FormatID: (FMTID_ImageProperties) {14B81DA1-0135-4D31-96D9-6CBFC9671A99}, 37382 + /// + public static PropertyKey SubjectDistance + { + get + { + var key = new PropertyKey(new Guid("{14B81DA1-0135-4D31-96D9-6CBFC9671A99}"), 37382); + + return key; + } + } + + /// + /// Name: System.Photo.SubjectDistanceDenominator -- PKEY_Photo_SubjectDistanceDenominator + /// Description: Denominator of PKEY_Photo_SubjectDistance + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {0C840A88-B043-466D-9766-D4B26DA3FA77}, 100 + /// + public static PropertyKey SubjectDistanceDenominator + { + get + { + var key = new PropertyKey(new Guid("{0C840A88-B043-466D-9766-D4B26DA3FA77}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.SubjectDistanceNumerator -- PKEY_Photo_SubjectDistanceNumerator + /// Description: Numerator of PKEY_Photo_SubjectDistance + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {8AF4961C-F526-43E5-AA81-DB768219178D}, 100 + /// + public static PropertyKey SubjectDistanceNumerator + { + get + { + var key = new PropertyKey(new Guid("{8AF4961C-F526-43E5-AA81-DB768219178D}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.TagViewAggregate -- PKEY_Photo_TagViewAggregate + /// Description: A read-only aggregation of tag-like properties for use in building views. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) Legacy code may treat this as VT_LPSTR. + /// FormatID: {B812F15D-C2D8-4BBF-BACD-79744346113F}, 100 + /// + public static PropertyKey TagViewAggregate + { + get + { + var key = new PropertyKey(new Guid("{B812F15D-C2D8-4BBF-BACD-79744346113F}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.TranscodedForSync -- PKEY_Photo_TranscodedForSync + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {9A8EBB75-6458-4E82-BACB-35C0095B03BB}, 100 + /// + public static PropertyKey TranscodedForSync + { + get + { + var key = new PropertyKey(new Guid("{9A8EBB75-6458-4E82-BACB-35C0095B03BB}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.WhiteBalance -- PKEY_Photo_WhiteBalance + /// Description: This indicates the white balance mode set when the image was shot. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {EE3D3D8A-5381-4CFA-B13B-AAF66B5F4EC9}, 100 + /// + public static PropertyKey WhiteBalance + { + get + { + var key = new PropertyKey(new Guid("{EE3D3D8A-5381-4CFA-B13B-AAF66B5F4EC9}"), 100); + + return key; + } + } + + /// + /// Name: System.Photo.WhiteBalanceText -- PKEY_Photo_WhiteBalanceText + /// Description: This is the user-friendly form of System.Photo.WhiteBalance. Not intended to be parsed + ///programmatically. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6336B95E-C7A7-426D-86FD-7AE3D39C84B4}, 100 + /// + public static PropertyKey WhiteBalanceText + { + get + { + var key = new PropertyKey(new Guid("{6336B95E-C7A7-426D-86FD-7AE3D39C84B4}"), 100); + + return key; + } + } + #endregion + + + + } + + /// + /// PropGroup Properties + /// + public static class PropGroup + { + + + #region Properties + + /// + /// Name: System.PropGroup.Advanced -- PKEY_PropGroup_Advanced + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {900A403B-097B-4B95-8AE2-071FDAEEB118}, 100 + /// + public static PropertyKey Advanced + { + get + { + var key = new PropertyKey(new Guid("{900A403B-097B-4B95-8AE2-071FDAEEB118}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.Audio -- PKEY_PropGroup_Audio + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {2804D469-788F-48AA-8570-71B9C187E138}, 100 + /// + public static PropertyKey Audio + { + get + { + var key = new PropertyKey(new Guid("{2804D469-788F-48AA-8570-71B9C187E138}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.Calendar -- PKEY_PropGroup_Calendar + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {9973D2B5-BFD8-438A-BA94-5349B293181A}, 100 + /// + public static PropertyKey Calendar + { + get + { + var key = new PropertyKey(new Guid("{9973D2B5-BFD8-438A-BA94-5349B293181A}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.Camera -- PKEY_PropGroup_Camera + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {DE00DE32-547E-4981-AD4B-542F2E9007D8}, 100 + /// + public static PropertyKey Camera + { + get + { + var key = new PropertyKey(new Guid("{DE00DE32-547E-4981-AD4B-542F2E9007D8}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.Contact -- PKEY_PropGroup_Contact + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {DF975FD3-250A-4004-858F-34E29A3E37AA}, 100 + /// + public static PropertyKey Contact + { + get + { + var key = new PropertyKey(new Guid("{DF975FD3-250A-4004-858F-34E29A3E37AA}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.Content -- PKEY_PropGroup_Content + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {D0DAB0BA-368A-4050-A882-6C010FD19A4F}, 100 + /// + public static PropertyKey Content + { + get + { + var key = new PropertyKey(new Guid("{D0DAB0BA-368A-4050-A882-6C010FD19A4F}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.Description -- PKEY_PropGroup_Description + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {8969B275-9475-4E00-A887-FF93B8B41E44}, 100 + /// + public static PropertyKey Description + { + get + { + var key = new PropertyKey(new Guid("{8969B275-9475-4E00-A887-FF93B8B41E44}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.FileSystem -- PKEY_PropGroup_FileSystem + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {E3A7D2C1-80FC-4B40-8F34-30EA111BDC2E}, 100 + /// + public static PropertyKey FileSystem + { + get + { + var key = new PropertyKey(new Guid("{E3A7D2C1-80FC-4B40-8F34-30EA111BDC2E}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.General -- PKEY_PropGroup_General + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {CC301630-B192-4C22-B372-9F4C6D338E07}, 100 + /// + public static PropertyKey General + { + get + { + var key = new PropertyKey(new Guid("{CC301630-B192-4C22-B372-9F4C6D338E07}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.GPS -- PKEY_PropGroup_GPS + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {F3713ADA-90E3-4E11-AAE5-FDC17685B9BE}, 100 + /// + public static PropertyKey GPS + { + get + { + var key = new PropertyKey(new Guid("{F3713ADA-90E3-4E11-AAE5-FDC17685B9BE}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.Image -- PKEY_PropGroup_Image + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {E3690A87-0FA8-4A2A-9A9F-FCE8827055AC}, 100 + /// + public static PropertyKey Image + { + get + { + var key = new PropertyKey(new Guid("{E3690A87-0FA8-4A2A-9A9F-FCE8827055AC}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.Media -- PKEY_PropGroup_Media + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {61872CF7-6B5E-4B4B-AC2D-59DA84459248}, 100 + /// + public static PropertyKey Media + { + get + { + var key = new PropertyKey(new Guid("{61872CF7-6B5E-4B4B-AC2D-59DA84459248}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.MediaAdvanced -- PKEY_PropGroup_MediaAdvanced + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {8859A284-DE7E-4642-99BA-D431D044B1EC}, 100 + /// + public static PropertyKey MediaAdvanced + { + get + { + var key = new PropertyKey(new Guid("{8859A284-DE7E-4642-99BA-D431D044B1EC}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.Message -- PKEY_PropGroup_Message + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {7FD7259D-16B4-4135-9F97-7C96ECD2FA9E}, 100 + /// + public static PropertyKey Message + { + get + { + var key = new PropertyKey(new Guid("{7FD7259D-16B4-4135-9F97-7C96ECD2FA9E}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.Music -- PKEY_PropGroup_Music + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {68DD6094-7216-40F1-A029-43FE7127043F}, 100 + /// + public static PropertyKey Music + { + get + { + var key = new PropertyKey(new Guid("{68DD6094-7216-40F1-A029-43FE7127043F}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.Origin -- PKEY_PropGroup_Origin + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {2598D2FB-5569-4367-95DF-5CD3A177E1A5}, 100 + /// + public static PropertyKey Origin + { + get + { + var key = new PropertyKey(new Guid("{2598D2FB-5569-4367-95DF-5CD3A177E1A5}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.PhotoAdvanced -- PKEY_PropGroup_PhotoAdvanced + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {0CB2BF5A-9EE7-4A86-8222-F01E07FDADAF}, 100 + /// + public static PropertyKey PhotoAdvanced + { + get + { + var key = new PropertyKey(new Guid("{0CB2BF5A-9EE7-4A86-8222-F01E07FDADAF}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.RecordedTV -- PKEY_PropGroup_RecordedTV + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {E7B33238-6584-4170-A5C0-AC25EFD9DA56}, 100 + /// + public static PropertyKey RecordedTV + { + get + { + var key = new PropertyKey(new Guid("{E7B33238-6584-4170-A5C0-AC25EFD9DA56}"), 100); + + return key; + } + } + + /// + /// Name: System.PropGroup.Video -- PKEY_PropGroup_Video + /// Description: + /// Type: Null -- VT_NULL + /// FormatID: {BEBE0920-7671-4C54-A3EB-49FDDFC191EE}, 100 + /// + public static PropertyKey Video + { + get + { + var key = new PropertyKey(new Guid("{BEBE0920-7671-4C54-A3EB-49FDDFC191EE}"), 100); + + return key; + } + } + #endregion + + + + } + + /// + /// PropList Properties + /// + public static class PropList + { + + + #region Properties + + /// + /// Name: System.PropList.ConflictPrompt -- PKEY_PropList_ConflictPrompt + /// Description: The list of properties to show in the file operation conflict resolution dialog. Properties with empty + ///values will not be displayed. Register under the regvalue of "ConflictPrompt". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 11 + /// + public static PropertyKey ConflictPrompt + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 11); + + return key; + } + } + + /// + /// Name: System.PropList.ContentViewModeForBrowse -- PKEY_PropList_ContentViewModeForBrowse + /// Description: The list of properties to show in the content view mode of an item in the context of browsing. + ///Register the regvalue under the name of "ContentViewModeForBrowse". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 13 + /// + public static PropertyKey ContentViewModeForBrowse + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 13); + + return key; + } + } + + /// + /// Name: System.PropList.ContentViewModeForSearch -- PKEY_PropList_ContentViewModeForSearch + /// Description: The list of properties to show in the content view mode of an item in the context of searching. + ///Register the regvalue under the name of "ContentViewModeForSearch". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 14 + /// + public static PropertyKey ContentViewModeForSearch + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 14); + + return key; + } + } + + /// + /// Name: System.PropList.ExtendedTileInfo -- PKEY_PropList_ExtendedTileInfo + /// Description: The list of properties to show in the listview on extended tiles. Register under the regvalue of + ///"ExtendedTileInfo". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 9 + /// + public static PropertyKey ExtendedTileInfo + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 9); + + return key; + } + } + + /// + /// Name: System.PropList.FileOperationPrompt -- PKEY_PropList_FileOperationPrompt + /// Description: The list of properties to show in the file operation confirmation dialog. Properties with empty values + ///will not be displayed. If this list is not specified, then the InfoTip property list is used instead. + ///Register under the regvalue of "FileOperationPrompt". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 10 + /// + public static PropertyKey FileOperationPrompt + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 10); + + return key; + } + } + + /// + /// Name: System.PropList.FullDetails -- PKEY_PropList_FullDetails + /// Description: The list of all the properties to show in the details page. Property groups can be included in this list + ///in order to more easily organize the UI. Register under the regvalue of "FullDetails". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 2 + /// + public static PropertyKey FullDetails + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 2); + + return key; + } + } + + /// + /// Name: System.PropList.InfoTip -- PKEY_PropList_InfoTip + /// Description: The list of properties to show in the infotip. Properties with empty values will not be displayed. Register + ///under the regvalue of "InfoTip". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 4 (PID_PROPLIST_INFOTIP) + /// + public static PropertyKey InfoTip + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 4); + + return key; + } + } + + /// + /// Name: System.PropList.NonPersonal -- PKEY_PropList_NonPersonal + /// Description: The list of properties that are considered 'non-personal'. When told to remove all non-personal properties + ///from a given file, the system will leave these particular properties untouched. Register under the regvalue + ///of "NonPersonal". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {49D1091F-082E-493F-B23F-D2308AA9668C}, 100 + /// + public static PropertyKey NonPersonal + { + get + { + var key = new PropertyKey(new Guid("{49D1091F-082E-493F-B23F-D2308AA9668C}"), 100); + + return key; + } + } + + /// + /// Name: System.PropList.PreviewDetails -- PKEY_PropList_PreviewDetails + /// Description: The list of properties to display in the preview pane. Register under the regvalue of "PreviewDetails". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 8 + /// + public static PropertyKey PreviewDetails + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 8); + + return key; + } + } + + /// + /// Name: System.PropList.PreviewTitle -- PKEY_PropList_PreviewTitle + /// Description: The one or two properties to display in the preview pane title section. The optional second property is + ///displayed as a subtitle. Register under the regvalue of "PreviewTitle". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 6 + /// + public static PropertyKey PreviewTitle + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 6); + + return key; + } + } + + /// + /// Name: System.PropList.QuickTip -- PKEY_PropList_QuickTip + /// Description: The list of properties to show in the infotip when the item is on a slow network. Properties with empty + ///values will not be displayed. Register under the regvalue of "QuickTip". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 5 (PID_PROPLIST_QUICKTIP) + /// + public static PropertyKey QuickTip + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 5); + + return key; + } + } + + /// + /// Name: System.PropList.TileInfo -- PKEY_PropList_TileInfo + /// Description: The list of properties to show in the listview on tiles. Register under the regvalue of "TileInfo". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {C9944A21-A406-48FE-8225-AEC7E24C211B}, 3 (PID_PROPLIST_TILEINFO) + /// + public static PropertyKey TileInfo + { + get + { + var key = new PropertyKey(new Guid("{C9944A21-A406-48FE-8225-AEC7E24C211B}"), 3); + + return key; + } + } + + /// + /// Name: System.PropList.XPDetailsPanel -- PKEY_PropList_XPDetailsPanel + /// Description: The list of properties to display in the XP webview details panel. Obsolete. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_WebView) {F2275480-F782-4291-BD94-F13693513AEC}, 0 (PID_DISPLAY_PROPERTIES) + /// + public static PropertyKey XPDetailsPanel + { + get + { + var key = new PropertyKey(new Guid("{F2275480-F782-4291-BD94-F13693513AEC}"), 0); + + return key; + } + } + #endregion + + + + } + + /// + /// RecordedTV Properties + /// + public static class RecordedTV + { + + + #region Properties + + /// + /// Name: System.RecordedTV.ChannelNumber -- PKEY_RecordedTV_ChannelNumber + /// Description: Example: 42 + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 7 + /// + public static PropertyKey ChannelNumber + { + get + { + var key = new PropertyKey(new Guid("{6D748DE2-8D38-4CC3-AC60-F009B057C557}"), 7); + + return key; + } + } + + /// + /// Name: System.RecordedTV.Credits -- PKEY_RecordedTV_Credits + /// Description: Example: "Don Messick/Frank Welker/Casey Kasem/Heather North/Nicole Jaffe;;;" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 4 + /// + public static PropertyKey Credits + { + get + { + var key = new PropertyKey(new Guid("{6D748DE2-8D38-4CC3-AC60-F009B057C557}"), 4); + + return key; + } + } + + /// + /// Name: System.RecordedTV.DateContentExpires -- PKEY_RecordedTV_DateContentExpires + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 15 + /// + public static PropertyKey DateContentExpires + { + get + { + var key = new PropertyKey(new Guid("{6D748DE2-8D38-4CC3-AC60-F009B057C557}"), 15); + + return key; + } + } + + /// + /// Name: System.RecordedTV.EpisodeName -- PKEY_RecordedTV_EpisodeName + /// Description: Example: "Nowhere to Hyde" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 2 + /// + public static PropertyKey EpisodeName + { + get + { + var key = new PropertyKey(new Guid("{6D748DE2-8D38-4CC3-AC60-F009B057C557}"), 2); + + return key; + } + } + + /// + /// Name: System.RecordedTV.IsATSCContent -- PKEY_RecordedTV_IsATSCContent + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 16 + /// + public static PropertyKey IsATSCContent + { + get + { + var key = new PropertyKey(new Guid("{6D748DE2-8D38-4CC3-AC60-F009B057C557}"), 16); + + return key; + } + } + + /// + /// Name: System.RecordedTV.IsClosedCaptioningAvailable -- PKEY_RecordedTV_IsClosedCaptioningAvailable + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 12 + /// + public static PropertyKey IsClosedCaptioningAvailable + { + get + { + var key = new PropertyKey(new Guid("{6D748DE2-8D38-4CC3-AC60-F009B057C557}"), 12); + + return key; + } + } + + /// + /// Name: System.RecordedTV.IsDTVContent -- PKEY_RecordedTV_IsDTVContent + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 17 + /// + public static PropertyKey IsDTVContent + { + get + { + var key = new PropertyKey(new Guid("{6D748DE2-8D38-4CC3-AC60-F009B057C557}"), 17); + + return key; + } + } + + /// + /// Name: System.RecordedTV.IsHDContent -- PKEY_RecordedTV_IsHDContent + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 18 + /// + public static PropertyKey IsHDContent + { + get + { + var key = new PropertyKey(new Guid("{6D748DE2-8D38-4CC3-AC60-F009B057C557}"), 18); + + return key; + } + } + + /// + /// Name: System.RecordedTV.IsRepeatBroadcast -- PKEY_RecordedTV_IsRepeatBroadcast + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 13 + /// + public static PropertyKey IsRepeatBroadcast + { + get + { + var key = new PropertyKey(new Guid("{6D748DE2-8D38-4CC3-AC60-F009B057C557}"), 13); + + return key; + } + } + + /// + /// Name: System.RecordedTV.IsSAP -- PKEY_RecordedTV_IsSAP + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 14 + /// + public static PropertyKey IsSAP + { + get + { + var key = new PropertyKey(new Guid("{6D748DE2-8D38-4CC3-AC60-F009B057C557}"), 14); + + return key; + } + } + + /// + /// Name: System.RecordedTV.NetworkAffiliation -- PKEY_RecordedTV_NetworkAffiliation + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {2C53C813-FB63-4E22-A1AB-0B331CA1E273}, 100 + /// + public static PropertyKey NetworkAffiliation + { + get + { + var key = new PropertyKey(new Guid("{2C53C813-FB63-4E22-A1AB-0B331CA1E273}"), 100); + + return key; + } + } + + /// + /// Name: System.RecordedTV.OriginalBroadcastDate -- PKEY_RecordedTV_OriginalBroadcastDate + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {4684FE97-8765-4842-9C13-F006447B178C}, 100 + /// + public static PropertyKey OriginalBroadcastDate + { + get + { + var key = new PropertyKey(new Guid("{4684FE97-8765-4842-9C13-F006447B178C}"), 100); + + return key; + } + } + + /// + /// Name: System.RecordedTV.ProgramDescription -- PKEY_RecordedTV_ProgramDescription + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 3 + /// + public static PropertyKey ProgramDescription + { + get + { + var key = new PropertyKey(new Guid("{6D748DE2-8D38-4CC3-AC60-F009B057C557}"), 3); + + return key; + } + } + + /// + /// Name: System.RecordedTV.RecordingTime -- PKEY_RecordedTV_RecordingTime + /// Description: + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {A5477F61-7A82-4ECA-9DDE-98B69B2479B3}, 100 + /// + public static PropertyKey RecordingTime + { + get + { + var key = new PropertyKey(new Guid("{A5477F61-7A82-4ECA-9DDE-98B69B2479B3}"), 100); + + return key; + } + } + + /// + /// Name: System.RecordedTV.StationCallSign -- PKEY_RecordedTV_StationCallSign + /// Description: Example: "TOONP" + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {6D748DE2-8D38-4CC3-AC60-F009B057C557}, 5 + /// + public static PropertyKey StationCallSign + { + get + { + var key = new PropertyKey(new Guid("{6D748DE2-8D38-4CC3-AC60-F009B057C557}"), 5); + + return key; + } + } + + /// + /// Name: System.RecordedTV.StationName -- PKEY_RecordedTV_StationName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {1B5439E7-EBA1-4AF8-BDD7-7AF1D4549493}, 100 + /// + public static PropertyKey StationName + { + get + { + var key = new PropertyKey(new Guid("{1B5439E7-EBA1-4AF8-BDD7-7AF1D4549493}"), 100); + + return key; + } + } + #endregion + + + + } + + /// + /// Search Properties + /// + public static class Search + { + + + #region Properties + + /// + /// Name: System.Search.AutoSummary -- PKEY_Search_AutoSummary + /// Description: General Summary of the document. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {560C36C0-503A-11CF-BAA1-00004C752A9A}, 2 + /// + public static PropertyKey AutoSummary + { + get + { + var key = new PropertyKey(new Guid("{560C36C0-503A-11CF-BAA1-00004C752A9A}"), 2); + + return key; + } + } + + /// + /// Name: System.Search.ContainerHash -- PKEY_Search_ContainerHash + /// Description: Hash code used to identify attachments to be deleted based on a common container url + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {BCEEE283-35DF-4D53-826A-F36A3EEFC6BE}, 100 + /// + public static PropertyKey ContainerHash + { + get + { + var key = new PropertyKey(new Guid("{BCEEE283-35DF-4D53-826A-F36A3EEFC6BE}"), 100); + + return key; + } + } + + /// + /// Name: System.Search.Contents -- PKEY_Search_Contents + /// Description: The contents of the item. This property is for query restrictions only; it cannot be retrieved in a + ///query result. The Indexing Service friendly name is 'contents'. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 19 (PID_STG_CONTENTS) + /// + public static PropertyKey Contents + { + get + { + var key = new PropertyKey(new Guid("{B725F130-47EF-101A-A5F1-02608C9EEBAC}"), 19); + + return key; + } + } + + /// + /// Name: System.Search.EntryID -- PKEY_Search_EntryID + /// Description: The entry ID for an item within a given catalog in the Windows Search Index. + ///This value may be recycled, and therefore is not considered unique over time. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_Query) {49691C90-7E17-101A-A91C-08002B2ECDA9}, 5 (PROPID_QUERY_WORKID) + /// + public static PropertyKey EntryID + { + get + { + var key = new PropertyKey(new Guid("{49691C90-7E17-101A-A91C-08002B2ECDA9}"), 5); + + return key; + } + } + + /// + /// Name: System.Search.ExtendedProperties -- PKEY_Search_ExtendedProperties + /// Description: + /// Type: Blob -- VT_BLOB + /// FormatID: {7B03B546-FA4F-4A52-A2FE-03D5311E5865}, 100 + /// + public static PropertyKey ExtendedProperties + { + get + { + var key = new PropertyKey(new Guid("{7B03B546-FA4F-4A52-A2FE-03D5311E5865}"), 100); + + return key; + } + } + + /// + /// Name: System.Search.GatherTime -- PKEY_Search_GatherTime + /// Description: The Datetime that the Windows Search Gatherer process last pushed properties of this document to the Windows Search Gatherer Plugins. + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {0B63E350-9CCC-11D0-BCDB-00805FCCCE04}, 8 + /// + public static PropertyKey GatherTime + { + get + { + var key = new PropertyKey(new Guid("{0B63E350-9CCC-11D0-BCDB-00805FCCCE04}"), 8); + + return key; + } + } + + /// + /// Name: System.Search.HitCount -- PKEY_Search_HitCount + /// Description: When using CONTAINS over the Windows Search Index, this is the number of matches of the term. + ///If there are multiple CONTAINS, an AND computes the min number of hits and an OR the max number of hits. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_Query) {49691C90-7E17-101A-A91C-08002B2ECDA9}, 4 (PROPID_QUERY_HITCOUNT) + /// + public static PropertyKey HitCount + { + get + { + var key = new PropertyKey(new Guid("{49691C90-7E17-101A-A91C-08002B2ECDA9}"), 4); + + return key; + } + } + + /// + /// Name: System.Search.IsClosedDirectory -- PKEY_Search_IsClosedDirectory + /// Description: If this property is emitted with a value of TRUE, then it indicates that this URL's last modified time applies to all of it's children, and if this URL is deleted then all of it's children are deleted as well. For example, this would be emitted as TRUE when emitting the URL of an email so that all attachments are tied to the last modified time of that email. + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {0B63E343-9CCC-11D0-BCDB-00805FCCCE04}, 23 + /// + public static PropertyKey IsClosedDirectory + { + get + { + var key = new PropertyKey(new Guid("{0B63E343-9CCC-11D0-BCDB-00805FCCCE04}"), 23); + + return key; + } + } + + /// + /// Name: System.Search.IsFullyContained -- PKEY_Search_IsFullyContained + /// Description: Any child URL of a URL which has System.Search.IsClosedDirectory=TRUE must emit System.Search.IsFullyContained=TRUE. This ensures that the URL is not deleted at the end of a crawl because it hasn't been visited (which is the normal mechanism for detecting deletes). For example an email attachment would emit this property + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: {0B63E343-9CCC-11D0-BCDB-00805FCCCE04}, 24 + /// + public static PropertyKey IsFullyContained + { + get + { + var key = new PropertyKey(new Guid("{0B63E343-9CCC-11D0-BCDB-00805FCCCE04}"), 24); + + return key; + } + } + + /// + /// Name: System.Search.QueryFocusedSummary -- PKEY_Search_QueryFocusedSummary + /// Description: Query Focused Summary of the document. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {560C36C0-503A-11CF-BAA1-00004C752A9A}, 3 + /// + public static PropertyKey QueryFocusedSummary + { + get + { + var key = new PropertyKey(new Guid("{560C36C0-503A-11CF-BAA1-00004C752A9A}"), 3); + + return key; + } + } + + /// + /// Name: System.Search.QueryFocusedSummaryWithFallback -- PKEY_Search_QueryFocusedSummaryWithFallback + /// Description: Query Focused Summary of the document, if none is available it returns the AutoSummary. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {560C36C0-503A-11CF-BAA1-00004C752A9A}, 4 + /// + public static PropertyKey QueryFocusedSummaryWithFallback + { + get + { + var key = new PropertyKey(new Guid("{560C36C0-503A-11CF-BAA1-00004C752A9A}"), 4); + + return key; + } + } + + /// + /// Name: System.Search.Rank -- PKEY_Search_Rank + /// Description: Relevance rank of row. Ranges from 0-1000. Larger numbers = better matches. Query-time only. + /// + /// Type: Int32 -- VT_I4 + /// FormatID: (FMTID_Query) {49691C90-7E17-101A-A91C-08002B2ECDA9}, 3 (PROPID_QUERY_RANK) + /// + public static PropertyKey Rank + { + get + { + var key = new PropertyKey(new Guid("{49691C90-7E17-101A-A91C-08002B2ECDA9}"), 3); + + return key; + } + } + + /// + /// Name: System.Search.Store -- PKEY_Search_Store + /// Description: The identifier for the protocol handler that produced this item. (E.g. MAPI, CSC, FILE etc.) + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {A06992B3-8CAF-4ED7-A547-B259E32AC9FC}, 100 + /// + public static PropertyKey Store + { + get + { + var key = new PropertyKey(new Guid("{A06992B3-8CAF-4ED7-A547-B259E32AC9FC}"), 100); + + return key; + } + } + + /// + /// Name: System.Search.UrlToIndex -- PKEY_Search_UrlToIndex + /// Description: This property should be emitted by a container IFilter for each child URL within the container. The children will eventually be crawled by the indexer if they are within scope. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {0B63E343-9CCC-11D0-BCDB-00805FCCCE04}, 2 + /// + public static PropertyKey UrlToIndex + { + get + { + var key = new PropertyKey(new Guid("{0B63E343-9CCC-11D0-BCDB-00805FCCCE04}"), 2); + + return key; + } + } + + /// + /// Name: System.Search.UrlToIndexWithModificationTime -- PKEY_Search_UrlToIndexWithModificationTime + /// Description: This property is the same as System.Search.UrlToIndex except that it includes the time the URL was last modified. This is an optimization for the indexer as it doesn't have to call back into the protocol handler to ask for this information to determine if the content needs to be indexed again. The property is a vector with two elements, a VT_LPWSTR with the URL and a VT_FILETIME for the last modified time. + /// + /// Type: Multivalue Any -- VT_VECTOR | VT_NULL (For variants: VT_ARRAY | VT_NULL) + /// FormatID: {0B63E343-9CCC-11D0-BCDB-00805FCCCE04}, 12 + /// + public static PropertyKey UrlToIndexWithModificationTime + { + get + { + var key = new PropertyKey(new Guid("{0B63E343-9CCC-11D0-BCDB-00805FCCCE04}"), 12); + + return key; + } + } + #endregion + + + + } + + /// + /// Shell Properties + /// + public static class Shell + { + + + #region Properties + + /// + /// Name: System.Shell.OmitFromView -- PKEY_Shell_OmitFromView + /// Description: Set this to a string value of 'True' to omit this item from shell views + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {DE35258C-C695-4CBC-B982-38B0AD24CED0}, 2 + /// + public static PropertyKey OmitFromView + { + get + { + var key = new PropertyKey(new Guid("{DE35258C-C695-4CBC-B982-38B0AD24CED0}"), 2); + + return key; + } + } + + /// + /// Name: System.Shell.SFGAOFlagsStrings -- PKEY_Shell_SFGAOFlagsStrings + /// Description: Expresses the SFGAO flags as string values and is used as a query optimization. + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: {D6942081-D53B-443D-AD47-5E059D9CD27A}, 2 + /// + [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags")] + public static PropertyKey SFGAOFlagsStrings + { + get + { + var key = new PropertyKey(new Guid("{D6942081-D53B-443D-AD47-5E059D9CD27A}"), 2); + + return key; + } + } + #endregion + + + + } + + /// + /// Software Properties + /// + public static class Software + { + + + #region Properties + + /// + /// Name: System.Software.DateLastUsed -- PKEY_Software_DateLastUsed + /// Description: + /// + /// Type: DateTime -- VT_FILETIME (For variants: VT_DATE) + /// FormatID: {841E4F90-FF59-4D16-8947-E81BBFFAB36D}, 16 + /// + public static PropertyKey DateLastUsed + { + get + { + var key = new PropertyKey(new Guid("{841E4F90-FF59-4D16-8947-E81BBFFAB36D}"), 16); + + return key; + } + } + + /// + /// Name: System.Software.ProductName -- PKEY_Software_ProductName + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (PSFMTID_VERSION) {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 7 + /// + public static PropertyKey ProductName + { + get + { + var key = new PropertyKey(new Guid("{0CEF7D53-FA64-11D1-A203-0000F81FEDEE}"), 7); + + return key; + } + } + #endregion + + + + } + + /// + /// Sync Properties + /// + public static class Sync + { + + + #region Properties + + /// + /// Name: System.Sync.Comments -- PKEY_Sync_Comments + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 13 + /// + public static PropertyKey Comments + { + get + { + var key = new PropertyKey(new Guid("{7BD5533E-AF15-44DB-B8C8-BD6624E1D032}"), 13); + + return key; + } + } + + /// + /// Name: System.Sync.ConflictDescription -- PKEY_Sync_ConflictDescription + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CE50C159-2FB8-41FD-BE68-D3E042E274BC}, 4 + /// + public static PropertyKey ConflictDescription + { + get + { + var key = new PropertyKey(new Guid("{CE50C159-2FB8-41FD-BE68-D3E042E274BC}"), 4); + + return key; + } + } + + /// + /// Name: System.Sync.ConflictFirstLocation -- PKEY_Sync_ConflictFirstLocation + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CE50C159-2FB8-41FD-BE68-D3E042E274BC}, 6 + /// + public static PropertyKey ConflictFirstLocation + { + get + { + var key = new PropertyKey(new Guid("{CE50C159-2FB8-41FD-BE68-D3E042E274BC}"), 6); + + return key; + } + } + + /// + /// Name: System.Sync.ConflictSecondLocation -- PKEY_Sync_ConflictSecondLocation + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CE50C159-2FB8-41FD-BE68-D3E042E274BC}, 7 + /// + public static PropertyKey ConflictSecondLocation + { + get + { + var key = new PropertyKey(new Guid("{CE50C159-2FB8-41FD-BE68-D3E042E274BC}"), 7); + + return key; + } + } + + /// + /// Name: System.Sync.HandlerCollectionID -- PKEY_Sync_HandlerCollectionID + /// Description: + /// Type: Guid -- VT_CLSID + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 2 + /// + public static PropertyKey HandlerCollectionID + { + get + { + var key = new PropertyKey(new Guid("{7BD5533E-AF15-44DB-B8C8-BD6624E1D032}"), 2); + + return key; + } + } + + /// + /// Name: System.Sync.HandlerID -- PKEY_Sync_HandlerID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 3 + /// + public static PropertyKey HandlerID + { + get + { + var key = new PropertyKey(new Guid("{7BD5533E-AF15-44DB-B8C8-BD6624E1D032}"), 3); + + return key; + } + } + + /// + /// Name: System.Sync.HandlerName -- PKEY_Sync_HandlerName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CE50C159-2FB8-41FD-BE68-D3E042E274BC}, 2 + /// + public static PropertyKey HandlerName + { + get + { + var key = new PropertyKey(new Guid("{CE50C159-2FB8-41FD-BE68-D3E042E274BC}"), 2); + + return key; + } + } + + /// + /// Name: System.Sync.HandlerType -- PKEY_Sync_HandlerType + /// Description: + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 8 + /// + public static PropertyKey HandlerType + { + get + { + var key = new PropertyKey(new Guid("{7BD5533E-AF15-44DB-B8C8-BD6624E1D032}"), 8); + + return key; + } + } + + /// + /// Name: System.Sync.HandlerTypeLabel -- PKEY_Sync_HandlerTypeLabel + /// Description: + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 9 + /// + public static PropertyKey HandlerTypeLabel + { + get + { + var key = new PropertyKey(new Guid("{7BD5533E-AF15-44DB-B8C8-BD6624E1D032}"), 9); + + return key; + } + } + + /// + /// Name: System.Sync.ItemID -- PKEY_Sync_ItemID + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 6 + /// + public static PropertyKey ItemID + { + get + { + var key = new PropertyKey(new Guid("{7BD5533E-AF15-44DB-B8C8-BD6624E1D032}"), 6); + + return key; + } + } + + /// + /// Name: System.Sync.ItemName -- PKEY_Sync_ItemName + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {CE50C159-2FB8-41FD-BE68-D3E042E274BC}, 3 + /// + public static PropertyKey ItemName + { + get + { + var key = new PropertyKey(new Guid("{CE50C159-2FB8-41FD-BE68-D3E042E274BC}"), 3); + + return key; + } + } + + /// + /// Name: System.Sync.ProgressPercentage -- PKEY_Sync_ProgressPercentage + /// Description: An integer value between 0 and 100 representing the percentage completed. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 23 + /// + public static PropertyKey ProgressPercentage + { + get + { + var key = new PropertyKey(new Guid("{7BD5533E-AF15-44DB-B8C8-BD6624E1D032}"), 23); + + return key; + } + } + + /// + /// Name: System.Sync.State -- PKEY_Sync_State + /// Description: Sync state. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 24 + /// + public static PropertyKey State + { + get + { + var key = new PropertyKey(new Guid("{7BD5533E-AF15-44DB-B8C8-BD6624E1D032}"), 24); + + return key; + } + } + + /// + /// Name: System.Sync.Status -- PKEY_Sync_Status + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {7BD5533E-AF15-44DB-B8C8-BD6624E1D032}, 10 + /// + public static PropertyKey Status + { + get + { + var key = new PropertyKey(new Guid("{7BD5533E-AF15-44DB-B8C8-BD6624E1D032}"), 10); + + return key; + } + } + #endregion + + + + } + + /// + /// Task Properties + /// + public static class Task + { + + + #region Properties + + /// + /// Name: System.Task.BillingInformation -- PKEY_Task_BillingInformation + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {D37D52C6-261C-4303-82B3-08B926AC6F12}, 100 + /// + public static PropertyKey BillingInformation + { + get + { + var key = new PropertyKey(new Guid("{D37D52C6-261C-4303-82B3-08B926AC6F12}"), 100); + + return key; + } + } + + /// + /// Name: System.Task.CompletionStatus -- PKEY_Task_CompletionStatus + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {084D8A0A-E6D5-40DE-BF1F-C8820E7C877C}, 100 + /// + public static PropertyKey CompletionStatus + { + get + { + var key = new PropertyKey(new Guid("{084D8A0A-E6D5-40DE-BF1F-C8820E7C877C}"), 100); + + return key; + } + } + + /// + /// Name: System.Task.Owner -- PKEY_Task_Owner + /// Description: + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: {08C7CC5F-60F2-4494-AD75-55E3E0B5ADD0}, 100 + /// + public static PropertyKey Owner + { + get + { + var key = new PropertyKey(new Guid("{08C7CC5F-60F2-4494-AD75-55E3E0B5ADD0}"), 100); + + return key; + } + } + #endregion + + + + } + + /// + /// Video Properties + /// + public static class Video + { + + + #region Properties + + /// + /// Name: System.Video.Compression -- PKEY_Video_Compression + /// Description: Indicates the level of compression for the video stream. "Compression". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 10 (PIDVSI_COMPRESSION) + /// + public static PropertyKey Compression + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 10); + + return key; + } + } + + /// + /// Name: System.Video.Director -- PKEY_Video_Director + /// Description: + /// + /// Type: Multivalue String -- VT_VECTOR | VT_LPWSTR (For variants: VT_ARRAY | VT_BSTR) + /// FormatID: (PSGUID_MEDIAFILESUMMARYINFORMATION) {64440492-4C8B-11D1-8B70-080036B11A03}, 20 (PIDMSI_DIRECTOR) + /// + public static PropertyKey Director + { + get + { + var key = new PropertyKey(new Guid("{64440492-4C8B-11D1-8B70-080036B11A03}"), 20); + + return key; + } + } + + /// + /// Name: System.Video.EncodingBitrate -- PKEY_Video_EncodingBitrate + /// Description: Indicates the data rate in "bits per second" for the video stream. "DataRate". + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 8 (PIDVSI_DATA_RATE) + /// + public static PropertyKey EncodingBitrate + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 8); + + return key; + } + } + + /// + /// Name: System.Video.FourCC -- PKEY_Video_FourCC + /// Description: Indicates the 4CC for the video stream. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 44 + /// + public static PropertyKey FourCC + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 44); + + return key; + } + } + + /// + /// Name: System.Video.FrameHeight -- PKEY_Video_FrameHeight + /// Description: Indicates the frame height for the video stream. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 4 + /// + public static PropertyKey FrameHeight + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 4); + + return key; + } + } + + /// + /// Name: System.Video.FrameRate -- PKEY_Video_FrameRate + /// Description: Indicates the frame rate in "frames per millisecond" for the video stream. "FrameRate". + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 6 (PIDVSI_FRAME_RATE) + /// + public static PropertyKey FrameRate + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 6); + + return key; + } + } + + /// + /// Name: System.Video.FrameWidth -- PKEY_Video_FrameWidth + /// Description: Indicates the frame width for the video stream. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 3 + /// + public static PropertyKey FrameWidth + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 3); + + return key; + } + } + + /// + /// Name: System.Video.HorizontalAspectRatio -- PKEY_Video_HorizontalAspectRatio + /// Description: Indicates the horizontal portion of the aspect ratio. The X portion of XX:YY, + ///like 16:9. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 42 + /// + public static PropertyKey HorizontalAspectRatio + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 42); + + return key; + } + } + + /// + /// Name: System.Video.SampleSize -- PKEY_Video_SampleSize + /// Description: Indicates the sample size in bits for the video stream. "SampleSize". + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 9 (PIDVSI_SAMPLE_SIZE) + /// + public static PropertyKey SampleSize + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 9); + + return key; + } + } + + /// + /// Name: System.Video.StreamName -- PKEY_Video_StreamName + /// Description: Indicates the name for the video stream. "StreamName". + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 2 (PIDVSI_STREAM_NAME) + /// + public static PropertyKey StreamName + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 2); + + return key; + } + } + + /// + /// Name: System.Video.StreamNumber -- PKEY_Video_StreamNumber + /// Description: "Stream Number". + /// + /// Type: UInt16 -- VT_UI2 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 11 (PIDVSI_STREAM_NUMBER) + /// + public static PropertyKey StreamNumber + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 11); + + return key; + } + } + + /// + /// Name: System.Video.TotalBitrate -- PKEY_Video_TotalBitrate + /// Description: Indicates the total data rate in "bits per second" for all video and audio streams. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 43 (PIDVSI_TOTAL_BITRATE) + /// + public static PropertyKey TotalBitrate + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 43); + + return key; + } + } + + /// + /// Name: System.Video.TranscodedForSync -- PKEY_Video_TranscodedForSync + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 46 + /// + public static PropertyKey TranscodedForSync + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 46); + + return key; + } + } + + /// + /// Name: System.Video.VerticalAspectRatio -- PKEY_Video_VerticalAspectRatio + /// Description: Indicates the vertical portion of the aspect ratio. The Y portion of + ///XX:YY, like 16:9. + /// + /// Type: UInt32 -- VT_UI4 + /// FormatID: (FMTID_VideoSummaryInformation) {64440491-4C8B-11D1-8B70-080036B11A03}, 45 + /// + public static PropertyKey VerticalAspectRatio + { + get + { + var key = new PropertyKey(new Guid("{64440491-4C8B-11D1-8B70-080036B11A03}"), 45); + + return key; + } + } + #endregion + + + + } + + /// + /// Volume Properties + /// + public static class Volume + { + + + #region Properties + + /// + /// Name: System.Volume.FileSystem -- PKEY_Volume_FileSystem + /// Description: Indicates the filesystem of the volume. + /// + /// Type: String -- VT_LPWSTR (For variants: VT_BSTR) + /// FormatID: (FMTID_Volume) {9B174B35-40FF-11D2-A27E-00C04FC30871}, 4 (PID_VOLUME_FILESYSTEM) (Filesystem Volume Properties) + /// + public static PropertyKey FileSystem + { + get + { + var key = new PropertyKey(new Guid("{9B174B35-40FF-11D2-A27E-00C04FC30871}"), 4); + + return key; + } + } + + /// + /// Name: System.Volume.IsMappedDrive -- PKEY_Volume_IsMappedDrive + /// Description: + /// Type: Boolean -- VT_BOOL + /// FormatID: {149C0B69-2C2D-48FC-808F-D318D78C4636}, 2 + /// + public static PropertyKey IsMappedDrive + { + get + { + var key = new PropertyKey(new Guid("{149C0B69-2C2D-48FC-808F-D318D78C4636}"), 2); + + return key; + } + } + + /// + /// Name: System.Volume.IsRoot -- PKEY_Volume_IsRoot + /// Description: + /// + /// Type: Boolean -- VT_BOOL + /// FormatID: (FMTID_Volume) {9B174B35-40FF-11D2-A27E-00C04FC30871}, 10 (Filesystem Volume Properties) + /// + public static PropertyKey IsRoot + { + get + { + var key = new PropertyKey(new Guid("{9B174B35-40FF-11D2-A27E-00C04FC30871}"), 10); + + return key; + } + } + #endregion + + + + } + #endregion + } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Resources/LocalizedMessages.Designer.cs b/VG Music Studio - WinForms/API/Shell/Resources/LocalizedMessages.Designer.cs new file mode 100644 index 0000000..d197700 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Resources/LocalizedMessages.Designer.cs @@ -0,0 +1,1251 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class LocalizedMessages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal LocalizedMessages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Kermalis.VGMusicStudio.WinForms.API.Shell.Resources.LocalizedMessages", typeof(LocalizedMessages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to AddToMostRecentlyUsedList cannot be changed while dialog is showing.. + /// + internal static string AddToMostRecentlyUsedListCannotBeChanged { + get { + return ResourceManager.GetString("AddToMostRecentlyUsedListCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AlwaysAppendDefaultExtension cannot be changed while dialog is showing.. + /// + internal static string AlwaysAppendDefaultExtensionCannotBeChanged { + get { + return ResourceManager.GetString("AlwaysAppendDefaultExtensionCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Index was outside the bounds of the CommonFileDialogComboBox.. + /// + internal static string ComboBoxIndexOutsideBounds { + get { + return ResourceManager.GetString("ComboBoxIndexOutsideBounds", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File name not available - dialog was canceled.. + /// + internal static string CommonFileDialogCanceled { + get { + return ResourceManager.GetString("CommonFileDialogCanceled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shell item could not be created.. + /// + internal static string CommonFileDialogCannotCreateShellItem { + get { + return ResourceManager.GetString("CommonFileDialogCannotCreateShellItem", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Handle provided cannot be IntPtr.Zero.. + /// + internal static string CommonFileDialogInvalidHandle { + get { + return ResourceManager.GetString("CommonFileDialogInvalidHandle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Multiple files selected - the FileNames property should be used instead.. + /// + internal static string CommonFileDialogMultipleFiles { + get { + return ResourceManager.GetString("CommonFileDialogMultipleFiles", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Multiple files selected - the Items property should be used instead.. + /// + internal static string CommonFileDialogMultipleItems { + get { + return ResourceManager.GetString("CommonFileDialogMultipleItems", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File name not available - dialog has not closed yet.. + /// + internal static string CommonFileDialogNotClosed { + get { + return ResourceManager.GetString("CommonFileDialogNotClosed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Common File Dialog requires Windows Vista or later.. + /// + internal static string CommonFileDialogRequiresVista { + get { + return ResourceManager.GetString("CommonFileDialogRequiresVista", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Office Files. + /// + internal static string CommonFiltersOffice { + get { + return ResourceManager.GetString("CommonFiltersOffice", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All Picture Files. + /// + internal static string CommonFiltersPicture { + get { + return ResourceManager.GetString("CommonFiltersPicture", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Text Files. + /// + internal static string CommonFiltersText { + get { + return ResourceManager.GetString("CommonFiltersText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to CreatePrompt cannot be changed while dialog is showing.. + /// + internal static string CreatePromptCannotBeChanged { + get { + return ResourceManager.GetString("CreatePromptCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Custom controls cannot be removed from a File dialog once added.. + /// + internal static string DialogControlCollectionCannotRemoveControls { + get { + return ResourceManager.GetString("DialogControlCollectionCannotRemoveControls", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Control name cannot be null or zero length.. + /// + internal static string DialogControlCollectionEmptyName { + get { + return ResourceManager.GetString("DialogControlCollectionEmptyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to CommonFileDialogMenuItem controls can only be added to CommonFileDialogMenu controls.. + /// + internal static string DialogControlCollectionMenuItemControlsCannotBeAdded { + get { + return ResourceManager.GetString("DialogControlCollectionMenuItemControlsCannotBeAdded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Modifying controls collection while dialog is showing is not supported.. + /// + internal static string DialogControlCollectionModifyingControls { + get { + return ResourceManager.GetString("DialogControlCollectionModifyingControls", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dialog cannot have more than one control with the same name.. + /// + internal static string DialogControlCollectionMoreThanOneControl { + get { + return ResourceManager.GetString("DialogControlCollectionMoreThanOneControl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dialog control must be removed from current collections first.. + /// + internal static string DialogControlCollectionRemoveControlFirst { + get { + return ResourceManager.GetString("DialogControlCollectionRemoveControlFirst", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to EnsureFileExists cannot be changed while dialog is showing.. + /// + internal static string EnsureFileExistsCannotBeChanged { + get { + return ResourceManager.GetString("EnsureFileExistsCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to EnsurePathExists cannot be changed while dialog is showing.. + /// + internal static string EnsurePathExistsCannotBeChanged { + get { + return ResourceManager.GetString("EnsurePathExistsCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to EnsureReadOnly cannot be changed while dialog is showing.. + /// + internal static string EnsureReadonlyCannotBeChanged { + get { + return ResourceManager.GetString("EnsureReadonlyCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to EnsureValidNames cannot be changed while dialog is showing.. + /// + internal static string EnsureValidNamesCannotBeChanged { + get { + return ResourceManager.GetString("EnsureValidNamesCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Browsing to object failed.. + /// + internal static string ExplorerBrowserBrowseToObjectFailed { + get { + return ResourceManager.GetString("ExplorerBrowserBrowseToObjectFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ExplorerBrowser failed to get current view.. + /// + internal static string ExplorerBrowserFailedToGetView { + get { + return ResourceManager.GetString("ExplorerBrowserFailedToGetView", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to get icon size.. + /// + internal static string ExplorerBrowserIconSize { + get { + return ResourceManager.GetString("ExplorerBrowserIconSize", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unexpected error retrieving item count.. + /// + internal static string ExplorerBrowserItemCount { + get { + return ResourceManager.GetString("ExplorerBrowserItemCount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unexpected error retrieving selected item count.. + /// + internal static string ExplorerBrowserSelectedItemCount { + get { + return ResourceManager.GetString("ExplorerBrowserSelectedItemCount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unexpected error retrieving selection.. + /// + internal static string ExplorerBrowserUnexpectedError { + get { + return ResourceManager.GetString("ExplorerBrowserUnexpectedError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unexpected error retrieving view items.. + /// + internal static string ExplorerBrowserViewItems { + get { + return ResourceManager.GetString("ExplorerBrowserViewItems", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given path does not exist ({0}). + /// + internal static string FilePathNotExist { + get { + return ResourceManager.GetString("FilePathNotExist", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Guid does not identify a known folder.. + /// + internal static string FolderIdsUnknownGuid { + get { + return ResourceManager.GetString("FolderIdsUnknownGuid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ControlPanel Category. + /// + internal static string FolderTypeCategory { + get { + return ResourceManager.GetString("FolderTypeCategory", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ControlPanel Classic. + /// + internal static string FolderTypeClassic { + get { + return ResourceManager.GetString("FolderTypeClassic", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Communications. + /// + internal static string FolderTypeCommunications { + get { + return ResourceManager.GetString("FolderTypeCommunications", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Compressed Folder. + /// + internal static string FolderTypeCompressedFolder { + get { + return ResourceManager.GetString("FolderTypeCompressedFolder", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Contacts. + /// + internal static string FolderTypeContacts { + get { + return ResourceManager.GetString("FolderTypeContacts", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Documents. + /// + internal static string FolderTypeDocuments { + get { + return ResourceManager.GetString("FolderTypeDocuments", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Games. + /// + internal static string FolderTypeGames { + get { + return ResourceManager.GetString("FolderTypeGames", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Generic Library. + /// + internal static string FolderTypeGenericLibrary { + get { + return ResourceManager.GetString("FolderTypeGenericLibrary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid. + /// + internal static string FolderTypeInvalid { + get { + return ResourceManager.GetString("FolderTypeInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Library. + /// + internal static string FolderTypeLibrary { + get { + return ResourceManager.GetString("FolderTypeLibrary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Music. + /// + internal static string FolderTypeMusic { + get { + return ResourceManager.GetString("FolderTypeMusic", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Music Icons. + /// + internal static string FolderTypeMusicIcons { + get { + return ResourceManager.GetString("FolderTypeMusicIcons", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Network Explorer. + /// + internal static string FolderTypeNetworkExplorer { + get { + return ResourceManager.GetString("FolderTypeNetworkExplorer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not Specified. + /// + internal static string FolderTypeNotSpecified { + get { + return ResourceManager.GetString("FolderTypeNotSpecified", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open Search. + /// + internal static string FolderTypeOpenSearch { + get { + return ResourceManager.GetString("FolderTypeOpenSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Other Users. + /// + internal static string FolderTypeOtherUsers { + get { + return ResourceManager.GetString("FolderTypeOtherUsers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pictures. + /// + internal static string FolderTypePictures { + get { + return ResourceManager.GetString("FolderTypePictures", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Printers. + /// + internal static string FolderTypePrinters { + get { + return ResourceManager.GetString("FolderTypePrinters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to RecordedTV. + /// + internal static string FolderTypeRecordedTV { + get { + return ResourceManager.GetString("FolderTypeRecordedTV", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to RecycleBin. + /// + internal static string FolderTypeRecycleBin { + get { + return ResourceManager.GetString("FolderTypeRecycleBin", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Saved Games. + /// + internal static string FolderTypeSavedGames { + get { + return ResourceManager.GetString("FolderTypeSavedGames", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search Connector. + /// + internal static string FolderTypeSearchConnector { + get { + return ResourceManager.GetString("FolderTypeSearchConnector", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Searches. + /// + internal static string FolderTypeSearches { + get { + return ResourceManager.GetString("FolderTypeSearches", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Generic SearchResults. + /// + internal static string FolderTypeSearchResults { + get { + return ResourceManager.GetString("FolderTypeSearchResults", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Software Explorer. + /// + internal static string FolderTypeSoftwareExplorer { + get { + return ResourceManager.GetString("FolderTypeSoftwareExplorer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to User Files. + /// + internal static string FolderTypeUserFiles { + get { + return ResourceManager.GetString("FolderTypeUserFiles", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Users Libraries. + /// + internal static string FolderTypeUserLibraries { + get { + return ResourceManager.GetString("FolderTypeUserLibraries", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Videos. + /// + internal static string FolderTypeVideos { + get { + return ResourceManager.GetString("FolderTypeVideos", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to IsExpandedMode cannot be changed while dialog is showing.. + /// + internal static string IsExpandedModeCannotBeChanged { + get { + return ResourceManager.GetString("IsExpandedModeCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Custom categories cannot be added while recent documents tracking is turned off.. + /// + internal static string JumpListCustomCategoriesDisabled { + get { + return ResourceManager.GetString("JumpListCustomCategoriesDisabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The file type is not registered with this application.. + /// + internal static string JumpListFileTypeNotRegistered { + get { + return ResourceManager.GetString("JumpListFileTypeNotRegistered", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to JumpListLink's path is required and cannot be null.. + /// + internal static string JumpListLinkPathRequired { + get { + return ResourceManager.GetString("JumpListLinkPathRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to JumpListLink's title is required and cannot be null.. + /// + internal static string JumpListLinkTitleRequired { + get { + return ResourceManager.GetString("JumpListLinkTitleRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Negative numbers are not allowed for the ordinal position.. + /// + internal static string JumpListNegativeOrdinalPosition { + get { + return ResourceManager.GetString("JumpListNegativeOrdinalPosition", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Given Known Folder ID is invalid.. + /// + internal static string KnownFolderInvalidGuid { + get { + return ResourceManager.GetString("KnownFolderInvalidGuid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parsing name is invalid.. + /// + internal static string KnownFolderParsingName { + get { + return ResourceManager.GetString("KnownFolderParsingName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creation of window has failed, view inner exception for details.. + /// + internal static string MessageListenerCannotCreateWindow { + get { + return ResourceManager.GetString("MessageListenerCannotCreateWindow", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Window class could not be registered, check inner exception for more details.. + /// + internal static string MessageListenerClassNotRegistered { + get { + return ResourceManager.GetString("MessageListenerClassNotRegistered", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Message filter registration failed.. + /// + internal static string MessageListenerFilterUnableToRegister { + get { + return ResourceManager.GetString("MessageListenerFilterUnableToRegister", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No listener handled of that value is registered.. + /// + internal static string MessageListenerFilterUnknownListenerHandle { + get { + return ResourceManager.GetString("MessageListenerFilterUnknownListenerHandle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot create window on the listener thread because there is no existing window on the listener thread.. + /// + internal static string MessageListenerNoWindowHandle { + get { + return ResourceManager.GetString("MessageListenerNoWindowHandle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to NavigateToShortcut cannot be changed while dialog is showing.. + /// + internal static string NavigateToShortcutCannotBeChanged { + get { + return ResourceManager.GetString("NavigateToShortcutCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parent cannot be null.. + /// + internal static string NavigationLogNullParent { + get { + return ResourceManager.GetString("NavigationLogNullParent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The method or operation is not implemented.. + /// + internal static string NotImplementedException { + get { + return ResourceManager.GetString("NotImplementedException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to OverwritePrompt cannot be changed while dialog is showing.. + /// + internal static string OverwritePromptCannotBeChanged { + get { + return ResourceManager.GetString("OverwritePromptCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This CanonicalName is not a valid index.. + /// + internal static string PropertyCollectionCanonicalInvalidIndex { + get { + return ResourceManager.GetString("PropertyCollectionCanonicalInvalidIndex", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This PropertyKey is not a valid index.. + /// + internal static string PropertyCollectionInvalidIndex { + get { + return ResourceManager.GetString("PropertyCollectionInvalidIndex", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Argument CanonicalName cannot be null or empty.. + /// + internal static string PropertyCollectionNullCanonicalName { + get { + return ResourceManager.GetString("PropertyCollectionNullCanonicalName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Index was outside the bounds of the CommonFileDialogRadioButtonList.. + /// + internal static string RadioButtonListIndexOutOfBounds { + get { + return ResourceManager.GetString("RadioButtonListIndexOutOfBounds", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to RestoreDirectory cannot be changed while dialog is showing.. + /// + internal static string RestoreDirectoryCannotBeChanged { + get { + return ResourceManager.GetString("RestoreDirectoryCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Retrieved a null shell item from dialog.. + /// + internal static string SaveFileNullItem { + get { + return ResourceManager.GetString("SaveFileNullItem", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Given property key is invalid.. + /// + internal static string SearchConditionFactoryInvalidProperty { + get { + return ResourceManager.GetString("SearchConditionFactoryInvalidProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shell Exception has occurred, look at inner exception for information.. + /// + internal static string ShellExceptionDefaultText { + get { + return ResourceManager.GetString("ShellExceptionDefaultText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to GetParsingName has failed.. + /// + internal static string ShellHelperGetParsingNameFailed { + get { + return ResourceManager.GetString("ShellHelperGetParsingNameFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given CanonicalName is not valid.. + /// + internal static string ShellInvalidCanonicalName { + get { + return ResourceManager.GetString("ShellInvalidCanonicalName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to DefaultSaveFolder path not found.. + /// + internal static string ShellLibraryDefaultSaveFolderNotFound { + get { + return ResourceManager.GetString("ShellLibraryDefaultSaveFolderNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to LibraryName cannot be empty.. + /// + internal static string ShellLibraryEmptyName { + get { + return ResourceManager.GetString("ShellLibraryEmptyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Folder path not found.. + /// + internal static string ShellLibraryFolderNotFound { + get { + return ResourceManager.GetString("ShellLibraryFolderNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid FolderType Guid.. + /// + internal static string ShellLibraryInvalidFolderType { + get { + return ResourceManager.GetString("ShellLibraryInvalidFolderType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given known folder is not a valid library.. + /// + internal static string ShellLibraryInvalidLibrary { + get { + return ResourceManager.GetString("ShellLibraryInvalidLibrary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Can't get the display name.. + /// + internal static string ShellObjectCannotGetDisplayName { + get { + return ResourceManager.GetString("ShellObjectCannotGetDisplayName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Destination array too small, or invalid arrayIndex.. + /// + internal static string ShellObjectCollectionArrayTooSmall { + get { + return ResourceManager.GetString("ShellObjectCollectionArrayTooSmall", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Must have at least one shell object in the collection.. + /// + internal static string ShellObjectCollectionEmptyCollection { + get { + return ResourceManager.GetString("ShellObjectCollectionEmptyCollection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot insert items into a read only list.. + /// + internal static string ShellObjectCollectionInsertReadOnly { + get { + return ResourceManager.GetString("ShellObjectCollectionInsertReadOnly", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot remove items from a read only list.. + /// + internal static string ShellObjectCollectionRemoveReadOnly { + get { + return ResourceManager.GetString("ShellObjectCollectionRemoveReadOnly", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shell item could not be created.. + /// + internal static string ShellObjectCreationFailed { + get { + return ResourceManager.GetString("ShellObjectCreationFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shell Object creation requires Windows Vista or higher operating system.. + /// + internal static string ShellObjectFactoryPlatformNotSupported { + get { + return ResourceManager.GetString("ShellObjectFactoryPlatformNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to Create Shell Item.. + /// + internal static string ShellObjectFactoryUnableToCreateItem { + get { + return ResourceManager.GetString("ShellObjectFactoryUnableToCreateItem", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registration for change notification has failed.. + /// + internal static string ShellObjectWatcherRegisterFailed { + get { + return ResourceManager.GetString("ShellObjectWatcherRegisterFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to change watched events while listening.. + /// + internal static string ShellObjectWatcherUnableToChangeEvents { + get { + return ResourceManager.GetString("ShellObjectWatcherUnableToChangeEvents", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value on this property cannot be set. To set the property value, use the ShellObject that is associated with this property.. + /// + internal static string ShellPropertyCannotSetProperty { + get { + return ResourceManager.GetString("ShellPropertyCannotSetProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No constructor found matching requested argument types.. + /// + internal static string ShellPropertyFactoryConstructorNotFound { + get { + return ResourceManager.GetString("ShellPropertyFactoryConstructorNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to set property.. + /// + internal static string ShellPropertySetValue { + get { + return ResourceManager.GetString("ShellPropertySetValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to get writable property store for this property.. + /// + internal static string ShellPropertyUnableToGetWritableProperty { + get { + return ResourceManager.GetString("ShellPropertyUnableToGetWritableProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A value had to be truncated in a string or rounded if a numeric value. Set AllowTruncatedValue to true to prevent this exception.. + /// + internal static string ShellPropertyValueTruncated { + get { + return ResourceManager.GetString("ShellPropertyValueTruncated", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This Property is available on Windows 7 only.. + /// + internal static string ShellPropertyWindows7 { + get { + return ResourceManager.GetString("ShellPropertyWindows7", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This property only accepts a value of type \"{0}\".. + /// + internal static string ShellPropertyWrongType { + get { + return ResourceManager.GetString("ShellPropertyWrongType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to set list of sort columns.. + /// + internal static string ShellSearchFolderUnableToSetSortColumns { + get { + return ResourceManager.GetString("ShellSearchFolderUnableToSetSortColumns", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to set visible columns.. + /// + internal static string ShellSearchFolderUnableToSetVisibleColumns { + get { + return ResourceManager.GetString("ShellSearchFolderUnableToSetVisibleColumns", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to CurrentSize (width or height) cannot be greater than the maximum size: {0}.. + /// + internal static string ShellThumbnailCurrentSizeRange { + get { + return ResourceManager.GetString("ShellThumbnailCurrentSizeRange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The current ShellObject does not have a thumbnail. Try using ShellThumbnailFormatOption.Default to get the icon for this item.. + /// + internal static string ShellThumbnailDoesNotHaveThumbnail { + get { + return ResourceManager.GetString("ShellThumbnailDoesNotHaveThumbnail", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The current ShellObject does not have a valid thumbnail handler or there was a problem in extracting the thumbnail for this specific shell object.. + /// + internal static string ShellThumbnailNoHandler { + get { + return ResourceManager.GetString("ShellThumbnailNoHandler", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to CurrentSize (width or height) cannot be 0.. + /// + internal static string ShellThumbnailSizeCannotBe0 { + get { + return ResourceManager.GetString("ShellThumbnailSizeCannotBe0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ShowHiddenItems cannot be changed while dialog is showing.. + /// + internal static string ShowHiddenItemsCannotBeChanged { + get { + return ResourceManager.GetString("ShowHiddenItemsCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show places list cannot be changed while dialog is showing.. + /// + internal static string ShowPlacesListCannotBeChanged { + get { + return ResourceManager.GetString("ShowPlacesListCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Stock Icon identifier given is invalid ({0}).. + /// + internal static string StockIconInvalidGuid { + get { + return ResourceManager.GetString("StockIconInvalidGuid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Child control's window handle cannot be zero.. + /// + internal static string TabbedThumbnailZeroChildHandle { + get { + return ResourceManager.GetString("TabbedThumbnailZeroChildHandle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parent window handle cannot be zero.. + /// + internal static string TabbedThumbnailZeroParentHandle { + get { + return ResourceManager.GetString("TabbedThumbnailZeroParentHandle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to TabbedThumbnailProxyWindow has not been set.. + /// + internal static string TasbarWindowProxyWindowSet { + get { + return ResourceManager.GetString("TasbarWindowProxyWindowSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A valid active Window is needed to update the Taskbar.. + /// + internal static string TaskbarManagerValidWindowRequired { + get { + return ResourceManager.GetString("TaskbarManagerValidWindowRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The array of buttons must contain at least 1 item.. + /// + internal static string TaskbarWindowEmptyButtonArray { + get { + return ResourceManager.GetString("TaskbarWindowEmptyButtonArray", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Tool bar buttons for this window are already added. Please refer to the Remarks section of the AddButtons method for more information on updating the properties or hiding existing buttons.. + /// + internal static string TaskbarWindowManagerButtonsAlreadyAdded { + get { + return ResourceManager.GetString("TaskbarWindowManagerButtonsAlreadyAdded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value is already set. It cannot be set more than once.. + /// + internal static string TaskbarWindowValueSet { + get { + return ResourceManager.GetString("TaskbarWindowValueSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given control has not been added to the taskbar.. + /// + internal static string ThumbnailManagerControlNotAdded { + get { + return ResourceManager.GetString("ThumbnailManagerControlNotAdded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Window handle is invalid.. + /// + internal static string ThumbnailManagerInvalidHandle { + get { + return ResourceManager.GetString("ThumbnailManagerInvalidHandle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This preview has already been added.. + /// + internal static string ThumbnailManagerPreviewAdded { + get { + return ResourceManager.GetString("ThumbnailManagerPreviewAdded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given preview has not been added to the taskbar.. + /// + internal static string ThumbnailManagerPreviewNotAdded { + get { + return ResourceManager.GetString("ThumbnailManagerPreviewNotAdded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maximum number of buttons allowed is 7.. + /// + internal static string ThumbnailToolbarManagerMaxButtons { + get { + return ResourceManager.GetString("ThumbnailToolbarManagerMaxButtons", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Null or empty arrays are not allowed.. + /// + internal static string ThumbnailToolbarManagerNullEmptyArray { + get { + return ResourceManager.GetString("ThumbnailToolbarManagerNullEmptyArray", resourceCulture); + } + } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Resources/LocalizedMessages.resx b/VG Music Studio - WinForms/API/Shell/Resources/LocalizedMessages.resx new file mode 100644 index 0000000..0beed52 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Resources/LocalizedMessages.resx @@ -0,0 +1,516 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + AddToMostRecentlyUsedList cannot be changed while dialog is showing. + + + AlwaysAppendDefaultExtension cannot be changed while dialog is showing. + + + Index was outside the bounds of the CommonFileDialogComboBox. + + + File name not available - dialog was canceled. + + + Shell item could not be created. + + + Handle provided cannot be IntPtr.Zero. + + + Multiple files selected - the FileNames property should be used instead. + + + Multiple files selected - the Items property should be used instead. + + + File name not available - dialog has not closed yet. + + + Common File Dialog requires Windows Vista or later. + + + Office Files + + + All Picture Files + + + Text Files + + + CreatePrompt cannot be changed while dialog is showing. + + + Custom controls cannot be removed from a File dialog once added. + + + Control name cannot be null or zero length. + + + CommonFileDialogMenuItem controls can only be added to CommonFileDialogMenu controls. + + + Modifying controls collection while dialog is showing is not supported. + + + Dialog cannot have more than one control with the same name. + + + Dialog control must be removed from current collections first. + + + EnsureFileExists cannot be changed while dialog is showing. + + + EnsurePathExists cannot be changed while dialog is showing. + + + EnsureReadOnly cannot be changed while dialog is showing. + + + EnsureValidNames cannot be changed while dialog is showing. + + + ExplorerBrowser failed to get current view. + + + Unable to get icon size. + + + Unexpected error retrieving item count. + + + Unexpected error retrieving selected item count. + + + Unexpected error retrieving selection. + + + Unexpected error retrieving view items. + + + The given path does not exist ({0}) + + + Guid does not identify a known folder. + + + ControlPanel Category + + + ControlPanel Classic + + + Communications + + + Compressed Folder + + + Contacts + + + Documents + + + Games + + + Generic Library + + + Invalid + + + Library + + + Music + + + Music Icons + + + Network Explorer + + + Not Specified + + + Open Search + + + Other Users + + + Pictures + + + Printers + + + RecordedTV + + + RecycleBin + + + Saved Games + + + Search Connector + + + Searches + + + Generic SearchResults + + + Software Explorer + + + User Files + + + Users Libraries + + + Videos + + + IsExpandedMode cannot be changed while dialog is showing. + + + Custom categories cannot be added while recent documents tracking is turned off. + + + The file type is not registered with this application. + + + JumpListLink's path is required and cannot be null. + + + JumpListLink's title is required and cannot be null. + + + Negative numbers are not allowed for the ordinal position. + + + Given Known Folder ID is invalid. + + + Parsing name is invalid. + + + NavigateToShortcut cannot be changed while dialog is showing. + + + Parent cannot be null. + + + The method or operation is not implemented. + + + OverwritePrompt cannot be changed while dialog is showing. + + + This CanonicalName is not a valid index. + + + This PropertyKey is not a valid index. + + + Argument CanonicalName cannot be null or empty. + + + Index was outside the bounds of the CommonFileDialogRadioButtonList. + + + RestoreDirectory cannot be changed while dialog is showing. + + + Retrieved a null shell item from dialog. + + + Given property key is invalid. + + + Shell Exception has occurred, look at inner exception for information. + + + GetParsingName has failed. + + + The given CanonicalName is not valid. + + + DefaultSaveFolder path not found. + + + LibraryName cannot be empty. + + + Folder path not found. + + + Invalid FolderType Guid. + + + The given known folder is not a valid library. + + + Can't get the display name. + + + Destination array too small, or invalid arrayIndex. + + + Must have at least one shell object in the collection. + + + Cannot insert items into a read only list. + + + Cannot remove items from a read only list. + + + Shell item could not be created. + + + Shell Object creation requires Windows Vista or higher operating system. + + + Unable to Create Shell Item. + + + The value on this property cannot be set. To set the property value, use the ShellObject that is associated with this property. + + + Unable to set property. + + + Unable to get writable property store for this property. + + + A value had to be truncated in a string or rounded if a numeric value. Set AllowTruncatedValue to true to prevent this exception. + + + This Property is available on Windows 7 only. + + + This property only accepts a value of type \"{0}\". + + + Unable to set list of sort columns. + + + Unable to set visible columns. + + + CurrentSize (width or height) cannot be greater than the maximum size: {0}. + + + The current ShellObject does not have a thumbnail. Try using ShellThumbnailFormatOption.Default to get the icon for this item. + + + The current ShellObject does not have a valid thumbnail handler or there was a problem in extracting the thumbnail for this specific shell object. + + + CurrentSize (width or height) cannot be 0. + + + ShowHiddenItems cannot be changed while dialog is showing. + + + Show places list cannot be changed while dialog is showing. + + + The Stock Icon identifier given is invalid ({0}). + + + Child control's window handle cannot be zero. + + + Parent window handle cannot be zero. + + + TabbedThumbnailProxyWindow has not been set. + + + A valid active Window is needed to update the Taskbar. + + + The array of buttons must contain at least 1 item. + + + Tool bar buttons for this window are already added. Please refer to the Remarks section of the AddButtons method for more information on updating the properties or hiding existing buttons. + + + Value is already set. It cannot be set more than once. + + + The given control has not been added to the taskbar. + + + Window handle is invalid. + + + This preview has already been added. + + + The given preview has not been added to the taskbar. + + + Maximum number of buttons allowed is 7. + + + Null or empty arrays are not allowed. + + + Browsing to object failed. + + + Registration for change notification has failed. + + + No constructor found matching requested argument types. + + + Cannot create window on the listener thread because there is no existing window on the listener thread. + + + Window class could not be registered, check inner exception for more details. + + + Message filter registration failed. + + + No listener handled of that value is registered. + + + Creation of window has failed, view inner exception for details. + + + Unable to change watched events while listening. + + \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/ShellObjectWatcher/ShellObjectWatcherEnums.cs b/VG Music Studio - WinForms/API/Shell/ShellObjectWatcher/ShellObjectWatcherEnums.cs new file mode 100644 index 0000000..5005528 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/ShellObjectWatcher/ShellObjectWatcherEnums.cs @@ -0,0 +1,187 @@ +using System; + +namespace Kermalis.VGMusicStudio.WinForms.API.Shell +{ + /// + /// Describes the event that has occurred. + /// Typically, only one event is specified at a time. + /// If more than one event is specified, + /// the values contained in the dwItem1 and dwItem2 parameters must be the same, + /// respectively, for all specified events. + /// This parameter can be one or more of the following values: + /// + [Flags] + public enum ShellObjectChangeTypes + { + /// + /// None + /// + None = 0, + + /// + /// The name of a nonfolder item has changed. + /// SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the previous PIDL or name of the item. + /// dwItem2 contains the new PIDL or name of the item. + /// + ItemRename = 0x00000001, + + /// + /// A nonfolder item has been created. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the item that was created. + /// dwItem2 is not used and should be NULL. + /// + ItemCreate = 0x00000002, + + /// + /// A nonfolder item has been deleted. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the item that was deleted. + /// dwItem2 is not used and should be NULL. + /// + ItemDelete = 0x00000004, + + /// + /// A folder has been created. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the folder that was created. + /// dwItem2 is not used and should be NULL. + /// + DirectoryCreate = 0x00000008, + + /// + /// A folder has been removed. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the folder that was removed. + /// dwItem2 is not used and should be NULL. + /// + DirectoryDelete = 0x00000010, + + /// + /// Storage media has been inserted into a drive. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the root of the drive that contains the new media. + /// dwItem2 is not used and should be NULL. + /// + MediaInsert = 0x00000020, + + /// + /// Storage media has been removed from a drive. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the root of the drive from which the media was removed. + /// dwItem2 is not used and should be NULL. + /// + MediaRemove = 0x00000040, + + /// + /// A drive has been removed. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the root of the drive that was removed. + /// dwItem2 is not used and should be NULL. + /// + DriveRemove = 0x00000080, + + /// + /// A drive has been added. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the root of the drive that was added. + /// dwItem2 is not used and should be NULL. + /// + DriveAdd = 0x00000100, + + /// + /// A folder on the local computer is being shared via the network. + /// SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the folder that is being shared. + /// dwItem2 is not used and should be NULL. + /// + NetShare = 0x00000200, + + /// + /// A folder on the local computer is no longer being shared via the network. + /// SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the folder that is no longer being shared. + /// dwItem2 is not used and should be NULL. + /// + NetUnshare = 0x00000400, + + /// + /// The attributes of an item or folder have changed. + /// SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the item or folder that has changed. + /// dwItem2 is not used and should be NULL. + /// + AttributesChange = 0x00000800, + + /// + /// The contents of an existing folder have changed, but the folder still exists and has not been renamed. + /// SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the folder that has changed. + /// dwItem2 is not used and should be NULL. + /// If a folder has been created, deleted, or renamed, use SHCNE_MKDIR, SHCNE_RMDIR, or SHCNE_RENAMEFOLDER, respectively. + /// + DirectoryContentsUpdate = 0x00001000, + + /// + /// An existing item (a folder or a nonfolder) has changed, but the item still exists and has not been renamed. + /// SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the item that has changed. + /// dwItem2 is not used and should be NULL. + /// If a nonfolder item has been created, deleted, or renamed, + /// use SHCNE_CREATE, SHCNE_DELETE, or SHCNE_RENAMEITEM, respectively, instead. + /// + Update = 0x00002000, + + /// + /// The computer has disconnected from a server. + /// SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the server from which the computer was disconnected. + /// dwItem2 is not used and should be NULL. + /// + ServerDisconnect = 0x00004000, + + /// + /// An image in the system image list has changed. + /// SHCNF_DWORD must be specified in uFlags. + /// dwItem1 is not used and should be NULL. + /// dwItem2 contains the index in the system image list that has changed. + /// //verify this is not opposite? + SystemImageUpdate = 0x00008000, + + /// + /// The name of a folder has changed. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the previous PIDL or name of the folder. + /// dwItem2 contains the new PIDL or name of the folder. + /// + DirectoryRename = 0x00020000, + + /// + /// The amount of free space on a drive has changed. + /// SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. + /// dwItem1 contains the root of the drive on which the free space changed. + /// dwItem2 is not used and should be NULL. + /// + FreeSpace = 0x00040000, + + /// + /// A file type association has changed. + /// SHCNF_IDLIST must be specified in the uFlags parameter. + /// dwItem1 and dwItem2 are not used and must be NULL. + /// + AssociationChange = 0x08000000, + + /// + /// Specifies a combination of all of the disk event identifiers. + /// + DiskEventsMask = 0x0002381F, + + /// + /// Specifies a combination of all of the global event identifiers. + /// + GlobalEventsMask = 0x0C0581E0, + + /// + /// All events have occurred. + /// + AllEventsMask = 0x7FFFFFFF, + + /// + /// The specified event occurred as a result of a system interrupt. + /// As this value modifies other event values, it cannot be used alone. + /// + FromInterrupt = unchecked((int)0x80000000), + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/JumpList.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpList.cs new file mode 100644 index 0000000..508a25c --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpList.cs @@ -0,0 +1,555 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell; +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Represents an instance of a Taskbar button jump list. + /// + public class JumpList + { + + /// + /// Create a JumpList for the application's taskbar button. + /// + /// A new JumpList that is associated with the app id of the main application window + /// If there are any other child (top-level) windows for this application and they don't have + /// a specific JumpList created for them, they all will share the same JumpList as the main application window. + /// In order to have a individual JumpList for a top-level window, use the overloaded method CreateJumpListForIndividualWindow. + public static JumpList CreateJumpList() => new JumpList(TaskbarManager.Instance.ApplicationId); + + /// + /// Create a JumpList for the application's taskbar button. + /// + /// Application Id for the individual window. This must be unique for each top-level window in order to have a individual JumpList. + /// Handle of the window associated with the new JumpList + /// A new JumpList that is associated with the specific window handle + public static JumpList CreateJumpListForIndividualWindow(string appId, IntPtr windowHandle) => new JumpList(appId, windowHandle); + + /// + /// Create a JumpList for the application's taskbar button. + /// + /// Application Id for the individual window. This must be unique for each top-level window in order to have a individual JumpList. + /// WPF Window associated with the new JumpList + /// A new JumpList that is associated with the specific WPF window + public static JumpList CreateJumpListForIndividualWindow(string appId, System.Windows.Window window) => new JumpList(appId, window); + + // Best practice recommends defining a private object to lock on + private readonly object syncLock = new object(); + + // Native implementation of destination list + private readonly ICustomDestinationList customDestinationList; + + #region Properties + + private JumpListCustomCategoryCollection customCategoriesCollection; + /// + /// Adds a collection of custom categories to the Taskbar jump list. + /// + /// The catagories to add to the jump list. + public void AddCustomCategories(params JumpListCustomCategory[] customCategories) + { + lock (syncLock) + { + if (customCategoriesCollection == null) + { + customCategoriesCollection = new JumpListCustomCategoryCollection(); + } + } + + if (customCategories != null) + { + foreach (var category in customCategories) + { + customCategoriesCollection.Add(category); + } + } + } + + private JumpListItemCollection userTasks; + /// + /// Adds user tasks to the Taskbar JumpList. User tasks can only consist of JumpListTask or + /// JumpListSeparator objects. + /// + /// The user tasks to add to the JumpList. + public void AddUserTasks(params JumpListTask[] tasks) + { + if (userTasks == null) + { + // Make sure that we don't create multiple instances + // of this object + lock (syncLock) + { + if (userTasks == null) + { + userTasks = new JumpListItemCollection(); + } + } + } + + if (tasks != null) + { + foreach (var task in tasks) + { + userTasks.Add(task); + } + } + } + + /// + /// Removes all user tasks that have been added. + /// + public void ClearAllUserTasks() + { + if (userTasks != null) + { + userTasks.Clear(); + } + } + + /// + /// Gets the recommended number of items to add to the jump list. + /// + /// + /// This number doesn’t + /// imply or suggest how many items will appear on the jump list. + /// This number should only be used for reference purposes since + /// the actual number of slots in the jump list can change after the last + /// refresh due to items being pinned or removed and resolution changes. + /// The jump list can increase in size accordingly. + /// + public uint MaxSlotsInList + { + get + { + // Because we need the correct number for max slots, start a commit, get the max slots + // and then abort. If we wait until the user calls RefreshTaskbarlist(), it will be too late. + // The user needs to use this number before they update the jumplist. + + // default + + // Native call to start adding items to the taskbar destination list + var hr = customDestinationList.BeginList( + out var maxSlotsInList, + ref TaskbarNativeMethods.TaskbarGuids.IObjectArray, + out var removedItems); + + if (CoreErrorHelper.Succeeded(hr)) + { + customDestinationList.AbortList(); + } + + return maxSlotsInList; + } + } + + /// + /// Gets or sets the type of known categories to display. + /// + public JumpListKnownCategoryType KnownCategoryToDisplay { get; set; } + + private int knownCategoryOrdinalPosition; + /// + /// Gets or sets the value for the known category location relative to the + /// custom category collection. + /// + public int KnownCategoryOrdinalPosition + { + get => knownCategoryOrdinalPosition; + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException("value", LocalizedMessages.JumpListNegativeOrdinalPosition); + } + + knownCategoryOrdinalPosition = value; + } + + } + + /// + /// Gets or sets the application ID to use for this jump list. + /// + public string ApplicationId { get; private set; } + + #endregion + + /// + /// Creates a new instance of the JumpList class with the specified + /// appId. The JumpList is associated with the main window of the application. + /// + /// Application Id to use for this instace. + internal JumpList(string appID) + : this(appID, TaskbarManager.Instance.OwnerHandle) + { + } + + /// + /// Creates a new instance of the JumpList class with the specified + /// appId. The JumpList is associated with the given WPF Window. + /// + /// Application Id to use for this instace. + /// WPF Window that is associated with this JumpList + internal JumpList(string appID, System.Windows.Window window) + : this(appID, (new System.Windows.Interop.WindowInteropHelper(window)).Handle) + { + } + + /// + /// Creates a new instance of the JumpList class with the specified + /// appId. The JumpList is associated with the given window. + /// + /// Application Id to use for this instace. + /// Window handle for the window that is associated with this JumpList + private JumpList(string appID, IntPtr windowHandle) + { + // Throw exception if not running on Win7 or newer + CoreHelpers.ThrowIfNotWin7(); + + // Native implementation of destination list + customDestinationList = (ICustomDestinationList)new CDestinationList(); + + // Set application user model ID + if (!string.IsNullOrEmpty(appID)) + { + ApplicationId = appID; + + // If the user hasn't yet set the application id for the whole process, + // use the first JumpList's AppId for the whole process. This will ensure + // we have the same JumpList for all the windows (unless user overrides and creates a new + // JumpList for a specific child window) + if (!TaskbarManager.Instance.ApplicationIdSetProcessWide) + { + TaskbarManager.Instance.ApplicationId = appID; + } + + TaskbarManager.Instance.SetApplicationIdForSpecificWindow(windowHandle, appID); + } + } + + /// + /// Reports document usage to the shell. + /// + /// The full path of the file to report usage. + public static void AddToRecent(string destination) => TaskbarNativeMethods.SHAddToRecentDocs(destination); + + /// + /// Commits the pending JumpList changes and refreshes the Taskbar. + /// + /// Will throw if the type of the file being added to the JumpList is not registered with the application. + /// Will throw if recent documents tracking is turned off by the user or via group policy. + /// Will throw if updating the JumpList fails for any other reason. + public void Refresh() + { + // Let the taskbar know which specific jumplist we are updating + if (!string.IsNullOrEmpty(ApplicationId)) + { + customDestinationList.SetAppID(ApplicationId); + } + + // Begins rendering on the taskbar destination list + BeginList(); + + Exception exception = null; + + try + { + // try to add the user tasks first + AppendTaskList(); + } + catch (Exception e) + { + // If this fails, save the exception but don't throw it yet. + // We need to continue to try and add the custom categories + exception = e; + } + + // Even it fails, continue appending the custom categories + try + { + // Add custom categories + AppendCustomCategories(); + } + finally + { + // End rendering of the taskbar destination list + customDestinationList.CommitList(); + } + + // If an exception was thrown while adding the user tasks or + // custom categories, throw it. + if (exception != null) + { + throw exception; + } + } + + private void BeginList() + { + // Get list of removed items from native code + // default + + // Native call to start adding items to the taskbar destination list + var hr = customDestinationList.BeginList( + out var maxSlotsInList, + ref TaskbarNativeMethods.TaskbarGuids.IObjectArray, + out var removedItems); + + if (!CoreErrorHelper.Succeeded(hr)) + { + throw new ShellException(hr); + } + + // Process the deleted items + IEnumerable removedItemsArray = ProcessDeletedItems((IObjectArray)removedItems); + + // Raise the event if items were removed + if (JumpListItemsRemoved != null && removedItemsArray != null && removedItemsArray.GetEnumerator().MoveNext()) + { + JumpListItemsRemoved(this, new UserRemovedJumpListItemsEventArgs(removedItemsArray)); + } + } + + /// + /// Occurs when items are removed from the Taskbar's jump list since the last + /// refresh. + /// + /// + /// This event is not triggered + /// immediately when a user removes an item from the jump list but rather + /// when the application refreshes the task bar list directly. + /// + public event EventHandler JumpListItemsRemoved = delegate { }; + + /// + /// Retrieves the current list of destinations that have been removed from the existing jump list by the user. + /// The removed destinations may become items on a custom jump list. + /// + /// A collection of items (filenames) removed from the existing jump list by the user. + public IEnumerable RemovedDestinations + { + get + { + // Get list of removed items from native code + + customDestinationList.GetRemovedDestinations(ref TaskbarNativeMethods.TaskbarGuids.IObjectArray, out var removedItems); + + return ProcessDeletedItems((IObjectArray)removedItems); + } + } + + private IEnumerable ProcessDeletedItems(IObjectArray removedItems) + { + var removedItemsArray = new List(); + + removedItems.GetCount(out var count); + + // Process each removed item based on its type + for (uint i = 0; i < count; i++) + { + // Native call to retrieve objects from IObjectArray + removedItems.GetAt(i, + ref TaskbarNativeMethods.TaskbarGuids.IUnknown, + out var item); + + var shellItem = item as IShellItem; + IShellLinkW shellLink; + // Process item + if (shellItem != null) + { + removedItemsArray.Add(RemoveCustomCategoryItem(shellItem)); + } + else if ((shellLink = item as IShellLinkW) != null) + { + removedItemsArray.Add(RemoveCustomCategoryLink(shellLink)); + } + } + return removedItemsArray; + } + + private string RemoveCustomCategoryItem(IShellItem item) + { + string path = null; + + if (customCategoriesCollection != null) + { + var pszString = IntPtr.Zero; + var hr = item.GetDisplayName(ShellNativeMethods.ShellItemDesignNameOptions.FileSystemPath, out pszString); + if (hr == HResult.Ok && pszString != IntPtr.Zero) + { + path = Marshal.PtrToStringAuto(pszString); + // Free the string + Marshal.FreeCoTaskMem(pszString); + } + + // Remove this item from each category + foreach (var category in customCategoriesCollection) + { + category.RemoveJumpListItem(path); + } + + } + + return path; + } + + + private string RemoveCustomCategoryLink(IShellLinkW link) + { + string path = null; + + if (customCategoriesCollection != null) + { + var sb = new StringBuilder(256); + link.GetPath(sb, sb.Capacity, IntPtr.Zero, 2); + + path = sb.ToString(); + + // Remove this item from each category + foreach (var category in customCategoriesCollection) + { + category.RemoveJumpListItem(path); + } + } + + return path; + } + + private void AppendCustomCategories() + { + // Initialize our current index in the custom categories list + var currentIndex = 0; + + // Keep track whether we add the Known Categories to our list + var knownCategoriesAdded = false; + + if (customCategoriesCollection != null) + { + // Append each category to list + foreach (var category in customCategoriesCollection) + { + // If our current index is same as the KnownCategory OrdinalPosition, + // append the Known Categories + if (!knownCategoriesAdded && currentIndex == KnownCategoryOrdinalPosition) + { + AppendKnownCategories(); + knownCategoriesAdded = true; + } + + // Don't process empty categories + if (category.JumpListItems.Count == 0) { continue; } + + var categoryContent = + (IObjectCollection)new CEnumerableObjectCollection(); + + // Add each link's shell representation to the object array + foreach (var link in category.JumpListItems) + { + var listItem = link as JumpListItem; + var listLink = link as JumpListLink; + if (listItem != null) + { + categoryContent.AddObject(listItem.NativeShellItem); + } + else if (listLink != null) + { + categoryContent.AddObject(listLink.NativeShellLink); + } + } + + // Add current category to destination list + var hr = customDestinationList.AppendCategory( + category.Name, + (IObjectArray)categoryContent); + + if (!CoreErrorHelper.Succeeded(hr)) + { + if ((uint)hr == 0x80040F03) + { + throw new InvalidOperationException(LocalizedMessages.JumpListFileTypeNotRegistered); + } + else if ((uint)hr == 0x80070005 /*E_ACCESSDENIED*/) + { + // If the recent documents tracking is turned off by the user, + // custom categories or items to an existing category cannot be added. + // The recent documents tracking can be changed via: + // 1. Group Policy “Do not keep history of recently opened documents”. + // 2. Via the user setting “Store and display recently opened items in + // the Start menu and the taskbar” in the Start menu property dialog. + // + throw new UnauthorizedAccessException(LocalizedMessages.JumpListCustomCategoriesDisabled); + } + + throw new ShellException(hr); + } + + // Increase our current index + currentIndex++; + } + } + + // If the ordinal position was out of range, append the Known Categories + // at the end + if (!knownCategoriesAdded) + { + AppendKnownCategories(); + } + } + + private void AppendTaskList() + { + if (userTasks == null || userTasks.Count == 0) { return; } + + var taskContent = + (IObjectCollection)new CEnumerableObjectCollection(); + + // Add each task's shell representation to the object array + foreach (var task in userTasks) + { + JumpListSeparator seperator; + var link = task as JumpListLink; + if (link != null) + { + taskContent.AddObject(link.NativeShellLink); + } + else if ((seperator = task as JumpListSeparator) != null) + { + taskContent.AddObject(seperator.NativeShellLink); + } + } + + // Add tasks to the taskbar + var hr = customDestinationList.AddUserTasks((IObjectArray)taskContent); + + if (!CoreErrorHelper.Succeeded(hr)) + { + if ((uint)hr == 0x80040F03) + { + throw new InvalidOperationException(LocalizedMessages.JumpListFileTypeNotRegistered); + } + throw new ShellException(hr); + } + } + + private void AppendKnownCategories() + { + if (KnownCategoryToDisplay == JumpListKnownCategoryType.Recent) + { + customDestinationList.AppendKnownCategory(KnownDestinationCategory.Recent); + } + else if (KnownCategoryToDisplay == JumpListKnownCategoryType.Frequent) + { + customDestinationList.AppendKnownCategory(KnownDestinationCategory.Frequent); + } + } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListCustomCategory.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListCustomCategory.cs new file mode 100644 index 0000000..dad10dd --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListCustomCategory.cs @@ -0,0 +1,89 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Represents a custom category on the taskbar's jump list + /// + public class JumpListCustomCategory + { + private string name; + + internal JumpListItemCollection JumpListItems + { + get; + private set; + } + + /// + /// Category name + /// + public string Name + { + get => name; + set + { + if (value != name) + { + name = value; + CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } + } + + + /// + /// Add JumpList items for this category + /// + /// The items to add to the JumpList. + public void AddJumpListItems(params IJumpListItem[] items) + { + if (items != null) + { + foreach (var item in items) + { + JumpListItems.Add(item); + } + } + } + + /// + /// Event that is triggered when the jump list collection is modified + /// + internal event NotifyCollectionChangedEventHandler CollectionChanged = delegate { }; + + /// + /// Creates a new custom category instance + /// + /// Category name + public JumpListCustomCategory(string categoryName) + { + Name = categoryName; + + JumpListItems = new JumpListItemCollection(); + JumpListItems.CollectionChanged += OnJumpListCollectionChanged; + } + + internal void OnJumpListCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) => CollectionChanged(this, args); + + + internal void RemoveJumpListItem(string path) + { + var itemsToRemove = new List( + from i in JumpListItems + where string.Equals(path, i.Path, StringComparison.OrdinalIgnoreCase) + select i); + + // Remove matching items + for (var i = 0; i < itemsToRemove.Count; i++) + { + JumpListItems.Remove(itemsToRemove[i]); + } + } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListCustomCategoryCollection.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListCustomCategoryCollection.cs new file mode 100644 index 0000000..5e0ac2a --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListCustomCategoryCollection.cs @@ -0,0 +1,120 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Represents a collection of custom categories + /// + internal class JumpListCustomCategoryCollection + : ICollection, INotifyCollectionChanged + { + private readonly List categories = new List(); + + /// + /// Event to trigger anytime this collection is modified + /// + public event NotifyCollectionChangedEventHandler CollectionChanged = delegate { }; + + /// + /// Determines if this collection is read-only + /// + public bool IsReadOnly { get; set; } + + /// + /// The number of items in this collection + /// + public int Count => categories.Count; + + /// + /// Add the specified category to this collection + /// + /// Category to add + public void Add(JumpListCustomCategory category) + { + if (category == null) + { + throw new ArgumentNullException("category"); + } + categories.Add(category); + + // Trigger CollectionChanged event + CollectionChanged( + this, + new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Add, + category)); + + // Make sure that a collection changed event is fire if this category + // or it's corresponding jumplist is modified + category.CollectionChanged += CollectionChanged; + category.JumpListItems.CollectionChanged += CollectionChanged; + } + + /// + /// Remove the specified category from this collection + /// + /// Category item to remove + /// True if item was removed. + public bool Remove(JumpListCustomCategory category) + { + var removed = categories.Remove(category); + + if (removed == true) + { + // Trigger CollectionChanged event + CollectionChanged( + this, + new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Remove, + 0)); + } + + return removed; + } + + /// + /// Clear all items from the collection + /// + public void Clear() + { + categories.Clear(); + + CollectionChanged( + this, + new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Reset)); + } + + /// + /// Determine if this collection contains the specified item + /// + /// Category to search for + /// True if category was found + public bool Contains(JumpListCustomCategory category) => categories.Contains(category); + + /// + /// Copy this collection to a compatible one-dimensional array, + /// starting at the specified index of the target array + /// + /// Array to copy to + /// Index of target array to start copy + public void CopyTo(JumpListCustomCategory[] array, int index) => categories.CopyTo(array, index); + + /// + /// Returns an enumerator that iterates through this collection. + /// + /// Enumerator to iterate through this collection. + IEnumerator IEnumerable.GetEnumerator() => categories.GetEnumerator(); + + /// + /// Returns an enumerator that iterates through this collection. + /// + /// Enumerator to iterate through this collection. + IEnumerator IEnumerable.GetEnumerator() => categories.GetEnumerator(); + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListItem.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListItem.cs new file mode 100644 index 0000000..9edf639 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListItem.cs @@ -0,0 +1,33 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Represents a jump list item. + /// + public class JumpListItem : ShellFile, IJumpListItem + { + /// + /// Creates a jump list item with the specified path. + /// + /// The path to the jump list item. + /// The file type should associate the given file + /// with the calling application. + public JumpListItem(string path) : base(path) { } + + #region IJumpListItem Members + + /// + /// Gets or sets the target path for this jump list item. + /// + public new string Path + { + get => base.Path; + set => base.ParsingName = value; + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListItemCollection.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListItemCollection.cs new file mode 100644 index 0000000..fc2ccf9 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListItemCollection.cs @@ -0,0 +1,111 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Represents a collection of jump list items. + /// + /// The type of elements in this collection. + internal class JumpListItemCollection : ICollection, INotifyCollectionChanged + { + private readonly List items = new List(); + + /// + /// Occurs anytime a change is made to the underlying collection. + /// + public event NotifyCollectionChangedEventHandler CollectionChanged = delegate { }; + + /// + /// Gets or sets a value that determines if this collection is read-only. + /// + public bool IsReadOnly { get; set; } + + /// + /// Gets a count of the items currently in this collection. + /// + public int Count => items.Count; + + /// + /// Adds the specified item to this collection. + /// + /// The item to add. + public void Add(T item) + { + items.Add(item); + + // Trigger CollectionChanged event + CollectionChanged( + this, + new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Add, + item)); + } + + /// + /// Removes the first instance of the specified item from the collection. + /// + /// The item to remove. + /// true if an item was removed, otherwise false if no items were removed. + public bool Remove(T item) + { + var removed = items.Remove(item); + + if (removed == true) + { + // Trigger CollectionChanged event + CollectionChanged( + this, + new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Remove, + 0)); + } + + return removed; + } + + /// + /// Clears all items from this collection. + /// + public void Clear() + { + items.Clear(); + + // Trigger CollectionChanged event + CollectionChanged( + this, + new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Reset)); + } + + /// + /// Determines if this collection contains the specified item. + /// + /// The search item. + /// true if an item was found, otherwise false. + public bool Contains(T item) => items.Contains(item); + + /// + /// Copies this collection to a compatible one-dimensional array, + /// starting at the specified index of the target array. + /// + /// The array name. + /// The index of the starting element. + public void CopyTo(T[] array, int index) => items.CopyTo(array, index); + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// An enumerator to iterate through this collection. + IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator(); + + /// + /// Returns an enumerator that iterates through a collection of a specified type. + /// + /// An enumerator to iterate through this collection. + IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator(); + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListLink.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListLink.cs new file mode 100644 index 0000000..24c8db0 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListLink.cs @@ -0,0 +1,201 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell; +using Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem; +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Represents a jump list link object. + /// + public class JumpListLink : JumpListTask, IJumpListItem, IDisposable + { + internal static PropertyKey PKEY_Title = SystemProperties.System.Title; + + /// + /// Initializes a new instance of a JumpListLink with the specified path. + /// + /// The path to the item. The path is required for the JumpList Link + /// The title for the JumpListLink item. The title is required for the JumpList link. + public JumpListLink(string pathValue, string titleValue) + { + if (string.IsNullOrEmpty(pathValue)) + { + throw new ArgumentNullException("pathValue", LocalizedMessages.JumpListLinkPathRequired); + } + + if (string.IsNullOrEmpty(titleValue)) + { + throw new ArgumentNullException("titleValue", LocalizedMessages.JumpListLinkTitleRequired); + } + + Path = pathValue; + Title = titleValue; + } + + private string title; + /// + /// Gets or sets the link's title + /// + public string Title + { + get => title; + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentNullException("value", LocalizedMessages.JumpListLinkTitleRequired); + } + + title = value; + } + } + + private string path; + /// + /// Gets or sets the link's path + /// + public string Path + { + get => path; + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentNullException("value", LocalizedMessages.JumpListLinkTitleRequired); + } + + path = value; + } + } + + /// + /// Gets or sets the icon reference (location and index) of the link's icon. + /// + public IconReference IconReference { get; set; } + + /// + /// Gets or sets the object's arguments (passed to the command line). + /// + public string Arguments { get; set; } + + /// + /// Gets or sets the object's working directory. + /// + public string WorkingDirectory { get; set; } + + /// + /// Gets or sets the show command of the lauched application. + /// + public WindowShowCommand ShowCommand { get; set; } + + private IPropertyStore nativePropertyStore; + private IShellLinkW nativeShellLink; + /// + /// Gets an IShellLinkW representation of this object + /// + internal override IShellLinkW NativeShellLink + { + get + { + if (nativeShellLink != null) + { + Marshal.ReleaseComObject(nativeShellLink); + nativeShellLink = null; + } + + nativeShellLink = (IShellLinkW)new CShellLink(); + + if (nativePropertyStore != null) + { + Marshal.ReleaseComObject(nativePropertyStore); + nativePropertyStore = null; + } + + nativePropertyStore = (IPropertyStore)nativeShellLink; + + nativeShellLink.SetPath(Path); + + if (!string.IsNullOrEmpty(IconReference.ModuleName)) + { + nativeShellLink.SetIconLocation(IconReference.ModuleName, IconReference.ResourceId); + } + + if (!string.IsNullOrEmpty(Arguments)) + { + nativeShellLink.SetArguments(Arguments); + } + + if (!string.IsNullOrEmpty(WorkingDirectory)) + { + nativeShellLink.SetWorkingDirectory(WorkingDirectory); + } + + nativeShellLink.SetShowCmd((uint)ShowCommand); + + using (var propVariant = new PropVariant(Title)) + { + var result = nativePropertyStore.SetValue(ref PKEY_Title, propVariant); + if (!CoreErrorHelper.Succeeded(result)) + { + throw new ShellException(result); + } + + nativePropertyStore.Commit(); + } + + return nativeShellLink; + } + } + + #region IDisposable Members + + /// + /// Release the native and managed objects + /// + /// Indicates that this is being called from Dispose(), rather than the finalizer. + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + title = null; + } + + if (nativePropertyStore != null) + { + Marshal.ReleaseComObject(nativePropertyStore); + nativePropertyStore = null; + } + + if (nativeShellLink != null) + { + Marshal.ReleaseComObject(nativeShellLink); + nativeShellLink = null; + } + } + + /// + /// Release the native objects. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Implement the finalizer. + /// + ~JumpListLink() + { + Dispose(false); + } + + #endregion + + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListSeparator.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListSeparator.cs new file mode 100644 index 0000000..75c7da2 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/JumpListSeparator.cs @@ -0,0 +1,99 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell; +using Kermalis.VGMusicStudio.WinForms.API.Shell.PropertySystem; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Represents a separator in the user task list. The JumpListSeparator control + /// can only be used in a user task list. + /// + public class JumpListSeparator : JumpListTask, IDisposable + { + internal static PropertyKey PKEY_AppUserModel_IsDestListSeparator = SystemProperties.System.AppUserModel.IsDestinationListSeparator; + + private IPropertyStore nativePropertyStore; + private IShellLinkW nativeShellLink; + /// + /// Gets an IShellLinkW representation of this object + /// + internal override IShellLinkW NativeShellLink + { + get + { + if (nativeShellLink != null) + { + Marshal.ReleaseComObject(nativeShellLink); + nativeShellLink = null; + } + + nativeShellLink = (IShellLinkW)new CShellLink(); + + if (nativePropertyStore != null) + { + Marshal.ReleaseComObject(nativePropertyStore); + nativePropertyStore = null; + } + + nativePropertyStore = (IPropertyStore)nativeShellLink; + + using (var propVariant = new PropVariant(true)) + { + var result = nativePropertyStore.SetValue(ref PKEY_AppUserModel_IsDestListSeparator, propVariant); + if (!CoreErrorHelper.Succeeded(result)) + { + throw new ShellException(result); + } + nativePropertyStore.Commit(); + } + + return nativeShellLink; ; + } + } + + #region IDisposable Members + + /// + /// Release the native and managed objects + /// + /// Indicates that this is being called from Dispose(), rather than the finalizer. + protected virtual void Dispose(bool disposing) + { + if (nativePropertyStore != null) + { + Marshal.ReleaseComObject(nativePropertyStore); + nativePropertyStore = null; + } + + if (nativeShellLink != null) + { + Marshal.ReleaseComObject(nativeShellLink); + nativeShellLink = null; + } + } + + /// + /// Release the native objects. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Implement the finalizer. + /// + ~JumpListSeparator() + { + Dispose(false); + } + + #endregion + + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnail.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnail.cs new file mode 100644 index 0000000..668f02e --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnail.cs @@ -0,0 +1,561 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell; +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Drawing; +using System.IO; +using System.Threading; +using System.Windows; +using System.Windows.Forms; +using System.Windows.Interop; +using System.Windows.Media.Imaging; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Represents a tabbed thumbnail on the taskbar for a given window or a control. + /// + public class TabbedThumbnail : IDisposable + { + #region Internal members + + // Control properties + internal IntPtr WindowHandle { get; set; } + internal IntPtr ParentWindowHandle { get; set; } + + // WPF properties + internal UIElement WindowsControl { get; set; } + internal Window WindowsControlParentWindow { get; set; } + + private TaskbarWindow _taskbarWindow; + internal TaskbarWindow TaskbarWindow + { + get => _taskbarWindow; + set + { + _taskbarWindow = value; + + // If we have a TaskbarWindow assigned, set it's icon + if (_taskbarWindow != null && _taskbarWindow.TabbedThumbnailProxyWindow != null) + { + _taskbarWindow.TabbedThumbnailProxyWindow.Icon = Icon; + } + } + } + + private bool _addedToTaskbar; + internal bool AddedToTaskbar + { + get => _addedToTaskbar; + set + { + _addedToTaskbar = value; + + // The user has updated the clipping region, so invalidate our existing preview + if (ClippingRectangle != null) + { + TaskbarWindowManager.InvalidatePreview(TaskbarWindow); + } + } + } + + internal bool RemovedFromTaskbar { get; set; } + + #endregion + + #region Constructors + + /// + /// Creates a new TabbedThumbnail with the given window handle of the parent and + /// a child control/window's handle (e.g. TabPage or Panel) + /// + /// Window handle of the parent window. + /// This window has to be a top-level window and the handle cannot be null or IntPtr.Zero + /// Window handle of the child control or window for which a tabbed + /// thumbnail needs to be displayed + public TabbedThumbnail(IntPtr parentWindowHandle, IntPtr windowHandle) + { + if (parentWindowHandle == IntPtr.Zero) + { + throw new ArgumentException(LocalizedMessages.TabbedThumbnailZeroParentHandle, "parentWindowHandle"); + } + if (windowHandle == IntPtr.Zero) + { + throw new ArgumentException(LocalizedMessages.TabbedThumbnailZeroChildHandle, "windowHandle"); + } + + WindowHandle = windowHandle; + ParentWindowHandle = parentWindowHandle; + } + + /// + /// Creates a new TabbedThumbnail with the given window handle of the parent and + /// a child control (e.g. TabPage or Panel) + /// + /// Window handle of the parent window. + /// This window has to be a top-level window and the handle cannot be null or IntPtr.Zero + /// Child control for which a tabbed thumbnail needs to be displayed + /// This method can also be called when using a WindowsFormHost control in a WPF application. + /// Call this method with the main WPF Window's handle, and windowsFormHost.Child control. + public TabbedThumbnail(IntPtr parentWindowHandle, Control control) + { + if (parentWindowHandle == IntPtr.Zero) + { + throw new ArgumentException(LocalizedMessages.TabbedThumbnailZeroParentHandle, "parentWindowHandle"); + } + if (control == null) + { + throw new ArgumentNullException("control"); + } + + WindowHandle = control.Handle; + ParentWindowHandle = parentWindowHandle; + } + + /// + /// Creates a new TabbedThumbnail with the given window handle of the parent and + /// a WPF child Window. For WindowsFormHost control, use TabbedThumbnail(IntPtr, Control) overload and pass + /// the WindowsFormHost.Child as the second parameter. + /// + /// Parent window for the UIElement control. + /// This window has to be a top-level window and the handle cannot be null + /// WPF Control (UIElement) for which a tabbed thumbnail needs to be displayed + /// Offset point used for displaying the peek bitmap. This setting is + /// recomended for hidden WPF controls as it is difficult to calculate their offset. + public TabbedThumbnail(Window parentWindow, UIElement windowsControl, Vector peekOffset) + { + if (windowsControl == null) + { + throw new ArgumentNullException("windowsControl"); + } + if (parentWindow == null) + { + throw new ArgumentNullException("parentWindow"); + } + + WindowHandle = IntPtr.Zero; + + WindowsControl = windowsControl; + WindowsControlParentWindow = parentWindow; + ParentWindowHandle = (new WindowInteropHelper(parentWindow)).Handle; + PeekOffset = peekOffset; + } + + #endregion + + #region Public Properties + + private string _title = string.Empty; + /// + /// Title for the window shown as the taskbar thumbnail. + /// + public string Title + { + get => _title; + set + { + if (_title != value) + { + _title = value; + if (TitleChanged != null) { TitleChanged(this, EventArgs.Empty); } + } + } + } + + private string _tooltip = string.Empty; + /// + /// Tooltip to be shown for this thumbnail on the taskbar. + /// By default this is full title of the window shown on the taskbar. + /// + public string Tooltip + { + get => _tooltip; + set + { + if (_tooltip != value) + { + _tooltip = value; + if (TooltipChanged != null) { TooltipChanged(this, EventArgs.Empty); } + } + } + } + + /// + /// Sets the window icon for this thumbnail preview + /// + /// System.Drawing.Icon for the window/control associated with this preview + public void SetWindowIcon(Icon icon) + { + Icon = icon; + + // If we have a TaskbarWindow assigned, set its icon + if (TaskbarWindow != null && TaskbarWindow.TabbedThumbnailProxyWindow != null) + { + TaskbarWindow.TabbedThumbnailProxyWindow.Icon = Icon; + } + } + + /// + /// Sets the window icon for this thumbnail preview + /// + /// Icon handle (hIcon) for the window/control associated with this preview + /// This method will not release the icon handle. It is the caller's responsibility to release the icon handle. + public void SetWindowIcon(IntPtr iconHandle) + { + Icon = iconHandle != IntPtr.Zero ? System.Drawing.Icon.FromHandle(iconHandle) : null; + + if (TaskbarWindow != null && TaskbarWindow.TabbedThumbnailProxyWindow != null) + { + TaskbarWindow.TabbedThumbnailProxyWindow.Icon = Icon; + } + } + + private Rectangle? _clippingRectangle; + /// + /// Specifies that only a portion of the window's client area + /// should be used in the window's thumbnail. + /// A value of null will clear the clipping area and use the default thumbnail. + /// + public Rectangle? ClippingRectangle + { + get => _clippingRectangle; + set + { + _clippingRectangle = value; + + // The user has updated the clipping region, so invalidate our existing preview + TaskbarWindowManager.InvalidatePreview(TaskbarWindow); + } + } + + internal IntPtr CurrentHBitmap { get; set; } + + internal Icon Icon { get; private set; } + + /// + /// Override the thumbnail and peek bitmap. + /// By providing this bitmap manually, Thumbnail Window manager will provide the + /// Desktop Window Manager (DWM) this bitmap instead of rendering one automatically. + /// Use this property to update the bitmap whenever the control is updated and the user + /// needs to be shown a new thumbnail on the taskbar preview (or aero peek). + /// + /// The image to use. + /// + /// If the bitmap doesn't have the right dimensions, the DWM may scale it or not + /// render certain areas as appropriate - it is the user's responsibility + /// to render a bitmap with the proper dimensions. + /// + public void SetImage(Bitmap bitmap) + { + if (bitmap != null) + { + SetImage(bitmap.GetHbitmap()); + } + else + { + SetImage(IntPtr.Zero); + } + } + + /// + /// Override the thumbnail and peek bitmap. + /// By providing this bitmap manually, Thumbnail Window manager will provide the + /// Desktop Window Manager (DWM) this bitmap instead of rendering one automatically. + /// Use this property to update the bitmap whenever the control is updated and the user + /// needs to be shown a new thumbnail on the taskbar preview (or aero peek). + /// + /// The image to use. + /// + /// If the bitmap doesn't have the right dimensions, the DWM may scale it or not + /// render certain areas as appropriate - it is the user's responsibility + /// to render a bitmap with the proper dimensions. + /// + public void SetImage(BitmapSource bitmapSource) + { + if (bitmapSource == null) + { + SetImage(IntPtr.Zero); + return; + } + + var encoder = new BmpBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(bitmapSource)); + + using (var memoryStream = new MemoryStream()) + { + encoder.Save(memoryStream); + memoryStream.Position = 0; + + using (var bmp = new Bitmap(memoryStream)) + { + SetImage(bmp.GetHbitmap()); + } + } + } + + /// + /// Override the thumbnail and peek bitmap. + /// By providing this bitmap manually, Thumbnail Window manager will provide the + /// Desktop Window Manager (DWM) this bitmap instead of rendering one automatically. + /// Use this property to update the bitmap whenever the control is updated and the user + /// needs to be shown a new thumbnail on the taskbar preview (or aero peek). + /// + /// A bitmap handle for the image to use. + /// When the TabbedThumbnail is finalized, this class will delete the provided hBitmap. + /// + /// If the bitmap doesn't have the right dimensions, the DWM may scale it or not + /// render certain areas as appropriate - it is the user's responsibility + /// to render a bitmap with the proper dimensions. + /// + internal void SetImage(IntPtr hBitmap) + { + // Before we set a new bitmap, dispose the old one + if (CurrentHBitmap != IntPtr.Zero) + { + ShellNativeMethods.DeleteObject(CurrentHBitmap); + } + + // Set the new bitmap + CurrentHBitmap = hBitmap; + + // Let DWM know to invalidate its cached thumbnail/preview and ask us for a new one + TaskbarWindowManager.InvalidatePreview(TaskbarWindow); + } + + /// + /// Specifies whether a standard window frame will be displayed + /// around the bitmap. If the bitmap represents a top-level window, + /// you would probably set this flag to true. If the bitmap + /// represents a child window (or a frameless window), you would + /// probably set this flag to false. + /// + public bool DisplayFrameAroundBitmap { get; set; } + + /// + /// Invalidate any existing thumbnail preview. Calling this method + /// will force DWM to request a new bitmap next time user previews the thumbnails + /// or requests Aero peek preview. + /// + public void InvalidatePreview() => + // clear current image and invalidate + SetImage(IntPtr.Zero); + + /// + /// Gets or sets the offset used for displaying the peek bitmap. This setting is + /// recomended for hidden WPF controls as it is difficult to calculate their offset. + /// + public Vector? PeekOffset { get; set; } + + #endregion + + + + #region Events + + /// + /// This event is raised when the Title property changes. + /// + public event EventHandler TitleChanged; + + /// + /// This event is raised when the Tooltip property changes. + /// + public event EventHandler TooltipChanged; + + /// + /// The event that occurs when a tab is closed on the taskbar thumbnail preview. + /// + public event EventHandler TabbedThumbnailClosed; + + /// + /// The event that occurs when a tab is maximized via the taskbar thumbnail preview (context menu). + /// + public event EventHandler TabbedThumbnailMaximized; + + /// + /// The event that occurs when a tab is minimized via the taskbar thumbnail preview (context menu). + /// + public event EventHandler TabbedThumbnailMinimized; + + /// + /// The event that occurs when a tab is activated (clicked) on the taskbar thumbnail preview. + /// + public event EventHandler TabbedThumbnailActivated; + + /// + /// The event that occurs when a thumbnail or peek bitmap is requested by the user. + /// + public event EventHandler TabbedThumbnailBitmapRequested; + + + internal void OnTabbedThumbnailMaximized() + { + if (TabbedThumbnailMaximized != null) + { + TabbedThumbnailMaximized(this, GetTabbedThumbnailEventArgs()); + } + else + { + // No one is listening to these events. + // Forward the message to the main window + CoreNativeMethods.SendMessage(ParentWindowHandle, WindowMessage.SystemCommand, new IntPtr(TabbedThumbnailNativeMethods.ScMaximize), IntPtr.Zero); + } + } + + internal void OnTabbedThumbnailMinimized() + { + if (TabbedThumbnailMinimized != null) + { + TabbedThumbnailMinimized(this, GetTabbedThumbnailEventArgs()); + } + else + { + // No one is listening to these events. + // Forward the message to the main window + CoreNativeMethods.SendMessage(ParentWindowHandle, WindowMessage.SystemCommand, new IntPtr(TabbedThumbnailNativeMethods.ScMinimize), IntPtr.Zero); + } + + } + + /// + /// Returns true if the thumbnail was removed from the taskbar; false if it was not. + /// + /// Returns true if the thumbnail was removed from the taskbar; false if it was not. + internal bool OnTabbedThumbnailClosed() + { + var closedHandler = TabbedThumbnailClosed; + if (closedHandler != null) + { + var closingEvent = GetTabbedThumbnailClosingEventArgs(); + + closedHandler(this, closingEvent); + + if (closingEvent.Cancel) { return false; } + } + else + { + // No one is listening to these events. Forward the message to the main window + CoreNativeMethods.SendMessage(ParentWindowHandle, WindowMessage.NCDestroy, IntPtr.Zero, IntPtr.Zero); + } + + // Remove it from the internal list as well as the taskbar + TaskbarManager.Instance.TabbedThumbnail.RemoveThumbnailPreview(this); + return true; + } + + internal void OnTabbedThumbnailActivated() + { + if (TabbedThumbnailActivated != null) + { + TabbedThumbnailActivated(this, GetTabbedThumbnailEventArgs()); + } + else + { + // No one is listening to these events. + // Forward the message to the main window + CoreNativeMethods.SendMessage(ParentWindowHandle, WindowMessage.ActivateApplication, new IntPtr(1), new IntPtr(Thread.CurrentThread.GetHashCode())); + } + } + + internal void OnTabbedThumbnailBitmapRequested() + { + if (TabbedThumbnailBitmapRequested != null) + { + TabbedThumbnailBitmapRequestedEventArgs eventArgs = null; + + if (WindowHandle != IntPtr.Zero) + { + eventArgs = new TabbedThumbnailBitmapRequestedEventArgs(WindowHandle); + } + else if (WindowsControl != null) + { + eventArgs = new TabbedThumbnailBitmapRequestedEventArgs(WindowsControl); + } + + TabbedThumbnailBitmapRequested(this, eventArgs); + } + } + + private TabbedThumbnailClosedEventArgs GetTabbedThumbnailClosingEventArgs() + { + TabbedThumbnailClosedEventArgs eventArgs = null; + + if (WindowHandle != IntPtr.Zero) + { + eventArgs = new TabbedThumbnailClosedEventArgs(WindowHandle); + } + else if (WindowsControl != null) + { + eventArgs = new TabbedThumbnailClosedEventArgs(WindowsControl); + } + + return eventArgs; + } + + private TabbedThumbnailEventArgs GetTabbedThumbnailEventArgs() + { + TabbedThumbnailEventArgs eventArgs = null; + + if (WindowHandle != IntPtr.Zero) + { + eventArgs = new TabbedThumbnailEventArgs(WindowHandle); + } + else if (WindowsControl != null) + { + eventArgs = new TabbedThumbnailEventArgs(WindowsControl); + } + + return eventArgs; + } + + #endregion + + #region IDisposable Members + + /// + /// + /// + ~TabbedThumbnail() + { + Dispose(false); + } + + /// + /// Release the native objects. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Release the native objects. + /// + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _taskbarWindow = null; + + if (Icon != null) { Icon.Dispose(); } + Icon = null; + + _title = null; + _tooltip = null; + WindowsControl = null; + } + + if (CurrentHBitmap != IntPtr.Zero) + { + ShellNativeMethods.DeleteObject(CurrentHBitmap); + CurrentHBitmap = IntPtr.Zero; + } + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailBitmapRequestedEventArgs.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailBitmapRequestedEventArgs.cs new file mode 100644 index 0000000..f8543d9 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailBitmapRequestedEventArgs.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Windows; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Event args for the TabbedThumbnailBitmapRequested event. The event allows applications to + /// provide a bitmap for the tabbed thumbnail's preview and peek. The application should also + /// set the Handled property if a custom bitmap is provided. + /// + public class TabbedThumbnailBitmapRequestedEventArgs : TabbedThumbnailEventArgs + { + /// + /// Creates a Event Args for a TabbedThumbnailBitmapRequested event. + /// + /// Window handle for the control/window related to the event + public TabbedThumbnailBitmapRequestedEventArgs(IntPtr windowHandle) + : base(windowHandle) + { + } + + /// + /// Creates a Event Args for a TabbedThumbnailBitmapRequested event. + /// + /// WPF Control (UIElement) related to the event + public TabbedThumbnailBitmapRequestedEventArgs(UIElement windowsControl) + : base(windowsControl) + { + } + + + /// + /// Gets or sets a value indicating whether the TabbedThumbnailBitmapRequested event was handled. + /// Set this property if the SetImage method is called with a custom bitmap for the thumbnail/peek. + /// + public bool Handled { get; set; } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailClosedEventArgs.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailClosedEventArgs.cs new file mode 100644 index 0000000..fc63b53 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailClosedEventArgs.cs @@ -0,0 +1,29 @@ +using System; +using System.Windows; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Event args for when close is selected on a tabbed thumbnail proxy window. + /// + public class TabbedThumbnailClosedEventArgs : TabbedThumbnailEventArgs + { + /// + /// Creates a Event Args for a specific tabbed thumbnail event. + /// + /// Window handle for the control/window related to the event + public TabbedThumbnailClosedEventArgs(IntPtr windowHandle) : base(windowHandle) { } + + /// + /// Creates a Event Args for a specific tabbed thumbnail event. + /// + /// WPF Control (UIElement) related to the event + public TabbedThumbnailClosedEventArgs(UIElement windowsControl) : base(windowsControl) { } + + /// + /// If set to true, the proxy window will not be removed from the taskbar. + /// + public bool Cancel { get; set; } + + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailEventArgs.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailEventArgs.cs new file mode 100644 index 0000000..99d3de4 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailEventArgs.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Windows; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Event args for various Tabbed Thumbnail related events + /// + public class TabbedThumbnailEventArgs : EventArgs + { + /// + /// Creates a Event Args for a specific tabbed thumbnail event. + /// + /// Window handle for the control/window related to the event + public TabbedThumbnailEventArgs(IntPtr windowHandle) + { + WindowHandle = windowHandle; + WindowsControl = null; + } + + /// + /// Creates a Event Args for a specific tabbed thumbnail event. + /// + /// WPF Control (UIElement) related to the event + public TabbedThumbnailEventArgs(UIElement windowsControl) + { + WindowHandle = IntPtr.Zero; + WindowsControl = windowsControl; + } + + /// + /// Gets the Window handle for the specific control/window that is related to this event. + /// + /// For WPF Controls (UIElement) the WindowHandle will be IntPtr.Zero. + /// Check the WindowsControl property to get the specific control associated with this event. + public IntPtr WindowHandle { get; private set; } + + /// + /// Gets the WPF Control (UIElement) that is related to this event. This property may be null + /// for non-WPF applications. + /// + public UIElement WindowsControl { get; private set; } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailManager.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailManager.cs new file mode 100644 index 0000000..cac29e2 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailManager.cs @@ -0,0 +1,458 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell; +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Represents the main class for adding and removing tabbed thumbnails on the Taskbar + /// for child windows and controls. + /// + public class TabbedThumbnailManager + { + /// + /// Internal dictionary to keep track of the user's window handle and its + /// corresponding thumbnail preview objects. + /// + private readonly Dictionary _tabbedThumbnailCache; + private readonly Dictionary _tabbedThumbnailCacheWPF; // list for WPF controls + + /// + /// Internal constructor that creates a new dictionary for keeping track of the window handles + /// and their corresponding thumbnail preview objects. + /// + internal TabbedThumbnailManager() + { + _tabbedThumbnailCache = new Dictionary(); + _tabbedThumbnailCacheWPF = new Dictionary(); + } + + /// + /// Adds a new tabbed thumbnail to the taskbar. + /// + /// Thumbnail preview for a specific window handle or control. The preview + /// object can be initialized with specific properties for the title, bitmap, and tooltip. + /// If the tabbed thumbnail has already been added + public void AddThumbnailPreview(TabbedThumbnail preview) + { + if (preview == null) { throw new ArgumentNullException("preview"); } + + // UI Element has a windowHandle of zero. + if (preview.WindowHandle == IntPtr.Zero) + { + if (_tabbedThumbnailCacheWPF.ContainsKey(preview.WindowsControl)) + { + throw new ArgumentException(LocalizedMessages.ThumbnailManagerPreviewAdded, "preview"); + } + _tabbedThumbnailCacheWPF.Add(preview.WindowsControl, preview); + } + else + { + // Regular control with a valid handle + if (_tabbedThumbnailCache.ContainsKey(preview.WindowHandle)) + { + throw new ArgumentException(LocalizedMessages.ThumbnailManagerPreviewAdded, "preview"); + } + _tabbedThumbnailCache.Add(preview.WindowHandle, preview); + } + + TaskbarWindowManager.AddTabbedThumbnail(preview); + + preview.InvalidatePreview(); // Note: Why this here? + } + + /// + /// Gets the TabbedThumbnail object associated with the given window handle + /// + /// Window handle for the control/window + /// TabbedThumbnail associated with the given window handle + public TabbedThumbnail GetThumbnailPreview(IntPtr windowHandle) + { + if (windowHandle == IntPtr.Zero) + { + throw new ArgumentException(LocalizedMessages.ThumbnailManagerInvalidHandle, "windowHandle"); + } + + return _tabbedThumbnailCache.TryGetValue(windowHandle, out var thumbnail) ? thumbnail : null; + } + + /// + /// Gets the TabbedThumbnail object associated with the given control + /// + /// Specific control for which the preview object is requested + /// TabbedThumbnail associated with the given control + public TabbedThumbnail GetThumbnailPreview(Control control) + { + if (control == null) + { + throw new ArgumentNullException("control"); + } + + return GetThumbnailPreview(control.Handle); + } + + /// + /// Gets the TabbedThumbnail object associated with the given WPF Window + /// + /// WPF Control (UIElement) for which the preview object is requested + /// TabbedThumbnail associated with the given WPF Window + public TabbedThumbnail GetThumbnailPreview(UIElement windowsControl) + { + if (windowsControl == null) + { + throw new ArgumentNullException("windowsControl"); + } + + return _tabbedThumbnailCacheWPF.TryGetValue(windowsControl, out var thumbnail) ? thumbnail : null; + } + + /// + /// Remove the tabbed thumbnail from the taskbar. + /// + /// TabbedThumbnail associated with the control/window that + /// is to be removed from the taskbar + public void RemoveThumbnailPreview(TabbedThumbnail preview) + { + if (preview == null) + { + throw new ArgumentNullException("preview"); + } + + if (_tabbedThumbnailCache.ContainsKey(preview.WindowHandle)) + { + RemoveThumbnailPreview(preview.WindowHandle); + } + else if (_tabbedThumbnailCacheWPF.ContainsKey(preview.WindowsControl)) + { + RemoveThumbnailPreview(preview.WindowsControl); + } + } + + /// + /// Remove the tabbed thumbnail from the taskbar. + /// + /// TabbedThumbnail associated with the window handle that + /// is to be removed from the taskbar + public void RemoveThumbnailPreview(IntPtr windowHandle) + { + if (!_tabbedThumbnailCache.ContainsKey(windowHandle)) + { + throw new ArgumentException(LocalizedMessages.ThumbnailManagerControlNotAdded, "windowHandle"); + } + + TaskbarWindowManager.UnregisterTab(_tabbedThumbnailCache[windowHandle].TaskbarWindow); + + _tabbedThumbnailCache.Remove(windowHandle); + + var taskbarWindow = TaskbarWindowManager.GetTaskbarWindow(windowHandle, TaskbarProxyWindowType.TabbedThumbnail); + + if (taskbarWindow != null) + { + if (TaskbarWindowManager._taskbarWindowList.Contains(taskbarWindow)) + TaskbarWindowManager._taskbarWindowList.Remove(taskbarWindow); + taskbarWindow.Dispose(); + taskbarWindow = null; + } + } + + /// + /// Remove the tabbed thumbnail from the taskbar. + /// + /// TabbedThumbnail associated with the control that + /// is to be removed from the taskbar + public void RemoveThumbnailPreview(Control control) + { + if (control == null) + { + throw new ArgumentNullException("control"); + } + + var handle = control.Handle; + + RemoveThumbnailPreview(handle); + } + + /// + /// Remove the tabbed thumbnail from the taskbar. + /// + /// TabbedThumbnail associated with the WPF Control (UIElement) that + /// is to be removed from the taskbar + public void RemoveThumbnailPreview(UIElement windowsControl) + { + if (windowsControl == null) { throw new ArgumentNullException("windowsControl"); } + + if (!_tabbedThumbnailCacheWPF.ContainsKey(windowsControl)) + { + throw new ArgumentException(LocalizedMessages.ThumbnailManagerControlNotAdded, "windowsControl"); + } + + TaskbarWindowManager.UnregisterTab(_tabbedThumbnailCacheWPF[windowsControl].TaskbarWindow); + + _tabbedThumbnailCacheWPF.Remove(windowsControl); + + var taskbarWindow = TaskbarWindowManager.GetTaskbarWindow(windowsControl, TaskbarProxyWindowType.TabbedThumbnail); + + if (taskbarWindow != null) + { + if (TaskbarWindowManager._taskbarWindowList.Contains(taskbarWindow)) + { + TaskbarWindowManager._taskbarWindowList.Remove(taskbarWindow); + } + taskbarWindow.Dispose(); + taskbarWindow = null; + } + } + + /// + /// Sets the given tabbed thumbnail preview object as being active on the taskbar tabbed thumbnails list. + /// Call this method to keep the application and the taskbar in sync as to which window/control + /// is currently active (or selected, in the case of tabbed application). + /// + /// TabbedThumbnail for the specific control/indow that is currently active in the application + /// If the control/window is not yet added to the tabbed thumbnails list + public void SetActiveTab(TabbedThumbnail preview) + { + if (preview == null) { throw new ArgumentNullException("preview"); } + + if (preview.WindowHandle != IntPtr.Zero) + { + if (!_tabbedThumbnailCache.ContainsKey(preview.WindowHandle)) + { + throw new ArgumentException(LocalizedMessages.ThumbnailManagerPreviewNotAdded, "preview"); + } + TaskbarWindowManager.SetActiveTab(_tabbedThumbnailCache[preview.WindowHandle].TaskbarWindow); + } + else if (preview.WindowsControl != null) + { + if (!_tabbedThumbnailCacheWPF.ContainsKey(preview.WindowsControl)) + { + throw new ArgumentException(LocalizedMessages.ThumbnailManagerPreviewNotAdded, "preview"); + } + TaskbarWindowManager.SetActiveTab(_tabbedThumbnailCacheWPF[preview.WindowsControl].TaskbarWindow); + } + } + + /// + /// Sets the given window handle as being active on the taskbar tabbed thumbnails list. + /// Call this method to keep the application and the taskbar in sync as to which window/control + /// is currently active (or selected, in the case of tabbed application). + /// + /// Window handle for the control/window that is currently active in the application + /// If the control/window is not yet added to the tabbed thumbnails list + public void SetActiveTab(IntPtr windowHandle) + { + if (!_tabbedThumbnailCache.ContainsKey(windowHandle)) + { + throw new ArgumentException(LocalizedMessages.ThumbnailManagerPreviewNotAdded, "windowHandle"); + } + TaskbarWindowManager.SetActiveTab(_tabbedThumbnailCache[windowHandle].TaskbarWindow); + } + + /// + /// Sets the given Control/Form window as being active on the taskbar tabbed thumbnails list. + /// Call this method to keep the application and the taskbar in sync as to which window/control + /// is currently active (or selected, in the case of tabbed application). + /// + /// Control/Form that is currently active in the application + /// If the control/window is not yet added to the tabbed thumbnails list + public void SetActiveTab(Control control) + { + if (control == null) + { + throw new ArgumentNullException("control"); + } + SetActiveTab(control.Handle); + } + + /// + /// Sets the given WPF window as being active on the taskbar tabbed thumbnails list. + /// Call this method to keep the application and the taskbar in sync as to which window/control + /// is currently active (or selected, in the case of tabbed application). + /// + /// WPF control that is currently active in the application + /// If the control/window is not yet added to the tabbed thumbnails list + public void SetActiveTab(UIElement windowsControl) + { + if (windowsControl == null) + { + throw new ArgumentNullException("windowsControl"); + } + + if (!_tabbedThumbnailCacheWPF.ContainsKey(windowsControl)) + { + throw new ArgumentException(LocalizedMessages.ThumbnailManagerPreviewNotAdded, "windowsControl"); + } + TaskbarWindowManager.SetActiveTab(_tabbedThumbnailCacheWPF[windowsControl].TaskbarWindow); + + } + + /// + /// Determines whether the given preview has been added to the taskbar's tabbed thumbnail list. + /// + /// The preview to locate on the taskbar's tabbed thumbnail list + /// true if the tab is already added on the taskbar; otherwise, false. + public bool IsThumbnailPreviewAdded(TabbedThumbnail preview) + { + if (preview == null) + { + throw new ArgumentNullException("preview"); + } + + if (preview.WindowHandle != IntPtr.Zero && _tabbedThumbnailCache.ContainsKey(preview.WindowHandle)) + { + return true; + } + else if (preview.WindowsControl != null && _tabbedThumbnailCacheWPF.ContainsKey(preview.WindowsControl)) + { + return true; + } + + return false; + } + + /// + /// Determines whether the given window has been added to the taskbar's tabbed thumbnail list. + /// + /// The window to locate on the taskbar's tabbed thumbnail list + /// true if the tab is already added on the taskbar; otherwise, false. + public bool IsThumbnailPreviewAdded(IntPtr windowHandle) + { + if (windowHandle == IntPtr.Zero) + { + throw new ArgumentException(LocalizedMessages.ThumbnailManagerInvalidHandle, "windowHandle"); + } + + return _tabbedThumbnailCache.ContainsKey(windowHandle); + } + + /// + /// Determines whether the given control has been added to the taskbar's tabbed thumbnail list. + /// + /// The preview to locate on the taskbar's tabbed thumbnail list + /// true if the tab is already added on the taskbar; otherwise, false. + public bool IsThumbnailPreviewAdded(Control control) + { + if (control == null) + { + throw new ArgumentNullException("control"); + } + + return _tabbedThumbnailCache.ContainsKey(control.Handle); + } + + /// + /// Determines whether the given control has been added to the taskbar's tabbed thumbnail list. + /// + /// The preview to locate on the taskbar's tabbed thumbnail list + /// true if the tab is already added on the taskbar; otherwise, false. + public bool IsThumbnailPreviewAdded(UIElement control) + { + if (control == null) + { + throw new ArgumentNullException("control"); + } + + return _tabbedThumbnailCacheWPF.ContainsKey(control); + } + + /// + /// Invalidates all the tabbed thumbnails. This will force the Desktop Window Manager + /// to not use the cached thumbnail or preview or aero peek and request a new one next time. + /// + /// This method should not be called frequently. + /// Doing so can lead to poor performance as new bitmaps are created and retrieved. + public void InvalidateThumbnails() + { + // Invalidate all the previews currently in our cache. + // This will ensure we get updated bitmaps next time + + foreach (var thumbnail in _tabbedThumbnailCache.Values) + { + TaskbarWindowManager.InvalidatePreview(thumbnail.TaskbarWindow); + thumbnail.SetImage(IntPtr.Zero); // TODO: Investigate this, and why it needs to be called. + } + + foreach (var thumbnail in _tabbedThumbnailCacheWPF.Values) + { + TaskbarWindowManager.InvalidatePreview(thumbnail.TaskbarWindow); + thumbnail.SetImage(IntPtr.Zero); + } + } + + /// + /// Clear a clip that is already in place and return to the default display of the thumbnail. + /// + /// The handle to a window represented in the taskbar. This has to be a top-level window. + public static void ClearThumbnailClip(IntPtr windowHandle) => TaskbarList.Instance.SetThumbnailClip(windowHandle, IntPtr.Zero); + + /// + /// Selects a portion of a window's client area to display as that window's thumbnail in the taskbar. + /// + /// The handle to a window represented in the taskbar. This has to be a top-level window. + /// Rectangle structure that specifies a selection within the window's client area, + /// relative to the upper-left corner of that client area. + /// If this parameter is null, the clipping area will be cleared and the default display of the thumbnail will be used instead. + public void SetThumbnailClip(IntPtr windowHandle, Rectangle? clippingRectangle) + { + if (clippingRectangle == null) + { + ClearThumbnailClip(windowHandle); + return; + } + + var rect = new NativeRect + { + Left = clippingRectangle.Value.Left, + Top = clippingRectangle.Value.Top, + Right = clippingRectangle.Value.Right, + Bottom = clippingRectangle.Value.Bottom + }; + + var rectPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(rect)); + try + { + Marshal.StructureToPtr(rect, rectPtr, true); + TaskbarList.Instance.SetThumbnailClip(windowHandle, rectPtr); + } + finally + { + Marshal.FreeCoTaskMem(rectPtr); + } + } + + /// + /// Moves an existing thumbnail to a new position in the application's group. + /// + /// Preview for the window whose order is being changed. + /// This value is required, must already be added via AddThumbnailPreview method, and cannot be null. + /// The preview of the tab window whose thumbnail that previewToChange is inserted to the left of. + /// This preview must already be added via AddThumbnailPreview. If this value is null, the previewToChange tab is added to the end of the list. + /// + public static void SetTabOrder(TabbedThumbnail previewToChange, TabbedThumbnail insertBeforePreview) + { + if (previewToChange == null) + { + throw new ArgumentNullException("previewToChange"); + } + + var handleToReorder = previewToChange.TaskbarWindow.WindowToTellTaskbarAbout; + + if (insertBeforePreview == null) + { + TaskbarList.Instance.SetTabOrder(handleToReorder, IntPtr.Zero); + } + else + { + var handleBefore = insertBeforePreview.TaskbarWindow.WindowToTellTaskbarAbout; + TaskbarList.Instance.SetTabOrder(handleToReorder, handleBefore); + } + } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailProxyWindow.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailProxyWindow.cs new file mode 100644 index 0000000..001ed28 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailProxyWindow.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Windows; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + internal sealed class TabbedThumbnailProxyWindow : Form, IDisposable + { + + internal TabbedThumbnailProxyWindow(TabbedThumbnail preview) + { + TabbedThumbnail = preview; + Size = new System.Drawing.Size(1, 1); + + if (!string.IsNullOrEmpty(preview.Title)) + { + Text = preview.Title; + } + + if (preview.WindowsControl != null) + { + WindowsControl = preview.WindowsControl; + } + } + + internal TabbedThumbnail TabbedThumbnail { get; private set; } + + internal UIElement WindowsControl { get; private set; } + + internal IntPtr WindowToTellTaskbarAbout => Handle; + + protected override void WndProc(ref Message m) + { + var handled = false; + + if (TabbedThumbnail != null) + { + handled = TaskbarWindowManager.DispatchMessage(ref m, TabbedThumbnail.TaskbarWindow); + } + + // If it's a WM_Destroy message, then also forward it to the base class (our native window) + if ((m.Msg == (int)WindowMessage.Destroy) || + (m.Msg == (int)WindowMessage.NCDestroy) || + ((m.Msg == (int)WindowMessage.SystemCommand) && (((int)m.WParam) == TabbedThumbnailNativeMethods.ScClose))) + { + base.WndProc(ref m); + } + else if (!handled) { base.WndProc(ref m); } + } + + #region IDisposable Members + + /// + /// + /// + ~TabbedThumbnailProxyWindow() + { + Dispose(false); + } + + /// + /// Release the native objects. + /// + void IDisposable.Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + // Dispose managed resources + if (TabbedThumbnail != null) { TabbedThumbnail.Dispose(); } + + TabbedThumbnail = null; + + WindowsControl = null; + } + + base.Dispose(disposing); + } + + #endregion + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailScreenCapture.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailScreenCapture.cs new file mode 100644 index 0000000..9acad1f --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TabbedThumbnailScreenCapture.cs @@ -0,0 +1,180 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell; +using System; +using System.Drawing; +using System.IO; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Helper class to capture a control or window as System.Drawing.Bitmap + /// + public static class TabbedThumbnailScreenCapture + { + /// + /// Captures a screenshot of the specified window at the specified + /// bitmap size. NOTE: This method will not accurately capture controls + /// that are hidden or obstructed (partially or completely) by another control (e.g. hidden tabs, + /// or MDI child windows that are obstructed by other child windows/forms). + /// + /// The window handle. + /// The requested bitmap size. + /// A screen capture of the window. + public static Bitmap GrabWindowBitmap(IntPtr windowHandle, System.Drawing.Size bitmapSize) + { + if (bitmapSize.Height <= 0 || bitmapSize.Width <= 0) { return null; } + + var windowDC = IntPtr.Zero; + + try + { + windowDC = TabbedThumbnailNativeMethods.GetWindowDC(windowHandle); + + TabbedThumbnailNativeMethods.GetClientSize(windowHandle, out var realWindowSize); + + if (realWindowSize == System.Drawing.Size.Empty) + { + realWindowSize = new System.Drawing.Size(200, 200); + } + + var size = (bitmapSize == System.Drawing.Size.Empty) ? + realWindowSize : bitmapSize; + + Bitmap targetBitmap = null; + try + { + + + targetBitmap = new Bitmap(size.Width, size.Height); + + using (var targetGr = Graphics.FromImage(targetBitmap)) + { + var targetDC = targetGr.GetHdc(); + uint operation = 0x00CC0020 /*SRCCOPY*/; + + var ncArea = WindowUtilities.GetNonClientArea(windowHandle); + + var success = TabbedThumbnailNativeMethods.StretchBlt( + targetDC, 0, 0, targetBitmap.Width, targetBitmap.Height, + windowDC, ncArea.Width, ncArea.Height, realWindowSize.Width, + realWindowSize.Height, operation); + + targetGr.ReleaseHdc(targetDC); + + if (!success) { return null; } + + return targetBitmap; + } + } + catch + { + if (targetBitmap != null) { targetBitmap.Dispose(); } + throw; + } + } + finally + { + if (windowDC != IntPtr.Zero) + { + TabbedThumbnailNativeMethods.ReleaseDC(windowHandle, windowDC); + } + } + } + + /// + /// Grabs a snapshot of a WPF UIElement and returns the image as Bitmap. + /// + /// Represents the element to take the snapshot from. + /// Represents the X DPI value used to capture this snapshot. + /// Represents the Y DPI value used to capture this snapshot. + /// The requested bitmap width. + /// The requested bitmap height. + /// Returns the bitmap (PNG format). + public static Bitmap GrabWindowBitmap(UIElement element, int dpiX, int dpiY, int width, int height) + { + // Special case for HwndHost controls + var host = element as HwndHost; + if (host != null) + { + var handle = host.Handle; + return GrabWindowBitmap(handle, new System.Drawing.Size(width, height)); + } + + var bounds = VisualTreeHelper.GetDescendantBounds(element); + + // create the renderer. + if (bounds.Height == 0 || bounds.Width == 0) + { + return null; // 0 sized element. Probably hidden + } + + var rendertarget = new RenderTargetBitmap((int)(bounds.Width * dpiX / 96.0), + (int)(bounds.Height * dpiY / 96.0), dpiX, dpiY, PixelFormats.Default); + + var dv = new DrawingVisual(); + using (var ctx = dv.RenderOpen()) + { + var vb = new VisualBrush(element); + ctx.DrawRectangle(vb, null, new Rect(new System.Windows.Point(), bounds.Size)); + } + + rendertarget.Render(dv); + + BitmapEncoder bmpe = new PngBitmapEncoder(); + bmpe.Frames.Add(BitmapFrame.Create(rendertarget)); + + Bitmap bmp; + // Create a MemoryStream with the image. + using (var fl = new MemoryStream()) + { + bmpe.Save(fl); + fl.Position = 0; + bmp = new Bitmap(fl); + } + + return (Bitmap)bmp.GetThumbnailImage(width, height, null, IntPtr.Zero); + } + + /// + /// Resizes the given bitmap while maintaining the aspect ratio. + /// + /// Original/source bitmap + /// Maximum width for the new image + /// Maximum height for the new image + /// If true and requested image is wider than the source, the new image is resized accordingly. + /// + internal static Bitmap ResizeImageWithAspect(IntPtr originalHBitmap, int newWidth, int maxHeight, bool resizeIfWider) + { + var originalBitmap = Bitmap.FromHbitmap(originalHBitmap); + + try + { + if (resizeIfWider && originalBitmap.Width <= newWidth) + { + newWidth = originalBitmap.Width; + } + + var newHeight = originalBitmap.Height * newWidth / originalBitmap.Width; + + if (newHeight > maxHeight) // Height resize if necessary + { + newWidth = originalBitmap.Width * maxHeight / originalBitmap.Height; + newHeight = maxHeight; + } + + // Create the new image with the sizes we've calculated + return (Bitmap)originalBitmap.GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero); + } + finally + { + originalBitmap.Dispose(); + originalBitmap = null; + } + } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarEnums.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarEnums.cs new file mode 100644 index 0000000..4f46b42 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarEnums.cs @@ -0,0 +1,65 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + + internal enum TaskbarProxyWindowType + { + TabbedThumbnail, + ThumbnailToolbar, + } + + /// + /// Known category to display + /// + public enum JumpListKnownCategoryType + { + /// + /// Don't display either known category. You must have at least one + /// user task or custom category link in order to not see the + /// default 'Recent' known category + /// + Neither = 0, + + /// + /// Display the 'Recent' known category + /// + Recent, + + /// + /// Display the 'Frequent' known category + /// + Frequent, + } + + /// + /// Represents the thumbnail progress bar state. + /// + public enum TaskbarProgressBarState + { + /// + /// No progress is displayed. + /// + NoProgress = 0, + + /// + /// The progress is indeterminate (marquee). + /// + Indeterminate = 0x1, + + /// + /// Normal progress is displayed. + /// + Normal = 0x2, + + /// + /// An error occurred (red). + /// + Error = 0x4, + + /// + /// The operation is paused (yellow). + /// + Paused = 0x8 + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarInterfaces.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarInterfaces.cs new file mode 100644 index 0000000..15dbb63 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarInterfaces.cs @@ -0,0 +1,24 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell; +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Interface for jump list items + /// + public interface IJumpListItem + { + /// + /// Gets or sets this item's path + /// + string Path { get; set; } + } + + /// + /// Interface for jump list tasks + /// + public abstract class JumpListTask + { + internal abstract IShellLinkW NativeShellLink { get; } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarList.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarList.cs new file mode 100644 index 0000000..7dff3c7 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarList.cs @@ -0,0 +1,32 @@ +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Provides internal access to the functions provided by the ITaskbarList4 interface, + /// without being forced to refer to it through another singleton. + /// + internal static class TaskbarList + { + private static readonly object _syncLock = new object(); + + private static ITaskbarList4 _taskbarList; + internal static ITaskbarList4 Instance + { + get + { + if (_taskbarList == null) + { + lock (_syncLock) + { + if (_taskbarList == null) + { + _taskbarList = (ITaskbarList4)new CTaskbarList(); + _taskbarList.HrInit(); + } + } + } + + return _taskbarList; + } + } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarManager.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarManager.cs new file mode 100644 index 0000000..ade9405 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarManager.cs @@ -0,0 +1,272 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Diagnostics; +using System.Windows.Interop; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Represents an instance of the Windows taskbar + /// + public class TaskbarManager + { + // Hide the default constructor + private TaskbarManager() => CoreHelpers.ThrowIfNotWin7(); + + // Best practice recommends defining a private object to lock on + private static readonly object _syncLock = new object(); + + private static TaskbarManager _instance; + /// + /// Represents an instance of the Windows Taskbar + /// + public static TaskbarManager Instance + { + get + { + if (_instance == null) + { + lock (_syncLock) + { + if (_instance == null) + { + _instance = new TaskbarManager(); + } + } + } + + return _instance; + } + } + + /// + /// Applies an overlay to a taskbar button of the main application window to indicate application status or a notification to the user. + /// + /// The overlay icon + /// String that provides an alt text version of the information conveyed by the overlay, for accessibility purposes + public void SetOverlayIcon(System.Drawing.Icon icon, string accessibilityText) => TaskbarList.Instance.SetOverlayIcon( + OwnerHandle, + icon != null ? icon.Handle : IntPtr.Zero, + accessibilityText); + + /// + /// Applies an overlay to a taskbar button of the given window handle to indicate application status or a notification to the user. + /// + /// The handle of the window whose associated taskbar button receives the overlay. This handle must belong to a calling process associated with the button's application and must be a valid HWND or the call is ignored. + /// The overlay icon + /// String that provides an alt text version of the information conveyed by the overlay, for accessibility purposes + public void SetOverlayIcon(IntPtr windowHandle, System.Drawing.Icon icon, string accessibilityText) => TaskbarList.Instance.SetOverlayIcon( + windowHandle, + icon != null ? icon.Handle : IntPtr.Zero, + accessibilityText); + + /// + /// Applies an overlay to a taskbar button of the given WPF window to indicate application status or a notification to the user. + /// + /// The window whose associated taskbar button receives the overlay. This window belong to a calling process associated with the button's application and must be already loaded. + /// The overlay icon + /// String that provides an alt text version of the information conveyed by the overlay, for accessibility purposes + public void SetOverlayIcon(System.Windows.Window window, System.Drawing.Icon icon, string accessibilityText) => TaskbarList.Instance.SetOverlayIcon( + (new WindowInteropHelper(window)).Handle, + icon != null ? icon.Handle : IntPtr.Zero, + accessibilityText); + + /// + /// Displays or updates a progress bar hosted in a taskbar button of the main application window + /// to show the specific percentage completed of the full operation. + /// + /// An application-defined value that indicates the proportion of the operation that has been completed at the time the method is called. + /// An application-defined value that specifies the value currentValue will have when the operation is complete. + public void SetProgressValue(int currentValue, int maximumValue) => TaskbarList.Instance.SetProgressValue( + OwnerHandle, + Convert.ToUInt32(currentValue), + Convert.ToUInt32(maximumValue)); + + /// + /// Displays or updates a progress bar hosted in a taskbar button of the given window handle + /// to show the specific percentage completed of the full operation. + /// + /// The handle of the window whose associated taskbar button is being used as a progress indicator. + /// This window belong to a calling process associated with the button's application and must be already loaded. + /// An application-defined value that indicates the proportion of the operation that has been completed at the time the method is called. + /// An application-defined value that specifies the value currentValue will have when the operation is complete. + public void SetProgressValue(int currentValue, int maximumValue, IntPtr windowHandle) => TaskbarList.Instance.SetProgressValue( + windowHandle, + Convert.ToUInt32(currentValue), + Convert.ToUInt32(maximumValue)); + + /// + /// Displays or updates a progress bar hosted in a taskbar button of the given WPF window + /// to show the specific percentage completed of the full operation. + /// + /// The window whose associated taskbar button is being used as a progress indicator. + /// This window belong to a calling process associated with the button's application and must be already loaded. + /// An application-defined value that indicates the proportion of the operation that has been completed at the time the method is called. + /// An application-defined value that specifies the value currentValue will have when the operation is complete. + public void SetProgressValue(int currentValue, int maximumValue, System.Windows.Window window) => TaskbarList.Instance.SetProgressValue( + (new WindowInteropHelper(window)).Handle, + Convert.ToUInt32(currentValue), + Convert.ToUInt32(maximumValue)); + + /// + /// Sets the type and state of the progress indicator displayed on a taskbar button of the main application window. + /// + /// Progress state of the progress button + public void SetProgressState(TaskbarProgressBarState state) => TaskbarList.Instance.SetProgressState(OwnerHandle, (TaskbarProgressBarStatus)state); + + /// + /// Sets the type and state of the progress indicator displayed on a taskbar button + /// of the given window handle + /// + /// The handle of the window whose associated taskbar button is being used as a progress indicator. + /// This window belong to a calling process associated with the button's application and must be already loaded. + /// Progress state of the progress button + public void SetProgressState(TaskbarProgressBarState state, IntPtr windowHandle) => TaskbarList.Instance.SetProgressState(windowHandle, (TaskbarProgressBarStatus)state); + + /// + /// Sets the type and state of the progress indicator displayed on a taskbar button + /// of the given WPF window + /// + /// The window whose associated taskbar button is being used as a progress indicator. + /// This window belong to a calling process associated with the button's application and must be already loaded. + /// Progress state of the progress button + public void SetProgressState(TaskbarProgressBarState state, System.Windows.Window window) => TaskbarList.Instance.SetProgressState( + (new WindowInteropHelper(window)).Handle, + (TaskbarProgressBarStatus)state); + + private TabbedThumbnailManager _tabbedThumbnail; + /// + /// Gets the Tabbed Thumbnail manager class for adding/updating + /// tabbed thumbnail previews. + /// + public TabbedThumbnailManager TabbedThumbnail + { + get + { + if (_tabbedThumbnail == null) + { + _tabbedThumbnail = new TabbedThumbnailManager(); + } + return _tabbedThumbnail; + } + } + + private ThumbnailToolBarManager _thumbnailToolBarManager; + /// + /// Gets the Thumbnail toolbar manager class for adding/updating + /// toolbar buttons. + /// + public ThumbnailToolBarManager ThumbnailToolBars + { + get + { + if (_thumbnailToolBarManager == null) + { + _thumbnailToolBarManager = new ThumbnailToolBarManager(); + } + + return _thumbnailToolBarManager; + } + } + + /// + /// Gets or sets the application user model id. Use this to explicitly + /// set the application id when generating custom jump lists + /// + public string ApplicationId + { + get => GetCurrentProcessAppId(); + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentNullException("value"); + } + + SetCurrentProcessAppId(value); + ApplicationIdSetProcessWide = true; + } + } + + private IntPtr _ownerHandle; + /// + /// Sets the handle of the window whose taskbar button will be used + /// to display progress. + /// + internal IntPtr OwnerHandle + { + get + { + if (_ownerHandle == IntPtr.Zero) + { + var currentProcess = Process.GetCurrentProcess(); + + if (currentProcess == null || currentProcess.MainWindowHandle == IntPtr.Zero) + { + throw new InvalidOperationException(LocalizedMessages.TaskbarManagerValidWindowRequired); + } + + _ownerHandle = currentProcess.MainWindowHandle; + } + + return _ownerHandle; + } + } + + /// + /// Sets the application user model id for an individual window + /// + /// The app id to set + /// Window handle for the window that needs a specific application id + /// AppId specifies a unique Application User Model ID (AppID) for the application or individual + /// top-level window whose taskbar button will hold the custom JumpList built through the methods class. + /// By setting an appId for a specific window, the window will not be grouped with it's parent window/application. Instead it will have it's own taskbar button. + public void SetApplicationIdForSpecificWindow(IntPtr windowHandle, string appId) => + // Left as instance method, to follow singleton pattern. + TaskbarNativeMethods.SetWindowAppId(windowHandle, appId); + + /// + /// Sets the application user model id for a given window + /// + /// The app id to set + /// Window that needs a specific application id + /// AppId specifies a unique Application User Model ID (AppID) for the application or individual + /// top-level window whose taskbar button will hold the custom JumpList built through the methods class. + /// By setting an appId for a specific window, the window will not be grouped with it's parent window/application. Instead it will have it's own taskbar button. + public void SetApplicationIdForSpecificWindow(System.Windows.Window window, string appId) => + // Left as instance method, to follow singleton pattern. + TaskbarNativeMethods.SetWindowAppId((new WindowInteropHelper(window)).Handle, appId); + + /// + /// Sets the current process' explicit application user model id. + /// + /// The application id. + private void SetCurrentProcessAppId(string appId) => TaskbarNativeMethods.SetCurrentProcessExplicitAppUserModelID(appId); + + /// + /// Gets the current process' explicit application user model id. + /// + /// The app id or null if no app id has been defined. + private string GetCurrentProcessAppId() + { + var appId = string.Empty; + TaskbarNativeMethods.GetCurrentProcessExplicitAppUserModelID(out appId); + return appId; + } + + /// + /// Indicates if the user has set the application id for the whole process (all windows) + /// + internal bool ApplicationIdSetProcessWide { get; private set; } + + /// + /// Indicates whether this feature is supported on the current platform. + /// + public static bool IsPlatformSupported => + // We need Windows 7 onwards ... + CoreHelpers.RunningOnWin7; + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarWindow.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarWindow.cs new file mode 100644 index 0000000..cb08869 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarWindow.cs @@ -0,0 +1,218 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using System; +using System.Windows; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + internal class TaskbarWindow : IDisposable + { + internal TabbedThumbnailProxyWindow TabbedThumbnailProxyWindow { get; set; } + + internal ThumbnailToolbarProxyWindow ThumbnailToolbarProxyWindow { get; set; } + + internal bool EnableTabbedThumbnails { get; set; } + + internal bool EnableThumbnailToolbars { get; set; } + + internal IntPtr UserWindowHandle { get; set; } + + internal UIElement WindowsControl { get; set; } + + private TabbedThumbnail _tabbedThumbnailPreview; + internal TabbedThumbnail TabbedThumbnail + { + get => _tabbedThumbnailPreview; + set + { + if (_tabbedThumbnailPreview != null) + { + throw new InvalidOperationException(LocalizedMessages.TaskbarWindowValueSet); + } + + TabbedThumbnailProxyWindow = new TabbedThumbnailProxyWindow(value); + _tabbedThumbnailPreview = value; + _tabbedThumbnailPreview.TaskbarWindow = this; + } + } + + private ThumbnailToolBarButton[] _thumbnailButtons; + internal ThumbnailToolBarButton[] ThumbnailButtons + { + get => _thumbnailButtons; + set + { + _thumbnailButtons = value; + UpdateHandles(); + } + } + + private void UpdateHandles() + { + foreach (var button in _thumbnailButtons) + { + button.WindowHandle = WindowToTellTaskbarAbout; + button.AddedToTaskbar = false; + } + } + + + // TODO: Verify the logic of this property. There are situations where this will throw InvalidOperationException when it shouldn't. + internal IntPtr WindowToTellTaskbarAbout + { + get + { + if (EnableThumbnailToolbars && !EnableTabbedThumbnails && ThumbnailToolbarProxyWindow != null) + { + return ThumbnailToolbarProxyWindow.WindowToTellTaskbarAbout; + } + else if (!EnableThumbnailToolbars && EnableTabbedThumbnails && TabbedThumbnailProxyWindow != null) + { + return TabbedThumbnailProxyWindow.WindowToTellTaskbarAbout; + } + // Bug: What should happen when TabedThumbnailProxyWindow IS null, but it is enabled? + // This occurs during the TabbedThumbnailProxyWindow constructor at line 31. + else if (EnableTabbedThumbnails && EnableThumbnailToolbars && TabbedThumbnailProxyWindow != null) + { + return TabbedThumbnailProxyWindow.WindowToTellTaskbarAbout; + } + + throw new InvalidOperationException(); + } + } + + internal void SetTitle(string title) + { + if (TabbedThumbnailProxyWindow == null) + { + throw new InvalidOperationException(LocalizedMessages.TasbarWindowProxyWindowSet); + } + TabbedThumbnailProxyWindow.Text = title; + } + + internal TaskbarWindow(IntPtr userWindowHandle, params ThumbnailToolBarButton[] buttons) + { + if (userWindowHandle == IntPtr.Zero) + { + throw new ArgumentException(LocalizedMessages.CommonFileDialogInvalidHandle, "userWindowHandle"); + } + + if (buttons == null || buttons.Length == 0) + { + throw new ArgumentException(LocalizedMessages.TaskbarWindowEmptyButtonArray, "buttons"); + } + + // Create our proxy window + ThumbnailToolbarProxyWindow = new ThumbnailToolbarProxyWindow(userWindowHandle, buttons) + { + TaskbarWindow = this + }; + + // Set our current state + EnableThumbnailToolbars = true; + EnableTabbedThumbnails = false; + + // + ThumbnailButtons = buttons; + UserWindowHandle = userWindowHandle; + WindowsControl = null; + } + + internal TaskbarWindow(System.Windows.UIElement windowsControl, params ThumbnailToolBarButton[] buttons) + { + if (windowsControl == null) + { + throw new ArgumentNullException("windowsControl"); + } + + if (buttons == null || buttons.Length == 0) + { + throw new ArgumentException(LocalizedMessages.TaskbarWindowEmptyButtonArray, "buttons"); + } + + // Create our proxy window + ThumbnailToolbarProxyWindow = new ThumbnailToolbarProxyWindow(windowsControl, buttons) + { + TaskbarWindow = this + }; + + // Set our current state + EnableThumbnailToolbars = true; + EnableTabbedThumbnails = false; + + ThumbnailButtons = buttons; + UserWindowHandle = IntPtr.Zero; + WindowsControl = windowsControl; + } + + internal TaskbarWindow(TabbedThumbnail preview) + { + if (preview == null) { throw new ArgumentNullException("preview"); } + + // Create our proxy window + // Bug: This is only called in this constructor. Which will cause the property + // to fail if TaskbarWindow is initialized from a different constructor. + TabbedThumbnailProxyWindow = new TabbedThumbnailProxyWindow(preview); + + // set our current state + EnableThumbnailToolbars = false; + EnableTabbedThumbnails = true; + + // copy values + UserWindowHandle = preview.WindowHandle; + WindowsControl = preview.WindowsControl; + TabbedThumbnail = preview; + } + + #region IDisposable Members + + /// + /// + /// + ~TaskbarWindow() + { + Dispose(false); + } + + /// + /// Release the native objects. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void Dispose(bool disposing) + { + if (disposing) + { + // Dispose managed resources + if (_tabbedThumbnailPreview != null) + { + _tabbedThumbnailPreview.Dispose(); + } + _tabbedThumbnailPreview = null; + + if (ThumbnailToolbarProxyWindow != null) + { + ThumbnailToolbarProxyWindow.Dispose(); + } + ThumbnailToolbarProxyWindow = null; + + if (TabbedThumbnailProxyWindow != null) + { + TabbedThumbnailProxyWindow.Dispose(); + } + TabbedThumbnailProxyWindow = null; + + // Don't dispose the thumbnail buttons as they might be used in another window. + // Setting them to null will indicate we don't need use anymore. + _thumbnailButtons = null; + } + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarWindowManager.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarWindowManager.cs new file mode 100644 index 0000000..f2e7b11 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/TaskbarWindowManager.cs @@ -0,0 +1,820 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell; +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; + +/* Unmerged change from project 'Shell (net452)' +Before: +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.WindowsAPICodePack.Internal; +After: +using Microsoft.WindowsAPICodePack.Forms; +using System.Windows.Media; +*/ + +/* Unmerged change from project 'Shell (net462)' +Before: +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.WindowsAPICodePack.Internal; +After: +using Microsoft.WindowsAPICodePack.Forms; +using System.Windows.Media; +*/ + +/* Unmerged change from project 'Shell (net472)' +Before: +using Microsoft.WindowsAPICodePack.Shell.Resources; +using MS.WindowsAPICodePack.Internal; +After: +using Microsoft.WindowsAPICodePack.Forms; +using System.Windows.Media; +*/ +using System.Windows.Media; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + internal static class TaskbarWindowManager + { + internal static List _taskbarWindowList = new List(); + + private static bool _buttonsAdded; + + internal static void AddThumbnailButtons(IntPtr userWindowHandle, params ThumbnailToolBarButton[] buttons) + { + // Try to get an existing taskbar window for this user windowhandle + var taskbarWindow = GetTaskbarWindow(userWindowHandle, TaskbarProxyWindowType.ThumbnailToolbar); + TaskbarWindow temp = null; + try + { + AddThumbnailButtons( + taskbarWindow ?? (temp = new TaskbarWindow(userWindowHandle, buttons)), + taskbarWindow == null, + buttons); + } + catch + { + if (temp != null) { temp.Dispose(); } + throw; + } + } + + internal static void AddThumbnailButtons(System.Windows.UIElement control, params ThumbnailToolBarButton[] buttons) + { + // Try to get an existing taskbar window for this user uielement + var taskbarWindow = GetTaskbarWindow(control, TaskbarProxyWindowType.ThumbnailToolbar); + TaskbarWindow temp = null; + try + { + AddThumbnailButtons( + taskbarWindow ?? (temp = new TaskbarWindow(control, buttons)), + taskbarWindow == null, + buttons); + } + catch + { + if (temp != null) { temp.Dispose(); } + throw; + } + } + + private static void AddThumbnailButtons(TaskbarWindow taskbarWindow, bool add, params ThumbnailToolBarButton[] buttons) + { + if (add) + { + _taskbarWindowList.Add(taskbarWindow); + } + else if (taskbarWindow.ThumbnailButtons == null) + { + taskbarWindow.ThumbnailButtons = buttons; + } + else + { + // We already have buttons assigned + throw new InvalidOperationException(LocalizedMessages.TaskbarWindowManagerButtonsAlreadyAdded); + } + } + + internal static void AddTabbedThumbnail(TabbedThumbnail preview) + { + // Create a TOP-LEVEL proxy window for the user's source window/control + TaskbarWindow taskbarWindow = null; + + // get the TaskbarWindow for UIElement/WindowHandle respectfully. + if (preview.WindowHandle == IntPtr.Zero) + { + taskbarWindow = GetTaskbarWindow(preview.WindowsControl, TaskbarProxyWindowType.TabbedThumbnail); + } + else + { + taskbarWindow = GetTaskbarWindow(preview.WindowHandle, TaskbarProxyWindowType.TabbedThumbnail); + } + + //create taskbar, or set its TabbedThumbnail + if (taskbarWindow == null) + { + taskbarWindow = new TaskbarWindow(preview); + _taskbarWindowList.Add(taskbarWindow); + } + else if (taskbarWindow.TabbedThumbnail == null) + { + taskbarWindow.TabbedThumbnail = preview; + } + + // Listen for Title changes + preview.TitleChanged += new EventHandler(thumbnailPreview_TitleChanged); + preview.TooltipChanged += new EventHandler(thumbnailPreview_TooltipChanged); + + // Get/Set properties for proxy window + var windowHandle = taskbarWindow.WindowToTellTaskbarAbout; + + // Register this new tab and set it as being active. + TaskbarList.Instance.RegisterTab(windowHandle, preview.ParentWindowHandle); + TaskbarList.Instance.SetTabOrder(windowHandle, IntPtr.Zero); + TaskbarList.Instance.SetTabActive(windowHandle, preview.ParentWindowHandle, 0); + + // We need to make sure we can set these properties even when running with admin + TabbedThumbnailNativeMethods.ChangeWindowMessageFilter( + TabbedThumbnailNativeMethods.WmDwmSendIconicThumbnail, + TabbedThumbnailNativeMethods.MsgfltAdd); + + TabbedThumbnailNativeMethods.ChangeWindowMessageFilter( + TabbedThumbnailNativeMethods.WmDwmSendIconicLivePreviewBitmap, + TabbedThumbnailNativeMethods.MsgfltAdd); + + // BUG: There should be somewhere to disable CustomWindowPreview. I didn't find it. + TabbedThumbnailNativeMethods.EnableCustomWindowPreview(windowHandle, true); + + // Make sure we use the initial title set by the user + // Trigger a "fake" title changed event, so the title is set on the taskbar thumbnail. + // Empty/null title will be ignored. + thumbnailPreview_TitleChanged(preview, EventArgs.Empty); + thumbnailPreview_TooltipChanged(preview, EventArgs.Empty); + + // Indicate to the preview that we've added it on the taskbar + preview.AddedToTaskbar = true; + } + + internal static TaskbarWindow GetTaskbarWindow(System.Windows.UIElement windowsControl, TaskbarProxyWindowType taskbarProxyWindowType) + { + if (windowsControl == null) { throw new ArgumentNullException("windowsControl"); } + + var toReturn = _taskbarWindowList.FirstOrDefault(window => + { + return (window.TabbedThumbnail != null && window.TabbedThumbnail.WindowsControl == windowsControl) || + (window.ThumbnailToolbarProxyWindow != null && + window.ThumbnailToolbarProxyWindow.WindowsControl == windowsControl); + }); + + if (toReturn != null) + { + if (taskbarProxyWindowType == TaskbarProxyWindowType.ThumbnailToolbar) + { + toReturn.EnableThumbnailToolbars = true; + } + else if (taskbarProxyWindowType == TaskbarProxyWindowType.TabbedThumbnail) + { + toReturn.EnableTabbedThumbnails = true; + } + } + + return toReturn; + } + + internal static TaskbarWindow GetTaskbarWindow(IntPtr userWindowHandle, TaskbarProxyWindowType taskbarProxyWindowType) + { + if (userWindowHandle == IntPtr.Zero) + { + throw new ArgumentException(LocalizedMessages.CommonFileDialogInvalidHandle, "userWindowHandle"); + } + + var toReturn = _taskbarWindowList.FirstOrDefault(window => window.UserWindowHandle == userWindowHandle); + + // If its not in the list, return null so it can be added. + if (toReturn != null) + { + if (taskbarProxyWindowType == TaskbarProxyWindowType.ThumbnailToolbar) + { + toReturn.EnableThumbnailToolbars = true; + } + else if (taskbarProxyWindowType == TaskbarProxyWindowType.TabbedThumbnail) + { + toReturn.EnableTabbedThumbnails = true; + } + } + + return toReturn; + } + + #region Message dispatch methods + private static void DispatchTaskbarButtonMessages(ref System.Windows.Forms.Message m, TaskbarWindow taskbarWindow) + { + if (m.Msg == (int)TaskbarNativeMethods.WmTaskbarButtonCreated) + { + AddButtons(taskbarWindow); + } + else + { + if (!_buttonsAdded) + { + AddButtons(taskbarWindow); + } + + if (m.Msg == TaskbarNativeMethods.WmCommand && + CoreNativeMethods.GetHiWord(m.WParam.ToInt64(), 16) == ThumbButton.Clicked) + { + var buttonId = CoreNativeMethods.GetLoWord(m.WParam.ToInt64()); + + var buttonsFound = + from b in taskbarWindow.ThumbnailButtons + where b.Id == buttonId + select b; + + foreach (var button in buttonsFound) + { + button.FireClick(taskbarWindow); + } + } + } + } + + private static bool DispatchActivateMessage(ref System.Windows.Forms.Message m, TaskbarWindow taskbarWindow) + { + if (m.Msg == (int)WindowMessage.Activate) + { + // Raise the event + taskbarWindow.TabbedThumbnail.OnTabbedThumbnailActivated(); + SetActiveTab(taskbarWindow); + return true; + } + return false; + } + + private static bool DispatchSendIconThumbnailMessage(ref System.Windows.Forms.Message m, TaskbarWindow taskbarWindow) + { + if (m.Msg == (int)TaskbarNativeMethods.WmDwmSendIconThumbnail) + { + var width = (int)((long)m.LParam >> 16); + var height = (int)(((long)m.LParam) & (0xFFFF)); + var requestedSize = new Size(width, height); + + // Fire an event to let the user update their bitmap + taskbarWindow.TabbedThumbnail.OnTabbedThumbnailBitmapRequested(); + + var hBitmap = IntPtr.Zero; + + // Default size for the thumbnail + var realWindowSize = new Size(200, 200); + + // Get the size of teh control or UIElement + if (taskbarWindow.TabbedThumbnail.WindowHandle != IntPtr.Zero) + { + TabbedThumbnailNativeMethods.GetClientSize(taskbarWindow.TabbedThumbnail.WindowHandle, out realWindowSize); + } + else if (taskbarWindow.TabbedThumbnail.WindowsControl != null) + { + realWindowSize = new Size( + Convert.ToInt32(taskbarWindow.TabbedThumbnail.WindowsControl.RenderSize.Width), + Convert.ToInt32(taskbarWindow.TabbedThumbnail.WindowsControl.RenderSize.Height)); + } + + if (realWindowSize.Height == -1 && realWindowSize.Width == -1) + { + realWindowSize.Width = realWindowSize.Height = 199; + } + + // capture the bitmap for the given control + // If the user has already specified us a bitmap to use, use that. + if (taskbarWindow.TabbedThumbnail.ClippingRectangle != null && + taskbarWindow.TabbedThumbnail.ClippingRectangle.Value != Rectangle.Empty) + { + if (taskbarWindow.TabbedThumbnail.CurrentHBitmap == IntPtr.Zero) + { + hBitmap = GrabBitmap(taskbarWindow, realWindowSize); + } + else + { + hBitmap = taskbarWindow.TabbedThumbnail.CurrentHBitmap; + } + + // Clip the bitmap we just got. + var bmp = Bitmap.FromHbitmap(hBitmap); + + var clippingRectangle = taskbarWindow.TabbedThumbnail.ClippingRectangle.Value; + + // If our clipping rect is out of bounds, update it + if (clippingRectangle.Height > requestedSize.Height) + { + clippingRectangle.Height = requestedSize.Height; + } + if (clippingRectangle.Width > requestedSize.Width) + { + clippingRectangle.Width = requestedSize.Width; + } + + // NOTE: Is this a memory leak? + bmp = bmp.Clone(clippingRectangle, bmp.PixelFormat); + + // Make sure we dispose the bitmap before assigning, otherwise we'll have a memory leak + if (hBitmap != IntPtr.Zero && taskbarWindow.TabbedThumbnail.CurrentHBitmap == IntPtr.Zero) + { + ShellNativeMethods.DeleteObject(hBitmap); + } + hBitmap = bmp.GetHbitmap(); + bmp.Dispose(); + } + else + { + // Else, user didn't want any clipping, if they haven't provided us a bitmap, + // use the screencapture utility and capture it. + + hBitmap = taskbarWindow.TabbedThumbnail.CurrentHBitmap; + + // If no bitmap, capture one using the utility + if (hBitmap == IntPtr.Zero) + { + hBitmap = GrabBitmap(taskbarWindow, realWindowSize); + } + } + + // Only set the thumbnail if it's not null. + // If it's null (either we didn't get the bitmap or size was 0), + // let DWM handle it + if (hBitmap != IntPtr.Zero) + { + var temp = TabbedThumbnailScreenCapture.ResizeImageWithAspect( + hBitmap, requestedSize.Width, requestedSize.Height, true); + + if (taskbarWindow.TabbedThumbnail.CurrentHBitmap == IntPtr.Zero) + { + ShellNativeMethods.DeleteObject(hBitmap); + } + + hBitmap = temp.GetHbitmap(); + TabbedThumbnailNativeMethods.SetIconicThumbnail(taskbarWindow.WindowToTellTaskbarAbout, hBitmap); + temp.Dispose(); + } + + // If the bitmap we have is not coming from the user (i.e. we created it here), + // then make sure we delete it as we don't need it now. + if (taskbarWindow.TabbedThumbnail.CurrentHBitmap == IntPtr.Zero) + { + ShellNativeMethods.DeleteObject(hBitmap); + } + + return true; + } + return false; + } + + private static bool DispatchLivePreviewBitmapMessage(ref System.Windows.Forms.Message m, TaskbarWindow taskbarWindow) + { + if (m.Msg == (int)TaskbarNativeMethods.WmDwmSendIconicLivePreviewBitmap) + { + // Try to get the width/height + var width = (int)(((long)m.LParam) >> 16); + var height = (int)(((long)m.LParam) & (0xFFFF)); + + // Default size for the thumbnail + var realWindowSize = new Size(200, 200); + + if (taskbarWindow.TabbedThumbnail.WindowHandle != IntPtr.Zero) + { + TabbedThumbnailNativeMethods.GetClientSize(taskbarWindow.TabbedThumbnail.WindowHandle, out realWindowSize); + } + else if (taskbarWindow.TabbedThumbnail.WindowsControl != null) + { + realWindowSize = new Size( + Convert.ToInt32(taskbarWindow.TabbedThumbnail.WindowsControl.RenderSize.Width), + Convert.ToInt32(taskbarWindow.TabbedThumbnail.WindowsControl.RenderSize.Height)); + } + + // If we don't have a valid height/width, use the original window's size + if (width <= 0) + { + width = realWindowSize.Width; + } + if (height <= 0) + { + height = realWindowSize.Height; + } + + // Fire an event to let the user update their bitmap + // Raise the event + taskbarWindow.TabbedThumbnail.OnTabbedThumbnailBitmapRequested(); + + // capture the bitmap for the given control + // If the user has already specified us a bitmap to use, use that. + var hBitmap = taskbarWindow.TabbedThumbnail.CurrentHBitmap == IntPtr.Zero ? GrabBitmap(taskbarWindow, realWindowSize) : taskbarWindow.TabbedThumbnail.CurrentHBitmap; + + // If we have a valid parent window handle, + // calculate the offset so we can place the "peek" bitmap + // correctly on the app window + if (taskbarWindow.TabbedThumbnail.ParentWindowHandle != IntPtr.Zero && taskbarWindow.TabbedThumbnail.WindowHandle != IntPtr.Zero) + { + var offset = new System.Drawing.Point(); + + // if we don't have a offset specified already by the user... + if (!taskbarWindow.TabbedThumbnail.PeekOffset.HasValue) + { + offset = WindowUtilities.GetParentOffsetOfChild(taskbarWindow.TabbedThumbnail.WindowHandle, taskbarWindow.TabbedThumbnail.ParentWindowHandle); + } + else + { + offset = new System.Drawing.Point(Convert.ToInt32(taskbarWindow.TabbedThumbnail.PeekOffset.Value.X), + Convert.ToInt32(taskbarWindow.TabbedThumbnail.PeekOffset.Value.Y)); + } + + // Only set the peek bitmap if it's not null. + // If it's null (either we didn't get the bitmap or size was 0), + // let DWM handle it + if (hBitmap != IntPtr.Zero) + { + if (offset.X >= 0 && offset.Y >= 0) + { + TabbedThumbnailNativeMethods.SetPeekBitmap( + taskbarWindow.WindowToTellTaskbarAbout, + hBitmap, offset, + taskbarWindow.TabbedThumbnail.DisplayFrameAroundBitmap); + } + } + + // If the bitmap we have is not coming from the user (i.e. we created it here), + // then make sure we delete it as we don't need it now. + if (taskbarWindow.TabbedThumbnail.CurrentHBitmap == IntPtr.Zero) + { + ShellNativeMethods.DeleteObject(hBitmap); + } + + return true; + } + // Else, we don't have a valid window handle from the user. This is mostly likely because + // we have a WPF UIElement control. If that's the case, use a different screen capture method + // and also couple of ways to try to calculate the control's offset w.r.t it's parent. + else if (taskbarWindow.TabbedThumbnail.ParentWindowHandle != IntPtr.Zero && + taskbarWindow.TabbedThumbnail.WindowsControl != null) + { + System.Windows.Point offset; + + if (!taskbarWindow.TabbedThumbnail.PeekOffset.HasValue) + { + // Calculate the offset for a WPF UIElement control + // For hidden controls, we can't seem to perform the transform. + var objGeneralTransform = taskbarWindow.TabbedThumbnail.WindowsControl.TransformToVisual(taskbarWindow.TabbedThumbnail.WindowsControlParentWindow); + offset = objGeneralTransform.Transform(new System.Windows.Point(0, 0)); + } + else + { + offset = new System.Windows.Point(taskbarWindow.TabbedThumbnail.PeekOffset.Value.X, taskbarWindow.TabbedThumbnail.PeekOffset.Value.Y); + } + + // Only set the peek bitmap if it's not null. + // If it's null (either we didn't get the bitmap or size was 0), + // let DWM handle it + if (hBitmap != IntPtr.Zero) + { + if (offset.X >= 0 && offset.Y >= 0) + { + TabbedThumbnailNativeMethods.SetPeekBitmap( + taskbarWindow.WindowToTellTaskbarAbout, + hBitmap, new System.Drawing.Point((int)offset.X, (int)offset.Y), + taskbarWindow.TabbedThumbnail.DisplayFrameAroundBitmap); + } + else + { + TabbedThumbnailNativeMethods.SetPeekBitmap( + taskbarWindow.WindowToTellTaskbarAbout, + hBitmap, + taskbarWindow.TabbedThumbnail.DisplayFrameAroundBitmap); + } + } + + // If the bitmap we have is not coming from the user (i.e. we created it here), + // then make sure we delete it as we don't need it now. + if (taskbarWindow.TabbedThumbnail.CurrentHBitmap == IntPtr.Zero) + { + ShellNativeMethods.DeleteObject(hBitmap); + } + + return true; + } + else + { + // Else (no parent specified), just set the bitmap. It would take over the entire + // application window (would work only if you are a MDI app) + + // Only set the peek bitmap if it's not null. + // If it's null (either we didn't get the bitmap or size was 0), + // let DWM handle it + if (hBitmap != null) + { + TabbedThumbnailNativeMethods.SetPeekBitmap(taskbarWindow.WindowToTellTaskbarAbout, hBitmap, taskbarWindow.TabbedThumbnail.DisplayFrameAroundBitmap); + } + + // If the bitmap we have is not coming from the user (i.e. we created it here), + // then make sure we delete it as we don't need it now. + if (taskbarWindow.TabbedThumbnail.CurrentHBitmap == IntPtr.Zero) + { + ShellNativeMethods.DeleteObject(hBitmap); + } + + return true; + } + } + return false; + } + + private static bool DispatchDestroyMessage(ref System.Windows.Forms.Message m, TaskbarWindow taskbarWindow) + { + if (m.Msg == (int)WindowMessage.Destroy) + { + TaskbarList.Instance.UnregisterTab(taskbarWindow.WindowToTellTaskbarAbout); + + taskbarWindow.TabbedThumbnail.RemovedFromTaskbar = true; + + return true; + } + return false; + } + + private static bool DispatchNCDestroyMessage(ref System.Windows.Forms.Message m, TaskbarWindow taskbarWindow) + { + if (m.Msg == (int)WindowMessage.NCDestroy) + { + // Raise the event + taskbarWindow.TabbedThumbnail.OnTabbedThumbnailClosed(); + + // Remove the taskbar window from our internal list + if (_taskbarWindowList.Contains(taskbarWindow)) + { + _taskbarWindowList.Remove(taskbarWindow); + } + + taskbarWindow.Dispose(); + + return true; + } + return false; + } + + private static bool DispatchSystemCommandMessage(ref System.Windows.Forms.Message m, TaskbarWindow taskbarWindow) + { + if (m.Msg == (int)WindowMessage.SystemCommand) + { + if (((int)m.WParam) == TabbedThumbnailNativeMethods.ScClose) + { + // Raise the event + if (taskbarWindow.TabbedThumbnail.OnTabbedThumbnailClosed()) + { + // Remove the taskbar window from our internal list + if (_taskbarWindowList.Contains(taskbarWindow)) + { + _taskbarWindowList.Remove(taskbarWindow); + } + + taskbarWindow.Dispose(); + taskbarWindow = null; + } + } + else if (((int)m.WParam) == TabbedThumbnailNativeMethods.ScMaximize) + { + // Raise the event + taskbarWindow.TabbedThumbnail.OnTabbedThumbnailMaximized(); + } + else if (((int)m.WParam) == TabbedThumbnailNativeMethods.ScMinimize) + { + // Raise the event + taskbarWindow.TabbedThumbnail.OnTabbedThumbnailMinimized(); + } + + return true; + } + return false; + } + + #endregion + + /// + /// Dispatches a window message so that the appropriate events + /// can be invoked. This is used for the Taskbar's thumbnail toolbar feature. + /// + /// The window message, typically obtained + /// from a Windows Forms or WPF window procedure. + /// Taskbar window for which we are intercepting the messages + /// Returns true if this method handles the window message + internal static bool DispatchMessage(ref System.Windows.Forms.Message m, TaskbarWindow taskbarWindow) + { + if (taskbarWindow.EnableThumbnailToolbars) + { + DispatchTaskbarButtonMessages(ref m, taskbarWindow); + } + + // If we are removed from the taskbar, ignore all the messages + if (taskbarWindow.EnableTabbedThumbnails) + { + if (taskbarWindow.TabbedThumbnail == null || + taskbarWindow.TabbedThumbnail.RemovedFromTaskbar) + { + return false; + } + + if (DispatchActivateMessage(ref m, taskbarWindow)) + { + return true; + } + + if (DispatchSendIconThumbnailMessage(ref m, taskbarWindow)) + { + return true; + } + + if (DispatchLivePreviewBitmapMessage(ref m, taskbarWindow)) + { + return true; + } + + if (DispatchDestroyMessage(ref m, taskbarWindow)) + { + return true; + } + + if (DispatchNCDestroyMessage(ref m, taskbarWindow)) + { + return true; + } + + if (DispatchSystemCommandMessage(ref m, taskbarWindow)) + { + return true; + } + } + + return false; + } + + /// + /// Helper function to capture a bitmap for a given window handle or incase of WPF app, + /// an UIElement. + /// + /// The proxy window for which a bitmap needs to be created + /// Size for the requested bitmap image + /// Bitmap captured from the window handle or UIElement. Null if the window is hidden or it's size is zero. + private static IntPtr GrabBitmap(TaskbarWindow taskbarWindow, System.Drawing.Size requestedSize) + { + var hBitmap = IntPtr.Zero; + + if (taskbarWindow.TabbedThumbnail.WindowHandle != IntPtr.Zero) + { //TabbedThumbnail is linked to WinformsControl + if (taskbarWindow.TabbedThumbnail.CurrentHBitmap == IntPtr.Zero) + { + using (var bmp = TabbedThumbnailScreenCapture.GrabWindowBitmap( + taskbarWindow.TabbedThumbnail.WindowHandle, requestedSize)) + { + + hBitmap = bmp.GetHbitmap(); + } + } + else + { + using (Image img = Image.FromHbitmap(taskbarWindow.TabbedThumbnail.CurrentHBitmap)) + { + using (var bmp = new Bitmap(img, requestedSize)) + { + hBitmap = bmp != null ? bmp.GetHbitmap() : IntPtr.Zero; + } + } + } + } + else if (taskbarWindow.TabbedThumbnail.WindowsControl != null) + { //TabbedThumbnail is linked to a WPF UIElement + if (taskbarWindow.TabbedThumbnail.CurrentHBitmap == IntPtr.Zero) + { + var bmp = TabbedThumbnailScreenCapture.GrabWindowBitmap( + taskbarWindow.TabbedThumbnail.WindowsControl, + 96, 96, requestedSize.Width, requestedSize.Height); + + if (bmp != null) + { + hBitmap = bmp.GetHbitmap(); + bmp.Dispose(); + } + } + else + { + using (Image img = Image.FromHbitmap(taskbarWindow.TabbedThumbnail.CurrentHBitmap)) + { + using (var bmp = new Bitmap(img, requestedSize)) + { + + hBitmap = bmp != null ? bmp.GetHbitmap() : IntPtr.Zero; + } + } + } + } + + return hBitmap; + } + + internal static void SetActiveTab(TaskbarWindow taskbarWindow) + { + if (taskbarWindow != null) + { + TaskbarList.Instance.SetTabActive( + taskbarWindow.WindowToTellTaskbarAbout, + taskbarWindow.TabbedThumbnail.ParentWindowHandle, 0); + } + } + + internal static void UnregisterTab(TaskbarWindow taskbarWindow) + { + if (taskbarWindow != null) + { + TaskbarList.Instance.UnregisterTab(taskbarWindow.WindowToTellTaskbarAbout); + } + } + + internal static void InvalidatePreview(TaskbarWindow taskbarWindow) + { + if (taskbarWindow != null) + { + TabbedThumbnailNativeMethods.DwmInvalidateIconicBitmaps( + taskbarWindow.WindowToTellTaskbarAbout); + } + } + + private static void AddButtons(TaskbarWindow taskbarWindow) + { + // Add the buttons + // Get the array of thumbnail buttons in native format + var nativeButtons = (from thumbButton in taskbarWindow.ThumbnailButtons + select thumbButton.Win32ThumbButton).ToArray(); + + // Add the buttons on the taskbar + var hr = TaskbarList.Instance.ThumbBarAddButtons(taskbarWindow.WindowToTellTaskbarAbout, (uint)taskbarWindow.ThumbnailButtons.Length, nativeButtons); + + if (!CoreErrorHelper.Succeeded(hr)) + { + throw new ShellException(hr); + } + + _buttonsAdded = true; + + foreach (var button in taskbarWindow.ThumbnailButtons) + { + button.AddedToTaskbar = _buttonsAdded; + } + } + + #region Event handlers + + private static void thumbnailPreview_TooltipChanged(object sender, EventArgs e) + { + var preview = sender as TabbedThumbnail; + + TaskbarWindow taskbarWindow = null; + + if (preview.WindowHandle == IntPtr.Zero) + { + taskbarWindow = GetTaskbarWindow(preview.WindowsControl, TaskbarProxyWindowType.TabbedThumbnail); + } + else + { + taskbarWindow = GetTaskbarWindow(preview.WindowHandle, TaskbarProxyWindowType.TabbedThumbnail); + } + + // Update the proxy window for the tabbed thumbnail + if (taskbarWindow != null) + { + TaskbarList.Instance.SetThumbnailTooltip(taskbarWindow.WindowToTellTaskbarAbout, preview.Tooltip); + } + } + + private static void thumbnailPreview_TitleChanged(object sender, EventArgs e) + { + var preview = sender as TabbedThumbnail; + + TaskbarWindow taskbarWindow = null; + + if (preview.WindowHandle == IntPtr.Zero) + { + taskbarWindow = GetTaskbarWindow(preview.WindowsControl, TaskbarProxyWindowType.TabbedThumbnail); + } + else + { + taskbarWindow = GetTaskbarWindow(preview.WindowHandle, TaskbarProxyWindowType.TabbedThumbnail); + } + + // Update the proxy window for the tabbed thumbnail + if (taskbarWindow != null) + { + taskbarWindow.SetTitle(preview.Title); + } + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailButton.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailButton.cs new file mode 100644 index 0000000..9859335 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailButton.cs @@ -0,0 +1,344 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Drawing; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Represents a taskbar thumbnail button in the thumbnail toolbar. + /// + public sealed class ThumbnailToolBarButton : IDisposable + { + private static uint nextId = 101; + private ThumbButton win32ThumbButton; + + /// + /// The event that occurs when the taskbar thumbnail button + /// is clicked. + /// + public event EventHandler Click; + + // Internal bool to track whether we should be updating the taskbar + // if any of our properties change or if it's just an internal update + // on the properties (via the constructor) + private readonly bool internalUpdate = false; + + /// + /// Initializes an instance of this class + /// + /// The icon to use for this button + /// The tooltip string to use for this button. + public ThumbnailToolBarButton(Icon icon, string tooltip) + { + // Start internal update (so we don't accidently update the taskbar + // via the native API) + internalUpdate = true; + + // Set our id + Id = nextId; + + // increment the ID + if (nextId == int.MaxValue) + nextId = 101; // our starting point + else + nextId++; + + // Set user settings + Icon = icon; + Tooltip = tooltip; + + // Defaults + Enabled = true; + + // Create a native + win32ThumbButton = new ThumbButton(); + + // End our internal update + internalUpdate = false; + } + + #region Public properties + + /// + /// Gets thumbnail button's id. + /// + internal uint Id { get; set; } + + private Icon icon; + /// + /// Gets or sets the thumbnail button's icon. + /// + public Icon Icon + { + get => icon; + set + { + if (icon != value) + { + icon = value; + UpdateThumbnailButton(); + } + } + } + + private string tooltip; + /// + /// Gets or sets the thumbnail button's tooltip. + /// + public string Tooltip + { + get => tooltip; + set + { + if (tooltip != value) + { + tooltip = value; + UpdateThumbnailButton(); + } + } + } + + private bool visible = true; + /// + /// Gets or sets the thumbnail button's visibility. Default is true. + /// + public bool Visible + { + get => (Flags & ThumbButtonOptions.Hidden) == 0; + set + { + if (visible != value) + { + visible = value; + + if (value) + { + Flags &= ~(ThumbButtonOptions.Hidden); + } + else + { + Flags |= ThumbButtonOptions.Hidden; + } + + UpdateThumbnailButton(); + } + + } + } + + private bool enabled = true; + /// + /// Gets or sets the thumbnail button's enabled state. If the button is disabled, it is present, + /// but has a visual state that indicates that it will not respond to user action. Default is true. + /// + public bool Enabled + { + get => (Flags & ThumbButtonOptions.Disabled) == 0; + set + { + if (value != enabled) + { + enabled = value; + + if (value) + { + Flags &= ~(ThumbButtonOptions.Disabled); + } + else + { + Flags |= ThumbButtonOptions.Disabled; + } + + UpdateThumbnailButton(); + } + } + } + + private bool dismissOnClick; + /// + /// Gets or sets the property that describes the behavior when the button is clicked. + /// If set to true, the taskbar button's flyout will close immediately. Default is false. + /// + public bool DismissOnClick + { + get => (Flags & ThumbButtonOptions.DismissOnClick) == 0; + set + { + if (value != dismissOnClick) + { + dismissOnClick = value; + + if (value) + { + Flags |= ThumbButtonOptions.DismissOnClick; + } + else + { + Flags &= ~(ThumbButtonOptions.DismissOnClick); + } + + UpdateThumbnailButton(); + } + } + } + + private bool isInteractive = true; + /// + /// Gets or sets the property that describes whether the button is interactive with the user. Default is true. + /// + /// + /// Non-interactive buttons don't display any hover behavior nor do they raise click events. + /// They are intended to be used as status icons. This is mostly similar to being not Enabled, + /// but the image is not desaturated. + /// + public bool IsInteractive + { + get => (Flags & ThumbButtonOptions.NonInteractive) == 0; + set + { + if (value != isInteractive) + { + isInteractive = value; + + if (value) + { + Flags &= ~(ThumbButtonOptions.NonInteractive); + } + else + { + Flags |= ThumbButtonOptions.NonInteractive; + } + + UpdateThumbnailButton(); + } + } + } + + #endregion + + #region Internal Methods + + /// + /// Native flags enum (used when creating the native button) + /// + internal ThumbButtonOptions Flags { get; set; } + + /// + /// Native representation of the thumbnail button + /// + internal ThumbButton Win32ThumbButton + { + get + { + win32ThumbButton.Id = Id; + win32ThumbButton.Tip = Tooltip; + win32ThumbButton.Icon = Icon != null ? Icon.Handle : IntPtr.Zero; + win32ThumbButton.Flags = Flags; + + win32ThumbButton.Mask = ThumbButtonMask.THB_FLAGS; + if (Tooltip != null) + { + win32ThumbButton.Mask |= ThumbButtonMask.Tooltip; + } + if (Icon != null) + { + win32ThumbButton.Mask |= ThumbButtonMask.Icon; + } + + return win32ThumbButton; + } + } + + /// + /// The window manager should call this method to raise the public click event to all + /// the subscribers. + /// + /// Taskbar Window associated with this button + internal void FireClick(TaskbarWindow taskbarWindow) + { + if (Click != null && taskbarWindow != null) + { + if (taskbarWindow.UserWindowHandle != IntPtr.Zero) + { + Click(this, new ThumbnailButtonClickedEventArgs(taskbarWindow.UserWindowHandle, this)); + } + else if (taskbarWindow.WindowsControl != null) + { + Click(this, new ThumbnailButtonClickedEventArgs(taskbarWindow.WindowsControl, this)); + } + } + } + + /// + /// Handle to the window to which this button is for (on the taskbar). + /// + internal IntPtr WindowHandle + { + get; + set; + } + + /// + /// Indicates if this button was added to the taskbar. If it's not yet added, + /// then we can't do any updates on it. + /// + internal bool AddedToTaskbar + { + get; + set; + } + + internal void UpdateThumbnailButton() + { + if (internalUpdate || !AddedToTaskbar) { return; } + + // Get the array of thumbnail buttons in native format + ThumbButton[] nativeButtons = { Win32ThumbButton }; + + var hr = TaskbarList.Instance.ThumbBarUpdateButtons(WindowHandle, 1, nativeButtons); + + if (!CoreErrorHelper.Succeeded(hr)) { throw new ShellException(hr); } + } + + #endregion + + #region IDisposable Members + + /// + /// + /// + ~ThumbnailToolBarButton() + { + Dispose(false); + } + + /// + /// Release the native objects. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Release the native objects. + /// + /// + public void Dispose(bool disposing) + { + if (disposing) + { + // Dispose managed resources + Icon.Dispose(); + tooltip = null; + } + } + + #endregion + } + +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailButtonClickedEventArgs.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailButtonClickedEventArgs.cs new file mode 100644 index 0000000..9327c56 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailButtonClickedEventArgs.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Windows; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Event args for TabbedThumbnailButton.Click event + /// + public class ThumbnailButtonClickedEventArgs : EventArgs + { + /// + /// Creates a Event Args for the TabbedThumbnailButton.Click event + /// + /// Window handle for the control/window related to the event + /// Thumbnail toolbar button that was clicked + public ThumbnailButtonClickedEventArgs(IntPtr windowHandle, ThumbnailToolBarButton button) + { + ThumbnailButton = button; + WindowHandle = windowHandle; + WindowsControl = null; + } + + /// + /// Creates a Event Args for the TabbedThumbnailButton.Click event + /// + /// WPF Control (UIElement) related to the event + /// Thumbnail toolbar button that was clicked + public ThumbnailButtonClickedEventArgs(UIElement windowsControl, ThumbnailToolBarButton button) + { + ThumbnailButton = button; + WindowHandle = IntPtr.Zero; + WindowsControl = windowsControl; + } + + /// + /// Gets the Window handle for the specific control/window that is related to this event. + /// + /// For WPF Controls (UIElement) the WindowHandle will be IntPtr.Zero. + /// Check the WindowsControl property to get the specific control associated with this event. + public IntPtr WindowHandle { get; private set; } + + /// + /// Gets the WPF Control (UIElement) that is related to this event. This property may be null + /// for non-WPF applications. + /// + public UIElement WindowsControl { get; private set; } + + /// + /// Gets the ThumbnailToolBarButton that was clicked + /// + public ThumbnailToolBarButton ThumbnailButton { get; private set; } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailToolbarManager.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailToolbarManager.cs new file mode 100644 index 0000000..93547ea --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailToolbarManager.cs @@ -0,0 +1,77 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using System; +using System.Windows; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Thumbnail toolbar manager class for adding a thumbnail toolbar with a specified set of buttons + /// to the thumbnail image of a window in a taskbar button flyout. + /// + public class ThumbnailToolBarManager + { + internal ThumbnailToolBarManager() + { + // Hide the public constructor so users can't create an instance of this class. + } + + /// + /// Adds thumbnail toolbar for the specified window. + /// + /// Window handle for which the thumbnail toolbar buttons need to be added + /// Thumbnail buttons for the window's thumbnail toolbar + /// If the number of buttons exceed the maximum allowed capacity (7). + /// If the Window Handle passed in invalid + /// After a toolbar has been added to a thumbnail, buttons can be altered only through various + /// properties on the . While individual buttons cannot be added or removed, + /// they can be shown and hidden through as needed. + /// The toolbar itself cannot be removed without re-creating the window itself. + /// + public void AddButtons(IntPtr windowHandle, params ThumbnailToolBarButton[] buttons) + { + if (windowHandle == IntPtr.Zero) + { + throw new ArgumentException(LocalizedMessages.ThumbnailManagerInvalidHandle, "windowHandle"); + } + VerifyButtons(buttons); + + // Add the buttons to our window manager, which will also create a proxy window + TaskbarWindowManager.AddThumbnailButtons(windowHandle, buttons); + } + + /// + /// Adds thumbnail toolbar for the specified WPF Control. + /// + /// WPF Control for which the thumbnail toolbar buttons need to be added + /// Thumbnail buttons for the window's thumbnail toolbar + /// If the number of buttons exceed the maximum allowed capacity (7). + /// If the control passed in null + /// After a toolbar has been added to a thumbnail, buttons can be altered only through various + /// properties on the ThumbnailToolBarButton. While individual buttons cannot be added or removed, + /// they can be shown and hidden through ThumbnailToolBarButton.Visible as needed. + /// The toolbar itself cannot be removed without re-creating the window itself. + /// + public void AddButtons(UIElement control, params ThumbnailToolBarButton[] buttons) + { + if (control == null) { throw new ArgumentNullException("control"); } + VerifyButtons(buttons); + + // Add the buttons to our window manager, which will also create a proxy window + TaskbarWindowManager.AddThumbnailButtons(control, buttons); + } + + private static void VerifyButtons(params ThumbnailToolBarButton[] buttons) + { + if (buttons != null && buttons.Length == 0) + { + throw new ArgumentException(LocalizedMessages.ThumbnailToolbarManagerNullEmptyArray, "buttons"); + } + if (buttons.Length > 7) + { + throw new ArgumentException(LocalizedMessages.ThumbnailToolbarManagerMaxButtons, "buttons"); + } + } + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailToolbarProxyWindow.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailToolbarProxyWindow.cs new file mode 100644 index 0000000..df1af2e --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/ThumbnailToolbarProxyWindow.cs @@ -0,0 +1,119 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using Kermalis.VGMusicStudio.WinForms.API.Shell.Resources; +using Kermalis.VGMusicStudio.WinForms.API.Internal; +using System; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + internal class ThumbnailToolbarProxyWindow : NativeWindow, IDisposable + { + private ThumbnailToolBarButton[] _thumbnailButtons; + private readonly IntPtr _internalWindowHandle; + + internal System.Windows.UIElement WindowsControl { get; set; } + + internal IntPtr WindowToTellTaskbarAbout => _internalWindowHandle != IntPtr.Zero ? _internalWindowHandle : Handle; + + internal TaskbarWindow TaskbarWindow { get; set; } + + internal ThumbnailToolbarProxyWindow(IntPtr windowHandle, ThumbnailToolBarButton[] buttons) + { + if (windowHandle == IntPtr.Zero) + { + throw new ArgumentException(LocalizedMessages.CommonFileDialogInvalidHandle, "windowHandle"); + } + if (buttons != null && buttons.Length == 0) + { + throw new ArgumentException(LocalizedMessages.ThumbnailToolbarManagerNullEmptyArray, "buttons"); + } + + _internalWindowHandle = windowHandle; + _thumbnailButtons = buttons; + + // Set the window handle on the buttons (for future updates) + Array.ForEach(_thumbnailButtons, new Action(UpdateHandle)); + + // Assign the window handle (coming from the user) to this native window + // so we can intercept the window messages sent from the taskbar to this window. + AssignHandle(windowHandle); + } + + internal ThumbnailToolbarProxyWindow(System.Windows.UIElement windowsControl, ThumbnailToolBarButton[] buttons) + { + if (windowsControl == null) { throw new ArgumentNullException("windowsControl"); } + if (buttons != null && buttons.Length == 0) + { + throw new ArgumentException(LocalizedMessages.ThumbnailToolbarManagerNullEmptyArray, "buttons"); + } + + _internalWindowHandle = IntPtr.Zero; + WindowsControl = windowsControl; + _thumbnailButtons = buttons; + + // Set the window handle on the buttons (for future updates) + Array.ForEach(_thumbnailButtons, new Action(UpdateHandle)); + } + + private void UpdateHandle(ThumbnailToolBarButton button) + { + button.WindowHandle = _internalWindowHandle; + button.AddedToTaskbar = false; + } + + protected override void WndProc(ref Message m) + { + var handled = false; + + handled = TaskbarWindowManager.DispatchMessage(ref m, TaskbarWindow); + + // If it's a WM_Destroy message, then also forward it to the base class (our native window) + if ((m.Msg == (int)WindowMessage.Destroy) || + (m.Msg == (int)WindowMessage.NCDestroy) || + ((m.Msg == (int)WindowMessage.SystemCommand) && (((int)m.WParam) == TabbedThumbnailNativeMethods.ScClose))) + { + base.WndProc(ref m); + } + else if (!handled) + { + base.WndProc(ref m); + } + } + + #region IDisposable Members + + /// + /// + /// + ~ThumbnailToolbarProxyWindow() + { + Dispose(false); + } + + /// + /// Release the native objects. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void Dispose(bool disposing) + { + if (disposing) + { + // Dispose managed resources + + // Don't dispose the thumbnail buttons + // as they might be used in another window. + // Setting them to null will indicate we don't need use anymore. + _thumbnailButtons = null; + } + } + + #endregion + + } +} diff --git a/VG Music Studio - WinForms/API/Shell/Taskbar/UserRemovedJumpListItemsEventArg.cs b/VG Music Studio - WinForms/API/Shell/Taskbar/UserRemovedJumpListItemsEventArg.cs new file mode 100644 index 0000000..8455023 --- /dev/null +++ b/VG Music Studio - WinForms/API/Shell/Taskbar/UserRemovedJumpListItemsEventArg.cs @@ -0,0 +1,23 @@ +//Copyright (c) Microsoft Corporation. All rights reserved. + +using System; +using System.Collections; + +namespace Kermalis.VGMusicStudio.WinForms.API.Taskbar +{ + /// + /// Event arguments for when the user is notified of items + /// that have been removed from the taskbar destination list + /// + public class UserRemovedJumpListItemsEventArgs : EventArgs + { + private readonly IEnumerable _removedItems; + + internal UserRemovedJumpListItemsEventArgs(IEnumerable RemovedItems) => _removedItems = RemovedItems; + + /// + /// The collection of removed items based on path. + /// + public IEnumerable RemovedItems => _removedItems; + } +} diff --git a/VG Music Studio - WinForms/MainForm.cs b/VG Music Studio - WinForms/MainForm.cs index 8820c08..bdb40f5 100644 --- a/VG Music Studio - WinForms/MainForm.cs +++ b/VG Music Studio - WinForms/MainForm.cs @@ -7,7 +7,7 @@ using Kermalis.VGMusicStudio.Core.Util; using Kermalis.VGMusicStudio.WinForms.Properties; using Kermalis.VGMusicStudio.WinForms.Util; -using Microsoft.WindowsAPICodePack.Taskbar; +using Kermalis.VGMusicStudio.WinForms.API.Taskbar; using System; using System.Collections.Generic; using System.ComponentModel; @@ -264,9 +264,14 @@ private void EndCurrentPlaylist(object? sender, EventArgs e) private void OpenDSE(object? sender, EventArgs e) { - var d = new FolderBrowserDialog + var f = WinFormsUtils.CreateLoadDialog(".swd", Strings.MenuOpenSWD, Strings.FilterOpenSWD + " (*.swd)|*.swd"); + if (f is null) + { + return; + } + var d = new FolderBrowserDialog { - Description = Strings.MenuOpenDSE, + Description = Strings.MenuOpenSMD, UseDescriptionForTitle = true, }; if (d.ShowDialog() != DialogResult.OK) @@ -277,7 +282,7 @@ private void OpenDSE(object? sender, EventArgs e) DisposeEngine(); try { - _ = new DSEEngine(d.SelectedPath); + _ = new DSEEngine(f.ToString(), d.SelectedPath); } catch (Exception ex) { @@ -286,7 +291,7 @@ private void OpenDSE(object? sender, EventArgs e) } DSEConfig config = DSEEngine.DSEInstance!.Config; - FinishLoading(config.BGMFiles.Length); + FinishLoading(config.SMDFiles.Length); _songNumerical.Visible = false; _exportDLSItem.Visible = false; _exportMIDIItem.Visible = false; diff --git a/VG Music Studio - WinForms/ObjectListView/CellEditing/CellEditKeyEngine.cs b/VG Music Studio - WinForms/ObjectListView/CellEditing/CellEditKeyEngine.cs new file mode 100644 index 0000000..6227b99 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/CellEditing/CellEditKeyEngine.cs @@ -0,0 +1,520 @@ +/* + * CellEditKeyEngine - A engine that allows the behaviour of arbitrary keys to be configured + * + * Author: Phillip Piper + * Date: 3-March-2011 10:53 pm + * + * Change log: + * v2.8 + * 2014-05-30 JPP - When a row is disabled, skip over it when looking for another cell to edit + * v2.5 + * 2012-04-14 JPP - Fixed bug where, on a OLV with only a single editable column, tabbing + * to change rows would edit the cell above rather than the cell below + * the cell being edited. + * 2.5 + * 2011-03-03 JPP - First version + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using Kermalis.VGMusicStudio.WinForms.ObjectListView; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + /// + /// Indicates the behavior of a key when a cell "on the edge" is being edited. + /// and the normal behavior of that key would exceed the edge. For example, + /// for a key that normally moves one column to the left, the "edge" would be + /// the left most column, since the normal action of the key cannot be taken + /// (since there are no more columns to the left). + /// + public enum CellEditAtEdgeBehaviour { + /// + /// The key press will be ignored + /// + Ignore, + + /// + /// The key press will result in the cell editing wrapping to the + /// cell on the opposite edge. + /// + Wrap, + + /// + /// The key press will wrap, but the column will be changed to the + /// appropriate adjacent column. This only makes sense for keys where + /// the normal action is ChangeRow. + /// + ChangeColumn, + + /// + /// The key press will wrap, but the row will be changed to the + /// appropriate adjacent row. This only makes sense for keys where + /// the normal action is ChangeColumn. + /// + ChangeRow, + + /// + /// The key will result in the current edit operation being ended. + /// + EndEdit + }; + + /// + /// Indicates the normal behaviour of a key when used during a cell edit + /// operation. + /// + public enum CellEditCharacterBehaviour { + /// + /// The key press will be ignored + /// + Ignore, + + /// + /// The key press will end the current edit and begin an edit + /// operation on the next editable cell to the left. + /// + ChangeColumnLeft, + + /// + /// The key press will end the current edit and begin an edit + /// operation on the next editable cell to the right. + /// + ChangeColumnRight, + + /// + /// The key press will end the current edit and begin an edit + /// operation on the row above. + /// + ChangeRowUp, + + /// + /// The key press will end the current edit and begin an edit + /// operation on the row below + /// + ChangeRowDown, + + /// + /// The key press will cancel the current edit + /// + CancelEdit, + + /// + /// The key press will finish the current edit operation + /// + EndEdit, + + /// + /// Custom verb that can be used for specialized actions. + /// + CustomVerb1, + + /// + /// Custom verb that can be used for specialized actions. + /// + CustomVerb2, + + /// + /// Custom verb that can be used for specialized actions. + /// + CustomVerb3, + + /// + /// Custom verb that can be used for specialized actions. + /// + CustomVerb4, + + /// + /// Custom verb that can be used for specialized actions. + /// + CustomVerb5, + + /// + /// Custom verb that can be used for specialized actions. + /// + CustomVerb6, + + /// + /// Custom verb that can be used for specialized actions. + /// + CustomVerb7, + + /// + /// Custom verb that can be used for specialized actions. + /// + CustomVerb8, + + /// + /// Custom verb that can be used for specialized actions. + /// + CustomVerb9, + + /// + /// Custom verb that can be used for specialized actions. + /// + CustomVerb10, + }; + + /// + /// Instances of this class handle key presses during a cell edit operation. + /// + public class CellEditKeyEngine { + + #region Public interface + + /// + /// Sets the behaviour of a given key + /// + /// + /// + /// + public virtual void SetKeyBehaviour(Keys key, CellEditCharacterBehaviour normalBehaviour, CellEditAtEdgeBehaviour atEdgeBehaviour) { + this.CellEditKeyMap[key] = normalBehaviour; + this.CellEditKeyAtEdgeBehaviourMap[key] = atEdgeBehaviour; + } + + /// + /// Handle a key press + /// + /// + /// + /// True if the key was completely handled. + public virtual bool HandleKey(ObjectListView olv, Keys keyData) { + if (olv == null) throw new ArgumentNullException("olv"); + + CellEditCharacterBehaviour behaviour; + if (!CellEditKeyMap.TryGetValue(keyData, out behaviour)) + return false; + + this.ListView = olv; + + switch (behaviour) { + case CellEditCharacterBehaviour.Ignore: + break; + case CellEditCharacterBehaviour.CancelEdit: + this.HandleCancelEdit(); + break; + case CellEditCharacterBehaviour.EndEdit: + this.HandleEndEdit(); + break; + case CellEditCharacterBehaviour.ChangeColumnLeft: + case CellEditCharacterBehaviour.ChangeColumnRight: + this.HandleColumnChange(keyData, behaviour); + break; + case CellEditCharacterBehaviour.ChangeRowDown: + case CellEditCharacterBehaviour.ChangeRowUp: + this.HandleRowChange(keyData, behaviour); + break; + default: + return this.HandleCustomVerb(keyData, behaviour); + }; + + return true; + } + + #endregion + + #region Implementation properties + + /// + /// Gets or sets the ObjectListView on which the current key is being handled. + /// This cannot be null. + /// + protected ObjectListView ListView { + get { return listView; } + set { listView = value; } + } + private ObjectListView listView; + + /// + /// Gets the row of the cell that is currently being edited + /// + protected OLVListItem ItemBeingEdited { + get { + return (this.ListView == null || this.ListView.CellEditEventArgs == null) ? null : this.ListView.CellEditEventArgs.ListViewItem; + } + } + + /// + /// Gets the index of the column of the cell that is being edited + /// + protected int SubItemIndexBeingEdited { + get { + return (this.ListView == null || this.ListView.CellEditEventArgs == null) ? -1 : this.ListView.CellEditEventArgs.SubItemIndex; + } + } + + /// + /// Gets or sets the map that remembers the normal behaviour of keys + /// + protected IDictionary CellEditKeyMap { + get { + if (cellEditKeyMap == null) + this.InitializeCellEditKeyMaps(); + return cellEditKeyMap; + } + set { + cellEditKeyMap = value; + } + } + private IDictionary cellEditKeyMap; + + /// + /// Gets or sets the map that remembers the desired behaviour of keys + /// on edge cases. + /// + protected IDictionary CellEditKeyAtEdgeBehaviourMap { + get { + if (cellEditKeyAtEdgeBehaviourMap == null) + this.InitializeCellEditKeyMaps(); + return cellEditKeyAtEdgeBehaviourMap; + } + set { + cellEditKeyAtEdgeBehaviourMap = value; + } + } + private IDictionary cellEditKeyAtEdgeBehaviourMap; + + #endregion + + #region Initialization + + /// + /// Setup the default key mapping + /// + protected virtual void InitializeCellEditKeyMaps() { + this.cellEditKeyMap = new Dictionary(); + this.cellEditKeyMap[Keys.Escape] = CellEditCharacterBehaviour.CancelEdit; + this.cellEditKeyMap[Keys.Return] = CellEditCharacterBehaviour.EndEdit; + this.cellEditKeyMap[Keys.Enter] = CellEditCharacterBehaviour.EndEdit; + this.cellEditKeyMap[Keys.Tab] = CellEditCharacterBehaviour.ChangeColumnRight; + this.cellEditKeyMap[Keys.Tab | Keys.Shift] = CellEditCharacterBehaviour.ChangeColumnLeft; + this.cellEditKeyMap[Keys.Left | Keys.Alt] = CellEditCharacterBehaviour.ChangeColumnLeft; + this.cellEditKeyMap[Keys.Right | Keys.Alt] = CellEditCharacterBehaviour.ChangeColumnRight; + this.cellEditKeyMap[Keys.Up | Keys.Alt] = CellEditCharacterBehaviour.ChangeRowUp; + this.cellEditKeyMap[Keys.Down | Keys.Alt] = CellEditCharacterBehaviour.ChangeRowDown; + + this.cellEditKeyAtEdgeBehaviourMap = new Dictionary(); + this.cellEditKeyAtEdgeBehaviourMap[Keys.Tab] = CellEditAtEdgeBehaviour.Wrap; + this.cellEditKeyAtEdgeBehaviourMap[Keys.Tab | Keys.Shift] = CellEditAtEdgeBehaviour.Wrap; + this.cellEditKeyAtEdgeBehaviourMap[Keys.Left | Keys.Alt] = CellEditAtEdgeBehaviour.Wrap; + this.cellEditKeyAtEdgeBehaviourMap[Keys.Right | Keys.Alt] = CellEditAtEdgeBehaviour.Wrap; + this.cellEditKeyAtEdgeBehaviourMap[Keys.Up | Keys.Alt] = CellEditAtEdgeBehaviour.ChangeColumn; + this.cellEditKeyAtEdgeBehaviourMap[Keys.Down | Keys.Alt] = CellEditAtEdgeBehaviour.ChangeColumn; + } + + #endregion + + #region Command handling + + /// + /// Handle the end edit command + /// + protected virtual void HandleEndEdit() { + this.ListView.PossibleFinishCellEditing(); + } + + /// + /// Handle the cancel edit command + /// + protected virtual void HandleCancelEdit() { + this.ListView.CancelCellEdit(); + } + + /// + /// Placeholder that subclasses can override to handle any custom verbs + /// + /// + /// + /// + protected virtual bool HandleCustomVerb(Keys keyData, CellEditCharacterBehaviour behaviour) { + return false; + } + + /// + /// Handle a change row command + /// + /// + /// + protected virtual void HandleRowChange(Keys keyData, CellEditCharacterBehaviour behaviour) { + // If we couldn't finish editing the current cell, don't try to move it + if (!this.ListView.PossibleFinishCellEditing()) + return; + + OLVListItem olvi = this.ItemBeingEdited; + int subItemIndex = this.SubItemIndexBeingEdited; + bool isGoingUp = behaviour == CellEditCharacterBehaviour.ChangeRowUp; + + // Try to find a row above (or below) the currently edited cell + // If we find one, start editing it and we're done. + OLVListItem adjacentOlvi = this.GetAdjacentItemOrNull(olvi, isGoingUp); + if (adjacentOlvi != null) { + this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex); + return; + } + + // There is no adjacent row in the direction we want, so we must be on an edge. + CellEditAtEdgeBehaviour atEdgeBehaviour; + if (!this.CellEditKeyAtEdgeBehaviourMap.TryGetValue(keyData, out atEdgeBehaviour)) + atEdgeBehaviour = CellEditAtEdgeBehaviour.Wrap; + switch (atEdgeBehaviour) { + case CellEditAtEdgeBehaviour.Ignore: + break; + case CellEditAtEdgeBehaviour.EndEdit: + this.ListView.PossibleFinishCellEditing(); + break; + case CellEditAtEdgeBehaviour.Wrap: + adjacentOlvi = this.GetAdjacentItemOrNull(null, isGoingUp); + this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex); + break; + case CellEditAtEdgeBehaviour.ChangeColumn: + // Figure out the next editable column + List editableColumnsInDisplayOrder = this.EditableColumnsInDisplayOrder; + int displayIndex = Math.Max(0, editableColumnsInDisplayOrder.IndexOf(this.ListView.GetColumn(subItemIndex))); + if (isGoingUp) + displayIndex = (editableColumnsInDisplayOrder.Count + displayIndex - 1) % editableColumnsInDisplayOrder.Count; + else + displayIndex = (displayIndex + 1) % editableColumnsInDisplayOrder.Count; + subItemIndex = editableColumnsInDisplayOrder[displayIndex].Index; + + // Wrap to the next row and start the cell edit + adjacentOlvi = this.GetAdjacentItemOrNull(null, isGoingUp); + this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex); + break; + } + } + + /// + /// Handle a change column command + /// + /// + /// + protected virtual void HandleColumnChange(Keys keyData, CellEditCharacterBehaviour behaviour) + { + // If we couldn't finish editing the current cell, don't try to move it + if (!this.ListView.PossibleFinishCellEditing()) + return; + + // Changing columns only works in details mode + if (this.ListView.View != View.Details) + return; + + List editableColumns = this.EditableColumnsInDisplayOrder; + OLVListItem olvi = this.ItemBeingEdited; + int displayIndex = Math.Max(0, + editableColumns.IndexOf(this.ListView.GetColumn(this.SubItemIndexBeingEdited))); + bool isGoingLeft = behaviour == CellEditCharacterBehaviour.ChangeColumnLeft; + + // Are we trying to continue past one of the edges? + if ((isGoingLeft && displayIndex == 0) || + (!isGoingLeft && displayIndex == editableColumns.Count - 1)) + { + // Yes, so figure out our at edge behaviour + CellEditAtEdgeBehaviour atEdgeBehaviour; + if (!this.CellEditKeyAtEdgeBehaviourMap.TryGetValue(keyData, out atEdgeBehaviour)) + atEdgeBehaviour = CellEditAtEdgeBehaviour.Wrap; + switch (atEdgeBehaviour) + { + case CellEditAtEdgeBehaviour.Ignore: + return; + case CellEditAtEdgeBehaviour.EndEdit: + this.HandleEndEdit(); + return; + case CellEditAtEdgeBehaviour.ChangeRow: + case CellEditAtEdgeBehaviour.Wrap: + if (atEdgeBehaviour == CellEditAtEdgeBehaviour.ChangeRow) + olvi = GetAdjacentItem(olvi, isGoingLeft && displayIndex == 0); + if (isGoingLeft) + displayIndex = editableColumns.Count - 1; + else + displayIndex = 0; + break; + } + } + else + { + if (isGoingLeft) + displayIndex -= 1; + else + displayIndex += 1; + } + + int subItemIndex = editableColumns[displayIndex].Index; + this.StartCellEditIfDifferent(olvi, subItemIndex); + } + + #endregion + + #region Utilities + + /// + /// Start editing the indicated cell if that cell is not already being edited + /// + /// The row to edit + /// The cell within that row to edit + protected void StartCellEditIfDifferent(OLVListItem olvi, int subItemIndex) { + if (this.ItemBeingEdited == olvi && this.SubItemIndexBeingEdited == subItemIndex) + return; + + this.ListView.EnsureVisible(olvi.Index); + this.ListView.StartCellEdit(olvi, subItemIndex); + } + + /// + /// Gets the adjacent item to the given item in the given direction. + /// If that item is disabled, continue in that direction until an enabled item is found. + /// + /// The row whose neighbour is sought + /// The direction of the adjacentness + /// An OLVListView adjacent to the given item, or null if there are no more enabled items in that direction. + protected OLVListItem GetAdjacentItemOrNull(OLVListItem olvi, bool up) { + OLVListItem item = up ? this.ListView.GetPreviousItem(olvi) : this.ListView.GetNextItem(olvi); + while (item != null && !item.Enabled) + item = up ? this.ListView.GetPreviousItem(item) : this.ListView.GetNextItem(item); + return item; + } + + /// + /// Gets the adjacent item to the given item in the given direction, wrapping if needed. + /// + /// The row whose neighbour is sought + /// The direction of the adjacentness + /// An OLVListView adjacent to the given item, or null if there are no more items in that direction. + protected OLVListItem GetAdjacentItem(OLVListItem olvi, bool up) { + return this.GetAdjacentItemOrNull(olvi, up) ?? this.GetAdjacentItemOrNull(null, up); + } + + /// + /// Gets a collection of columns that are editable in the order they are shown to the user + /// + protected List EditableColumnsInDisplayOrder { + get { + List editableColumnsInDisplayOrder = new List(); + foreach (OLVColumn x in this.ListView.ColumnsInDisplayOrder) + if (x.IsEditable) + editableColumnsInDisplayOrder.Add(x); + return editableColumnsInDisplayOrder; + } + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/CellEditing/CellEditors.cs b/VG Music Studio - WinForms/ObjectListView/CellEditing/CellEditors.cs new file mode 100644 index 0000000..2e043d6 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/CellEditing/CellEditors.cs @@ -0,0 +1,325 @@ +/* + * CellEditors - Several slightly modified controls that are used as cell editors within ObjectListView. + * + * Author: Phillip Piper + * Date: 20/10/2008 5:15 PM + * + * Change log: + * 2018-05-05 JPP - Added ControlUtilities.AutoResizeDropDown() + * v2.6 + * 2012-08-02 JPP - Make most editors public so they can be reused/subclassed + * v2.3 + * 2009-08-13 JPP - Standardized code formatting + * v2.2.1 + * 2008-01-18 JPP - Added special handling for enums + * 2008-01-16 JPP - Added EditorRegistry + * v2.0.1 + * 2008-10-20 JPP - Separated from ObjectListView.cs + * + * Copyright (C) 2006-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// An interface that allows cell editors to specifically handle getting and setting + /// values from ObjectListView + /// + public interface IOlvEditor { + object Value { get; set; } + } + + public static class ControlUtilities { + + /// + /// Configure the given ComboBox so that the dropped down menu is auto-sized to + /// be wide enough to show the widest item. + /// + /// + public static void AutoResizeDropDown(ComboBox dropDown) { + if (dropDown == null) + throw new ArgumentNullException("dropDown"); + + dropDown.DropDown += delegate(object sender, EventArgs args) { + + // Calculate the maximum width of the drop down items + int newWidth = 0; + foreach (object item in dropDown.Items) { + newWidth = Math.Max(newWidth, TextRenderer.MeasureText(item.ToString(), dropDown.Font).Width); + } + + int vertScrollBarWidth = dropDown.Items.Count > dropDown.MaxDropDownItems ? SystemInformation.VerticalScrollBarWidth : 0; + dropDown.DropDownWidth = newWidth + vertScrollBarWidth; + }; + } + } + + /// + /// These items allow combo boxes to remember a value and its description. + /// + public class ComboBoxItem + { + /// + /// + /// + /// + /// + public ComboBoxItem(Object key, String description) { + this.key = key; + this.description = description; + } + private readonly String description; + + /// + /// + /// + public Object Key { + get { return key; } + } + private readonly Object key; + + /// + /// Returns a string that represents the current object. + /// + /// + /// A string that represents the current object. + /// + /// 2 + public override string ToString() { + return this.description; + } + } + + //----------------------------------------------------------------------- + // Cell editors + // These classes are simple cell editors that make it easier to get and set + // the value that the control is showing. + // In many cases, you can intercept the CellEditStarting event to + // change the characteristics of the editor. For example, changing + // the acceptable range for a numeric editor or changing the strings + // that represent true and false values for a boolean editor. + + /// + /// This editor shows and auto completes values from the given listview column. + /// + [ToolboxItem(false)] + public class AutoCompleteCellEditor : ComboBox + { + /// + /// Create an AutoCompleteCellEditor + /// + /// + /// + public AutoCompleteCellEditor(ObjectListView lv, OLVColumn column) { + this.DropDownStyle = ComboBoxStyle.DropDown; + + Dictionary alreadySeen = new Dictionary(); + for (int i = 0; i < Math.Min(lv.GetItemCount(), 1000); i++) { + String str = column.GetStringValue(lv.GetModelObject(i)); + if (!alreadySeen.ContainsKey(str)) { + this.Items.Add(str); + alreadySeen[str] = true; + } + } + + this.Sorted = true; + this.AutoCompleteSource = AutoCompleteSource.ListItems; + this.AutoCompleteMode = AutoCompleteMode.Append; + + ControlUtilities.AutoResizeDropDown(this); + } + } + + /// + /// This combo box is specialized to allow editing of an enum. + /// + [ToolboxItem(false)] + public class EnumCellEditor : ComboBox + { + /// + /// + /// + /// + public EnumCellEditor(Type type) { + this.DropDownStyle = ComboBoxStyle.DropDownList; + this.ValueMember = "Key"; + + ArrayList values = new ArrayList(); + foreach (object value in Enum.GetValues(type)) + values.Add(new ComboBoxItem(value, Enum.GetName(type, value))); + + this.DataSource = values; + + ControlUtilities.AutoResizeDropDown(this); + } + } + + /// + /// This editor simply shows and edits integer values. + /// + [ToolboxItem(false)] + public class IntUpDown : NumericUpDown + { + /// + /// + /// + public IntUpDown() { + this.DecimalPlaces = 0; + this.Minimum = -9999999; + this.Maximum = 9999999; + } + + /// + /// Gets or sets the value shown by this editor + /// + public new int Value { + get { return Decimal.ToInt32(base.Value); } + set { base.Value = new Decimal(value); } + } + } + + /// + /// This editor simply shows and edits unsigned integer values. + /// + /// This class can't be made public because unsigned int is not a + /// CLS-compliant type. If you want to use, just copy the code to this class + /// into your project and use it from there. + [ToolboxItem(false)] + internal class UintUpDown : NumericUpDown + { + public UintUpDown() { + this.DecimalPlaces = 0; + this.Minimum = 0; + this.Maximum = 9999999; + } + + public new uint Value { + get { return Decimal.ToUInt32(base.Value); } + set { base.Value = new Decimal(value); } + } + } + + /// + /// This editor simply shows and edits boolean values. + /// + [ToolboxItem(false)] + public class BooleanCellEditor : ComboBox + { + /// + /// + /// + public BooleanCellEditor() { + this.DropDownStyle = ComboBoxStyle.DropDownList; + this.ValueMember = "Key"; + + ArrayList values = new ArrayList(); + values.Add(new ComboBoxItem(false, "False")); + values.Add(new ComboBoxItem(true, "True")); + + this.DataSource = values; + } + } + + /// + /// This editor simply shows and edits boolean values using a checkbox + /// + [ToolboxItem(false)] + public class BooleanCellEditor2 : CheckBox + { + /// + /// Gets or sets the value shown by this editor + /// + public bool? Value { + get { + switch (this.CheckState) { + case CheckState.Checked: return true; + case CheckState.Indeterminate: return null; + case CheckState.Unchecked: + default: return false; + } + } + set { + if (value.HasValue) + this.CheckState = value.Value ? CheckState.Checked : CheckState.Unchecked; + else + this.CheckState = CheckState.Indeterminate; + } + } + + /// + /// Gets or sets how the checkbox will be aligned + /// + public new HorizontalAlignment TextAlign { + get { + switch (this.CheckAlign) { + case ContentAlignment.MiddleRight: return HorizontalAlignment.Right; + case ContentAlignment.MiddleCenter: return HorizontalAlignment.Center; + case ContentAlignment.MiddleLeft: + default: return HorizontalAlignment.Left; + } + } + set { + switch (value) { + case HorizontalAlignment.Left: + this.CheckAlign = ContentAlignment.MiddleLeft; + break; + case HorizontalAlignment.Center: + this.CheckAlign = ContentAlignment.MiddleCenter; + break; + case HorizontalAlignment.Right: + this.CheckAlign = ContentAlignment.MiddleRight; + break; + } + } + } + } + + /// + /// This editor simply shows and edits floating point values. + /// + /// You can intercept the CellEditStarting event if you want + /// to change the characteristics of the editor. For example, by increasing + /// the number of decimal places. + [ToolboxItem(false)] + public class FloatCellEditor : NumericUpDown + { + /// + /// + /// + public FloatCellEditor() { + this.DecimalPlaces = 2; + this.Minimum = -9999999; + this.Maximum = 9999999; + } + + /// + /// Gets or sets the value shown by this editor + /// + public new double Value { + get { return Convert.ToDouble(base.Value); } + set { base.Value = Convert.ToDecimal(value); } + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/CellEditing/EditorRegistry.cs b/VG Music Studio - WinForms/ObjectListView/CellEditing/EditorRegistry.cs new file mode 100644 index 0000000..d892175 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/CellEditing/EditorRegistry.cs @@ -0,0 +1,213 @@ +/* + * EditorRegistry - A registry mapping types to cell editors. + * + * Author: Phillip Piper + * Date: 6-March-2011 7:53 am + * + * Change log: + * 2011-03-31 JPP - Use OLVColumn.DataType if the value to be edited is null + * 2011-03-06 JPP - Separated from CellEditors.cs + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using System.Reflection; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// A delegate that creates an editor for the given value + /// + /// The model from which that value came + /// The column for which the editor is being created + /// A representative value of the type to be edited. This value may not be the exact + /// value for the column/model combination. It could be simply representative of + /// the appropriate type of value. + /// A control which can edit the given value + public delegate Control EditorCreatorDelegate(Object model, OLVColumn column, Object value); + + /// + /// An editor registry gives a way to decide what cell editor should be used to edit + /// the value of a cell. Programmers can register non-standard types and the control that + /// should be used to edit instances of that type. + /// + /// + /// All ObjectListViews share the same editor registry. + /// + public class EditorRegistry { + #region Initializing + + /// + /// Create an EditorRegistry + /// + public EditorRegistry() { + this.InitializeStandardTypes(); + } + + private void InitializeStandardTypes() { + this.Register(typeof(Boolean), typeof(BooleanCellEditor)); + this.Register(typeof(Int16), typeof(IntUpDown)); + this.Register(typeof(Int32), typeof(IntUpDown)); + this.Register(typeof(Int64), typeof(IntUpDown)); + this.Register(typeof(UInt16), typeof(UintUpDown)); + this.Register(typeof(UInt32), typeof(UintUpDown)); + this.Register(typeof(UInt64), typeof(UintUpDown)); + this.Register(typeof(Single), typeof(FloatCellEditor)); + this.Register(typeof(Double), typeof(FloatCellEditor)); + this.Register(typeof(DateTime), delegate(Object model, OLVColumn column, Object value) { + DateTimePicker c = new DateTimePicker(); + c.Format = DateTimePickerFormat.Short; + return c; + }); + this.Register(typeof(Boolean), delegate(Object model, OLVColumn column, Object value) { + CheckBox c = new BooleanCellEditor2(); + c.ThreeState = column.TriStateCheckBoxes; + return c; + }); + } + + #endregion + + #region Registering + + /// + /// Register that values of 'type' should be edited by instances of 'controlType'. + /// + /// The type of value to be edited + /// The type of the Control that will edit values of 'type' + /// + /// ObjectListView.EditorRegistry.Register(typeof(Color), typeof(MySpecialColorEditor)); + /// + public void Register(Type type, Type controlType) { + this.Register(type, delegate(Object model, OLVColumn column, Object value) { + return controlType.InvokeMember("", BindingFlags.CreateInstance, null, null, null) as Control; + }); + } + + /// + /// Register the given delegate so that it is called to create editors + /// for values of the given type + /// + /// The type of value to be edited + /// The delegate that will create a control that can edit values of 'type' + /// + /// ObjectListView.EditorRegistry.Register(typeof(Color), CreateColorEditor); + /// ... + /// public Control CreateColorEditor(Object model, OLVColumn column, Object value) + /// { + /// return new MySpecialColorEditor(); + /// } + /// + public void Register(Type type, EditorCreatorDelegate creator) { + this.creatorMap[type] = creator; + } + + /// + /// Register a delegate that will be called to create an editor for values + /// that have not been handled. + /// + /// The delegate that will create a editor for all other types + public void RegisterDefault(EditorCreatorDelegate creator) { + this.defaultCreator = creator; + } + + /// + /// Register a delegate that will be given a chance to create a control + /// before any other option is considered. + /// + /// The delegate that will create a control + public void RegisterFirstChance(EditorCreatorDelegate creator) { + this.firstChanceCreator = creator; + } + + /// + /// Remove the registered handler for the given type + /// + /// Does nothing if the given type doesn't exist + /// The type whose registration is to be removed + public void Unregister(Type type) { + if (this.creatorMap.ContainsKey(type)) + this.creatorMap.Remove(type); + } + + #endregion + + #region Accessing + + /// + /// Create and return an editor that is appropriate for the given value. + /// Return null if no appropriate editor can be found. + /// + /// The model involved + /// The column to be edited + /// The value to be edited. This value may not be the exact + /// value for the column/model combination. It could be simply representative of + /// the appropriate type of value. + /// A Control that can edit the given type of values + public Control GetEditor(Object model, OLVColumn column, Object value) { + Control editor; + + // Give the first chance delegate a chance to decide + if (this.firstChanceCreator != null) { + editor = this.firstChanceCreator(model, column, value); + if (editor != null) + return editor; + } + + // Try to find a creator based on the type of the value (or the column) + Type type = value == null ? column.DataType : value.GetType(); + if (type != null && this.creatorMap.ContainsKey(type)) { + editor = this.creatorMap[type](model, column, value); + if (editor != null) + return editor; + } + + // Enums without other processing get a special editor + if (value != null && value.GetType().IsEnum) + return this.CreateEnumEditor(value.GetType()); + + // Give any default creator a final chance + if (this.defaultCreator != null) + return this.defaultCreator(model, column, value); + + return null; + } + + /// + /// Create and return an editor that will edit values of the given type + /// + /// A enum type + protected Control CreateEnumEditor(Type type) { + return new EnumCellEditor(type); + } + + #endregion + + #region Private variables + + private EditorCreatorDelegate firstChanceCreator; + private EditorCreatorDelegate defaultCreator; + private Dictionary creatorMap = new Dictionary(); + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/CustomDictionary.xml b/VG Music Studio - WinForms/ObjectListView/CustomDictionary.xml new file mode 100644 index 0000000..f2cf5b9 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/CustomDictionary.xml @@ -0,0 +1,46 @@ + + + + + br + Canceled + Center + Color + Colors + f + fmt + g + gdi + hti + i + lightbox + lv + lvi + lvsi + m + multi + Munger + n + olv + olvi + p + parms + r + Renderer + s + SubItem + Unapply + Unpause + x + y + + + ComPlus + + + + + OLV + + + diff --git a/VG Music Studio - WinForms/ObjectListView/DataListView.cs b/VG Music Studio - WinForms/ObjectListView/DataListView.cs new file mode 100644 index 0000000..9a9a662 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/DataListView.cs @@ -0,0 +1,240 @@ +/* + * DataListView - A data-bindable listview + * + * Author: Phillip Piper + * Date: 27/09/2008 9:15 AM + * + * Change log: + * 2015-02-02 JPP - Made Unfreezing more efficient by removing a redundant BuildList() call + * v2.6 + * 2011-02-27 JPP - Moved most of the logic to DataSourceAdapter (where it + * can be used by FastDataListView too) + * v2.3 + * 2009-01-18 JPP - Boolean columns are now handled as checkboxes + * - Auto-generated columns would fail if the data source was + * reseated, even to the same data source + * v2.0.1 + * 2009-01-07 JPP - Made all public and protected methods virtual + * 2008-10-03 JPP - Separated from ObjectListView.cs + * + * Copyright (C) 2006-2015 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing.Design; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + + /// + /// A DataListView is a ListView that can be bound to a datasource (which would normally be a DataTable or DataView). + /// + /// + /// This listview keeps itself in sync with its source datatable by listening for change events. + /// The DataListView will automatically create columns to show all of the data source's columns/properties, if there is not already + /// a column showing that property. This allows you to define one or two columns in the designer and then have the others generated automatically. + /// If you don't want any column to be auto generated, set to false. + /// These generated columns will be only the simplest view of the world, and would look more interesting with a few delegates installed. + /// This listview will also automatically generate missing aspect getters to fetch the values from the data view. + /// Changing data sources is possible, but error prone. Before changing data sources, the programmer is responsible for modifying/resetting + /// the column collection to be valid for the new data source. + /// Internally, a CurrencyManager controls keeping the data source in-sync with other users of the data source (as per normal .NET + /// behavior). This means that the model objects in the DataListView are DataRowView objects. If you write your own AspectGetters/Setters, + /// they will be given DataRowView objects. + /// + public class DataListView : ObjectListView + { + #region Life and death + + /// + /// Make a DataListView + /// + public DataListView() + { + this.Adapter = new DataSourceAdapter(this); + } + + /// + /// + /// + /// + protected override void Dispose(bool disposing) { + this.Adapter.Dispose(); + base.Dispose(disposing); + } + + #endregion + + #region Public Properties + + /// + /// Gets or sets whether or not columns will be automatically generated to show the + /// columns when the DataSource is set. + /// + /// This must be set before the DataSource is set. It has no effect afterwards. + [Category("Data"), + Description("Should the control automatically generate columns from the DataSource"), + DefaultValue(true)] + public bool AutoGenerateColumns { + get { return this.Adapter.AutoGenerateColumns; } + set { this.Adapter.AutoGenerateColumns = value; } + } + + /// + /// Get or set the DataSource that will be displayed in this list view. + /// + /// The DataSource should implement either , , + /// or . Some common examples are the following types of objects: + /// + /// + /// + /// + /// + /// + /// + /// When binding to a list container (i.e. one that implements the + /// interface, such as ) + /// you must also set the property in order + /// to identify which particular list you would like to display. You + /// may also set the property even when + /// DataSource refers to a list, since can + /// also be used to navigate relations between lists. + /// When a DataSource is set, the control will create OLVColumns to show any + /// data source columns that are not already shown. + /// If the DataSource is changed, you will have to remove any previously + /// created columns, since they will be configured for the previous DataSource. + /// . + /// + [Category("Data"), + TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")] + public virtual Object DataSource + { + get { return this.Adapter.DataSource; } + set { this.Adapter.DataSource = value; } + } + + /// + /// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data. + /// + /// If the data source is not a DataSet or DataViewManager, this property has no effect + [Category("Data"), + Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)), + DefaultValue("")] + public virtual string DataMember + { + get { return this.Adapter.DataMember; } + set { this.Adapter.DataMember = value; } + } + + #endregion + + #region Implementation properties + + /// + /// Gets or sets the DataSourceAdaptor that does the bulk of the work needed + /// for data binding. + /// + /// + /// Adaptors cannot be shared between controls. Each DataListView needs its own adapter. + /// + protected DataSourceAdapter Adapter { + get { + Debug.Assert(adapter != null, "Data adapter should not be null"); + return adapter; + } + set { adapter = value; } + } + private DataSourceAdapter adapter; + + #endregion + + #region Object manipulations + + /// + /// Add the given collection of model objects to this control. + /// + /// A collection of model objects + /// This is a no-op for data lists, since the data + /// is controlled by the DataSource. Manipulate the data source + /// rather than this view of the data source. + public override void AddObjects(ICollection modelObjects) + { + } + + /// + /// Insert the given collection of objects before the given position + /// + /// Where to insert the objects + /// The objects to be inserted + /// This is a no-op for data lists, since the data + /// is controlled by the DataSource. Manipulate the data source + /// rather than this view of the data source. + public override void InsertObjects(int index, ICollection modelObjects) { + } + + /// + /// Remove the given collection of model objects from this control. + /// + /// This is a no-op for data lists, since the data + /// is controlled by the DataSource. Manipulate the data source + /// rather than this view of the data source. + public override void RemoveObjects(ICollection modelObjects) + { + } + + #endregion + + #region Event Handlers + + /// + /// Change the Unfreeze behaviour + /// + protected override void DoUnfreeze() { + + // Copied from base method, but we don't need to BuildList() since we know that our + // data adaptor is going to do that immediately after this method exits. + this.EndUpdate(); + this.ResizeFreeSpaceFillingColumns(); + // this.BuildList(); + } + + /// + /// Handles parent binding context changes + /// + /// Unused EventArgs. + protected override void OnParentBindingContextChanged(EventArgs e) + { + base.OnParentBindingContextChanged(e); + + // BindingContext is an ambient property - by default it simply picks + // up the parent control's context (unless something has explicitly + // given us our own). So we must respond to changes in our parent's + // binding context in the same way we would changes to our own + // binding context. + + // THINK: Do we need to forward this to the adapter? + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/DataTreeListView.cs b/VG Music Studio - WinForms/ObjectListView/DataTreeListView.cs new file mode 100644 index 0000000..5493696 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/DataTreeListView.cs @@ -0,0 +1,240 @@ +/* + * DataTreeListView - A data bindable TreeListView + * + * Author: Phillip Piper + * Date: 05/05/2012 3:26 PM + * + * Change log: + + * 2012-05-05 JPP Initial version + * + * TO DO: + + * + * Copyright (C) 2012 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing.Design; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// A DataTreeListView is a TreeListView that calculates its hierarchy based on + /// information in the data source. + /// + /// + /// Like a , a DataTreeListView sources all its information + /// from a combination of and . + /// can be a DataTable, DataSet, + /// or anything that implements . + /// + /// + /// To function properly, the DataTreeListView requires: + /// + /// the table to have a column which holds a unique for the row. The name of this column must be set in . + /// the table to have a column which holds id of the hierarchical parent of the row. The name of this column must be set in . + /// a value which identifies which rows are the roots of the tree (). + /// + /// The hierarchy structure is determined finding all the rows where the parent key is equal to . These rows + /// become the root objects of the hierarchy. + /// + /// Like a TreeListView, the hierarchy must not contain cycles. Bad things will happen if the data is cyclic. + /// + public partial class DataTreeListView : TreeListView + { + #region Public Properties + + /// + /// Gets or sets whether or not columns will be automatically generated to show the + /// columns when the DataSource is set. + /// + /// This must be set before the DataSource is set. It has no effect afterwards. + [Category("Data"), + Description("Should the control automatically generate columns from the DataSource"), + DefaultValue(true)] + public bool AutoGenerateColumns + { + get { return this.Adapter.AutoGenerateColumns; } + set { this.Adapter.AutoGenerateColumns = value; } + } + + /// + /// Get or set the DataSource that will be displayed in this list view. + /// + /// The DataSource should implement either , , + /// or . Some common examples are the following types of objects: + /// + /// + /// + /// + /// + /// + /// + /// When binding to a list container (i.e. one that implements the + /// interface, such as ) + /// you must also set the property in order + /// to identify which particular list you would like to display. You + /// may also set the property even when + /// DataSource refers to a list, since can + /// also be used to navigate relations between lists. + /// + [Category("Data"), + TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")] + public virtual Object DataSource { + get { return this.Adapter.DataSource; } + set { this.Adapter.DataSource = value; } + } + + /// + /// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data. + /// + /// If the data source is not a DataSet or DataViewManager, this property has no effect + [Category("Data"), + Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)), + DefaultValue("")] + public virtual string DataMember { + get { return this.Adapter.DataMember; } + set { this.Adapter.DataMember = value; } + } + + /// + /// Gets or sets the name of the property/column that uniquely identifies each row. + /// + /// + /// + /// The value contained by this column must be unique across all rows + /// in the data source. Odd and unpredictable things will happen if two + /// rows have the same id. + /// + /// Null cannot be a valid key value. + /// + [Category("Data"), + Description("The name of the property/column that holds the key of a row"), + DefaultValue(null)] + public virtual string KeyAspectName { + get { return this.Adapter.KeyAspectName; } + set { this.Adapter.KeyAspectName = value; } + } + + /// + /// Gets or sets the name of the property/column that contains the key of + /// the parent of a row. + /// + /// + /// + /// The test condition for deciding if one row is the parent of another is functionally + /// equivalent to this: + /// + /// Object.Equals(candidateParentRow[this.KeyAspectName], row[this.ParentKeyAspectName]) + /// + /// + /// Unlike key value, parent keys can be null but a null parent key can only be used + /// to identify root objects. + /// + [Category("Data"), + Description("The name of the property/column that holds the key of the parent of a row"), + DefaultValue(null)] + public virtual string ParentKeyAspectName { + get { return this.Adapter.ParentKeyAspectName; } + set { this.Adapter.ParentKeyAspectName = value; } + } + + /// + /// Gets or sets the value that identifies a row as a root object. + /// When the ParentKey of a row equals the RootKeyValue, that row will + /// be treated as root of the TreeListView. + /// + /// + /// + /// The test condition for deciding a root object is functionally + /// equivalent to this: + /// + /// Object.Equals(candidateRow[this.ParentKeyAspectName], this.RootKeyValue) + /// + /// + /// The RootKeyValue can be null. Actually, it can be any value that can + /// be compared for equality against a basic type. + /// If this is set to the wrong value (i.e. to a value that no row + /// has in the parent id column), the list will be empty. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual object RootKeyValue { + get { return this.Adapter.RootKeyValue; } + set { this.Adapter.RootKeyValue = value; } + } + + /// + /// Gets or sets the value that identifies a row as a root object. + /// . The RootKeyValue can be of any type, + /// but the IDE cannot sensibly represent a value of any type, + /// so this is a typed wrapper around that property. + /// + /// + /// If you want the root value to be something other than a string, + /// you will have set it yourself. + /// + [Category("Data"), + Description("The parent id value that identifies a row as a root object"), + DefaultValue(null)] + public virtual string RootKeyValueString { + get { return Convert.ToString(this.Adapter.RootKeyValue); } + set { this.Adapter.RootKeyValue = value; } + } + + /// + /// Gets or sets whether or not the key columns (id and parent id) should + /// be shown to the user. + /// + /// This must be set before the DataSource is set. It has no effect + /// afterwards. + [Category("Data"), + Description("Should the keys columns (id and parent id) be shown to the user?"), + DefaultValue(true)] + public virtual bool ShowKeyColumns { + get { return this.Adapter.ShowKeyColumns; } + set { this.Adapter.ShowKeyColumns = value; } + } + + #endregion + + #region Implementation properties + + /// + /// Gets or sets the DataSourceAdaptor that does the bulk of the work needed + /// for data binding. + /// + protected TreeDataSourceAdapter Adapter { + get { + if (this.adapter == null) + this.adapter = new TreeDataSourceAdapter(this); + return adapter; + } + set { adapter = value; } + } + private TreeDataSourceAdapter adapter; + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/DragDrop/DragSource.cs b/VG Music Studio - WinForms/ObjectListView/DragDrop/DragSource.cs new file mode 100644 index 0000000..ed632a7 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/DragDrop/DragSource.cs @@ -0,0 +1,219 @@ +/* + * DragSource.cs - Add drag source functionality to an ObjectListView + * + * Author: Phillip Piper + * Date: 2009-03-17 5:15 PM + * + * Change log: + * 2011-03-29 JPP - Separate OLVDataObject.cs + * v2.3 + * 2009-07-06 JPP - Make sure Link is acceptable as an drop effect by default + * (since MS didn't make it part of the 'All' value) + * v2.2 + * 2009-04-15 JPP - Separated DragSource.cs into DropSink.cs + * 2009-03-17 JPP - Initial version + * + * Copyright (C) 2009-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// An IDragSource controls how drag out from the ObjectListView will behave + /// + public interface IDragSource + { + /// + /// A drag operation is beginning. Return the data object that will be used + /// for data transfer. Return null to prevent the drag from starting. The data + /// object will normally include all the selected objects. + /// + /// + /// The returned object is later passed to the GetAllowedEffect() and EndDrag() + /// methods. + /// + /// What ObjectListView is being dragged from. + /// Which mouse button is down? + /// What item was directly dragged by the user? There may be more than just this + /// item selected. + /// The data object that will be used for data transfer. This will often be a subclass + /// of DataObject, but does not need to be. + Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item); + + /// + /// What operations are possible for this drag? This controls the icon shown during the drag + /// + /// The data object returned by StartDrag() + /// A combination of DragDropEffects flags + DragDropEffects GetAllowedEffects(Object dragObject); + + /// + /// The drag operation is complete. Do whatever is necessary to complete the action. + /// + /// The data object returned by StartDrag() + /// The value returned from GetAllowedEffects() + void EndDrag(Object dragObject, DragDropEffects effect); + } + + /// + /// A do-nothing implementation of IDragSource that can be safely subclassed. + /// + public class AbstractDragSource : IDragSource + { + #region IDragSource Members + + /// + /// See IDragSource documentation + /// + /// + /// + /// + /// + public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) { + return null; + } + + /// + /// See IDragSource documentation + /// + /// + /// + public virtual DragDropEffects GetAllowedEffects(Object data) { + return DragDropEffects.None; + } + + /// + /// See IDragSource documentation + /// + /// + /// + public virtual void EndDrag(Object dragObject, DragDropEffects effect) { + } + + #endregion + } + + /// + /// A reasonable implementation of IDragSource that provides normal + /// drag source functionality. It creates a data object that supports + /// inter-application dragging of text and HTML representation of + /// the dragged rows. It can optionally force a refresh of all dragged + /// rows when the drag is complete. + /// + /// Subclasses can override GetDataObject() to add new + /// data formats to the data transfer object. + public class SimpleDragSource : IDragSource + { + #region Constructors + + /// + /// Construct a SimpleDragSource + /// + public SimpleDragSource() { + } + + /// + /// Construct a SimpleDragSource that refreshes the dragged rows when + /// the drag is complete + /// + /// + public SimpleDragSource(bool refreshAfterDrop) { + this.RefreshAfterDrop = refreshAfterDrop; + } + + #endregion + + #region Public properties + + /// + /// Gets or sets whether the dragged rows should be refreshed when the + /// drag operation is complete. + /// + public bool RefreshAfterDrop { + get { return refreshAfterDrop; } + set { refreshAfterDrop = value; } + } + private bool refreshAfterDrop; + + #endregion + + #region IDragSource Members + + /// + /// Create a DataObject when the user does a left mouse drag operation. + /// See IDragSource for further information. + /// + /// + /// + /// + /// + public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) { + // We only drag on left mouse + if (button != MouseButtons.Left) + return null; + + return this.CreateDataObject(olv); + } + + /// + /// Which operations are allowed in the operation? By default, all operations are supported. + /// + /// + /// All operations are supported + public virtual DragDropEffects GetAllowedEffects(Object data) { + return DragDropEffects.All | DragDropEffects.Link; // why didn't MS include 'Link' in 'All'?? + } + + /// + /// The drag operation is finished. Refreshes the dragged rows if so configured. + /// + /// + /// + public virtual void EndDrag(Object dragObject, DragDropEffects effect) { + OLVDataObject data = dragObject as OLVDataObject; + if (data == null) + return; + + if (this.RefreshAfterDrop) + data.ListView.RefreshObjects(data.ModelObjects); + } + + /// + /// Create a data object that will be used to as the data object + /// for the drag operation. + /// + /// + /// Subclasses can override this method add new formats to the data object. + /// + /// The ObjectListView that is the source of the drag + /// A data object for the drag + protected virtual object CreateDataObject(ObjectListView olv) { + return new OLVDataObject(olv); + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/DragDrop/DropSink.cs b/VG Music Studio - WinForms/ObjectListView/DragDrop/DropSink.cs new file mode 100644 index 0000000..27e14aa --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/DragDrop/DropSink.cs @@ -0,0 +1,1562 @@ +/* + * DropSink.cs - Add drop sink ability to an ObjectListView + * + * Author: Phillip Piper + * Date: 2009-03-17 5:15 PM + * + * Change log: + * 2018-04-26 JPP - Implemented LeftOfItem and RightOfItem target locations + * - Added support for rearranging on non-Detail views. + * v2.9 + * 2015-07-08 JPP - Added SimpleDropSink.EnableFeedback to allow all the pretty and helpful + * user feedback during drags to be turned off + * v2.7 + * 2011-04-20 JPP - Rewrote how ModelDropEventArgs.RefreshObjects() works on TreeListViews + * v2.4.1 + * 2010-08-24 JPP - Moved AcceptExternal property up to SimpleDragSource. + * v2.3 + * 2009-09-01 JPP - Correctly handle case where RefreshObjects() is called for + * objects that were children but are now roots. + * 2009-08-27 JPP - Added ModelDropEventArgs.RefreshObjects() to simplify updating after + * a drag-drop operation + * 2009-08-19 JPP - Changed to use OlvHitTest() + * v2.2.1 + * 2007-07-06 JPP - Added StandardDropActionFromKeys property to OlvDropEventArgs + * v2.2 + * 2009-05-17 JPP - Added a Handled flag to OlvDropEventArgs + * - Tweaked the appearance of the drop-on-background feedback + * 2009-04-15 JPP - Separated DragDrop.cs into DropSink.cs + * 2009-03-17 JPP - Initial version + * + * Copyright (C) 2009-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// Objects that implement this interface can acts as the receiver for drop + /// operation for an ObjectListView. + /// + public interface IDropSink + { + /// + /// Gets or sets the ObjectListView that is the drop sink + /// + ObjectListView ListView { get; set; } + + /// + /// Draw any feedback that is appropriate to the current drop state. + /// + /// + /// Any drawing is done over the top of the ListView. This operation should disturb + /// the Graphic as little as possible. Specifically, do not erase the area into which + /// you draw. + /// + /// A Graphic for drawing + /// The contents bounds of the ListView (not including any header) + void DrawFeedback(Graphics g, Rectangle bounds); + + /// + /// The user has released the drop over this control + /// + /// + /// Implementors should set args.Effect to the appropriate DragDropEffects. This value is returned + /// to the originator of the drag. + /// + /// + void Drop(DragEventArgs args); + + /// + /// A drag has entered this control. + /// + /// Implementors should set args.Effect to the appropriate DragDropEffects. + /// + void Enter(DragEventArgs args); + + /// + /// Change the cursor to reflect the current drag operation. + /// + /// + void GiveFeedback(GiveFeedbackEventArgs args); + + /// + /// The drag has left the bounds of this control + /// + void Leave(); + + /// + /// The drag is moving over this control. + /// + /// This is where any drop target should be calculated. + /// Implementors should set args.Effect to the appropriate DragDropEffects. + /// + /// + void Over(DragEventArgs args); + + /// + /// Should the drag be allowed to continue? + /// + /// + void QueryContinue(QueryContinueDragEventArgs args); + } + + /// + /// This is a do-nothing implementation of IDropSink that is a useful + /// base class for more sophisticated implementations. + /// + public class AbstractDropSink : IDropSink + { + #region IDropSink Members + + /// + /// Gets or sets the ObjectListView that is the drop sink + /// + public virtual ObjectListView ListView { + get { return listView; } + set { this.listView = value; } + } + private ObjectListView listView; + + /// + /// Draw any feedback that is appropriate to the current drop state. + /// + /// + /// Any drawing is done over the top of the ListView. This operation should disturb + /// the Graphic as little as possible. Specifically, do not erase the area into which + /// you draw. + /// + /// A Graphic for drawing + /// The contents bounds of the ListView (not including any header) + public virtual void DrawFeedback(Graphics g, Rectangle bounds) { + } + + /// + /// The user has released the drop over this control + /// + /// + /// Implementors should set args.Effect to the appropriate DragDropEffects. This value is returned + /// to the originator of the drag. + /// + /// + public virtual void Drop(DragEventArgs args) { + this.Cleanup(); + } + + /// + /// A drag has entered this control. + /// + /// Implementors should set args.Effect to the appropriate DragDropEffects. + /// + public virtual void Enter(DragEventArgs args) { + } + + /// + /// The drag has left the bounds of this control + /// + public virtual void Leave() { + this.Cleanup(); + } + + /// + /// The drag is moving over this control. + /// + /// This is where any drop target should be calculated. + /// Implementors should set args.Effect to the appropriate DragDropEffects. + /// + /// + public virtual void Over(DragEventArgs args) { + } + + /// + /// Change the cursor to reflect the current drag operation. + /// + /// You only need to override this if you want non-standard cursors. + /// The standard cursors are supplied automatically. + /// + public virtual void GiveFeedback(GiveFeedbackEventArgs args) { + args.UseDefaultCursors = true; + } + + /// + /// Should the drag be allowed to continue? + /// + /// + /// You only need to override this if you want the user to be able + /// to end the drop in some non-standard way, e.g. dragging to a + /// certain point even without releasing the mouse, or going outside + /// the bounds of the application. + /// + /// + public virtual void QueryContinue(QueryContinueDragEventArgs args) { + } + + + #endregion + + #region Commands + + /// + /// This is called when the mouse leaves the drop region and after the + /// drop has completed. + /// + protected virtual void Cleanup() { + } + + #endregion + } + + /// + /// The enum indicates which target has been found for a drop operation + /// + [Flags] + public enum DropTargetLocation + { + /// + /// No applicable target has been found + /// + None = 0, + + /// + /// The list itself is the target of the drop + /// + Background = 0x01, + + /// + /// An item is the target + /// + Item = 0x02, + + /// + /// Between two items (or above the top item or below the bottom item) + /// can be the target. This is not actually ever a target, only a value indicate + /// that it is valid to drop between items + /// + BetweenItems = 0x04, + + /// + /// Above an item is the target + /// + AboveItem = 0x08, + + /// + /// Below an item is the target + /// + BelowItem = 0x10, + + /// + /// A subitem is the target of the drop + /// + SubItem = 0x20, + + /// + /// On the right of an item is the target + /// + RightOfItem = 0x40, + + /// + /// On the left of an item is the target + /// + LeftOfItem = 0x80 + } + + /// + /// This class represents a simple implementation of a drop sink. + /// + /// + /// Actually, it should be called CleverDropSink -- it's far from simple and can do quite a lot in its own right. + /// + public class SimpleDropSink : AbstractDropSink + { + #region Life and death + + /// + /// Make a new drop sink + /// + public SimpleDropSink() { + this.timer = new Timer(); + this.timer.Interval = 250; + this.timer.Tick += new EventHandler(this.timer_Tick); + + this.CanDropOnItem = true; + //this.CanDropOnSubItem = true; + //this.CanDropOnBackground = true; + //this.CanDropBetween = true; + + this.FeedbackColor = Color.FromArgb(180, Color.MediumBlue); + this.billboard = new BillboardOverlay(); + } + + #endregion + + #region Public properties + + /// + /// Get or set the locations where a drop is allowed to occur (OR-ed together) + /// + public DropTargetLocation AcceptableLocations { + get { return this.acceptableLocations; } + set { this.acceptableLocations = value; } + } + private DropTargetLocation acceptableLocations; + + /// + /// Gets or sets whether this sink allows model objects to be dragged from other lists. Defaults to true. + /// + public bool AcceptExternal { + get { return this.acceptExternal; } + set { this.acceptExternal = value; } + } + private bool acceptExternal = true; + + /// + /// Gets or sets whether the ObjectListView should scroll when the user drags + /// something near to the top or bottom rows. Defaults to true. + /// + /// AutoScroll does not scroll horizontally. + public bool AutoScroll { + get { return this.autoScroll; } + set { this.autoScroll = value; } + } + private bool autoScroll = true; + + /// + /// Gets the billboard overlay that will be used to display feedback + /// messages during a drag operation. + /// + /// Set this to null to stop the feedback. + public BillboardOverlay Billboard { + get { return this.billboard; } + set { this.billboard = value; } + } + private BillboardOverlay billboard; + + /// + /// Get or set whether a drop can occur between items of the list + /// + public bool CanDropBetween { + get { return (this.AcceptableLocations & DropTargetLocation.BetweenItems) == DropTargetLocation.BetweenItems; } + set { + if (value) + this.AcceptableLocations |= DropTargetLocation.BetweenItems; + else + this.AcceptableLocations &= ~DropTargetLocation.BetweenItems; + } + } + + /// + /// Get or set whether a drop can occur on the listview itself + /// + public bool CanDropOnBackground { + get { return (this.AcceptableLocations & DropTargetLocation.Background) == DropTargetLocation.Background; } + set { + if (value) + this.AcceptableLocations |= DropTargetLocation.Background; + else + this.AcceptableLocations &= ~DropTargetLocation.Background; + } + } + + /// + /// Get or set whether a drop can occur on items in the list + /// + public bool CanDropOnItem { + get { return (this.AcceptableLocations & DropTargetLocation.Item) == DropTargetLocation.Item; } + set { + if (value) + this.AcceptableLocations |= DropTargetLocation.Item; + else + this.AcceptableLocations &= ~DropTargetLocation.Item; + } + } + + /// + /// Get or set whether a drop can occur on a subitem in the list + /// + public bool CanDropOnSubItem { + get { return (this.AcceptableLocations & DropTargetLocation.SubItem) == DropTargetLocation.SubItem; } + set { + if (value) + this.AcceptableLocations |= DropTargetLocation.SubItem; + else + this.AcceptableLocations &= ~DropTargetLocation.SubItem; + } + } + + /// + /// Gets or sets whether the drop sink should draw feedback onto the given list + /// during the drag operation. Defaults to true. + /// + /// If this is false, you will have to give the user feedback in some + /// other fashion, like cursor changes + public bool EnableFeedback { + get { return enableFeedback; } + set { enableFeedback = value; } + } + private bool enableFeedback = true; + + /// + /// Get or set the index of the item that is the target of the drop + /// + public int DropTargetIndex { + get { return dropTargetIndex; } + set { + if (this.dropTargetIndex != value) { + this.dropTargetIndex = value; + this.ListView.Invalidate(); + } + } + } + private int dropTargetIndex = -1; + + /// + /// Get the item that is the target of the drop + /// + public OLVListItem DropTargetItem { + get { + return this.ListView.GetItem(this.DropTargetIndex); + } + } + + /// + /// Get or set the location of the target of the drop + /// + public DropTargetLocation DropTargetLocation { + get { return dropTargetLocation; } + set { + if (this.dropTargetLocation != value) { + this.dropTargetLocation = value; + this.ListView.Invalidate(); + } + } + } + private DropTargetLocation dropTargetLocation; + + /// + /// Get or set the index of the subitem that is the target of the drop + /// + public int DropTargetSubItemIndex { + get { return dropTargetSubItemIndex; } + set { + if (this.dropTargetSubItemIndex != value) { + this.dropTargetSubItemIndex = value; + this.ListView.Invalidate(); + } + } + } + private int dropTargetSubItemIndex = -1; + + /// + /// Get or set the color that will be used to provide drop feedback + /// + public Color FeedbackColor { + get { return this.feedbackColor; } + set { this.feedbackColor = value; } + } + private Color feedbackColor; + + /// + /// Get whether the alt key was down during this drop event + /// + public bool IsAltDown { + get { return (this.KeyState & 32) == 32; } + } + + /// + /// Get whether any modifier key was down during this drop event + /// + public bool IsAnyModifierDown { + get { return (this.KeyState & (4 + 8 + 32)) != 0; } + } + + /// + /// Get whether the control key was down during this drop event + /// + public bool IsControlDown { + get { return (this.KeyState & 8) == 8; } + } + + /// + /// Get whether the left mouse button was down during this drop event + /// + public bool IsLeftMouseButtonDown { + get { return (this.KeyState & 1) == 1; } + } + + /// + /// Get whether the right mouse button was down during this drop event + /// + public bool IsMiddleMouseButtonDown { + get { return (this.KeyState & 16) == 16; } + } + + /// + /// Get whether the right mouse button was down during this drop event + /// + public bool IsRightMouseButtonDown { + get { return (this.KeyState & 2) == 2; } + } + + /// + /// Get whether the shift key was down during this drop event + /// + public bool IsShiftDown { + get { return (this.KeyState & 4) == 4; } + } + + /// + /// Get or set the state of the keys during this drop event + /// + public int KeyState { + get { return this.keyState; } + set { this.keyState = value; } + } + private int keyState; + + /// + /// Gets or sets whether the drop sink will automatically use cursors + /// based on the drop effect. By default, this is true. If this is + /// set to false, you must set the Cursor yourself. + /// + public bool UseDefaultCursors { + get { return useDefaultCursors; } + set { useDefaultCursors = value; } + } + private bool useDefaultCursors = true; + + #endregion + + #region Events + + /// + /// Triggered when the sink needs to know if a drop can occur. + /// + /// + /// Handlers should set Effect to indicate what is possible. + /// Handlers can change any of the DropTarget* settings to change + /// the target of the drop. + /// + public event EventHandler CanDrop; + + /// + /// Triggered when the drop is made. + /// + public event EventHandler Dropped; + + /// + /// Triggered when the sink needs to know if a drop can occur + /// AND the source is an ObjectListView + /// + /// + /// Handlers should set Effect to indicate what is possible. + /// Handlers can change any of the DropTarget* settings to change + /// the target of the drop. + /// + public event EventHandler ModelCanDrop; + + /// + /// Triggered when the drop is made. + /// AND the source is an ObjectListView + /// + public event EventHandler ModelDropped; + + #endregion + + #region DropSink Interface + + /// + /// Cleanup the drop sink when the mouse has left the control or + /// the drag has finished. + /// + protected override void Cleanup() { + this.DropTargetLocation = DropTargetLocation.None; + this.ListView.FullRowSelect = this.originalFullRowSelect; + this.Billboard.Text = null; + } + + /// + /// Draw any feedback that is appropriate to the current drop state. + /// + /// + /// Any drawing is done over the top of the ListView. This operation should disturb + /// the Graphic as little as possible. Specifically, do not erase the area into which + /// you draw. + /// + /// A Graphic for drawing + /// The contents bounds of the ListView (not including any header) + public override void DrawFeedback(Graphics g, Rectangle bounds) { + g.SmoothingMode = ObjectListView.SmoothingMode; + + if (this.EnableFeedback) { + switch (this.DropTargetLocation) { + case DropTargetLocation.Background: + this.DrawFeedbackBackgroundTarget(g, bounds); + break; + case DropTargetLocation.Item: + this.DrawFeedbackItemTarget(g, bounds); + break; + case DropTargetLocation.AboveItem: + this.DrawFeedbackAboveItemTarget(g, bounds); + break; + case DropTargetLocation.BelowItem: + this.DrawFeedbackBelowItemTarget(g, bounds); + break; + case DropTargetLocation.LeftOfItem: + this.DrawFeedbackLeftOfItemTarget(g, bounds); + break; + case DropTargetLocation.RightOfItem: + this.DrawFeedbackRightOfItemTarget(g, bounds); + break; + } + } + + if (this.Billboard != null) { + this.Billboard.Draw(this.ListView, g, bounds); + } + } + + /// + /// The user has released the drop over this control + /// + /// + public override void Drop(DragEventArgs args) { + this.dropEventArgs.DragEventArgs = args; + this.TriggerDroppedEvent(args); + this.timer.Stop(); + this.Cleanup(); + } + + /// + /// A drag has entered this control. + /// + /// Implementors should set args.Effect to the appropriate DragDropEffects. + /// + public override void Enter(DragEventArgs args) { + //System.Diagnostics.Debug.WriteLine("Enter"); + + /* + * When FullRowSelect is true, we have two problems: + * 1) GetItemRect(ItemOnly) returns the whole row rather than just the icon/text, which messes + * up our calculation of the drop rectangle. + * 2) during the drag, the Timer events will not fire! This is the major problem, since without + * those events we can't autoscroll. + * + * The first problem we can solve through coding, but the second is more difficult. + * We avoid both problems by turning off FullRowSelect during the drop operation. + */ + this.originalFullRowSelect = this.ListView.FullRowSelect; + this.ListView.FullRowSelect = false; + + // Setup our drop event args block + this.dropEventArgs = new ModelDropEventArgs(); + this.dropEventArgs.DropSink = this; + this.dropEventArgs.ListView = this.ListView; + this.dropEventArgs.DragEventArgs = args; + this.dropEventArgs.DataObject = args.Data; + OLVDataObject olvData = args.Data as OLVDataObject; + if (olvData != null) { + this.dropEventArgs.SourceListView = olvData.ListView; + this.dropEventArgs.SourceModels = olvData.ModelObjects; + } + + this.Over(args); + } + + /// + /// Change the cursor to reflect the current drag operation. + /// + /// + public override void GiveFeedback(GiveFeedbackEventArgs args) { + args.UseDefaultCursors = this.UseDefaultCursors; + } + + /// + /// The drag is moving over this control. + /// + /// + public override void Over(DragEventArgs args) { + //System.Diagnostics.Debug.WriteLine("Over"); + this.dropEventArgs.DragEventArgs = args; + this.KeyState = args.KeyState; + Point pt = this.ListView.PointToClient(new Point(args.X, args.Y)); + args.Effect = this.CalculateDropAction(args, pt); + this.CheckScrolling(pt); + } + + #endregion + + #region Events + + /// + /// Trigger the Dropped events + /// + /// + protected virtual void TriggerDroppedEvent(DragEventArgs args) { + this.dropEventArgs.Handled = false; + + // If the source is an ObjectListView, trigger the ModelDropped event + if (this.dropEventArgs.SourceListView != null) + this.OnModelDropped(this.dropEventArgs); + + if (!this.dropEventArgs.Handled) + this.OnDropped(this.dropEventArgs); + } + + /// + /// Trigger CanDrop + /// + /// + protected virtual void OnCanDrop(OlvDropEventArgs args) { + if (this.CanDrop != null) + this.CanDrop(this, args); + } + + /// + /// Trigger Dropped + /// + /// + protected virtual void OnDropped(OlvDropEventArgs args) { + if (this.Dropped != null) + this.Dropped(this, args); + } + + /// + /// Trigger ModelCanDrop + /// + /// + protected virtual void OnModelCanDrop(ModelDropEventArgs args) { + + // Don't allow drops from other list, if that's what's configured + if (!this.AcceptExternal && args.SourceListView != null && args.SourceListView != this.ListView) { + args.Effect = DragDropEffects.None; + args.DropTargetLocation = DropTargetLocation.None; + args.InfoMessage = "This list doesn't accept drops from other lists"; + return; + } + + if (this.ModelCanDrop != null) + this.ModelCanDrop(this, args); + } + + /// + /// Trigger ModelDropped + /// + /// + protected virtual void OnModelDropped(ModelDropEventArgs args) { + if (this.ModelDropped != null) + this.ModelDropped(this, args); + } + + #endregion + + #region Implementation + + private void timer_Tick(object sender, EventArgs e) { + this.HandleTimerTick(); + } + + /// + /// Handle the timer tick event, which is sent when the listview should + /// scroll + /// + protected virtual void HandleTimerTick() { + + // If the mouse has been released, stop scrolling. + // This is only necessary if the mouse is released outside of the control. + // If the mouse is released inside the control, we would receive a Drop event. + if ((this.IsLeftMouseButtonDown && (Control.MouseButtons & MouseButtons.Left) != MouseButtons.Left) || + (this.IsMiddleMouseButtonDown && (Control.MouseButtons & MouseButtons.Middle) != MouseButtons.Middle) || + (this.IsRightMouseButtonDown && (Control.MouseButtons & MouseButtons.Right) != MouseButtons.Right)) { + this.timer.Stop(); + this.Cleanup(); + return; + } + + // Auto scrolling will continue while the mouse is close to the ListView + const int GRACE_PERIMETER = 30; + + Point pt = this.ListView.PointToClient(Cursor.Position); + Rectangle r2 = this.ListView.ClientRectangle; + r2.Inflate(GRACE_PERIMETER, GRACE_PERIMETER); + if (r2.Contains(pt)) { + this.ListView.LowLevelScroll(0, this.scrollAmount); + } + } + + /// + /// When the mouse is at the given point, what should the target of the drop be? + /// + /// This method should update the DropTarget* members of the given arg block + /// + /// The mouse point, in client co-ordinates + protected virtual void CalculateDropTarget(OlvDropEventArgs args, Point pt) { + const int SMALL_VALUE = 3; + DropTargetLocation location = DropTargetLocation.None; + int targetIndex = -1; + int targetSubIndex = 0; + + if (this.CanDropOnBackground) + location = DropTargetLocation.Background; + + // Which item is the mouse over? + // If it is not over any item, it's over the background. + OlvListViewHitTestInfo info = this.ListView.OlvHitTest(pt.X, pt.Y); + if (info.Item != null && this.CanDropOnItem) { + location = DropTargetLocation.Item; + targetIndex = info.Item.Index; + if (info.SubItem != null && this.CanDropOnSubItem) + targetSubIndex = info.Item.SubItems.IndexOf(info.SubItem); + } + + // Check to see if the mouse is "between" rows. + // ("between" is somewhat loosely defined). + // If the view is Details or List, then "between" is considered vertically. + // If the view is SmallIcon, LargeIcon or Tile, then "between" is considered horizontally. + if (this.CanDropBetween && this.ListView.GetItemCount() > 0) { + + switch (this.ListView.View) { + case View.LargeIcon: + case View.Tile: + case View.SmallIcon: + // If the mouse is over an item, check to see if it is near the left or right edge. + if (info.Item != null) { + int delta = this.CanDropOnItem ? SMALL_VALUE : info.Item.Bounds.Width / 2; + if (pt.X <= info.Item.Bounds.Left + delta) { + targetIndex = info.Item.Index; + location = DropTargetLocation.LeftOfItem; + } else if (pt.X >= info.Item.Bounds.Right - delta) { + targetIndex = info.Item.Index; + location = DropTargetLocation.RightOfItem; + } + } else { + // Is there an item a little to the *right* of the mouse? + // If so, we say the drop point is *left* that item + int probeWidth = SMALL_VALUE * 2; + info = this.ListView.OlvHitTest(pt.X + probeWidth, pt.Y); + if (info.Item != null) { + targetIndex = info.Item.Index; + location = DropTargetLocation.LeftOfItem; + } else { + // Is there an item a little to the left of the mouse? + info = this.ListView.OlvHitTest(pt.X - probeWidth, pt.Y); + if (info.Item != null) { + targetIndex = info.Item.Index; + location = DropTargetLocation.RightOfItem; + } + } + } + break; + case View.Details: + case View.List: + // If the mouse is over an item, check to see if it is near the top or bottom + if (info.Item != null) { + int delta = this.CanDropOnItem ? SMALL_VALUE : this.ListView.RowHeightEffective / 2; + + if (pt.Y <= info.Item.Bounds.Top + delta) { + targetIndex = info.Item.Index; + location = DropTargetLocation.AboveItem; + } else if (pt.Y >= info.Item.Bounds.Bottom - delta) { + targetIndex = info.Item.Index; + location = DropTargetLocation.BelowItem; + } + } else { + // Is there an item a little below the mouse? + // If so, we say the drop point is above that row + info = this.ListView.OlvHitTest(pt.X, pt.Y + SMALL_VALUE); + if (info.Item != null) { + targetIndex = info.Item.Index; + location = DropTargetLocation.AboveItem; + } else { + // Is there an item a little above the mouse? + info = this.ListView.OlvHitTest(pt.X, pt.Y - SMALL_VALUE); + if (info.Item != null) { + targetIndex = info.Item.Index; + location = DropTargetLocation.BelowItem; + } + } + } + + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + args.DropTargetLocation = location; + args.DropTargetIndex = targetIndex; + args.DropTargetSubItemIndex = targetSubIndex; + } + + /// + /// What sort of action is possible when the mouse is at the given point? + /// + /// + /// + /// + /// + /// + public virtual DragDropEffects CalculateDropAction(DragEventArgs args, Point pt) { + + this.CalculateDropTarget(this.dropEventArgs, pt); + + this.dropEventArgs.MouseLocation = pt; + this.dropEventArgs.InfoMessage = null; + this.dropEventArgs.Handled = false; + + if (this.dropEventArgs.SourceListView != null) { + this.dropEventArgs.TargetModel = this.ListView.GetModelObject(this.dropEventArgs.DropTargetIndex); + this.OnModelCanDrop(this.dropEventArgs); + } + + if (!this.dropEventArgs.Handled) + this.OnCanDrop(this.dropEventArgs); + + this.UpdateAfterCanDropEvent(this.dropEventArgs); + + return this.dropEventArgs.Effect; + } + + /// + /// Based solely on the state of the modifier keys, what drop operation should + /// be used? + /// + /// The drop operation that matches the state of the keys + public DragDropEffects CalculateStandardDropActionFromKeys() { + if (this.IsControlDown) { + if (this.IsShiftDown) + return DragDropEffects.Link; + else + return DragDropEffects.Copy; + } else { + return DragDropEffects.Move; + } + } + + /// + /// Should the listview be made to scroll when the mouse is at the given point? + /// + /// + protected virtual void CheckScrolling(Point pt) { + if (!this.AutoScroll) + return; + + Rectangle r = this.ListView.ContentRectangle; + int rowHeight = this.ListView.RowHeightEffective; + int close = rowHeight; + + // In Tile view, using the whole row height is too much + if (this.ListView.View == View.Tile) + close /= 2; + + if (pt.Y <= (r.Top + close)) { + // Scroll faster if the mouse is closer to the top + this.timer.Interval = ((pt.Y <= (r.Top + close / 2)) ? 100 : 350); + this.timer.Start(); + this.scrollAmount = -rowHeight; + } else { + if (pt.Y >= (r.Bottom - close)) { + this.timer.Interval = ((pt.Y >= (r.Bottom - close / 2)) ? 100 : 350); + this.timer.Start(); + this.scrollAmount = rowHeight; + } else { + this.timer.Stop(); + } + } + } + + /// + /// Update the state of our sink to reflect the information that + /// may have been written into the drop event args. + /// + /// + protected virtual void UpdateAfterCanDropEvent(OlvDropEventArgs args) { + this.DropTargetIndex = args.DropTargetIndex; + this.DropTargetLocation = args.DropTargetLocation; + this.DropTargetSubItemIndex = args.DropTargetSubItemIndex; + + if (this.Billboard != null) { + Point pt = args.MouseLocation; + pt.Offset(5, 5); + if (this.Billboard.Text != this.dropEventArgs.InfoMessage || this.Billboard.Location != pt) { + this.Billboard.Text = this.dropEventArgs.InfoMessage; + this.Billboard.Location = pt; + this.ListView.Invalidate(); + } + } + } + + #endregion + + #region Rendering + + /// + /// Draw the feedback that shows that the background is the target + /// + /// + /// + protected virtual void DrawFeedbackBackgroundTarget(Graphics g, Rectangle bounds) { + float penWidth = 12.0f; + Rectangle r = bounds; + r.Inflate((int)-penWidth / 2, (int)-penWidth / 2); + using (Pen p = new Pen(Color.FromArgb(128, this.FeedbackColor), penWidth)) { + using (GraphicsPath path = this.GetRoundedRect(r, 30.0f)) { + g.DrawPath(p, path); + } + } + } + + /// + /// Draw the feedback that shows that an item (or a subitem) is the target + /// + /// + /// + /// + /// DropTargetItem and DropTargetSubItemIndex tells what is the target + /// + protected virtual void DrawFeedbackItemTarget(Graphics g, Rectangle bounds) { + if (this.DropTargetItem == null) + return; + Rectangle r = this.CalculateDropTargetRectangle(this.DropTargetItem, this.DropTargetSubItemIndex); + r.Inflate(1, 1); + float diameter = r.Height / 3; + using (GraphicsPath path = this.GetRoundedRect(r, diameter)) { + using (SolidBrush b = new SolidBrush(Color.FromArgb(48, this.FeedbackColor))) { + g.FillPath(b, path); + } + using (Pen p = new Pen(this.FeedbackColor, 3.0f)) { + g.DrawPath(p, path); + } + } + } + + /// + /// Draw the feedback that shows the drop will occur before target + /// + /// + /// + protected virtual void DrawFeedbackAboveItemTarget(Graphics g, Rectangle bounds) { + if (this.DropTargetItem == null) + return; + + Rectangle r = this.CalculateDropTargetRectangle(this.DropTargetItem, this.DropTargetSubItemIndex); + this.DrawBetweenLine(g, r.Left, r.Top, r.Right, r.Top); + } + + /// + /// Draw the feedback that shows the drop will occur after target + /// + /// + /// + protected virtual void DrawFeedbackBelowItemTarget(Graphics g, Rectangle bounds) + { + if (this.DropTargetItem == null) + return; + + Rectangle r = this.CalculateDropTargetRectangle(this.DropTargetItem, this.DropTargetSubItemIndex); + this.DrawBetweenLine(g, r.Left, r.Bottom, r.Right, r.Bottom); + } + + /// + /// Draw the feedback that shows the drop will occur to the left of target + /// + /// + /// + protected virtual void DrawFeedbackLeftOfItemTarget(Graphics g, Rectangle bounds) + { + if (this.DropTargetItem == null) + return; + + Rectangle r = this.CalculateDropTargetRectangle(this.DropTargetItem, this.DropTargetSubItemIndex); + this.DrawBetweenLine(g, r.Left, r.Top, r.Left, r.Bottom); + } + + /// + /// Draw the feedback that shows the drop will occur to the right of target + /// + /// + /// + protected virtual void DrawFeedbackRightOfItemTarget(Graphics g, Rectangle bounds) + { + if (this.DropTargetItem == null) + return; + + Rectangle r = this.CalculateDropTargetRectangle(this.DropTargetItem, this.DropTargetSubItemIndex); + this.DrawBetweenLine(g, r.Right, r.Top, r.Right, r.Bottom); + } + + /// + /// Return a GraphicPath that is round corner rectangle. + /// + /// + /// + /// + protected GraphicsPath GetRoundedRect(Rectangle rect, float diameter) { + GraphicsPath path = new GraphicsPath(); + + RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter); + path.AddArc(arc, 180, 90); + arc.X = rect.Right - diameter; + path.AddArc(arc, 270, 90); + arc.Y = rect.Bottom - diameter; + path.AddArc(arc, 0, 90); + arc.X = rect.Left; + path.AddArc(arc, 90, 90); + path.CloseFigure(); + + return path; + } + + /// + /// Calculate the target rectangle when the given item (and possible subitem) + /// is the target of the drop. + /// + /// + /// + /// + protected virtual Rectangle CalculateDropTargetRectangle(OLVListItem item, int subItem) { + if (subItem > 0) + return item.SubItems[subItem].Bounds; + + Rectangle r = this.ListView.CalculateCellTextBounds(item, subItem); + + // Allow for indent + if (item.IndentCount > 0) { + int indentWidth = this.ListView.SmallImageSize.Width * item.IndentCount; + r.X += indentWidth; + r.Width -= indentWidth; + } + + return r; + } + + /// + /// Draw a "between items" line at the given co-ordinates + /// + /// + /// + /// + /// + /// + protected virtual void DrawBetweenLine(Graphics g, int x1, int y1, int x2, int y2) { + using (Brush b = new SolidBrush(this.FeedbackColor)) { + if (y1 == y2) { + // Put right and left arrow on a horizontal line + DrawClosedFigure(g, b, RightPointingArrow(x1, y1)); + DrawClosedFigure(g, b, LeftPointingArrow(x2, y2)); + } else { + // Put up and down arrows on a vertical line + DrawClosedFigure(g, b, DownPointingArrow(x1, y1)); + DrawClosedFigure(g, b, UpPointingArrow(x2, y2)); + } + } + + using (Pen p = new Pen(this.FeedbackColor, 3.0f)) { + g.DrawLine(p, x1, y1, x2, y2); + } + } + + private const int ARROW_SIZE = 6; + + private static void DrawClosedFigure(Graphics g, Brush b, Point[] pts) { + using (GraphicsPath gp = new GraphicsPath()) { + gp.StartFigure(); + gp.AddLines(pts); + gp.CloseFigure(); + g.FillPath(b, gp); + } + } + + private static Point[] RightPointingArrow(int x, int y) { + return new Point[] { + new Point(x, y - ARROW_SIZE), + new Point(x, y + ARROW_SIZE), + new Point(x + ARROW_SIZE, y) + }; + } + + private static Point[] LeftPointingArrow(int x, int y) { + return new Point[] { + new Point(x, y - ARROW_SIZE), + new Point(x, y + ARROW_SIZE), + new Point(x - ARROW_SIZE, y) + }; + } + + private static Point[] DownPointingArrow(int x, int y) { + return new Point[] { + new Point(x - ARROW_SIZE, y), + new Point(x + ARROW_SIZE, y), + new Point(x, y + ARROW_SIZE) + }; + } + + private static Point[] UpPointingArrow(int x, int y) { + return new Point[] { + new Point(x - ARROW_SIZE, y), + new Point(x + ARROW_SIZE, y), + new Point(x, y - ARROW_SIZE) + }; + } + + #endregion + + private Timer timer; + private int scrollAmount; + private bool originalFullRowSelect; + private ModelDropEventArgs dropEventArgs; + } + + /// + /// This drop sink allows items within the same list to be rearranged, + /// as well as allowing items to be dropped from other lists. + /// + /// + /// + /// This class can only be used on plain ObjectListViews and FastObjectListViews. + /// The other flavours have no way to implement the insert operation that is required. + /// + /// + /// This class does not work with grouping. + /// + /// + /// This class works when the OLV is sorted, but it is up to the programmer + /// to decide what rearranging such lists "means". Example: if the control is sorting + /// students by academic grade, and the user drags a "Fail" grade student up amongst the "A+" + /// students, it is the responsibility of the programmer to makes the appropriate changes + /// to the model and redraw/rebuild the control so that the users action makes sense. + /// + /// + /// Users of this class should listen for the CanDrop event to decide + /// if models from another OLV can be moved to OLV under this sink. + /// + /// + public class RearrangingDropSink : SimpleDropSink + { + /// + /// Create a RearrangingDropSink + /// + public RearrangingDropSink() { + this.CanDropBetween = true; + this.CanDropOnBackground = true; + this.CanDropOnItem = false; + } + + /// + /// Create a RearrangingDropSink + /// + /// + public RearrangingDropSink(bool acceptDropsFromOtherLists) + : this() { + this.AcceptExternal = acceptDropsFromOtherLists; + } + + /// + /// Trigger OnModelCanDrop + /// + /// + protected override void OnModelCanDrop(ModelDropEventArgs args) { + base.OnModelCanDrop(args); + + if (args.Handled) + return; + + args.Effect = DragDropEffects.Move; + + // Don't allow drops from other list, if that's what's configured + if (!this.AcceptExternal && args.SourceListView != this.ListView) { + args.Effect = DragDropEffects.None; + args.DropTargetLocation = DropTargetLocation.None; + args.InfoMessage = "This list doesn't accept drops from other lists"; + } + + // If we are rearranging the same list, don't allow drops on the background + if (args.DropTargetLocation == DropTargetLocation.Background && args.SourceListView == this.ListView) { + args.Effect = DragDropEffects.None; + args.DropTargetLocation = DropTargetLocation.None; + } + } + + /// + /// Trigger OnModelDropped + /// + /// + protected override void OnModelDropped(ModelDropEventArgs args) { + base.OnModelDropped(args); + + if (!args.Handled) + this.RearrangeModels(args); + } + + /// + /// Do the work of processing the dropped items + /// + /// + public virtual void RearrangeModels(ModelDropEventArgs args) { + switch (args.DropTargetLocation) { + case DropTargetLocation.AboveItem: + case DropTargetLocation.LeftOfItem: + this.ListView.MoveObjects(args.DropTargetIndex, args.SourceModels); + break; + case DropTargetLocation.BelowItem: + case DropTargetLocation.RightOfItem: + this.ListView.MoveObjects(args.DropTargetIndex + 1, args.SourceModels); + break; + case DropTargetLocation.Background: + this.ListView.AddObjects(args.SourceModels); + break; + default: + return; + } + + if (args.SourceListView != this.ListView) { + args.SourceListView.RemoveObjects(args.SourceModels); + } + + // Some views have to be "encouraged" to show the changes + switch (this.ListView.View) { + case View.LargeIcon: + case View.SmallIcon: + case View.Tile: + this.ListView.BuildList(); + break; + } + } + } + + /// + /// When a drop sink needs to know if something can be dropped, or + /// to notify that a drop has occurred, it uses an instance of this class. + /// + public class OlvDropEventArgs : EventArgs + { + /// + /// Create a OlvDropEventArgs + /// + public OlvDropEventArgs() { + } + + #region Data Properties + + /// + /// Get the original drag-drop event args + /// + public DragEventArgs DragEventArgs + { + get { return this.dragEventArgs; } + internal set { this.dragEventArgs = value; } + } + private DragEventArgs dragEventArgs; + + /// + /// Get the data object that is being dragged + /// + public object DataObject + { + get { return this.dataObject; } + internal set { this.dataObject = value; } + } + private object dataObject; + + /// + /// Get the drop sink that originated this event + /// + public SimpleDropSink DropSink { + get { return this.dropSink; } + internal set { this.dropSink = value; } + } + private SimpleDropSink dropSink; + + /// + /// Get or set the index of the item that is the target of the drop + /// + public int DropTargetIndex { + get { return dropTargetIndex; } + set { this.dropTargetIndex = value; } + } + private int dropTargetIndex = -1; + + /// + /// Get or set the location of the target of the drop + /// + public DropTargetLocation DropTargetLocation { + get { return dropTargetLocation; } + set { this.dropTargetLocation = value; } + } + private DropTargetLocation dropTargetLocation; + + /// + /// Get or set the index of the subitem that is the target of the drop + /// + public int DropTargetSubItemIndex { + get { return dropTargetSubItemIndex; } + set { this.dropTargetSubItemIndex = value; } + } + private int dropTargetSubItemIndex = -1; + + /// + /// Get the item that is the target of the drop + /// + public OLVListItem DropTargetItem { + get { + return this.ListView.GetItem(this.DropTargetIndex); + } + set { + if (value == null) + this.DropTargetIndex = -1; + else + this.DropTargetIndex = value.Index; + } + } + + /// + /// Get or set the drag effect that should be used for this operation + /// + public DragDropEffects Effect { + get { return this.effect; } + set { this.effect = value; } + } + private DragDropEffects effect; + + /// + /// Get or set if this event was handled. No further processing will be done for a handled event. + /// + public bool Handled { + get { return this.handled; } + set { this.handled = value; } + } + private bool handled; + + /// + /// Get or set the feedback message for this operation + /// + /// + /// If this is not null, it will be displayed as a feedback message + /// during the drag. + /// + public string InfoMessage { + get { return this.infoMessage; } + set { this.infoMessage = value; } + } + private string infoMessage; + + /// + /// Get the ObjectListView that is being dropped on + /// + public ObjectListView ListView { + get { return this.listView; } + internal set { this.listView = value; } + } + private ObjectListView listView; + + /// + /// Get the location of the mouse (in target ListView co-ords) + /// + public Point MouseLocation { + get { return this.mouseLocation; } + internal set { this.mouseLocation = value; } + } + private Point mouseLocation; + + /// + /// Get the drop action indicated solely by the state of the modifier keys + /// + public DragDropEffects StandardDropActionFromKeys { + get { + return this.DropSink.CalculateStandardDropActionFromKeys(); + } + } + + #endregion + } + + /// + /// These events are triggered when the drag source is an ObjectListView. + /// + public class ModelDropEventArgs : OlvDropEventArgs + { + /// + /// Create a ModelDropEventArgs + /// + public ModelDropEventArgs() + { + } + + /// + /// Gets the model objects that are being dragged. + /// + public IList SourceModels { + get { return this.dragModels; } + internal set { + this.dragModels = value; + TreeListView tlv = this.SourceListView as TreeListView; + if (tlv != null) { + foreach (object model in this.SourceModels) { + object parent = tlv.GetParent(model); + if (!toBeRefreshed.Contains(parent)) + toBeRefreshed.Add(parent); + } + } + } + } + private IList dragModels; + private ArrayList toBeRefreshed = new ArrayList(); + + /// + /// Gets the ObjectListView that is the source of the dragged objects. + /// + public ObjectListView SourceListView { + get { return this.sourceListView; } + internal set { this.sourceListView = value; } + } + private ObjectListView sourceListView; + + /// + /// Get the model object that is being dropped upon. + /// + /// This is only value for TargetLocation == Item + public object TargetModel { + get { return this.targetModel; } + internal set { this.targetModel = value; } + } + private object targetModel; + + /// + /// Refresh all the objects involved in the operation + /// + public void RefreshObjects() { + + toBeRefreshed.AddRange(this.SourceModels); + TreeListView tlv = this.SourceListView as TreeListView; + if (tlv == null) + this.SourceListView.RefreshObjects(toBeRefreshed); + else + tlv.RebuildAll(true); + + TreeListView tlv2 = this.ListView as TreeListView; + if (tlv2 == null) + this.ListView.RefreshObject(this.TargetModel); + else + tlv2.RebuildAll(true); + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/DragDrop/OLVDataObject.cs b/VG Music Studio - WinForms/ObjectListView/DragDrop/OLVDataObject.cs new file mode 100644 index 0000000..34b1181 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/DragDrop/OLVDataObject.cs @@ -0,0 +1,185 @@ +/* + * OLVDataObject.cs - An OLE DataObject that knows how to convert rows of an OLV to text and HTML + * + * Author: Phillip Piper + * Date: 2011-03-29 3:34PM + * + * Change log: + * v2.8 + * 2014-05-02 JPP - When the listview is completely empty, don't try to set CSV text in the clipboard. + * v2.6 + * 2012-08-08 JPP - Changed to use OLVExporter. + * - Added CSV to formats exported to Clipboard + * v2.4 + * 2011-03-29 JPP - Initial version + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// A data transfer object that knows how to transform a list of model + /// objects into a text and HTML representation. + /// + public class OLVDataObject : DataObject { + #region Life and death + + /// + /// Create a data object from the selected objects in the given ObjectListView + /// + /// The source of the data object + public OLVDataObject(ObjectListView olv) + : this(olv, olv.SelectedObjects) { + } + + /// + /// Create a data object which operates on the given model objects + /// in the given ObjectListView + /// + /// The source of the data object + /// The model objects to be put into the data object + public OLVDataObject(ObjectListView olv, IList modelObjects) { + this.objectListView = olv; + this.modelObjects = modelObjects; + this.includeHiddenColumns = olv.IncludeHiddenColumnsInDataTransfer; + this.includeColumnHeaders = olv.IncludeColumnHeadersInCopy; + this.CreateTextFormats(); + } + + #endregion + + #region Properties + + /// + /// Gets or sets whether hidden columns will also be included in the text + /// and HTML representation. If this is false, only visible columns will + /// be included. + /// + public bool IncludeHiddenColumns { + get { return includeHiddenColumns; } + } + private readonly bool includeHiddenColumns; + + /// + /// Gets or sets whether column headers will also be included in the text + /// and HTML representation. + /// + public bool IncludeColumnHeaders { + get { return includeColumnHeaders; } + } + private readonly bool includeColumnHeaders; + + /// + /// Gets the ObjectListView that is being used as the source of the data + /// + public ObjectListView ListView { + get { return objectListView; } + } + private readonly ObjectListView objectListView; + + /// + /// Gets the model objects that are to be placed in the data object + /// + public IList ModelObjects { + get { return modelObjects; } + } + private readonly IList modelObjects; + + #endregion + + /// + /// Put a text and HTML representation of our model objects + /// into the data object. + /// + public void CreateTextFormats() { + + OLVExporter exporter = this.CreateExporter(); + + // Put both the text and html versions onto the clipboard. + // For some reason, SetText() with UnicodeText doesn't set the basic CF_TEXT format, + // but using SetData() does. + //this.SetText(sbText.ToString(), TextDataFormat.UnicodeText); + this.SetData(exporter.ExportTo(OLVExporter.ExportFormat.TabSeparated)); + string exportTo = exporter.ExportTo(OLVExporter.ExportFormat.CSV); + if (!String.IsNullOrEmpty(exportTo)) + this.SetText(exportTo, TextDataFormat.CommaSeparatedValue); + this.SetText(ConvertToHtmlFragment(exporter.ExportTo(OLVExporter.ExportFormat.HTML)), TextDataFormat.Html); + } + + /// + /// Create an exporter for the data contained in this object + /// + /// + protected OLVExporter CreateExporter() { + OLVExporter exporter = new OLVExporter(this.ListView); + exporter.IncludeColumnHeaders = this.IncludeColumnHeaders; + exporter.IncludeHiddenColumns = this.IncludeHiddenColumns; + exporter.ModelObjects = this.ModelObjects; + return exporter; + } + + /// + /// Make a HTML representation of our model objects + /// + [Obsolete("Use OLVExporter directly instead", false)] + public string CreateHtml() { + OLVExporter exporter = this.CreateExporter(); + return exporter.ExportTo(OLVExporter.ExportFormat.HTML); + } + + /// + /// Convert the fragment of HTML into the Clipboards HTML format. + /// + /// The HTML format is found here http://msdn2.microsoft.com/en-us/library/aa767917.aspx + /// + /// The HTML to put onto the clipboard. It must be valid HTML! + /// A string that can be put onto the clipboard and will be recognised as HTML + private string ConvertToHtmlFragment(string fragment) { + // Minimal implementation of HTML clipboard format + const string SOURCE = "http://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView"; + + const String MARKER_BLOCK = + "Version:1.0\r\n" + + "StartHTML:{0,8}\r\n" + + "EndHTML:{1,8}\r\n" + + "StartFragment:{2,8}\r\n" + + "EndFragment:{3,8}\r\n" + + "StartSelection:{2,8}\r\n" + + "EndSelection:{3,8}\r\n" + + "SourceURL:{4}\r\n" + + "{5}"; + + int prefixLength = String.Format(MARKER_BLOCK, 0, 0, 0, 0, SOURCE, "").Length; + + const String DEFAULT_HTML_BODY = + "" + + "{0}"; + + string html = String.Format(DEFAULT_HTML_BODY, fragment); + int startFragment = prefixLength + html.IndexOf(fragment, StringComparison.Ordinal); + int endFragment = startFragment + fragment.Length; + + return String.Format(MARKER_BLOCK, prefixLength, prefixLength + html.Length, startFragment, endFragment, SOURCE, html); + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/FastDataListView.cs b/VG Music Studio - WinForms/ObjectListView/FastDataListView.cs new file mode 100644 index 0000000..4035f62 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/FastDataListView.cs @@ -0,0 +1,169 @@ +/* + * FastDataListView - A data bindable listview that has the speed of a virtual list + * + * Author: Phillip Piper + * Date: 22/09/2010 8:11 AM + * + * Change log: + * 2015-02-02 JPP - Made Unfreezing more efficient by removing a redundant BuildList() call + * v2.6 + * 2010-09-22 JPP - Initial version + * + * Copyright (C) 2006-2015 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.ComponentModel; +using System.Windows.Forms; +using System.Drawing.Design; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// A FastDataListView virtualizes the display of data from a DataSource. It operates on + /// DataSets and DataTables in the same way as a DataListView, but does so much more efficiently. + /// + /// + /// + /// A FastDataListView still has to load all its data from the DataSource. If you have SQL statement + /// that returns 1 million rows, all 1 million rows will still need to read from the database. + /// However, once the rows are loaded, the FastDataListView will only build rows as they are displayed. + /// + /// + public class FastDataListView : FastObjectListView + { + /// + /// + /// + /// + protected override void Dispose(bool disposing) + { + if (this.adapter != null) { + this.adapter.Dispose(); + this.adapter = null; + } + + base.Dispose(disposing); + } + + #region Public Properties + + /// + /// Gets or sets whether or not columns will be automatically generated to show the + /// columns when the DataSource is set. + /// + /// This must be set before the DataSource is set. It has no effect afterwards. + [Category("Data"), + Description("Should the control automatically generate columns from the DataSource"), + DefaultValue(true)] + public bool AutoGenerateColumns + { + get { return this.Adapter.AutoGenerateColumns; } + set { this.Adapter.AutoGenerateColumns = value; } + } + + /// + /// Get or set the VirtualListDataSource that will be displayed in this list view. + /// + /// The VirtualListDataSource should implement either , , + /// or . Some common examples are the following types of objects: + /// + /// + /// + /// + /// + /// + /// + /// When binding to a list container (i.e. one that implements the + /// interface, such as ) + /// you must also set the property in order + /// to identify which particular list you would like to display. You + /// may also set the property even when + /// VirtualListDataSource refers to a list, since can + /// also be used to navigate relations between lists. + /// + [Category("Data"), + TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")] + public virtual Object DataSource { + get { return this.Adapter.DataSource; } + set { this.Adapter.DataSource = value; } + } + + /// + /// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data. + /// + /// If the data source is not a DataSet or DataViewManager, this property has no effect + [Category("Data"), + Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)), + DefaultValue("")] + public virtual string DataMember { + get { return this.Adapter.DataMember; } + set { this.Adapter.DataMember = value; } + } + + #endregion + + #region Implementation properties + + /// + /// Gets or sets the DataSourceAdaptor that does the bulk of the work needed + /// for data binding. + /// + protected DataSourceAdapter Adapter { + get { + if (adapter == null) + adapter = this.CreateDataSourceAdapter(); + return adapter; + } + set { adapter = value; } + } + private DataSourceAdapter adapter; + + #endregion + + #region Implementation + + /// + /// Create the DataSourceAdapter that this control will use. + /// + /// A DataSourceAdapter configured for this list + /// Subclasses should override this to create their + /// own specialized adapters + protected virtual DataSourceAdapter CreateDataSourceAdapter() { + return new DataSourceAdapter(this); + } + + /// + /// Change the Unfreeze behaviour + /// + protected override void DoUnfreeze() + { + + // Copied from base method, but we don't need to BuildList() since we know that our + // data adaptor is going to do that immediately after this method exits. + this.EndUpdate(); + this.ResizeFreeSpaceFillingColumns(); + // this.BuildList(); + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/FastObjectListView.cs b/VG Music Studio - WinForms/ObjectListView/FastObjectListView.cs new file mode 100644 index 0000000..886fda4 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/FastObjectListView.cs @@ -0,0 +1,422 @@ +/* + * FastObjectListView - A listview that behaves like an ObjectListView but has the speed of a virtual list + * + * Author: Phillip Piper + * Date: 27/09/2008 9:15 AM + * + * Change log: + * 2014-10-15 JPP - Fire Filter event when applying filters + * v2.8 + * 2012-06-11 JPP - Added more efficient version of FilteredObjects + * v2.5.1 + * 2011-04-25 JPP - Fixed problem with removing objects from filtered or sorted list + * v2.4 + * 2010-04-05 JPP - Added filtering + * v2.3 + * 2009-08-27 JPP - Added GroupingStrategy + * - Added optimized Objects property + * v2.2.1 + * 2009-01-07 JPP - Made all public and protected methods virtual + * 2008-09-27 JPP - Separated from ObjectListView.cs + * + * Copyright (C) 2006-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// A FastObjectListView trades function for speed. + /// + /// + /// On my mid-range laptop, this view builds a list of 10,000 objects in 0.1 seconds, + /// as opposed to a normal ObjectListView which takes 10-15 seconds. Lists of up to 50,000 items should be + /// able to be handled with sub-second response times even on low end machines. + /// + /// A FastObjectListView is implemented as a virtual list with many of the virtual modes limits (e.g. no sorting) + /// fixed through coding. There are some functions that simply cannot be provided. Specifically, a FastObjectListView cannot: + /// + /// use Tile view + /// show groups on XP + /// + /// + /// + public class FastObjectListView : VirtualObjectListView + { + /// + /// Make a FastObjectListView + /// + public FastObjectListView() { + this.VirtualListDataSource = new FastObjectListDataSource(this); + this.GroupingStrategy = new FastListGroupingStrategy(); + } + + /// + /// Gets the collection of objects that survive any filtering that may be in place. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override IEnumerable FilteredObjects { + get { + // This is much faster than the base method + return ((FastObjectListDataSource)this.VirtualListDataSource).FilteredObjectList; + } + } + + /// + /// Get/set the collection of objects that this list will show + /// + /// + /// + /// The contents of the control will be updated immediately after setting this property. + /// + /// This method preserves selection, if possible. Use SetObjects() if + /// you do not want to preserve the selection. Preserving selection is the slowest part of this + /// code and performance is O(n) where n is the number of selected rows. + /// This method is not thread safe. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override IEnumerable Objects { + get { + // This is much faster than the base method + return ((FastObjectListDataSource)this.VirtualListDataSource).ObjectList; + } + set { base.Objects = value; } + } + + /// + /// Move the given collection of objects to the given index. + /// + /// This operation only makes sense on non-grouped ObjectListViews. + /// + /// + public override void MoveObjects(int index, ICollection modelObjects) { + if (this.InvokeRequired) { + this.Invoke((MethodInvoker)delegate() { this.MoveObjects(index, modelObjects); }); + return; + } + + // If any object that is going to be moved is before the point where the insertion + // will occur, then we have to reduce the location of our insertion point + int displacedObjectCount = 0; + foreach (object modelObject in modelObjects) { + int i = this.IndexOf(modelObject); + if (i >= 0 && i <= index) + displacedObjectCount++; + } + index -= displacedObjectCount; + + this.BeginUpdate(); + try { + this.RemoveObjects(modelObjects); + this.InsertObjects(index, modelObjects); + } + finally { + this.EndUpdate(); + } + } + + /// + /// Remove any sorting and revert to the given order of the model objects + /// + /// To be really honest, Unsort() doesn't work on FastObjectListViews since + /// the original ordering of model objects is lost when Sort() is called. So this method + /// effectively just turns off sorting. + public override void Unsort() { + this.ShowGroups = false; + this.PrimarySortColumn = null; + this.PrimarySortOrder = SortOrder.None; + this.SetObjects(this.Objects); + } + } + + /// + /// Provide a data source for a FastObjectListView + /// + /// + /// This class isn't intended to be used directly, but it is left as a public + /// class just in case someone wants to subclass it. + /// + public class FastObjectListDataSource : AbstractVirtualListDataSource + { + /// + /// Create a FastObjectListDataSource + /// + /// + public FastObjectListDataSource(FastObjectListView listView) + : base(listView) { + } + + #region IVirtualListDataSource Members + + /// + /// Get n'th object + /// + /// + /// + public override object GetNthObject(int n) { + if (n >= 0 && n < this.filteredObjectList.Count) + return this.filteredObjectList[n]; + + return null; + } + + /// + /// How many items are in the data source + /// + /// + public override int GetObjectCount() { + return this.filteredObjectList.Count; + } + + /// + /// Get the index of the given model + /// + /// + /// + public override int GetObjectIndex(object model) { + int index; + + if (model != null && this.objectsToIndexMap.TryGetValue(model, out index)) + return index; + + return -1; + } + + /// + /// + /// + /// + /// + /// + /// + /// + public override int SearchText(string text, int first, int last, OLVColumn column) { + if (first <= last) { + for (int i = first; i <= last; i++) { + string data = column.GetStringValue(this.listView.GetNthItemInDisplayOrder(i).RowObject); + if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase)) + return i; + } + } else { + for (int i = first; i >= last; i--) { + string data = column.GetStringValue(this.listView.GetNthItemInDisplayOrder(i).RowObject); + if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase)) + return i; + } + } + + return -1; + } + + /// + /// + /// + /// + /// + public override void Sort(OLVColumn column, SortOrder sortOrder) { + if (sortOrder != SortOrder.None) { + ModelObjectComparer comparer = new ModelObjectComparer(column, sortOrder, this.listView.SecondarySortColumn, this.listView.SecondarySortOrder); + this.fullObjectList.Sort(comparer); + this.filteredObjectList.Sort(comparer); + } + this.RebuildIndexMap(); + } + + /// + /// + /// + /// + public override void AddObjects(ICollection modelObjects) { + foreach (object modelObject in modelObjects) { + if (modelObject != null) + this.fullObjectList.Add(modelObject); + } + this.FilterObjects(); + this.RebuildIndexMap(); + } + + /// + /// + /// + /// + /// + public override void InsertObjects(int index, ICollection modelObjects) { + this.fullObjectList.InsertRange(index, modelObjects); + this.FilterObjects(); + this.RebuildIndexMap(); + } + + /// + /// Remove the given collection of models from this source. + /// + /// + public override void RemoveObjects(ICollection modelObjects) { + + // We have to unselect any object that is about to be deleted + List indicesToRemove = new List(); + foreach (object modelObject in modelObjects) { + int i = this.GetObjectIndex(modelObject); + if (i >= 0) + indicesToRemove.Add(i); + } + + // Sort the indices from highest to lowest so that we + // remove latter ones before earlier ones. In this way, the + // indices of the rows doesn't change after the deletes. + indicesToRemove.Sort(); + indicesToRemove.Reverse(); + + foreach (int i in indicesToRemove) + this.listView.SelectedIndices.Remove(i); + + // Remove the objects from the unfiltered list + foreach (object modelObject in modelObjects) + this.fullObjectList.Remove(modelObject); + + this.FilterObjects(); + this.RebuildIndexMap(); + } + + /// + /// + /// + /// + public override void SetObjects(IEnumerable collection) { + ArrayList newObjects = ObjectListView.EnumerableToArray(collection, true); + + this.fullObjectList = newObjects; + this.FilterObjects(); + this.RebuildIndexMap(); + } + + /// + /// Update/replace the nth object with the given object + /// + /// + /// + public override void UpdateObject(int index, object modelObject) { + if (index < 0 || index >= this.filteredObjectList.Count) + return; + + int i = this.fullObjectList.IndexOf(this.filteredObjectList[index]); + if (i < 0) + return; + + if (ReferenceEquals(this.fullObjectList[i], modelObject)) + return; + + this.fullObjectList[i] = modelObject; + this.filteredObjectList[index] = modelObject; + this.objectsToIndexMap[modelObject] = index; + } + + private ArrayList fullObjectList = new ArrayList(); + private ArrayList filteredObjectList = new ArrayList(); + private IModelFilter modelFilter; + private IListFilter listFilter; + + #endregion + + #region IFilterableDataSource Members + + /// + /// Apply the given filters to this data source. One or both may be null. + /// + /// + /// + public override void ApplyFilters(IModelFilter iModelFilter, IListFilter iListFilter) { + this.modelFilter = iModelFilter; + this.listFilter = iListFilter; + this.SetObjects(this.fullObjectList); + } + + #endregion + + #region Implementation + + /// + /// Gets the full list of objects being used for this fast list. + /// This list is unfiltered. + /// + public ArrayList ObjectList { + get { return fullObjectList; } + } + + /// + /// Gets the list of objects from ObjectList which survive any installed filters. + /// + public ArrayList FilteredObjectList { + get { return filteredObjectList; } + } + + /// + /// Rebuild the map that remembers which model object is displayed at which line + /// + protected void RebuildIndexMap() { + this.objectsToIndexMap.Clear(); + for (int i = 0; i < this.filteredObjectList.Count; i++) + this.objectsToIndexMap[this.filteredObjectList[i]] = i; + } + readonly Dictionary objectsToIndexMap = new Dictionary(); + + /// + /// Build our filtered list from our full list. + /// + protected void FilterObjects() { + + // If this list isn't filtered, we don't need to do anything else + if (!this.listView.UseFiltering) { + this.filteredObjectList = new ArrayList(this.fullObjectList); + return; + } + + // Tell the world to filter the objects. If they do so, don't do anything else + // ReSharper disable PossibleMultipleEnumeration + FilterEventArgs args = new FilterEventArgs(this.fullObjectList); + this.listView.OnFilter(args); + if (args.FilteredObjects != null) { + this.filteredObjectList = ObjectListView.EnumerableToArray(args.FilteredObjects, false); + return; + } + + IEnumerable objects = (this.listFilter == null) ? + this.fullObjectList : this.listFilter.Filter(this.fullObjectList); + + // Apply the object filter if there is one + if (this.modelFilter == null) { + this.filteredObjectList = ObjectListView.EnumerableToArray(objects, false); + } else { + this.filteredObjectList = new ArrayList(); + foreach (object model in objects) { + if (this.modelFilter.Filter(model)) + this.filteredObjectList.Add(model); + } + } + } + + #endregion + } + +} diff --git a/VG Music Studio - WinForms/ObjectListView/Filtering/Cluster.cs b/VG Music Studio - WinForms/ObjectListView/Filtering/Cluster.cs new file mode 100644 index 0000000..0ba9eca --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Filtering/Cluster.cs @@ -0,0 +1,125 @@ +/* + * Cluster - Implements a simple cluster + * + * Author: Phillip Piper + * Date: 3-March-2011 10:53 pm + * + * Change log: + * 2011-03-03 JPP - First version + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// Concrete implementation of the ICluster interface. + /// + public class Cluster : ICluster { + + #region Life and death + + /// + /// Create a cluster + /// + /// The key for the cluster + public Cluster(object key) { + this.Count = 1; + this.ClusterKey = key; + } + + #endregion + + #region Public overrides + + /// + /// Return a string representation of this cluster + /// + /// + public override string ToString() { + return this.DisplayLabel ?? "[empty]"; + } + + #endregion + + #region Implementation of ICluster + + /// + /// Gets or sets how many items belong to this cluster + /// + public int Count { + get { return count; } + set { count = value; } + } + private int count; + + /// + /// Gets or sets the label that will be shown to the user to represent + /// this cluster + /// + public string DisplayLabel { + get { return displayLabel; } + set { displayLabel = value; } + } + private string displayLabel; + + /// + /// Gets or sets the actual data object that all members of this cluster + /// have commonly returned. + /// + public object ClusterKey { + get { return clusterKey; } + set { clusterKey = value; } + } + private object clusterKey; + + #endregion + + #region Implementation of IComparable + + /// + /// Return an indication of the ordering between this object and the given one + /// + /// + /// + public int CompareTo(object other) { + if (other == null || other == System.DBNull.Value) + return 1; + + ICluster otherCluster = other as ICluster; + if (otherCluster == null) + return 1; + + string keyAsString = this.ClusterKey as string; + if (keyAsString != null) + return String.Compare(keyAsString, otherCluster.ClusterKey as string, StringComparison.CurrentCultureIgnoreCase); + + IComparable keyAsComparable = this.ClusterKey as IComparable; + if (keyAsComparable != null) + return keyAsComparable.CompareTo(otherCluster.ClusterKey); + + return -1; + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Filtering/ClusteringStrategy.cs b/VG Music Studio - WinForms/ObjectListView/Filtering/ClusteringStrategy.cs new file mode 100644 index 0000000..6d67f2e --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Filtering/ClusteringStrategy.cs @@ -0,0 +1,189 @@ +/* + * ClusteringStrategy - Implements a simple clustering strategy + * + * Author: Phillip Piper + * Date: 3-March-2011 10:53 pm + * + * Change log: + * 2011-03-03 JPP - First version + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Text; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// This class provides a useful base implementation of a clustering + /// strategy where the clusters are grouped around the value of a given column. + /// + public class ClusteringStrategy : IClusteringStrategy { + + #region Static properties + + /// + /// This field is the text that will be shown to the user when a cluster + /// key is null. It is exposed so it can be localized. + /// + static public string NULL_LABEL = "[null]"; + + /// + /// This field is the text that will be shown to the user when a cluster + /// key is empty (i.e. a string of zero length). It is exposed so it can be localized. + /// + static public string EMPTY_LABEL = "[empty]"; + + /// + /// Gets or sets the format that will be used by default for clusters that only + /// contain 1 item. The format string must accept two placeholders: + /// - {0} is the cluster key converted to a string + /// - {1} is the number of items in the cluster (always 1 in this case) + /// + static public string DefaultDisplayLabelFormatSingular { + get { return defaultDisplayLabelFormatSingular; } + set { defaultDisplayLabelFormatSingular = value; } + } + static private string defaultDisplayLabelFormatSingular = "{0} ({1} item)"; + + /// + /// Gets or sets the format that will be used by default for clusters that + /// contain 0 or two or more items. The format string must accept two placeholders: + /// - {0} is the cluster key converted to a string + /// - {1} is the number of items in the cluster + /// + static public string DefaultDisplayLabelFormatPlural { + get { return defaultDisplayLabelFormatPural; } + set { defaultDisplayLabelFormatPural = value; } + } + static private string defaultDisplayLabelFormatPural = "{0} ({1} items)"; + + #endregion + + #region Life and death + + /// + /// Create a clustering strategy + /// + public ClusteringStrategy() { + this.DisplayLabelFormatSingular = DefaultDisplayLabelFormatSingular; + this.DisplayLabelFormatPlural = DefaultDisplayLabelFormatPlural; + } + + #endregion + + #region Public properties + + /// + /// Gets or sets the column upon which this strategy is operating + /// + public OLVColumn Column { + get { return column; } + set { column = value; } + } + private OLVColumn column; + + /// + /// Gets or sets the format that will be used when the cluster + /// contains only 1 item. The format string must accept two placeholders: + /// - {0} is the cluster key converted to a string + /// - {1} is the number of items in the cluster (always 1 in this case) + /// + /// If this is not set, the value from + /// ClusteringStrategy.DefaultDisplayLabelFormatSingular will be used + public string DisplayLabelFormatSingular { + get { return displayLabelFormatSingular; } + set { displayLabelFormatSingular = value; } + } + private string displayLabelFormatSingular; + + /// + /// Gets or sets the format that will be used when the cluster + /// contains 0 or two or more items. The format string must accept two placeholders: + /// - {0} is the cluster key converted to a string + /// - {1} is the number of items in the cluster + /// + /// If this is not set, the value from + /// ClusteringStrategy.DefaultDisplayLabelFormatPlural will be used + public string DisplayLabelFormatPlural { + get { return displayLabelFormatPural; } + set { displayLabelFormatPural = value; } + } + private string displayLabelFormatPural; + + #endregion + + #region ICluster implementation + + /// + /// Get the cluster key by which the given model will be partitioned by this strategy + /// + /// + /// + virtual public object GetClusterKey(object model) { + return this.Column.GetValue(model); + } + + /// + /// Create a cluster to hold the given cluster key + /// + /// + /// + virtual public ICluster CreateCluster(object clusterKey) { + return new Cluster(clusterKey); + } + + /// + /// Gets the display label that the given cluster should use + /// + /// + /// + virtual public string GetClusterDisplayLabel(ICluster cluster) { + string s = this.Column.ValueToString(cluster.ClusterKey) ?? NULL_LABEL; + if (String.IsNullOrEmpty(s)) + s = EMPTY_LABEL; + return this.ApplyDisplayFormat(cluster, s); + } + + /// + /// Create a filter that will include only model objects that + /// match one or more of the given values. + /// + /// + /// + virtual public IModelFilter CreateFilter(IList valuesChosenForFiltering) { + return new OneOfFilter(this.GetClusterKey, valuesChosenForFiltering); + } + + /// + /// Create a label that combines the string representation of the cluster + /// key with a format string that holds an "X [N items in cluster]" type layout. + /// + /// + /// + /// + virtual protected string ApplyDisplayFormat(ICluster cluster, string s) { + string format = (cluster.Count == 1) ? this.DisplayLabelFormatSingular : this.DisplayLabelFormatPlural; + return String.IsNullOrEmpty(format) ? s : String.Format(format, s, cluster.Count); + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Filtering/ClustersFromGroupsStrategy.cs b/VG Music Studio - WinForms/ObjectListView/Filtering/ClustersFromGroupsStrategy.cs new file mode 100644 index 0000000..a851b3e --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Filtering/ClustersFromGroupsStrategy.cs @@ -0,0 +1,70 @@ +/* + * ClusteringStrategy - Implements a simple clustering strategy + * + * Author: Phillip Piper + * Date: 1-April-2011 8:12am + * + * Change log: + * 2011-04-01 JPP - First version + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// This class calculates clusters from the groups that the column uses. + /// + /// + /// + /// This is the default strategy for all non-date, filterable columns. + /// + /// + /// This class does not strictly mimic the groups created by the given column. + /// In particular, if the programmer changes the default grouping technique + /// by listening for grouping events, this class will not mimic that behaviour. + /// + /// + public class ClustersFromGroupsStrategy : ClusteringStrategy { + + /// + /// Get the cluster key by which the given model will be partitioned by this strategy + /// + /// + /// + public override object GetClusterKey(object model) { + return this.Column.GetGroupKey(model); + } + + /// + /// Gets the display label that the given cluster should use + /// + /// + /// + public override string GetClusterDisplayLabel(ICluster cluster) { + string s = this.Column.ConvertGroupKeyToTitle(cluster.ClusterKey); + if (String.IsNullOrEmpty(s)) + s = EMPTY_LABEL; + return this.ApplyDisplayFormat(cluster, s); + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Filtering/DateTimeClusteringStrategy.cs b/VG Music Studio - WinForms/ObjectListView/Filtering/DateTimeClusteringStrategy.cs new file mode 100644 index 0000000..542fd94 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Filtering/DateTimeClusteringStrategy.cs @@ -0,0 +1,187 @@ +/* + * DateTimeClusteringStrategy - A strategy to cluster objects by a date time + * + * Author: Phillip Piper + * Date: 30-March-2011 9:40am + * + * Change log: + * 2011-03-30 JPP - First version + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Globalization; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// This enum is used to indicate various portions of a datetime + /// + [Flags] + public enum DateTimePortion { + /// + /// Year + /// + Year = 0x01, + + /// + /// Month + /// + Month = 0x02, + + /// + /// Day of the month + /// + Day = 0x04, + + /// + /// Hour + /// + Hour = 0x08, + + /// + /// Minute + /// + Minute = 0x10, + + /// + /// Second + /// + Second = 0x20 + } + + /// + /// This class implements a strategy where the model objects are clustered + /// according to some portion of the datetime value in the configured column. + /// + /// To create a strategy that grouped people who were born in + /// the same month, you would create a strategy that extracted just + /// the month, and formatted it to show just the month's name. Like this: + /// + /// + /// someColumn.ClusteringStrategy = new DateTimeClusteringStrategy(DateTimePortion.Month, "MMMM"); + /// + public class DateTimeClusteringStrategy : ClusteringStrategy { + #region Life and death + + /// + /// Create a strategy that clusters by month/year + /// + public DateTimeClusteringStrategy() + : this(DateTimePortion.Year | DateTimePortion.Month, "MMMM yyyy") { + } + + /// + /// Create a strategy that clusters around the given parts + /// + /// + /// + public DateTimeClusteringStrategy(DateTimePortion portions, string format) { + this.Portions = portions; + this.Format = format; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the format string that will be used to create a user-presentable + /// version of the cluster key. + /// + /// The format should use the date/time format strings, as documented + /// in the Windows SDK. Both standard formats and custom format will work. + /// "D" - long date pattern + /// "MMMM, yyyy" - "January, 1999" + public string Format { + get { return format; } + set { format = value; } + } + private string format; + + /// + /// Gets or sets the parts of the DateTime that will be extracted when + /// determining the clustering key for an object. + /// + public DateTimePortion Portions { + get { return portions; } + set { portions = value; } + } + private DateTimePortion portions = DateTimePortion.Year | DateTimePortion.Month; + + #endregion + + #region IClusterStrategy implementation + + /// + /// Get the cluster key by which the given model will be partitioned by this strategy + /// + /// + /// + public override object GetClusterKey(object model) { + // Get the data attribute we want from the given model + // Make sure the returned value is a DateTime + DateTime? dateTime = this.Column.GetValue(model) as DateTime?; + if (!dateTime.HasValue) + return null; + + // Extract the parts of the datetime that we are interested in. + // Even if we aren't interested in a particular portion, we still have to give it a reasonable default + // otherwise we won't be able to build a DateTime object for it + int year = ((this.Portions & DateTimePortion.Year) == DateTimePortion.Year) ? dateTime.Value.Year : 1; + int month = ((this.Portions & DateTimePortion.Month) == DateTimePortion.Month) ? dateTime.Value.Month : 1; + int day = ((this.Portions & DateTimePortion.Day) == DateTimePortion.Day) ? dateTime.Value.Day : 1; + int hour = ((this.Portions & DateTimePortion.Hour) == DateTimePortion.Hour) ? dateTime.Value.Hour : 0; + int minute = ((this.Portions & DateTimePortion.Minute) == DateTimePortion.Minute) ? dateTime.Value.Minute : 0; + int second = ((this.Portions & DateTimePortion.Second) == DateTimePortion.Second) ? dateTime.Value.Second : 0; + + return new DateTime(year, month, day, hour, minute, second); + } + + /// + /// Gets the display label that the given cluster should use + /// + /// + /// + public override string GetClusterDisplayLabel(ICluster cluster) { + DateTime? dateTime = cluster.ClusterKey as DateTime?; + + return this.ApplyDisplayFormat(cluster, dateTime.HasValue ? this.DateToString(dateTime.Value) : NULL_LABEL); + } + + /// + /// Convert the given date into a user presentable string + /// + /// + /// + protected virtual string DateToString(DateTime dateTime) { + if (String.IsNullOrEmpty(this.Format)) + return dateTime.ToString(CultureInfo.CurrentUICulture); + + try { + return dateTime.ToString(this.Format); + } + catch (FormatException) { + return String.Format("Bad format string '{0}' for value '{1}'", this.Format, dateTime); + } + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Filtering/FilterMenuBuilder.cs b/VG Music Studio - WinForms/ObjectListView/Filtering/FilterMenuBuilder.cs new file mode 100644 index 0000000..2d586ab --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Filtering/FilterMenuBuilder.cs @@ -0,0 +1,369 @@ +/* + * FilterMenuBuilder - Responsible for creating a Filter menu + * + * Author: Phillip Piper + * Date: 4-March-2011 11:59 pm + * + * Change log: + * 2012-05-20 JPP - Allow the same model object to be in multiple clusters + * Useful for xor'ed flag fields, and multi-value strings + * (e.g. hobbies that are stored as comma separated values). + * v2.5.1 + * 2012-04-14 JPP - Fixed rare bug with clustering an empty list (SF #3445118) + * v2.5 + * 2011-04-12 JPP - Added some images to menu + * 2011-03-04 JPP - First version + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using System.Collections; +using System.Drawing; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// Instances of this class know how to build a Filter menu. + /// It is responsible for clustering the values in the target column, + /// build a menu that shows those clusters, and then constructing + /// a filter that will enact the users choices. + /// + /// + /// Almost all of the methods in this class are declared as "virtual protected" + /// so that subclasses can provide alternative behaviours. + /// + public class FilterMenuBuilder { + + #region Static properties + + /// + /// Gets or sets the string that labels the Apply button. + /// Exposed so it can be localized. + /// + static public string APPLY_LABEL = "Apply"; + + /// + /// Gets or sets the string that labels the Clear All menu item. + /// Exposed so it can be localized. + /// + static public string CLEAR_ALL_FILTERS_LABEL = "Clear All Filters"; + + /// + /// Gets or sets the string that labels the Filtering menu as a whole.. + /// Exposed so it can be localized. + /// + static public string FILTERING_LABEL = "Filtering"; + + /// + /// Gets or sets the string that represents Select All values. + /// If this is set to null or empty, no Select All option will be included. + /// Exposed so it can be localized. + /// + static public string SELECT_ALL_LABEL = "Select All"; + + /// + /// Gets or sets the image that will be placed next to the Clear Filtering menu item + /// + static public Bitmap ClearFilteringImage = Kermalis.VGMusicStudio.WinForms.ObjectListView.Properties.Resources.ClearFiltering; + + /// + /// Gets or sets the image that will be placed next to all "Apply" menu items on the filtering menu + /// + static public Bitmap FilteringImage = Kermalis.VGMusicStudio.WinForms.ObjectListView.Properties.Resources.Filtering; + + #endregion + + #region Public properties + + /// + /// Gets or sets whether null should be considered as a valid data value. + /// If this is true (the default), then a cluster will null as a key will be allow. + /// If this is false, object that return a cluster key of null will ignored. + /// + public bool TreatNullAsDataValue { + get { return treatNullAsDataValue; } + set { treatNullAsDataValue = value; } + } + private bool treatNullAsDataValue = true; + + /// + /// Gets or sets the maximum number of objects that the clustering strategy + /// will consider. This should be large enough to collect all unique clusters, + /// but small enough to finish in a reasonable time. + /// + /// The default value is 10,000. This should be perfectly + /// acceptable for almost all lists. + public int MaxObjectsToConsider { + get { return maxObjectsToConsider; } + set { maxObjectsToConsider = value; } + } + private int maxObjectsToConsider = 10000; + + #endregion + + /// + /// Create a Filter menu on the given tool tip for the given column in the given ObjectListView. + /// + /// This is the main entry point into this class. + /// + /// + /// + /// The strip that should be shown to the user + virtual public ToolStripDropDown MakeFilterMenu(ToolStripDropDown strip, ObjectListView listView, OLVColumn column) { + if (strip == null) throw new ArgumentNullException("strip"); + if (listView == null) throw new ArgumentNullException("listView"); + if (column == null) throw new ArgumentNullException("column"); + + if (!column.UseFiltering || column.ClusteringStrategy == null || listView.Objects == null) + return strip; + + List clusters = this.Cluster(column.ClusteringStrategy, listView, column); + if (clusters.Count > 0) { + this.SortClusters(column.ClusteringStrategy, clusters); + strip.Items.Add(this.CreateFilteringMenuItem(column, clusters)); + } + + return strip; + } + + /// + /// Create a collection of clusters that should be presented to the user + /// + /// + /// + /// + /// + virtual protected List Cluster(IClusteringStrategy strategy, ObjectListView listView, OLVColumn column) { + // Build a map that correlates cluster key to clusters + NullableDictionary map = new NullableDictionary(); + int count = 0; + foreach (object model in listView.ObjectsForClustering) { + this.ClusterOneModel(strategy, map, model); + + if (count++ > this.MaxObjectsToConsider) + break; + } + + // Now that we know exactly how many items are in each cluster, create a label for it + foreach (ICluster cluster in map.Values) + cluster.DisplayLabel = strategy.GetClusterDisplayLabel(cluster); + + return new List(map.Values); + } + + private void ClusterOneModel(IClusteringStrategy strategy, NullableDictionary map, object model) { + object clusterKey = strategy.GetClusterKey(model); + + // If the returned value is an IEnumerable, that means the given model can belong to more than one cluster + IEnumerable keyEnumerable = clusterKey as IEnumerable; + if (clusterKey is string || keyEnumerable == null) + keyEnumerable = new object[] {clusterKey}; + + // Deal with nulls and DBNulls + ArrayList nullCorrected = new ArrayList(); + foreach (object key in keyEnumerable) { + if (key == null || key == System.DBNull.Value) { + if (this.TreatNullAsDataValue) + nullCorrected.Add(null); + } else nullCorrected.Add(key); + } + + // Group by key + foreach (object key in nullCorrected) { + if (map.ContainsKey(key)) + map[key].Count += 1; + else + map[key] = strategy.CreateCluster(key); + } + } + + /// + /// Order the given list of clusters in the manner in which they should be presented to the user. + /// + /// + /// + virtual protected void SortClusters(IClusteringStrategy strategy, List clusters) { + clusters.Sort(); + } + + /// + /// Do the work of making a menu that shows the clusters to the users + /// + /// + /// + /// + virtual protected ToolStripMenuItem CreateFilteringMenuItem(OLVColumn column, List clusters) { + ToolStripCheckedListBox checkedList = new ToolStripCheckedListBox(); + checkedList.Tag = column; + foreach (ICluster cluster in clusters) + checkedList.AddItem(cluster, column.ValuesChosenForFiltering.Contains(cluster.ClusterKey)); + if (!String.IsNullOrEmpty(SELECT_ALL_LABEL)) { + int checkedCount = checkedList.CheckedItems.Count; + if (checkedCount == 0) + checkedList.AddItem(SELECT_ALL_LABEL, CheckState.Unchecked); + else + checkedList.AddItem(SELECT_ALL_LABEL, checkedCount == clusters.Count ? CheckState.Checked : CheckState.Indeterminate); + } + checkedList.ItemCheck += new ItemCheckEventHandler(HandleItemCheckedWrapped); + + ToolStripMenuItem clearAll = new ToolStripMenuItem(CLEAR_ALL_FILTERS_LABEL, ClearFilteringImage, delegate(object sender, EventArgs args) { + this.ClearAllFilters(column); + }); + ToolStripMenuItem apply = new ToolStripMenuItem(APPLY_LABEL, FilteringImage, delegate(object sender, EventArgs args) { + this.EnactFilter(checkedList, column); + }); + ToolStripMenuItem subMenu = new ToolStripMenuItem(FILTERING_LABEL, null, new ToolStripItem[] { + clearAll, new ToolStripSeparator(), checkedList, apply }); + return subMenu; + } + + /// + /// Wrap a protected section around the real HandleItemChecked method, so that if + /// that method tries to change a "checkedness" of an item, we don't get a recursive + /// stack error. Effectively, this ensure that HandleItemChecked is only called + /// in response to a user action. + /// + /// + /// + private void HandleItemCheckedWrapped(object sender, ItemCheckEventArgs e) { + if (alreadyInHandleItemChecked) + return; + + try { + alreadyInHandleItemChecked = true; + this.HandleItemChecked(sender, e); + } + finally { + alreadyInHandleItemChecked = false; + } + } + bool alreadyInHandleItemChecked = false; + + /// + /// Handle a user-generated ItemCheck event + /// + /// + /// + virtual protected void HandleItemChecked(object sender, ItemCheckEventArgs e) { + + ToolStripCheckedListBox checkedList = sender as ToolStripCheckedListBox; + if (checkedList == null) return; + OLVColumn column = checkedList.Tag as OLVColumn; + if (column == null) return; + ObjectListView listView = column.ListView as ObjectListView; + if (listView == null) return; + + // Deal with the "Select All" item if there is one + int selectAllIndex = checkedList.Items.IndexOf(SELECT_ALL_LABEL); + if (selectAllIndex >= 0) + HandleSelectAllItem(e, checkedList, selectAllIndex); + } + + /// + /// Handle any checking/unchecking of the Select All option, and keep + /// its checkedness in sync with everything else that is checked. + /// + /// + /// + /// + virtual protected void HandleSelectAllItem(ItemCheckEventArgs e, ToolStripCheckedListBox checkedList, int selectAllIndex) { + // Did they check/uncheck the "Select All"? + if (e.Index == selectAllIndex) { + if (e.NewValue == CheckState.Checked) + checkedList.CheckAll(); + if (e.NewValue == CheckState.Unchecked) + checkedList.UncheckAll(); + return; + } + + // OK. The user didn't check/uncheck SelectAll. Now we have to update it's + // checkedness to reflect the state of everything else + // If all clusters are checked, we check the Select All. + // If no clusters are checked, the uncheck the Select All. + // For everything else, Select All is set to indeterminate. + + // How many items are currently checked? + int count = checkedList.CheckedItems.Count; + + // First complication. + // The value of the Select All itself doesn't count + if (checkedList.GetItemCheckState(selectAllIndex) != CheckState.Unchecked) + count -= 1; + + // Another complication. + // CheckedItems does not yet know about the item the user has just + // clicked, so we have to adjust the count of checked items to what + // it is going to be + if (e.NewValue != e.CurrentValue) { + if (e.NewValue == CheckState.Checked) + count += 1; + else + count -= 1; + } + + // Update the state of the Select All item + if (count == 0) + checkedList.SetItemState(selectAllIndex, CheckState.Unchecked); + else if (count == checkedList.Items.Count - 1) + checkedList.SetItemState(selectAllIndex, CheckState.Checked); + else + checkedList.SetItemState(selectAllIndex, CheckState.Indeterminate); + } + + /// + /// Clear all the filters that are applied to the given column + /// + /// The column from which filters are to be removed + virtual protected void ClearAllFilters(OLVColumn column) { + + ObjectListView olv = column.ListView as ObjectListView; + if (olv == null || olv.IsDisposed) + return; + + olv.ResetColumnFiltering(); + } + + /// + /// Apply the selected values from the given list as a filter on the given column + /// + /// A list in which the checked items should be used as filters + /// The column for which a filter should be generated + virtual protected void EnactFilter(ToolStripCheckedListBox checkedList, OLVColumn column) { + + ObjectListView olv = column.ListView as ObjectListView; + if (olv == null || olv.IsDisposed) + return; + + // Collect all the checked values + ArrayList chosenValues = new ArrayList(); + foreach (object x in checkedList.CheckedItems) { + ICluster cluster = x as ICluster; + if (cluster != null) { + chosenValues.Add(cluster.ClusterKey); + } + } + column.ValuesChosenForFiltering = chosenValues; + + olv.UpdateColumnFiltering(); + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Filtering/Filters.cs b/VG Music Studio - WinForms/ObjectListView/Filtering/Filters.cs new file mode 100644 index 0000000..d08a7c1 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Filtering/Filters.cs @@ -0,0 +1,489 @@ +/* + * Filters - Filtering on ObjectListViews + * + * Author: Phillip Piper + * Date: 03/03/2010 17:00 + * + * Change log: + * 2011-03-01 JPP Added CompositeAllFilter, CompositeAnyFilter and OneOfFilter + * v2.4.1 + * 2010-06-23 JPP Extended TextMatchFilter to handle regular expressions and string prefix matching. + * v2.4 + * 2010-03-03 JPP Initial version + * + * TO DO: + * + * Copyright (C) 2010-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Reflection; +using System.Drawing; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// Interface for model-by-model filtering + /// + public interface IModelFilter + { + /// + /// Should the given model be included when this filter is installed + /// + /// The model object to consider + /// Returns true if the model will be included by the filter + bool Filter(object modelObject); + } + + /// + /// Interface for whole list filtering + /// + public interface IListFilter + { + /// + /// Return a subset of the given list of model objects as the new + /// contents of the ObjectListView + /// + /// The collection of model objects that the list will possibly display + /// The filtered collection that holds the model objects that will be displayed. + IEnumerable Filter(IEnumerable modelObjects); + } + + /// + /// Base class for model-by-model filters + /// + public class AbstractModelFilter : IModelFilter + { + /// + /// Should the given model be included when this filter is installed + /// + /// The model object to consider + /// Returns true if the model will be included by the filter + virtual public bool Filter(object modelObject) { + return true; + } + } + + /// + /// This filter calls a given Predicate to decide if a model object should be included + /// + public class ModelFilter : IModelFilter + { + /// + /// Create a filter based on the given predicate + /// + /// The function that will filter objects + public ModelFilter(Predicate predicate) { + this.Predicate = predicate; + } + + /// + /// Gets or sets the predicate used to filter model objects + /// + protected Predicate Predicate { + get { return predicate; } + set { predicate = value; } + } + private Predicate predicate; + + /// + /// Should the given model object be included? + /// + /// + /// + virtual public bool Filter(object modelObject) { + return this.Predicate == null ? true : this.Predicate(modelObject); + } + } + + /// + /// A CompositeFilter joins several other filters together. + /// If there are no filters, all model objects are included + /// + abstract public class CompositeFilter : IModelFilter { + + /// + /// Create an empty filter + /// + public CompositeFilter() { + } + + /// + /// Create a composite filter from the given list of filters + /// + /// A list of filters + public CompositeFilter(IEnumerable filters) { + foreach (IModelFilter filter in filters) { + if (filter != null) + Filters.Add(filter); + } + } + + /// + /// Gets or sets the filters used by this composite + /// + public IList Filters { + get { return filters; } + set { filters = value; } + } + private IList filters = new List(); + + /// + /// Get the sub filters that are text match filters + /// + public IEnumerable TextFilters { + get { + foreach (IModelFilter filter in this.Filters) { + TextMatchFilter textFilter = filter as TextMatchFilter; + if (textFilter != null) + yield return textFilter; + } + } + } + + /// + /// Decide whether or not the given model should be included by the filter + /// + /// + /// True if the object is included by the filter + virtual public bool Filter(object modelObject) { + if (this.Filters == null || this.Filters.Count == 0) + return true; + + return this.FilterObject(modelObject); + } + + /// + /// Decide whether or not the given model should be included by the filter + /// + /// Filters is guaranteed to be non-empty when this method is called + /// The model object under consideration + /// True if the object is included by the filter + abstract public bool FilterObject(object modelObject); + } + + /// + /// A CompositeAllFilter joins several other filters together. + /// A model object must satisfy all filters to be included. + /// If there are no filters, all model objects are included + /// + public class CompositeAllFilter : CompositeFilter { + + /// + /// Create a filter + /// + /// + public CompositeAllFilter(List filters) + : base(filters) { + } + + /// + /// Decide whether or not the given model should be included by the filter + /// + /// Filters is guaranteed to be non-empty when this method is called + /// The model object under consideration + /// True if the object is included by the filter + override public bool FilterObject(object modelObject) { + foreach (IModelFilter filter in this.Filters) + if (!filter.Filter(modelObject)) + return false; + + return true; + } + } + + /// + /// A CompositeAllFilter joins several other filters together. + /// A model object must only satisfy one of the filters to be included. + /// If there are no filters, all model objects are included + /// + public class CompositeAnyFilter : CompositeFilter { + + /// + /// Create a filter from the given filters + /// + /// + public CompositeAnyFilter(List filters) + : base(filters) { + } + + /// + /// Decide whether or not the given model should be included by the filter + /// + /// Filters is guaranteed to be non-empty when this method is called + /// The model object under consideration + /// True if the object is included by the filter + override public bool FilterObject(object modelObject) { + foreach (IModelFilter filter in this.Filters) + if (filter.Filter(modelObject)) + return true; + + return false; + } + } + + /// + /// Instances of this class extract a value from the model object + /// and compare that value to a list of fixed values. The model + /// object is included if the extracted value is in the list + /// + /// If there is no delegate installed or there are + /// no values to match, no model objects will be matched + public class OneOfFilter : IModelFilter { + + /// + /// Create a filter that will use the given delegate to extract values + /// + /// + public OneOfFilter(AspectGetterDelegate valueGetter) : + this(valueGetter, new ArrayList()) { + } + + /// + /// Create a filter that will extract values using the given delegate + /// and compare them to the values in the given list. + /// + /// + /// + public OneOfFilter(AspectGetterDelegate valueGetter, ICollection possibleValues) { + this.ValueGetter = valueGetter; + this.PossibleValues = new ArrayList(possibleValues); + } + + /// + /// Gets or sets the delegate that will be used to extract values + /// from model objects + /// + virtual public AspectGetterDelegate ValueGetter { + get { return valueGetter; } + set { valueGetter = value; } + } + private AspectGetterDelegate valueGetter; + + /// + /// Gets or sets the list of values that the value extracted from + /// the model object must match in order to be included. + /// + virtual public IList PossibleValues { + get { return possibleValues; } + set { possibleValues = value; } + } + private IList possibleValues; + + /// + /// Should the given model object be included? + /// + /// + /// + public virtual bool Filter(object modelObject) { + if (this.ValueGetter == null || this.PossibleValues == null || this.PossibleValues.Count == 0) + return false; + + object result = this.ValueGetter(modelObject); + IEnumerable enumerable = result as IEnumerable; + if (result is string || enumerable == null) + return this.DoesValueMatch(result); + + foreach (object x in enumerable) { + if (this.DoesValueMatch(x)) + return true; + } + return false; + } + + /// + /// Decides if the given property is a match for the values in the PossibleValues collection + /// + /// + /// + protected virtual bool DoesValueMatch(object result) { + return this.PossibleValues.Contains(result); + } + } + + /// + /// Instances of this class match a property of a model objects against + /// a list of bit flags. The property should be an xor-ed collection + /// of bits flags. + /// + /// Both the property compared and the list of possible values + /// must be convertible to ulongs. + public class FlagBitSetFilter : OneOfFilter { + + /// + /// Create an instance + /// + /// + /// + public FlagBitSetFilter(AspectGetterDelegate valueGetter, ICollection possibleValues) : base(valueGetter, possibleValues) { + this.ConvertPossibleValues(); + } + + /// + /// Gets or sets the collection of values that will be matched. + /// These must be ulongs (or convertible to ulongs). + /// + public override IList PossibleValues { + get { return base.PossibleValues; } + set { + base.PossibleValues = value; + this.ConvertPossibleValues(); + } + } + + private void ConvertPossibleValues() { + this.possibleValuesAsUlongs = new List(); + foreach (object x in this.PossibleValues) + this.possibleValuesAsUlongs.Add(Convert.ToUInt64(x)); + } + + /// + /// Decides if the given property is a match for the values in the PossibleValues collection + /// + /// + /// + protected override bool DoesValueMatch(object result) { + try { + UInt64 value = Convert.ToUInt64(result); + foreach (ulong flag in this.possibleValuesAsUlongs) { + if ((value & flag) == flag) + return true; + } + return false; + } + catch (InvalidCastException) { + return false; + } + catch (FormatException) { + return false; + } + } + + private List possibleValuesAsUlongs = new List(); + } + + /// + /// Base class for whole list filters + /// + public class AbstractListFilter : IListFilter + { + /// + /// Return a subset of the given list of model objects as the new + /// contents of the ObjectListView + /// + /// The collection of model objects that the list will possibly display + /// The filtered collection that holds the model objects that will be displayed. + virtual public IEnumerable Filter(IEnumerable modelObjects) { + return modelObjects; + } + } + + /// + /// Instance of this class implement delegate based whole list filtering + /// + public class ListFilter : AbstractListFilter + { + /// + /// A delegate that filters on a whole list + /// + /// + /// + public delegate IEnumerable ListFilterDelegate(IEnumerable rowObjects); + + /// + /// Create a ListFilter + /// + /// + public ListFilter(ListFilterDelegate function) { + this.Function = function; + } + + /// + /// Gets or sets the delegate that will filter the list + /// + public ListFilterDelegate Function { + get { return function; } + set { function = value; } + } + private ListFilterDelegate function; + + /// + /// Do the actual work of filtering + /// + /// + /// + public override IEnumerable Filter(IEnumerable modelObjects) { + if (this.Function == null) + return modelObjects; + + return this.Function(modelObjects); + } + } + + /// + /// Filter the list so only the last N entries are displayed + /// + public class TailFilter : AbstractListFilter + { + /// + /// Create a no-op tail filter + /// + public TailFilter() { + } + + /// + /// Create a filter that includes on the last N model objects + /// + /// + public TailFilter(int numberOfObjects) { + this.Count = numberOfObjects; + } + + /// + /// Gets or sets the number of model objects that will be + /// returned from the tail of the list + /// + public int Count { + get { return count; } + set { count = value; } + } + private int count; + + /// + /// Return the last N subset of the model objects + /// + /// + /// + public override IEnumerable Filter(IEnumerable modelObjects) { + if (this.Count <= 0) + return modelObjects; + + ArrayList list = ObjectListView.EnumerableToArray(modelObjects, false); + + if (this.Count > list.Count) + return list; + + object[] tail = new object[this.Count]; + list.CopyTo(list.Count - this.Count, tail, 0, this.Count); + return new ArrayList(tail); + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/Filtering/FlagClusteringStrategy.cs b/VG Music Studio - WinForms/ObjectListView/Filtering/FlagClusteringStrategy.cs new file mode 100644 index 0000000..0c7f289 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Filtering/FlagClusteringStrategy.cs @@ -0,0 +1,160 @@ +/* + * FlagClusteringStrategy - Implements a clustering strategy for a field which is a single integer + * containing an XOR'ed collection of bit flags + * + * Author: Phillip Piper + * Date: 23-March-2012 8:33 am + * + * Change log: + * 2012-03-23 JPP - First version + * + * Copyright (C) 2012 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// Instances of this class cluster model objects on the basis of a + /// property that holds an xor-ed collection of bit flags. + /// + public class FlagClusteringStrategy : ClusteringStrategy + { + #region Life and death + + /// + /// Create a clustering strategy that operates on the flags of the given enum + /// + /// + public FlagClusteringStrategy(Type enumType) { + if (enumType == null) throw new ArgumentNullException("enumType"); + if (!enumType.IsEnum) throw new ArgumentException("Type must be enum", "enumType"); + if (enumType.GetCustomAttributes(typeof(FlagsAttribute), false) == null) throw new ArgumentException("Type must have [Flags] attribute", "enumType"); + + List flags = new List(); + foreach (object x in Enum.GetValues(enumType)) + flags.Add(Convert.ToInt64(x)); + + List flagLabels = new List(); + foreach (string x in Enum.GetNames(enumType)) + flagLabels.Add(x); + + this.SetValues(flags.ToArray(), flagLabels.ToArray()); + } + + /// + /// Create a clustering strategy around the given collections of flags and their display labels. + /// There must be the same number of elements in both collections. + /// + /// The list of flags. + /// + public FlagClusteringStrategy(long[] values, string[] labels) { + this.SetValues(values, labels); + } + + #endregion + + #region Implementation + + /// + /// Gets the value that will be xor-ed to test for the presence of a particular value. + /// + public long[] Values { + get { return this.values; } + private set { this.values = value; } + } + private long[] values; + + /// + /// Gets the labels that will be used when the corresponding Value is XOR present in the data. + /// + public string[] Labels { + get { return this.labels; } + private set { this.labels = value; } + } + private string[] labels; + + private void SetValues(long[] flags, string[] flagLabels) { + if (flags == null || flags.Length == 0) throw new ArgumentNullException("flags"); + if (flagLabels == null || flagLabels.Length == 0) throw new ArgumentNullException("flagLabels"); + if (flags.Length != flagLabels.Length) throw new ArgumentException("values and labels must have the same number of entries", "flags"); + + this.Values = flags; + this.Labels = flagLabels; + } + + #endregion + + #region Implementation of IClusteringStrategy + + /// + /// Get the cluster key by which the given model will be partitioned by this strategy + /// + /// + /// + public override object GetClusterKey(object model) { + List flags = new List(); + try { + long modelValue = Convert.ToInt64(this.Column.GetValue(model)); + foreach (long x in this.Values) { + if ((x & modelValue) == x) + flags.Add(x); + } + return flags; + } + catch (InvalidCastException ex) { + System.Diagnostics.Debug.Write(ex); + return flags; + } + catch (FormatException ex) { + System.Diagnostics.Debug.Write(ex); + return flags; + } + } + + /// + /// Gets the display label that the given cluster should use + /// + /// + /// + public override string GetClusterDisplayLabel(ICluster cluster) { + long clusterKeyAsUlong = Convert.ToInt64(cluster.ClusterKey); + for (int i = 0; i < this.Values.Length; i++ ) { + if (clusterKeyAsUlong == this.Values[i]) + return this.ApplyDisplayFormat(cluster, this.Labels[i]); + } + return this.ApplyDisplayFormat(cluster, clusterKeyAsUlong.ToString(CultureInfo.CurrentUICulture)); + } + + /// + /// Create a filter that will include only model objects that + /// match one or more of the given values. + /// + /// + /// + public override IModelFilter CreateFilter(IList valuesChosenForFiltering) { + return new FlagBitSetFilter(this.GetClusterKey, valuesChosenForFiltering); + } + + #endregion + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/Filtering/ICluster.cs b/VG Music Studio - WinForms/ObjectListView/Filtering/ICluster.cs new file mode 100644 index 0000000..d92f427 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Filtering/ICluster.cs @@ -0,0 +1,56 @@ +/* + * ICluster - A cluster is a group of objects that can be included or excluded as a whole + * + * Author: Phillip Piper + * Date: 4-March-2011 11:59 pm + * + * Change log: + * 2011-03-04 JPP - First version + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// A cluster is a like collection of objects that can be usefully filtered + /// as whole using the filtering UI provided by the ObjectListView. + /// + public interface ICluster : IComparable { + /// + /// Gets or sets how many items belong to this cluster + /// + int Count { get; set; } + + /// + /// Gets or sets the label that will be shown to the user to represent + /// this cluster + /// + string DisplayLabel { get; set; } + + /// + /// Gets or sets the actual data object that all members of this cluster + /// have commonly returned. + /// + object ClusterKey { get; set; } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Filtering/IClusteringStrategy.cs b/VG Music Studio - WinForms/ObjectListView/Filtering/IClusteringStrategy.cs new file mode 100644 index 0000000..5b3b789 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Filtering/IClusteringStrategy.cs @@ -0,0 +1,80 @@ +/* + * IClusterStrategy - Encapsulates the ability to create a list of clusters from an ObjectListView + * + * Author: Phillip Piper + * Date: 4-March-2011 11:59 pm + * + * Change log: + * 2012-05-23 JPP - Added CreateFilter() method to interface to allow the strategy + * to control the actual model filter that is created. + * v2.5 + * 2011-03-04 JPP - First version + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView{ + + /// + /// Implementation of this interface control the selecting of cluster keys + /// and how those clusters will be presented to the user + /// + public interface IClusteringStrategy { + + /// + /// Gets or sets the column upon which this strategy will operate + /// + OLVColumn Column { get; set; } + + /// + /// Get the cluster key by which the given model will be partitioned by this strategy + /// + /// If the returned value is an IEnumerable, the given model is considered + /// to belong to multiple clusters + /// + /// + object GetClusterKey(object model); + + /// + /// Create a cluster to hold the given cluster key + /// + /// + /// + ICluster CreateCluster(object clusterKey); + + /// + /// Gets the display label that the given cluster should use + /// + /// + /// + string GetClusterDisplayLabel(ICluster cluster); + + /// + /// Create a filter that will include only model objects that + /// match one or more of the given values. + /// + /// + /// + IModelFilter CreateFilter(IList valuesChosenForFiltering); + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Filtering/TextMatchFilter.cs b/VG Music Studio - WinForms/ObjectListView/Filtering/TextMatchFilter.cs new file mode 100644 index 0000000..3f4c63f --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Filtering/TextMatchFilter.cs @@ -0,0 +1,642 @@ +/* + * TextMatchFilter - Text based filtering on ObjectListViews + * + * Author: Phillip Piper + * Date: 31/05/2011 7:45am + * + * Change log: + * 2018-05-01 JPP - Added ITextMatchFilter to allow for alternate implementations + * - Made several classes public so they can be subclassed + * v2.6 + * 2012-10-13 JPP Allow filtering to consider additional columns + * v2.5.1 + * 2011-06-22 JPP Handle searching for empty strings + * v2.5.0 + * 2011-05-31 JPP Initial version + * + * TO DO: + * + * Copyright (C) 2011-2018 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.Drawing; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + public interface ITextMatchFilter: IModelFilter { + /// + /// Find all the ways in which this filter matches the given string. + /// + /// This is used by the renderer to decide which bits of + /// the string should be highlighted + /// + /// A list of character ranges indicating the matched substrings + IEnumerable FindAllMatchedRanges(string cellText); + } + + /// + /// Instances of this class include only those rows of the listview + /// that match one or more given strings. + /// + /// This class can match strings by prefix, regex, or simple containment. + /// There are factory methods for each of these matching strategies. + public class TextMatchFilter : AbstractModelFilter, ITextMatchFilter { + + #region Life and death + + /// + /// Create a text filter that will include rows where any cell matches + /// any of the given regex expressions. + /// + /// + /// + /// + /// Any string that is not a valid regex expression will be ignored. + public static TextMatchFilter Regex(ObjectListView olv, params string[] texts) { + TextMatchFilter filter = new TextMatchFilter(olv); + filter.RegexStrings = texts; + return filter; + } + + /// + /// Create a text filter that includes rows where any cell begins with one of the given strings + /// + /// + /// + /// + public static TextMatchFilter Prefix(ObjectListView olv, params string[] texts) { + TextMatchFilter filter = new TextMatchFilter(olv); + filter.PrefixStrings = texts; + return filter; + } + + /// + /// Create a text filter that includes rows where any cell contains any of the given strings. + /// + /// + /// + /// + public static TextMatchFilter Contains(ObjectListView olv, params string[] texts) { + TextMatchFilter filter = new TextMatchFilter(olv); + filter.ContainsStrings = texts; + return filter; + } + + /// + /// Create a TextFilter + /// + /// + public TextMatchFilter(ObjectListView olv) { + this.ListView = olv; + } + + /// + /// Create a TextFilter that finds the given string + /// + /// + /// + public TextMatchFilter(ObjectListView olv, string text) { + this.ListView = olv; + this.ContainsStrings = new string[] { text }; + } + + /// + /// Create a TextFilter that finds the given string using the given comparison + /// + /// + /// + /// + public TextMatchFilter(ObjectListView olv, string text, StringComparison comparison) { + this.ListView = olv; + this.ContainsStrings = new string[] { text }; + this.StringComparison = comparison; + } + + #endregion + + #region Public properties + + /// + /// Gets or sets which columns will be used for the comparisons? If this is null, all columns will be used + /// + public OLVColumn[] Columns { + get { return columns; } + set { columns = value; } + } + private OLVColumn[] columns; + + /// + /// Gets or sets additional columns which will be used in the comparison. These will be used + /// in addition to either the Columns property or to all columns taken from the control. + /// + public OLVColumn[] AdditionalColumns { + get { return additionalColumns; } + set { additionalColumns = value; } + } + private OLVColumn[] additionalColumns; + + /// + /// Gets or sets the collection of strings that will be used for + /// contains matching. Setting this replaces all previous texts + /// of any kind. + /// + public IEnumerable ContainsStrings { + get { + foreach (TextMatchingStrategy component in this.MatchingStrategies) + yield return component.Text; + } + set { + this.MatchingStrategies = new List(); + if (value != null) { + foreach (string text in value) + this.MatchingStrategies.Add(new TextContainsMatchingStrategy(this, text)); + } + } + } + + /// + /// Gets whether or not this filter has any search criteria + /// + public bool HasComponents { + get { + return this.MatchingStrategies.Count > 0; + } + } + + /// + /// Gets or set the ObjectListView upon which this filter will work + /// + /// + /// You cannot really rebase a filter after it is created, so do not change this value. + /// It is included so that it can be set in an object initialiser. + /// + public ObjectListView ListView { + get { return listView; } + set { listView = value; } + } + private ObjectListView listView; + + /// + /// Gets or sets the collection of strings that will be used for + /// prefix matching. Setting this replaces all previous texts + /// of any kind. + /// + public IEnumerable PrefixStrings { + get { + foreach (TextMatchingStrategy component in this.MatchingStrategies) + yield return component.Text; + } + set { + this.MatchingStrategies = new List(); + if (value != null) { + foreach (string text in value) + this.MatchingStrategies.Add(new TextBeginsMatchingStrategy(this, text)); + } + } + } + + /// + /// Gets or sets the options that will be used when compiling the regular expression. + /// + /// + /// This is only used when doing Regex matching (obviously). + /// If this is not set specifically, the appropriate options are chosen to match the + /// StringComparison setting (culture invariant, case sensitive). + /// + public RegexOptions RegexOptions { + get { + if (!regexOptions.HasValue) { + switch (this.StringComparison) { + case StringComparison.CurrentCulture: + regexOptions = RegexOptions.None; + break; + case StringComparison.CurrentCultureIgnoreCase: + regexOptions = RegexOptions.IgnoreCase; + break; + case StringComparison.Ordinal: + case StringComparison.InvariantCulture: + regexOptions = RegexOptions.CultureInvariant; + break; + case StringComparison.OrdinalIgnoreCase: + case StringComparison.InvariantCultureIgnoreCase: + regexOptions = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase; + break; + default: + regexOptions = RegexOptions.None; + break; + } + } + return regexOptions.Value; + } + set { + regexOptions = value; + } + } + private RegexOptions? regexOptions; + + /// + /// Gets or sets the collection of strings that will be used for + /// regex pattern matching. Setting this replaces all previous texts + /// of any kind. + /// + public IEnumerable RegexStrings { + get { + foreach (TextMatchingStrategy component in this.MatchingStrategies) + yield return component.Text; + } + set { + this.MatchingStrategies = new List(); + if (value != null) { + foreach (string text in value) + this.MatchingStrategies.Add(new TextRegexMatchingStrategy(this, text)); + } + } + } + + /// + /// Gets or sets how the filter will match text + /// + public StringComparison StringComparison { + get { return this.stringComparison; } + set { this.stringComparison = value; } + } + private StringComparison stringComparison = StringComparison.InvariantCultureIgnoreCase; + + #endregion + + #region Implementation + + /// + /// Loop over the columns that are being considering by the filter + /// + /// + protected virtual IEnumerable IterateColumns() { + if (this.Columns == null) { + foreach (OLVColumn column in this.ListView.Columns) + yield return column; + } else { + foreach (OLVColumn column in this.Columns) + yield return column; + } + if (this.AdditionalColumns != null) { + foreach (OLVColumn column in this.AdditionalColumns) + yield return column; + } + } + + #endregion + + #region Public interface + + /// + /// Do the actual work of filtering + /// + /// + /// + public override bool Filter(object modelObject) { + if (this.ListView == null || !this.HasComponents) + return true; + + foreach (OLVColumn column in this.IterateColumns()) { + if (column.IsVisible && column.Searchable) { + string[] cellTexts = column.GetSearchValues(modelObject); + if (cellTexts != null && cellTexts.Length > 0) { + foreach (TextMatchingStrategy filter in this.MatchingStrategies) { + if (String.IsNullOrEmpty(filter.Text)) + return true; + foreach (string cellText in cellTexts) { + if (filter.MatchesText(cellText)) + return true; + } + } + } + } + } + + return false; + } + + /// + /// Find all the ways in which this filter matches the given string. + /// + /// This is used by the renderer to decide which bits of + /// the string should be highlighted + /// + /// A list of character ranges indicating the matched substrings + public IEnumerable FindAllMatchedRanges(string cellText) { + List ranges = new List(); + + foreach (TextMatchingStrategy filter in this.MatchingStrategies) { + if (!String.IsNullOrEmpty(filter.Text)) + ranges.AddRange(filter.FindAllMatchedRanges(cellText)); + } + + return ranges; + } + + /// + /// Is the given column one of the columns being used by this filter? + /// + /// + /// + public bool IsIncluded(OLVColumn column) { + if (this.Columns == null) { + return column.ListView == this.ListView; + } + + foreach (OLVColumn x in this.Columns) { + if (x == column) + return true; + } + + return false; + } + + #endregion + + #region Implementation members + + protected List MatchingStrategies = new List(); + + #endregion + + #region Components + + /// + /// Base class for the various types of string matching that TextMatchFilter provides + /// + public abstract class TextMatchingStrategy { + + /// + /// Gets how the filter will match text + /// + public StringComparison StringComparison { + get { return this.TextFilter.StringComparison; } + } + + /// + /// Gets the text filter to which this component belongs + /// + public TextMatchFilter TextFilter { + get { return textFilter; } + set { textFilter = value; } + } + private TextMatchFilter textFilter; + + /// + /// Gets or sets the text that will be matched + /// + public string Text { + get { return this.text; } + set { this.text = value; } + } + private string text; + + /// + /// Find all the ways in which this filter matches the given string. + /// + /// + /// + /// This is used by the renderer to decide which bits of + /// the string should be highlighted. + /// + /// this.Text will not be null or empty when this is called. + /// + /// The text of the cell we want to search + /// A list of character ranges indicating the matched substrings + public abstract IEnumerable FindAllMatchedRanges(string cellText); + + /// + /// Does the given text match the filter + /// + /// + /// this.Text will not be null or empty when this is called. + /// + /// The text of the cell we want to search + /// Return true if the given cellText matches our strategy + public abstract bool MatchesText(string cellText); + } + + /// + /// This component provides text contains matching strategy. + /// + public class TextContainsMatchingStrategy : TextMatchingStrategy { + + /// + /// Create a text contains strategy + /// + /// + /// + public TextContainsMatchingStrategy(TextMatchFilter filter, string text) { + this.TextFilter = filter; + this.Text = text; + } + + /// + /// Does the given text match the filter + /// + /// + /// this.Text will not be null or empty when this is called. + /// + /// The text of the cell we want to search + /// Return true if the given cellText matches our strategy + public override bool MatchesText(string cellText) { + return cellText.IndexOf(this.Text, this.StringComparison) != -1; + } + + /// + /// Find all the ways in which this filter matches the given string. + /// + /// + /// + /// This is used by the renderer to decide which bits of + /// the string should be highlighted. + /// + /// this.Text will not be null or empty when this is called. + /// + /// The text of the cell we want to search + /// A list of character ranges indicating the matched substrings + public override IEnumerable FindAllMatchedRanges(string cellText) { + List ranges = new List(); + + int matchIndex = cellText.IndexOf(this.Text, this.StringComparison); + while (matchIndex != -1) { + ranges.Add(new CharacterRange(matchIndex, this.Text.Length)); + matchIndex = cellText.IndexOf(this.Text, matchIndex + this.Text.Length, this.StringComparison); + } + + return ranges; + } + } + + /// + /// This component provides text begins with matching strategy. + /// + public class TextBeginsMatchingStrategy : TextMatchingStrategy { + + /// + /// Create a text begins strategy + /// + /// + /// + public TextBeginsMatchingStrategy(TextMatchFilter filter, string text) { + this.TextFilter = filter; + this.Text = text; + } + + /// + /// Does the given text match the filter + /// + /// + /// this.Text will not be null or empty when this is called. + /// + /// The text of the cell we want to search + /// Return true if the given cellText matches our strategy + public override bool MatchesText(string cellText) { + return cellText.StartsWith(this.Text, this.StringComparison); + } + + /// + /// Find all the ways in which this filter matches the given string. + /// + /// + /// + /// This is used by the renderer to decide which bits of + /// the string should be highlighted. + /// + /// this.Text will not be null or empty when this is called. + /// + /// The text of the cell we want to search + /// A list of character ranges indicating the matched substrings + public override IEnumerable FindAllMatchedRanges(string cellText) { + List ranges = new List(); + + if (cellText.StartsWith(this.Text, this.StringComparison)) + ranges.Add(new CharacterRange(0, this.Text.Length)); + + return ranges; + } + + } + + /// + /// This component provides regex matching strategy. + /// + public class TextRegexMatchingStrategy : TextMatchingStrategy { + + /// + /// Creates a regex strategy + /// + /// + /// + public TextRegexMatchingStrategy(TextMatchFilter filter, string text) { + this.TextFilter = filter; + this.Text = text; + } + + /// + /// Gets or sets the options that will be used when compiling the regular expression. + /// + public RegexOptions RegexOptions { + get { + return this.TextFilter.RegexOptions; + } + } + + /// + /// Gets or sets a compiled regular expression, based on our current Text and RegexOptions. + /// + /// + /// If Text fails to compile as a regular expression, this will return a Regex object + /// that will match all strings. + /// + protected Regex Regex { + get { + if (this.regex == null) { + try { + this.regex = new Regex(this.Text, this.RegexOptions); + } + catch (ArgumentException) { + this.regex = TextRegexMatchingStrategy.InvalidRegexMarker; + } + } + return this.regex; + } + set { + this.regex = value; + } + } + private Regex regex; + + /// + /// Gets whether or not our current regular expression is a valid regex + /// + protected bool IsRegexInvalid { + get { + return this.Regex == TextRegexMatchingStrategy.InvalidRegexMarker; + } + } + private static Regex InvalidRegexMarker = new Regex(".*"); + + /// + /// Does the given text match the filter + /// + /// + /// this.Text will not be null or empty when this is called. + /// + /// The text of the cell we want to search + /// Return true if the given cellText matches our strategy + public override bool MatchesText(string cellText) { + if (this.IsRegexInvalid) + return true; + return this.Regex.Match(cellText).Success; + } + + /// + /// Find all the ways in which this filter matches the given string. + /// + /// + /// + /// This is used by the renderer to decide which bits of + /// the string should be highlighted. + /// + /// this.Text will not be null or empty when this is called. + /// + /// The text of the cell we want to search + /// A list of character ranges indicating the matched substrings + public override IEnumerable FindAllMatchedRanges(string cellText) { + List ranges = new List(); + + if (!this.IsRegexInvalid) { + foreach (Match match in this.Regex.Matches(cellText)) { + if (match.Length > 0) + ranges.Add(new CharacterRange(match.Index, match.Length)); + } + } + + return ranges; + } + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/FullClassDiagram.cd b/VG Music Studio - WinForms/ObjectListView/FullClassDiagram.cd new file mode 100644 index 0000000..7d0a8dd --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/FullClassDiagram.cd @@ -0,0 +1,1261 @@ + + + + + + AQCABIAAAIAhAACgAAAAAIAMAAAECAAggAIAIIAAAEA= + CellEditing\CellEditKeyEngine.cs + + + + + + AAAAAAAAAAAAAAQEIAAEAAAAAAAAAAAAAAAAAAAAAAA= + CellEditing\CellEditors.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + CellEditing\CellEditors.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + CellEditing\CellEditors.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA= + CellEditing\CellEditors.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA= + CellEditing\CellEditors.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + CellEditing\CellEditors.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAgAAAAAAA= + CellEditing\CellEditors.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA= + CellEditing\CellEditors.cs + + + + + + AAAAAAQIAAABAQAAQAAgAAAAAAABAIAAAAQAAAAAAAA= + CellEditing\EditorRegistry.cs + + + + + + ABgAAAAAAAAAAgIhAAACAAAEAAAAAAAAAAAAAAAAAAA= + DataListView.cs + + + + + + BAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAQAA= + DragDrop\DragSource.cs + + + + + + + BAAAAAAAAAAAAAAAAQAAAAAQAAAQEAAAAAAAAAAAQAA= + DragDrop\DragSource.cs + + + + + + + AAAAAAAAAAAQQAAAAAIAEAAAAAEFAAAAgAAAAMAAAAA= + DragDrop\DropSink.cs + + + + + + + ZS0gAiQEBAoQDA8YQBMAMCAFgwQHBALcAEBEiEAAAQA= + DragDrop\DropSink.cs + + + + + + BYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + DragDrop\DropSink.cs + + + + + + IACgGiAAIBoAAAAAAAAAAAAAAALAAAAKgAQECIAEgAA= + DragDrop\DropSink.cs + + + + + + BAEAAAAAAAAAAAAAAAEQAAAAAAAAAAIAAAAAgAQAAEA= + DragDrop\DropSink.cs + + + + + + AQAAEAEABAAAAAAAEAAAAAAAAAIAAgACgAAAAAAAQBA= + DragDrop\OLVDataObject.cs + + + + + + AAAAAAAAAAAAAgIAAAACAAAEQAAAAAAAAAAAAAAAAAA= + FastDataListView.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEQAAAAAAAAAAA= + FastObjectListView.cs + + + + + + ABAAAAgAQAQAABAgAAASQ4AQAAIEAAAAAAAAAEIAgAA= + FastObjectListView.cs + + + + + + AAAAAAAQAAAAEAQEBAAAAAQAgAAAAIAAAAAAAAAAAAA= + Filtering\Cluster.cs + + + + + + + AAAAAAAEAAAAAAAAAhBABAgAQAAIKAQBAQAAAAAAoIA= + Filtering\ClusteringStrategy.cs + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQBAAAAAAAAAAA= + Filtering\ClustersFromGroupsStrategy.cs + + + + + + AAAAAAIAAAACAAAAAAAACAAAAQgAAAQBAAAAAAAAAAA= + Filtering\DateTimeClusteringStrategy.cs + + + + + + SAEAADAQgEEAAQAIAAgQIAAAAFQAAAAAAAAAoAAAAAE= + Filtering\FilterMenuBuilder.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAA= + Filtering\Filters.cs + + + + + + + AAAAAAAAAEAAAABAAAAAAAAAABAAAAAAAAAAAAAAAAA= + Filtering\Filters.cs + + + + + + + AAAAAAAAAAgAAAAAAIAAAACAABAAAAAAAAAAAAAAAAA= + Filtering\Filters.cs + + + + + + + AAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Filtering\Filters.cs + + + + + + AAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Filtering\Filters.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAA= + Filtering\Filters.cs + + + + + + + AAAAAAAAAAAAAAAAAgAAAAIAABAAAAAAAAAAAAAAAAA= + Filtering\Filters.cs + + + + + + AAAAAAAAAAAAAAAABAAAAAQAABAAAAAAAAAAAAAAAAA= + Filtering\Filters.cs + + + + + + AAAgAAACAAAABAAABAAgAQACABABAAAAyEIAAIgSgAA= + Filtering\TextMatchFilter.cs + + + + + + EgAAQTAAZAEgWGAASFwIIEYECGBGAQKAQEEGAQJAAEE= + Implementation\Attributes.cs + + + + + + AAIAAAAAAAQAAAAAAAAAAAAAQAAAAAAAAABQAAAAAAA= + Implementation\Comparers.cs + + + + + + + AAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAA= + Implementation\Comparers.cs + + + + + + + AAIAAAAAAAQAAAAAAAAAAAAAQAAAAAAAAABQAAAAAAA= + Implementation\Comparers.cs + + + + + + + AZggAAAwAGAAAgACQAYCBCAEICACACUEhAEAQKBAgQg= + Implementation\DataSourceAdapter.cs + + + + + + + 5/7+//3///fX/+//+///f/3//f/37N////+//7+///8= + Implementation\Enums.cs + + + + + + + ASgQyXBAABICBAAAAIAAACCEMAKBQAOAABDAgAUpAQA= + Implementation\Events.cs + + + + + + ABEAEAAFQAAAABAABABQEAQAQAAAECAAAAAgAAAAAAA= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAA= + Implementation\Events.cs + + + + + + AAAABAAAIAAAAAAAAAEAAAQAAAAABAAAAAAAABAAIAA= + Implementation\Events.cs + + + + + + AAQABAAAAAQQAAAAAAEAAAQAAAAABAAAAAAgABQAIAE= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAAEAAAAAAAAAAAAEAAAAAAAAAAAA= + Implementation\Events.cs + + + + + + AAAEAAAAAAAAAAAAAAAEAAAQAAAAAAAAAAAAAAAQAAA= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA= + Implementation\Events.cs + + + + + + AAAAAAAAAAAgAAAAIAAAAAAAAAAAAgAAAAAAAAAAAAA= + Implementation\Events.cs + + + + + + AAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Implementation\Events.cs + + + + + + AAAAQAAAIAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAEA= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAQAAAAA= + Implementation\Events.cs + + + + + + AAAAAAAAMABAAAAQgAZAFAAGUARAAAAAgAgAAIAIAAA= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAAIAAAACAAAAAAAAAAAAAAAAAAAA= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAA= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Implementation\Events.cs + + + + + + CAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA= + Implementation\Events.cs + + + + + + AAAAAQAAAAAAAEGAAAAAAAAAAAgAACAIAAAACAAHAAA= + Implementation\Events.cs + + + + + + AAAgAAAAMAAAAAAAgARABAAEUAQIAAAAiAgAAIAIAAA= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAAABAAAAAQAAAAAAIiAgACIAIAAA= + Implementation\Events.cs + + + + + + AEAAAAAAAAAAAAAAgAQAAAAEAAAAAQAAgAkEAIAAAAA= + Implementation\Events.cs + + + + + + AAAAAAAAEAAAAAAAAABABAAAUAQAAAAAAAgAAAAAAAA= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAACAAAAAAAAAA= + Implementation\Events.cs + + + + + + AAIgAAAGJACRCAAEEABEAAAAJACBAAAAAAgIAAAAAAA= + Implementation\Events.cs + + + + + + QAAAEAAAAAAAABAABABQEAQAQAAAEAAAAAAAAEAAAAA= + Implementation\Events.cs + + + + + + QAAAAEAAAAAAAAAAgAAAAIAAAAAAAAAAAIAAAACAAAA= + Implementation\Events.cs + + + + + + QAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Implementation\Events.cs + + + + + + QAAAgEAACggAgAAIAAAAAEAAAIAAAAAAAAAAAAAAAII= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAgAAAAIAAA= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAgAAAAIAAA= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAgAAAAIAAA= + Implementation\Events.cs + + + + + + AAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAgAAAAIAAA= + Implementation\Events.cs + + + + + + AAAAAAAAIMAAAACBEAEAoQAAAKAACAAUhAAgRIQAIAE= + Implementation\GroupingParameters.cs + + + + + + bEYChOwmAAQgiCQEQBgEAMwAEAAMAEQMgEFAFODAGhM= + Implementation\Groups.cs + + + + + + AAAAgAAAJAAAAAQEABCAAAAAhAAAAACAAAAAAAIAAAE= + Implementation\Munger.cs + + + + + + AAAIACAAJAAAgAAEACAAAAAABAAAIAAAAAEAAAAAEAA= + Implementation\Munger.cs + + + + + + AAEAAAAAAAAAAAAAIAAAACAAAAAAAAAAAAAAAAABAAA= + Implementation\Munger.cs + + + + + + cChxFKmMjlNGI/LfZWKToPLMK45gioYxDANnzL7yfN4= + Implementation\NativeMethods.cs + + + + + + AAEAAAAAAAAEAAAACAAAAAAAQAAAAAAAAAAAAAAAAAA= + Implementation\NullableDictionary.cs + + + + + + ABAAAAAAAABEAACAAAAAAAAQABAAEgAQAAIAASAAAgE= + Implementation\OLVListItem.cs + + + + + + AAAAAAAAAAAEAAAAAAAAAAAAABAIAAAQCAgACSAAAAk= + Implementation\OLVListSubItem.cs + + + + + + AAAAgEAAEAgAAABEAAZABAAGAAQAEAAAgAAAAAAIAAA= + Implementation\OlvListViewHitTestInfo.cs + + + + + + AAAAAAAAAAAAAACAAAAEAAAAAAAAAAAEAAAACAAAAAA= + Implementation\VirtualGroups.cs + + + + + + + AAAAABAAAAAAAACAAAAAAAAAAAAAAAAEAAAACAAAAAA= + Implementation\VirtualGroups.cs + + + + + + AIAAAAAAAAAAAAAAAAAAAgAIAAAQAAAAAAAEAEACAAA= + Implementation\VirtualGroups.cs + + + + + + + ABAAAAAAgAAAAAAgAAASQgAQAAAEAAAAAAAAAIAAgAA= + Implementation\VirtualListDataSource.cs + + + + + + + AABAAAAAAAAAAAAAAABCAAAAAAAEAAAAAAAAAAAAAAA= + Implementation\VirtualListDataSource.cs + + + + + + MgARTxAAJEcEWCbkoNwJbE6WTnSOEnOKQhSWGDJgsFk= + OLVColumn.cs + + + + + + AACgAIAABAAAAIABACCiAIAgAACAAgAAABAIAAAAgAE= + Rendering\Adornments.cs + + + + + + ABAAAAAAAIAAAAAAAEAAAAAAEAAAABAAAEABAAAAAAA= + Rendering\Adornments.cs + + + + + + QAIggAAEIAAgBIAIAAIASEACIABAACIIAAMACCADAsA= + Rendering\Adornments.cs + + + + + + AAEAAAAAAAABAgAAAQAABAAAAAQBAAAAAAAAAAAAAAA= + Rendering\Decorations.cs + + + + + + + AgAAAAAAEAAAAgAAAAAAAAAAAAAAAAAAAAAQAAIAAAA= + Rendering\Decorations.cs + + + + + + YAgAgCABAAAgA4AAAAIAgAAAAAAAAAAAAAIAACBIgAA= + Rendering\Decorations.cs + + + + + + AAAAgAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAABAAIA= + Rendering\Decorations.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA= + Rendering\Decorations.cs + + + + + + QAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAEAAAAA= + Rendering\Decorations.cs + + + + + + AAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Rendering\Decorations.cs + + + + + + AAAAAAAAAAABAgAAAQAABAAAAAQAAAAAAAAAAAAAAAA= + Rendering\Decorations.cs + + + + + + + AAAAAAAAAAABAgAAAQAABAAAAAQAAAAAAAAAAAAAAAA= + Rendering\Decorations.cs + + + + + + + AAAAAAAAAAAAAgAAAAAAAIAAAACAAAAAAAAAAAAAAAA= + Rendering\Overlays.cs + + + + + + + AAAAAAAAAAAAAgAAAAAAABAAAAAQAAAAAAAAAAAAAAA= + Rendering\Overlays.cs + + + + + + + AAAAAAAAAIAAAgAAAAAAABAAAAAQAAAAAAAAAAAAAAA= + Rendering\Overlays.cs + + + + + + + AAAAAAAAAAAAAgAAAAIAAAACAAAAAAAAAAAAAAAAAAA= + Rendering\Overlays.cs + + + + + + AAAAAQAAAABAAAAAABAAAAAAAAAAABAAAAAAAAAAAAA= + Rendering\Renderers.cs + + + + + + + AAABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Rendering\Renderers.cs + + + + + + AxLAQSEQZQhhAmA5IRBAASSQUhQgkFAAgJDGAAMDE8I= + Rendering\Renderers.cs + + + + + + QAggCAEAAEAAAIAwAAKAEAIQABAAEAAAAAAAAAAKAAA= + Rendering\Renderers.cs + + + + + + AAIAEBAAAQAAAAgAABAAEAAAAAAAAAAAABAAAAAAAAA= + Rendering\Renderers.cs + + + + + + AAAAAAQBIAAAAAAAAAAAAAAAAAAAAAAACBAAAAIAAAA= + Rendering\Renderers.cs + + + + + + AAAgAAAAAAAAAAAAAIAAAAAgIAAAAABBABAAAAAEIAA= + Rendering\Renderers.cs + + + + + + AAIAAQAAAAEAAgAAEAIAABAAAAAAAAAAIAEAACADAAA= + Rendering\Styles.cs + + + + + + + AAIAAQAAAAEAAgAAAAIAAAAAAAAAAAAAAAEAAAADAAA= + Rendering\Styles.cs + + + + + + + AAAAAAAAQAiAAAAAgAIAAAAAAAAIBAAgAAAAAAAAAAA= + Rendering\Styles.cs + + + + + + AAIAAQAAAAEAAAAAAAAAAAACACAAAgAgAAEAAAADAAA= + Rendering\Styles.cs + + + + + + AAAAAAAQAAgAAAAAAAAAAICAAIAIAAAABAAAAQAEAAA= + Rendering\Styles.cs + + + + + + BgCkgAAARAoAAEAyEAAAAAIAAAAIAhCAAQIERAgAASA= + SubControls\GlassPanelForm.cs + + + + + + MAAIOQAUABAAAACQiBQCKRgAACECAAoAwAAQxJAACaE= + SubControls\HeaderControl.cs + + + + + + AkAACAgAACCACAAAAAAAAIAAwAAIAAQCAAAAAAAAAAA= + SubControls\ToolStripCheckedListBox.cs + + + + + + CkoAByAwQQAggEvSAAIQiIWIBELAYOIpiAKQUIQDqEA= + SubControls\ToolTipControl.cs + + + + + + AAAAAEAAFDAAQTAUAACIBAoWEAAAAAAoAAAAAIEAAgA= + Utilities\ColumnSelectionForm.cs + + + + + + AAABAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAACAAAAAA= + Utilities\Generator.cs + + + + + + AAAAwABEAAAAAACIAIAQEAABEAAAAAEEggAAAAACQAA= + Utilities\TypedObjectListView.cs + + + + + + AAAACAAAAEAEAABAAEAQCAAAQAAEAEAAAAgAAAAAAAA= + Utilities\TypedObjectListView.cs + + + + + + AVkQQAAAAGIEAQAkKkHAEAgRH4gBgAGOCCEigAAgIAQ= + VirtualObjectListView.cs + + + + + + AAEAAAABACAEAQAEAAAAAAAgAQCAAAAAAAMIQAABEAA= + ObjectListView.DesignTime.cs + + + + + + AAAAAAAAAAAAAABAAAABAAAAAAAAQAAAAAAAAAAAAAA= + ObjectListView.DesignTime.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAIAAAAAAAA= + ObjectListView.DesignTime.cs + + + + + + AAAAAAAAAAAAAAIAAAABEAAAgQAAAAAAAAAAQAIAAIg= + + + + + + AAAAAAAAAAAAAgIAAAACAAEGAAAAAEAAAAAAABAAQAA= + DataTreeListView.cs + + + + + + BAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAQAA= + DragDrop\DragSource.cs + + + + + + AAAAAAAAAAAQQAAAAAIAEAAAAAEFAAAAgAAAAAAAAAA= + DragDrop\DropSink.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAA= + Filtering\Filters.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAA= + Filtering\Filters.cs + + + + + + AAAAAAAQAAAAAAAAAAAAAAQAgAAAAAAAAAAAAAAAAAA= + Filtering\ICluster.cs + + + + + + AAAAAAAAAAAAAAAAABBAAAAAAAAAAAQBAQAAAAAAAAA= + Filtering\IClusteringStrategy.cs + + + + + + AAAAAAAAAAAAAACAAAAEAAAAAAAAAAAEAAAACAAAAAA= + Implementation\VirtualGroups.cs + + + + + + AIAAAAAAAAAAAAAAAAAAAgAIAAAQAAAAAAAEAEAAAAA= + Implementation\VirtualGroups.cs + + + + + + ABAAAAAAAAAAAAAgAAASAgAQAAAEAAAAAAAAAAAAgAA= + Implementation\VirtualListDataSource.cs + + + + + + AAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAA= + Implementation\VirtualListDataSource.cs + + + + + + AAAAAAAAAAAAAAAAAQAAAAAAAAQAAAAAAAAAAAAAAAA= + Rendering\Decorations.cs + + + + + + AAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Rendering\Overlays.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAA= + Rendering\Overlays.cs + + + + + + AAAAAQAAAABAAAAAABAAAAAAAAAAABAAAAAAAAAAAAA= + Rendering\Renderers.cs + + + + + + AAAAAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAADAAA= + Rendering\Styles.cs + + + + + + AAAgAAAACAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAA= + CellEditing\CellEditKeyEngine.cs + + + + + + gAEAAAAQCAIAAAAAAIAAAAAAAUAQIABAAEAgAAAgACA= + CellEditing\CellEditKeyEngine.cs + + + + + + AAAAAQAAAAABAAAAAAAAAAAEAAQBBAAAAAIgAAEAAAA= + DragDrop\DropSink.cs + + + + + + AAAAAAAAJAAAAAAAAAAAAAIAAAAAACIEAAAAAAAAAAA= + Filtering\DateTimeClusteringStrategy.cs + + + + + + AEAAAAAAAAAAABAAEAQAARAAIQAAAAQAAAAAAEAAAAA= + Implementation\Groups.cs + + + + + + AgAAgAAAwABAAAAAAAUAAAIAgQAAAAAEACAgAAAFAAA= + Implementation\Groups.cs + + + + + + AAAAAAQAAAAAAAAAAAAAAQAAAAAAEAAAAIAAAAAAAAA= + Implementation\Groups.cs + + + + + + ASAAEEAAAAAAAAQAEAAAAAAAAAAAABAAAAAACAAAEAA= + Implementation\OlvListViewHitTestInfo.cs + + + + + + AAAEAAAAAAAAAAAAAAAAAAAQAAAABAAAAAAAAAAAAAA= + CellEditing\EditorRegistry.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAgA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAQAAQAAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAgAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAEAAAQAAAAAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAQABgAAAAACAQAAAAAAAAAAAAAAAAAAAAAAgAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAABAAAACAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAABAAAAAAgACAAAAACAAAAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA= + Implementation\Delegates.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAABAAAAAAAAAA= + Implementation\Events.cs + + + + \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/Attributes.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/Attributes.cs new file mode 100644 index 0000000..98e9234 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/Attributes.cs @@ -0,0 +1,335 @@ +/* + * Attributes - Attributes that can be attached to properties of models to allow columns to be + * built from them directly + * + * Author: Phillip Piper + * Date: 15/08/2009 22:01 + * + * Change log: + * v2.6 + * 2012-08-16 JPP - Added [OLVChildren] and [OLVIgnore] + * - OLV attributes can now only be set on properties + * v2.4 + * 2010-04-14 JPP - Allow Name property to be set + * + * v2.3 + * 2009-08-15 JPP - Initial version + * + * To do: + * + * Copyright (C) 2009-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// This attribute is used to mark a property of a model + /// class that should be noticed by Generator class. + /// + /// + /// All the attributes of this class match their equivalent properties on OLVColumn. + /// + [AttributeUsage(AttributeTargets.Property)] + public class OLVColumnAttribute : Attribute + { + #region Constructor + + // There are several property where we actually want nullable value (bool?, int?), + // but it seems attribute properties can't be nullable types. + // So we explicitly track if those properties have been set. + + /// + /// Create a new OLVColumnAttribute + /// + public OLVColumnAttribute() { + } + + /// + /// Create a new OLVColumnAttribute with the given title + /// + /// The title of the column + public OLVColumnAttribute(string title) { + this.Title = title; + } + + #endregion + + #region Public properties + + /// + /// + /// + public string AspectToStringFormat { + get { return aspectToStringFormat; } + set { aspectToStringFormat = value; } + } + private string aspectToStringFormat; + + /// + /// + /// + public bool CheckBoxes { + get { return checkBoxes; } + set { + checkBoxes = value; + this.IsCheckBoxesSet = true; + } + } + private bool checkBoxes; + internal bool IsCheckBoxesSet = false; + + /// + /// + /// + public int DisplayIndex { + get { return displayIndex; } + set { displayIndex = value; } + } + private int displayIndex = -1; + + /// + /// + /// + public bool FillsFreeSpace { + get { return fillsFreeSpace; } + set { fillsFreeSpace = value; } + } + private bool fillsFreeSpace; + + /// + /// + /// + public int FreeSpaceProportion { + get { return freeSpaceProportion; } + set { + freeSpaceProportion = value; + IsFreeSpaceProportionSet = true; + } + } + private int freeSpaceProportion; + internal bool IsFreeSpaceProportionSet = false; + + /// + /// An array of IComparables that mark the cutoff points for values when + /// grouping on this column. + /// + public object[] GroupCutoffs { + get { return groupCutoffs; } + set { groupCutoffs = value; } + } + private object[] groupCutoffs; + + /// + /// + /// + public string[] GroupDescriptions { + get { return groupDescriptions; } + set { groupDescriptions = value; } + } + private string[] groupDescriptions; + + /// + /// + /// + public string GroupWithItemCountFormat { + get { return groupWithItemCountFormat; } + set { groupWithItemCountFormat = value; } + } + private string groupWithItemCountFormat; + + /// + /// + /// + public string GroupWithItemCountSingularFormat { + get { return groupWithItemCountSingularFormat; } + set { groupWithItemCountSingularFormat = value; } + } + private string groupWithItemCountSingularFormat; + + /// + /// + /// + public bool Hyperlink { + get { return hyperlink; } + set { hyperlink = value; } + } + private bool hyperlink; + + /// + /// + /// + public string ImageAspectName { + get { return imageAspectName; } + set { imageAspectName = value; } + } + private string imageAspectName; + + /// + /// + /// + public bool IsEditable { + get { return isEditable; } + set { + isEditable = value; + this.IsEditableSet = true; + } + } + private bool isEditable = true; + internal bool IsEditableSet = false; + + /// + /// + /// + public bool IsVisible { + get { return isVisible; } + set { isVisible = value; } + } + private bool isVisible = true; + + /// + /// + /// + public bool IsTileViewColumn { + get { return isTileViewColumn; } + set { isTileViewColumn = value; } + } + private bool isTileViewColumn; + + /// + /// + /// + public int MaximumWidth { + get { return maximumWidth; } + set { maximumWidth = value; } + } + private int maximumWidth = -1; + + /// + /// + /// + public int MinimumWidth { + get { return minimumWidth; } + set { minimumWidth = value; } + } + private int minimumWidth = -1; + + /// + /// + /// + public String Name { + get { return name; } + set { name = value; } + } + private String name; + + /// + /// + /// + public HorizontalAlignment TextAlign { + get { return this.textAlign; } + set { + this.textAlign = value; + IsTextAlignSet = true; + } + } + private HorizontalAlignment textAlign = HorizontalAlignment.Left; + internal bool IsTextAlignSet = false; + + /// + /// + /// + public String Tag { + get { return tag; } + set { tag = value; } + } + private String tag; + + /// + /// + /// + public String Title { + get { return title; } + set { title = value; } + } + private String title; + + /// + /// + /// + public String ToolTipText { + get { return toolTipText; } + set { toolTipText = value; } + } + private String toolTipText; + + /// + /// + /// + public bool TriStateCheckBoxes { + get { return triStateCheckBoxes; } + set { + triStateCheckBoxes = value; + this.IsTriStateCheckBoxesSet = true; + } + } + private bool triStateCheckBoxes; + internal bool IsTriStateCheckBoxesSet = false; + + /// + /// + /// + public bool UseInitialLetterForGroup { + get { return useInitialLetterForGroup; } + set { useInitialLetterForGroup = value; } + } + private bool useInitialLetterForGroup; + + /// + /// + /// + public int Width { + get { return width; } + set { width = value; } + } + private int width = 150; + + #endregion + } + + /// + /// Properties marked with [OLVChildren] will be used as the children source in a TreeListView. + /// + [AttributeUsage(AttributeTargets.Property)] + public class OLVChildrenAttribute : Attribute + { + + } + + /// + /// Properties marked with [OLVIgnore] will not have columns generated for them. + /// + [AttributeUsage(AttributeTargets.Property)] + public class OLVIgnoreAttribute : Attribute + { + + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/Comparers.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/Comparers.cs new file mode 100644 index 0000000..78bd9fd --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/Comparers.cs @@ -0,0 +1,330 @@ +/* + * Comparers - Various Comparer classes used within ObjectListView + * + * Author: Phillip Piper + * Date: 25/11/2008 17:15 + * + * Change log: + * v2.8.1 + * 2014-12-03 JPP - Added StringComparer + * v2.3 + * 2009-08-24 JPP - Added OLVGroupComparer + * 2009-06-01 JPP - ModelObjectComparer would crash if secondary sort column was null. + * 2008-12-20 JPP - Fixed bug with group comparisons when a group key was null (SF#2445761) + * 2008-11-25 JPP Initial version + * + * TO DO: + * + * Copyright (C) 2006-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// ColumnComparer is the workhorse for all comparison between two values of a particular column. + /// If the column has a specific comparer, use that to compare the values. Otherwise, do + /// a case insensitive string compare of the string representations of the values. + /// + /// This class inherits from both IComparer and its generic counterpart + /// so that it can be used on untyped and typed collections. + /// This is used by normal (non-virtual) ObjectListViews. Virtual lists use + /// ModelObjectComparer + /// + public class ColumnComparer : IComparer, IComparer + { + /// + /// Gets or sets the method that will be used to compare two strings. + /// The default is to compare on the current culture, case-insensitive + /// + public static StringCompareDelegate StringComparer + { + get { return stringComparer; } + set { stringComparer = value; } + } + private static StringCompareDelegate stringComparer; + + /// + /// Create a ColumnComparer that will order the rows in a list view according + /// to the values in a given column + /// + /// The column whose values will be compared + /// The ordering for column values + public ColumnComparer(OLVColumn col, SortOrder order) + { + this.column = col; + this.sortOrder = order; + } + + /// + /// Create a ColumnComparer that will order the rows in a list view according + /// to the values in a given column, and by a secondary column if the primary + /// column is equal. + /// + /// The column whose values will be compared + /// The ordering for column values + /// The column whose values will be compared for secondary sorting + /// The ordering for secondary column values + public ColumnComparer(OLVColumn col, SortOrder order, OLVColumn col2, SortOrder order2) + : this(col, order) + { + // There is no point in secondary sorting on the same column + if (col != col2) + this.secondComparer = new ColumnComparer(col2, order2); + } + + /// + /// Compare two rows + /// + /// row1 + /// row2 + /// An ordering indication: -1, 0, 1 + public int Compare(object x, object y) + { + return this.Compare((OLVListItem)x, (OLVListItem)y); + } + + /// + /// Compare two rows + /// + /// row1 + /// row2 + /// An ordering indication: -1, 0, 1 + public int Compare(OLVListItem x, OLVListItem y) + { + if (this.sortOrder == SortOrder.None) + return 0; + + int result = 0; + object x1 = this.column.GetValue(x.RowObject); + object y1 = this.column.GetValue(y.RowObject); + + // Handle nulls. Null values come last + bool xIsNull = (x1 == null || x1 == System.DBNull.Value); + bool yIsNull = (y1 == null || y1 == System.DBNull.Value); + if (xIsNull || yIsNull) { + if (xIsNull && yIsNull) + result = 0; + else + result = (xIsNull ? -1 : 1); + } else { + result = this.CompareValues(x1, y1); + } + + if (this.sortOrder == SortOrder.Descending) + result = 0 - result; + + // If the result was equality, use the secondary comparer to resolve it + if (result == 0 && this.secondComparer != null) + result = this.secondComparer.Compare(x, y); + + return result; + } + + /// + /// Compare the actual values to be used for sorting + /// + /// The aspect extracted from the first row + /// The aspect extracted from the second row + /// An ordering indication: -1, 0, 1 + public int CompareValues(object x, object y) + { + // Force case insensitive compares on strings + String xAsString = x as String; + if (xAsString != null) + return CompareStrings(xAsString, y as String); + + IComparable comparable = x as IComparable; + return comparable != null ? comparable.CompareTo(y) : 0; + } + + private static int CompareStrings(string x, string y) + { + if (StringComparer == null) + return String.Compare(x, y, StringComparison.CurrentCultureIgnoreCase); + else + return StringComparer(x, y); + } + + private OLVColumn column; + private SortOrder sortOrder; + private ColumnComparer secondComparer; + } + + + /// + /// This comparer sort list view groups. OLVGroups have a "SortValue" property, + /// which is used if present. Otherwise, the titles of the groups will be compared. + /// + public class OLVGroupComparer : IComparer + { + /// + /// Create a group comparer + /// + /// The ordering for column values + public OLVGroupComparer(SortOrder order) { + this.sortOrder = order; + } + + /// + /// Compare the two groups. OLVGroups have a "SortValue" property, + /// which is used if present. Otherwise, the titles of the groups will be compared. + /// + /// group1 + /// group2 + /// An ordering indication: -1, 0, 1 + public int Compare(OLVGroup x, OLVGroup y) { + // If we can compare the sort values, do that. + // Otherwise do a case insensitive compare on the group header. + int result; + if (x.SortValue != null && y.SortValue != null) + result = x.SortValue.CompareTo(y.SortValue); + else + result = String.Compare(x.Header, y.Header, StringComparison.CurrentCultureIgnoreCase); + + if (this.sortOrder == SortOrder.Descending) + result = 0 - result; + + return result; + } + + private SortOrder sortOrder; + } + + /// + /// This comparer can be used to sort a collection of model objects by a given column + /// + /// + /// This is used by virtual ObjectListViews. Non-virtual lists use + /// ColumnComparer + /// + public class ModelObjectComparer : IComparer, IComparer + { + /// + /// Gets or sets the method that will be used to compare two strings. + /// The default is to compare on the current culture, case-insensitive + /// + public static StringCompareDelegate StringComparer + { + get { return stringComparer; } + set { stringComparer = value; } + } + private static StringCompareDelegate stringComparer; + + /// + /// Create a model object comparer + /// + /// + /// + public ModelObjectComparer(OLVColumn col, SortOrder order) + { + this.column = col; + this.sortOrder = order; + } + + /// + /// Create a model object comparer with a secondary sorting column + /// + /// + /// + /// + /// + public ModelObjectComparer(OLVColumn col, SortOrder order, OLVColumn col2, SortOrder order2) + : this(col, order) + { + // There is no point in secondary sorting on the same column + if (col != col2 && col2 != null && order2 != SortOrder.None) + this.secondComparer = new ModelObjectComparer(col2, order2); + } + + /// + /// Compare the two model objects + /// + /// + /// + /// + public int Compare(object x, object y) + { + int result = 0; + object x1 = this.column.GetValue(x); + object y1 = this.column.GetValue(y); + + if (this.sortOrder == SortOrder.None) + return 0; + + // Handle nulls. Null values come last + bool xIsNull = (x1 == null || x1 == System.DBNull.Value); + bool yIsNull = (y1 == null || y1 == System.DBNull.Value); + if (xIsNull || yIsNull) { + if (xIsNull && yIsNull) + result = 0; + else + result = (xIsNull ? -1 : 1); + } else { + result = this.CompareValues(x1, y1); + } + + if (this.sortOrder == SortOrder.Descending) + result = 0 - result; + + // If the result was equality, use the secondary comparer to resolve it + if (result == 0 && this.secondComparer != null) + result = this.secondComparer.Compare(x, y); + + return result; + } + + /// + /// Compare the actual values + /// + /// + /// + /// + public int CompareValues(object x, object y) + { + // Force case insensitive compares on strings + String xStr = x as String; + if (xStr != null) + return CompareStrings(xStr, y as String); + + IComparable comparable = x as IComparable; + return comparable != null ? comparable.CompareTo(y) : 0; + } + + private static int CompareStrings(string x, string y) + { + if (StringComparer == null) + return String.Compare(x, y, StringComparison.CurrentCultureIgnoreCase); + else + return StringComparer(x, y); + } + + private OLVColumn column; + private SortOrder sortOrder; + private ModelObjectComparer secondComparer; + + #region IComparer Members + + #endregion + } + +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/DataSourceAdapter.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/DataSourceAdapter.cs new file mode 100644 index 0000000..642a973 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/DataSourceAdapter.cs @@ -0,0 +1,628 @@ +/* + * DataSourceAdapter - A helper class that translates DataSource events for an ObjectListView + * + * Author: Phillip Piper + * Date: 20/09/2010 7:42 AM + * + * Change log: + * 2018-04-30 JPP - Sanity check upper limit against CurrencyManager rather than ListView just in + * case the CurrencyManager has gotten ahead of the ListView's contents. + * v2.9 + * 2015-10-31 JPP - Put back sanity check on upper limit of source items + * 2015-02-02 JPP - Made CreateColumnsFromSource() only rebuild columns when new ones were added + * v2.8.1 + * 2014-11-23 JPP - Honour initial CurrencyManager.Position when setting DataSource. + * 2014-10-27 JPP - Fix issue where SelectedObject was not sync'ed with CurrencyManager.Position (SF #129) + * v2.6 + * 2012-08-16 JPP - Unify common column creation functionality with Generator when possible + * + * 2010-09-20 JPP - Initial version + * + * Copyright (C) 2010-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.ComponentModel; +using System.Data; +using System.Windows.Forms; +using System.Diagnostics; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// A helper class that translates DataSource events for an ObjectListView + /// + public class DataSourceAdapter : IDisposable + { + #region Life and death + + /// + /// Make a DataSourceAdapter + /// + public DataSourceAdapter(ObjectListView olv) { + if (olv == null) throw new ArgumentNullException("olv"); + + this.ListView = olv; + // ReSharper disable once DoNotCallOverridableMethodsInConstructor + this.BindListView(this.ListView); + } + + /// + /// Finalize this object + /// + ~DataSourceAdapter() { + this.Dispose(false); + } + + /// + /// Release all the resources used by this instance + /// + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Release all the resources used by this instance + /// + public virtual void Dispose(bool fromUser) { + this.UnbindListView(this.ListView); + this.UnbindDataSource(); + } + + #endregion + + #region Public Properties + + /// + /// Gets or sets whether or not columns will be automatically generated to show the + /// columns when the DataSource is set. + /// + /// This must be set before the DataSource is set. It has no effect afterwards. + public bool AutoGenerateColumns { + get { return this.autoGenerateColumns; } + set { this.autoGenerateColumns = value; } + } + private bool autoGenerateColumns = true; + + /// + /// Get or set the DataSource that will be displayed in this list view. + /// + public virtual Object DataSource { + get { return dataSource; } + set { + dataSource = value; + this.RebindDataSource(true); + } + } + private Object dataSource; + + /// + /// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data. + /// + /// If the data source is not a DataSet or DataViewManager, this property has no effect + public virtual string DataMember { + get { return dataMember; } + set { + if (dataMember != value) { + dataMember = value; + RebindDataSource(); + } + } + } + private string dataMember = ""; + + /// + /// Gets the ObjectListView upon which this adaptor will operate + /// + public ObjectListView ListView { + get { return listView; } + internal set { listView = value; } + } + private ObjectListView listView; + + #endregion + + #region Implementation properties + + /// + /// Gets or sets the currency manager which is handling our binding context + /// + protected CurrencyManager CurrencyManager { + get { return currencyManager; } + set { currencyManager = value; } + } + private CurrencyManager currencyManager; + + #endregion + + #region Binding and unbinding + + /// + /// + /// + /// + protected virtual void BindListView(ObjectListView olv) { + if (olv == null) + return; + + olv.Freezing += new EventHandler(HandleListViewFreezing); + olv.SelectionChanged += new EventHandler(HandleListViewSelectionChanged); + olv.BindingContextChanged += new EventHandler(HandleListViewBindingContextChanged); + } + + /// + /// + /// + /// + protected virtual void UnbindListView(ObjectListView olv) { + if (olv == null) + return; + + olv.Freezing -= new EventHandler(HandleListViewFreezing); + olv.SelectionChanged -= new EventHandler(HandleListViewSelectionChanged); + olv.BindingContextChanged -= new EventHandler(HandleListViewBindingContextChanged); + } + + /// + /// + /// + protected virtual void BindDataSource() { + if (this.CurrencyManager == null) + return; + + this.CurrencyManager.MetaDataChanged += new EventHandler(HandleCurrencyManagerMetaDataChanged); + this.CurrencyManager.PositionChanged += new EventHandler(HandleCurrencyManagerPositionChanged); + this.CurrencyManager.ListChanged += new ListChangedEventHandler(CurrencyManagerListChanged); + } + + /// + /// + /// + protected virtual void UnbindDataSource() { + if (this.CurrencyManager == null) + return; + + this.CurrencyManager.MetaDataChanged -= new EventHandler(HandleCurrencyManagerMetaDataChanged); + this.CurrencyManager.PositionChanged -= new EventHandler(HandleCurrencyManagerPositionChanged); + this.CurrencyManager.ListChanged -= new ListChangedEventHandler(CurrencyManagerListChanged); + } + + #endregion + + #region Initialization + + /// + /// Our data source has changed. Figure out how to handle the new source + /// + protected virtual void RebindDataSource() { + RebindDataSource(false); + } + + /// + /// Our data source has changed. Figure out how to handle the new source + /// + protected virtual void RebindDataSource(bool forceDataInitialization) { + + CurrencyManager tempCurrencyManager = null; + if (this.ListView != null && this.ListView.BindingContext != null && this.DataSource != null) { + tempCurrencyManager = this.ListView.BindingContext[this.DataSource, this.DataMember] as CurrencyManager; + } + + // Has our currency manager changed? + if (this.CurrencyManager != tempCurrencyManager) { + this.UnbindDataSource(); + this.CurrencyManager = tempCurrencyManager; + this.BindDataSource(); + + // Our currency manager has changed so we have to initialize a new data source + forceDataInitialization = true; + } + + if (forceDataInitialization) + InitializeDataSource(); + } + + /// + /// The data source for this control has changed. Reconfigure the control for the new source + /// + protected virtual void InitializeDataSource() { + if (this.ListView.Frozen || this.CurrencyManager == null) + return; + + this.CreateColumnsFromSource(); + this.CreateMissingAspectGettersAndPutters(); + this.SetListContents(); + this.ListView.AutoSizeColumns(); + + // Fake a position change event so that the control matches any initial Position + this.HandleCurrencyManagerPositionChanged(null, null); + } + + /// + /// Take the contents of the currently bound list and put them into the control + /// + protected virtual void SetListContents() { + this.ListView.Objects = this.CurrencyManager.List; + } + + /// + /// Create columns for the listview based on what properties are available in the data source + /// + /// + /// This method will create columns if there is not already a column displaying that property. + /// + protected virtual void CreateColumnsFromSource() { + if (this.CurrencyManager == null) + return; + + // Don't generate any columns in design mode. If we do, the user will see them, + // but the Designer won't know about them and won't persist them, which is very confusing + if (this.ListView.IsDesignMode) + return; + + // Don't create columns if we've been told not to + if (!this.AutoGenerateColumns) + return; + + // Use a Generator to create columns + Generator generator = Generator.Instance as Generator ?? new Generator(); + + PropertyDescriptorCollection properties = this.CurrencyManager.GetItemProperties(); + if (properties.Count == 0) + return; + + bool wereColumnsAdded = false; + foreach (PropertyDescriptor property in properties) { + + if (!this.ShouldCreateColumn(property)) + continue; + + // Create a column + OLVColumn column = generator.MakeColumnFromPropertyDescriptor(property); + this.ConfigureColumn(column, property); + + // Add it to our list + this.ListView.AllColumns.Add(column); + wereColumnsAdded = true; + } + + if (wereColumnsAdded) + generator.PostCreateColumns(this.ListView); + } + + /// + /// Decide if a new column should be added to the control to display + /// the given property + /// + /// + /// + protected virtual bool ShouldCreateColumn(PropertyDescriptor property) { + + // Is there a column that already shows this property? If so, we don't show it again + if (this.ListView.AllColumns.Exists(delegate(OLVColumn x) { return x.AspectName == property.Name; })) + return false; + + // Relationships to other tables turn up as IBindibleLists. Don't make columns to show them. + // CHECK: Is this always true? What other things could be here? Constraints? Triggers? + if (property.PropertyType == typeof(IBindingList)) + return false; + + // Ignore anything marked with [OLVIgnore] + return property.Attributes[typeof(OLVIgnoreAttribute)] == null; + } + + /// + /// Configure the given column to show the given property. + /// The title and aspect name of the column are already filled in. + /// + /// + /// + protected virtual void ConfigureColumn(OLVColumn column, PropertyDescriptor property) { + + column.LastDisplayIndex = this.ListView.AllColumns.Count; + + // If our column is a BLOB, it could be an image, so assign a renderer to draw it. + // CONSIDER: Is this a common enough case to warrant this code? + if (property.PropertyType == typeof(System.Byte[])) + column.Renderer = new ImageRenderer(); + } + + /// + /// Generate aspect getters and putters for any columns that are missing them (and for which we have + /// enough information to actually generate a getter) + /// + protected virtual void CreateMissingAspectGettersAndPutters() { + foreach (OLVColumn x in this.ListView.AllColumns) { + OLVColumn column = x; // stack based variable accessible from closures + if (column.AspectGetter == null && !String.IsNullOrEmpty(column.AspectName)) { + column.AspectGetter = delegate(object row) { + // In most cases, rows will be DataRowView objects + DataRowView drv = row as DataRowView; + if (drv == null) + return column.GetAspectByName(row); + return (drv.Row.RowState == DataRowState.Detached) ? null : drv[column.AspectName]; + }; + } + if (column.IsEditable && column.AspectPutter == null && !String.IsNullOrEmpty(column.AspectName)) { + column.AspectPutter = delegate(object row, object newValue) { + // In most cases, rows will be DataRowView objects + DataRowView drv = row as DataRowView; + if (drv == null) + column.PutAspectByName(row, newValue); + else { + if (drv.Row.RowState != DataRowState.Detached) + drv[column.AspectName] = newValue; + } + }; + } + } + } + + #endregion + + #region Event Handlers + + /// + /// CurrencyManager ListChanged event handler. + /// Deals with fine-grained changes to list items. + /// + /// + /// It's actually difficult to deal with these changes in a fine-grained manner. + /// If our listview is grouped, then any change may make a new group appear or + /// an old group disappear. It is rarely enough to simply update the affected row. + /// + /// + /// + protected virtual void CurrencyManagerListChanged(object sender, ListChangedEventArgs e) { + Debug.Assert(sender == this.CurrencyManager); + + // Ignore changes make while frozen, since we will do a complete rebuild when we unfreeze + if (this.ListView.Frozen) + return; + + //System.Diagnostics.Debug.WriteLine(e.ListChangedType); + Stopwatch sw = Stopwatch.StartNew(); + switch (e.ListChangedType) { + + case ListChangedType.Reset: + this.HandleListChangedReset(e); + break; + + case ListChangedType.ItemChanged: + this.HandleListChangedItemChanged(e); + break; + + case ListChangedType.ItemAdded: + this.HandleListChangedItemAdded(e); + break; + + // An item has gone away. + case ListChangedType.ItemDeleted: + this.HandleListChangedItemDeleted(e); + break; + + // An item has changed its index. + case ListChangedType.ItemMoved: + this.HandleListChangedItemMoved(e); + break; + + // Something has changed in the metadata. + // CHECK: When are these events actually fired? + case ListChangedType.PropertyDescriptorAdded: + case ListChangedType.PropertyDescriptorChanged: + case ListChangedType.PropertyDescriptorDeleted: + this.HandleListChangedMetadataChanged(e); + break; + } + sw.Stop(); + System.Diagnostics.Debug.WriteLine(String.Format("PERF - Processing {0} event on {1} rows took {2}ms", e.ListChangedType, this.ListView.GetItemCount(), sw.ElapsedMilliseconds)); + + } + + /// + /// Handle PropertyDescriptor* events + /// + /// + protected virtual void HandleListChangedMetadataChanged(ListChangedEventArgs e) { + this.InitializeDataSource(); + } + + /// + /// Handle ItemMoved event + /// + /// + protected virtual void HandleListChangedItemMoved(ListChangedEventArgs e) { + // When is this actually triggered? + this.InitializeDataSource(); + } + + /// + /// Handle the ItemDeleted event + /// + /// + protected virtual void HandleListChangedItemDeleted(ListChangedEventArgs e) { + this.InitializeDataSource(); + } + + /// + /// Handle an ItemAdded event. + /// + /// + protected virtual void HandleListChangedItemAdded(ListChangedEventArgs e) { + // We get this event twice if certain grid controls are used to add a new row to a + // datatable: once when the editing of a new row begins, and once again when that + // editing commits. (If the user cancels the creation of the new row, we never see + // the second creation.) We detect this by seeing if this is a view on a row in a + // DataTable, and if it is, testing to see if it's a new row under creation. + + Object newRow = this.CurrencyManager.List[e.NewIndex]; + DataRowView drv = newRow as DataRowView; + if (drv == null || !drv.IsNew) { + // Either we're not dealing with a view on a data table, or this is the commit + // notification. Either way, this is the final notification, so we want to + // handle the new row now! + this.InitializeDataSource(); + } + } + + /// + /// Handle the Reset event + /// + /// + protected virtual void HandleListChangedReset(ListChangedEventArgs e) { + // The whole list has changed utterly, so reload it. + this.InitializeDataSource(); + } + + /// + /// Handle ItemChanged event. This is triggered when a single item + /// has changed, so just refresh that one item. + /// + /// + /// Even in this simple case, we should probably rebuild the list. + /// For example, the change could put the item into its own new group. + protected virtual void HandleListChangedItemChanged(ListChangedEventArgs e) { + // A single item has changed, so just refresh that. + //System.Diagnostics.Debug.WriteLine(String.Format("HandleListChangedItemChanged: {0}, {1}", e.NewIndex, e.PropertyDescriptor.Name)); + + Object changedRow = this.CurrencyManager.List[e.NewIndex]; + this.ListView.RefreshObject(changedRow); + } + + /// + /// The CurrencyManager calls this if the data source looks + /// different. We just reload everything. + /// + /// + /// + /// + /// CHECK: Do we need this if we are handle ListChanged metadata events? + /// + protected virtual void HandleCurrencyManagerMetaDataChanged(object sender, EventArgs e) { + this.InitializeDataSource(); + } + + /// + /// Called by the CurrencyManager when the currently selected item + /// changes. We update the ListView selection so that we stay in sync + /// with any other controls bound to the same source. + /// + /// + /// + protected virtual void HandleCurrencyManagerPositionChanged(object sender, EventArgs e) { + int index = this.CurrencyManager.Position; + + // Make sure the index is sane (-1 pops up from time to time) + if (index < 0 || index >= this.CurrencyManager.Count) + return; + + // Avoid recursion. If we are currently changing the index, don't + // start the process again. + if (this.isChangingIndex) + return; + + try { + this.isChangingIndex = true; + this.ChangePosition(index); + } + finally { + this.isChangingIndex = false; + } + } + private bool isChangingIndex = false; + + /// + /// Change the control's position (which is it's currently selected row) + /// to the nth row in the dataset + /// + /// The index of the row to be selected + protected virtual void ChangePosition(int index) { + // We can't use the index directly, since our listview may be sorted + this.ListView.SelectedObject = this.CurrencyManager.List[index]; + + // THINK: Do we always want to bring it into view? + if (this.ListView.SelectedIndices.Count > 0) + this.ListView.EnsureVisible(this.ListView.SelectedIndices[0]); + } + + #endregion + + #region ObjectListView event handlers + + /// + /// Handle the selection changing in our ListView. + /// We need to tell our currency manager about the new position. + /// + /// + /// + protected virtual void HandleListViewSelectionChanged(object sender, EventArgs e) { + // Prevent recursion + if (this.isChangingIndex) + return; + + // Sanity + if (this.CurrencyManager == null) + return; + + // If only one item is selected, tell the currency manager which item is selected. + // CurrencyManager can't handle multiple selection so there's nothing we can do + // if more than one row is selected. + if (this.ListView.SelectedIndices.Count != 1) + return; + + try { + this.isChangingIndex = true; + + // We can't use the selectedIndex directly, since our listview may be sorted and/or filtered + // So we have to find the index of the selected object within the original list. + this.CurrencyManager.Position = this.CurrencyManager.List.IndexOf(this.ListView.SelectedObject); + } finally { + this.isChangingIndex = false; + } + } + + /// + /// Handle the frozenness of our ListView changing. + /// + /// + /// + protected virtual void HandleListViewFreezing(object sender, FreezeEventArgs e) { + if (!alreadyFreezing && e.FreezeLevel == 0) { + try { + alreadyFreezing = true; + this.RebindDataSource(true); + } finally { + alreadyFreezing = false; + } + } + } + private bool alreadyFreezing = false; + + /// + /// Handle a change to the BindingContext of our ListView. + /// + /// + /// + protected virtual void HandleListViewBindingContextChanged(object sender, EventArgs e) { + this.RebindDataSource(false); + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/Delegates.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/Delegates.cs new file mode 100644 index 0000000..55b3dfd --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/Delegates.cs @@ -0,0 +1,168 @@ +/* + * Delegates - All delegate definitions used in ObjectListView + * + * Author: Phillip Piper + * Date: 31-March-2011 5:53 pm + * + * Change log: + * v2.10 + * 2015-12-30 JPP - Added CellRendererGetterDelegate + * v2.? + * 2011-03-31 JPP - Split into its own file + * + * Copyright (C) 2011-2015 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Windows.Forms; +using System.Drawing; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + #region Delegate declarations + + /// + /// These delegates are used to extract an aspect from a row object + /// + public delegate Object AspectGetterDelegate(Object rowObject); + + /// + /// These delegates are used to put a changed value back into a model object + /// + public delegate void AspectPutterDelegate(Object rowObject, Object newValue); + + /// + /// These delegates can be used to convert an aspect value to a display string, + /// instead of using the default ToString() + /// + public delegate string AspectToStringConverterDelegate(Object value); + + /// + /// These delegates are used to get the tooltip for a cell + /// + public delegate String CellToolTipGetterDelegate(OLVColumn column, Object modelObject); + + /// + /// These delegates are used to the state of the checkbox for a row object. + /// + /// + /// For reasons known only to someone in Microsoft, we can only set + /// a boolean on the ListViewItem to indicate it's "checked-ness", but when + /// we receive update events, we have to use a tristate CheckState. So we can + /// be told about an indeterminate state, but we can't set it ourselves. + /// + /// As of version 2.0, we can now return indeterminate state. + /// + public delegate CheckState CheckStateGetterDelegate(Object rowObject); + + /// + /// These delegates are used to get the state of the checkbox for a row object. + /// + /// + /// + public delegate bool BooleanCheckStateGetterDelegate(Object rowObject); + + /// + /// These delegates are used to put a changed check state back into a model object + /// + public delegate CheckState CheckStatePutterDelegate(Object rowObject, CheckState newValue); + + /// + /// These delegates are used to put a changed check state back into a model object + /// + /// + /// + /// + public delegate bool BooleanCheckStatePutterDelegate(Object rowObject, bool newValue); + + /// + /// These delegates are used to get the renderer for a particular cell + /// + public delegate IRenderer CellRendererGetterDelegate(Object rowObject, OLVColumn column); + + /// + /// The callbacks for RightColumnClick events + /// + public delegate void ColumnRightClickEventHandler(object sender, ColumnClickEventArgs e); + + /// + /// This delegate will be used to own draw header column. + /// + public delegate bool HeaderDrawingDelegate(Graphics g, Rectangle r, int columnIndex, OLVColumn column, bool isPressed, HeaderStateStyle stateStyle); + + /// + /// This delegate is called when a group has been created but not yet made + /// into a real ListViewGroup. The user can take this opportunity to fill + /// in lots of other details about the group. + /// + public delegate void GroupFormatterDelegate(OLVGroup group, GroupingParameters parms); + + /// + /// These delegates are used to retrieve the object that is the key of the group to which the given row belongs. + /// + public delegate Object GroupKeyGetterDelegate(Object rowObject); + + /// + /// These delegates are used to convert a group key into a title for the group + /// + public delegate string GroupKeyToTitleConverterDelegate(Object groupKey); + + /// + /// These delegates are used to get the tooltip for a column header + /// + public delegate String HeaderToolTipGetterDelegate(OLVColumn column); + + /// + /// These delegates are used to fetch the image selector that should be used + /// to choose an image for this column. + /// + public delegate Object ImageGetterDelegate(Object rowObject); + + /// + /// These delegates are used to draw a cell + /// + public delegate bool RenderDelegate(EventArgs e, Graphics g, Rectangle r, Object rowObject); + + /// + /// These delegates are used to fetch a row object for virtual lists + /// + public delegate Object RowGetterDelegate(int rowIndex); + + /// + /// These delegates are used to format a listviewitem before it is added to the control. + /// + public delegate void RowFormatterDelegate(OLVListItem olvItem); + + /// + /// These delegates can be used to return the array of texts that should be searched for text filtering + /// + public delegate string[] SearchValueGetterDelegate(Object value); + + /// + /// These delegates are used to sort the listview in some custom fashion + /// + public delegate void SortDelegate(OLVColumn column, SortOrder sortOrder); + + /// + /// These delegates are used to order two strings. + /// x cannot be null. y can be null. + /// + public delegate int StringCompareDelegate(string x, string y); + + #endregion +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/Enums.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/Enums.cs new file mode 100644 index 0000000..e19f31a --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/Enums.cs @@ -0,0 +1,104 @@ +/* + * Enums - All enum definitions used in ObjectListView + * + * Author: Phillip Piper + * Date: 31-March-2011 5:53 pm + * + * Change log: + * 2011-03-31 JPP - Split into its own file + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + public partial class ObjectListView { + /// + /// How does a user indicate that they want to edit cells? + /// + public enum CellEditActivateMode { + /// + /// This list cannot be edited. F2 does nothing. + /// + None = 0, + + /// + /// A single click on a subitem will edit the value. Single clicking the primary column, + /// selects the row just like normal. The user must press F2 to edit the primary column. + /// + SingleClick = 1, + + /// + /// Double clicking a subitem or the primary column will edit that cell. + /// F2 will edit the primary column. + /// + DoubleClick = 2, + + /// + /// Pressing F2 is the only way to edit the cells. Once the primary column is being edited, + /// the other cells in the row can be edited by pressing Tab. + /// + F2Only = 3, + + /// + /// A single click on a any cell will edit the value, even the primary column. + /// + SingleClickAlways = 4, + } + + /// + /// These values specify how column selection will be presented to the user + /// + public enum ColumnSelectBehaviour { + /// + /// No column selection will be presented + /// + None, + + /// + /// The columns will be show in the main menu + /// + InlineMenu, + + /// + /// The columns will be shown in a submenu + /// + Submenu, + + /// + /// A model dialog will be presented to allow the user to choose columns + /// + ModelDialog, + + /* + * NonModelDialog is just a little bit tricky since the OLV can change views while the dialog is showing + * So, just comment this out for the time being. + + /// + /// A non-model dialog will be presented to allow the user to choose columns + /// + NonModelDialog + * + */ + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/Events.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/Events.cs new file mode 100644 index 0000000..610b52f --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/Events.cs @@ -0,0 +1,2514 @@ +/* + * Events - All the events that can be triggered within an ObjectListView. + * + * Author: Phillip Piper + * Date: 17/10/2008 9:15 PM + * + * Change log: + * v2.8.0 + * 2014-05-20 JPP - Added IsHyperlinkEventArgs.IsHyperlink + * v2.6 + * 2012-04-17 JPP - Added group state change and group expansion events + * v2.5 + * 2010-08-08 JPP - CellEdit validation and finish events now have NewValue property. + * v2.4 + * 2010-03-04 JPP - Added filtering events + * v2.3 + * 2009-08-16 JPP - Added group events + * 2009-08-08 JPP - Added HotItem event + * 2009-07-24 JPP - Added Hyperlink events + * - Added Formatting events + * v2.2.1 + * 2009-06-13 JPP - Added Cell events + * - Moved all event parameter blocks to this file. + * - Added Handled property to AfterSearchEventArgs + * v2.2 + * 2009-06-01 JPP - Added ColumnToGroupBy and GroupByOrder to sorting events + - Gave all event descriptions + * 2009-04-23 JPP - Added drag drop events + * v2.1 + * 2009-01-18 JPP - Moved SelectionChanged event to this file + * v2.0 + * 2008-12-06 JPP - Added searching events + * 2008-12-01 JPP - Added secondary sort information to Before/AfterSorting events + * 2008-10-17 JPP - Separated from ObjectListView.cs + * + * Copyright (C) 2006-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + /// + /// The callbacks for CellEditing events + /// + /// this + /// We could replace this with EventHandler<CellEditEventArgs> but that would break all + /// cell editing event code from v1.x. + /// + public delegate void CellEditEventHandler(object sender, CellEditEventArgs e); + + public partial class ObjectListView { + //----------------------------------------------------------------------------------- + + #region Events + + /// + /// Triggered after a ObjectListView has been searched by the user typing into the list + /// + [Category("ObjectListView"), + Description("This event is triggered after the control has done a search-by-typing action.")] + public event EventHandler AfterSearching; + + /// + /// Triggered after a ObjectListView has been sorted + /// + [Category("ObjectListView"), + Description("This event is triggered after the items in the list have been sorted.")] + public event EventHandler AfterSorting; + + /// + /// Triggered before a ObjectListView is searched by the user typing into the list + /// + /// + /// Set Cancelled to true to prevent the searching from taking place. + /// Changing StringToFind or StartSearchFrom will change the subsequent search. + /// + [Category("ObjectListView"), + Description("This event is triggered before the control does a search-by-typing action.")] + public event EventHandler BeforeSearching; + + /// + /// Triggered before a ObjectListView is sorted + /// + /// + /// Set Cancelled to true to prevent the sort from taking place. + /// Changing ColumnToSort or SortOrder will change the subsequent sort. + /// + [Category("ObjectListView"), + Description("This event is triggered before the items in the list are sorted.")] + public event EventHandler BeforeSorting; + + /// + /// Triggered after a ObjectListView has created groups + /// + [Category("ObjectListView"), + Description("This event is triggered after the groups are created.")] + public event EventHandler AfterCreatingGroups; + + /// + /// Triggered before a ObjectListView begins to create groups + /// + /// + /// Set Groups to prevent the default group creation process + /// + [Category("ObjectListView"), + Description("This event is triggered before the groups are created.")] + public event EventHandler BeforeCreatingGroups; + + /// + /// Triggered just before a ObjectListView creates groups + /// + /// + /// You can make changes to the groups, which have been created, before those + /// groups are created within the listview. + /// + [Category("ObjectListView"), + Description("This event is triggered when the groups are just about to be created.")] + public event EventHandler AboutToCreateGroups; + + /// + /// Triggered when a button in a cell is left clicked. + /// + [Category("ObjectListView"), + Description("This event is triggered when the user left clicks a button.")] + public event EventHandler ButtonClick; + + /// + /// This event is triggered when the user moves a drag over an ObjectListView that + /// has a SimpleDropSink installed as the drop handler. + /// + /// + /// Handlers for this event should set the Effect argument and optionally the + /// InfoMsg property. They can also change any of the DropTarget* settings to change + /// the target of the drop. + /// + [Category("ObjectListView"), + Description("Can the user drop the currently dragged items at the current mouse location?")] + public event EventHandler CanDrop; + + /// + /// Triggered when a cell has finished being edited. + /// + [Category("ObjectListView"), + Description("This event is triggered cell edit operation has completely finished")] + public event CellEditEventHandler CellEditFinished; + + /// + /// Triggered when a cell is about to finish being edited. + /// + /// If Cancel is already true, the user is cancelling the edit operation. + /// Set Cancel to true to prevent the value from the cell being written into the model. + /// You cannot prevent the editing from finishing within this event -- you need + /// the CellEditValidating event for that. + [Category("ObjectListView"), + Description("This event is triggered cell edit operation is finishing.")] + public event CellEditEventHandler CellEditFinishing; + + /// + /// Triggered when a cell is about to be edited. + /// + /// Set Cancel to true to prevent the cell being edited. + /// You can change the Control to be something completely different. + [Category("ObjectListView"), + Description("This event is triggered when cell edit is about to begin.")] + public event CellEditEventHandler CellEditStarting; + + /// + /// Triggered when a cell editor needs to be validated + /// + /// + /// If this event is cancelled, focus will remain on the cell editor. + /// + [Category("ObjectListView"), + Description("This event is triggered when a cell editor is about to lose focus and its new contents need to be validated.")] + public event CellEditEventHandler CellEditValidating; + + /// + /// Triggered when a cell is left clicked. + /// + [Category("ObjectListView"), + Description("This event is triggered when the user left clicks a cell.")] + public event EventHandler CellClick; + + /// + /// Triggered when the mouse is above a cell. + /// + [Category("ObjectListView"), + Description("This event is triggered when the mouse is over a cell.")] + public event EventHandler CellOver; + + /// + /// Triggered when a cell is right clicked. + /// + [Category("ObjectListView"), + Description("This event is triggered when the user right clicks a cell.")] + public event EventHandler CellRightClick; + + /// + /// This event is triggered when a cell needs a tool tip. + /// + [Category("ObjectListView"), + Description("This event is triggered when a cell needs a tool tip.")] + public event EventHandler CellToolTipShowing; + + /// + /// This event is triggered when a checkbox is checked/unchecked on a subitem + /// + [Category("ObjectListView"), + Description("This event is triggered when a checkbox is checked/unchecked on a subitem.")] + public event EventHandler SubItemChecking; + + /// + /// Triggered when a column header is right clicked. + /// + [Category("ObjectListView"), + Description("This event is triggered when the user right clicks a column header.")] + public event ColumnRightClickEventHandler ColumnRightClick; + + /// + /// This event is triggered when the user releases a drag over an ObjectListView that + /// has a SimpleDropSink installed as the drop handler. + /// + [Category("ObjectListView"), + Description("This event is triggered when the user dropped items onto the control.")] + public event EventHandler Dropped; + + /// + /// This event is triggered when the control needs to filter its collection of objects. + /// + [Category("ObjectListView"), + Description("This event is triggered when the control needs to filter its collection of objects.")] + public event EventHandler Filter; + + /// + /// This event is triggered when a cell needs to be formatted. + /// + [Category("ObjectListView"), + Description("This event is triggered when a cell needs to be formatted.")] + public event EventHandler FormatCell; + + /// + /// This event is triggered when the frozenness of the control changes. + /// + [Category("ObjectListView"), + Description("This event is triggered when frozenness of the control changes.")] + public event EventHandler Freezing; + + /// + /// This event is triggered when a row needs to be formatted. + /// + [Category("ObjectListView"), + Description("This event is triggered when a row needs to be formatted.")] + public event EventHandler FormatRow; + + /// + /// This event is triggered when a group is about to collapse or expand. + /// This can be cancelled to prevent the expansion. + /// + [Category("ObjectListView"), + Description("This event is triggered when a group is about to collapse or expand.")] + public event EventHandler GroupExpandingCollapsing; + + /// + /// This event is triggered when a group changes state. + /// + [Category("ObjectListView"), + Description("This event is triggered when a group changes state.")] + public event EventHandler GroupStateChanged; + + /// + /// This event is triggered when a header checkbox is changing value + /// + [Category("ObjectListView"), + Description("This event is triggered when a header checkbox changes value.")] + public event EventHandler HeaderCheckBoxChanging; + + /// + /// This event is triggered when a header needs a tool tip. + /// + [Category("ObjectListView"), + Description("This event is triggered when a header needs a tool tip.")] + public event EventHandler HeaderToolTipShowing; + + /// + /// Triggered when the "hot" item changes + /// + [Category("ObjectListView"), + Description("This event is triggered when the hot item changed.")] + public event EventHandler HotItemChanged; + + /// + /// Triggered when a hyperlink cell is clicked. + /// + [Category("ObjectListView"), + Description("This event is triggered when a hyperlink cell is clicked.")] + public event EventHandler HyperlinkClicked; + + /// + /// Triggered when the task text of a group is clicked. + /// + [Category("ObjectListView"), + Description("This event is triggered when the task text of a group is clicked.")] + public event EventHandler GroupTaskClicked; + + /// + /// Is the value in the given cell a hyperlink. + /// + [Category("ObjectListView"), + Description("This event is triggered when the control needs to know if a given cell contains a hyperlink.")] + public event EventHandler IsHyperlink; + + /// + /// Some new objects are about to be added to an ObjectListView. + /// + [Category("ObjectListView"), + Description("This event is triggered when objects are about to be added to the control")] + public event EventHandler ItemsAdding; + + /// + /// The contents of the ObjectListView has changed. + /// + [Category("ObjectListView"), + Description("This event is triggered when the contents of the control have changed.")] + public event EventHandler ItemsChanged; + + /// + /// The contents of the ObjectListView is about to change via a SetObjects call + /// + /// + /// Set Cancelled to true to prevent the contents of the list changing. This does not work with virtual lists. + /// + [Category("ObjectListView"), + Description("This event is triggered when the contents of the control changes.")] + public event EventHandler ItemsChanging; + + /// + /// Some objects are about to be removed from an ObjectListView. + /// + [Category("ObjectListView"), + Description("This event is triggered when objects are removed from the control.")] + public event EventHandler ItemsRemoving; + + /// + /// This event is triggered when the user moves a drag over an ObjectListView that + /// has a SimpleDropSink installed as the drop handler, and when the source control + /// for the drag was an ObjectListView. + /// + /// + /// Handlers for this event should set the Effect argument and optionally the + /// InfoMsg property. They can also change any of the DropTarget* settings to change + /// the target of the drop. + /// + [Category("ObjectListView"), + Description("Can the dragged collection of model objects be dropped at the current mouse location")] + public event EventHandler ModelCanDrop; + + /// + /// This event is triggered when the user releases a drag over an ObjectListView that + /// has a SimpleDropSink installed as the drop handler and when the source control + /// for the drag was an ObjectListView. + /// + [Category("ObjectListView"), + Description("A collection of model objects from a ObjectListView has been dropped on this control")] + public event EventHandler ModelDropped; + + /// + /// This event is triggered once per user action that changes the selection state + /// of one or more rows. + /// + [Category("ObjectListView"), + Description("This event is triggered once per user action that changes the selection state of one or more rows.")] + public event EventHandler SelectionChanged; + + /// + /// This event is triggered when the contents of the ObjectListView has scrolled. + /// + [Category("ObjectListView"), + Description("This event is triggered when the contents of the ObjectListView has scrolled.")] + public event EventHandler Scroll; + + #endregion + + //----------------------------------------------------------------------------------- + + #region OnEvents + + /// + /// + /// + /// + protected virtual void OnAboutToCreateGroups(CreateGroupsEventArgs e) { + if (this.AboutToCreateGroups != null) + this.AboutToCreateGroups(this, e); + } + + /// + /// + /// + /// + protected virtual void OnBeforeCreatingGroups(CreateGroupsEventArgs e) { + if (this.BeforeCreatingGroups != null) + this.BeforeCreatingGroups(this, e); + } + + /// + /// + /// + /// + protected virtual void OnAfterCreatingGroups(CreateGroupsEventArgs e) { + if (this.AfterCreatingGroups != null) + this.AfterCreatingGroups(this, e); + } + + /// + /// + /// + /// + protected virtual void OnAfterSearching(AfterSearchingEventArgs e) { + if (this.AfterSearching != null) + this.AfterSearching(this, e); + } + + /// + /// + /// + /// + protected virtual void OnAfterSorting(AfterSortingEventArgs e) { + if (this.AfterSorting != null) + this.AfterSorting(this, e); + } + + /// + /// + /// + /// + protected virtual void OnBeforeSearching(BeforeSearchingEventArgs e) { + if (this.BeforeSearching != null) + this.BeforeSearching(this, e); + } + + /// + /// + /// + /// + protected virtual void OnBeforeSorting(BeforeSortingEventArgs e) { + if (this.BeforeSorting != null) + this.BeforeSorting(this, e); + } + + /// + /// + /// + /// + protected virtual void OnButtonClick(CellClickEventArgs args) { + if (this.ButtonClick != null) + this.ButtonClick(this, args); + } + + /// + /// + /// + /// + protected virtual void OnCanDrop(OlvDropEventArgs args) { + if (this.CanDrop != null) + this.CanDrop(this, args); + } + + /// + /// + /// + /// + protected virtual void OnCellClick(CellClickEventArgs args) { + if (this.CellClick != null) + this.CellClick(this, args); + } + + /// + /// + /// + /// + protected virtual void OnCellOver(CellOverEventArgs args) { + if (this.CellOver != null) + this.CellOver(this, args); + } + + /// + /// + /// + /// + protected virtual void OnCellRightClick(CellRightClickEventArgs args) { + if (this.CellRightClick != null) + this.CellRightClick(this, args); + } + + /// + /// + /// + /// + protected virtual void OnCellToolTip(ToolTipShowingEventArgs args) { + if (this.CellToolTipShowing != null) + this.CellToolTipShowing(this, args); + } + + /// + /// + /// + /// + protected virtual void OnSubItemChecking(SubItemCheckingEventArgs args) { + if (this.SubItemChecking != null) + this.SubItemChecking(this, args); + } + + /// + /// + /// + /// + protected virtual void OnColumnRightClick(ColumnRightClickEventArgs e) { + if (this.ColumnRightClick != null) + this.ColumnRightClick(this, e); + } + + /// + /// + /// + /// + protected virtual void OnDropped(OlvDropEventArgs args) { + if (this.Dropped != null) + this.Dropped(this, args); + } + + /// + /// + /// + /// + internal protected virtual void OnFilter(FilterEventArgs e) { + if (this.Filter != null) + this.Filter(this, e); + } + + /// + /// + /// + /// + protected virtual void OnFormatCell(FormatCellEventArgs args) { + if (this.FormatCell != null) + this.FormatCell(this, args); + } + + /// + /// + /// + /// + protected virtual void OnFormatRow(FormatRowEventArgs args) { + if (this.FormatRow != null) + this.FormatRow(this, args); + } + + /// + /// + /// + /// + protected virtual void OnFreezing(FreezeEventArgs args) { + if (this.Freezing != null) + this.Freezing(this, args); + } + + /// + /// + /// + /// + protected virtual void OnGroupExpandingCollapsing(GroupExpandingCollapsingEventArgs args) { + if (this.GroupExpandingCollapsing != null) + this.GroupExpandingCollapsing(this, args); + } + + /// + /// + /// + /// + protected virtual void OnGroupStateChanged(GroupStateChangedEventArgs args) { + if (this.GroupStateChanged != null) + this.GroupStateChanged(this, args); + } + + /// + /// + /// + /// + protected virtual void OnHeaderCheckBoxChanging(HeaderCheckBoxChangingEventArgs args) { + if (this.HeaderCheckBoxChanging != null) + this.HeaderCheckBoxChanging(this, args); + } + + /// + /// + /// + /// + protected virtual void OnHeaderToolTip(ToolTipShowingEventArgs args) { + if (this.HeaderToolTipShowing != null) + this.HeaderToolTipShowing(this, args); + } + + /// + /// + /// + /// + protected virtual void OnHotItemChanged(HotItemChangedEventArgs e) { + if (this.HotItemChanged != null) + this.HotItemChanged(this, e); + } + + /// + /// + /// + /// + protected virtual void OnHyperlinkClicked(HyperlinkClickedEventArgs e) { + if (this.HyperlinkClicked != null) + this.HyperlinkClicked(this, e); + } + + /// + /// + /// + /// + protected virtual void OnGroupTaskClicked(GroupTaskClickedEventArgs e) { + if (this.GroupTaskClicked != null) + this.GroupTaskClicked(this, e); + } + + /// + /// + /// + /// + protected virtual void OnIsHyperlink(IsHyperlinkEventArgs e) { + if (this.IsHyperlink != null) + this.IsHyperlink(this, e); + } + + /// + /// + /// + /// + protected virtual void OnItemsAdding(ItemsAddingEventArgs e) { + if (this.ItemsAdding != null) + this.ItemsAdding(this, e); + } + + /// + /// + /// + /// + protected virtual void OnItemsChanged(ItemsChangedEventArgs e) { + if (this.ItemsChanged != null) + this.ItemsChanged(this, e); + } + + /// + /// + /// + /// + protected virtual void OnItemsChanging(ItemsChangingEventArgs e) { + if (this.ItemsChanging != null) + this.ItemsChanging(this, e); + } + + /// + /// + /// + /// + protected virtual void OnItemsRemoving(ItemsRemovingEventArgs e) { + if (this.ItemsRemoving != null) + this.ItemsRemoving(this, e); + } + + /// + /// + /// + /// + protected virtual void OnModelCanDrop(ModelDropEventArgs args) { + if (this.ModelCanDrop != null) + this.ModelCanDrop(this, args); + } + + /// + /// + /// + /// + protected virtual void OnModelDropped(ModelDropEventArgs args) { + if (this.ModelDropped != null) + this.ModelDropped(this, args); + } + + /// + /// + /// + /// + protected virtual void OnSelectionChanged(EventArgs e) { + if (this.SelectionChanged != null) + this.SelectionChanged(this, e); + } + + /// + /// + /// + /// + protected virtual void OnScroll(ScrollEventArgs e) { + if (this.Scroll != null) + this.Scroll(this, e); + } + + + /// + /// Tell the world when a cell is about to be edited. + /// + protected virtual void OnCellEditStarting(CellEditEventArgs e) { + if (this.CellEditStarting != null) + this.CellEditStarting(this, e); + } + + /// + /// Tell the world when a cell is about to finish being edited. + /// + protected virtual void OnCellEditorValidating(CellEditEventArgs e) { + // Hack. ListView is an imperfect control container. It does not manage validation + // perfectly. If the ListView is part of a TabControl, and the cell editor loses + // focus by the user clicking on another tab, the TabControl processes the click + // and switches tabs, even if this Validating event cancels. This results in the + // strange situation where the cell editor is active, but isn't visible. When the + // user switches back to the tab with the ListView, composite controls like spin + // controls, DateTimePicker and ComboBoxes do not work properly. Specifically, + // keyboard input still works fine, but the controls do not respond to mouse + // input. SO, if the validation fails, we have to specifically give focus back to + // the cell editor. (this is the Select() call in the code below). + // But (there is always a 'but'), doing that changes the focus so the cell editor + // triggers another Validating event -- which fails again. From the user's point + // of view, they click away from the cell editor, and the validating code + // complains twice. So we only trigger a Validating event if more than 0.1 seconds + // has elapsed since the last validate event. + // I know it's a hack. I'm very open to hear a neater solution. + + // Also, this timed response stops us from sending a series of validation events + // if the user clicks and holds on the OLV scroll bar. + //System.Diagnostics.Debug.WriteLine(Environment.TickCount - lastValidatingEvent); + if ((Environment.TickCount - lastValidatingEvent) < 100) { + e.Cancel = true; + } else { + lastValidatingEvent = Environment.TickCount; + if (this.CellEditValidating != null) + this.CellEditValidating(this, e); + } + + lastValidatingEvent = Environment.TickCount; + } + + private int lastValidatingEvent = 0; + + /// + /// Tell the world when a cell is about to finish being edited. + /// + protected virtual void OnCellEditFinishing(CellEditEventArgs e) { + if (this.CellEditFinishing != null) + this.CellEditFinishing(this, e); + } + + /// + /// Tell the world when a cell has finished being edited. + /// + protected virtual void OnCellEditFinished(CellEditEventArgs e) { + if (this.CellEditFinished != null) + this.CellEditFinished(this, e); + } + + #endregion + } + + public partial class TreeListView { + + #region Events + + /// + /// This event is triggered when user input requests the expansion of a list item. + /// + [Category("ObjectListView"), + Description("This event is triggered when a branch is about to expand.")] + public event EventHandler Expanding; + + /// + /// This event is triggered when user input requests the collapse of a list item. + /// + [Category("ObjectListView"), + Description("This event is triggered when a branch is about to collapsed.")] + public event EventHandler Collapsing; + + /// + /// This event is triggered after the expansion of a list item due to user input. + /// + [Category("ObjectListView"), + Description("This event is triggered when a branch has been expanded.")] + public event EventHandler Expanded; + + /// + /// This event is triggered after the collapse of a list item due to user input. + /// + [Category("ObjectListView"), + Description("This event is triggered when a branch has been collapsed.")] + public event EventHandler Collapsed; + + #endregion + + #region OnEvents + + /// + /// Trigger the expanding event + /// + /// + protected virtual void OnExpanding(TreeBranchExpandingEventArgs e) { + if (this.Expanding != null) + this.Expanding(this, e); + } + + /// + /// Trigger the collapsing event + /// + /// + protected virtual void OnCollapsing(TreeBranchCollapsingEventArgs e) { + if (this.Collapsing != null) + this.Collapsing(this, e); + } + + /// + /// Trigger the expanded event + /// + /// + protected virtual void OnExpanded(TreeBranchExpandedEventArgs e) { + if (this.Expanded != null) + this.Expanded(this, e); + } + + /// + /// Trigger the collapsed event + /// + /// + protected virtual void OnCollapsed(TreeBranchCollapsedEventArgs e) { + if (this.Collapsed != null) + this.Collapsed(this, e); + } + + #endregion + } + + //----------------------------------------------------------------------------------- + + #region Event Parameter Blocks + + /// + /// Let the world know that a cell edit operation is beginning or ending + /// + public class CellEditEventArgs : EventArgs { + /// + /// Create an event args + /// + /// + /// + /// + /// + /// + public CellEditEventArgs(OLVColumn column, Control control, Rectangle cellBounds, OLVListItem item, int subItemIndex) { + this.Control = control; + this.column = column; + this.cellBounds = cellBounds; + this.listViewItem = item; + this.rowObject = item.RowObject; + this.subItemIndex = subItemIndex; + this.value = column.GetValue(item.RowObject); + } + + /// + /// Change this to true to cancel the cell editing operation. + /// + /// + /// During the CellEditStarting event, setting this to true will prevent the cell from being edited. + /// During the CellEditFinishing event, if this value is already true, this indicates that the user has + /// cancelled the edit operation and that the handler should perform cleanup only. Setting this to true, + /// will prevent the ObjectListView from trying to write the new value into the model object. + /// + public bool Cancel; + + /// + /// During the CellEditStarting event, this can be modified to be the control that you want + /// to edit the value. You must fully configure the control before returning from the event, + /// including its bounds and the value it is showing. + /// During the CellEditFinishing event, you can use this to get the value that the user + /// entered and commit that value to the model. Changing the control during the finishing + /// event has no effect. + /// + public Control Control; + + /// + /// The column of the cell that is going to be or has been edited. + /// + public OLVColumn Column { + get { return this.column; } + } + + private OLVColumn column; + + /// + /// The model object of the row of the cell that is going to be or has been edited. + /// + public Object RowObject { + get { return this.rowObject; } + } + + private Object rowObject; + + /// + /// The listview item of the cell that is going to be or has been edited. + /// + public OLVListItem ListViewItem { + get { return this.listViewItem; } + } + + private OLVListItem listViewItem; + + /// + /// The data value of the cell as it stands in the control. + /// + /// Only validate during Validating and Finishing events. + public Object NewValue { + get { return this.newValue; } + set { this.newValue = value; } + } + + private Object newValue; + + /// + /// The index of the cell that is going to be or has been edited. + /// + public int SubItemIndex { + get { return this.subItemIndex; } + } + + private int subItemIndex; + + /// + /// The data value of the cell before the edit operation began. + /// + public Object Value { + get { return this.value; } + } + + private Object value; + + /// + /// The bounds of the cell that is going to be or has been edited. + /// + public Rectangle CellBounds { + get { return this.cellBounds; } + } + + private Rectangle cellBounds; + + /// + /// Gets or sets whether the control used for editing should be auto matically disposed + /// when the cell edit operation finishes. Defaults to true + /// + /// If the control is expensive to create, you might want to cache it and reuse for + /// for various cells. If so, you don't want ObjectListView to dispose of the control automatically + public bool AutoDispose { + get { return autoDispose; } + set { autoDispose = value; } + } + + private bool autoDispose = true; + } + + /// + /// Event blocks for events that can be cancelled + /// + public class CancellableEventArgs : EventArgs { + /// + /// Has this event been cancelled by the event handler? + /// + public bool Canceled; + } + + /// + /// BeforeSorting + /// + public class BeforeSortingEventArgs : CancellableEventArgs { + /// + /// Create BeforeSortingEventArgs + /// + /// + /// + /// + /// + public BeforeSortingEventArgs(OLVColumn column, SortOrder order, OLVColumn column2, SortOrder order2) { + this.ColumnToGroupBy = column; + this.GroupByOrder = order; + this.ColumnToSort = column; + this.SortOrder = order; + this.SecondaryColumnToSort = column2; + this.SecondarySortOrder = order2; + } + + /// + /// Create BeforeSortingEventArgs + /// + /// + /// + /// + /// + /// + /// + public BeforeSortingEventArgs(OLVColumn groupColumn, SortOrder groupOrder, OLVColumn column, SortOrder order, OLVColumn column2, SortOrder order2) { + this.ColumnToGroupBy = groupColumn; + this.GroupByOrder = groupOrder; + this.ColumnToSort = column; + this.SortOrder = order; + this.SecondaryColumnToSort = column2; + this.SecondarySortOrder = order2; + } + + /// + /// Did the event handler already do the sorting for us? + /// + public bool Handled; + + /// + /// What column will be used for grouping + /// + public OLVColumn ColumnToGroupBy; + + /// + /// How will groups be ordered + /// + public SortOrder GroupByOrder; + + /// + /// What column will be used for sorting + /// + public OLVColumn ColumnToSort; + + /// + /// What order will be used for sorting. None means no sorting. + /// + public SortOrder SortOrder; + + /// + /// What column will be used for secondary sorting? + /// + public OLVColumn SecondaryColumnToSort; + + /// + /// What order will be used for secondary sorting? + /// + public SortOrder SecondarySortOrder; + } + + /// + /// Sorting has just occurred. + /// + public class AfterSortingEventArgs : EventArgs { + /// + /// Create a AfterSortingEventArgs + /// + /// + /// + /// + /// + /// + /// + public AfterSortingEventArgs(OLVColumn groupColumn, SortOrder groupOrder, OLVColumn column, SortOrder order, OLVColumn column2, SortOrder order2) { + this.columnToGroupBy = groupColumn; + this.groupByOrder = groupOrder; + this.columnToSort = column; + this.sortOrder = order; + this.secondaryColumnToSort = column2; + this.secondarySortOrder = order2; + } + + /// + /// Create a AfterSortingEventArgs + /// + /// + public AfterSortingEventArgs(BeforeSortingEventArgs args) { + this.columnToGroupBy = args.ColumnToGroupBy; + this.groupByOrder = args.GroupByOrder; + this.columnToSort = args.ColumnToSort; + this.sortOrder = args.SortOrder; + this.secondaryColumnToSort = args.SecondaryColumnToSort; + this.secondarySortOrder = args.SecondarySortOrder; + } + + /// + /// What column was used for grouping? + /// + public OLVColumn ColumnToGroupBy { + get { return columnToGroupBy; } + } + + private OLVColumn columnToGroupBy; + + /// + /// What ordering was used for grouping? + /// + public SortOrder GroupByOrder { + get { return groupByOrder; } + } + + private SortOrder groupByOrder; + + /// + /// What column was used for sorting? + /// + public OLVColumn ColumnToSort { + get { return columnToSort; } + } + + private OLVColumn columnToSort; + + /// + /// What ordering was used for sorting? + /// + public SortOrder SortOrder { + get { return sortOrder; } + } + + private SortOrder sortOrder; + + /// + /// What column was used for secondary sorting? + /// + public OLVColumn SecondaryColumnToSort { + get { return secondaryColumnToSort; } + } + + private OLVColumn secondaryColumnToSort; + + /// + /// What order was used for secondary sorting? + /// + public SortOrder SecondarySortOrder { + get { return secondarySortOrder; } + } + + private SortOrder secondarySortOrder; + } + + /// + /// This event is triggered when the contents of a list have changed + /// and we want the world to have a chance to filter the list. + /// + public class FilterEventArgs : EventArgs { + /// + /// Create a FilterEventArgs + /// + /// + public FilterEventArgs(IEnumerable objects) { + this.Objects = objects; + } + + /// + /// Gets or sets what objects are being filtered + /// + public IEnumerable Objects; + + /// + /// Gets or sets what objects survived the filtering + /// + public IEnumerable FilteredObjects; + } + + /// + /// This event is triggered after the items in the list have been changed, + /// either through SetObjects, AddObjects or RemoveObjects. + /// + public class ItemsChangedEventArgs : EventArgs { + /// + /// Create a ItemsChangedEventArgs + /// + public ItemsChangedEventArgs() { } + + /// + /// Constructor for this event when used by a virtual list + /// + /// + /// + public ItemsChangedEventArgs(int oldObjectCount, int newObjectCount) { + this.oldObjectCount = oldObjectCount; + this.newObjectCount = newObjectCount; + } + + /// + /// Gets how many items were in the list before it changed + /// + public int OldObjectCount { + get { return oldObjectCount; } + } + + private int oldObjectCount; + + /// + /// Gets how many objects are in the list after the change. + /// + public int NewObjectCount { + get { return newObjectCount; } + } + + private int newObjectCount; + } + + /// + /// This event is triggered by AddObjects before any change has been made to the list. + /// + public class ItemsAddingEventArgs : CancellableEventArgs { + /// + /// Create an ItemsAddingEventArgs + /// + /// + public ItemsAddingEventArgs(ICollection objectsToAdd) { + this.ObjectsToAdd = objectsToAdd; + } + + /// + /// Create an ItemsAddingEventArgs + /// + /// + /// + public ItemsAddingEventArgs(int index, ICollection objectsToAdd) { + this.Index = index; + this.ObjectsToAdd = objectsToAdd; + } + + /// + /// Gets or sets where the collection is going to be inserted. + /// + public int Index; + + /// + /// Gets or sets the objects to be added to the list + /// + public ICollection ObjectsToAdd; + } + + /// + /// This event is triggered by SetObjects before any change has been made to the list. + /// + /// + /// When used with a virtual list, OldObjects will always be null. + /// + public class ItemsChangingEventArgs : CancellableEventArgs { + /// + /// Create ItemsChangingEventArgs + /// + /// + /// + public ItemsChangingEventArgs(IEnumerable oldObjects, IEnumerable newObjects) { + this.oldObjects = oldObjects; + this.NewObjects = newObjects; + } + + /// + /// Gets the objects that were in the list before it change. + /// For virtual lists, this will always be null. + /// + public IEnumerable OldObjects { + get { return oldObjects; } + } + + private IEnumerable oldObjects; + + /// + /// Gets or sets the objects that will be in the list after it changes. + /// + public IEnumerable NewObjects; + } + + /// + /// This event is triggered by RemoveObjects before any change has been made to the list. + /// + public class ItemsRemovingEventArgs : CancellableEventArgs { + /// + /// Create an ItemsRemovingEventArgs + /// + /// + public ItemsRemovingEventArgs(ICollection objectsToRemove) { + this.ObjectsToRemove = objectsToRemove; + } + + /// + /// Gets or sets the objects that will be removed + /// + public ICollection ObjectsToRemove; + } + + /// + /// Triggered after the user types into a list + /// + public class AfterSearchingEventArgs : EventArgs { + /// + /// Create an AfterSearchingEventArgs + /// + /// + /// + public AfterSearchingEventArgs(string stringToFind, int indexSelected) { + this.stringToFind = stringToFind; + this.indexSelected = indexSelected; + } + + /// + /// Gets the string that was actually searched for + /// + public string StringToFind { + get { return this.stringToFind; } + } + + private string stringToFind; + + /// + /// Gets or sets whether an the event handler already handled this event + /// + public bool Handled; + + /// + /// Gets the index of the row that was selected by the search. + /// -1 means that no row was matched + /// + public int IndexSelected { + get { return this.indexSelected; } + } + + private int indexSelected; + } + + /// + /// Triggered when the user types into a list + /// + public class BeforeSearchingEventArgs : CancellableEventArgs { + /// + /// Create BeforeSearchingEventArgs + /// + /// + /// + public BeforeSearchingEventArgs(string stringToFind, int startSearchFrom) { + this.StringToFind = stringToFind; + this.StartSearchFrom = startSearchFrom; + } + + /// + /// Gets or sets the string that will be found by the search routine + /// + /// Modifying this value does not modify the memory of what the user has typed. + /// When the user next presses a character, the search string will revert to what + /// the user has actually typed. + public string StringToFind; + + /// + /// Gets or sets the index of the first row that will be considered to matching. + /// + public int StartSearchFrom; + } + + /// + /// The parameter block when telling the world about a cell based event + /// + public class CellEventArgs : EventArgs { + /// + /// Gets the ObjectListView that is the source of the event + /// + public ObjectListView ListView { + get { return this.listView; } + internal set { this.listView = value; } + } + + private ObjectListView listView; + + /// + /// Gets the model object under the cell + /// + /// This is null for events triggered by the header. + public object Model { + get { return this.model; } + internal set { this.model = value; } + } + + private object model; + + /// + /// Gets the row index of the cell + /// + /// This is -1 for events triggered by the header. + public int RowIndex { + get { return this.rowIndex; } + internal set { this.rowIndex = value; } + } + + private int rowIndex = -1; + + /// + /// Gets the column index of the cell + /// + /// This is -1 when the view is not in details view. + public int ColumnIndex { + get { return this.columnIndex; } + internal set { this.columnIndex = value; } + } + + private int columnIndex = -1; + + /// + /// Gets the column of the cell + /// + /// This is null when the view is not in details view. + public OLVColumn Column { + get { return this.column; } + internal set { this.column = value; } + } + + private OLVColumn column; + + /// + /// Gets the location of the mouse at the time of the event + /// + public Point Location { + get { return this.location; } + internal set { this.location = value; } + } + + private Point location; + + /// + /// Gets the state of the modifier keys at the time of the event + /// + public Keys ModifierKeys { + get { return this.modifierKeys; } + internal set { this.modifierKeys = value; } + } + + private Keys modifierKeys; + + /// + /// Gets the item of the cell + /// + public OLVListItem Item { + get { return item; } + internal set { this.item = value; } + } + + private OLVListItem item; + + /// + /// Gets the subitem of the cell + /// + /// This is null when the view is not in details view and + /// for event triggered by the header + public OLVListSubItem SubItem { + get { return subItem; } + internal set { this.subItem = value; } + } + + private OLVListSubItem subItem; + + /// + /// Gets the HitTest object that determined which cell was hit + /// + public OlvListViewHitTestInfo HitTest { + get { return hitTest; } + internal set { hitTest = value; } + } + + private OlvListViewHitTestInfo hitTest; + + /// + /// Gets or set if this event completely handled. If it was, no further processing + /// will be done for it. + /// + public bool Handled; + } + + /// + /// Tells the world that a cell was clicked + /// + public class CellClickEventArgs : CellEventArgs { + /// + /// Gets or sets the number of clicks associated with this event + /// + public int ClickCount { + get { return this.clickCount; } + set { this.clickCount = value; } + } + + private int clickCount; + } + + /// + /// Tells the world that a cell was right clicked + /// + public class CellRightClickEventArgs : CellEventArgs { + /// + /// Gets or sets the menu that should be displayed as a result of this event. + /// + /// The menu will be positioned at Location, so changing that property changes + /// where the menu will be displayed. + public ContextMenuStrip MenuStrip; + } + + /// + /// Tell the world that the mouse is over a given cell + /// + public class CellOverEventArgs : CellEventArgs { } + + /// + /// Tells the world that the frozen-ness of the ObjectListView has changed. + /// + public class FreezeEventArgs : EventArgs { + /// + /// Make a FreezeEventArgs + /// + /// + public FreezeEventArgs(int freeze) { + this.FreezeLevel = freeze; + } + + /// + /// How frozen is the control? 0 means that the control is unfrozen, + /// more than 0 indicates froze. + /// + public int FreezeLevel { + get { return this.freezeLevel; } + set { this.freezeLevel = value; } + } + + private int freezeLevel; + } + + /// + /// The parameter block when telling the world that a tool tip is about to be shown. + /// + public class ToolTipShowingEventArgs : CellEventArgs { + /// + /// Gets the tooltip control that is triggering the tooltip event + /// + public ToolTipControl ToolTipControl { + get { return this.toolTipControl; } + internal set { this.toolTipControl = value; } + } + + private ToolTipControl toolTipControl; + + /// + /// Gets or sets the text should be shown on the tooltip for this event + /// + /// Setting this to empty or null prevents any tooltip from showing + public string Text; + + /// + /// In what direction should the text for this tooltip be drawn? + /// + public RightToLeft RightToLeft; + + /// + /// Should the tooltip for this event been shown in bubble style? + /// + /// This doesn't work reliable under Vista + public bool? IsBalloon; + + /// + /// What color should be used for the background of the tooltip + /// + /// Setting this does nothing under Vista + public Color? BackColor; + + /// + /// What color should be used for the foreground of the tooltip + /// + /// Setting this does nothing under Vista + public Color? ForeColor; + + /// + /// What string should be used as the title for the tooltip for this event? + /// + public string Title; + + /// + /// Which standard icon should be used for the tooltip for this event + /// + public ToolTipControl.StandardIcons? StandardIcon; + + /// + /// How many milliseconds should the tooltip remain before it automatically + /// disappears. + /// + public int? AutoPopDelay; + + /// + /// What font should be used to draw the text of the tooltip? + /// + public Font Font; + } + + /// + /// Common information to all hyperlink events + /// + public class HyperlinkEventArgs : EventArgs { + //TODO: Unified with CellEventArgs + + /// + /// Gets the ObjectListView that is the source of the event + /// + public ObjectListView ListView { + get { return this.listView; } + internal set { this.listView = value; } + } + + private ObjectListView listView; + + /// + /// Gets the model object under the cell + /// + public object Model { + get { return this.model; } + internal set { this.model = value; } + } + + private object model; + + /// + /// Gets the row index of the cell + /// + public int RowIndex { + get { return this.rowIndex; } + internal set { this.rowIndex = value; } + } + + private int rowIndex = -1; + + /// + /// Gets the column index of the cell + /// + /// This is -1 when the view is not in details view. + public int ColumnIndex { + get { return this.columnIndex; } + internal set { this.columnIndex = value; } + } + + private int columnIndex = -1; + + /// + /// Gets the column of the cell + /// + /// This is null when the view is not in details view. + public OLVColumn Column { + get { return this.column; } + internal set { this.column = value; } + } + + private OLVColumn column; + + /// + /// Gets the item of the cell + /// + public OLVListItem Item { + get { return item; } + internal set { this.item = value; } + } + + private OLVListItem item; + + /// + /// Gets the subitem of the cell + /// + /// This is null when the view is not in details view + public OLVListSubItem SubItem { + get { return subItem; } + internal set { this.subItem = value; } + } + + private OLVListSubItem subItem; + + /// + /// Gets the ObjectListView that is the source of the event + /// + public string Url { + get { return this.url; } + internal set { this.url = value; } + } + + private string url; + + /// + /// Gets or set if this event completely handled. If it was, no further processing + /// will be done for it. + /// + public bool Handled { + get { return handled; } + set { handled = value; } + } + + private bool handled; + + } + + /// + /// + /// + public class IsHyperlinkEventArgs : EventArgs { + /// + /// Gets the ObjectListView that is the source of the event + /// + public ObjectListView ListView { + get { return this.listView; } + internal set { this.listView = value; } + } + + private ObjectListView listView; + + /// + /// Gets the model object under the cell + /// + public object Model { + get { return this.model; } + internal set { this.model = value; } + } + + private object model; + + /// + /// Gets the column of the cell + /// + /// This is null when the view is not in details view. + public OLVColumn Column { + get { return this.column; } + internal set { this.column = value; } + } + + private OLVColumn column; + + /// + /// Gets the text of the cell + /// + public string Text { + get { return this.text; } + internal set { this.text = value; } + } + + private string text; + + /// + /// Gets or sets whether or not this cell is a hyperlink. + /// Defaults to true for enabled rows and false for disabled rows. + /// + public bool IsHyperlink { + get { return this.isHyperlink; } + set { this.isHyperlink = value; } + } + + private bool isHyperlink; + + /// + /// Gets or sets the url that should be invoked when this cell is clicked. + /// + /// Setting this to None or String.Empty means that this cell is not a hyperlink + public string Url; + } + + /// + /// + public class FormatRowEventArgs : EventArgs { + //TODO: Unified with CellEventArgs + + /// + /// Gets the ObjectListView that is the source of the event + /// + public ObjectListView ListView { + get { return this.listView; } + internal set { this.listView = value; } + } + + private ObjectListView listView; + + /// + /// Gets the item of the cell + /// + public OLVListItem Item { + get { return item; } + internal set { this.item = value; } + } + + private OLVListItem item; + + /// + /// Gets the model object under the cell + /// + public object Model { + get { return this.Item.RowObject; } + } + + /// + /// Gets the row index of the cell + /// + public int RowIndex { + get { return this.rowIndex; } + internal set { this.rowIndex = value; } + } + + private int rowIndex = -1; + + /// + /// Gets the display index of the row + /// + public int DisplayIndex { + get { return this.displayIndex; } + internal set { this.displayIndex = value; } + } + + private int displayIndex = -1; + + /// + /// Should events be triggered for each cell in this row? + /// + public bool UseCellFormatEvents { + get { return useCellFormatEvents; } + set { useCellFormatEvents = value; } + } + + private bool useCellFormatEvents; + } + + /// + /// Parameter block for FormatCellEvent + /// + public class FormatCellEventArgs : FormatRowEventArgs { + /// + /// Gets the column index of the cell + /// + /// This is -1 when the view is not in details view. + public int ColumnIndex { + get { return this.columnIndex; } + internal set { this.columnIndex = value; } + } + + private int columnIndex = -1; + + /// + /// Gets the column of the cell + /// + /// This is null when the view is not in details view. + public OLVColumn Column { + get { return this.column; } + internal set { this.column = value; } + } + + private OLVColumn column; + + /// + /// Gets the subitem of the cell + /// + /// This is null when the view is not in details view + public OLVListSubItem SubItem { + get { return subItem; } + internal set { this.subItem = value; } + } + + private OLVListSubItem subItem; + + /// + /// Gets the model value that is being displayed by the cell. + /// + /// This is null when the view is not in details view + public object CellValue { + get { return this.SubItem == null ? null : this.SubItem.ModelValue; } + } + } + + /// + /// The event args when a hyperlink is clicked + /// + public class HyperlinkClickedEventArgs : CellEventArgs { + /// + /// Gets the url that was associated with this cell. + /// + public string Url { + get { return url; } + set { url = value; } + } + + private string url; + + } + + /// + /// The event args when the check box in a column header is changing + /// + public class HeaderCheckBoxChangingEventArgs : CancelEventArgs { + + /// + /// Get the column whose checkbox is changing + /// + public OLVColumn Column { + get { return column; } + internal set { column = value; } + } + + private OLVColumn column; + + /// + /// Get or set the new state that should be used by the column + /// + public CheckState NewCheckState { + get { return newCheckState; } + set { newCheckState = value; } + } + + private CheckState newCheckState; + } + + /// + /// The event args when the hot item changed + /// + public class HotItemChangedEventArgs : EventArgs { + /// + /// Gets or set if this event completely handled. If it was, no further processing + /// will be done for it. + /// + public bool Handled { + get { return handled; } + set { handled = value; } + } + + private bool handled; + + /// + /// Gets the part of the cell that the mouse is over + /// + public HitTestLocation HotCellHitLocation { + get { return newHotCellHitLocation; } + internal set { newHotCellHitLocation = value; } + } + + private HitTestLocation newHotCellHitLocation; + + /// + /// Gets an extended indication of the part of item/subitem/group that the mouse is currently over + /// + public virtual HitTestLocationEx HotCellHitLocationEx { + get { return this.hotCellHitLocationEx; } + internal set { this.hotCellHitLocationEx = value; } + } + + private HitTestLocationEx hotCellHitLocationEx; + + /// + /// Gets the index of the column that the mouse is over + /// + /// In non-details view, this will always be 0. + public int HotColumnIndex { + get { return newHotColumnIndex; } + internal set { newHotColumnIndex = value; } + } + + private int newHotColumnIndex; + + /// + /// Gets the index of the row that the mouse is over + /// + public int HotRowIndex { + get { return newHotRowIndex; } + internal set { newHotRowIndex = value; } + } + + private int newHotRowIndex; + + /// + /// Gets the group that the mouse is over + /// + public OLVGroup HotGroup { + get { return hotGroup; } + internal set { hotGroup = value; } + } + + private OLVGroup hotGroup; + + /// + /// Gets the part of the cell that the mouse used to be over + /// + public HitTestLocation OldHotCellHitLocation { + get { return oldHotCellHitLocation; } + internal set { oldHotCellHitLocation = value; } + } + + private HitTestLocation oldHotCellHitLocation; + + /// + /// Gets an extended indication of the part of item/subitem/group that the mouse used to be over + /// + public virtual HitTestLocationEx OldHotCellHitLocationEx { + get { return this.oldHotCellHitLocationEx; } + internal set { this.oldHotCellHitLocationEx = value; } + } + + private HitTestLocationEx oldHotCellHitLocationEx; + + /// + /// Gets the index of the column that the mouse used to be over + /// + public int OldHotColumnIndex { + get { return oldHotColumnIndex; } + internal set { oldHotColumnIndex = value; } + } + + private int oldHotColumnIndex; + + /// + /// Gets the index of the row that the mouse used to be over + /// + public int OldHotRowIndex { + get { return oldHotRowIndex; } + internal set { oldHotRowIndex = value; } + } + + private int oldHotRowIndex; + + /// + /// Gets the group that the mouse used to be over + /// + public OLVGroup OldHotGroup { + get { return oldHotGroup; } + internal set { oldHotGroup = value; } + } + + private OLVGroup oldHotGroup; + + /// + /// Returns a string that represents the current object. + /// + /// + /// A string that represents the current object. + /// + /// 2 + public override string ToString() { + return string.Format("NewHotCellHitLocation: {0}, HotCellHitLocationEx: {1}, NewHotColumnIndex: {2}, NewHotRowIndex: {3}, HotGroup: {4}", this.newHotCellHitLocation, this.hotCellHitLocationEx, this.newHotColumnIndex, this.newHotRowIndex, this.hotGroup); + } + } + + /// + /// Let the world know that a checkbox on a subitem is changing + /// + public class SubItemCheckingEventArgs : CancellableEventArgs { + /// + /// Create a new event block + /// + /// + /// + /// + /// + /// + public SubItemCheckingEventArgs(OLVColumn column, OLVListItem item, int subItemIndex, CheckState currentValue, CheckState newValue) { + this.column = column; + this.listViewItem = item; + this.subItemIndex = subItemIndex; + this.currentValue = currentValue; + this.newValue = newValue; + } + + /// + /// The column of the cell that is having its checkbox changed. + /// + public OLVColumn Column { + get { return this.column; } + } + + private OLVColumn column; + + /// + /// The model object of the row of the cell that is having its checkbox changed. + /// + public Object RowObject { + get { return this.listViewItem.RowObject; } + } + + /// + /// The listview item of the cell that is having its checkbox changed. + /// + public OLVListItem ListViewItem { + get { return this.listViewItem; } + } + + private OLVListItem listViewItem; + + /// + /// The current check state of the cell. + /// + public CheckState CurrentValue { + get { return this.currentValue; } + } + + private CheckState currentValue; + + /// + /// The proposed new check state of the cell. + /// + public CheckState NewValue { + get { return this.newValue; } + set { this.newValue = value; } + } + + private CheckState newValue; + + /// + /// The index of the cell that is going to be or has been edited. + /// + public int SubItemIndex { + get { return this.subItemIndex; } + } + + private int subItemIndex; + } + + /// + /// This event argument block is used when groups are created for a list. + /// + public class CreateGroupsEventArgs : EventArgs { + /// + /// Create a CreateGroupsEventArgs + /// + /// + public CreateGroupsEventArgs(GroupingParameters parms) { + this.parameters = parms; + } + + /// + /// Gets the settings that control the creation of groups + /// + public GroupingParameters Parameters { + get { return this.parameters; } + } + + private GroupingParameters parameters; + + /// + /// Gets or sets the groups that should be used + /// + public IList Groups { + get { return this.groups; } + set { this.groups = value; } + } + + private IList groups; + + /// + /// Has this event been cancelled by the event handler? + /// + public bool Canceled { + get { return canceled; } + set { canceled = value; } + } + + private bool canceled; + + } + + /// + /// This event argument block is used when the text of a group task is clicked + /// + public class GroupTaskClickedEventArgs : EventArgs { + /// + /// Create a GroupTaskClickedEventArgs + /// + /// + public GroupTaskClickedEventArgs(OLVGroup group) { + this.group = group; + } + + /// + /// Gets which group was clicked + /// + public OLVGroup Group { + get { return this.group; } + } + + private readonly OLVGroup group; + } + + /// + /// This event argument block is used when a group is about to expand or collapse + /// + public class GroupExpandingCollapsingEventArgs : CancellableEventArgs { + /// + /// Create a GroupExpandingCollapsingEventArgs + /// + /// + public GroupExpandingCollapsingEventArgs(OLVGroup group) { + if (group == null) throw new ArgumentNullException("group"); + this.olvGroup = group; + } + + /// + /// Gets which group is expanding/collapsing + /// + public OLVGroup Group { + get { return this.olvGroup; } + } + + private readonly OLVGroup olvGroup; + + /// + /// Gets whether this event is going to expand the group. + /// If this is false, the group must be collapsing. + /// + public bool IsExpanding { + get { return this.Group.Collapsed; } + } + } + + /// + /// This event argument block is used when the state of group has changed (collapsed, selected) + /// + public class GroupStateChangedEventArgs : EventArgs { + /// + /// Create a GroupStateChangedEventArgs + /// + /// + /// + /// + public GroupStateChangedEventArgs(OLVGroup group, GroupState oldState, GroupState newState) { + this.group = group; + this.oldState = oldState; + this.newState = newState; + } + + /// + /// Gets whether the group was collapsed by this event + /// + public bool Collapsed { + get { + return ((oldState & GroupState.LVGS_COLLAPSED) != GroupState.LVGS_COLLAPSED) && + ((newState & GroupState.LVGS_COLLAPSED) == GroupState.LVGS_COLLAPSED); + } + } + + /// + /// Gets whether the group was focused by this event + /// + public bool Focused { + get { + return ((oldState & GroupState.LVGS_FOCUSED) != GroupState.LVGS_FOCUSED) && + ((newState & GroupState.LVGS_FOCUSED) == GroupState.LVGS_FOCUSED); + } + } + + /// + /// Gets whether the group was selected by this event + /// + public bool Selected { + get { + return ((oldState & GroupState.LVGS_SELECTED) != GroupState.LVGS_SELECTED) && + ((newState & GroupState.LVGS_SELECTED) == GroupState.LVGS_SELECTED); + } + } + + /// + /// Gets whether the group was uncollapsed by this event + /// + public bool Uncollapsed { + get { + return ((oldState & GroupState.LVGS_COLLAPSED) == GroupState.LVGS_COLLAPSED) && + ((newState & GroupState.LVGS_COLLAPSED) != GroupState.LVGS_COLLAPSED); + } + } + + /// + /// Gets whether the group was unfocused by this event + /// + public bool Unfocused { + get { + return ((oldState & GroupState.LVGS_FOCUSED) == GroupState.LVGS_FOCUSED) && + ((newState & GroupState.LVGS_FOCUSED) != GroupState.LVGS_FOCUSED); + } + } + + /// + /// Gets whether the group was unselected by this event + /// + public bool Unselected { + get { + return ((oldState & GroupState.LVGS_SELECTED) == GroupState.LVGS_SELECTED) && + ((newState & GroupState.LVGS_SELECTED) != GroupState.LVGS_SELECTED); + } + } + + /// + /// Gets which group had its state changed + /// + public OLVGroup Group { + get { return this.group; } + } + + private readonly OLVGroup group; + + /// + /// Gets the previous state of the group + /// + public GroupState OldState { + get { return this.oldState; } + } + + private readonly GroupState oldState; + + + /// + /// Gets the new state of the group + /// + public GroupState NewState { + get { return this.newState; } + } + + private readonly GroupState newState; + } + + /// + /// This event argument block is used when a branch of a tree is about to be expanded + /// + public class TreeBranchExpandingEventArgs : CancellableEventArgs { + /// + /// Create a new event args + /// + /// + /// + public TreeBranchExpandingEventArgs(object model, OLVListItem item) { + this.Model = model; + this.Item = item; + } + + /// + /// Gets the model that is about to expand. If null, all branches are going to be expanded. + /// + public object Model { + get { return model; } + private set { model = value; } + } + + private object model; + + /// + /// Gets the OLVListItem that is about to be expanded + /// + public OLVListItem Item { + get { return item; } + private set { item = value; } + } + + private OLVListItem item; + + } + + /// + /// This event argument block is used when a branch of a tree has just been expanded + /// + public class TreeBranchExpandedEventArgs : EventArgs { + /// + /// Create a new event args + /// + /// + /// + public TreeBranchExpandedEventArgs(object model, OLVListItem item) { + this.Model = model; + this.Item = item; + } + + /// + /// Gets the model that is was expanded. If null, all branches were expanded. + /// + public object Model { + get { return model; } + private set { model = value; } + } + + private object model; + + /// + /// Gets the OLVListItem that was expanded + /// + public OLVListItem Item { + get { return item; } + private set { item = value; } + } + + private OLVListItem item; + + } + + /// + /// This event argument block is used when a branch of a tree is about to be collapsed + /// + public class TreeBranchCollapsingEventArgs : CancellableEventArgs { + /// + /// Create a new event args + /// + /// + /// + public TreeBranchCollapsingEventArgs(object model, OLVListItem item) { + this.Model = model; + this.Item = item; + } + + /// + /// Gets the model that is about to collapse. If this is null, all models are going to collapse. + /// + public object Model { + get { return model; } + private set { model = value; } + } + + private object model; + + /// + /// Gets the OLVListItem that is about to be collapsed. Can be null + /// + public OLVListItem Item { + get { return item; } + private set { item = value; } + } + + private OLVListItem item; + } + + + /// + /// This event argument block is used when a branch of a tree has just been collapsed + /// + public class TreeBranchCollapsedEventArgs : EventArgs { + /// + /// Create a new event args + /// + /// + /// + public TreeBranchCollapsedEventArgs(object model, OLVListItem item) { + this.Model = model; + this.Item = item; + } + + /// + /// Gets the model that is was collapsed. If null, all branches were collapsed + /// + public object Model { + get { return model; } + private set { model = value; } + } + + private object model; + + /// + /// Gets the OLVListItem that was collapsed + /// + public OLVListItem Item { + get { return item; } + private set { item = value; } + } + + private OLVListItem item; + + } + + /// + /// Tells the world that a column header was right clicked + /// + public class ColumnRightClickEventArgs : ColumnClickEventArgs { + public ColumnRightClickEventArgs(int columnIndex, ToolStripDropDown menu, Point location) : base(columnIndex) { + MenuStrip = menu; + Location = location; + } + + /// + /// Set this to true to cancel the right click operation. + /// + public bool Cancel; + + /// + /// Gets or sets the menu that should be displayed as a result of this event. + /// + /// The menu will be positioned at Location, so changing that property changes + /// where the menu will be displayed. + public ToolStripDropDown MenuStrip; + + /// + /// Gets the location of the mouse at the time of the event + /// + public Point Location; + } + + #endregion +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/GroupingParameters.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/GroupingParameters.cs new file mode 100644 index 0000000..2eddf88 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/GroupingParameters.cs @@ -0,0 +1,204 @@ +/* + * GroupingParameters - All the data that is used to create groups in an ObjectListView + * + * Author: Phillip Piper + * Date: 31-March-2011 5:53 pm + * + * Change log: + * 2011-03-31 JPP - Split into its own file + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// This class contains all the settings used when groups are created + /// + public class GroupingParameters { + /// + /// Create a GroupingParameters + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public GroupingParameters(ObjectListView olv, OLVColumn groupByColumn, SortOrder groupByOrder, + OLVColumn column, SortOrder order, OLVColumn secondaryColumn, SortOrder secondaryOrder, + string titleFormat, string titleSingularFormat, bool sortItemsByPrimaryColumn) { + this.ListView = olv; + this.GroupByColumn = groupByColumn; + this.GroupByOrder = groupByOrder; + this.PrimarySort = column; + this.PrimarySortOrder = order; + this.SecondarySort = secondaryColumn; + this.SecondarySortOrder = secondaryOrder; + this.SortItemsByPrimaryColumn = sortItemsByPrimaryColumn; + this.TitleFormat = titleFormat; + this.TitleSingularFormat = titleSingularFormat; + } + + /// + /// Gets or sets the ObjectListView being grouped + /// + public ObjectListView ListView { + get { return this.listView; } + set { this.listView = value; } + } + private ObjectListView listView; + + /// + /// Gets or sets the column used to create groups + /// + public OLVColumn GroupByColumn { + get { return this.groupByColumn; } + set { this.groupByColumn = value; } + } + private OLVColumn groupByColumn; + + /// + /// In what order will the groups themselves be sorted? + /// + public SortOrder GroupByOrder { + get { return this.groupByOrder; } + set { this.groupByOrder = value; } + } + private SortOrder groupByOrder; + + /// + /// If this is set, this comparer will be used to order the groups + /// + public IComparer GroupComparer { + get { return this.groupComparer; } + set { this.groupComparer = value; } + } + private IComparer groupComparer; + + /// + /// If this is set, this comparer will be used to order items within each group + /// + public IComparer ItemComparer { + get { return this.itemComparer; } + set { this.itemComparer = value; } + } + private IComparer itemComparer; + + /// + /// Gets or sets the column that will be the primary sort + /// + public OLVColumn PrimarySort { + get { return this.primarySort; } + set { this.primarySort = value; } + } + private OLVColumn primarySort; + + /// + /// Gets or sets the ordering for the primary sort + /// + public SortOrder PrimarySortOrder { + get { return this.primarySortOrder; } + set { this.primarySortOrder = value; } + } + private SortOrder primarySortOrder; + + /// + /// Gets or sets the column used for secondary sorting + /// + public OLVColumn SecondarySort { + get { return this.secondarySort; } + set { this.secondarySort = value; } + } + private OLVColumn secondarySort; + + /// + /// Gets or sets the ordering for the secondary sort + /// + public SortOrder SecondarySortOrder { + get { return this.secondarySortOrder; } + set { this.secondarySortOrder = value; } + } + private SortOrder secondarySortOrder; + + /// + /// Gets or sets the title format used for groups with zero or more than one element + /// + public string TitleFormat { + get { return this.titleFormat; } + set { this.titleFormat = value; } + } + private string titleFormat; + + /// + /// Gets or sets the title format used for groups with only one element + /// + public string TitleSingularFormat { + get { return this.titleSingularFormat; } + set { this.titleSingularFormat = value; } + } + private string titleSingularFormat; + + /// + /// Gets or sets whether the items should be sorted by the primary column + /// + public bool SortItemsByPrimaryColumn { + get { return this.sortItemsByPrimaryColumn; } + set { this.sortItemsByPrimaryColumn = value; } + } + private bool sortItemsByPrimaryColumn; + + /// + /// Create an OLVGroup for the given information + /// + /// + /// + /// + /// + public OLVGroup CreateGroup(object key, int count, bool hasCollapsibleGroups) { + string title = GroupByColumn.ConvertGroupKeyToTitle(key); + if (!String.IsNullOrEmpty(TitleFormat)) + { + string format = (count == 1 ? TitleSingularFormat : TitleFormat); + try + { + title = String.Format(format, title, count); + } + catch (FormatException) + { + title = "Invalid group format: " + format; + } + } + OLVGroup lvg = new OLVGroup(title); + lvg.Column = GroupByColumn; + lvg.Collapsible = hasCollapsibleGroups; + lvg.Key = key; + lvg.SortValue = key as IComparable; + return lvg; + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/Groups.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/Groups.cs new file mode 100644 index 0000000..19ed0d9 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/Groups.cs @@ -0,0 +1,761 @@ +/* + * Groups - Enhancements to the normal ListViewGroup + * + * Author: Phillip Piper + * Date: 22/08/2009 6:03PM + * + * Change log: + * v2.3 + * 2009-09-09 JPP - Added Collapsed and Collapsible properties + * 2009-09-01 JPP - Cleaned up code, added more docs + * - Works under VS2005 again + * 2009-08-22 JPP - Initial version + * + * To do: + * - Implement subseting + * - Implement footer items + * + * Copyright (C) 2009-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Windows.Forms; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// These values indicate what is the state of the group. These values + /// are taken directly from the SDK and many are not used by ObjectListView. + /// + [Flags] + public enum GroupState + { + /// + /// Normal + /// + LVGS_NORMAL = 0x0, + + /// + /// Collapsed + /// + LVGS_COLLAPSED = 0x1, + + /// + /// Hidden + /// + LVGS_HIDDEN = 0x2, + + /// + /// NoHeader + /// + LVGS_NOHEADER = 0x4, + + /// + /// Can be collapsed + /// + LVGS_COLLAPSIBLE = 0x8, + + /// + /// Has focus + /// + LVGS_FOCUSED = 0x10, + + /// + /// Is Selected + /// + LVGS_SELECTED = 0x20, + + /// + /// Is subsetted + /// + LVGS_SUBSETED = 0x40, + + /// + /// Subset link has focus + /// + LVGS_SUBSETLINKFOCUSED = 0x80, + + /// + /// All styles + /// + LVGS_ALL = 0xFFFF + } + + /// + /// This mask indicates which members of a LVGROUP have valid data. These values + /// are taken directly from the SDK and many are not used by ObjectListView. + /// + [Flags] + public enum GroupMask + { + /// + /// No mask + /// + LVGF_NONE = 0, + + /// + /// Group has header + /// + LVGF_HEADER = 1, + + /// + /// Group has footer + /// + LVGF_FOOTER = 2, + + /// + /// Group has state + /// + LVGF_STATE = 4, + + /// + /// + /// + LVGF_ALIGN = 8, + + /// + /// + /// + LVGF_GROUPID = 0x10, + + /// + /// pszSubtitle is valid + /// + LVGF_SUBTITLE = 0x00100, + + /// + /// pszTask is valid + /// + LVGF_TASK = 0x00200, + + /// + /// pszDescriptionTop is valid + /// + LVGF_DESCRIPTIONTOP = 0x00400, + + /// + /// pszDescriptionBottom is valid + /// + LVGF_DESCRIPTIONBOTTOM = 0x00800, + + /// + /// iTitleImage is valid + /// + LVGF_TITLEIMAGE = 0x01000, + + /// + /// iExtendedImage is valid + /// + LVGF_EXTENDEDIMAGE = 0x02000, + + /// + /// iFirstItem and cItems are valid + /// + LVGF_ITEMS = 0x04000, + + /// + /// pszSubsetTitle is valid + /// + LVGF_SUBSET = 0x08000, + + /// + /// readonly, cItems holds count of items in visible subset, iFirstItem is valid + /// + LVGF_SUBSETITEMS = 0x10000 + } + + /// + /// This mask indicates which members of a GROUPMETRICS structure are valid + /// + [Flags] + public enum GroupMetricsMask + { + /// + /// + /// + LVGMF_NONE = 0, + + /// + /// + /// + LVGMF_BORDERSIZE = 1, + + /// + /// + /// + LVGMF_BORDERCOLOR = 2, + + /// + /// + /// + LVGMF_TEXTCOLOR = 4 + } + + /// + /// Instances of this class enhance the capabilities of a normal ListViewGroup, + /// enabling the functionality that was released in v6 of the common controls. + /// + /// + /// + /// In this implementation (2009-09), these objects are essentially passive. + /// Setting properties does not automatically change the associated group in + /// the listview. Collapsed and Collapsible are two exceptions to this and + /// give immediate results. + /// + /// + /// This really should be a subclass of ListViewGroup, but that class is + /// sealed (why is that?). So this class provides the same interface as a + /// ListViewGroup, plus many other new properties. + /// + /// + public class OLVGroup + { + #region Creation + + /// + /// Create an OLVGroup + /// + public OLVGroup() : this("Default group header") { + } + + /// + /// Create a group with the given title + /// + /// Title of the group + public OLVGroup(string header) { + this.Header = header; + this.Id = OLVGroup.nextId++; + this.TitleImage = -1; + this.ExtendedImage = -1; + } + private static int nextId; + + #endregion + + #region Public properties + + /// + /// Gets or sets the bottom description of the group + /// + /// + /// + /// Descriptions only appear when group is centered and there is a title image + /// + /// + /// THIS PROPERTY IS CURRENTLY NOT USED. + /// + /// + public string BottomDescription { + get { return this.bottomDescription; } + set { this.bottomDescription = value; } + } + private string bottomDescription; + + /// + /// Gets or sets whether or not this group is collapsed + /// + public bool Collapsed { + get { return this.GetOneState(GroupState.LVGS_COLLAPSED); } + set { this.SetOneState(value, GroupState.LVGS_COLLAPSED); } + } + + /// + /// Gets or sets whether or not this group can be collapsed + /// + public bool Collapsible { + get { return this.GetOneState(GroupState.LVGS_COLLAPSIBLE); } + set { this.SetOneState(value, GroupState.LVGS_COLLAPSIBLE); } + } + + /// + /// Gets or sets the column that was used to construct this group. + /// + public OLVColumn Column { + get { return this.column; } + set { this.column = value; } + } + private OLVColumn column; + + /// + /// Gets or sets some representation of the contents of this group + /// + /// This is user defined (like Tag) + public IList Contents { + get { return this.contents; } + set { this.contents = value; } + } + private IList contents; + + /// + /// Gets whether this group has been created. + /// + public bool Created { + get { return this.ListView != null; } + } + + /// + /// Gets or sets the int or string that will select the extended image to be shown against the title + /// + public object ExtendedImage { + get { return this.extendedImage; } + set { this.extendedImage = value; } + } + private object extendedImage; + + /// + /// Gets or sets the footer of the group + /// + public string Footer { + get { return this.footer; } + set { this.footer = value; } + } + private string footer; + + /// + /// Gets the internal id of our associated ListViewGroup. + /// + public int GroupId { + get { + if (this.ListViewGroup == null) + return this.Id; + + // Use reflection to get around the access control on the ID property + if (OLVGroup.groupIdPropInfo == null) { + OLVGroup.groupIdPropInfo = typeof(ListViewGroup).GetProperty("ID", + BindingFlags.NonPublic | BindingFlags.Instance); + System.Diagnostics.Debug.Assert(OLVGroup.groupIdPropInfo != null); + } + + int? groupId = OLVGroup.groupIdPropInfo.GetValue(this.ListViewGroup, null) as int?; + return groupId.HasValue ? groupId.Value : -1; + } + } + private static PropertyInfo groupIdPropInfo; + + /// + /// Gets or sets the header of the group + /// + public string Header { + get { return this.header; } + set { this.header = value; } + } + private string header; + + /// + /// Gets or sets the horizontal alignment of the group header + /// + public HorizontalAlignment HeaderAlignment { + get { return this.headerAlignment; } + set { this.headerAlignment = value; } + } + private HorizontalAlignment headerAlignment; + + /// + /// Gets or sets the internally created id of the group + /// + public int Id { + get { return this.id; } + set { this.id = value; } + } + private int id; + + /// + /// Gets or sets ListViewItems that are members of this group + /// + /// Listener of the BeforeCreatingGroups event can populate this collection. + /// It is only used on non-virtual lists. + public IList Items { + get { return this.items; } + set { this.items = value; } + } + private IList items = new List(); + + /// + /// Gets or sets the key that was used to partition objects into this group + /// + /// This is user defined (like Tag) + public object Key { + get { return this.key; } + set { this.key = value; } + } + private object key; + + /// + /// Gets the ObjectListView that this group belongs to + /// + /// If this is null, the group has not yet been created. + public ObjectListView ListView { + get { return this.listView; } + protected set { this.listView = value; } + } + private ObjectListView listView; + + /// + /// Gets or sets the name of the group + /// + /// As of 2009-09-01, this property is not used. + public string Name { + get { return this.name; } + set { this.name = value; } + } + private string name; + + /// + /// Gets or sets whether this group is focused + /// + public bool Focused + { + get { return this.GetOneState(GroupState.LVGS_FOCUSED); } + set { this.SetOneState(value, GroupState.LVGS_FOCUSED); } + } + + /// + /// Gets or sets whether this group is selected + /// + public bool Selected + { + get { return this.GetOneState(GroupState.LVGS_SELECTED); } + set { this.SetOneState(value, GroupState.LVGS_SELECTED); } + } + + /// + /// Gets or sets the text that will show that this group is subsetted + /// + /// + /// As of WinSDK v7.0, subsetting of group is officially unimplemented. + /// We can get around this using undocumented interfaces and may do so. + /// + public string SubsetTitle { + get { return this.subsetTitle; } + set { this.subsetTitle = value; } + } + private string subsetTitle; + + /// + /// Gets or set the subtitleof the task + /// + public string Subtitle { + get { return this.subtitle; } + set { this.subtitle = value; } + } + private string subtitle; + + /// + /// Gets or sets the value by which this group will be sorted. + /// + public IComparable SortValue { + get { return this.sortValue; } + set { this.sortValue = value; } + } + private IComparable sortValue; + + /// + /// Gets or sets the state of the group + /// + public GroupState State { + get { return this.state; } + set { this.state = value; } + } + private GroupState state; + + /// + /// Gets or sets which bits of State are valid + /// + public GroupState StateMask { + get { return this.stateMask; } + set { this.stateMask = value; } + } + private GroupState stateMask; + + /// + /// Gets or sets whether this group is showing only a subset of its elements + /// + /// + /// As of WinSDK v7.0, this property officially does nothing. + /// + public bool Subseted { + get { return this.GetOneState(GroupState.LVGS_SUBSETED); } + set { this.SetOneState(value, GroupState.LVGS_SUBSETED); } + } + + /// + /// Gets or sets the user-defined data attached to this group + /// + public object Tag { + get { return this.tag; } + set { this.tag = value; } + } + private object tag; + + /// + /// Gets or sets the task of this group + /// + /// This task is the clickable text that appears on the right margin + /// of the group header. + public string Task { + get { return this.task; } + set { this.task = value; } + } + private string task; + + /// + /// Gets or sets the int or string that will select the image to be shown against the title + /// + public object TitleImage { + get { return this.titleImage; } + set { this.titleImage = value; } + } + private object titleImage; + + /// + /// Gets or sets the top description of the group + /// + /// + /// Descriptions only appear when group is centered and there is a title image + /// + public string TopDescription { + get { return this.topDescription; } + set { this.topDescription = value; } + } + private string topDescription; + + /// + /// Gets or sets the number of items that are within this group. + /// + /// This should only be used for virtual groups. + public int VirtualItemCount { + get { return this.virtualItemCount; } + set { this.virtualItemCount = value; } + } + private int virtualItemCount; + + #endregion + + #region Protected properties + + /// + /// Gets or sets the ListViewGroup that is shadowed by this group. + /// + /// For virtual groups, this will always be null. + protected ListViewGroup ListViewGroup { + get { return this.listViewGroup; } + set { this.listViewGroup = value; } + } + private ListViewGroup listViewGroup; + #endregion + + #region Calculations/Conversions + + /// + /// Calculate the index into the group image list of the given image selector + /// + /// + /// + public int GetImageIndex(object imageSelector) { + if (imageSelector == null || this.ListView == null || this.ListView.GroupImageList == null) + return -1; + + if (imageSelector is Int32) + return (int)imageSelector; + + String imageSelectorAsString = imageSelector as String; + if (imageSelectorAsString != null) + return this.ListView.GroupImageList.Images.IndexOfKey(imageSelectorAsString); + + return -1; + } + + /// + /// Convert this object to a string representation + /// + /// + public override string ToString() { + return this.Header; + } + + #endregion + + #region Commands + + /// + /// Insert a native group into the underlying Windows control, + /// *without* using a ListViewGroup + /// + /// + /// This is used when creating virtual groups + public void InsertGroupNewStyle(ObjectListView olv) { + this.ListView = olv; + NativeMethods.InsertGroup(olv, this.AsNativeGroup(true)); + } + + /// + /// Insert a native group into the underlying control via a ListViewGroup + /// + /// + public void InsertGroupOldStyle(ObjectListView olv) { + this.ListView = olv; + + // Create/update the associated ListViewGroup + if (this.ListViewGroup == null) + this.ListViewGroup = new ListViewGroup(); + this.ListViewGroup.Header = this.Header; + this.ListViewGroup.HeaderAlignment = this.HeaderAlignment; + this.ListViewGroup.Name = this.Name; + + // Remember which OLVGroup created the ListViewGroup + this.ListViewGroup.Tag = this; + + // Add the group to the control + olv.Groups.Add(this.ListViewGroup); + + // Add any extra information + NativeMethods.SetGroupInfo(olv, this.GroupId, this.AsNativeGroup(false)); + } + + /// + /// Change the members of the group to match the current contents of Items, + /// using a ListViewGroup + /// + public void SetItemsOldStyle() { + List list = this.Items as List; + if (list == null) { + foreach (OLVListItem item in this.Items) { + this.ListViewGroup.Items.Add(item); + } + } else { + this.ListViewGroup.Items.AddRange(list.ToArray()); + } + } + + #endregion + + #region Implementation + + /// + /// Create a native LVGROUP structure that matches this group + /// + internal NativeMethods.LVGROUP2 AsNativeGroup(bool withId) { + + NativeMethods.LVGROUP2 group = new NativeMethods.LVGROUP2(); + group.cbSize = (uint)Marshal.SizeOf(typeof(NativeMethods.LVGROUP2)); + group.mask = (uint)(GroupMask.LVGF_HEADER ^ GroupMask.LVGF_ALIGN ^ GroupMask.LVGF_STATE); + group.pszHeader = this.Header; + group.uAlign = (uint)this.HeaderAlignment; + group.stateMask = (uint)this.StateMask; + group.state = (uint)this.State; + + if (withId) { + group.iGroupId = this.GroupId; + group.mask ^= (uint)GroupMask.LVGF_GROUPID; + } + + if (!String.IsNullOrEmpty(this.Footer)) { + group.pszFooter = this.Footer; + group.mask ^= (uint)GroupMask.LVGF_FOOTER; + } + + if (!String.IsNullOrEmpty(this.Subtitle)) { + group.pszSubtitle = this.Subtitle; + group.mask ^= (uint)GroupMask.LVGF_SUBTITLE; + } + + if (!String.IsNullOrEmpty(this.Task)) { + group.pszTask = this.Task; + group.mask ^= (uint)GroupMask.LVGF_TASK; + } + + if (!String.IsNullOrEmpty(this.TopDescription)) { + group.pszDescriptionTop = this.TopDescription; + group.mask ^= (uint)GroupMask.LVGF_DESCRIPTIONTOP; + } + + if (!String.IsNullOrEmpty(this.BottomDescription)) { + group.pszDescriptionBottom = this.BottomDescription; + group.mask ^= (uint)GroupMask.LVGF_DESCRIPTIONBOTTOM; + } + + int imageIndex = this.GetImageIndex(this.TitleImage); + if (imageIndex >= 0) { + group.iTitleImage = imageIndex; + group.mask ^= (uint)GroupMask.LVGF_TITLEIMAGE; + } + + imageIndex = this.GetImageIndex(this.ExtendedImage); + if (imageIndex >= 0) { + group.iExtendedImage = imageIndex; + group.mask ^= (uint)GroupMask.LVGF_EXTENDEDIMAGE; + } + + if (!String.IsNullOrEmpty(this.SubsetTitle)) { + group.pszSubsetTitle = this.SubsetTitle; + group.mask ^= (uint)GroupMask.LVGF_SUBSET; + } + + if (this.VirtualItemCount > 0) { + group.cItems = this.VirtualItemCount; + group.mask ^= (uint)GroupMask.LVGF_ITEMS; + } + + return group; + } + + private bool GetOneState(GroupState mask) { + if (this.Created) + this.State = this.GetState(); + return (this.State & mask) == mask; + } + + /// + /// Get the current state of this group from the underlying control + /// + protected GroupState GetState() { + return NativeMethods.GetGroupState(this.ListView, this.GroupId, GroupState.LVGS_ALL); + } + + /// + /// Get the current state of this group from the underlying control + /// + protected int SetState(GroupState newState, GroupState mask) { + NativeMethods.LVGROUP2 group = new NativeMethods.LVGROUP2(); + group.cbSize = ((uint)Marshal.SizeOf(typeof(NativeMethods.LVGROUP2))); + group.mask = (uint)GroupMask.LVGF_STATE; + group.state = (uint)newState; + group.stateMask = (uint)mask; + return NativeMethods.SetGroupInfo(this.ListView, this.GroupId, group); + } + + private void SetOneState(bool value, GroupState mask) + { + this.StateMask ^= mask; + if (value) + this.State ^= mask; + else + this.State &= ~mask; + + if (this.Created) + this.SetState(this.State, mask); + } + + #endregion + + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/Munger.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/Munger.cs new file mode 100644 index 0000000..5b197f7 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/Munger.cs @@ -0,0 +1,568 @@ +/* + * Munger - An Interface pattern on getting and setting values from object through Reflection + * + * Author: Phillip Piper + * Date: 28/11/2008 17:15 + * + * Change log: + * v2.5.1 + * 2012-05-01 JPP - Added IgnoreMissingAspects property + * v2.5 + * 2011-05-20 JPP - Accessing through an indexer when the target had both a integer and + * a string indexer didn't work reliably. + * v2.4.1 + * 2010-08-10 JPP - Refactored into Munger/SimpleMunger. 3x faster! + * v2.3 + * 2009-02-15 JPP - Made Munger a public class + * 2009-01-20 JPP - Made the Munger capable of handling indexed access. + * Incidentally, this removed the ugliness that the last change introduced. + * 2009-01-18 JPP - Handle target objects from a DataListView (normally DataRowViews) + * v2.0 + * 2008-11-28 JPP Initial version + * + * TO DO: + * + * Copyright (C) 2006-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// An instance of Munger gets a value from or puts a value into a target object. The property + /// to be peeked (or poked) is determined from a string. The peeking or poking is done using reflection. + /// + /// + /// Name of the aspect to be peeked can be a field, property or parameterless method. The name of an + /// aspect to poke can be a field, writable property or single parameter method. + /// + /// Aspect names can be dotted to chain a series of references. + /// + /// Order.Customer.HomeAddress.State + /// + public class Munger + { + #region Life and death + + /// + /// Create a do nothing Munger + /// + public Munger() + { + } + + /// + /// Create a Munger that works on the given aspect name + /// + /// The name of the + public Munger(String aspectName) + { + this.AspectName = aspectName; + } + + #endregion + + #region Static utility methods + + /// + /// A helper method to put the given value into the given aspect of the given object. + /// + /// This method catches and silently ignores any errors that occur + /// while modifying the target object + /// The object to be modified + /// The name of the property/field to be modified + /// The value to be assigned + /// Did the modification work? + public static bool PutProperty(object target, string propertyName, object value) { + try { + Munger munger = new Munger(propertyName); + return munger.PutValue(target, value); + } + catch (MungerException) { + // Not a lot we can do about this. Something went wrong in the bowels + // of the property. Let's take the ostrich approach and just ignore it :-) + + // Normally, we would never just silently ignore an exception. + // However, in this case, this is a utility method that explicitly + // contracts to catch and ignore errors. If this is not acceptable, + // the programmer should not use this method. + } + + return false; + } + + /// + /// Gets or sets whether Mungers will silently ignore missing aspect errors. + /// + /// + /// + /// By default, if a Munger is asked to fetch a field/property/method + /// that does not exist from a model, it returns an error message, since that + /// condition is normally a programming error. There are some use cases where + /// this is not an error, and the munger should simply keep quiet. + /// + /// By default this is true during release builds. + /// + public static bool IgnoreMissingAspects { + get { return ignoreMissingAspects; } + set { ignoreMissingAspects = value; } + } + private static bool ignoreMissingAspects +#if !DEBUG + = true +#endif + ; + + #endregion + + #region Public properties + + /// + /// The name of the aspect that is to be peeked or poked. + /// + /// + /// + /// This name can be a field, property or parameter-less method. + /// + /// + /// The name can be dotted, which chains references. If any link in the chain returns + /// null, the entire chain is considered to return null. + /// + /// + /// "DateOfBirth" + /// "Owner.HomeAddress.Postcode" + public string AspectName + { + get { return aspectName; } + set { + aspectName = value; + + // Clear any cache + aspectParts = null; + } + } + private string aspectName; + + #endregion + + + #region Public interface + + /// + /// Extract the value indicated by our AspectName from the given target. + /// + /// If the aspect name is null or empty, this will return null. + /// The object that will be peeked + /// The value read from the target + public Object GetValue(Object target) { + if (this.Parts.Count == 0) + return null; + + try { + return this.EvaluateParts(target, this.Parts); + } catch (MungerException ex) { + if (Munger.IgnoreMissingAspects) + return null; + + return String.Format("'{0}' is not a parameter-less method, property or field of type '{1}'", + ex.Munger.AspectName, ex.Target.GetType()); + } + } + + /// + /// Extract the value indicated by our AspectName from the given target, raising exceptions + /// if the munger fails. + /// + /// If the aspect name is null or empty, this will return null. + /// The object that will be peeked + /// The value read from the target + public Object GetValueEx(Object target) { + if (this.Parts.Count == 0) + return null; + + return this.EvaluateParts(target, this.Parts); + } + + /// + /// Poke the given value into the given target indicated by our AspectName. + /// + /// + /// + /// If the AspectName is a dotted path, all the selectors bar the last + /// are used to find the object that should be updated, and the last + /// selector is used as the property to update on that object. + /// + /// + /// So, if 'target' is a Person and the AspectName is "HomeAddress.Postcode", + /// this method will first fetch "HomeAddress" property, and then try to set the + /// "Postcode" property on the home address object. + /// + /// + /// The object that will be poked + /// The value that will be poked into the target + /// bool indicating whether the put worked + public bool PutValue(Object target, Object value) + { + if (this.Parts.Count == 0) + return false; + + SimpleMunger lastPart = this.Parts[this.Parts.Count - 1]; + + if (this.Parts.Count > 1) { + List parts = new List(this.Parts); + parts.RemoveAt(parts.Count - 1); + try { + target = this.EvaluateParts(target, parts); + } catch (MungerException ex) { + this.ReportPutValueException(ex); + return false; + } + } + + if (target != null) { + try { + return lastPart.PutValue(target, value); + } catch (MungerException ex) { + this.ReportPutValueException(ex); + } + } + + return false; + } + + #endregion + + #region Implementation + + /// + /// Gets the list of SimpleMungers that match our AspectName + /// + private IList Parts { + get { + if (aspectParts == null) + aspectParts = BuildParts(this.AspectName); + return aspectParts; + } + } + private IList aspectParts; + + /// + /// Convert a possibly dotted AspectName into a list of SimpleMungers + /// + /// + /// + private IList BuildParts(string aspect) { + List parts = new List(); + if (!String.IsNullOrEmpty(aspect)) { + foreach (string part in aspect.Split('.')) { + parts.Add(new SimpleMunger(part.Trim())); + } + } + return parts; + } + + /// + /// Evaluate the given chain of SimpleMungers against an initial target. + /// + /// + /// + /// + private object EvaluateParts(object target, IList parts) { + foreach (SimpleMunger part in parts) { + if (target == null) + break; + target = part.GetValue(target); + } + return target; + } + + private void ReportPutValueException(MungerException ex) { + //TODO: How should we report this error? + System.Diagnostics.Debug.WriteLine("PutValue failed"); + System.Diagnostics.Debug.WriteLine(String.Format("- Culprit aspect: {0}", ex.Munger.AspectName)); + System.Diagnostics.Debug.WriteLine(String.Format("- Target: {0} of type {1}", ex.Target, ex.Target.GetType())); + System.Diagnostics.Debug.WriteLine(String.Format("- Inner exception: {0}", ex.InnerException)); + } + + #endregion + } + + /// + /// A SimpleMunger deals with a single property/field/method on its target. + /// + /// + /// Munger uses a chain of these resolve a dotted aspect name. + /// + public class SimpleMunger + { + #region Life and death + + /// + /// Create a SimpleMunger + /// + /// + public SimpleMunger(String aspectName) + { + this.aspectName = aspectName; + } + + #endregion + + #region Public properties + + /// + /// The name of the aspect that is to be peeked or poked. + /// + /// + /// + /// This name can be a field, property or method. + /// When using a method to get a value, the method must be parameter-less. + /// When using a method to set a value, the method must accept 1 parameter. + /// + /// + /// It cannot be a dotted name. + /// + /// + public string AspectName { + get { return aspectName; } + } + private readonly string aspectName; + + #endregion + + #region Public interface + + /// + /// Get a value from the given target + /// + /// + /// + public Object GetValue(Object target) { + if (target == null) + return null; + + this.ResolveName(target, this.AspectName, 0); + + try { + if (this.resolvedPropertyInfo != null) + return this.resolvedPropertyInfo.GetValue(target, null); + + if (this.resolvedMethodInfo != null) + return this.resolvedMethodInfo.Invoke(target, null); + + if (this.resolvedFieldInfo != null) + return this.resolvedFieldInfo.GetValue(target); + + // If that didn't work, try to use the indexer property. + // This covers things like dictionaries and DataRows. + if (this.indexerPropertyInfo != null) + return this.indexerPropertyInfo.GetValue(target, new object[] { this.AspectName }); + } catch (Exception ex) { + // Lots of things can do wrong in these invocations + throw new MungerException(this, target, ex); + } + + // If we get to here, we couldn't find a match for the aspect + throw new MungerException(this, target, new MissingMethodException()); + } + + /// + /// Poke the given value into the given target indicated by our AspectName. + /// + /// The object that will be poked + /// The value that will be poked into the target + /// bool indicating if the put worked + public bool PutValue(object target, object value) { + if (target == null) + return false; + + this.ResolveName(target, this.AspectName, 1); + + try { + if (this.resolvedPropertyInfo != null) { + this.resolvedPropertyInfo.SetValue(target, value, null); + return true; + } + + if (this.resolvedMethodInfo != null) { + this.resolvedMethodInfo.Invoke(target, new object[] { value }); + return true; + } + + if (this.resolvedFieldInfo != null) { + this.resolvedFieldInfo.SetValue(target, value); + return true; + } + + // If that didn't work, try to use the indexer property. + // This covers things like dictionaries and DataRows. + if (this.indexerPropertyInfo != null) { + this.indexerPropertyInfo.SetValue(target, value, new object[] { this.AspectName }); + return true; + } + } catch (Exception ex) { + // Lots of things can do wrong in these invocations + throw new MungerException(this, target, ex); + } + + return false; + } + + #endregion + + #region Implementation + + private void ResolveName(object target, string name, int numberMethodParameters) { + + if (cachedTargetType == target.GetType() && cachedName == name && cachedNumberParameters == numberMethodParameters) + return; + + cachedTargetType = target.GetType(); + cachedName = name; + cachedNumberParameters = numberMethodParameters; + + resolvedFieldInfo = null; + resolvedPropertyInfo = null; + resolvedMethodInfo = null; + indexerPropertyInfo = null; + + const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance /*| BindingFlags.NonPublic*/; + + foreach (PropertyInfo pinfo in target.GetType().GetProperties(flags)) { + if (pinfo.Name == name) { + resolvedPropertyInfo = pinfo; + return; + } + + // See if we can find an string indexer property while we are here. + // We also need to allow for old style keyed collections. + if (indexerPropertyInfo == null && pinfo.Name == "Item") { + ParameterInfo[] par = pinfo.GetGetMethod().GetParameters(); + if (par.Length > 0) { + Type parameterType = par[0].ParameterType; + if (parameterType == typeof(string) || parameterType == typeof(object)) + indexerPropertyInfo = pinfo; + } + } + } + + foreach (FieldInfo info in target.GetType().GetFields(flags)) { + if (info.Name == name) { + resolvedFieldInfo = info; + return; + } + } + + foreach (MethodInfo info in target.GetType().GetMethods(flags)) { + if (info.Name == name && info.GetParameters().Length == numberMethodParameters) { + resolvedMethodInfo = info; + return; + } + } + } + + private Type cachedTargetType; + private string cachedName; + private int cachedNumberParameters; + + private FieldInfo resolvedFieldInfo; + private PropertyInfo resolvedPropertyInfo; + private MethodInfo resolvedMethodInfo; + private PropertyInfo indexerPropertyInfo; + + #endregion + } + + /// + /// These exceptions are raised when a munger finds something it cannot process + /// + public class MungerException : ApplicationException + { + /// + /// Create a MungerException + /// + /// + /// + /// + public MungerException(SimpleMunger munger, object target, Exception ex) + : base("Munger failed", ex) { + this.munger = munger; + this.target = target; + } + + /// + /// Get the munger that raised the exception + /// + public SimpleMunger Munger { + get { return munger; } + } + private readonly SimpleMunger munger; + + /// + /// Gets the target that threw the exception + /// + public object Target { + get { return target; } + } + private readonly object target; + } + + /* + * We don't currently need this + * 2010-08-06 + * + + internal class SimpleBinder : Binder + { + public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value, System.Globalization.CultureInfo culture) { + //return Type.DefaultBinder.BindToField( + throw new NotImplementedException(); + } + + public override object ChangeType(object value, Type type, System.Globalization.CultureInfo culture) { + throw new NotImplementedException(); + } + + public override MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] names, out object state) { + throw new NotImplementedException(); + } + + public override void ReorderArgumentArray(ref object[] args, object state) { + throw new NotImplementedException(); + } + + public override MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers) { + throw new NotImplementedException(); + } + + public override PropertyInfo SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type returnType, Type[] indexes, ParameterModifier[] modifiers) { + if (match == null) + throw new ArgumentNullException("match"); + + if (match.Length == 0) + return null; + + return match[0]; + } + } + */ + +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/NativeMethods.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/NativeMethods.cs new file mode 100644 index 0000000..ef97fd7 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/NativeMethods.cs @@ -0,0 +1,1223 @@ +/* + * NativeMethods - All the Windows SDK structures and imports + * + * Author: Phillip Piper + * Date: 10/10/2006 + * + * Change log: + * v2.8.0 + * 2014-05-21 JPP - Added DeselectOneItem + * - Added new imagelist drawing + * v2.3 + * 2006-10-10 JPP - Initial version + * + * To do: + * + * Copyright (C) 2006-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// Wrapper for all native method calls on ListView controls + /// + internal static class NativeMethods + { + #region Constants + + private const int LVM_FIRST = 0x1000; + private const int LVM_GETCOLUMN = LVM_FIRST + 95; + private const int LVM_GETCOUNTPERPAGE = LVM_FIRST + 40; + private const int LVM_GETGROUPINFO = LVM_FIRST + 149; + private const int LVM_GETGROUPSTATE = LVM_FIRST + 92; + private const int LVM_GETHEADER = LVM_FIRST + 31; + private const int LVM_GETTOOLTIPS = LVM_FIRST + 78; + private const int LVM_GETTOPINDEX = LVM_FIRST + 39; + private const int LVM_HITTEST = LVM_FIRST + 18; + private const int LVM_INSERTGROUP = LVM_FIRST + 145; + private const int LVM_REMOVEALLGROUPS = LVM_FIRST + 160; + private const int LVM_SCROLL = LVM_FIRST + 20; + private const int LVM_SETBKIMAGE = LVM_FIRST + 0x8A; + private const int LVM_SETCOLUMN = LVM_FIRST + 96; + private const int LVM_SETEXTENDEDLISTVIEWSTYLE = LVM_FIRST + 54; + private const int LVM_SETGROUPINFO = LVM_FIRST + 147; + private const int LVM_SETGROUPMETRICS = LVM_FIRST + 155; + private const int LVM_SETIMAGELIST = LVM_FIRST + 3; + private const int LVM_SETITEM = LVM_FIRST + 76; + private const int LVM_SETITEMCOUNT = LVM_FIRST + 47; + private const int LVM_SETITEMSTATE = LVM_FIRST + 43; + private const int LVM_SETSELECTEDCOLUMN = LVM_FIRST + 140; + private const int LVM_SETTOOLTIPS = LVM_FIRST + 74; + private const int LVM_SUBITEMHITTEST = LVM_FIRST + 57; + private const int LVS_EX_SUBITEMIMAGES = 0x0002; + + private const int LVIF_TEXT = 0x0001; + private const int LVIF_IMAGE = 0x0002; + private const int LVIF_PARAM = 0x0004; + private const int LVIF_STATE = 0x0008; + private const int LVIF_INDENT = 0x0010; + private const int LVIF_NORECOMPUTE = 0x0800; + + private const int LVIS_SELECTED = 2; + + private const int LVCF_FMT = 0x0001; + private const int LVCF_WIDTH = 0x0002; + private const int LVCF_TEXT = 0x0004; + private const int LVCF_SUBITEM = 0x0008; + private const int LVCF_IMAGE = 0x0010; + private const int LVCF_ORDER = 0x0020; + private const int LVCFMT_LEFT = 0x0000; + private const int LVCFMT_RIGHT = 0x0001; + private const int LVCFMT_CENTER = 0x0002; + private const int LVCFMT_JUSTIFYMASK = 0x0003; + + private const int LVCFMT_IMAGE = 0x0800; + private const int LVCFMT_BITMAP_ON_RIGHT = 0x1000; + private const int LVCFMT_COL_HAS_IMAGES = 0x8000; + + private const int LVBKIF_SOURCE_NONE = 0x0; + private const int LVBKIF_SOURCE_HBITMAP = 0x1; + private const int LVBKIF_SOURCE_URL = 0x2; + private const int LVBKIF_SOURCE_MASK = 0x3; + private const int LVBKIF_STYLE_NORMAL = 0x0; + private const int LVBKIF_STYLE_TILE = 0x10; + private const int LVBKIF_STYLE_MASK = 0x10; + private const int LVBKIF_FLAG_TILEOFFSET = 0x100; + private const int LVBKIF_TYPE_WATERMARK = 0x10000000; + private const int LVBKIF_FLAG_ALPHABLEND = 0x20000000; + + private const int LVSICF_NOINVALIDATEALL = 1; + private const int LVSICF_NOSCROLL = 2; + + private const int HDM_FIRST = 0x1200; + private const int HDM_HITTEST = HDM_FIRST + 6; + private const int HDM_GETITEMRECT = HDM_FIRST + 7; + private const int HDM_GETITEM = HDM_FIRST + 11; + private const int HDM_SETITEM = HDM_FIRST + 12; + + private const int HDI_WIDTH = 0x0001; + private const int HDI_TEXT = 0x0002; + private const int HDI_FORMAT = 0x0004; + private const int HDI_BITMAP = 0x0010; + private const int HDI_IMAGE = 0x0020; + + private const int HDF_LEFT = 0x0000; + private const int HDF_RIGHT = 0x0001; + private const int HDF_CENTER = 0x0002; + private const int HDF_JUSTIFYMASK = 0x0003; + private const int HDF_RTLREADING = 0x0004; + private const int HDF_STRING = 0x4000; + private const int HDF_BITMAP = 0x2000; + private const int HDF_BITMAP_ON_RIGHT = 0x1000; + private const int HDF_IMAGE = 0x0800; + private const int HDF_SORTUP = 0x0400; + private const int HDF_SORTDOWN = 0x0200; + + private const int SB_HORZ = 0; + private const int SB_VERT = 1; + private const int SB_CTL = 2; + private const int SB_BOTH = 3; + + private const int SIF_RANGE = 0x0001; + private const int SIF_PAGE = 0x0002; + private const int SIF_POS = 0x0004; + private const int SIF_DISABLENOSCROLL = 0x0008; + private const int SIF_TRACKPOS = 0x0010; + private const int SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS); + + private const int ILD_NORMAL = 0x0; + private const int ILD_TRANSPARENT = 0x1; + private const int ILD_MASK = 0x10; + private const int ILD_IMAGE = 0x20; + private const int ILD_BLEND25 = 0x2; + private const int ILD_BLEND50 = 0x4; + + const int SWP_NOSIZE = 1; + const int SWP_NOMOVE = 2; + const int SWP_NOZORDER = 4; + const int SWP_NOREDRAW = 8; + const int SWP_NOACTIVATE = 16; + public const int SWP_FRAMECHANGED = 32; + + const int SWP_ZORDERONLY = SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOACTIVATE; + const int SWP_SIZEONLY = SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER | SWP_NOACTIVATE; + const int SWP_UPDATE_FRAME = SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED; + + #endregion + + #region Structures + + [StructLayout(LayoutKind.Sequential)] + public struct HDITEM + { + public int mask; + public int cxy; + public IntPtr pszText; + public IntPtr hbm; + public int cchTextMax; + public int fmt; + public IntPtr lParam; + public int iImage; + public int iOrder; + //if (_WIN32_IE >= 0x0500) + public int type; + public IntPtr pvFilter; + } + + [StructLayout(LayoutKind.Sequential)] + public class HDHITTESTINFO + { + public int pt_x; + public int pt_y; + public int flags; + public int iItem; + } + + [StructLayout(LayoutKind.Sequential)] + public class HDLAYOUT + { + public IntPtr prc; + public IntPtr pwpos; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGELISTDRAWPARAMS + { + public int cbSize; + public IntPtr himl; + public int i; + public IntPtr hdcDst; + public int x; + public int y; + public int cx; + public int cy; + public int xBitmap; + public int yBitmap; + public uint rgbBk; + public uint rgbFg; + public uint fStyle; + public uint dwRop; + public uint fState; + public uint Frame; + public uint crEffect; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct LVBKIMAGE + { + public int ulFlags; + public IntPtr hBmp; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszImage; + public int cchImageMax; + public int xOffset; + public int yOffset; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct LVCOLUMN + { + public int mask; + public int fmt; + public int cx; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszText; + public int cchTextMax; + public int iSubItem; + // These are available in Common Controls >= 0x0300 + public int iImage; + public int iOrder; + }; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct LVFINDINFO + { + public int flags; + public string psz; + public IntPtr lParam; + public int ptX; + public int ptY; + public int vkDirection; + } + + [StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] + public struct LVGROUP + { + public uint cbSize; + public uint mask; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszHeader; + public int cchHeader; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszFooter; + public int cchFooter; + public int iGroupId; + public uint stateMask; + public uint state; + public uint uAlign; + } + + [StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] + public struct LVGROUP2 + { + public uint cbSize; + public uint mask; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszHeader; + public uint cchHeader; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszFooter; + public int cchFooter; + public int iGroupId; + public uint stateMask; + public uint state; + public uint uAlign; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszSubtitle; + public uint cchSubtitle; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszTask; + public uint cchTask; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszDescriptionTop; + public uint cchDescriptionTop; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszDescriptionBottom; + public uint cchDescriptionBottom; + public int iTitleImage; + public int iExtendedImage; + public int iFirstItem; // Read only + public int cItems; // Read only + [MarshalAs(UnmanagedType.LPTStr)] + public string pszSubsetTitle; // NULL if group is not subset + public uint cchSubsetTitle; + } + + [StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] + public struct LVGROUPMETRICS + { + public uint cbSize; + public uint mask; + public uint Left; + public uint Top; + public uint Right; + public uint Bottom; + public int crLeft; + public int crTop; + public int crRight; + public int crBottom; + public int crHeader; + public int crFooter; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct LVHITTESTINFO + { + public int pt_x; + public int pt_y; + public int flags; + public int iItem; + public int iSubItem; + public int iGroup; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct LVITEM + { + public int mask; + public int iItem; + public int iSubItem; + public int state; + public int stateMask; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszText; + public int cchTextMax; + public int iImage; + public IntPtr lParam; + // These are available in Common Controls >= 0x0300 + public int iIndent; + // These are available in Common Controls >= 0x056 + public int iGroupId; + public int cColumns; + public IntPtr puColumns; + }; + + [StructLayout(LayoutKind.Sequential)] + public struct NMHDR + { + public IntPtr hwndFrom; + public IntPtr idFrom; + public int code; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NMCUSTOMDRAW + { + public NativeMethods.NMHDR nmcd; + public int dwDrawStage; + public IntPtr hdc; + public NativeMethods.RECT rc; + public IntPtr dwItemSpec; + public int uItemState; + public IntPtr lItemlParam; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NMHEADER + { + public NMHDR nhdr; + public int iItem; + public int iButton; + public IntPtr pHDITEM; + } + + const int MAX_LINKID_TEXT = 48; + const int L_MAX_URL_LENGTH = 2048 + 32 + 4; + //#define L_MAX_URL_LENGTH (2048 + 32 + sizeof("://")) + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct LITEM + { + public uint mask; + public int iLink; + public uint state; + public uint stateMask; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_LINKID_TEXT)] + public string szID; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = L_MAX_URL_LENGTH)] + public string szUrl; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NMLISTVIEW + { + public NativeMethods.NMHDR hdr; + public int iItem; + public int iSubItem; + public int uNewState; + public int uOldState; + public int uChanged; + public IntPtr lParam; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NMLVCUSTOMDRAW + { + public NativeMethods.NMCUSTOMDRAW nmcd; + public int clrText; + public int clrTextBk; + public int iSubItem; + public int dwItemType; + public int clrFace; + public int iIconEffect; + public int iIconPhase; + public int iPartId; + public int iStateId; + public NativeMethods.RECT rcText; + public uint uAlign; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NMLVFINDITEM + { + public NativeMethods.NMHDR hdr; + public int iStart; + public NativeMethods.LVFINDINFO lvfi; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NMLVGETINFOTIP + { + public NativeMethods.NMHDR hdr; + public int dwFlags; + public string pszText; + public int cchTextMax; + public int iItem; + public int iSubItem; + public IntPtr lParam; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NMLVGROUP + { + public NMHDR hdr; + public int iGroupId; // which group is changing + public uint uNewState; // LVGS_xxx flags + public uint uOldState; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NMLVLINK + { + public NMHDR hdr; + public LITEM link; + public int iItem; + public int iSubItem; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NMLVSCROLL + { + public NativeMethods.NMHDR hdr; + public int dx; + public int dy; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct NMTTDISPINFO + { + public NativeMethods.NMHDR hdr; + [MarshalAs(UnmanagedType.LPTStr)] + public string lpszText; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] + public string szText; + public IntPtr hinst; + public int uFlags; + public IntPtr lParam; + //public int hbmp; This is documented but doesn't work + } + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + } + + [StructLayout(LayoutKind.Sequential)] + public class SCROLLINFO + { + public int cbSize = Marshal.SizeOf(typeof(NativeMethods.SCROLLINFO)); + public int fMask; + public int nMin; + public int nMax; + public int nPage; + public int nPos; + public int nTrackPos; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public class TOOLINFO + { + public int cbSize = Marshal.SizeOf(typeof(NativeMethods.TOOLINFO)); + public int uFlags; + public IntPtr hwnd; + public IntPtr uId; + public NativeMethods.RECT rect; + public IntPtr hinst = IntPtr.Zero; + public IntPtr lpszText; + public IntPtr lParam = IntPtr.Zero; + } + + [StructLayout(LayoutKind.Sequential)] + public struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndInsertAfter; + public int x; + public int y; + public int cx; + public int cy; + public int flags; + } + + #endregion + + #region Entry points + + // Various flavours of SendMessage: plain vanilla, and passing references to various structures + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, int lParam); + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, int lParam); + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam); + [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessageLVItem(IntPtr hWnd, int msg, int wParam, ref LVITEM lvi); + [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, ref LVHITTESTINFO ht); + [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessageRECT(IntPtr hWnd, int msg, int wParam, ref RECT r); + //[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] + //private static extern IntPtr SendMessageLVColumn(IntPtr hWnd, int m, int wParam, ref LVCOLUMN lvc); + [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] + private static extern IntPtr SendMessageHDItem(IntPtr hWnd, int msg, int wParam, ref HDITEM hdi); + [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessageHDHITTESTINFO(IntPtr hWnd, int Msg, IntPtr wParam, [In, Out] HDHITTESTINFO lParam); + [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessageTOOLINFO(IntPtr hWnd, int Msg, int wParam, NativeMethods.TOOLINFO lParam); + [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessageLVBKIMAGE(IntPtr hWnd, int Msg, int wParam, ref NativeMethods.LVBKIMAGE lParam); + [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessageString(IntPtr hWnd, int Msg, int wParam, string lParam); + [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessageIUnknown(IntPtr hWnd, int msg, [MarshalAs(UnmanagedType.IUnknown)] object wParam, int lParam); + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, ref LVGROUP lParam); + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, ref LVGROUP2 lParam); + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, ref LVGROUPMETRICS lParam); + + [DllImport("gdi32.dll")] + public static extern bool DeleteObject(IntPtr objectHandle); + + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern bool GetClientRect(IntPtr hWnd, ref Rectangle r); + + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern bool GetScrollInfo(IntPtr hWnd, int fnBar, SCROLLINFO scrollInfo); + + [DllImport("user32.dll", EntryPoint = "GetUpdateRect", CharSet = CharSet.Auto)] + private static extern bool GetUpdateRectInternal(IntPtr hWnd, ref Rectangle r, bool eraseBackground); + + [DllImport("comctl32.dll", CharSet = CharSet.Auto)] + private static extern bool ImageList_Draw(IntPtr himl, int i, IntPtr hdcDst, int x, int y, int fStyle); + + [DllImport("comctl32.dll", CharSet = CharSet.Auto)] + private static extern bool ImageList_DrawIndirect(ref IMAGELISTDRAWPARAMS parms); + + [DllImport("user32.dll")] + public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern bool GetWindowRect(IntPtr hWnd, ref Rectangle r); + + [DllImport("user32.dll", EntryPoint = "GetWindowLong", CharSet = CharSet.Auto)] + public static extern IntPtr GetWindowLong32(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", CharSet = CharSet.Auto)] + public static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Auto)] + public static extern IntPtr SetWindowLongPtr32(IntPtr hWnd, int nIndex, int dwNewLong); + + [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Auto)] + public static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, int dwNewLong); + + [DllImport("user32.dll")] + public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("user32.dll", EntryPoint = "ValidateRect", CharSet = CharSet.Auto)] + private static extern IntPtr ValidatedRectInternal(IntPtr hWnd, ref Rectangle r); + + #endregion + + //[DllImport("user32.dll", EntryPoint = "LockWindowUpdate", CharSet = CharSet.Auto)] + //private static extern int LockWindowUpdateInternal(IntPtr hWnd); + + //public static void LockWindowUpdate(IWin32Window window) { + // if (window == null) + // NativeMethods.LockWindowUpdateInternal(IntPtr.Zero); + // else + // NativeMethods.LockWindowUpdateInternal(window.Handle); + //} + + /// + /// Put an image under the ListView. + /// + /// + /// + /// The ListView must have its handle created before calling this. + /// + /// + /// This doesn't work very well. Specifically, it doesn't play well with owner drawn, + /// and grid lines are drawn over it. + /// + /// + /// + /// The image to be used as the background. If this is null, any existing background image will be cleared. + /// If this is true, the image is pinned to the bottom right and does not scroll. The other parameters are ignored + /// If this is true, the image will be tiled to fill the whole control background. The offset parameters will be ignored. + /// If both watermark and tiled are false, this indicates the horizontal percentage where the image will be placed. 0 is absolute left, 100 is absolute right. + /// If both watermark and tiled are false, this indicates the vertical percentage where the image will be placed. + /// + public static bool SetBackgroundImage(ListView lv, Image image, bool isWatermark, bool isTiled, int xOffset, int yOffset) { + + LVBKIMAGE lvbkimage = new LVBKIMAGE(); + + // We have to clear any pre-existing background image, otherwise the attempt to set the image will fail. + // We don't know which type may already have been set, so we just clear both the watermark and the image. + lvbkimage.ulFlags = LVBKIF_TYPE_WATERMARK; + IntPtr result = NativeMethods.SendMessageLVBKIMAGE(lv.Handle, LVM_SETBKIMAGE, 0, ref lvbkimage); + lvbkimage.ulFlags = LVBKIF_SOURCE_HBITMAP; + result = NativeMethods.SendMessageLVBKIMAGE(lv.Handle, LVM_SETBKIMAGE, 0, ref lvbkimage); + + Bitmap bm = image as Bitmap; + if (bm != null) { + lvbkimage.hBmp = bm.GetHbitmap(); + lvbkimage.ulFlags = isWatermark ? LVBKIF_TYPE_WATERMARK : (isTiled ? LVBKIF_SOURCE_HBITMAP | LVBKIF_STYLE_TILE : LVBKIF_SOURCE_HBITMAP); + lvbkimage.xOffset = xOffset; + lvbkimage.yOffset = yOffset; + result = NativeMethods.SendMessageLVBKIMAGE(lv.Handle, LVM_SETBKIMAGE, 0, ref lvbkimage); + } + + return (result != IntPtr.Zero); + } + + public static bool DrawImageList(Graphics g, ImageList il, int index, int x, int y, bool isSelected, bool isDisabled) { + ImageListDrawItemConstants flags = (isSelected ? ImageListDrawItemConstants.ILD_SELECTED : ImageListDrawItemConstants.ILD_NORMAL) | ImageListDrawItemConstants.ILD_TRANSPARENT; + ImageListDrawStateConstants state = isDisabled ? ImageListDrawStateConstants.ILS_SATURATE : ImageListDrawStateConstants.ILS_NORMAL; + try { + IntPtr hdc = g.GetHdc(); + return DrawImage(il, hdc, index, x, y, flags, 0, 0, state); + } + finally { + g.ReleaseHdc(); + } + } + + /// + /// Flags controlling how the Image List item is + /// drawn + /// + [Flags] + public enum ImageListDrawItemConstants + { + /// + /// Draw item normally. + /// + ILD_NORMAL = 0x0, + /// + /// Draw item transparently. + /// + ILD_TRANSPARENT = 0x1, + /// + /// Draw item blended with 25% of the specified foreground colour + /// or the Highlight colour if no foreground colour specified. + /// + ILD_BLEND25 = 0x2, + /// + /// Draw item blended with 50% of the specified foreground colour + /// or the Highlight colour if no foreground colour specified. + /// + ILD_SELECTED = 0x4, + /// + /// Draw the icon's mask + /// + ILD_MASK = 0x10, + /// + /// Draw the icon image without using the mask + /// + ILD_IMAGE = 0x20, + /// + /// Draw the icon using the ROP specified. + /// + ILD_ROP = 0x40, + /// + /// Preserves the alpha channel in dest. XP only. + /// + ILD_PRESERVEALPHA = 0x1000, + /// + /// Scale the image to cx, cy instead of clipping it. XP only. + /// + ILD_SCALE = 0x2000, + /// + /// Scale the image to the current DPI of the display. XP only. + /// + ILD_DPISCALE = 0x4000 + } + + /// + /// Enumeration containing XP ImageList Draw State options + /// + [Flags] + public enum ImageListDrawStateConstants + { + /// + /// The image state is not modified. + /// + ILS_NORMAL = (0x00000000), + /// + /// Adds a glow effect to the icon, which causes the icon to appear to glow + /// with a given color around the edges. (Note: does not appear to be implemented) + /// + ILS_GLOW = (0x00000001), //The color for the glow effect is passed to the IImageList::Draw method in the crEffect member of IMAGELISTDRAWPARAMS. + /// + /// Adds a drop shadow effect to the icon. (Note: does not appear to be implemented) + /// + ILS_SHADOW = (0x00000002), //The color for the drop shadow effect is passed to the IImageList::Draw method in the crEffect member of IMAGELISTDRAWPARAMS. + /// + /// Saturates the icon by increasing each color component + /// of the RGB triplet for each pixel in the icon. (Note: only ever appears to result in a completely unsaturated icon) + /// + ILS_SATURATE = (0x00000004), // The amount to increase is indicated by the frame member in the IMAGELISTDRAWPARAMS method. + /// + /// Alpha blends the icon. Alpha blending controls the transparency + /// level of an icon, according to the value of its alpha channel. + /// (Note: does not appear to be implemented). + /// + ILS_ALPHA = (0x00000008) //The value of the alpha channel is indicated by the frame member in the IMAGELISTDRAWPARAMS method. The alpha channel can be from 0 to 255, with 0 being completely transparent, and 255 being completely opaque. + } + + private const uint CLR_DEFAULT = 0xFF000000; + + /// + /// Draws an image using the specified flags and state on XP systems. + /// + /// The image list from which an item will be drawn + /// Device context to draw to + /// Index of image to draw + /// X Position to draw at + /// Y Position to draw at + /// Drawing flags + /// Width to draw + /// Height to draw + /// State flags + public static bool DrawImage(ImageList il, IntPtr hdc, int index, int x, int y, ImageListDrawItemConstants flags, int cx, int cy, ImageListDrawStateConstants stateFlags) { + IMAGELISTDRAWPARAMS pimldp = new IMAGELISTDRAWPARAMS(); + pimldp.hdcDst = hdc; + pimldp.cbSize = Marshal.SizeOf(pimldp.GetType()); + pimldp.i = index; + pimldp.x = x; + pimldp.y = y; + pimldp.cx = cx; + pimldp.cy = cy; + pimldp.rgbFg = CLR_DEFAULT; + pimldp.fStyle = (uint) flags; + pimldp.fState = (uint) stateFlags; + pimldp.himl = il.Handle; + return ImageList_DrawIndirect(ref pimldp); + } + + /// + /// Make sure the ListView has the extended style that says to display subitem images. + /// + /// This method must be called after any .NET call that update the extended styles + /// since they seem to erase this setting. + /// The listview to send a m to + public static void ForceSubItemImagesExStyle(ListView list) { + SendMessage(list.Handle, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_SUBITEMIMAGES, LVS_EX_SUBITEMIMAGES); + } + + /// + /// Change the virtual list size of the given ListView (which must be in virtual mode) + /// + /// This will not change the scroll position + /// The listview to send a message to + /// How many rows should the list have? + public static void SetItemCount(ListView list, int count) { + SendMessage(list.Handle, LVM_SETITEMCOUNT, count, LVSICF_NOSCROLL); + } + + /// + /// Make sure the ListView has the extended style that says to display subitem images. + /// + /// This method must be called after any .NET call that update the extended styles + /// since they seem to erase this setting. + /// The listview to send a m to + /// + /// + public static void SetExtendedStyle(ListView list, int style, int styleMask) { + SendMessage(list.Handle, LVM_SETEXTENDEDLISTVIEWSTYLE, styleMask, style); + } + + /// + /// Calculates the number of items that can fit vertically in the visible area of a list-view (which + /// must be in details or list view. + /// + /// The listView + /// Number of visible items per page + public static int GetCountPerPage(ListView list) { + return (int)SendMessage(list.Handle, LVM_GETCOUNTPERPAGE, 0, 0); + } + /// + /// For the given item and subitem, make it display the given image + /// + /// The listview to send a m to + /// row number (0 based) + /// subitem (0 is the item itself) + /// index into the image list + public static void SetSubItemImage(ListView list, int itemIndex, int subItemIndex, int imageIndex) { + LVITEM lvItem = new LVITEM(); + lvItem.mask = LVIF_IMAGE; + lvItem.iItem = itemIndex; + lvItem.iSubItem = subItemIndex; + lvItem.iImage = imageIndex; + SendMessageLVItem(list.Handle, LVM_SETITEM, 0, ref lvItem); + } + + /// + /// Setup the given column of the listview to show the given image to the right of the text. + /// If the image index is -1, any previous image is cleared + /// + /// The listview to send a m to + /// Index of the column to modify + /// + /// Index into the small image list + public static void SetColumnImage(ListView list, int columnIndex, SortOrder order, int imageIndex) { + IntPtr hdrCntl = NativeMethods.GetHeaderControl(list); + if (hdrCntl.ToInt32() == 0) + return; + + HDITEM item = new HDITEM(); + item.mask = HDI_FORMAT; + IntPtr result = SendMessageHDItem(hdrCntl, HDM_GETITEM, columnIndex, ref item); + + item.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN | HDF_IMAGE | HDF_BITMAP_ON_RIGHT); + + if (NativeMethods.HasBuiltinSortIndicators()) { + if (order == SortOrder.Ascending) + item.fmt |= HDF_SORTUP; + if (order == SortOrder.Descending) + item.fmt |= HDF_SORTDOWN; + } else { + item.mask |= HDI_IMAGE; + item.fmt |= (HDF_IMAGE | HDF_BITMAP_ON_RIGHT); + item.iImage = imageIndex; + } + + result = SendMessageHDItem(hdrCntl, HDM_SETITEM, columnIndex, ref item); + } + + /// + /// Does this version of the operating system have builtin sort indicators? + /// + /// Are there builtin sort indicators + /// XP and later have these + public static bool HasBuiltinSortIndicators() { + return OSFeature.Feature.GetVersionPresent(OSFeature.Themes) != null; + } + + /// + /// Return the bounds of the update region on the given control. + /// + /// The BeginPaint() system call validates the update region, effectively wiping out this information. + /// So this call has to be made before the BeginPaint() call. + /// The control whose update region is be calculated + /// A rectangle + public static Rectangle GetUpdateRect(Control cntl) { + Rectangle r = new Rectangle(); + GetUpdateRectInternal(cntl.Handle, ref r, false); + return r; + } + + /// + /// Validate an area of the given control. A validated area will not be repainted at the next redraw. + /// + /// The control to be validated + /// The area of the control to be validated + public static void ValidateRect(Control cntl, Rectangle r) { + ValidatedRectInternal(cntl.Handle, ref r); + } + + /// + /// Select all rows on the given listview + /// + /// The listview whose items are to be selected + public static void SelectAllItems(ListView list) { + NativeMethods.SetItemState(list, -1, LVIS_SELECTED, LVIS_SELECTED); + } + + /// + /// Deselect all rows on the given listview + /// + /// The listview whose items are to be deselected + public static void DeselectAllItems(ListView list) { + NativeMethods.SetItemState(list, -1, LVIS_SELECTED, 0); + } + + /// + /// Deselect a single row + /// + /// + /// + public static void DeselectOneItem(ListView list, int index) { + NativeMethods.SetItemState(list, index, LVIS_SELECTED, 0); + } + + /// + /// Set the item state on the given item + /// + /// The listview whose item's state is to be changed + /// The index of the item to be changed + /// Which bits of the value are to be set? + /// The value to be set + public static void SetItemState(ListView list, int itemIndex, int mask, int value) { + LVITEM lvItem = new LVITEM(); + lvItem.stateMask = mask; + lvItem.state = value; + SendMessageLVItem(list.Handle, LVM_SETITEMSTATE, itemIndex, ref lvItem); + } + + /// + /// Scroll the given listview by the given deltas + /// + /// + /// + /// + /// true if the scroll succeeded + public static bool Scroll(ListView list, int dx, int dy) { + return SendMessage(list.Handle, LVM_SCROLL, dx, dy) != IntPtr.Zero; + } + + /// + /// Return the handle to the header control on the given list + /// + /// The listview whose header control is to be returned + /// The handle to the header control + public static IntPtr GetHeaderControl(ListView list) { + return SendMessage(list.Handle, LVM_GETHEADER, 0, 0); + } + + /// + /// Return the edges of the given column. + /// + /// + /// + /// A Point holding the left and right co-ords of the column. + /// -1 means that the sides could not be retrieved. + public static Point GetColumnSides(ObjectListView lv, int columnIndex) { + Point sides = new Point(-1, -1); + IntPtr hdr = NativeMethods.GetHeaderControl(lv); + if (hdr == IntPtr.Zero) + return new Point(-1, -1); + + RECT r = new RECT(); + NativeMethods.SendMessageRECT(hdr, HDM_GETITEMRECT, columnIndex, ref r); + return new Point(r.left, r.right); + } + + /// + /// Return the edges of the given column. + /// + /// + /// + /// A Point holding the left and right co-ords of the column. + /// -1 means that the sides could not be retrieved. + public static Point GetScrolledColumnSides(ListView lv, int columnIndex) { + IntPtr hdr = NativeMethods.GetHeaderControl(lv); + if (hdr == IntPtr.Zero) + return new Point(-1, -1); + + RECT r = new RECT(); + IntPtr result = NativeMethods.SendMessageRECT(hdr, HDM_GETITEMRECT, columnIndex, ref r); + int scrollH = NativeMethods.GetScrollPosition(lv, true); + return new Point(r.left - scrollH, r.right - scrollH); + } + + /// + /// Return the index of the column of the header that is under the given point. + /// Return -1 if no column is under the pt + /// + /// The list we are interested in + /// The client co-ords + /// The index of the column under the point, or -1 if no column header is under that point + public static int GetColumnUnderPoint(IntPtr handle, Point pt) { + const int HHT_ONHEADER = 2; + const int HHT_ONDIVIDER = 4; + return NativeMethods.HeaderControlHitTest(handle, pt, HHT_ONHEADER | HHT_ONDIVIDER); + } + + private static int HeaderControlHitTest(IntPtr handle, Point pt, int flag) { + HDHITTESTINFO testInfo = new HDHITTESTINFO(); + testInfo.pt_x = pt.X; + testInfo.pt_y = pt.Y; + IntPtr result = NativeMethods.SendMessageHDHITTESTINFO(handle, HDM_HITTEST, IntPtr.Zero, testInfo); + if ((testInfo.flags & flag) != 0) + return testInfo.iItem; + else + return -1; + } + + /// + /// Return the index of the divider under the given point. Return -1 if no divider is under the pt + /// + /// The list we are interested in + /// The client co-ords + /// The index of the divider under the point, or -1 if no divider is under that point + public static int GetDividerUnderPoint(IntPtr handle, Point pt) { + const int HHT_ONDIVIDER = 4; + return NativeMethods.HeaderControlHitTest(handle, pt, HHT_ONDIVIDER); + } + + /// + /// Get the scroll position of the given scroll bar + /// + /// + /// + /// + public static int GetScrollPosition(ListView lv, bool horizontalBar) { + int fnBar = (horizontalBar ? SB_HORZ : SB_VERT); + + SCROLLINFO scrollInfo = new SCROLLINFO(); + scrollInfo.fMask = SIF_POS; + if (GetScrollInfo(lv.Handle, fnBar, scrollInfo)) + return scrollInfo.nPos; + else + return -1; + } + + /// + /// Change the z-order to the window 'toBeMoved' so it appear directly on top of 'reference' + /// + /// + /// + /// + public static bool ChangeZOrder(IWin32Window toBeMoved, IWin32Window reference) { + return NativeMethods.SetWindowPos(toBeMoved.Handle, reference.Handle, 0, 0, 0, 0, SWP_ZORDERONLY); + } + + /// + /// Make the given control/window a topmost window + /// + /// + /// + public static bool MakeTopMost(IWin32Window toBeMoved) { + IntPtr HWND_TOPMOST = (IntPtr)(-1); + return NativeMethods.SetWindowPos(toBeMoved.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_ZORDERONLY); + } + + /// + /// Change the size of the window without affecting any other attributes + /// + /// + /// + /// + /// + public static bool ChangeSize(IWin32Window toBeMoved, int width, int height) { + return NativeMethods.SetWindowPos(toBeMoved.Handle, IntPtr.Zero, 0, 0, width, height, SWP_SIZEONLY); + } + + /// + /// Show the given window without activating it + /// + /// The window to show + static public void ShowWithoutActivate(IWin32Window win) { + const int SW_SHOWNA = 8; + NativeMethods.ShowWindow(win.Handle, SW_SHOWNA); + } + + /// + /// Mark the given column as being selected. + /// + /// + /// The OLVColumn or null to clear + /// + /// This method works, but it prevents subitems in the given column from having + /// back colors. + /// + static public void SetSelectedColumn(ListView objectListView, ColumnHeader value) { + NativeMethods.SendMessage(objectListView.Handle, + LVM_SETSELECTEDCOLUMN, (value == null) ? -1 : value.Index, 0); + } + + static public int GetTopIndex(ListView lv) { + return (int)SendMessage(lv.Handle, LVM_GETTOPINDEX, 0, 0); + } + + static public IntPtr GetTooltipControl(ListView lv) { + return SendMessage(lv.Handle, LVM_GETTOOLTIPS, 0, 0); + } + + static public IntPtr SetTooltipControl(ListView lv, ToolTipControl tooltip) { + return SendMessage(lv.Handle, LVM_SETTOOLTIPS, 0, tooltip.Handle); + } + + static public bool HasHorizontalScrollBar(ListView lv) { + const int GWL_STYLE = -16; + const int WS_HSCROLL = 0x00100000; + + return (NativeMethods.GetWindowLong(lv.Handle, GWL_STYLE) & WS_HSCROLL) != 0; + } + + public static int GetWindowLong(IntPtr hWnd, int nIndex) { + if (IntPtr.Size == 4) + return (int)GetWindowLong32(hWnd, nIndex); + else + return (int)(long)GetWindowLongPtr64(hWnd, nIndex); + } + + public static int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong) { + if (IntPtr.Size == 4) + return (int)SetWindowLongPtr32(hWnd, nIndex, dwNewLong); + else + return (int)(long)SetWindowLongPtr64(hWnd, nIndex, dwNewLong); + } + + [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)] + public static extern IntPtr SetBkColor(IntPtr hDC, int clr); + + [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)] + public static extern IntPtr SetTextColor(IntPtr hDC, int crColor); + + [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr obj); + + [DllImport("uxtheme.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)] + public static extern IntPtr SetWindowTheme(IntPtr hWnd, string subApp, string subIdList); + + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern bool InvalidateRect(IntPtr hWnd, int ignored, bool erase); + + [StructLayout(LayoutKind.Sequential)] + public struct LVITEMINDEX + { + public int iItem; + public int iGroup; + } + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public int x; + public int y; + } + + public static int GetGroupInfo(ObjectListView olv, int groupId, ref LVGROUP2 group) { + return (int)NativeMethods.SendMessage(olv.Handle, LVM_GETGROUPINFO, groupId, ref group); + } + + public static GroupState GetGroupState(ObjectListView olv, int groupId, GroupState mask) { + return (GroupState)NativeMethods.SendMessage(olv.Handle, LVM_GETGROUPSTATE, groupId, (int)mask); + } + + public static int InsertGroup(ObjectListView olv, LVGROUP2 group) { + return (int)NativeMethods.SendMessage(olv.Handle, LVM_INSERTGROUP, -1, ref group); + } + + public static int SetGroupInfo(ObjectListView olv, int groupId, LVGROUP2 group) { + return (int)NativeMethods.SendMessage(olv.Handle, LVM_SETGROUPINFO, groupId, ref group); + } + + public static int SetGroupMetrics(ObjectListView olv, LVGROUPMETRICS metrics) { + return (int)NativeMethods.SendMessage(olv.Handle, LVM_SETGROUPMETRICS, 0, ref metrics); + } + + public static void ClearGroups(VirtualObjectListView virtualObjectListView) { + NativeMethods.SendMessage(virtualObjectListView.Handle, LVM_REMOVEALLGROUPS, 0, 0); + } + + public static void SetGroupImageList(ObjectListView olv, ImageList il) { + const int LVSIL_GROUPHEADER = 3; + NativeMethods.SendMessage(olv.Handle, LVM_SETIMAGELIST, LVSIL_GROUPHEADER, il == null ? IntPtr.Zero : il.Handle); + } + + public static int HitTest(ObjectListView olv, ref LVHITTESTINFO hittest) + { + return (int)NativeMethods.SendMessage(olv.Handle, olv.View == View.Details ? LVM_SUBITEMHITTEST : LVM_HITTEST, -1, ref hittest); + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/NullableDictionary.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/NullableDictionary.cs new file mode 100644 index 0000000..6e37a62 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/NullableDictionary.cs @@ -0,0 +1,87 @@ +/* + * NullableDictionary - A simple Dictionary that can handle null as a key + * + * Author: Phillip Piper + * Date: 31-March-2011 5:53 pm + * + * Change log: + * 2011-03-31 JPP - Split into its own file + * + * Copyright (C) 2011-2017 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Collections; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// A simple-minded implementation of a Dictionary that can handle null as a key. + /// + /// The type of the dictionary key + /// The type of the values to be stored + /// This is not a full implementation and is only meant to handle + /// collecting groups by their keys, since groups can have null as a key value. + internal class NullableDictionary : Dictionary { + private bool hasNullKey; + private TValue nullValue; + + new public TValue this[TKey key] { + get { + if (key != null) + return base[key]; + + if (this.hasNullKey) + return this.nullValue; + + throw new KeyNotFoundException(); + } + set { + if (key == null) { + this.hasNullKey = true; + this.nullValue = value; + } else + base[key] = value; + } + } + + new public bool ContainsKey(TKey key) { + return key == null ? this.hasNullKey : base.ContainsKey(key); + } + + new public IList Keys { + get { + ArrayList list = new ArrayList(base.Keys); + if (this.hasNullKey) + list.Add(null); + return list; + } + } + + new public IList Values { + get { + List list = new List(base.Values); + if (this.hasNullKey) + list.Add(this.nullValue); + return list; + } + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/OLVListItem.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/OLVListItem.cs new file mode 100644 index 0000000..d76d9d3 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/OLVListItem.cs @@ -0,0 +1,325 @@ +/* + * OLVListItem - A row in an ObjectListView + * + * Author: Phillip Piper + * Date: 31-March-2011 5:53 pm + * + * Change log: + * 2018-09-01 JPP - Handle rare case of getting subitems when there are no columns + * v2.9 + * 2015-08-22 JPP - Added OLVListItem.SelectedBackColor and SelectedForeColor + * 2015-06-09 JPP - Added HasAnyHyperlinks property + * v2.8 + * 2014-09-27 JPP - Remove faulty caching of CheckState + * 2014-05-06 JPP - Added OLVListItem.Enabled flag + * vOld + * 2011-03-31 JPP - Split into its own file + * + * Copyright (C) 2011-2018 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Windows.Forms; +using System.Drawing; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// OLVListItems are specialized ListViewItems that know which row object they came from, + /// and the row index at which they are displayed, even when in group view mode. They + /// also know the image they should draw against themselves + /// + public class OLVListItem : ListViewItem { + #region Constructors + + /// + /// Create a OLVListItem for the given row object + /// + public OLVListItem(object rowObject) { + this.rowObject = rowObject; + } + + /// + /// Create a OLVListItem for the given row object, represented by the given string and image + /// + public OLVListItem(object rowObject, string text, Object image) + : base(text, -1) { + this.rowObject = rowObject; + this.imageSelector = image; + } + + #endregion. + + #region Properties + + /// + /// Gets the bounding rectangle of the item, including all subitems + /// + new public Rectangle Bounds { + get { + try { + return base.Bounds; + } + catch (System.ArgumentException) { + // If the item is part of a collapsed group, Bounds will throw an exception + return Rectangle.Empty; + } + } + } + + /// + /// Gets or sets how many pixels will be left blank around each cell of this item + /// + /// This setting only takes effect when the control is owner drawn. + public Rectangle? CellPadding { + get { return this.cellPadding; } + set { this.cellPadding = value; } + } + private Rectangle? cellPadding; + + /// + /// Gets or sets how the cells of this item will be vertically aligned + /// + /// This setting only takes effect when the control is owner drawn. + public StringAlignment? CellVerticalAlignment { + get { return this.cellVerticalAlignment; } + set { this.cellVerticalAlignment = value; } + } + private StringAlignment? cellVerticalAlignment; + + /// + /// Gets or sets the checkedness of this item. + /// + /// + /// Virtual lists don't handle checkboxes well, so we have to intercept attempts to change them + /// through the items, and change them into something that will work. + /// Unfortunately, this won't work if this property is set through the base class, since + /// the property is not declared as virtual. + /// + new public bool Checked { + get { + return base.Checked; + } + set { + if (this.Checked != value) { + if (value) + ((ObjectListView)this.ListView).CheckObject(this.RowObject); + else + ((ObjectListView)this.ListView).UncheckObject(this.RowObject); + } + } + } + + /// + /// Enable tri-state checkbox. + /// + /// .NET's Checked property was not built to handle tri-state checkboxes, + /// and will return True for both Checked and Indeterminate states. + public CheckState CheckState { + get { + switch (this.StateImageIndex) { + case 0: + return System.Windows.Forms.CheckState.Unchecked; + case 1: + return System.Windows.Forms.CheckState.Checked; + case 2: + return System.Windows.Forms.CheckState.Indeterminate; + default: + return System.Windows.Forms.CheckState.Unchecked; + } + } + set { + switch (value) { + case System.Windows.Forms.CheckState.Unchecked: + this.StateImageIndex = 0; + break; + case System.Windows.Forms.CheckState.Checked: + this.StateImageIndex = 1; + break; + case System.Windows.Forms.CheckState.Indeterminate: + this.StateImageIndex = 2; + break; + } + } + } + + /// + /// Gets if this item has any decorations set for it. + /// + public bool HasDecoration { + get { + return this.decorations != null && this.decorations.Count > 0; + } + } + + /// + /// Gets or sets the decoration that will be drawn over this item + /// + /// Setting this replaces all other decorations + public IDecoration Decoration { + get { + if (this.HasDecoration) + return this.Decorations[0]; + else + return null; + } + set { + this.Decorations.Clear(); + if (value != null) + this.Decorations.Add(value); + } + } + + /// + /// Gets the collection of decorations that will be drawn over this item + /// + public IList Decorations { + get { + if (this.decorations == null) + this.decorations = new List(); + return this.decorations; + } + } + private IList decorations; + + /// + /// Gets whether or not this row can be selected and activated + /// + public bool Enabled + { + get { return this.enabled; } + internal set { this.enabled = value; } + } + private bool enabled; + + /// + /// Gets whether any cell on this item is showing a hyperlink + /// + public bool HasAnyHyperlinks { + get { + foreach (OLVListSubItem subItem in this.SubItems) { + if (!String.IsNullOrEmpty(subItem.Url)) + return true; + } + return false; + } + } + + /// + /// Get or set the image that should be shown against this item + /// + /// This can be an Image, a string or an int. A string or an int will + /// be used as an index into the small image list. + public Object ImageSelector { + get { return imageSelector; } + set { + imageSelector = value; + if (value is Int32) + this.ImageIndex = (Int32)value; + else if (value is String) + this.ImageKey = (String)value; + else + this.ImageIndex = -1; + } + } + private Object imageSelector; + + /// + /// Gets or sets the model object that is source of the data for this list item. + /// + public object RowObject { + get { return rowObject; } + set { rowObject = value; } + } + private object rowObject; + + /// + /// Gets or sets the color that will be used for this row's background when it is selected and + /// the control is focused. + /// + /// + /// To work reliably, this property must be set during a FormatRow event. + /// + /// If this is not set, the normal selection BackColor will be used. + /// + /// + public Color? SelectedBackColor { + get { return this.selectedBackColor; } + set { this.selectedBackColor = value; } + } + private Color? selectedBackColor; + + /// + /// Gets or sets the color that will be used for this row's foreground when it is selected and + /// the control is focused. + /// + /// + /// To work reliably, this property must be set during a FormatRow event. + /// + /// If this is not set, the normal selection ForeColor will be used. + /// + /// + public Color? SelectedForeColor + { + get { return this.selectedForeColor; } + set { this.selectedForeColor = value; } + } + private Color? selectedForeColor; + + #endregion + + #region Accessing + + /// + /// Return the sub item at the given index + /// + /// Index of the subitem to be returned + /// An OLVListSubItem + public virtual OLVListSubItem GetSubItem(int index) { + if (index >= 0 && index < this.SubItems.Count) + // If the control has 0 columns, ListViewItem.SubItems will auto create a + // SubItem of the wrong type. Casting using 'as' handles this rare case. + return this.SubItems[index] as OLVListSubItem; + + return null; + } + + + /// + /// Return bounds of the given subitem + /// + /// This correctly calculates the bounds even for column 0. + public virtual Rectangle GetSubItemBounds(int subItemIndex) { + if (subItemIndex == 0) { + Rectangle r = this.Bounds; + Point sides = NativeMethods.GetScrolledColumnSides(this.ListView, subItemIndex); + r.X = sides.X + 1; + r.Width = sides.Y - sides.X; + return r; + } + + OLVListSubItem subItem = this.GetSubItem(subItemIndex); + return subItem == null ? new Rectangle() : subItem.Bounds; + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/OLVListSubItem.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/OLVListSubItem.cs new file mode 100644 index 0000000..a9baca4 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/OLVListSubItem.cs @@ -0,0 +1,173 @@ +/* + * OLVListSubItem - A single cell in an ObjectListView + * + * Author: Phillip Piper + * Date: 31-March-2011 5:53 pm + * + * Change log: + * 2011-03-31 JPP - Split into its own file + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; +using System.Windows.Forms; +using System.ComponentModel; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// A ListViewSubItem that knows which image should be drawn against it. + /// + [Browsable(false)] + public class OLVListSubItem : ListViewItem.ListViewSubItem { + #region Constructors + + /// + /// Create a OLVListSubItem + /// + public OLVListSubItem() { + } + + /// + /// Create a OLVListSubItem that shows the given string and image + /// + public OLVListSubItem(object modelValue, string text, Object image) { + this.ModelValue = modelValue; + this.Text = text; + this.ImageSelector = image; + } + + #endregion + + #region Properties + + /// + /// Gets or sets how many pixels will be left blank around this cell + /// + /// This setting only takes effect when the control is owner drawn. + public Rectangle? CellPadding { + get { return this.cellPadding; } + set { this.cellPadding = value; } + } + private Rectangle? cellPadding; + + /// + /// Gets or sets how this cell will be vertically aligned + /// + /// This setting only takes effect when the control is owner drawn. + public StringAlignment? CellVerticalAlignment { + get { return this.cellVerticalAlignment; } + set { this.cellVerticalAlignment = value; } + } + private StringAlignment? cellVerticalAlignment; + + /// + /// Gets or sets the model value is being displayed by this subitem. + /// + public object ModelValue + { + get { return modelValue; } + private set { modelValue = value; } + } + private object modelValue; + + /// + /// Gets if this subitem has any decorations set for it. + /// + public bool HasDecoration { + get { + return this.decorations != null && this.decorations.Count > 0; + } + } + + /// + /// Gets or sets the decoration that will be drawn over this item + /// + /// Setting this replaces all other decorations + public IDecoration Decoration { + get { + return this.HasDecoration ? this.Decorations[0] : null; + } + set { + this.Decorations.Clear(); + if (value != null) + this.Decorations.Add(value); + } + } + + /// + /// Gets the collection of decorations that will be drawn over this item + /// + public IList Decorations { + get { + if (this.decorations == null) + this.decorations = new List(); + return this.decorations; + } + } + private IList decorations; + + /// + /// Get or set the image that should be shown against this item + /// + /// This can be an Image, a string or an int. A string or an int will + /// be used as an index into the small image list. + public Object ImageSelector { + get { return imageSelector; } + set { imageSelector = value; } + } + private Object imageSelector; + + /// + /// Gets or sets the url that should be invoked when this subitem is clicked + /// + public string Url + { + get { return this.url; } + set { this.url = value; } + } + private string url; + + /// + /// Gets or sets whether this cell is selected + /// + public bool Selected + { + get { return this.selected; } + set { this.selected = value; } + } + private bool selected; + + #endregion + + #region Implementation Properties + + /// + /// Return the state of the animation of the image on this subitem. + /// Null means there is either no image, or it is not an animation + /// + internal ImageRenderer.AnimationState AnimationState; + + #endregion + } + +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/OlvListViewHitTestInfo.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/OlvListViewHitTestInfo.cs new file mode 100644 index 0000000..1f26178 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/OlvListViewHitTestInfo.cs @@ -0,0 +1,388 @@ +/* + * OlvListViewHitTestInfo - All information gathered during a OlvHitTest() operation + * + * Author: Phillip Piper + * Date: 31-March-2011 5:53 pm + * + * Change log: + * 2011-03-31 JPP - Split into its own file + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// An indication of where a hit was within ObjectListView cell + /// + public enum HitTestLocation { + /// + /// Nowhere + /// + Nothing, + + /// + /// On the text + /// + Text, + + /// + /// On the image + /// + Image, + + /// + /// On the checkbox + /// + CheckBox, + + /// + /// On the expand button (TreeListView) + /// + ExpandButton, + + /// + /// in a button (cell must have ButtonRenderer) + /// + Button, + + /// + /// in the cell but not in any more specific location + /// + InCell, + + /// + /// UserDefined location1 (used for custom renderers) + /// + UserDefined, + + /// + /// On the expand/collapse widget of the group + /// + GroupExpander, + + /// + /// Somewhere on a group + /// + Group, + + /// + /// Somewhere in a column header + /// + Header, + + /// + /// Somewhere in a column header checkbox + /// + HeaderCheckBox, + + /// + /// Somewhere in a header divider + /// + HeaderDivider, + } + + /// + /// A collection of ListViewHitTest constants + /// + [Flags] + public enum HitTestLocationEx { + /// + /// + /// + LVHT_NOWHERE = 0x00000001, + /// + /// + /// + LVHT_ONITEMICON = 0x00000002, + /// + /// + /// + LVHT_ONITEMLABEL = 0x00000004, + /// + /// + /// + LVHT_ONITEMSTATEICON = 0x00000008, + /// + /// + /// + LVHT_ONITEM = (LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON), + + /// + /// + /// + LVHT_ABOVE = 0x00000008, + /// + /// + /// + LVHT_BELOW = 0x00000010, + /// + /// + /// + LVHT_TORIGHT = 0x00000020, + /// + /// + /// + LVHT_TOLEFT = 0x00000040, + + /// + /// + /// + LVHT_EX_GROUP_HEADER = 0x10000000, + /// + /// + /// + LVHT_EX_GROUP_FOOTER = 0x20000000, + /// + /// + /// + LVHT_EX_GROUP_COLLAPSE = 0x40000000, + /// + /// + /// + LVHT_EX_GROUP_BACKGROUND = -2147483648, // 0x80000000 + /// + /// + /// + LVHT_EX_GROUP_STATEICON = 0x01000000, + /// + /// + /// + LVHT_EX_GROUP_SUBSETLINK = 0x02000000, + /// + /// + /// + LVHT_EX_GROUP = (LVHT_EX_GROUP_BACKGROUND | LVHT_EX_GROUP_COLLAPSE | LVHT_EX_GROUP_FOOTER | LVHT_EX_GROUP_HEADER | LVHT_EX_GROUP_STATEICON | LVHT_EX_GROUP_SUBSETLINK), + /// + /// + /// + LVHT_EX_GROUP_MINUS_FOOTER_AND_BKGRD = (LVHT_EX_GROUP_COLLAPSE | LVHT_EX_GROUP_HEADER | LVHT_EX_GROUP_STATEICON | LVHT_EX_GROUP_SUBSETLINK), + /// + /// + /// + LVHT_EX_ONCONTENTS = 0x04000000, // On item AND not on the background + /// + /// + /// + LVHT_EX_FOOTER = 0x08000000, + } + + /// + /// Instances of this class encapsulate the information gathered during a OlvHitTest() + /// operation. + /// + /// Custom renderers can use HitTestLocation.UserDefined and the UserData + /// object to store more specific locations for use during event handlers. + public class OlvListViewHitTestInfo { + + /// + /// Create a OlvListViewHitTestInfo + /// + public OlvListViewHitTestInfo(OLVListItem olvListItem, OLVListSubItem subItem, int flags, OLVGroup group, int iColumn) + { + this.item = olvListItem; + this.subItem = subItem; + this.location = ConvertNativeFlagsToDotNetLocation(olvListItem, flags); + this.HitTestLocationEx = (HitTestLocationEx)flags; + this.Group = group; + this.ColumnIndex = iColumn; + this.ListView = olvListItem == null ? null : (ObjectListView)olvListItem.ListView; + + switch (location) { + case ListViewHitTestLocations.StateImage: + this.HitTestLocation = HitTestLocation.CheckBox; + break; + case ListViewHitTestLocations.Image: + this.HitTestLocation = HitTestLocation.Image; + break; + case ListViewHitTestLocations.Label: + this.HitTestLocation = HitTestLocation.Text; + break; + default: + if ((this.HitTestLocationEx & HitTestLocationEx.LVHT_EX_GROUP_COLLAPSE) == HitTestLocationEx.LVHT_EX_GROUP_COLLAPSE) + this.HitTestLocation = HitTestLocation.GroupExpander; + else if ((this.HitTestLocationEx & HitTestLocationEx.LVHT_EX_GROUP_MINUS_FOOTER_AND_BKGRD) != 0) + this.HitTestLocation = HitTestLocation.Group; + else + this.HitTestLocation = HitTestLocation.Nothing; + break; + } + } + + /// + /// Create a OlvListViewHitTestInfo when the header was hit + /// + public OlvListViewHitTestInfo(ObjectListView olv, int iColumn, bool isOverCheckBox, int iDivider) { + this.ListView = olv; + this.ColumnIndex = iColumn; + this.HeaderDividerIndex = iDivider; + this.HitTestLocation = isOverCheckBox ? HitTestLocation.HeaderCheckBox : (iDivider < 0 ? HitTestLocation.Header : HitTestLocation.HeaderDivider); + } + + private static ListViewHitTestLocations ConvertNativeFlagsToDotNetLocation(OLVListItem hitItem, int flags) + { + // Untangle base .NET behaviour. + + // In Windows SDK, the value 8 can have two meanings here: LVHT_ONITEMSTATEICON or LVHT_ABOVE. + // .NET changes these to be: + // - LVHT_ABOVE becomes ListViewHitTestLocations.AboveClientArea (which is 0x100). + // - LVHT_ONITEMSTATEICON becomes ListViewHitTestLocations.StateImage (which is 0x200). + // So, if we see the 8 bit set in flags, we change that to either a state image hit + // (if we hit an item) or to AboveClientAream if nothing was hit. + + if ((8 & flags) == 8) + return (ListViewHitTestLocations)(0xf7 & flags | (hitItem == null ? 0x100 : 0x200)); + + // Mask off the LVHT_EX_XXXX values since ListViewHitTestLocations doesn't have them + return (ListViewHitTestLocations)(flags & 0xffff); + } + + #region Public fields + + /// + /// Where is the hit location? + /// + public HitTestLocation HitTestLocation; + + /// + /// Where is the hit location? + /// + public HitTestLocationEx HitTestLocationEx; + + /// + /// Which group was hit? + /// + public OLVGroup Group; + + /// + /// Custom renderers can use this information to supply more details about the hit location + /// + public Object UserData; + + #endregion + + #region Public read-only properties + + /// + /// Gets the item that was hit + /// + public OLVListItem Item { + get { return item; } + internal set { item = value; } + } + private OLVListItem item; + + /// + /// Gets the subitem that was hit + /// + public OLVListSubItem SubItem { + get { return subItem; } + internal set { subItem = value; } + } + private OLVListSubItem subItem; + + /// + /// Gets the part of the subitem that was hit + /// + public ListViewHitTestLocations Location { + get { return location; } + internal set { location = value; } + } + private ListViewHitTestLocations location; + + /// + /// Gets the ObjectListView that was tested + /// + public ObjectListView ListView { + get { return listView; } + internal set { listView = value; } + } + private ObjectListView listView; + + /// + /// Gets the model object that was hit + /// + public Object RowObject { + get { + return this.Item == null ? null : this.Item.RowObject; + } + } + + /// + /// Gets the index of the row under the hit point or -1 + /// + public int RowIndex { + get { return this.Item == null ? -1 : this.Item.Index; } + } + + /// + /// Gets the index of the column under the hit point + /// + public int ColumnIndex { + get { return columnIndex; } + internal set { columnIndex = value; } + } + private int columnIndex; + + /// + /// Gets the index of the header divider + /// + public int HeaderDividerIndex { + get { return headerDividerIndex; } + internal set { headerDividerIndex = value; } + } + private int headerDividerIndex = -1; + + /// + /// Gets the column that was hit + /// + public OLVColumn Column { + get { + int index = this.ColumnIndex; + return index < 0 || this.ListView == null ? null : this.ListView.GetColumn(index); + } + } + + #endregion + + /// + /// Returns a string that represents the current object. + /// + /// + /// A string that represents the current object. + /// + /// 2 + public override string ToString() + { + return string.Format("HitTestLocation: {0}, HitTestLocationEx: {1}, Item: {2}, SubItem: {3}, Location: {4}, Group: {5}, ColumnIndex: {6}", + this.HitTestLocation, this.HitTestLocationEx, this.item, this.subItem, this.location, this.Group, this.ColumnIndex); + } + + internal class HeaderHitTestInfo + { + public int ColumnIndex; + public bool IsOverCheckBox; + public int OverDividerIndex; + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/TreeDataSourceAdapter.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/TreeDataSourceAdapter.cs new file mode 100644 index 0000000..a83d0ee --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/TreeDataSourceAdapter.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections; +using System.ComponentModel; +using System.Diagnostics; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// A TreeDataSourceAdapter knows how to build a tree structure from a binding list. + /// + /// To build a tree + public class TreeDataSourceAdapter : DataSourceAdapter + { + #region Life and death + + /// + /// Create a data source adaptor that knows how to build a tree structure + /// + /// + public TreeDataSourceAdapter(DataTreeListView tlv) + : base(tlv) { + this.treeListView = tlv; + this.treeListView.CanExpandGetter = delegate(object model) { return this.CalculateHasChildren(model); }; + this.treeListView.ChildrenGetter = delegate(object model) { return this.CalculateChildren(model); }; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the name of the property/column that uniquely identifies each row. + /// + /// + /// + /// The value contained by this column must be unique across all rows + /// in the data source. Odd and unpredictable things will happen if two + /// rows have the same id. + /// + /// Null cannot be a valid key value. + /// + public virtual string KeyAspectName { + get { return keyAspectName; } + set { + if (keyAspectName == value) + return; + keyAspectName = value; + this.keyMunger = new Munger(this.KeyAspectName); + this.InitializeDataSource(); + } + } + private string keyAspectName; + + /// + /// Gets or sets the name of the property/column that contains the key of + /// the parent of a row. + /// + /// + /// + /// The test condition for deciding if one row is the parent of another is functionally + /// equivalent to this: + /// + /// Object.Equals(candidateParentRow[this.KeyAspectName], row[this.ParentKeyAspectName]) + /// + /// + /// Unlike key value, parent keys can be null but a null parent key can only be used + /// to identify root objects. + /// + public virtual string ParentKeyAspectName { + get { return parentKeyAspectName; } + set { + if (parentKeyAspectName == value) + return; + parentKeyAspectName = value; + this.parentKeyMunger = new Munger(this.ParentKeyAspectName); + this.InitializeDataSource(); + } + } + private string parentKeyAspectName; + + /// + /// Gets or sets the value that identifies a row as a root object. + /// When the ParentKey of a row equals the RootKeyValue, that row will + /// be treated as root of the TreeListView. + /// + /// + /// + /// The test condition for deciding a root object is functionally + /// equivalent to this: + /// + /// Object.Equals(candidateRow[this.ParentKeyAspectName], this.RootKeyValue) + /// + /// + /// The RootKeyValue can be null. + /// + public virtual object RootKeyValue { + get { return rootKeyValue; } + set { + if (Equals(rootKeyValue, value)) + return; + rootKeyValue = value; + this.InitializeDataSource(); + } + } + private object rootKeyValue; + + /// + /// Gets or sets whether or not the key columns (id and parent id) should + /// be shown to the user. + /// + /// This must be set before the DataSource is set. It has no effect + /// afterwards. + public virtual bool ShowKeyColumns { + get { return showKeyColumns; } + set { showKeyColumns = value; } + } + private bool showKeyColumns = true; + + + #endregion + + #region Implementation properties + + /// + /// Gets the DataTreeListView that is being managed + /// + protected DataTreeListView TreeListView { + get { return treeListView; } + } + private readonly DataTreeListView treeListView; + + #endregion + + #region Implementation + + /// + /// + /// + protected override void InitializeDataSource() { + base.InitializeDataSource(); + this.TreeListView.RebuildAll(true); + } + + /// + /// + /// + protected override void SetListContents() { + this.TreeListView.Roots = this.CalculateRoots(); + } + + /// + /// + /// + /// + /// + protected override bool ShouldCreateColumn(PropertyDescriptor property) { + // If the property is a key column, and we aren't supposed to show keys, don't show it + if (!this.ShowKeyColumns && (property.Name == this.KeyAspectName || property.Name == this.ParentKeyAspectName)) + return false; + + return base.ShouldCreateColumn(property); + } + + /// + /// + /// + /// + protected override void HandleListChangedItemChanged(System.ComponentModel.ListChangedEventArgs e) { + // If the id or the parent id of a row changes, we just rebuild everything. + // We can't do anything more specific. We don't know what the previous values, so we can't + // tell the previous parent to refresh itself. If the id itself has changed, things that used + // to be children will no longer be children. Just rebuild everything. + // It seems PropertyDescriptor is only filled in .NET 4 :( + if (e.PropertyDescriptor != null && + (e.PropertyDescriptor.Name == this.KeyAspectName || + e.PropertyDescriptor.Name == this.ParentKeyAspectName)) + this.InitializeDataSource(); + else + base.HandleListChangedItemChanged(e); + } + + /// + /// + /// + /// + protected override void ChangePosition(int index) { + // We can't use our base method directly, since the normal position management + // doesn't know about our tree structure. They treat our dataset as a flat list + // but we have a collapsible structure. This means that the 5'th row to them + // may not even be visible to us + + // To display the n'th row, we have to make sure that all its ancestors + // are expanded. Then we will be able to select it. + object model = this.CurrencyManager.List[index]; + object parent = this.CalculateParent(model); + while (parent != null && !this.TreeListView.IsExpanded(parent)) { + this.TreeListView.Expand(parent); + parent = this.CalculateParent(parent); + } + + base.ChangePosition(index); + } + + private IEnumerable CalculateRoots() { + foreach (object x in this.CurrencyManager.List) { + object parentKey = this.GetParentValue(x); + if (Object.Equals(this.RootKeyValue, parentKey)) + yield return x; + } + } + + private bool CalculateHasChildren(object model) { + object keyValue = this.GetKeyValue(model); + if (keyValue == null) + return false; + + foreach (object x in this.CurrencyManager.List) { + object parentKey = this.GetParentValue(x); + if (Object.Equals(keyValue, parentKey)) + return true; + } + return false; + } + + private IEnumerable CalculateChildren(object model) { + object keyValue = this.GetKeyValue(model); + if (keyValue != null) { + foreach (object x in this.CurrencyManager.List) { + object parentKey = this.GetParentValue(x); + if (Object.Equals(keyValue, parentKey)) + yield return x; + } + } + } + + private object CalculateParent(object model) { + object parentValue = this.GetParentValue(model); + if (parentValue == null) + return null; + + foreach (object x in this.CurrencyManager.List) { + object key = this.GetKeyValue(x); + if (Object.Equals(parentValue, key)) + return x; + } + return null; + } + + private object GetKeyValue(object model) { + return this.keyMunger == null ? null : this.keyMunger.GetValue(model); + } + + private object GetParentValue(object model) { + return this.parentKeyMunger == null ? null : this.parentKeyMunger.GetValue(model); + } + + #endregion + + private Munger keyMunger; + private Munger parentKeyMunger; + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/VirtualGroups.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/VirtualGroups.cs new file mode 100644 index 0000000..303d157 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/VirtualGroups.cs @@ -0,0 +1,341 @@ +/* + * Virtual groups - Classes and interfaces needed to implement virtual groups + * + * Author: Phillip Piper + * Date: 28/08/2009 11:10am + * + * Change log: + * 2011-02-21 JPP - Correctly honor group comparer and collapsible groups settings + * v2.3 + * 2009-08-28 JPP - Initial version + * + * To do: + * + * Copyright (C) 2009-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Windows.Forms; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// A IVirtualGroups is the interface that a virtual list must implement to support virtual groups + /// + public interface IVirtualGroups + { + /// + /// Return the list of groups that should be shown according to the given parameters + /// + /// + /// + IList GetGroups(GroupingParameters parameters); + + /// + /// Return the index of the item that appears at the given position within the given group. + /// + /// + /// + /// + int GetGroupMember(OLVGroup group, int indexWithinGroup); + + /// + /// Return the index of the group to which the given item belongs + /// + /// + /// + int GetGroup(int itemIndex); + + /// + /// Return the index at which the given item is shown in the given group + /// + /// + /// + /// + int GetIndexWithinGroup(OLVGroup group, int itemIndex); + + /// + /// A hint that the given range of items are going to be required + /// + /// + /// + /// + /// + void CacheHint(int fromGroupIndex, int fromIndex, int toGroupIndex, int toIndex); + } + + /// + /// This is a safe, do nothing implementation of a grouping strategy + /// + public class AbstractVirtualGroups : IVirtualGroups + { + /// + /// Return the list of groups that should be shown according to the given parameters + /// + /// + /// + public virtual IList GetGroups(GroupingParameters parameters) { + return new List(); + } + + /// + /// Return the index of the item that appears at the given position within the given group. + /// + /// + /// + /// + public virtual int GetGroupMember(OLVGroup group, int indexWithinGroup) { + return -1; + } + + /// + /// Return the index of the group to which the given item belongs + /// + /// + /// + public virtual int GetGroup(int itemIndex) { + return -1; + } + + /// + /// Return the index at which the given item is shown in the given group + /// + /// + /// + /// + public virtual int GetIndexWithinGroup(OLVGroup group, int itemIndex) { + return -1; + } + + /// + /// A hint that the given range of items are going to be required + /// + /// + /// + /// + /// + public virtual void CacheHint(int fromGroupIndex, int fromIndex, int toGroupIndex, int toIndex) { + } + } + + + /// + /// Provides grouping functionality to a FastObjectListView + /// + public class FastListGroupingStrategy : AbstractVirtualGroups + { + /// + /// Create groups for FastListView + /// + /// + /// + public override IList GetGroups(GroupingParameters parameters) { + + // There is a lot of overlap between this method and ObjectListView.MakeGroups() + // Any changes made here may need to be reflected there + + // This strategy can only be used on FastObjectListViews + FastObjectListView folv = (FastObjectListView)parameters.ListView; + + // Separate the list view items into groups, using the group key as the descrimanent + int objectCount = 0; + NullableDictionary> map = new NullableDictionary>(); + foreach (object model in folv.FilteredObjects) { + object key = parameters.GroupByColumn.GetGroupKey(model); + if (!map.ContainsKey(key)) + map[key] = new List(); + map[key].Add(model); + objectCount++; + } + + // Sort the items within each group + OLVColumn primarySortColumn = parameters.SortItemsByPrimaryColumn ? parameters.ListView.GetColumn(0) : parameters.PrimarySort; + ModelObjectComparer sorter = new ModelObjectComparer(primarySortColumn, parameters.PrimarySortOrder, + parameters.SecondarySort, parameters.SecondarySortOrder); + foreach (object key in map.Keys) { + map[key].Sort(sorter); + } + + // Make a list of the required groups + List groups = new List(); + foreach (object key in map.Keys) { + OLVGroup lvg = parameters.CreateGroup(key, map[key].Count, folv.HasCollapsibleGroups); + lvg.Contents = map[key].ConvertAll(delegate(object x) { return folv.IndexOf(x); }); + lvg.VirtualItemCount = map[key].Count; + if (parameters.GroupByColumn.GroupFormatter != null) + parameters.GroupByColumn.GroupFormatter(lvg, parameters); + groups.Add(lvg); + } + + // Sort the groups + if (parameters.GroupByOrder != SortOrder.None) + groups.Sort(parameters.GroupComparer ?? new OLVGroupComparer(parameters.GroupByOrder)); + + // Build an array that remembers which group each item belongs to. + this.indexToGroupMap = new List(objectCount); + this.indexToGroupMap.AddRange(new int[objectCount]); + + for (int i = 0; i < groups.Count; i++) { + OLVGroup group = groups[i]; + List members = (List)group.Contents; + foreach (int j in members) + this.indexToGroupMap[j] = i; + } + + return groups; + } + private List indexToGroupMap; + + /// + /// + /// + /// + /// + /// + public override int GetGroupMember(OLVGroup group, int indexWithinGroup) { + return (int)group.Contents[indexWithinGroup]; + } + + /// + /// + /// + /// + /// + public override int GetGroup(int itemIndex) { + return this.indexToGroupMap[itemIndex]; + } + + /// + /// + /// + /// + /// + /// + public override int GetIndexWithinGroup(OLVGroup group, int itemIndex) { + return group.Contents.IndexOf(itemIndex); + } + } + + + /// + /// This is the COM interface that a ListView must be given in order for groups in virtual lists to work. + /// + /// + /// This interface is NOT documented by MS. It was found on Greg Chapell's site. This means that there is + /// no guarantee that it will work on future versions of Windows, nor continue to work on current ones. + /// + [ComImport(), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown), + Guid("44C09D56-8D3B-419D-A462-7B956B105B47")] + internal interface IOwnerDataCallback + { + /// + /// Not sure what this does + /// + /// + /// + void GetItemPosition(int i, out NativeMethods.POINT pt); + + /// + /// Not sure what this does + /// + /// + /// + void SetItemPosition(int t, NativeMethods.POINT pt); + + /// + /// Get the index of the item that occurs at the n'th position of the indicated group. + /// + /// Index of the group + /// Index within the group + /// Index of the item within the whole list + void GetItemInGroup(int groupIndex, int n, out int itemIndex); + + /// + /// Get the index of the group to which the given item belongs + /// + /// Index of the item within the whole list + /// Which occurrences of the item is wanted + /// Index of the group + void GetItemGroup(int itemIndex, int occurrenceCount, out int groupIndex); + + /// + /// Get the number of groups that contain the given item + /// + /// Index of the item within the whole list + /// How many groups does it occur within + void GetItemGroupCount(int itemIndex, out int occurrenceCount); + + /// + /// A hint to prepare any cache for the given range of requests + /// + /// + /// + void OnCacheHint(NativeMethods.LVITEMINDEX i, NativeMethods.LVITEMINDEX j); + } + + /// + /// A default implementation of the IOwnerDataCallback interface + /// + [Guid("6FC61F50-80E8-49b4-B200-3F38D3865ABD")] + internal class OwnerDataCallbackImpl : IOwnerDataCallback + { + public OwnerDataCallbackImpl(VirtualObjectListView olv) { + this.olv = olv; + } + VirtualObjectListView olv; + + #region IOwnerDataCallback Members + + public void GetItemPosition(int i, out NativeMethods.POINT pt) { + //System.Diagnostics.Debug.WriteLine("GetItemPosition"); + throw new NotSupportedException(); + } + + public void SetItemPosition(int t, NativeMethods.POINT pt) { + //System.Diagnostics.Debug.WriteLine("SetItemPosition"); + throw new NotSupportedException(); + } + + public void GetItemInGroup(int groupIndex, int n, out int itemIndex) { + //System.Diagnostics.Debug.WriteLine(String.Format("-> GetItemInGroup({0}, {1})", groupIndex, n)); + itemIndex = this.olv.GroupingStrategy.GetGroupMember(this.olv.OLVGroups[groupIndex], n); + //System.Diagnostics.Debug.WriteLine(String.Format("<- {0}", itemIndex)); + } + + public void GetItemGroup(int itemIndex, int occurrenceCount, out int groupIndex) { + //System.Diagnostics.Debug.WriteLine(String.Format("GetItemGroup({0}, {1})", itemIndex, occurrenceCount)); + groupIndex = this.olv.GroupingStrategy.GetGroup(itemIndex); + //System.Diagnostics.Debug.WriteLine(String.Format("<- {0}", groupIndex)); + } + + public void GetItemGroupCount(int itemIndex, out int occurrenceCount) { + //System.Diagnostics.Debug.WriteLine(String.Format("GetItemGroupCount({0})", itemIndex)); + occurrenceCount = 1; + } + + public void OnCacheHint(NativeMethods.LVITEMINDEX from, NativeMethods.LVITEMINDEX to) { + //System.Diagnostics.Debug.WriteLine(String.Format("OnCacheHint({0}, {1}, {2}, {3})", from.iGroup, from.iItem, to.iGroup, to.iItem)); + this.olv.GroupingStrategy.CacheHint(from.iGroup, from.iItem, to.iGroup, to.iItem); + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Implementation/VirtualListDataSource.cs b/VG Music Studio - WinForms/ObjectListView/Implementation/VirtualListDataSource.cs new file mode 100644 index 0000000..374c738 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Implementation/VirtualListDataSource.cs @@ -0,0 +1,349 @@ +/* + * VirtualListDataSource - Encapsulate how data is provided to a virtual list + * + * Author: Phillip Piper + * Date: 28/08/2009 11:10am + * + * Change log: + * v2.4 + * 2010-04-01 JPP - Added IFilterableDataSource + * v2.3 + * 2009-08-28 JPP - Initial version (Separated from VirtualObjectListView.cs) + * + * To do: + * + * Copyright (C) 2009-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// A VirtualListDataSource is a complete manner to provide functionality to a virtual list. + /// An object that implements this interface provides a VirtualObjectListView with all the + /// information it needs to be fully functional. + /// + /// Implementors must provide functioning implementations of at least GetObjectCount() + /// and GetNthObject(), otherwise nothing will appear in the list. + public interface IVirtualListDataSource + { + /// + /// Return the object that should be displayed at the n'th row. + /// + /// The index of the row whose object is to be returned. + /// The model object at the n'th row, or null if the fetching was unsuccessful. + Object GetNthObject(int n); + + /// + /// Return the number of rows that should be visible in the virtual list + /// + /// The number of rows the list view should have. + int GetObjectCount(); + + /// + /// Get the index of the row that is showing the given model object + /// + /// The model object sought + /// The index of the row showing the model, or -1 if the object could not be found. + int GetObjectIndex(Object model); + + /// + /// The ListView is about to request the given range of items. Do + /// whatever caching seems appropriate. + /// + /// + /// + void PrepareCache(int first, int last); + + /// + /// Find the first row that "matches" the given text in the given range. + /// + /// The text typed by the user + /// Start searching from this index. This may be greater than the 'to' parameter, + /// in which case the search should descend + /// Do not search beyond this index. This may be less than the 'from' parameter. + /// The column that should be considered when looking for a match. + /// Return the index of row that was matched, or -1 if no match was found + int SearchText(string value, int first, int last, OLVColumn column); + + /// + /// Sort the model objects in the data source. + /// + /// + /// + void Sort(OLVColumn column, SortOrder order); + + //----------------------------------------------------------------------------------- + // Modification commands + // THINK: Should we split these four into a separate interface? + + /// + /// Add the given collection of model objects to this control. + /// + /// A collection of model objects + void AddObjects(ICollection modelObjects); + + /// + /// Insert the given collection of model objects to this control at the position + /// + /// Index where the collection will be added + /// A collection of model objects + void InsertObjects(int index, ICollection modelObjects); + + /// + /// Remove all of the given objects from the control + /// + /// Collection of objects to be removed + void RemoveObjects(ICollection modelObjects); + + /// + /// Set the collection of objects that this control will show. + /// + /// + void SetObjects(IEnumerable collection); + + /// + /// Update/replace the nth object with the given object + /// + /// + /// + void UpdateObject(int index, object modelObject); + } + + /// + /// This extension allow virtual lists to filter their contents + /// + public interface IFilterableDataSource + { + /// + /// All subsequent retrievals on this data source should be filtered + /// through the given filters. null means no filtering of that kind. + /// + /// + /// + void ApplyFilters(IModelFilter modelFilter, IListFilter listFilter); + } + + /// + /// A do-nothing implementation of the VirtualListDataSource interface. + /// + public class AbstractVirtualListDataSource : IVirtualListDataSource, IFilterableDataSource + { + /// + /// Creates an AbstractVirtualListDataSource + /// + /// + public AbstractVirtualListDataSource(VirtualObjectListView listView) { + this.listView = listView; + } + + /// + /// The list view that this data source is giving information to. + /// + protected VirtualObjectListView listView; + + /// + /// + /// + /// + /// + public virtual object GetNthObject(int n) { + return null; + } + + /// + /// + /// + /// + public virtual int GetObjectCount() { + return -1; + } + + /// + /// + /// + /// + /// + public virtual int GetObjectIndex(object model) { + return -1; + } + + /// + /// + /// + /// + /// + public virtual void PrepareCache(int from, int to) { + } + + /// + /// + /// + /// + /// + /// + /// + /// + public virtual int SearchText(string value, int first, int last, OLVColumn column) { + return -1; + } + + /// + /// + /// + /// + /// + public virtual void Sort(OLVColumn column, SortOrder order) { + } + + /// + /// + /// + /// + public virtual void AddObjects(ICollection modelObjects) { + } + + /// + /// + /// + /// + /// + public virtual void InsertObjects(int index, ICollection modelObjects) { + } + + /// + /// + /// + /// + public virtual void RemoveObjects(ICollection modelObjects) { + } + + /// + /// + /// + /// + public virtual void SetObjects(IEnumerable collection) { + } + + /// + /// Update/replace the nth object with the given object + /// + /// + /// + public virtual void UpdateObject(int index, object modelObject) { + } + + /// + /// This is a useful default implementation of SearchText method, intended to be called + /// by implementors of IVirtualListDataSource. + /// + /// + /// + /// + /// + /// + /// + static public int DefaultSearchText(string value, int first, int last, OLVColumn column, IVirtualListDataSource source) { + if (first <= last) { + for (int i = first; i <= last; i++) { + string data = column.GetStringValue(source.GetNthObject(i)); + if (data.StartsWith(value, StringComparison.CurrentCultureIgnoreCase)) + return i; + } + } else { + for (int i = first; i >= last; i--) { + string data = column.GetStringValue(source.GetNthObject(i)); + if (data.StartsWith(value, StringComparison.CurrentCultureIgnoreCase)) + return i; + } + } + + return -1; + } + + #region IFilterableDataSource Members + + /// + /// + /// + /// + /// + virtual public void ApplyFilters(IModelFilter modelFilter, IListFilter listFilter) { + } + + #endregion + } + + /// + /// This class mimics the behavior of VirtualObjectListView v1.x. + /// + public class VirtualListVersion1DataSource : AbstractVirtualListDataSource + { + /// + /// Creates a VirtualListVersion1DataSource + /// + /// + public VirtualListVersion1DataSource(VirtualObjectListView listView) + : base(listView) { + } + + #region Public properties + + /// + /// How will the n'th object of the data source be fetched? + /// + public RowGetterDelegate RowGetter { + get { return rowGetter; } + set { rowGetter = value; } + } + private RowGetterDelegate rowGetter; + + #endregion + + #region IVirtualListDataSource implementation + + /// + /// + /// + /// + /// + public override object GetNthObject(int n) { + if (this.RowGetter == null) + return null; + else + return this.RowGetter(n); + } + + /// + /// + /// + /// + /// + /// + /// + /// + public override int SearchText(string value, int first, int last, OLVColumn column) { + return DefaultSearchText(value, first, last, column, this); + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/OLVColumn.cs b/VG Music Studio - WinForms/ObjectListView/OLVColumn.cs new file mode 100644 index 0000000..d0601ed --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/OLVColumn.cs @@ -0,0 +1,1909 @@ +/* + * OLVColumn - A column in an ObjectListView + * + * Author: Phillip Piper + * Date: 31-March-2011 5:53 pm + * + * Change log: + * 2018-05-05 JPP - Added EditorCreator to OLVColumn + * 2015-06-12 JPP - HeaderTextAlign became nullable so that it can be "not set" (this was always the intent) + * 2014-09-07 JPP - Added ability to have checkboxes in headers + * + * 2011-05-27 JPP - Added Sortable, Hideable, Groupable, Searchable, ShowTextInHeader properties + * 2011-04-12 JPP - Added HasFilterIndicator + * 2011-03-31 JPP - Split into its own file + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.ComponentModel; +using System.Windows.Forms; +using System.Drawing; +using System.Collections; +using System.Diagnostics; +using System.Drawing.Design; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + // TODO + //[TypeConverter(typeof(ExpandableObjectConverter))] + //public class CheckBoxSettings + //{ + // private bool useSettings; + // private Image checkedImage; + + // public bool UseSettings { + // get { return useSettings; } + // set { useSettings = value; } + // } + + // public Image CheckedImage { + // get { return checkedImage; } + // set { checkedImage = value; } + // } + + // public Image UncheckedImage { + // get { return checkedImage; } + // set { checkedImage = value; } + // } + + // public Image IndeterminateImage { + // get { return checkedImage; } + // set { checkedImage = value; } + // } + //} + + /// + /// An OLVColumn knows which aspect of an object it should present. + /// + /// + /// The column knows how to: + /// + /// extract its aspect from the row object + /// convert an aspect to a string + /// calculate the image for the row object + /// extract a group "key" from the row object + /// convert a group "key" into a title for the group + /// + /// For sorting to work correctly, aspects from the same column + /// must be of the same type, that is, the same aspect cannot sometimes + /// return strings and other times integers. + /// + [Browsable(false)] + public partial class OLVColumn : ColumnHeader { + + /// + /// How should the button be sized? + /// + public enum ButtonSizingMode + { + /// + /// Every cell will have the same sized button, as indicated by ButtonSize property + /// + FixedBounds, + + /// + /// Every cell will draw a button that fills the cell, inset by ButtonPadding + /// + CellBounds, + + /// + /// Each button will be resized to contain the text of the Aspect + /// + TextBounds + } + + #region Life and death + + /// + /// Create an OLVColumn + /// + public OLVColumn() { + } + + /// + /// Initialize a column to have the given title, and show the given aspect + /// + /// The title of the column + /// The aspect to be shown in the column + public OLVColumn(string title, string aspect) + : this() { + this.Text = title; + this.AspectName = aspect; + } + + #endregion + + #region Public Properties + + /// + /// This delegate will be used to extract a value to be displayed in this column. + /// + /// + /// If this is set, AspectName is ignored. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public AspectGetterDelegate AspectGetter { + get { return aspectGetter; } + set { aspectGetter = value; } + } + private AspectGetterDelegate aspectGetter; + + /// + /// Remember if this aspect getter for this column was generated internally, and can therefore + /// be regenerated at will + /// + [Obsolete("This property is no longer maintained", true), + Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool AspectGetterAutoGenerated { + get { return aspectGetterAutoGenerated; } + set { aspectGetterAutoGenerated = value; } + } + private bool aspectGetterAutoGenerated; + + /// + /// The name of the property or method that should be called to get the value to display in this column. + /// This is only used if a ValueGetterDelegate has not been given. + /// + /// This name can be dotted to chain references to properties or parameter-less methods. + /// "DateOfBirth" + /// "Owner.HomeAddress.Postcode" + [Category("ObjectListView"), + Description("The name of the property or method that should be called to get the aspect to display in this column"), + DefaultValue(null)] + public string AspectName { + get { return aspectName; } + set { + aspectName = value; + this.aspectMunger = null; + } + } + private string aspectName; + + /// + /// This delegate will be used to put an edited value back into the model object. + /// + /// + /// This does nothing if IsEditable == false. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public AspectPutterDelegate AspectPutter { + get { return aspectPutter; } + set { aspectPutter = value; } + } + private AspectPutterDelegate aspectPutter; + + /// + /// The delegate that will be used to translate the aspect to display in this column into a string. + /// + /// If this value is set, AspectToStringFormat will be ignored. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public AspectToStringConverterDelegate AspectToStringConverter { + get { return aspectToStringConverter; } + set { aspectToStringConverter = value; } + } + private AspectToStringConverterDelegate aspectToStringConverter; + + /// + /// This format string will be used to convert an aspect to its string representation. + /// + /// + /// This string is passed as the first parameter to the String.Format() method. + /// This is only used if AspectToStringConverter has not been set. + /// "{0:C}" to convert a number to currency + [Category("ObjectListView"), + Description("The format string that will be used to convert an aspect to its string representation"), + DefaultValue(null)] + public string AspectToStringFormat { + get { return aspectToStringFormat; } + set { aspectToStringFormat = value; } + } + private string aspectToStringFormat; + + /// + /// Gets or sets whether the cell editor should use AutoComplete + /// + [Category("ObjectListView"), + Description("Should the editor for cells of this column use AutoComplete"), + DefaultValue(true)] + public bool AutoCompleteEditor { + get { return this.AutoCompleteEditorMode != AutoCompleteMode.None; } + set { + if (value) { + if (this.AutoCompleteEditorMode == AutoCompleteMode.None) + this.AutoCompleteEditorMode = AutoCompleteMode.Append; + } else + this.AutoCompleteEditorMode = AutoCompleteMode.None; + } + } + + /// + /// Gets or sets whether the cell editor should use AutoComplete + /// + [Category("ObjectListView"), + Description("Should the editor for cells of this column use AutoComplete"), + DefaultValue(AutoCompleteMode.Append)] + public AutoCompleteMode AutoCompleteEditorMode { + get { return autoCompleteEditorMode; } + set { autoCompleteEditorMode = value; } + } + private AutoCompleteMode autoCompleteEditorMode = AutoCompleteMode.Append; + + /// + /// Gets whether this column can be hidden by user actions + /// + /// This take into account both the Hideable property and whether this column + /// is the primary column of the listview (column 0). + [Browsable(false)] + public bool CanBeHidden { + get { + return this.Hideable && (this.Index != 0); + } + } + + /// + /// When a cell is edited, should the whole cell be used (minus any space used by checkbox or image)? + /// + /// + /// This is always treated as true when the control is NOT owner drawn. + /// + /// When this is false (the default) and the control is owner drawn, + /// ObjectListView will try to calculate the width of the cell's + /// actual contents, and then size the editing control to be just the right width. If this is true, + /// the whole width of the cell will be used, regardless of the cell's contents. + /// + /// If this property is not set on the column, the value from the control will be used + /// + /// This value is only used when the control is in Details view. + /// Regardless of this setting, developers can specify the exact size of the editing control + /// by listening for the CellEditStarting event. + /// + [Category("ObjectListView"), + Description("When a cell is edited, should the whole cell be used?"), + DefaultValue(null)] + public virtual bool? CellEditUseWholeCell + { + get { return cellEditUseWholeCell; } + set { cellEditUseWholeCell = value; } + } + private bool? cellEditUseWholeCell; + + /// + /// Get whether the whole cell should be used when editing a cell in this column + /// + /// This calculates the current effective value, which may be different to CellEditUseWholeCell + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual bool CellEditUseWholeCellEffective { + get { + bool? columnSpecificValue = this.ListView.View == View.Details ? this.CellEditUseWholeCell : (bool?) null; + return (columnSpecificValue ?? ((ObjectListView) this.ListView).CellEditUseWholeCell); + } + } + + /// + /// Gets or sets how many pixels will be left blank around this cells in this column + /// + /// This setting only takes effect when the control is owner drawn. + [Category("ObjectListView"), + Description("How many pixels will be left blank around the cells in this column?"), + DefaultValue(null)] + public Rectangle? CellPadding + { + get { return this.cellPadding; } + set { this.cellPadding = value; } + } + private Rectangle? cellPadding; + + /// + /// Gets or sets how cells in this column will be vertically aligned. + /// + /// + /// + /// This setting only takes effect when the control is owner drawn. + /// + /// + /// If this is not set, the value from the control itself will be used. + /// + /// + [Category("ObjectListView"), + Description("How will cell values be vertically aligned?"), + DefaultValue(null)] + public virtual StringAlignment? CellVerticalAlignment { + get { return this.cellVerticalAlignment; } + set { this.cellVerticalAlignment = value; } + } + private StringAlignment? cellVerticalAlignment; + + /// + /// Gets or sets whether this column will show a checkbox. + /// + /// + /// Setting this on column 0 has no effect. Column 0 check box is controlled + /// by the CheckBoxes property on the ObjectListView itself. + /// + [Category("ObjectListView"), + Description("Should values in this column be treated as a checkbox, rather than a string?"), + DefaultValue(false)] + public virtual bool CheckBoxes { + get { return checkBoxes; } + set { + if (this.checkBoxes == value) + return; + + this.checkBoxes = value; + if (this.checkBoxes) { + if (this.Renderer == null) + this.Renderer = new CheckStateRenderer(); + } else { + if (this.Renderer is CheckStateRenderer) + this.Renderer = null; + } + } + } + private bool checkBoxes; + + /// + /// Gets or sets the clustering strategy used for this column. + /// + /// + /// + /// The clustering strategy is used to build a Filtering menu for this item. + /// If this is null, a useful default will be chosen. + /// + /// + /// To disable filtering on this column, set UseFiltering to false. + /// + /// + /// Cluster strategies belong to a particular column. The same instance + /// cannot be shared between multiple columns. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IClusteringStrategy ClusteringStrategy { + get { + if (this.clusteringStrategy == null) + this.ClusteringStrategy = this.DecideDefaultClusteringStrategy(); + return clusteringStrategy; + } + set { + this.clusteringStrategy = value; + if (this.clusteringStrategy != null) + this.clusteringStrategy.Column = this; + } + } + private IClusteringStrategy clusteringStrategy; + + /// + /// Gets or sets a delegate that will create an editor for a cell in this column. + /// + /// + /// If you need different editors for different cells in the same column, this + /// delegate is your solution. Return null to use the default editor for the cell. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public EditorCreatorDelegate EditorCreator { + get { return editorCreator; } + set { editorCreator = value; } + } + private EditorCreatorDelegate editorCreator; + + /// + /// Gets or sets whether the button in this column (if this column is drawing buttons) will be enabled + /// even if the row itself is disabled + /// + [Category("ObjectListView"), + Description("If this column contains a button, should the button be enabled even if the row is disabled?"), + DefaultValue(false)] + public bool EnableButtonWhenItemIsDisabled + { + get { return this.enableButtonWhenItemIsDisabled; } + set { this.enableButtonWhenItemIsDisabled = value; } + } + private bool enableButtonWhenItemIsDisabled; + + /// + /// Should this column resize to fill the free space in the listview? + /// + /// + /// + /// If you want two (or more) columns to equally share the available free space, set this property to True. + /// If you want this column to have a larger or smaller share of the free space, you must + /// set the FreeSpaceProportion property explicitly. + /// + /// + /// Space filling columns are still governed by the MinimumWidth and MaximumWidth properties. + /// + /// /// + [Category("ObjectListView"), + Description("Will this column resize to fill unoccupied horizontal space in the listview?"), + DefaultValue(false)] + public bool FillsFreeSpace { + get { return this.FreeSpaceProportion > 0; } + set { this.FreeSpaceProportion = value ? 1 : 0; } + } + + /// + /// What proportion of the unoccupied horizontal space in the control should be given to this column? + /// + /// + /// + /// There are situations where it would be nice if a column (normally the rightmost one) would expand as + /// the list view expands, so that as much of the column was visible as possible without having to scroll + /// horizontally (you should never, ever make your users have to scroll anything horizontally!). + /// + /// + /// A space filling column is resized to occupy a proportion of the unoccupied width of the listview (the + /// unoccupied width is the width left over once all the non-filling columns have been given their space). + /// This property indicates the relative proportion of that unoccupied space that will be given to this column. + /// The actual value of this property is not important -- only its value relative to the value in other columns. + /// For example: + /// + /// + /// If there is only one space filling column, it will be given all the free space, regardless of the value in FreeSpaceProportion. + /// + /// + /// If there are two or more space filling columns and they all have the same value for FreeSpaceProportion, + /// they will share the free space equally. + /// + /// + /// If there are three space filling columns with values of 3, 2, and 1 + /// for FreeSpaceProportion, then the first column with occupy half the free space, the second will + /// occupy one-third of the free space, and the third column one-sixth of the free space. + /// + /// + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int FreeSpaceProportion { + get { return freeSpaceProportion; } + set { freeSpaceProportion = Math.Max(0, value); } + } + private int freeSpaceProportion; + + /// + /// Gets or sets whether groups will be rebuild on this columns values when this column's header is clicked. + /// + /// + /// This setting is only used when ShowGroups is true. + /// + /// If this is false, clicking the header will not rebuild groups. It will not provide + /// any feedback as to why the list is not being regrouped. It is the programmers responsibility to + /// provide appropriate feedback. + /// + /// When this is false, BeforeCreatingGroups events are still fired, which can be used to allow grouping + /// or give feedback, on a case by case basis. + /// + [Category("ObjectListView"), + Description("Will the list create groups when this header is clicked?"), + DefaultValue(true)] + public bool Groupable { + get { return groupable; } + set { groupable = value; } + } + private bool groupable = true; + + /// + /// This delegate is called when a group has been created but not yet made + /// into a real ListViewGroup. The user can take this opportunity to fill + /// in lots of other details about the group. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public GroupFormatterDelegate GroupFormatter { + get { return groupFormatter; } + set { groupFormatter = value; } + } + private GroupFormatterDelegate groupFormatter; + + /// + /// This delegate is called to get the object that is the key for the group + /// to which the given row belongs. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public GroupKeyGetterDelegate GroupKeyGetter { + get { return groupKeyGetter; } + set { groupKeyGetter = value; } + } + private GroupKeyGetterDelegate groupKeyGetter; + + /// + /// This delegate is called to convert a group key into a title for that group. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public GroupKeyToTitleConverterDelegate GroupKeyToTitleConverter { + get { return groupKeyToTitleConverter; } + set { groupKeyToTitleConverter = value; } + } + private GroupKeyToTitleConverterDelegate groupKeyToTitleConverter; + + /// + /// When the listview is grouped by this column and group title has an item count, + /// how should the label be formatted? + /// + /// + /// The given format string can/should have two placeholders: + /// + /// {0} - the original group title + /// {1} - the number of items in the group + /// + /// + /// "{0} [{1} items]" + [Category("ObjectListView"), + Description("The format to use when suffixing item counts to group titles"), + DefaultValue(null), + Localizable(true)] + public string GroupWithItemCountFormat { + get { return groupWithItemCountFormat; } + set { groupWithItemCountFormat = value; } + } + private string groupWithItemCountFormat; + + /// + /// Gets this.GroupWithItemCountFormat or a reasonable default + /// + /// + /// If GroupWithItemCountFormat is not set, its value will be taken from the ObjectListView if possible. + /// + [Browsable(false)] + public string GroupWithItemCountFormatOrDefault { + get { + if (!String.IsNullOrEmpty(this.GroupWithItemCountFormat)) + return this.GroupWithItemCountFormat; + + if (this.ListView != null) { + cachedGroupWithItemCountFormat = ((ObjectListView)this.ListView).GroupWithItemCountFormatOrDefault; + return cachedGroupWithItemCountFormat; + } + + // There is one rare but pathologically possible case where the ListView can + // be null (if the column is grouping a ListView, but is not one of the columns + // for that ListView) so we have to provide a workable default for that rare case. + return cachedGroupWithItemCountFormat ?? "{0} [{1} items]"; + } + } + private string cachedGroupWithItemCountFormat; + + /// + /// When the listview is grouped by this column and a group title has an item count, + /// how should the label be formatted if there is only one item in the group? + /// + /// + /// The given format string can/should have two placeholders: + /// + /// {0} - the original group title + /// {1} - the number of items in the group (always 1) + /// + /// + /// "{0} [{1} item]" + [Category("ObjectListView"), + Description("The format to use when suffixing item counts to group titles"), + DefaultValue(null), + Localizable(true)] + public string GroupWithItemCountSingularFormat { + get { return groupWithItemCountSingularFormat; } + set { groupWithItemCountSingularFormat = value; } + } + private string groupWithItemCountSingularFormat; + + /// + /// Get this.GroupWithItemCountSingularFormat or a reasonable default + /// + /// + /// If this value is not set, the values from the list view will be used + /// + [Browsable(false)] + public string GroupWithItemCountSingularFormatOrDefault { + get { + if (!String.IsNullOrEmpty(this.GroupWithItemCountSingularFormat)) + return this.GroupWithItemCountSingularFormat; + + if (this.ListView != null) { + cachedGroupWithItemCountSingularFormat = ((ObjectListView)this.ListView).GroupWithItemCountSingularFormatOrDefault; + return cachedGroupWithItemCountSingularFormat; + } + + // There is one rare but pathologically possible case where the ListView can + // be null (if the column is grouping a ListView, but is not one of the columns + // for that ListView) so we have to provide a workable default for that rare case. + return cachedGroupWithItemCountSingularFormat ?? "{0} [{1} item]"; + } + } + private string cachedGroupWithItemCountSingularFormat; + + /// + /// Gets whether this column should be drawn with a filter indicator in the column header. + /// + [Browsable(false)] + public bool HasFilterIndicator { + get { + return this.UseFiltering && this.ValuesChosenForFiltering != null && this.ValuesChosenForFiltering.Count > 0; + } + } + + /// + /// Gets or sets a delegate that will be used to own draw header column. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public HeaderDrawingDelegate HeaderDrawing { + get { return headerDrawing; } + set { headerDrawing = value; } + } + private HeaderDrawingDelegate headerDrawing; + + /// + /// Gets or sets the style that will be used to draw the header for this column + /// + /// This is only uses when the owning ObjectListView has HeaderUsesThemes set to false. + [Category("ObjectListView"), + Description("What style will be used to draw the header of this column"), + DefaultValue(null)] + public HeaderFormatStyle HeaderFormatStyle { + get { return this.headerFormatStyle; } + set { this.headerFormatStyle = value; } + } + private HeaderFormatStyle headerFormatStyle; + + /// + /// Gets or sets the font in which the header for this column will be drawn + /// + /// You should probably use a HeaderFormatStyle instead of this property + /// This is only uses when HeaderUsesThemes is false. + [Category("ObjectListView"), + Description("Which font will be used to draw the header?"), + DefaultValue(null)] + public Font HeaderFont { + get { return this.HeaderFormatStyle == null ? null : this.HeaderFormatStyle.Normal.Font; } + set { + if (value == null && this.HeaderFormatStyle == null) + return; + + if (this.HeaderFormatStyle == null) + this.HeaderFormatStyle = new HeaderFormatStyle(); + + this.HeaderFormatStyle.SetFont(value); + } + } + + /// + /// Gets or sets the color in which the text of the header for this column will be drawn + /// + /// You should probably use a HeaderFormatStyle instead of this property + /// This is only uses when HeaderUsesThemes is false. + [Category("ObjectListView"), + Description("In what color will the header text be drawn?"), + DefaultValue(typeof(Color), "")] + public Color HeaderForeColor { + get { return this.HeaderFormatStyle == null ? Color.Empty : this.HeaderFormatStyle.Normal.ForeColor; } + set { + if (value.IsEmpty && this.HeaderFormatStyle == null) + return; + + if (this.HeaderFormatStyle == null) + this.HeaderFormatStyle = new HeaderFormatStyle(); + + this.HeaderFormatStyle.SetForeColor(value); + } + } + + /// + /// Gets or sets the ImageList key of the image that will be drawn in the header of this column. + /// + /// This is only taken into account when HeaderUsesThemes is false. + [Category("ObjectListView"), + Description("Name of the image that will be shown in the column header."), + DefaultValue(null), + TypeConverter(typeof(ImageKeyConverter)), + Editor("System.Windows.Forms.Design.ImageIndexEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor)), + RefreshProperties(RefreshProperties.Repaint)] + public string HeaderImageKey { + get { return headerImageKey; } + set { headerImageKey = value; } + } + private string headerImageKey; + + + /// + /// Gets or sets how the text of the header will be drawn? + /// + [Category("ObjectListView"), + Description("How will the header text be aligned? If this is not set, the alignment of the header will follow the alignment of the column"), + DefaultValue(null)] + public HorizontalAlignment? HeaderTextAlign { + get { return headerTextAlign; } + set { headerTextAlign = value; } + } + private HorizontalAlignment? headerTextAlign; + + /// + /// Return the text alignment of the header. This will either have been set explicitly, + /// or will follow the alignment of the text in the column + /// + [Browsable(false)] + public HorizontalAlignment HeaderTextAlignOrDefault + { + get { return headerTextAlign.HasValue ? headerTextAlign.Value : this.TextAlign; } + } + + /// + /// Gets the header alignment converted to a StringAlignment + /// + [Browsable(false)] + public StringAlignment HeaderTextAlignAsStringAlignment { + get { + switch (this.HeaderTextAlignOrDefault) { + case HorizontalAlignment.Left: return StringAlignment.Near; + case HorizontalAlignment.Center: return StringAlignment.Center; + case HorizontalAlignment.Right: return StringAlignment.Far; + default: return StringAlignment.Near; + } + } + } + + /// + /// Gets whether or not this column has an image in the header + /// + [Browsable(false)] + public bool HasHeaderImage { + get { + return (this.ListView != null && + this.ListView.SmallImageList != null && + this.ListView.SmallImageList.Images.ContainsKey(this.HeaderImageKey)); + } + } + + /// + /// Gets or sets whether this header will place a checkbox in the header + /// + [Category("ObjectListView"), + Description("Draw a checkbox in the header of this column"), + DefaultValue(false)] + public bool HeaderCheckBox + { + get { return headerCheckBox; } + set { headerCheckBox = value; } + } + private bool headerCheckBox; + + /// + /// Gets or sets whether this header will place a tri-state checkbox in the header + /// + [Category("ObjectListView"), + Description("Draw a tri-state checkbox in the header of this column"), + DefaultValue(false)] + public bool HeaderTriStateCheckBox + { + get { return headerTriStateCheckBox; } + set { headerTriStateCheckBox = value; } + } + private bool headerTriStateCheckBox; + + /// + /// Gets or sets the checkedness of the checkbox in the header of this column + /// + [Category("ObjectListView"), + Description("Checkedness of the header checkbox"), + DefaultValue(CheckState.Unchecked)] + public CheckState HeaderCheckState + { + get { return headerCheckState; } + set { headerCheckState = value; } + } + private CheckState headerCheckState = CheckState.Unchecked; + + /// + /// Gets or sets whether the + /// checking/unchecking the value of the header's checkbox will result in the + /// checkboxes for all cells in this column being set to the same checked/unchecked. + /// Defaults to true. + /// + /// + /// + /// There is no reverse of this function that automatically updates the header when the + /// checkedness of a cell changes. + /// + /// + /// This property's behaviour on a TreeListView is probably best describes as undefined + /// and should be avoided. + /// + /// + /// The performance of this action (checking/unchecking all rows) is O(n) where n is the + /// number of rows. It will work on large virtual lists, but it may take some time. + /// + /// + [Category("ObjectListView"), + Description("Update row checkboxes when the header checkbox is clicked by the user"), + DefaultValue(true)] + public bool HeaderCheckBoxUpdatesRowCheckBoxes { + get { return headerCheckBoxUpdatesRowCheckBoxes; } + set { headerCheckBoxUpdatesRowCheckBoxes = value; } + } + private bool headerCheckBoxUpdatesRowCheckBoxes = true; + + /// + /// Gets or sets whether the checkbox in the header is disabled + /// + /// + /// Clicking on a disabled checkbox does not change its value, though it does raise + /// a HeaderCheckBoxChanging event, which allows the programmer the opportunity to do + /// something appropriate. + [Category("ObjectListView"), + Description("Is the checkbox in the header of this column disabled"), + DefaultValue(false)] + public bool HeaderCheckBoxDisabled + { + get { return headerCheckBoxDisabled; } + set { headerCheckBoxDisabled = value; } + } + private bool headerCheckBoxDisabled; + + /// + /// Gets or sets whether this column can be hidden by the user. + /// + /// + /// Column 0 can never be hidden, regardless of this setting. + /// + [Category("ObjectListView"), + Description("Will the user be able to choose to hide this column?"), + DefaultValue(true)] + public bool Hideable { + get { return hideable; } + set { hideable = value; } + } + private bool hideable = true; + + /// + /// Gets or sets whether the text values in this column will act like hyperlinks + /// + [Category("ObjectListView"), + Description("Will the text values in the cells of this column act like hyperlinks?"), + DefaultValue(false)] + public bool Hyperlink { + get { return hyperlink; } + set { hyperlink = value; } + } + private bool hyperlink; + + /// + /// This is the name of property that will be invoked to get the image selector of the + /// image that should be shown in this column. + /// It can return an int, string, Image or null. + /// + /// + /// This is ignored if ImageGetter is not null. + /// The property can use these return value to identify the image: + /// + /// null or -1 -- indicates no image + /// an int -- the int value will be used as an index into the image list + /// a String -- the string value will be used as a key into the image list + /// an Image -- the Image will be drawn directly (only in OwnerDrawn mode) + /// + /// + [Category("ObjectListView"), + Description("The name of the property that holds the image selector"), + DefaultValue(null)] + public string ImageAspectName { + get { return imageAspectName; } + set { imageAspectName = value; } + } + private string imageAspectName; + + /// + /// This delegate is called to get the image selector of the image that should be shown in this column. + /// It can return an int, string, Image or null. + /// + /// This delegate can use these return value to identify the image: + /// + /// null or -1 -- indicates no image + /// an int -- the int value will be used as an index into the image list + /// a String -- the string value will be used as a key into the image list + /// an Image -- the Image will be drawn directly (only in OwnerDrawn mode) + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ImageGetterDelegate ImageGetter { + get { return imageGetter; } + set { imageGetter = value; } + } + private ImageGetterDelegate imageGetter; + + /// + /// Gets or sets whether this column will draw buttons in its cells + /// + /// + /// + /// When this is set to true, the renderer for the column is become a ColumnButtonRenderer + /// if it isn't already. If this is set to false, any previous button renderer will be discarded + /// + /// If the cell's aspect is null or empty, nothing will be drawn in the cell. + [Category("ObjectListView"), + Description("Does this column draw its cells as buttons?"), + DefaultValue(false)] + public bool IsButton { + get { return isButton; } + set { + isButton = value; + if (value) { + ColumnButtonRenderer buttonRenderer = this.Renderer as ColumnButtonRenderer; + if (buttonRenderer == null) { + this.Renderer = this.CreateColumnButtonRenderer(); + this.FillInColumnButtonRenderer(); + } + } else { + if (this.Renderer is ColumnButtonRenderer) + this.Renderer = null; + } + } + } + private bool isButton; + + /// + /// Create a ColumnButtonRenderer to draw buttons in this column + /// + /// + protected virtual ColumnButtonRenderer CreateColumnButtonRenderer() { + return new ColumnButtonRenderer(); + } + + /// + /// Fill in details to our ColumnButtonRenderer based on the properties set on the column + /// + protected virtual void FillInColumnButtonRenderer() { + ColumnButtonRenderer buttonRenderer = this.Renderer as ColumnButtonRenderer; + if (buttonRenderer == null) + return; + + buttonRenderer.SizingMode = this.ButtonSizing; + buttonRenderer.ButtonSize = this.ButtonSize; + buttonRenderer.ButtonPadding = this.ButtonPadding; + buttonRenderer.MaxButtonWidth = this.ButtonMaxWidth; + } + + /// + /// Gets or sets the maximum width that a button can occupy. + /// -1 means there is no maximum width. + /// + /// This is only considered when the SizingMode is TextBounds + [Category("ObjectListView"), + Description("The maximum width that a button can occupy when the SizingMode is TextBounds"), + DefaultValue(-1)] + public int ButtonMaxWidth { + get { return this.buttonMaxWidth; } + set { + this.buttonMaxWidth = value; + FillInColumnButtonRenderer(); + } + } + private int buttonMaxWidth = -1; + + /// + /// Gets or sets the extra space that surrounds the cell when the SizingMode is TextBounds + /// + [Category("ObjectListView"), + Description("The extra space that surrounds the cell when the SizingMode is TextBounds"), + DefaultValue(null)] + public Size? ButtonPadding { + get { return this.buttonPadding; } + set { + this.buttonPadding = value; + this.FillInColumnButtonRenderer(); + } + } + private Size? buttonPadding; + + /// + /// Gets or sets the size of the button when the SizingMode is FixedBounds + /// + /// If this is not set, the bounds of the cell will be used + [Category("ObjectListView"), + Description("The size of the button when the SizingMode is FixedBounds"), + DefaultValue(null)] + public Size? ButtonSize { + get { return this.buttonSize; } + set { + this.buttonSize = value; + this.FillInColumnButtonRenderer(); + } + } + private Size? buttonSize; + + /// + /// Gets or sets how each button will be sized if this column is displaying buttons + /// + [Category("ObjectListView"), + Description("If this column is showing buttons, how each button will be sized"), + DefaultValue(ButtonSizingMode.TextBounds)] + public ButtonSizingMode ButtonSizing { + get { return this.buttonSizing; } + set { + this.buttonSizing = value; + this.FillInColumnButtonRenderer(); + } + } + private ButtonSizingMode buttonSizing = ButtonSizingMode.TextBounds; + + /// + /// Can the values shown in this column be edited? + /// + /// This defaults to true, since the primary means to control the editability of a listview + /// is on the listview itself. Once a listview is editable, all the columns are too, unless the + /// programmer explicitly marks them as not editable + [Category("ObjectListView"), + Description("Can the value in this column be edited?"), + DefaultValue(true)] + public bool IsEditable + { + get { return isEditable; } + set { isEditable = value; } + } + private bool isEditable = true; + + /// + /// Is this column a fixed width column? + /// + [Browsable(false)] + public bool IsFixedWidth { + get { + return (this.MinimumWidth != -1 && this.MaximumWidth != -1 && this.MinimumWidth >= this.MaximumWidth); + } + } + + /// + /// Get/set whether this column should be used when the view is switched to tile view. + /// + /// Column 0 is always included in tileview regardless of this setting. + /// Tile views do not work well with many "columns" of information. + /// Two or three works best. + [Category("ObjectListView"), + Description("Will this column be used when the view is switched to tile view"), + DefaultValue(false)] + public bool IsTileViewColumn { + get { return isTileViewColumn; } + set { isTileViewColumn = value; } + } + private bool isTileViewColumn; + + /// + /// Gets or sets whether the text of this header should be rendered vertically. + /// + /// + /// If this is true, it is a good idea to set ToolTipText to the name of the column so it's easy to read. + /// Vertical headers are text only. They do not draw their image. + /// + [Category("ObjectListView"), + Description("Will the header for this column be drawn vertically?"), + DefaultValue(false)] + public bool IsHeaderVertical { + get { return isHeaderVertical; } + set { isHeaderVertical = value; } + } + private bool isHeaderVertical; + + /// + /// Can this column be seen by the user? + /// + /// After changing this value, you must call RebuildColumns() before the changes will take effect. + [Category("ObjectListView"), + Description("Can this column be seen by the user?"), + DefaultValue(true)] + public bool IsVisible { + get { return isVisible; } + set + { + if (isVisible == value) + return; + + isVisible = value; + OnVisibilityChanged(EventArgs.Empty); + } + } + private bool isVisible = true; + + /// + /// Where was this column last positioned within the Detail view columns + /// + /// DisplayIndex is volatile. Once a column is removed from the control, + /// there is no way to discover where it was in the display order. This property + /// guards that information even when the column is not in the listview's active columns. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int LastDisplayIndex { + get { return this.lastDisplayIndex; } + set { this.lastDisplayIndex = value; } + } + private int lastDisplayIndex = -1; + + /// + /// What is the maximum width that the user can give to this column? + /// + /// -1 means there is no maximum width. Give this the same value as MinimumWidth to make a fixed width column. + [Category("ObjectListView"), + Description("What is the maximum width to which the user can resize this column? -1 means no limit"), + DefaultValue(-1)] + public int MaximumWidth { + get { return maxWidth; } + set { + maxWidth = value; + if (maxWidth != -1 && this.Width > maxWidth) + this.Width = maxWidth; + } + } + private int maxWidth = -1; + + /// + /// What is the minimum width that the user can give to this column? + /// + /// -1 means there is no minimum width. Give this the same value as MaximumWidth to make a fixed width column. + [Category("ObjectListView"), + Description("What is the minimum width to which the user can resize this column? -1 means no limit"), + DefaultValue(-1)] + public int MinimumWidth { + get { return minWidth; } + set { + minWidth = value; + if (this.Width < minWidth) + this.Width = minWidth; + } + } + private int minWidth = -1; + + /// + /// Get/set the renderer that will be invoked when a cell needs to be redrawn + /// + [Category("ObjectListView"), + Description("The renderer will draw this column when the ListView is owner drawn"), + DefaultValue(null)] + public IRenderer Renderer { + get { return renderer; } + set { renderer = value; } + } + private IRenderer renderer; + + /// + /// This delegate is called when a cell needs to be drawn in OwnerDrawn mode. + /// + /// This method is kept primarily for backwards compatibility. + /// New code should implement an IRenderer, though this property will be maintained. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public RenderDelegate RendererDelegate { + get { + Version1Renderer version1Renderer = this.Renderer as Version1Renderer; + return version1Renderer != null ? version1Renderer.RenderDelegate : null; + } + set { + this.Renderer = value == null ? null : new Version1Renderer(value); + } + } + + /// + /// Gets or sets whether the text in this column's cell will be used when doing text searching. + /// + /// + /// + /// If this is false, text filters will not trying searching this columns cells when looking for matches. + /// + /// + [Category("ObjectListView"), + Description("Will the text of the cells in this column be considered when searching?"), + DefaultValue(true)] + public bool Searchable { + get { return searchable; } + set { searchable = value; } + } + private bool searchable = true; + + /// + /// Gets or sets a delegate which will return the array of text values that should be + /// considered for text matching when using a text based filter. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public SearchValueGetterDelegate SearchValueGetter { + get { return searchValueGetter; } + set { searchValueGetter = value; } + } + private SearchValueGetterDelegate searchValueGetter; + + /// + /// Gets or sets whether the header for this column will include the column's Text. + /// + /// + /// + /// If this is false, the only thing rendered in the column header will be the image from . + /// + /// This setting is only considered when is false on the owning ObjectListView. + /// + [Category("ObjectListView"), + Description("Will the header for this column include text?"), + DefaultValue(true)] + public bool ShowTextInHeader { + get { return showTextInHeader; } + set { showTextInHeader = value; } + } + private bool showTextInHeader = true; + + /// + /// Gets or sets whether the contents of the list will be resorted when the user clicks the + /// header of this column. + /// + /// + /// + /// If this is false, clicking the header will not sort the list, but will not provide + /// any feedback as to why the list is not being sorted. It is the programmers responsibility to + /// provide appropriate feedback. + /// + /// When this is false, BeforeSorting events are still fired, which can be used to allow sorting + /// or give feedback, on a case by case basis. + /// + [Category("ObjectListView"), + Description("Will clicking this columns header resort the list?"), + DefaultValue(true)] + public bool Sortable { + get { return sortable; } + set { sortable = value; } + } + private bool sortable = true; + + /// + /// Gets or sets the horizontal alignment of the contents of the column. + /// + /// .NET will not allow column 0 to have any alignment except + /// to the left. We can't change the basic behaviour of the listview, + /// but when owner drawn, column 0 can now have other alignments. + new public HorizontalAlignment TextAlign { + get { + return this.textAlign.HasValue ? this.textAlign.Value : base.TextAlign; + } + set { + this.textAlign = value; + base.TextAlign = value; + } + } + private HorizontalAlignment? textAlign; + + /// + /// Gets the StringAlignment equivalent of the column text alignment + /// + [Browsable(false)] + public StringAlignment TextStringAlign { + get { + switch (this.TextAlign) { + case HorizontalAlignment.Center: + return StringAlignment.Center; + case HorizontalAlignment.Left: + return StringAlignment.Near; + case HorizontalAlignment.Right: + return StringAlignment.Far; + default: + return StringAlignment.Near; + } + } + } + + /// + /// What string should be displayed when the mouse is hovered over the header of this column? + /// + /// If a HeaderToolTipGetter is installed on the owning ObjectListView, this + /// value will be ignored. + [Category("ObjectListView"), + Description("The tooltip to show when the mouse is hovered over the header of this column"), + DefaultValue((String)null), + Localizable(true)] + public String ToolTipText { + get { return toolTipText; } + set { toolTipText = value; } + } + private String toolTipText; + + /// + /// Should this column have a tri-state checkbox? + /// + /// + /// If this is true, the user can choose the third state (normally Indeterminate). + /// + [Category("ObjectListView"), + Description("Should values in this column be treated as a tri-state checkbox?"), + DefaultValue(false)] + public virtual bool TriStateCheckBoxes { + get { return triStateCheckBoxes; } + set { + triStateCheckBoxes = value; + if (value && !this.CheckBoxes) + this.CheckBoxes = true; + } + } + private bool triStateCheckBoxes; + + /// + /// Group objects by the initial letter of the aspect of the column + /// + /// + /// One common pattern is to group column by the initial letter of the value for that group. + /// The aspect must be a string (obviously). + /// + [Category("ObjectListView"), + Description("The name of the property or method that should be called to get the aspect to display in this column"), + DefaultValue(false)] + public bool UseInitialLetterForGroup { + get { return useInitialLetterForGroup; } + set { useInitialLetterForGroup = value; } + } + private bool useInitialLetterForGroup; + + /// + /// Gets or sets whether or not this column should be user filterable + /// + [Category("ObjectListView"), + Description("Does this column want to show a Filter menu item when its header is right clicked"), + DefaultValue(true)] + public bool UseFiltering { + get { return useFiltering; } + set { useFiltering = value; } + } + private bool useFiltering = true; + + /// + /// Gets or sets a filter that will only include models where the model's value + /// for this column is one of the values in ValuesChosenForFiltering + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IModelFilter ValueBasedFilter { + get { + if (!this.UseFiltering) + return null; + + if (valueBasedFilter != null) + return valueBasedFilter; + + if (this.ClusteringStrategy == null) + return null; + + if (this.ValuesChosenForFiltering == null || this.ValuesChosenForFiltering.Count == 0) + return null; + + return this.ClusteringStrategy.CreateFilter(this.ValuesChosenForFiltering); + } + set { valueBasedFilter = value; } + } + private IModelFilter valueBasedFilter; + + /// + /// Gets or sets the values that will be used to generate a filter for this + /// column. For a model to be included by the generated filter, its value for this column + /// must be in this list. If the list is null or empty, this column will + /// not be used for filtering. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IList ValuesChosenForFiltering { + get { return this.valuesChosenForFiltering; } + set { this.valuesChosenForFiltering = value; } + } + private IList valuesChosenForFiltering = new ArrayList(); + + /// + /// What is the width of this column? + /// + [Category("ObjectListView"), + Description("The width in pixels of this column"), + DefaultValue(60)] + public new int Width { + get { return base.Width; } + set { + if (this.MaximumWidth != -1 && value > this.MaximumWidth) + base.Width = this.MaximumWidth; + else + base.Width = Math.Max(this.MinimumWidth, value); + } + } + + /// + /// Gets or set whether the contents of this column's cells should be word wrapped + /// + /// If this column uses a custom IRenderer (that is, one that is not descended + /// from BaseRenderer), then that renderer is responsible for implementing word wrapping. + [Category("ObjectListView"), + Description("Draw this column cell's word wrapped"), + DefaultValue(false)] + public bool WordWrap { + get { return wordWrap; } + set { wordWrap = value; } + } + + private bool wordWrap; + + #endregion + + #region Object commands + + /// + /// For a given group value, return the string that should be used as the groups title. + /// + /// The group key that is being converted to a title + /// string + public string ConvertGroupKeyToTitle(object value) { + if (this.groupKeyToTitleConverter != null) + return this.groupKeyToTitleConverter(value); + + return value == null ? ObjectListView.GroupTitleDefault : this.ValueToString(value); + } + + /// + /// Get the checkedness of the given object for this column + /// + /// The row object that is being displayed + /// The checkedness of the object + public CheckState GetCheckState(object rowObject) { + if (!this.CheckBoxes) + return CheckState.Unchecked; + + bool? aspectAsBool = this.GetValue(rowObject) as bool?; + if (aspectAsBool.HasValue) { + if (aspectAsBool.Value) + return CheckState.Checked; + else + return CheckState.Unchecked; + } else + return CheckState.Indeterminate; + } + + /// + /// Put the checkedness of the given object for this column + /// + /// The row object that is being displayed + /// + /// The checkedness of the object + public void PutCheckState(object rowObject, CheckState newState) { + if (newState == CheckState.Checked) + this.PutValue(rowObject, true); + else + if (newState == CheckState.Unchecked) + this.PutValue(rowObject, false); + else + this.PutValue(rowObject, null); + } + + /// + /// For a given row object, extract the value indicated by the AspectName property of this column. + /// + /// The row object that is being displayed + /// An object, which is the aspect named by AspectName + public object GetAspectByName(object rowObject) { + if (this.aspectMunger == null) + this.aspectMunger = new Munger(this.AspectName); + + return this.aspectMunger.GetValue(rowObject); + } + private Munger aspectMunger; + + /// + /// For a given row object, return the object that is the key of the group that this row belongs to. + /// + /// The row object that is being displayed + /// Group key object + public object GetGroupKey(object rowObject) { + if (this.groupKeyGetter != null) + return this.groupKeyGetter(rowObject); + + object key = this.GetValue(rowObject); + + if (this.UseInitialLetterForGroup) { + String keyAsString = key as String; + if (!String.IsNullOrEmpty(keyAsString)) + return keyAsString.Substring(0, 1).ToUpper(); + } + + return key; + } + + /// + /// For a given row object, return the image selector of the image that should displayed in this column. + /// + /// The row object that is being displayed + /// int or string or Image. int or string will be used as index into image list. null or -1 means no image + public Object GetImage(object rowObject) { + if (this.CheckBoxes) + return this.GetCheckStateImage(rowObject); + + if (this.ImageGetter != null) + return this.ImageGetter(rowObject); + + if (!String.IsNullOrEmpty(this.ImageAspectName)) { + if (this.imageAspectMunger == null) + this.imageAspectMunger = new Munger(this.ImageAspectName); + + return this.imageAspectMunger.GetValue(rowObject); + } + + // I think this is wrong. ImageKey is meant for the image in the header, not in the rows + if (!String.IsNullOrEmpty(this.ImageKey)) + return this.ImageKey; + + return this.ImageIndex; + } + private Munger imageAspectMunger; + + /// + /// Return the image that represents the check box for the given model + /// + /// + /// + public string GetCheckStateImage(Object rowObject) { + CheckState checkState = this.GetCheckState(rowObject); + + if (checkState == CheckState.Checked) + return ObjectListView.CHECKED_KEY; + + if (checkState == CheckState.Unchecked) + return ObjectListView.UNCHECKED_KEY; + + return ObjectListView.INDETERMINATE_KEY; + } + + /// + /// For a given row object, return the strings that will be searched when trying to filter by string. + /// + /// + /// This will normally be the simple GetStringValue result, but if this column is non-textual (e.g. image) + /// you might want to install a SearchValueGetter delegate which can return something that could be used + /// for text filtering. + /// + /// + /// The array of texts to be searched. If this returns null, search will not match that object. + public string[] GetSearchValues(object rowObject) { + if (this.SearchValueGetter != null) + return this.SearchValueGetter(rowObject); + + var stringValue = this.GetStringValue(rowObject); + + DescribedTaskRenderer dtr = this.Renderer as DescribedTaskRenderer; + if (dtr != null) { + return new string[] { stringValue, dtr.GetDescription(rowObject) }; + } + + return new string[] { stringValue }; + } + + /// + /// For a given row object, return the string representation of the value shown in this column. + /// + /// + /// For aspects that are string (e.g. aPerson.Name), the aspect and its string representation are the same. + /// For non-strings (e.g. aPerson.DateOfBirth), the string representation is very different. + /// + /// + /// + public string GetStringValue(object rowObject) + { + return this.ValueToString(this.GetValue(rowObject)); + } + + /// + /// For a given row object, return the object that is to be displayed in this column. + /// + /// The row object that is being displayed + /// An object, which is the aspect to be displayed + public object GetValue(object rowObject) { + if (this.AspectGetter == null) + return this.GetAspectByName(rowObject); + else + return this.AspectGetter(rowObject); + } + + /// + /// Update the given model object with the given value using the column's + /// AspectName. + /// + /// The model object to be updated + /// The value to be put into the model + public void PutAspectByName(Object rowObject, Object newValue) { + if (this.aspectMunger == null) + this.aspectMunger = new Munger(this.AspectName); + + this.aspectMunger.PutValue(rowObject, newValue); + } + + /// + /// Update the given model object with the given value + /// + /// The model object to be updated + /// The value to be put into the model + public void PutValue(Object rowObject, Object newValue) { + if (this.aspectPutter == null) + this.PutAspectByName(rowObject, newValue); + else + this.aspectPutter(rowObject, newValue); + } + + /// + /// Convert the aspect object to its string representation. + /// + /// + /// If the column has been given a AspectToStringConverter, that will be used to do + /// the conversion, otherwise just use ToString(). + /// The returned value will not be null. Nulls are always converted + /// to empty strings. + /// + /// The value of the aspect that should be displayed + /// A string representation of the aspect + public string ValueToString(object value) { + // Give the installed converter a chance to work (even if the value is null) + if (this.AspectToStringConverter != null) + return this.AspectToStringConverter(value) ?? String.Empty; + + // Without a converter, nulls become simple empty strings + if (value == null) + return String.Empty; + + string fmt = this.AspectToStringFormat; + if (String.IsNullOrEmpty(fmt)) + return value.ToString(); + else + return String.Format(fmt, value); + } + + #endregion + + #region Utilities + + /// + /// Decide the clustering strategy that will be used for this column + /// + /// + private IClusteringStrategy DecideDefaultClusteringStrategy() { + if (!this.UseFiltering) + return null; + + if (this.DataType == typeof(DateTime)) + return new DateTimeClusteringStrategy(); + + return new ClustersFromGroupsStrategy(); + } + + /// + /// Gets or sets the type of data shown in this column. + /// + /// If this is not set, it will try to get the type + /// by looking through the rows of the listview. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Type DataType { + get { + if (this.dataType == null) { + ObjectListView olv = this.ListView as ObjectListView; + if (olv != null) { + object value = olv.GetFirstNonNullValue(this); + if (value != null) + return value.GetType(); // THINK: Should we cache this? + } + } + return this.dataType; + } + set { + this.dataType = value; + } + } + private Type dataType; + + #region Events + + /// + /// This event is triggered when the visibility of this column changes. + /// + [Category("ObjectListView"), + Description("This event is triggered when the visibility of the column changes.")] + public event EventHandler VisibilityChanged; + + /// + /// Tell the world when visibility of a column changes. + /// + public virtual void OnVisibilityChanged(EventArgs e) + { + if (this.VisibilityChanged != null) + this.VisibilityChanged(this, e); + } + + #endregion + + /// + /// Create groupies + /// This is an untyped version to help with Generator and OLVColumn attributes + /// + /// + /// + public void MakeGroupies(object[] values, string[] descriptions) { + this.MakeGroupies(values, descriptions, null, null, null); + } + + /// + /// Create groupies + /// + /// + /// + /// + public void MakeGroupies(T[] values, string[] descriptions) { + this.MakeGroupies(values, descriptions, null, null, null); + } + + /// + /// Create groupies + /// + /// + /// + /// + /// + public void MakeGroupies(T[] values, string[] descriptions, object[] images) { + this.MakeGroupies(values, descriptions, images, null, null); + } + + /// + /// Create groupies + /// + /// + /// + /// + /// + /// + public void MakeGroupies(T[] values, string[] descriptions, object[] images, string[] subtitles) { + this.MakeGroupies(values, descriptions, images, subtitles, null); + } + + /// + /// Create groupies. + /// Install delegates that will group the columns aspects into progressive partitions. + /// If an aspect is less than value[n], it will be grouped with description[n]. + /// If an aspect has a value greater than the last element in "values", it will be grouped + /// with the last element in "descriptions". + /// + /// Array of values. Values must be able to be + /// compared to the aspect (using IComparable) + /// The description for the matching value. The last element is the default description. + /// If there are n values, there must be n+1 descriptions. + /// + /// this.salaryColumn.MakeGroupies( + /// new UInt32[] { 20000, 100000 }, + /// new string[] { "Lowly worker", "Middle management", "Rarified elevation"}); + /// + /// + /// + /// + /// + public void MakeGroupies(T[] values, string[] descriptions, object[] images, string[] subtitles, string[] tasks) { + // Sanity checks + if (values == null) + throw new ArgumentNullException("values"); + if (descriptions == null) + throw new ArgumentNullException("descriptions"); + if (values.Length + 1 != descriptions.Length) + throw new ArgumentException("descriptions must have one more element than values."); + + // Install a delegate that returns the index of the description to be shown + this.GroupKeyGetter = delegate(object row) { + Object aspect = this.GetValue(row); + if (aspect == null || aspect == DBNull.Value) + return -1; + IComparable comparable = (IComparable)aspect; + for (int i = 0; i < values.Length; i++) { + if (comparable.CompareTo(values[i]) < 0) + return i; + } + + // Display the last element in the array + return descriptions.Length - 1; + }; + + // Install a delegate that simply looks up the given index in the descriptions. + this.GroupKeyToTitleConverter = delegate(object key) { + if ((int)key < 0) + return ""; + + return descriptions[(int)key]; + }; + + // Install one delegate that does all the other formatting + this.GroupFormatter = delegate(OLVGroup group, GroupingParameters parms) { + int key = (int)group.Key; // we know this is an int since we created it in GroupKeyGetter + + if (key >= 0) { + if (images != null && key < images.Length) + group.TitleImage = images[key]; + + if (subtitles != null && key < subtitles.Length) + group.Subtitle = subtitles[key]; + + if (tasks != null && key < tasks.Length) + group.Task = tasks[key]; + } + }; + } + /// + /// Create groupies based on exact value matches. + /// + /// + /// Install delegates that will group rows into partitions based on equality of this columns aspects. + /// If an aspect is equal to value[n], it will be grouped with description[n]. + /// If an aspect is not equal to any value, it will be grouped with "[other]". + /// + /// Array of values. Values must be able to be + /// equated to the aspect + /// The description for the matching value. + /// + /// this.marriedColumn.MakeEqualGroupies( + /// new MaritalStatus[] { MaritalStatus.Single, MaritalStatus.Married, MaritalStatus.Divorced, MaritalStatus.Partnered }, + /// new string[] { "Looking", "Content", "Looking again", "Mostly content" }); + /// + /// + /// + /// + /// + public void MakeEqualGroupies(T[] values, string[] descriptions, object[] images, string[] subtitles, string[] tasks) { + // Sanity checks + if (values == null) + throw new ArgumentNullException("values"); + if (descriptions == null) + throw new ArgumentNullException("descriptions"); + if (values.Length != descriptions.Length) + throw new ArgumentException("descriptions must have the same number of elements as values."); + + ArrayList valuesArray = new ArrayList(values); + + // Install a delegate that returns the index of the description to be shown + this.GroupKeyGetter = delegate(object row) { + return valuesArray.IndexOf(this.GetValue(row)); + }; + + // Install a delegate that simply looks up the given index in the descriptions. + this.GroupKeyToTitleConverter = delegate(object key) { + int intKey = (int)key; // we know this is an int since we created it in GroupKeyGetter + return (intKey < 0) ? "[other]" : descriptions[intKey]; + }; + + // Install one delegate that does all the other formatting + this.GroupFormatter = delegate(OLVGroup group, GroupingParameters parms) { + int key = (int)group.Key; // we know this is an int since we created it in GroupKeyGetter + + if (key >= 0) { + if (images != null && key < images.Length) + group.TitleImage = images[key]; + + if (subtitles != null && key < subtitles.Length) + group.Subtitle = subtitles[key]; + + if (tasks != null && key < tasks.Length) + group.Task = tasks[key]; + } + }; + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/ObjectListView.DesignTime.cs b/VG Music Studio - WinForms/ObjectListView/ObjectListView.DesignTime.cs new file mode 100644 index 0000000..0b74fb2 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/ObjectListView.DesignTime.cs @@ -0,0 +1,550 @@ +/* + * DesignSupport - Design time support for the various classes within ObjectListView + * + * Author: Phillip Piper + * Date: 12/08/2009 8:36 PM + * + * Change log: + * 2012-08-27 JPP - Fall back to more specific type name for the ListViewDesigner if + * the first GetType() fails. + * v2.5.1 + * 2012-04-26 JPP - Filter group events from TreeListView since it can't have groups + * 2011-06-06 JPP - Vastly improved ObjectListViewDesigner, based off information in + * "'Inheriting' from an Internal WinForms Designer" on CodeProject. + * v2.3 + * 2009-08-12 JPP - Initial version + * + * To do: + * + * Copyright (C) 2009-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Drawing; +using System.Reflection; +using System.Windows.Forms; +using System.Windows.Forms.Design; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView.Design +{ + + /// + /// Designer for and its subclasses. + /// + /// + /// + /// This designer removes properties and events that are available on ListView but that are not + /// useful on ObjectListView. + /// + /// + /// We can't inherit from System.Windows.Forms.Design.ListViewDesigner, since it is marked internal. + /// So, this class uses reflection to create a ListViewDesigner and then forwards messages to that designer. + /// + /// + public class ObjectListViewDesigner : ControlDesigner + { + + #region Initialize & Dispose + + /// + /// Initialises the designer with the specified component. + /// + /// The to associate the designer with. This component must always be an instance of, or derive from, . + public override void Initialize(IComponent component) { + // Debug.WriteLine("ObjectListViewDesigner.Initialize"); + + // Use reflection to bypass the "internal" marker on ListViewDesigner + // If we can't get the unversioned designer, look specifically for .NET 4.0 version of it. + Type tListViewDesigner = Type.GetType("System.Windows.Forms.Design.ListViewDesigner, System.Design") ?? + Type.GetType("System.Windows.Forms.Design.ListViewDesigner, System.Design, " + + "Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + if (tListViewDesigner == null) throw new ArgumentException("Could not load ListViewDesigner"); + + this.listViewDesigner = (ControlDesigner)Activator.CreateInstance(tListViewDesigner, BindingFlags.Instance | BindingFlags.Public, null, null, null); + this.designerFilter = this.listViewDesigner; + + // Fetch the methods from the ListViewDesigner that we know we want to use + this.listViewDesignGetHitTest = tListViewDesigner.GetMethod("GetHitTest", BindingFlags.Instance | BindingFlags.NonPublic); + this.listViewDesignWndProc = tListViewDesigner.GetMethod("WndProc", BindingFlags.Instance | BindingFlags.NonPublic); + + Debug.Assert(this.listViewDesignGetHitTest != null, "Required method (GetHitTest) not found on ListViewDesigner"); + Debug.Assert(this.listViewDesignWndProc != null, "Required method (WndProc) not found on ListViewDesigner"); + + // Tell the Designer to use properties of default designer as well as the properties of this class (do before base.Initialize) + TypeDescriptor.CreateAssociation(component, this.listViewDesigner); + + IServiceContainer site = (IServiceContainer)component.Site; + if (site != null && GetService(typeof(DesignerCommandSet)) == null) { + site.AddService(typeof(DesignerCommandSet), new CDDesignerCommandSet(this)); + } else { + Debug.Fail("site != null && GetService(typeof (DesignerCommandSet)) == null"); + } + + this.listViewDesigner.Initialize(component); + base.Initialize(component); + + RemoveDuplicateDockingActionList(); + } + + /// + /// Initialises a newly created component. + /// + /// A name/value dictionary of default values to apply to properties. May be null if no default values are specified. + public override void InitializeNewComponent(IDictionary defaultValues) { + // Debug.WriteLine("ObjectListViewDesigner.InitializeNewComponent"); + base.InitializeNewComponent(defaultValues); + this.listViewDesigner.InitializeNewComponent(defaultValues); + } + + /// + /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) { + // Debug.WriteLine("ObjectListViewDesigner.Dispose"); + if (disposing) { + if (this.listViewDesigner != null) { + this.listViewDesigner.Dispose(); + // Normally we would now null out the designer, but this designer + // still has methods called AFTER it is disposed. + } + } + + base.Dispose(disposing); + } + + /// + /// Removes the duplicate DockingActionList added by this designer to the . + /// + /// + /// adds an internal DockingActionList : 'Dock/Undock in Parent Container'. + /// But the default designer has already added that action list. So we need to remove one. + /// + private void RemoveDuplicateDockingActionList() { + // This is a true hack -- in a class that is basically a huge hack itself. + // Reach into the bowel of our base class, get a private field, and use that fields value to + // remove an action from the designer. + // In ControlDesigner, there is "private DockingActionList dockingAction;" + // Don't you just love Reflector?! + FieldInfo fi = typeof(ControlDesigner).GetField("dockingAction", BindingFlags.Instance | BindingFlags.NonPublic); + if (fi != null) { + DesignerActionList dockingAction = (DesignerActionList)fi.GetValue(this); + if (dockingAction != null) { + DesignerActionService service = (DesignerActionService)GetService(typeof(DesignerActionService)); + if (service != null) { + service.Remove(this.Control, dockingAction); + } + } + } + } + + #endregion + + #region IDesignerFilter overrides + + /// + /// Adjusts the set of properties the component exposes through a . + /// + /// An containing the properties for the class of the component. + protected override void PreFilterProperties(IDictionary properties) { + // Debug.WriteLine("ObjectListViewDesigner.PreFilterProperties"); + + // Always call the base PreFilterProperties implementation + // before you modify the properties collection. + base.PreFilterProperties(properties); + + // Give the listviewdesigner a chance to filter the properties + // (though we already know it's not going to do anything) + this.designerFilter.PreFilterProperties(properties); + + // I'd like to just remove the redundant properties, but that would + // break backward compatibility. The deserialiser that handles the XXX.Designer.cs file + // works off the designer, so even if the property exists in the class, the deserialiser will + // throw an error if the associated designer actually removes that property. + // So we shadow the unwanted properties, and give the replacement properties + // non-browsable attributes so that they are hidden from the user + + List unwantedProperties = new List(new string[] { + "BackgroundImage", "BackgroundImageTiled", "HotTracking", "HoverSelection", + "LabelEdit", "VirtualListSize", "VirtualMode" }); + + // Also hid Tooltip properties, since giving a tooltip to the control through the IDE + // messes up the tooltip handling + foreach (string propertyName in properties.Keys) { + if (propertyName.StartsWith("ToolTip")) { + unwantedProperties.Add(propertyName); + } + } + + // If we are looking at a TreeListView, remove group related properties + // since TreeListViews can't show groups + if (this.Control is TreeListView) { + unwantedProperties.AddRange(new string[] { + "GroupImageList", "GroupWithItemCountFormat", "GroupWithItemCountSingularFormat", "HasCollapsibleGroups", + "SpaceBetweenGroups", "ShowGroups", "SortGroupItemsByPrimaryColumn", "ShowItemCountOnGroups" + }); + } + + // Shadow the unwanted properties, and give the replacement properties + // non-browsable attributes so that they are hidden from the user + foreach (string unwantedProperty in unwantedProperties) { + PropertyDescriptor propertyDesc = TypeDescriptor.CreateProperty( + typeof(ObjectListView), + (PropertyDescriptor)properties[unwantedProperty], + new BrowsableAttribute(false)); + properties[unwantedProperty] = propertyDesc; + } + } + + /// + /// Allows a designer to add to the set of events that it exposes through a . + /// + /// The events for the class of the component. + protected override void PreFilterEvents(IDictionary events) { + // Debug.WriteLine("ObjectListViewDesigner.PreFilterEvents"); + base.PreFilterEvents(events); + this.designerFilter.PreFilterEvents(events); + + // Remove the events that don't make sense for an ObjectListView. + // See PreFilterProperties() for why we do this dance rather than just remove the event. + List unwanted = new List(new string[] { + "AfterLabelEdit", + "BeforeLabelEdit", + "DrawColumnHeader", + "DrawItem", + "DrawSubItem", + "RetrieveVirtualItem", + "SearchForVirtualItem", + "VirtualItemsSelectionRangeChanged" + }); + + // If we are looking at a TreeListView, remove group related events + // since TreeListViews can't show groups + if (this.Control is TreeListView) { + unwanted.AddRange(new string[] { + "AboutToCreateGroups", + "AfterCreatingGroups", + "BeforeCreatingGroups", + "GroupTaskClicked", + "GroupExpandingCollapsing", + "GroupStateChanged" + }); + } + + foreach (string unwantedEvent in unwanted) { + EventDescriptor eventDesc = TypeDescriptor.CreateEvent( + typeof(ObjectListView), + (EventDescriptor)events[unwantedEvent], + new BrowsableAttribute(false)); + events[unwantedEvent] = eventDesc; + } + } + + /// + /// Allows a designer to change or remove items from the set of attributes that it exposes through a . + /// + /// The attributes for the class of the component. + protected override void PostFilterAttributes(IDictionary attributes) { + // Debug.WriteLine("ObjectListViewDesigner.PostFilterAttributes"); + this.designerFilter.PostFilterAttributes(attributes); + base.PostFilterAttributes(attributes); + } + + /// + /// Allows a designer to change or remove items from the set of events that it exposes through a . + /// + /// The events for the class of the component. + protected override void PostFilterEvents(IDictionary events) { + // Debug.WriteLine("ObjectListViewDesigner.PostFilterEvents"); + this.designerFilter.PostFilterEvents(events); + base.PostFilterEvents(events); + } + + #endregion + + #region Overrides + + /// + /// Gets the design-time action lists supported by the component associated with the designer. + /// + /// + /// The design-time action lists supported by the component associated with the designer. + /// + public override DesignerActionListCollection ActionLists { + get { + // We want to change the first action list so it only has the commands we want + DesignerActionListCollection actionLists = this.listViewDesigner.ActionLists; + if (actionLists.Count > 0 && !(actionLists[0] is ListViewActionListAdapter)) { + actionLists[0] = new ListViewActionListAdapter(this, actionLists[0]); + } + return actionLists; + } + } + + /// + /// Gets the collection of components associated with the component managed by the designer. + /// + /// + /// The components that are associated with the component managed by the designer. + /// + public override ICollection AssociatedComponents { + get { + ArrayList components = new ArrayList(base.AssociatedComponents); + components.AddRange(this.listViewDesigner.AssociatedComponents); + return components; + } + } + + /// + /// Indicates whether a mouse click at the specified point should be handled by the control. + /// + /// + /// true if a click at the specified point is to be handled by the control; otherwise, false. + /// + /// A indicating the position at which the mouse was clicked, in screen coordinates. + protected override bool GetHitTest(Point point) { + // The ListViewDesigner wants to allow column dividers to be resized + return (bool)this.listViewDesignGetHitTest.Invoke(listViewDesigner, new object[] { point }); + } + + /// + /// Processes Windows messages and optionally routes them to the control. + /// + /// The to process. + protected override void WndProc(ref Message m) { + switch (m.Msg) { + case 0x4e: + case 0x204e: + // The listview designer is interested in HDN_ENDTRACK notifications + this.listViewDesignWndProc.Invoke(listViewDesigner, new object[] { m }); + break; + default: + base.WndProc(ref m); + break; + } + } + + #endregion + + #region Implementation variables + + private ControlDesigner listViewDesigner; + private IDesignerFilter designerFilter; + private MethodInfo listViewDesignGetHitTest; + private MethodInfo listViewDesignWndProc; + + #endregion + + #region Custom action list + + /// + /// This class modifies a ListViewActionList, by removing the "Edit Items" and "Edit Groups" actions. + /// + /// + /// + /// That class is internal, so we cannot simply subclass it, which would be simpler. + /// + /// + /// Action lists use reflection to determine if that action can be executed, so we not + /// only have to modify the returned collection of actions, but we have to implement + /// the properties and commands that the returned actions use. + /// + private class ListViewActionListAdapter : DesignerActionList + { + public ListViewActionListAdapter(ObjectListViewDesigner designer, DesignerActionList wrappedList) + : base(wrappedList.Component) { + this.designer = designer; + this.wrappedList = wrappedList; + } + + public override DesignerActionItemCollection GetSortedActionItems() { + DesignerActionItemCollection items = wrappedList.GetSortedActionItems(); + items.RemoveAt(2); // remove Edit Groups + items.RemoveAt(0); // remove Edit Items + return items; + } + + private void EditValue(ComponentDesigner componentDesigner, IComponent iComponent, string propertyName) { + // One more complication. The ListViewActionList classes uses an internal class, EditorServiceContext, to + // edit the items/columns/groups collections. So, we use reflection to bypass the data hiding. + Type tEditorServiceContext = Type.GetType("System.Windows.Forms.Design.EditorServiceContext, System.Design"); + tEditorServiceContext.InvokeMember("EditValue", BindingFlags.InvokeMethod | BindingFlags.Static, null, null, new object[] { componentDesigner, iComponent, propertyName }); + } + + private void SetValue(object target, string propertyName, object value) { + TypeDescriptor.GetProperties(target)[propertyName].SetValue(target, value); + } + + public void InvokeColumnsDialog() { + EditValue(this.designer, base.Component, "Columns"); + } + + // Don't need these since we removed their corresponding actions from the list. + // Keep the methods just in case. + + //public void InvokeGroupsDialog() { + // EditValue(this.designer, base.Component, "Groups"); + //} + + //public void InvokeItemsDialog() { + // EditValue(this.designer, base.Component, "Items"); + //} + + public ImageList LargeImageList { + get { return ((ListView)base.Component).LargeImageList; } + set { SetValue(base.Component, "LargeImageList", value); } + } + + public ImageList SmallImageList { + get { return ((ListView)base.Component).SmallImageList; } + set { SetValue(base.Component, "SmallImageList", value); } + } + + public View View { + get { return ((ListView)base.Component).View; } + set { SetValue(base.Component, "View", value); } + } + + ObjectListViewDesigner designer; + DesignerActionList wrappedList; + } + + #endregion + + #region DesignerCommandSet + + private class CDDesignerCommandSet : DesignerCommandSet + { + + public CDDesignerCommandSet(ComponentDesigner componentDesigner) { + this.componentDesigner = componentDesigner; + } + + public override ICollection GetCommands(string name) { + // Debug.WriteLine("CDDesignerCommandSet.GetCommands:" + name); + if (componentDesigner != null) { + if (name.Equals("Verbs")) { + return componentDesigner.Verbs; + } + if (name.Equals("ActionLists")) { + return componentDesigner.ActionLists; + } + } + return base.GetCommands(name); + } + + private readonly ComponentDesigner componentDesigner; + } + + #endregion + } + + /// + /// This class works in conjunction with the OLVColumns property to allow OLVColumns + /// to be added to the ObjectListView. + /// + public class OLVColumnCollectionEditor : System.ComponentModel.Design.CollectionEditor + { + /// + /// Create a OLVColumnCollectionEditor + /// + /// + public OLVColumnCollectionEditor(Type t) + : base(t) { + } + + /// + /// What type of object does this editor create? + /// + /// + protected override Type CreateCollectionItemType() { + return typeof(OLVColumn); + } + + /// + /// Edit a given value + /// + /// + /// + /// + /// + public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) { + if (context == null) + throw new ArgumentNullException("context"); + if (provider == null) + throw new ArgumentNullException("provider"); + + // Figure out which ObjectListView we are working on. This should be the Instance of the context. + ObjectListView olv = context.Instance as ObjectListView; + Debug.Assert(olv != null, "Instance must be an ObjectListView"); + + // Edit all the columns, not just the ones that are visible + base.EditValue(context, provider, olv.AllColumns); + + // Set the columns on the ListView to just the visible columns + List newColumns = olv.GetFilteredColumns(View.Details); + olv.Columns.Clear(); + olv.Columns.AddRange(newColumns.ToArray()); + + return olv.Columns; + } + + /// + /// What text should be shown in the list for the given object? + /// + /// + /// + protected override string GetDisplayText(object value) { + OLVColumn col = value as OLVColumn; + if (col == null || String.IsNullOrEmpty(col.AspectName)) + return base.GetDisplayText(value); + + return String.Format("{0} ({1})", base.GetDisplayText(value), col.AspectName); + } + } + + /// + /// Control how the overlay is presented in the IDE + /// + internal class OverlayConverter : ExpandableObjectConverter + { + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { + return destinationType == typeof(string) || base.CanConvertTo(context, destinationType); + } + + public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { + if (destinationType == typeof(string)) { + ImageOverlay imageOverlay = value as ImageOverlay; + if (imageOverlay != null) { + return imageOverlay.Image == null ? "(none)" : "(set)"; + } + TextOverlay textOverlay = value as TextOverlay; + if (textOverlay != null) { + return String.IsNullOrEmpty(textOverlay.Text) ? "(none)" : "(set)"; + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/ObjectListView.FxCop b/VG Music Studio - WinForms/ObjectListView/ObjectListView.FxCop new file mode 100644 index 0000000..a1367d2 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/ObjectListView.FxCop @@ -0,0 +1,3521 @@ + + + + True + c:\program files\microsoft fxcop 1.36\Xml\FxCopReport.xsl + + + + + + True + True + True + 10 + 1 + + False + + False + 120 + True + 2.0 + + + + $(ProjectDir)/trunk/ObjectListView/bin/Debug/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'ObjectListView.dll' + + + + + + + + + + + 'BarRenderer' + 'Pen' + + + + + + + + + + + + + + 'BarRenderer.BarRenderer(Pen, Brush)' + 'BarRenderer.UseStandardBar' + 'bool' + false + + + + + + + + + + + + + + 'BarRenderer.BarRenderer(int, int, Pen, Brush)' + 'BarRenderer.UseStandardBar' + 'bool' + false + + + + + + + + + + + + + + 'BarRenderer.BackgroundColor' + 'BaseRenderer.GetBackgroundColor()' + + + + + + + + + + + + + + + + + + 'BaseRenderer.GetBackgroundColor()' + + + + + + + + + + + + + + 'BaseRenderer.GetForegroundColor()' + + + + + + + + + + + + + + 'BaseRenderer.GetImageSelector()' + + + + + + + + + 'BaseRenderer.GetText()' + + + + + + + + + + + + + + 'BaseRenderer.GetTextBackgroundColor()' + + + + + + + + + + + + + + + + 'BorderDecoration' + 'SolidBrush' + + + + + + + + + 'CellEditEventHandler' + + + + + + + + + + + + + + + + 'g' + 'CheckStateRenderer.CalculateCheckBoxBounds(Graphics, Rectangle)' + + + + + + + + + + + 'ColumnRightClickEventHandler' + + + + + + + + + + + + + + + + + + 'ComboBoxItem.Key.get()' + + + + + + + + + + + + + + + 'DataListView.currencyManager_ListChanged(object, ListChangedEventArgs)' + + + + + + + + + 'DataListView.currencyManager_MetaDataChanged(object, EventArgs)' + + + + + + + + + 'DataListView.currencyManager_PositionChanged(object, EventArgs)' + + + + + + + + + + + + + 'DescribedTaskRenderer.GetDescription()' + + + + + + + + + + + 'DropTargetLocation' + + + + + + + + + + + 'collection' + 'ICollection' + 'FastObjectListDataSource.EnumerableToArray(IEnumerable)' + castclass + + + + + + + + + + + 'FastObjectListDataSource.FilteredObjectList.get()' + + + + + + + + + + + + + Flag + 'FlagRenderer' + + + + + + + + + + + + + 'FloatCellEditor.Value.get()' + + + + + + + + + + + + + + 'FloatCellEditor.Value.set(double)' + + + + + + + + + + + + + + + + + + + + + + 'GlassPanelForm.CreateParams.get()' + 'Form.CreateParams.get()' + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] + + + + + + + + + + + 'GlassPanelForm.WndProc(ref Message)' + 'Form.WndProc(ref Message)' + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] + + + + + + + + + + + 'GroupMetricsMask' + 'GroupMetricsMask.LVGMF_NONE' + + + + + + + + + + 'GroupMetricsMask' + + + + + + + + + + + + + + 'GroupState' + 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000 + + + + + 'GroupState' + 'GroupState.LVGS_NORMAL' + + + + + 'GroupState' + + + + + + + + + + + 'HeaderControl.HeaderControl(ObjectListView)' + 'NativeWindow.AssignHandle(IntPtr)' + ->'HeaderControl.HeaderControl(ObjectListView)' ->'HeaderControl.HeaderControl(ObjectListView)' + + + 'HeaderControl.HeaderControl(ObjectListView)' + 'NativeWindow.NativeWindow()' + ->'HeaderControl.HeaderControl(ObjectListView)' ->'HeaderControl.HeaderControl(ObjectListView)' + + + + + + + + + + + + + + 'g' + 'HeaderControl.CalculateHeight(Graphics)' + 'Graphics' + 'IDeviceContext' + + + + + + + + + 'g' + 'HeaderControl.DrawHeaderText(Graphics, Rectangle, OLVColumn, HeaderStateStyle)' + 'Graphics' + 'IDeviceContext' + + + + + + + + + 'g' + 'HeaderControl.DrawThemedBackground(Graphics, Rectangle, int, bool)' + 'Graphics' + 'IDeviceContext' + + + + + + + + + 'g' + 'HeaderControl.DrawThemedSortIndicator(Graphics, Rectangle)' + 'Graphics' + 'IDeviceContext' + + + + + + + + + Unthemed + 'HeaderControl.DrawUnthemedBackground(Graphics, Rectangle, int, bool, HeaderStateStyle)' + + + + + + + + + Unthemed + 'HeaderControl.DrawUnthemedSortIndicator(Graphics, Rectangle)' + + + + + + + + + 'm' + 'HeaderControl.HandleDestroy(ref Message)' + + + + + + + + + 'm' + 'HeaderControl.HandleMouseMove(ref Message)' + + + + + 'm' + + + + + + + + + + + + + + 'HeaderControl.HandleNotify(ref Message)' + 'Message.GetLParam(Type)' + ->'HeaderControl.HandleNotify(ref Message)' ->'HeaderControl.HandleNotify(ref Message)' + + + 'HeaderControl.HandleNotify(ref Message)' + 'Message.LParam.get()' + ->'HeaderControl.HandleNotify(ref Message)' ->'HeaderControl.HandleNotify(ref Message)' + + + + + + + + + + 'm' + + + + + + + + + + + + + + + + 'value' + 'HeaderControl.HotFontStyle.set(FontStyle)' + + + + + + + + + + + Flags + 'HeaderControl.TextFormatFlags' + + + + + + + + + 'HeaderControl.WndProc(ref Message)' + 'NativeWindow.WndProc(ref Message)' + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] + + + + + 'HeaderControl.WndProc(ref Message)' + 'Message.Msg.get()' + ->'HeaderControl.WndProc(ref Message)' ->'HeaderControl.WndProc(ref Message)' + + + 'HeaderControl.WndProc(ref Message)' + 'NativeWindow.WndProc(ref Message)' + ->'HeaderControl.WndProc(ref Message)' ->'HeaderControl.WndProc(ref Message)' + + + + + + + + + + + + + + + + + + 'text' + 'HighlightTextRenderer.HighlightTextRenderer(string)' + + + + + + + + + + + 'value' + 'HighlightTextRenderer.StringComparison.set(StringComparison)' + + + + + + + + + + + + + 'value' + 'HighlightTextRenderer.TextToHighlight.set(string)' + + + + + + + + + + + + + + + + + 'HyperlinkEventArgs.Column.set(OLVColumn)' + + + + + + + + + + + + + 'HyperlinkEventArgs.ColumnIndex.set(int)' + + + + + + + + + + + + + 'HyperlinkEventArgs.Item.set(OLVListItem)' + + + + + + + + + + + + + 'HyperlinkEventArgs.ListView.set(ObjectListView)' + + + + + + + + + + + + + 'HyperlinkEventArgs.Model.set(object)' + + + + + + + + + + + + + 'HyperlinkEventArgs.RowIndex.set(int)' + + + + + + + + + + + + + 'HyperlinkEventArgs.SubItem.set(OLVListSubItem)' + + + + + + + + + + + 'HyperlinkEventArgs.Url' + + + + + + + + + 'HyperlinkEventArgs.Url.set(string)' + + + + + + + + + + + + + + + 'ImageRenderer.GetImageFromAspect()' + + + + + + + + + + + + + + + + + + + + 'IntUpDown.Value.get()' + + + + + + + + + + + + + + 'IntUpDown.Value.set(int)' + + + + + + + + + + + + + + + + + + + + 'IVirtualListDataSource.GetObjectCount()' + + + + + + + + + + + + + + + + Multi + 'MultiImageRenderer' + + + + + + + + + + + + + + 'MultiImageRenderer.ImageSelector' + 'BaseRenderer.GetImageSelector()' + + + + + + + + + + + + + 'Munger.GetValue(object)' + 'object' + + + + + + + + + + + + + + 'Munger.PutValue(object, object)' + 'object' + + + 'Munger.PutValue(object, object)' + 'object' + + + + + + + + + + + + + + + + + + 'NativeMethods.ChangeSize(IWin32Window, int, int)' + + + + + + + + + 'NativeMethods.ChangeZOrder(IWin32Window, IWin32Window)' + + + + + + + + + 'NativeMethods.DeleteObject(IntPtr)' + + + + + + + + + 'NativeMethods.DrawImageList(Graphics, ImageList, int, int, int, bool)' + + + + + + + + + 'NativeMethods.GetClientRect(IntPtr, ref Rectangle)' + + + + + + + + + 'NativeMethods.GetColumnSides(ObjectListView, int)' + + + + + 'NativeMethods.GetColumnSides(ObjectListView, int)' + 'Point' + + + + + + + + + 'NativeMethods.GetGroupInfo(ObjectListView, int, ref NativeMethods.LVGROUP2)' + + + + + + + + + 'NativeMethods.GetScrollInfo(IntPtr, int, NativeMethods.SCROLLINFO)' + + + + + + + + + 'NativeMethods.GetUpdateRect(Control)' + 'NativeMethods.GetUpdateRectInternal(IntPtr, ref Rectangle, bool)' + + + + + + + + + 'eraseBackground' + 'NativeMethods.GetUpdateRectInternal(IntPtr, ref Rectangle, bool)' + + + + + + + + + 'NativeMethods.GetWindowLong32(IntPtr, int)' + 8 + 64-bit + 4 + 'IntPtr' + + + + + + + + + 'NativeMethods.GetWindowLongPtr64(IntPtr, int)' + 'user32.dll' + GetWindowLongPtr + + + + + + + + + 'NativeMethods.ImageList_Draw(IntPtr, int, IntPtr, int, int, int)' + + + + + 'NativeMethods.ImageList_Draw(IntPtr, int, IntPtr, int, int, int)' + + + + + + + + + 'erase' + 'NativeMethods.InvalidateRect(IntPtr, int, bool)' + + + 'NativeMethods.InvalidateRect(IntPtr, int, bool)' + + + + + + + + + 'NativeMethods.SendMessage(IntPtr, int, int, ref NativeMethods.LVGROUP)' + + + + + 'wParam' + 'NativeMethods.SendMessage(IntPtr, int, int, ref NativeMethods.LVGROUP)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'wParam' + 'NativeMethods.SendMessage(IntPtr, int, int, ref NativeMethods.LVGROUP2)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'wParam' + 'NativeMethods.SendMessage(IntPtr, int, int, ref NativeMethods.LVGROUPMETRICS)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'NativeMethods.SendMessage(IntPtr, int, int, ref NativeMethods.LVHITTESTINFO)' + + + + + 'wParam' + 'NativeMethods.SendMessage(IntPtr, int, int, ref NativeMethods.LVHITTESTINFO)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'wParam' + 'NativeMethods.SendMessage(IntPtr, int, int, int)' + 4 + 64-bit + 8 + 'int' + + + + + 'lParam' + 'NativeMethods.SendMessage(IntPtr, int, int, int)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'wParam' + 'NativeMethods.SendMessage(IntPtr, int, int, IntPtr)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'lParam' + 'NativeMethods.SendMessage(IntPtr, int, IntPtr, int)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'wParam' + 'NativeMethods.SendMessageHDItem(IntPtr, int, int, ref NativeMethods.HDITEM)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'NativeMethods.SendMessageIUnknown(IntPtr, int, object, int)' + + + + + 'lParam' + 'NativeMethods.SendMessageIUnknown(IntPtr, int, object, int)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'NativeMethods.SendMessageLVBKIMAGE(IntPtr, int, int, ref NativeMethods.LVBKIMAGE)' + + + + + 'wParam' + 'NativeMethods.SendMessageLVBKIMAGE(IntPtr, int, int, ref NativeMethods.LVBKIMAGE)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'wParam' + 'NativeMethods.SendMessageLVItem(IntPtr, int, int, ref NativeMethods.LVITEM)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'wParam' + 'NativeMethods.SendMessageRECT(IntPtr, int, int, ref NativeMethods.RECT)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'wParam' + 'NativeMethods.SendMessageString(IntPtr, int, int, string)' + 4 + 64-bit + 8 + 'int' + + + + + 'lParam' + + + + + + + + + 'wParam' + 'NativeMethods.SendMessageTOOLINFO(IntPtr, int, int, NativeMethods.TOOLINFO)' + 4 + 64-bit + 8 + 'int' + + + + + + + + + 'NativeMethods.SetBackgroundImage(ListView, Image)' + + + + + + + + + 'NativeMethods.SetBkColor(IntPtr, int)' + + + + + + + + + 'NativeMethods.SetSelectedColumn(ListView, ColumnHeader)' + + + + + + + + + 'NativeMethods.SetTextColor(IntPtr, int)' + + + + + + + + + 'NativeMethods.SetTooltipControl(ListView, ToolTipControl)' + + + + + + + + + 'NativeMethods.SetWindowLongPtr32(IntPtr, int, int)' + 8 + 64-bit + 4 + 'IntPtr' + + + + + + + + + 'dwNewLong' + 'NativeMethods.SetWindowLongPtr64(IntPtr, int, int)' + 4 + 64-bit + 8 + 'int' + + + + + 'NativeMethods.SetWindowLongPtr64(IntPtr, int, int)' + 'user32.dll' + SetWindowLongPtr + + + + + + + + + 'NativeMethods.SetWindowPos(IntPtr, IntPtr, int, int, int, int, uint)' + + + + + + + + + 'NativeMethods.SetWindowTheme(IntPtr, string, string)' + 8 + 64-bit + 4 + 'IntPtr' + + + + + 'subApp' + + + + + 'subIdList' + + + + + + + + + 'NativeMethods.ShowWindow(IntPtr, int)' + + + + + + + + + 'NativeMethods.ValidatedRectInternal(IntPtr, ref Rectangle)' + + + + + + + + + 'NativeMethods.ValidateRect(Control, Rectangle)' + + + + + + + + + + + + + 'NativeMethods.SCROLLINFO.SCROLLINFO()' + 'Marshal.SizeOf(Type)' + ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.GetScrollPosition(ListView, bool)' ->'HeaderControl.ColumnIndexUnderCursor.get()' + + + 'NativeMethods.SCROLLINFO.SCROLLINFO()' + 'Marshal.SizeOf(Type)' + ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.GetScrollPosition(ListView, bool)' ->'HeaderControl.IsCursorOverLockedDivider.get()' + + + 'NativeMethods.SCROLLINFO.SCROLLINFO()' + 'Marshal.SizeOf(Type)' + ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.GetScrollPosition(ListView, bool)' ->'NativeMethods.GetScrolledColumnSides(ListView, int)' ->'OLVListItem.GetSubItemBounds(int)' + + + 'NativeMethods.SCROLLINFO.SCROLLINFO()' + 'Marshal.SizeOf(Type)' + ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.GetScrollPosition(ListView, bool)' ->'NativeMethods.GetScrolledColumnSides(ListView, int)' ->'ObjectListView.CalculateCellBounds(OLVListItem, int, ItemBoundsPortion)' ->'ObjectListView.CalculateCellBounds(OLVListItem, int)' + + + 'NativeMethods.SCROLLINFO.SCROLLINFO()' + 'Marshal.SizeOf(Type)' + ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.GetScrollPosition(ListView, bool)' ->'NativeMethods.GetScrolledColumnSides(ListView, int)' ->'ObjectListView.CalculateCellBounds(OLVListItem, int, ItemBoundsPortion)' ->'ObjectListView.CalculateCellTextBounds(OLVListItem, int)' + + + 'NativeMethods.SCROLLINFO.SCROLLINFO()' + 'Marshal.SizeOf(Type)' + ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.GetScrollPosition(ListView, bool)' ->'NativeMethods.GetScrolledColumnSides(ListView, int)' ->'ObjectListView.OlvHitTest(int, int)' + + + 'NativeMethods.SCROLLINFO.SCROLLINFO()' + 'Marshal.SizeOf(Type)' + ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.GetScrollPosition(ListView, bool)' ->'NativeMethods.GetScrolledColumnSides(ListView, int)' ->'TintedColumnDecoration.Draw(ObjectListView, Graphics, Rectangle)' + + + 'NativeMethods.SCROLLINFO.SCROLLINFO()' + 'Marshal.SizeOf(Type)' + ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.GetScrollPosition(ListView, bool)' ->'ObjectListView.EnsureGroupVisible(ListViewGroup)' + + + 'NativeMethods.SCROLLINFO.SCROLLINFO()' + 'Marshal.SizeOf(Type)' + ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.GetScrollPosition(ListView, bool)' ->'ObjectListView.HandleBeginScroll(ref Message)' + + + 'NativeMethods.SCROLLINFO.SCROLLINFO()' + 'Marshal.SizeOf(Type)' + ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.SCROLLINFO.SCROLLINFO()' ->'NativeMethods.GetScrollPosition(ListView, bool)' ->'ObjectListView.HandleKeyDown(ref Message)' + + + + + + + + + + + + + + + + + + 'NativeMethods.TOOLINFO.TOOLINFO()' + 'Marshal.SizeOf(Type)' + ->'NativeMethods.TOOLINFO.TOOLINFO()' ->'NativeMethods.TOOLINFO.TOOLINFO()' ->'ToolTipControl.MakeToolInfoStruct(IWin32Window)' ->'ToolTipControl.AddTool(IWin32Window)' + + + 'NativeMethods.TOOLINFO.TOOLINFO()' + 'Marshal.SizeOf(Type)' + ->'NativeMethods.TOOLINFO.TOOLINFO()' ->'NativeMethods.TOOLINFO.TOOLINFO()' ->'ToolTipControl.MakeToolInfoStruct(IWin32Window)' ->'ToolTipControl.RemoveToolTip(IWin32Window)' + + + + + + + + + + + + + + + + + + 'ObjectListView.AllColumns' + + + + + + + + + + 'List<OLVColumn>' + 'ObjectListView.AllColumns' + + + + + + + + + + + + + + 'rowIndex' + 'ObjectListView.ApplyHyperlinkStyle(int, OLVListItem)' + + + + + + + + + 'ObjectListView.BooleanCheckStateGetter' + + + + + + + + + 'ObjectListView.BooleanCheckStatePutter' + + + + + + + + + 'item' + 'ObjectListView.CalculateCellBounds(OLVListItem, int)' + 'OLVListItem' + 'ListViewItem' + + + + + + + + + + + + + + 'ObjectListView.CellEditor' + 'ObjectListView.GetCellEditor(OLVListItem, int)' + + + + + + + + + 'ObjectListView.CellEditor_Validating(object, CancelEventArgs)' + + + + + + + + + 'ObjectListView.CellToolTip' + 'ObjectListView.GetCellToolTip(int, int)' + + + + + + + + + 'ObjectListView.CheckedObject' + 'ObjectListView.GetCheckedObject()' + + + + + + + + + 'ObjectListView.CheckedObjects' + 'ObjectListView.GetCheckedObjects()' + + + + + 'ObjectListView.CheckedObjects' + + + + + + + + + + + + + + 'List<OLVColumn>' + 'ObjectListView.ColumnsInDisplayOrder' + + + + + + + + + + + + + + 'ObjectListView.ConfigureAutoComplete(TextBox, OLVColumn)' + tb + 'tb' + + + + + + + + + 'ObjectListView.ConfigureAutoComplete(TextBox, OLVColumn, int)' + tb + 'tb' + + + + + + + + + 'List<OLVListItem>' + 'ObjectListView.DrawAllDecorations(Graphics, List<OLVListItem>)' + + + + + + + + + 'ObjectListView.EditorRegistry' + + + + + + + + + 'ObjectListView.EnsureGroupVisible(ListViewGroup)' + lvg + 'lvg' + + + + + + + + + 'ObjectListView.FilterObjects(IEnumerable, IModelFilter, IListFilter)' + a + 'aListFilter' + + + 'ObjectListView.FilterObjects(IEnumerable, IModelFilter, IListFilter)' + a + 'aModelFilter' + + + + + + + + + 'control' + 'CheckBox' + 'ObjectListView.GetControlValue(Control)' + castclass + + + 'control' + 'ComboBox' + 'ObjectListView.GetControlValue(Control)' + castclass + + + 'control' + 'TextBox' + 'ObjectListView.GetControlValue(Control)' + castclass + + + + + + + + + 'List<OLVColumn>' + 'ObjectListView.GetFilteredColumns(View)' + + + + + + + + + + + + + + 'selectedColumn' + + + + + + + + + + + + + + 'ObjectListView.GetItemCount()' + + + + + + + + + + + + + + 'ObjectListView.GetLastItemInDisplayOrder()' + + + + + + + + + 'ObjectListView.GetSelectedObject()' + + + + + + + + + + + + + + 'ObjectListView.GetSelectedObjects()' + + + + + + + + + + + + + + 'ObjectListView.HandleApplicationIdle(object, EventArgs)' + + + + + + + + + 'ObjectListView.HandleApplicationIdle_ResizeColumns(object, EventArgs)' + + + + + + + + + 'ObjectListView.HandleCellToolTipShowing(object, ToolTipShowingEventArgs)' + + + + + + + + + 'ObjectListView.HandleChar(ref Message)' + 'Control.ProcessKeyEventArgs(ref Message)' + ->'ObjectListView.HandleChar(ref Message)' ->'ObjectListView.HandleChar(ref Message)' + + + 'ObjectListView.HandleChar(ref Message)' + 'Message.WParam.get()' + ->'ObjectListView.HandleChar(ref Message)' ->'ObjectListView.HandleChar(ref Message)' + + + + + + + + + + 'm' + + + + + + + + + + + + + + 'ObjectListView.HandleColumnClick(object, ColumnClickEventArgs)' + + + + + + + + + 'ObjectListView.HandleColumnWidthChanged(object, ColumnWidthChangedEventArgs)' + + + + + + + + + 'ObjectListView.HandleColumnWidthChanging(object, ColumnWidthChangingEventArgs)' + + + + + + + + + 'ObjectListView.HandleContextMenu(ref Message)' + 'Message.LParam.get()' + ->'ObjectListView.HandleContextMenu(ref Message)' ->'ObjectListView.HandleContextMenu(ref Message)' + + + 'ObjectListView.HandleContextMenu(ref Message)' + 'Message.WParam.get()' + ->'ObjectListView.HandleContextMenu(ref Message)' ->'ObjectListView.HandleContextMenu(ref Message)' + + + + + + + + + + 'm' + + + + + + + + + + + + + + 'ObjectListView.HandleFindItem(ref Message)' + 'Message.GetLParam(Type)' + ->'ObjectListView.HandleFindItem(ref Message)' ->'ObjectListView.HandleFindItem(ref Message)' + + + 'ObjectListView.HandleFindItem(ref Message)' + 'Message.Result.set(IntPtr)' + ->'ObjectListView.HandleFindItem(ref Message)' ->'ObjectListView.HandleFindItem(ref Message)' + + + 'ObjectListView.HandleFindItem(ref Message)' + 'Message.WParam.get()' + ->'ObjectListView.HandleFindItem(ref Message)' ->'ObjectListView.HandleFindItem(ref Message)' + + + + + + + + + + 'm' + + + + + + + + + + + + + + 'ObjectListView.HandleHeaderToolTipShowing(object, ToolTipShowingEventArgs)' + + + + + + + + + 'ObjectListView.HandleLayout(object, LayoutEventArgs)' + + + + + + + + + 'ObjectListView.HandleNotify(ref Message)' + 'Marshal.PtrToStructure(IntPtr, Type)' + ->'ObjectListView.HandleNotify(ref Message)' ->'ObjectListView.HandleNotify(ref Message)' + + + 'ObjectListView.HandleNotify(ref Message)' + 'Marshal.StructureToPtr(object, IntPtr, bool)' + ->'ObjectListView.HandleNotify(ref Message)' ->'ObjectListView.HandleNotify(ref Message)' + + + 'ObjectListView.HandleNotify(ref Message)' + 'Message.GetLParam(Type)' + ->'ObjectListView.HandleNotify(ref Message)' ->'ObjectListView.HandleNotify(ref Message)' + + + 'ObjectListView.HandleNotify(ref Message)' + 'Message.Result.set(IntPtr)' + ->'ObjectListView.HandleNotify(ref Message)' ->'ObjectListView.HandleNotify(ref Message)' + + + 'ObjectListView.HandleNotify(ref Message)' + 'NativeWindow.Handle.get()' + ->'ObjectListView.HandleNotify(ref Message)' ->'ObjectListView.HandleNotify(ref Message)' + + + + + + + + + + 'm' + + + + + + + + + + + + + + 'ObjectListView.HandleReflectNotify(ref Message)' + 'Marshal.StructureToPtr(object, IntPtr, bool)' + ->'ObjectListView.HandleReflectNotify(ref Message)' ->'ObjectListView.HandleReflectNotify(ref Message)' + + + 'ObjectListView.HandleReflectNotify(ref Message)' + 'Message.GetLParam(Type)' + ->'ObjectListView.HandleReflectNotify(ref Message)' ->'ObjectListView.HandleReflectNotify(ref Message)' + + + 'ObjectListView.HandleReflectNotify(ref Message)' + 'Message.LParam.get()' + ->'ObjectListView.HandleReflectNotify(ref Message)' ->'ObjectListView.HandleReflectNotify(ref Message)' + + + + + + + + + + 'm' + + + + + + + + + + + + + + 'ObjectListView.HeaderToolTip' + 'ObjectListView.GetHeaderToolTip(int)' + + + + + + + + + 'url' + 'ObjectListView.IsUrlVisited(string)' + + + + + + + + + 'url' + 'ObjectListView.MarkUrlVisited(string)' + + + + + + + + + Unsort + 'ObjectListView.MenuLabelUnsort' + + + + + + + + + 'ObjectListView.ProcessDialogKey(Keys)' + 'Control.ProcessDialogKey(Keys)' + [UIPermission(SecurityAction.LinkDemand, Window = UIPermissionWindow.AllWindows)] + + + + + 'ObjectListView.ProcessDialogKey(Keys)' + 'Control.ProcessDialogKey(Keys)' + ->'ObjectListView.ProcessDialogKey(Keys)' ->'ObjectListView.ProcessDialogKey(Keys)' + + + + + + + + + + + + + + 'ObjectListView.SelectedObject' + 'ObjectListView.GetSelectedObject()' + + + + + + + + + 'ObjectListView.SelectedObjects' + 'ObjectListView.GetSelectedObjects()' + + + + + 'ObjectListView.SelectedObjects' + + + + + + + + + + + + + + 'control' + 'ComboBox' + 'ObjectListView.SetControlValue(Control, object, string)' + castclass + + + + + + + + + Checkedness + 'ObjectListView.SetObjectCheckedness(object, CheckState)' + + + + + + + + + + + + + + 'item' + 'ObjectListView.SetSubItemImages(int, OLVListItem, bool)' + 'OLVListItem' + 'ListViewItem' + + + + + + + + + + + + + + 'columnToSort' + 'ObjectListView.ShowSortIndicator(OLVColumn, SortOrder)' + 'OLVColumn' + 'ColumnHeader' + + + + + + + + + + + + + + 'ObjectListView.SORT_INDICATOR_DOWN_KEY' + + + + + + + + + + + + + + 'ObjectListView.SORT_INDICATOR_UP_KEY' + + + + + + + + + + + + + + 'ObjectListView' + 'ISupportInitialize.BeginInit()' + + + + + + + + + 'ObjectListView' + 'ISupportInitialize.EndInit()' + + + + + + + + + Renderering + 'ObjectListView.TextRendereringHint' + + + + + + + + + Unsort + 'ObjectListView.Unsort()' + + + + + + + + + 'ObjectListView.WndProc(ref Message)' + 'ListView.WndProc(ref Message)' + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] + + + + + 'ObjectListView.WndProc(ref Message)' + 'Control.DefWndProc(ref Message)' + ->'ObjectListView.WndProc(ref Message)' ->'ObjectListView.WndProc(ref Message)' + + + 'ObjectListView.WndProc(ref Message)' + 'ListView.WndProc(ref Message)' + ->'ObjectListView.WndProc(ref Message)' ->'ObjectListView.WndProc(ref Message)' + + + 'ObjectListView.WndProc(ref Message)' + 'Message.Msg.get()' + ->'ObjectListView.WndProc(ref Message)' ->'ObjectListView.WndProc(ref Message)' + + + + + + + + + + + + + + + + + + 'ObjectListView.ObjectListViewState.VersionNumber' + + + + + + + + + + + + + + + + 'OLVColumnAttribute' + + + + + 'OLVColumnAttribute.Title' + 'title' + + + + + 'OLVColumnAttribute' + + + + + + + + + Cutoffs + 'OLVColumnAttribute.GroupCutoffs' + + + + + 'OLVColumnAttribute.GroupCutoffs' + + + + + + + + + 'OLVColumnAttribute.GroupDescriptions' + + + + + + + + + + + + + 'OLVDataObject.ConvertToHtmlFragment(string)' + 'string.IndexOf(string)' + 'string.IndexOf(string, StringComparison)' + + + + + + + + + + + + + 'OLVGroup.GetState()' + + + + + + + + + 'OLVGroup.State' + 'OLVGroup.GetState()' + + + + + + + + + Subseted + 'OLVGroup.Subseted' + + + + + + + + + + + 'OLVListItem' + + + + + + + + + 'OLVListItem.Bounds' + 'ListViewItem.GetBounds(ItemBoundsPortion)' + + + + + + + + + + + 'value' + 'string' + 'OLVListItem.ImageSelector.set(object)' + castclass + + + + + + + + + + + + + + + 'OLVListSubItem.Url' + + + + + + + + + + + 'SimpleDropSink' + 'Timer' + + + + + + + + + 'Timer.Interval.set(int)' + 'SimpleDropSink.SimpleDropSink()' + + + + + + + + + + + 'TextAdornment' + 'StringFormat' + + + + + + + + + + + 'TextMatchFilter.TextMatchFilter(ObjectListView, string, OLVColumn[])' + StringComparison.InvariantCultureIgnoreCase + 'TextMatchFilter.TextMatchFilter(ObjectListView, string, OLVColumn[], TextMatchFilter.MatchKind, StringComparison)' + + + + + + + + + 'TextMatchFilter.TextMatchFilter(ObjectListView, string, TextMatchFilter.MatchKind)' + StringComparison.InvariantCultureIgnoreCase + 'TextMatchFilter.TextMatchFilter(ObjectListView, string, OLVColumn[], TextMatchFilter.MatchKind, StringComparison)' + + + + + + + + + 'TextMatchFilter.Columns' + + + + + + + + + 'TextMatchFilter.IsIncluded(OLVColumn)' + + + + + + + + + + + 'TextMatchFilter.MatchKind' + + + + + + + + + + + + + + 'TintedColumnDecoration' + 'SolidBrush' + + + + + + + + + + + Disp + 'ToolTipControl.HandleGetDispInfo(ref Message)' + + + + + + + + + + + + + + 'window' + 'ToolTipControl.PopToolTip(IWin32Window)' + + + + + + + + + + + 'ToolTipControl.StandardIcons' + + + + + + + + + + + 'List<TreeListView.Branch>' + 'TreeListView.Branch.ChildBranches' + + + + + + + + + 'List<TreeListView.Branch>' + 'TreeListView.Branch.FilteredChildBranches' + + + + + + + + + Flag + 'TreeListView.Branch.ManageLastChildFlag(MethodInvoker)' + + + + + + + + + + + Flags + 'TreeListView.Branch.BranchFlags' + + + + + + + + + + + 'TreeListView.Tree.GetBranchComparer()' + + + + + + + + + + + + + + + + + + 'TreeListView.TreeRenderer.PIXELS_PER_LEVEL' + + + + + + + + + + + + + 'TypedObjectListView<T>.BooleanCheckStateGetter' + + + + + + + + + 'TypedObjectListView<T>.BooleanCheckStatePutter' + + + + + + + + + 'TypedObjectListView<T>.CellToolTipGetter' + + + + + + + + + 'TypedObjectListView<T>.CheckedObjects' + + + + + + + + + + + + + + 'TypedObjectListView<T>.SelectedObjects' + + + + + + + + + + + + + + + + + + + + 'UintUpDown.Value.get()' + + + + + + + + + + + + + + 'UintUpDown.Value.set(uint)' + + + + + + + + + + + + + + + + + + + + 'VirtualObjectListView.CheckedObjects' + + + + + + + + + + + + + + 'VirtualObjectListView.HandleCacheVirtualItems(object, CacheVirtualItemsEventArgs)' + + + + + + + + + 'VirtualObjectListView.HandleRetrieveVirtualItem(object, RetrieveVirtualItemEventArgs)' + + + + + + + + + 'VirtualObjectListView.HandleSearchForVirtualItem(object, SearchForVirtualItemEventArgs)' + + + + + + + + + 'VirtualObjectListView.SetVirtualListSize(int)' + 'Exception' + + + + + + + + + + + + + + + + + + + + + These should be methods rather than properties + The out parameter is necessary since this method returns two pieces of information: the item under the point and the subitem item too + This is used to ensure we understand the newly load state. + All these properties should be assignable. + Our project is build with unsafe code enabled, so it automatically has the SecurityProperty set + These initializations are not unnecessary + We have to pass the windows message by reference + Instances of this class do not need to be disposable + These are utility methods that could well be used at runtime + Old style constants. Can't change now + These are OK like this. We need List<>, not IList<> since only List has a ToArray() method + Legacy cases that have to be kept like this + These are acceptable as methods rather than properties + windows messages should be passed by reference + This is not a security risk + There are several problems that can occur here and we want to ignore them all + These spellings are acceptable + These will only be used by OL types + + + Not appropriate here + Can't change now + we want to catch everthing + MS! + not flags + MS! + MS + + + + + Sign {0} with a strong name key. + + + Consider a design that does not require that {0} be an out parameter. + + + {0} appears to have no upstream public or protected callers. + + + Seal {0}, if possible. + + + It appears that field {0} is never used or is only ever assigned to. Use this field or remove it. + + + Change {0} to be read-only by removing the property setter. + + + Consider changing the type of parameter {0} in {1} from {2} to its base type {3}. This method appears to only require base class members in its implementation. Suppress this violation if there is a compelling reason to require the more derived type in the method signature. + + + Remove the property setter from {0} or reduce its accessibility because it corresponds to positional argument {1}. + + + {0}, a parameter, is cast to type {1} multiple times in method {2}. Cache the result of the 'as' operator or direct cast in order to eliminate the redundant {3} instruction. + + + Modify {0} to catch a more specific exception than {1} or rethrow the exception. + + + Change {0} in {1} to use Collection<T>, ReadOnlyCollection<T> or KeyedCollection<K,V> + + + {0} calls {1} but does not use the HRESULT or error code that the method returns. This could lead to unexpected behavior in error conditions or low-resource situations. Use the result in a conditional statement, assign the result to a variable, or pass it as an argument to another method. + {0} creates a new instance of {1} which is never used. Pass the instance as an argument to another method, assign the instance to a variable, or remove the object creation if it is unnecessary. + + + + {0} initializes field {1} of type {2} to {3}. Remove this initialization because it will be done automatically by the runtime. + + + {0} is marked with FlagsAttribute but a discrete member cannot be found for every settable bit that is used across the range of enum values. Remove FlagsAttribute from the type or define new members for the following (currently missing) values: {1} + + + + Modify the call to {0} in method {1} to set the timer interval to a value that's greater than or equal to one second. + + + In enum {0}, change the name of {1} to 'None'. + + + If enumeration name {0} is singular, change it to a plural form. + + + Correct the spelling of '{0}' in member name {1} or remove it entirely if it represents any sort of Hungarian notation. + In method {0}, correct the spelling of '{1}' in parameter name {2} or remove it entirely if it represents any sort of Hungarian notation. + Correct the spelling of '{0}' in type name {1}. + + + + Make {0} sealed (a breaking change if this class has previously shipped), implement the method non-explicitly, or implement a new method that exposes the functionality of {1} and is visible to derived classes. + + + Specify AttributeUsage on {0}. + + + Add the MarshalAsAttribute to parameter {0} of P/Invoke {1}. If the corresponding unmanaged parameter is a 4-byte Win32 'BOOL', use [MarshalAs(UnmanagedType.Bool)]. For a 1-byte C++ 'bool', use MarshalAs(UnmanagedType.U1). + Add the MarshalAsAttribute to the return type of P/Invoke {0}. If the corresponding unmanaged return type is a 4-byte Win32 'BOOL', use MarshalAs(UnmanagedType.Bool). For a 1-byte C++ 'bool', use MarshalAs(UnmanagedType.U1). + + + The constituent members of {0} appear to represent flags that can be combined rather than discrete values. If this is correct, mark the enumeration with FlagsAttribute. + + + Add [Serializable] to {0} as this type implements ISerializable. + + + Consider making {0} non-public or a constant. + + + If the name {0} is plural, change it to its singular form. + + + Add the following security attribute to {0} in order to match a LinkDemand on base method {1}: {2}. + + + As it is declared in your code, parameter {0} of P/Invoke {1} will be {2} bytes wide on {3} platforms. This is not correct, as the actual native declaration of this API indicates it should be {4} bytes wide on {3} platforms. Consult the MSDN Platform SDK documentation for help determining what data type should be used instead of {5}. + As it is declared in your code, the return type of P/Invoke {0} will be {1} bytes wide on {2} platforms. This is not correct, as the actual native declaration of this API indicates it should be {3} bytes wide on {2} platforms. Consult the MSDN Platform SDK documentation for help determining what data type should be used instead of {4}. + + + Correct the declaration of {0} so that it correctly points to an existing entry point in {1}. The unmanaged entry point name currently linked to is {2}. + + + Because property {0} is write-only, either add a property getter with an accessibility that is greater than or equal to its setter or convert this property into a method. + + + Change {0} to return a collection or make it a method. + + + The property name {0} is confusing given the existence of inherited method {1}. Rename or remove this property. + The property name {0} is confusing given the existence of method {1}. Rename or remove one of these members. + + + Parameter {0} of {1} is never used. Remove the parameter or use it in the method body. + + + Consider making {0} not externally visible. + + + To reduce security risk, marshal parameter {0} as Unicode, by setting DllImport.CharSet to CharSet.Unicode, or by explicitly marshaling the parameter as UnmanagedType.LPWStr. If you need to marshal this string as ANSI or system-dependent, set BestFitMapping=false; for added security, also set ThrowOnUnmappableChar=true. + + + {0} makes a call to {1} that does not explicitly provide a StringComparison. This should be replaced with a call to {2}. + + + Implement IDisposable on {0} because it creates members of the following IDisposable types: {1}. If {0} has previously shipped, adding new members that implement IDisposable to this type is considered a breaking change to existing consumers. + + + Change the type of parameter {0} of method {1} from string to System.Uri, or provide an overload of {1}, that allows {0} to be passed as a System.Uri object. + + + Change the type of property {0} from string to System.Uri. + + + Remove {0} and replace its usage with EventHandler<T> + + + {0} passes {1} as an argument to {2}. Replace this usage with StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase if appropriate. + + + Replace the term '{0}' in member name {1} with an appropriate alternate or remove it entirely. + Replace the term '{0}' in type name {1} with an appropriate alternate or remove it entirely. + + + Change {0} to a property if appropriate. + + + + diff --git a/VG Music Studio - WinForms/ObjectListView/ObjectListView.cs b/VG Music Studio - WinForms/ObjectListView/ObjectListView.cs new file mode 100644 index 0000000..45f8cb7 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/ObjectListView.cs @@ -0,0 +1,10924 @@ +/* + * ObjectListView - A listview to show various aspects of a collection of objects + * + * Author: Phillip Piper + * Date: 9/10/2006 11:15 AM + * + * Change log + * 2018-10-06 JPP - InsertObjects() when in a non-Detail View now correctly positions the items (SF bug #154) + * 2018-09-01 JPP - Hardened code against the rare case of the control having no columns (SF bug #174) + * The underlying ListView does not like having rows when there are no columns and throws exceptions.j + * 2018-05-05 JPP - Added OLVColumn.EditorCreator to allow fine control over what control is used to edit + * a particular cell. + * - Added IOlvEditor to allow custom editor to easily integrate with our editing scheme + * - ComboBoxes resize drop downs to show the widest entry via ControlUtilities.AutoResizeDropDown() + * 2018-05-03 JPP - Extend OnColumnRightClick so the event handler can tweak the menu to be shown + * 2018-04-27 JPP - Sorting now works when grouping is locked on a column AND SortGroupItemsByPrimaryColumn is true + * - Correctly report right clicks on group headers via CellRightClick events. + * v2.9.2 + * 2016-06-02 JPP - Cell editors now respond to mouse wheel events. Set AllowCellEditorsToProcessMouseWheel + * to false revert to previous behaviour. + * - Fixed issue in PauseAnimations() that prevented it from working until + * after the control had been rendered at least once. + * - CellEditUseWholeCell now has correct default value (true). + * - Dropping on a subitem when CellEditActivation is set to SingleClick no longer + * initiates a cell edit + * v2.9.1 + * 2015-12-30 JPP - Added CellRendererGetter to allow each cell to have a different renderer. + * - Obsolete properties are no longer code-gen'ed. + * + * v2.9.0 + * 2015-08-22 JPP - Allow selected row back/fore colours to be specified for each row + * - Renamed properties related to selection colours: + * - HighlightBackgroundColor -> SelectedBackColor + * - HighlightForegroundColor -> SelectedForeColor + * - UnfocusedHighlightBackgroundColor -> UnfocusedSelectedBackColor + * - UnfocusedHighlightForegroundColor -> UnfocusedSelectedForeColor + * - UseCustomSelectionColors is no longer used + * 2015-08-03 JPP - Added ObjectListView.CellEditFinished event + * - Added EditorRegistry.Unregister() + * 2015-07-08 JPP - All ObjectListViews are now OwnerDrawn by default. This allows all the great features + * of ObjectListView to work correctly at the slight cost of more processing at render time. + * It also avoids the annoying "hot item background ignored in column 0" behaviour that native + * ListView has. Programmers can still turn it back off if they wish. + * 2015-06-27 JPP - Yet another attempt to disable ListView's "shift click toggles checkboxes" behaviour. + * The last strategy (fake right click) worked, but had nasty side effects. This one works + * by intercepting a HITTEST message so that it fails. It no longer creates fake right mouse events. + * - Trigger SelectionChanged when filter is changed + * 2015-06-23 JPP - [BIG] Added support for Buttons + * 2015-06-22 JPP - Added OLVColumn.SearchValueGetter to allow the text used when text filtering to be customised + * - The default DefaultRenderer is now a HighlightTextRenderer, since that seems more generally useful + * 2015-06-17 JPP - Added FocusedObject property + * - Hot item is now always applied to the row even if FullRowSelect is false + * 2015-06-11 JPP - Added DefaultHotItemStyle property + * 2015-06-07 JPP - Added HeaderMinimumHeight property + * - Added ObjectListView.CellEditUsesWholeCell and OLVColumn.CellEditUsesWholeCell properties. + * 2015-05-15 JPP - Allow ImageGetter to return an Image (which I can't believe didn't work from the beginning!) + * 2015-04-27 JPP - Fix bug where setting View to LargeIcon in the designer was not persisted + * 2015-04-07 JPP - Ensure changes to row.Font in FormatRow are not wiped out by FormatCell (SF #141) + * + * v2.8.1 + * 2014-10-15 JPP - Added CellEditActivateMode.SingleClickAlways mode + * - Fire Filter event even if ModelFilter and ListFilter are null (SF #126) + * - Fixed issue where single-click editing didn't work (SF #128) + * v2.8.0 + * 2014-10-11 JPP - Fixed some XP-only flicker issues + * 2014-09-26 JPP - Fixed intricate bug involving checkboxes on non-owner-drawn virtual lists. + * - Fixed long standing (but previously unreported) error on non-details virtual lists where + * users could not click on checkboxes. + * 2014-09-07 JPP - (Major) Added ability to have checkboxes in headers + * - CellOver events are raised when the mouse moves over the header. Set TriggerCellOverEventsWhenOverHeader + * to false to disable this behaviour. + * - Freeze/Unfreeze now use BeginUpdate/EndUpdate to disable Window level drawing + * - Changed default value of ObjectListView.HeaderUsesThemes from true to false. Too many people were + * being confused, trying to make something interesting appear in the header and nothing showing up + * 2014-08-04 JPP - Final attempt to fix the multiple hyperlink events being raised. This involves turning + * a NM_CLICK notification into a NM_RCLICK. + * 2014-05-21 JPP - (Major) Added ability to disable rows. DisabledObjects, DisableObjects(), DisabledItemStyle. + * 2014-04-25 JPP - Fixed issue where virtual lists containing a single row didn't update hyperlinks on MouseOver + * - Added sanity check before BuildGroups() + * 2014-03-22 JPP - Fixed some subtle bugs resulting from misuse of TryGetValue() + * 2014-03-09 JPP - Added CollapsedGroups property + * - Several minor Resharper complaints quiesced. + * v2.7 + * 2014-02-14 JPP - Fixed issue with ShowHeaderInAllViews (another one!) where setting it to false caused the list to lose + * its other extended styles, leading to nasty flickering and worse. + * 2014-02-06 JPP - Fix issue on virtual lists where the filter was not correctly reapplied after columns were added or removed. + * - Made disposing of cell editors optional (defaults to true). This allows controls to be cached and reused. + * - Bracketed column resizing with BeginUpdate/EndUpdate to smooth redraws (thanks to Davide) + * 2014-02-01 JPP - Added static property ObjectListView.GroupTitleDefault to allow the default group title to be localised. + * 2013-09-24 JPP - Fixed issue in RefreshObjects() when model objects overrode the Equals()/GetHashCode() methods. + * - Made sure get state checker were used when they should have been + * 2013-04-21 JPP - Clicking on a non-groupable column header when showing groups will now sort + * the group contents by that column. + * v2.6 + * 2012-08-16 JPP - Added ObjectListView.EditModel() -- a convenience method to start an edit operation on a model + * 2012-08-10 JPP - Don't trigger selection changed events during sorting/grouping or add/removing columns + * 2012-08-06 JPP - Don't start a cell edit operation when the user clicks on the background of a checkbox cell. + * - Honor values from the BeforeSorting event when calling a CustomSorter + * 2012-08-02 JPP - Added CellVerticalAlignment and CellPadding properties. + * 2012-07-04 JPP - Fixed issue with cell editing where the cell editing didn't finish until the first idle event. + * This meant that if you clicked and held on the scroll thumb to finish a cell edit, the editor + * wouldn't be removed until the mouse was released. + * 2012-07-03 JPP - Fixed issue with SingleClick cell edit mode where the cell editing would not begin until the + * mouse moved after the click. + * 2012-06-25 JPP - Fixed bug where removing a column from a LargeIcon or SmallIcon view would crash the control. + * 2012-06-15 JPP - Added Reset() method, which definitively removes all rows *and* columns from an ObjectListView. + * 2012-06-11 JPP - Added FilteredObjects property which returns the collection of objects that survives any installed filters. + * 2012-06-04 JPP - [Big] Added UseNotifyPropertyChanged to allow OLV to listen for INotifyPropertyChanged events on models. + * 2012-05-30 JPP - Added static property ObjectListView.IgnoreMissingAspects. If this is set to true, all + * ObjectListViews will silently ignore missing aspect errors. Read the remarks to see why this would be useful. + * 2012-05-23 JPP - Setting UseFilterIndicator to true now sets HeaderUsesTheme to false. + * Also, changed default value of UseFilterIndicator to false. Previously, HeaderUsesTheme and UseFilterIndicator + * defaulted to true, which was pointless since when the HeaderUsesTheme is true, UseFilterIndicator does nothing. + * v2.5.1 + * 2012-05-06 JPP - Fix bug where collapsing the first group would cause decorations to stop being drawn (SR #3502608) + * 2012-04-23 JPP - Trigger GroupExpandingCollapsing event to allow the expand/collapse to be cancelled + * - Fixed SetGroupSpacing() so it corrects updates the space between all groups. + * - ResizeLastGroup() now does nothing since it was broken and I can't remember what it was + * even supposed to do :) + * 2012-04-18 JPP - Upgraded hit testing to include hits on groups. + * - HotItemChanged is now correctly recalculated on each mouse move. Includes "hot" group information. + * 2012-04-14 JPP - Added GroupStateChanged event. Useful for knowing when a group is collapsed/expanded. + * - Added AdditionalFilter property. This filter is combined with the Excel-like filtering that + * the end user might enact at runtime. + * 2012-04-10 JPP - Added PersistentCheckBoxes property to allow primary checkboxes to remember their values + * across list rebuilds. + * 2012-04-05 JPP - Reverted some code to .NET 2.0 standard. + * - Tweaked some code + * 2012-02-05 JPP - Fixed bug when selecting a separator on a drop down menu + * 2011-06-24 JPP - Added CanUseApplicationIdle property to cover cases where Application.Idle events + * are not triggered. For example, when used within VS (and probably Office) extensions + * Application.Idle is never triggered. Set CanUseApplicationIdle to false to handle + * these cases. + * - Handle cases where a second tool tip is installed onto the ObjectListView. + * - Correctly recolour rows after an Insert or Move + * - Removed m.LParam cast which could cause overflow issues on Win7/64 bit. + * v2.5.0 + * 2011-05-31 JPP - SelectObject() and SelectObjects() no longer deselect all other rows. + Set the SelectedObject or SelectedObjects property to do that. + * - Added CheckedObjectsEnumerable + * - Made setting CheckedObjects more efficient on large collections + * - Deprecated GetSelectedObject() and GetSelectedObjects() + * 2011-04-25 JPP - Added SubItemChecking event + * - Fixed bug in handling of NewValue on CellEditFinishing event + * 2011-04-12 JPP - Added UseFilterIndicator + * - Added some more localizable messages + * 2011-04-10 JPP - FormatCellEventArgs now has a CellValue property, which is the model value displayed + * by the cell. For example, for the Birthday column, the CellValue might be + * DateTime(1980, 12, 31), whereas the cell's text might be 'Dec 31, 1980'. + * 2011-04-04 JPP - Tweaked UseTranslucentSelection and UseTranslucentHotItem to look (a little) more + * like Vista/Win7. + * - Alternate colours are now only applied in Details view (as they always should have been) + * - Alternate colours are now correctly recalculated after removing objects + * 2011-03-29 JPP - Added SelectColumnsOnRightClickBehaviour to allow the selecting of columns mechanism + * to be changed. Can now be InlineMenu (the default), SubMenu, or ModelDialog. + * - ColumnSelectionForm was moved from the demo into the ObjectListView project itself. + * - Ctrl-C copying is now able to use the DragSource to create the data transfer object. + * 2011-03-19 JPP - All model object comparisons now use Equals rather than == (thanks to vulkanino) + * - [Small Break] GetNextItem() and GetPreviousItem() now accept and return OLVListView + * rather than ListViewItems. + * 2011-03-07 JPP - [Big] Added Excel-style filtering. Right click on a header to show a Filtering menu. + * - Added CellEditKeyEngine to allow key handling when cell editing to be completely customised. + * Add CellEditTabChangesRows and CellEditEnterChangesRows to show some of these abilities. + * 2011-03-06 JPP - Added OLVColumn.AutoCompleteEditorMode in preference to AutoCompleteEditor + * (which is now just a wrapper). Thanks to Clive Haskins + * - Added lots of docs to new classes + * 2011-02-25 JPP - Preserve word wrap settings on TreeListView + * - Resize last group to keep it on screen (thanks to ?) + * 2010-11-16 JPP - Fixed (once and for all) DisplayIndex problem with Generator + * - Changed the serializer used in SaveState()/RestoreState() so that it resolves on + * class name alone. + * - Fixed bug in GroupWithItemCountSingularFormatOrDefault + * - Fixed strange flickering in grouped, owner drawn OLV's using RefreshObject() + * v2.4.1 + * 2010-08-25 JPP - Fixed bug where setting OLVColumn.CheckBoxes to false gave it a renderer + * specialized for checkboxes. Oddly, this made Generator created owner drawn + * lists appear to be completely empty. + * - In IDE, all ObjectListView properties are now in a single "ObjectListView" category, + * rather than splitting them between "Appearance" and "Behavior" categories. + * - Added GroupingParameters.GroupComparer to allow groups to be sorted in a customizable fashion. + * - Sorting of items within a group can be disabled by setting + * GroupingParameters.PrimarySortOrder to None. + * 2010-08-24 JPP - Added OLVColumn.IsHeaderVertical to make a column draw its header vertical. + * - Added OLVColumn.HeaderTextAlign to control the alignment of a column's header text. + * - Added HeaderMaximumHeight to limit how tall the header section can become + * 2010-08-18 JPP - Fixed long standing bug where having 0 columns caused a InvalidCast exception. + * - Added IncludeAllColumnsInDataObject property + * - Improved BuildList(bool) so that it preserves scroll position even when + * the listview is grouped. + * 2010-08-08 JPP - Added OLVColumn.HeaderImageKey to allow column headers to have an image. + * - CellEdit validation and finish events now have NewValue property. + * 2010-08-03 JPP - Subitem checkboxes improvements: obey IsEditable, can be hot, can be disabled. + * - No more flickering of selection when tabbing between cells + * - Added EditingCellBorderDecoration to make it clearer which cell is being edited. + * 2010-08-01 JPP - Added ObjectListView.SmoothingMode to control the smoothing of all graphics + * operations + * - Columns now cache their group item format strings so that they still work as + * grouping columns after they have been removed from the listview. This cached + * value is only used when the column is not part of the listview. + * 2010-07-25 JPP - Correctly trigger a Click event when the mouse is clicked. + * 2010-07-16 JPP - Invalidate the control before and after cell editing to make sure it looks right + * 2010-06-23 JPP - Right mouse clicks on checkboxes no longer confuse them + * 2010-06-21 JPP - Avoid bug in underlying ListView control where virtual lists in SmallIcon view + * generate GETTOOLINFO msgs with invalid item indices. + * - Fixed bug where FastObjectListView would throw an exception when showing hyperlinks + * in any view except Details. + * 2010-06-15 JPP - Fixed bug in ChangeToFilteredColumns() that resulted in column display order + * being lost when a column was hidden. + * - Renamed IsVista property to IsVistaOrLater which more accurately describes its function. + * v2.4 + * 2010-04-14 JPP - Prevent object disposed errors when mouse event handlers cause the + * ObjectListView to be destroyed (e.g. closing a form during a + * double click event). + * - Avoid checkbox munging bug in standard ListView when shift clicking on non-primary + * columns when FullRowSelect is true. + * 2010-04-12 JPP - Fixed bug in group sorting (thanks Mike). + * 2010-04-07 JPP - Prevent hyperlink processing from triggering spurious MouseUp events. + * This showed itself by launching the same url multiple times. + * 2010-04-06 JPP - Space filling columns correctly resize upon initial display + * - ShowHeaderInAllViews is better but still not working reliably. + * See comments on property for more details. + * 2010-03-23 JPP - Added ObjectListView.HeaderFormatStyle and OLVColumn.HeaderFormatStyle. + * This makes HeaderFont and HeaderForeColor properties unnecessary -- + * they will be marked obsolete in the next version and removed after that. + * 2010-03-16 JPP - Changed object checking so that objects can be pre-checked before they + * are added to the list. Normal ObjectListViews managed "checkedness" in + * the ListViewItem, so this won't work for them, unless check state getters + * and putters have been installed. It will work not on virtual lists (thus fast lists and + * tree views) since they manage their own check state. + * 2010-03-06 JPP - Hide "Items" and "Groups" from the IDE properties grid since they shouldn't be set like that. + * They can still be accessed through "Custom Commands" and there's nothing we can do + * about that. + * 2010-03-05 JPP - Added filtering + * 2010-01-18 JPP - Overlays can be turned off. They also only work on 32-bit displays + * v2.3 + * 2009-10-30 JPP - Plugged possible resource leak by using using() with CreateGraphics() + * 2009-10-28 JPP - Fix bug when right clicking in the empty area of the header + * 2009-10-20 JPP - Redraw the control after setting EmptyListMsg property + * v2.3 + * 2009-09-30 JPP - Added Dispose() method to properly release resources + * 2009-09-16 JPP - Added OwnerDrawnHeader, which you can set to true if you want to owner draw + * the header yourself. + * 2009-09-15 JPP - Added UseExplorerTheme, which allow complete visual compliance with Vista explorer. + * But see property documentation for its many limitations. + * - Added ShowHeaderInAllViews. To make this work, Columns are no longer + * changed when switching to/from Tile view. + * 2009-09-11 JPP - Added OLVColumn.AutoCompleteEditor to allow the autocomplete of cell editors + * to be disabled. + * 2009-09-01 JPP - Added ObjectListView.TextRenderingHint property which controls the + * text rendering hint of all drawn text. + * 2009-08-28 JPP - [BIG] Added group formatting to supercharge what is possible with groups + * - [BIG] Virtual groups now work + * - Extended MakeGroupies() to handle more aspects of group creation + * 2009-08-19 JPP - Added ability to show basic column commands when header is right clicked + * - Added SelectedRowDecoration, UseTranslucentSelection and UseTranslucentHotItem. + * - Added PrimarySortColumn and PrimarySortOrder + * 2009-08-15 JPP - Correct problems with standard hit test and subitems + * 2009-08-14 JPP - [BIG] Support Decorations + * - [BIG] Added header formatting capabilities: font, color, word wrap + * - Gave ObjectListView its own designer to hide unwanted properties + * - Separated design time stuff into separate file + * - Added FormatRow and FormatCell events + * 2009-08-09 JPP - Get around bug in HitTest when not FullRowSelect + * - Added OLVListItem.GetSubItemBounds() method which works correctly + * for all columns including column 0 + * 2009-08-07 JPP - Added Hot* properties that track where the mouse is + * - Added HotItemChanged event + * - Overrode TextAlign on columns so that column 0 can have something other + * than just left alignment. This is only honored when owner drawn. + * v2.2.1 + * 2009-08-03 JPP - Subitem edit rectangles always allowed for an image in the cell, even if there was none. + * Now they only allow for an image when there actually is one. + * - Added Bounds property to OLVListItem which handles items being part of collapsed groups. + * 2009-07-29 JPP - Added GetSubItem() methods to ObjectListView and OLVListItem + * 2009-07-26 JPP - Avoided bug in .NET framework involving column 0 of owner drawn listviews not being + * redrawn when the listview was scrolled horizontally (this was a LOT of work to track + * down and fix!) + * - The cell edit rectangle is now correctly calculated when the listview is scrolled + * horizontally. + * 2009-07-14 JPP - If the user clicks/double clicks on a tree list cell, an edit operation will no longer begin + * if the click was to the left of the expander. This is implemented in such a way that + * other renderers can have similar "dead" zones. + * 2009-07-11 JPP - CalculateCellBounds() messed with the FullRowSelect property, which confused the + * tooltip handling on the underlying control. It no longer does this. + * - The cell edit rectangle is now correctly calculated for owner-drawn, non-Details views. + * 2009-07-08 JPP - Added Cell events (CellClicked, CellOver, CellRightClicked) + * - Made BuildList(), AddObject() and RemoveObject() thread-safe + * 2009-07-04 JPP - Space bar now properly toggles checkedness of selected rows + * 2009-07-02 JPP - Fixed bug with tooltips when the underlying Windows control was destroyed. + * - CellToolTipShowing events are now triggered in all views. + * v2.2 + * 2009-06-02 JPP - BeforeSortingEventArgs now has a Handled property to let event handlers do + * the item sorting themselves. + * - AlwaysGroupByColumn works again, as does SortGroupItemsByPrimaryColumn and all their + * various permutations. + * - SecondarySortOrder and SecondarySortColumn are now "null" by default + * 2009-05-15 JPP - Fixed bug so that KeyPress events are again triggered + * 2009-05-10 JPP - Removed all unsafe code + * 2009-05-07 JPP - Don't use glass panel for overlays when in design mode. It's too confusing. + * 2009-05-05 JPP - Added Scroll event (thanks to Christophe Hosten for the complete patch to implement this) + * - Added Unfocused foreground and background colors (also thanks to Christophe Hosten) + * 2009-04-29 JPP - Added SelectedColumn property, which puts a slight tint on that column. Combine + * this with TintSortColumn property and the sort column is automatically tinted. + * - Use an overlay to implement "empty list" msg. Default empty list msg is now prettier. + * 2009-04-28 JPP - Fixed bug where DoubleClick events were not triggered when CheckBoxes was true + * 2009-04-23 JPP - Fixed various bugs under Vista. + * - Made groups collapsible - Vista only. Thanks to Crustyapplesniffer. + * - Forward events from DropSink to the control itself. This allows handlers to be defined + * within the IDE for drop events + * 2009-04-16 JPP - Made several properties localizable. + * 2009-04-11 JPP - Correctly renderer checkboxes when RowHeight is non-standard + * 2009-04-11 JPP - Implemented overlay architecture, based on CustomDraw scheme. + * This unified drag drop feedback, empty list msgs and overlay images. + * - Added OverlayImage and friends, which allows an image to be drawn + * transparently over the listview + * 2009-04-10 JPP - Fixed long-standing annoying flicker on owner drawn virtual lists! + * This means, amongst other things, that grid lines no longer get confused, + * and drag-select no longer flickers. + * 2009-04-07 JPP - Calculate edit rectangles more accurately + * 2009-04-06 JPP - Double-clicking no longer toggles the checkbox + * - Double-clicking on a checkbox no longer confuses the checkbox + * 2009-03-16 JPP - Optimized the build of autocomplete lists + * v2.1 + * 2009-02-24 JPP - Fix bug where double-clicking VERY quickly on two different cells + * could give two editors + * - Maintain focused item when rebuilding list (SF #2547060) + * 2009-02-22 JPP - Reworked checkboxes so that events are triggered for virtual lists + * 2009-02-15 JPP - Added ObjectListView.ConfigureAutoComplete utility method + * 2009-02-02 JPP - Fixed bug with AlwaysGroupByColumn where column header clicks would not resort groups. + * 2009-02-01 JPP - OLVColumn.CheckBoxes and TriStateCheckBoxes now work. + * 2009-01-28 JPP - Complete overhaul of renderers! + * - Use IRenderer + * - Added ObjectListView.ItemRenderer to draw whole items + * 2009-01-23 JPP - Simple Checkboxes now work properly + * - Added TriStateCheckBoxes property to control whether the user can + * set the row checkbox to have the Indeterminate value + * - CheckState property is now just a wrapper around the StateImageIndex property + * 2009-01-20 JPP - Changed to always draw columns when owner drawn, rather than falling back on DrawDefault. + * This simplified several owner drawn problems + * - Added DefaultRenderer property to help with the above + * - HotItem background color is applied to all cells even when FullRowSelect is false + * - Allow grouping by CheckedAspectName columns + * - Commented out experimental animations. Still needs work. + * 2009-01-17 JPP - Added HotItemStyle and UseHotItem to highlight the row under the cursor + * - Added UseCustomSelectionColors property + * - Owner draw mode now honors ForeColor and BackColor settings on the list + * 2009-01-16 JPP - Changed to use EditorRegistry rather than hard coding cell editors + * 2009-01-10 JPP - Changed to use Equals() method rather than == to compare model objects. + * v2.0.1 + * 2009-01-08 JPP - Fixed long-standing "multiple columns generated" problem. + * Thanks to pinkjones for his help with solving this one! + * - Added EnsureGroupVisible() + * 2009-01-07 JPP - Made all public and protected methods virtual + * - FinishCellEditing, PossibleFinishCellEditing and CancelCellEditing are now public + * 2008-12-20 JPP - Fixed bug with group comparisons when a group key was null (SF#2445761) + * 2008-12-19 JPP - Fixed bug with space filling columns and layout events + * - Fixed RowHeight so that it only changes the row height, not the width of the images. + * v2.0 + * 2008-12-10 JPP - Handle Backspace key. Resets the search-by-typing state without delay + * - Made some changes to the column collection editor to try and avoid + * the multiple column generation problem. + * - Updated some documentation + * 2008-12-07 JPP - Search-by-typing now works when showing groups + * - Added BeforeSearching and AfterSearching events which are triggered when the user types + * into the list. + * - Added secondary sort information to Before/AfterSorting events + * - Reorganized group sorting code. Now triggers Sorting events. + * - Added GetItemIndexInDisplayOrder() + * - Tweaked in the interaction of the column editor with the IDE so that we (normally) + * don't rely on a hack to find the owning ObjectListView + * - Changed all 'DefaultValue(typeof(Color), "Empty")' to 'DefaultValue(typeof(Color), "")' + * since the first does not given Color.Empty as I thought, but the second does. + * 2008-11-28 JPP - Fixed long standing bug with horizontal scrollbar when shrinking the window. + * (thanks to Bartosz Borowik) + * 2008-11-25 JPP - Added support for dynamic tooltips + * - Split out comparers and header controls stuff into their own files + * 2008-11-21 JPP - Fixed bug where enabling grouping when there was not a sort column would not + * produce a grouped list. Grouping column now defaults to column 0. + * - Preserve selection on virtual lists when sorting + * 2008-11-20 JPP - Added ability to search by sort column to ObjectListView. Unified this with + * ability that was already in VirtualObjectListView + * 2008-11-19 JPP - Fixed bug in ChangeToFilteredColumns() where DisplayOrder was not always restored correctly. + * 2008-10-29 JPP - Event argument blocks moved to directly within the namespace, rather than being + * nested inside ObjectListView class. + * - Removed OLVColumn.CellEditor since it was never used. + * - Marked OLVColumn.AspectGetterAutoGenerated as obsolete (it has not been used for + * several versions now). + * 2008-10-28 JPP - SelectedObjects is now an IList, rather than an ArrayList. This allows + * it to accept generic list (e.g. List). + * 2008-10-09 JPP - Support indeterminate checkbox values. + * [BREAKING CHANGE] CheckStateGetter/CheckStatePutter now use CheckState types only. + * BooleanCheckStateGetter and BooleanCheckStatePutter added to ease transition. + * 2008-10-08 JPP - Added setFocus parameter to SelectObject(), which allows focus to be set + * at the same time as selecting. + * 2008-09-27 JPP - BIG CHANGE: Fissioned this file into separate files for each component + * 2008-09-24 JPP - Corrected bug with owner drawn lists where a column 0 with a renderer + * would draw at column 0 even if column 0 was dragged to another position. + * - Correctly handle space filling columns when columns are added/removed + * 2008-09-16 JPP - Consistently use try..finally for BeginUpdate()/EndUpdate() pairs + * 2008-08-24 JPP - If LastSortOrder is None when adding objects, don't force a resort. + * 2008-08-22 JPP - Catch and ignore some problems with setting TopIndex on FastObjectListViews. + * 2008-08-05 JPP - In the right-click column select menu, columns are now sorted by display order, rather than alphabetically + * v1.13 + * 2008-07-23 JPP - Consistently use copy-on-write semantics with Add/RemoveObject methods + * 2008-07-10 JPP - Enable validation on cell editors through a CellEditValidating event. + * (thanks to Artiom Chilaru for the initial suggestion and implementation). + * 2008-07-09 JPP - Added HeaderControl.Handle to allow OLV to be used within UserControls. + * (thanks to Michael Coffey for tracking this down). + * 2008-06-23 JPP - Split the more generally useful CopyObjectsToClipboard() method + * out of CopySelectionToClipboard() + * 2008-06-22 JPP - Added AlwaysGroupByColumn and AlwaysGroupBySortOrder, which + * force the list view to always be grouped by a particular column. + * 2008-05-31 JPP - Allow check boxes on FastObjectListViews + * - Added CheckedObject and CheckedObjects properties + * 2008-05-11 JPP - Allow selection foreground and background colors to be changed. + * Windows doesn't allow this, so we can only make it happen when owner + * drawing. Set the HighlightForegroundColor and HighlightBackgroundColor + * properties and then call EnableCustomSelectionColors(). + * v1.12 + * 2008-05-08 JPP - Fixed bug where the column select menu would not appear if the + * ObjectListView has a context menu installed. + * 2008-05-05 JPP - Non detail views can now be owner drawn. The renderer installed for + * primary column is given the chance to render the whole item. + * See BusinessCardRenderer in the demo for an example. + * - BREAKING CHANGE: RenderDelegate now returns a bool to indicate if default + * rendering should be done. Previously returned void. Only important if your + * code used RendererDelegate directly. Renderers derived from BaseRenderer + * are unchanged. + * 2008-05-03 JPP - Changed cell editing to use values directly when the values are Strings. + * Previously, values were always handed to the AspectToStringConverter. + * - When editing a cell, tabbing no longer tries to edit the next subitem + * when not in details view! + * 2008-05-02 JPP - MappedImageRenderer can now handle a Aspects that return a collection + * of values. Each value will be drawn as its own image. + * - Made AddObjects() and RemoveObjects() work for all flavours (or at least not crash) + * - Fixed bug with clearing virtual lists that has been scrolled vertically + * - Made TopItemIndex work with virtual lists. + * 2008-05-01 JPP - Added AddObjects() and RemoveObjects() to allow faster mods to the list + * - Reorganised public properties. Now alphabetical. + * - Made the class ObjectListViewState internal, as it always should have been. + * v1.11 + * 2008-04-29 JPP - Preserve scroll position when building the list or changing columns. + * - Added TopItemIndex property. Due to problems with the underlying control, this + * property is not always reliable. See property docs for info. + * 2008-04-27 JPP - Added SelectedIndex property. + * - Use a different, more general strategy to handle Invoke(). Removed all delegates + * that were only declared to support Invoke(). + * - Check all native structures for 64-bit correctness. + * 2008-04-25 JPP - Released on SourceForge. + * 2008-04-13 JPP - Added ColumnRightClick event. + * - Made the assembly CLS-compliant. To do this, our cell editors were made internal, and + * the constraint on FlagRenderer template parameter was removed (the type must still + * be an IConvertible, but if it isn't, the error will be caught at runtime, not compile time). + * 2008-04-12 JPP - Changed HandleHeaderRightClick() to have a columnIndex parameter, which tells + * exactly which column was right-clicked. + * 2008-03-31 JPP - Added SaveState() and RestoreState() + * - When cell editing, scrolling with a mouse wheel now ends the edit operation. + * v1.10 + * 2008-03-25 JPP - Added space filling columns. See OLVColumn.FreeSpaceProportion property for details. + * A space filling columns fills all (or a portion) of the width unoccupied by other columns. + * 2008-03-23 JPP - Finished tinkering with support for Mono. Compile with conditional compilation symbol 'MONO' + * to enable. On Windows, current problems with Mono: + * - grid lines on virtual lists crashes + * - when grouped, items sometimes are not drawn when any item is scrolled out of view + * - i can't seem to get owner drawing to work + * - when editing cell values, the editing controls always appear behind the listview, + * where they function fine -- the user just can't see them :-) + * 2008-03-16 JPP - Added some methods suggested by Chris Marlowe (thanks for the suggestions Chris) + * - ClearObjects() + * - GetCheckedObject(), GetCheckedObjects() + * - GetItemAt() variation that gets both the item and the column under a point + * 2008-02-28 JPP - Fixed bug with subitem colors when using OwnerDrawn lists and a RowFormatter. + * v1.9.1 + * 2008-01-29 JPP - Fixed bug that caused owner-drawn virtual lists to use 100% CPU + * - Added FlagRenderer to help draw bitwise-OR'ed flag values + * 2008-01-23 JPP - Fixed bug (introduced in v1.9) that made alternate row colour with groups not quite right + * - Ensure that DesignerSerializationVisibility.Hidden is set on all non-browsable properties + * - Make sure that sort indicators are shown after changing which columns are visible + * 2008-01-21 JPP - Added FastObjectListView + * v1.9 + * 2008-01-18 JPP - Added IncrementalUpdate() + * 2008-01-16 JPP - Right clicking on column header will allow the user to choose which columns are visible. + * Set SelectColumnsOnRightClick to false to prevent this behaviour. + * - Added ImagesRenderer to draw more than one images in a column + * - Changed the positioning of the empty list m to use all the client area. Thanks to Matze. + * 2007-12-13 JPP - Added CopySelectionToClipboard(). Ctrl-C invokes this method. Supports text + * and HTML formats. + * 2007-12-12 JPP - Added support for checkboxes via CheckStateGetter and CheckStatePutter properties. + * - Made ObjectListView and OLVColumn into partial classes so that others can extend them. + * 2007-12-09 JPP - Added ability to have hidden columns, i.e. columns that the ObjectListView knows + * about but that are not visible to the user. Controlled by OLVColumn.IsVisible. + * Added ColumnSelectionForm to the project to show how it could be used in an application. + * + * v1.8 + * 2007-11-26 JPP - Cell editing fully functional + * 2007-11-21 JPP - Added SelectionChanged event. This event is triggered once when the + * selection changes, no matter how many items are selected or deselected (in + * contrast to SelectedIndexChanged which is called once for every row that + * is selected or deselected). Thanks to lupokehl42 (Daniel) for his suggestions and + * improvements on this idea. + * 2007-11-19 JPP - First take at cell editing + * 2007-11-17 JPP - Changed so that items within a group are not sorted if lastSortOrder == None + * - Only call MakeSortIndicatorImages() if we haven't already made the sort indicators + * (Corrected misspelling in the name of the method too) + * 2007-11-06 JPP - Added ability to have secondary sort criteria when sorting + * (SecondarySortColumn and SecondarySortOrder properties) + * - Added SortGroupItemsByPrimaryColumn to allow group items to be sorted by the + * primary column. Previous default was to sort by the grouping column. + * v1.7 + * No big changes to this version but made to work with ListViewPrinter and released with it. + * + * 2007-11-05 JPP - Changed BaseRenderer to use DrawString() rather than TextRenderer, since TextRenderer + * does not work when printing. + * v1.6 + * 2007-11-03 JPP - Fixed some bugs in the rebuilding of DataListView. + * 2007-10-31 JPP - Changed to use builtin sort indicators on XP and later. This also avoids alignment + * problems on Vista. (thanks to gravybod for the suggestion and example implementation) + * 2007-10-21 JPP - Added MinimumWidth and MaximumWidth properties to OLVColumn. + * - Added ability for BuildList() to preserve selection. Calling BuildList() directly + * tries to preserve selection; calling SetObjects() does not. + * - Added SelectAll() and DeselectAll() methods. Useful for working with large lists. + * 2007-10-08 JPP - Added GetNextItem() and GetPreviousItem(), which walk sequentially through the + * listview items, even when the view is grouped. + * - Added SelectedItem property + * 2007-09-28 JPP - Optimized aspect-to-string conversion. BuildList() 15% faster. + * - Added empty implementation of RefreshObjects() to VirtualObjectListView since + * RefreshObjects() cannot work on virtual lists. + * 2007-09-13 JPP - Corrected bug with custom sorter in VirtualObjectListView (thanks for mpgjunky) + * 2007-09-07 JPP - Corrected image scaling bug in DrawAlignedImage() (thanks to krita970) + * 2007-08-29 JPP - Allow item count labels on groups to be set per column (thanks to cmarlow for idea) + * 2007-08-14 JPP - Major rework of DataListView based on Ian Griffiths's great work + * 2007-08-11 JPP - When empty, the control can now draw a "List Empty" m + * - Added GetColumn() and GetItem() methods + * v1.5 + * 2007-08-03 JPP - Support animated GIFs in ImageRenderer + * - Allow height of rows to be specified - EXPERIMENTAL! + * 2007-07-26 JPP - Optimised redrawing of owner-drawn lists by remembering the update rect + * - Allow sort indicators to be turned off + * 2007-06-30 JPP - Added RowFormatter delegate + * - Allow a different label when there is only one item in a group (thanks to cmarlow) + * v1.4 + * 2007-04-12 JPP - Allow owner drawn on steriods! + * - Column headers now display sort indicators + * - ImageGetter delegates can now return ints, strings or Images + * (Images are only visible if the list is owner drawn) + * - Added OLVColumn.MakeGroupies to help with group partitioning + * - All normal listview views are now supported + * - Allow dotted aspect names, e.g. Owner.Workgroup.Name (thanks to OlafD) + * - Added SelectedObject and SelectedObjects properties + * v1.3 + * 2007-03-01 JPP - Added DataListView + * - Added VirtualObjectListView + * - Added Freeze/Unfreeze capabilities + * - Allowed sort handler to be installed + * - Simplified sort comparisons: handles 95% of cases with only 6 lines of code! + * - Fixed bug with alternative line colors on unsorted lists (thanks to cmarlow) + * 2007-01-13 JPP - Fixed bug with lastSortOrder (thanks to Kwan Fu Sit) + * - Non-OLVColumns are no longer allowed + * 2007-01-04 JPP - Clear sorter before rebuilding list. 10x faster! (thanks to aaberg) + * - Include GetField in GetAspectByName() so field values can be Invoked too. + * - Fixed subtle bug in RefreshItem() that erased background colors. + * 2006-11-01 JPP - Added alternate line colouring + * 2006-10-20 JPP - Refactored all sorting comparisons and made it extendable. See ComparerManager. + * - Improved IDE integration + * - Made control DoubleBuffered + * - Added object selection methods + * 2006-10-13 JPP Implemented grouping and column sorting + * 2006-10-09 JPP Initial version + * + * TO DO: + * - Support undocumented group features: subseted groups, group footer items + * + * Copyright (C) 2006-2018 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Serialization.Formatters.Binary; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; +using System.Runtime.Serialization.Formatters; +using System.Threading; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// An ObjectListView is a much easier to use, and much more powerful, version of the ListView. + /// + /// + /// + /// An ObjectListView automatically populates a ListView control with information taken + /// from a given collection of objects. It can do this because each column is configured + /// to know which bit of the model object (the "aspect") it should be displaying. Columns similarly + /// understand how to sort the list based on their aspect, and how to construct groups + /// using their aspect. + /// + /// + /// Aspects are extracted by giving the name of a method to be called or a + /// property to be fetched. These names can be simple names or they can be dotted + /// to chain property access e.g. "Owner.Address.Postcode". + /// Aspects can also be extracted by installing a delegate. + /// + /// + /// An ObjectListView can show a "this list is empty" message when there is nothing to show in the list, + /// so that the user knows the control is supposed to be empty. + /// + /// + /// Right clicking on a column header should present a menu which can contain: + /// commands (sort, group, ungroup); filtering; and column selection. Whether these + /// parts of the menu appear is controlled by ShowCommandMenuOnRightClick, + /// ShowFilterMenuOnRightClick and SelectColumnsOnRightClick respectively. + /// + /// + /// The groups created by an ObjectListView can be configured to include other formatting + /// information, including a group icon, subtitle and task button. Using some undocumented + /// interfaces, these groups can even on virtual lists. + /// + /// + /// ObjectListView supports dragging rows to other places, including other application. + /// Special support is provide for drops from other ObjectListViews in the same application. + /// In many cases, an ObjectListView becomes a full drag source by setting to + /// true. Similarly, to accept drops, it is usually enough to set to true, + /// and then handle the and events (or the and + /// events, if you only want to handle drops from other ObjectListViews in your application). + /// + /// + /// For these classes to build correctly, the project must have references to these assemblies: + /// + /// + /// System + /// System.Data + /// System.Design + /// System.Drawing + /// System.Windows.Forms (obviously) + /// + /// + [Designer(typeof(Kermalis.VGMusicStudio.WinForms.ObjectListView.Design.ObjectListViewDesigner))] + public partial class ObjectListView : ListView, ISupportInitialize { + + #region Life and death + + /// + /// Create an ObjectListView + /// + public ObjectListView() { + this.ColumnClick += new ColumnClickEventHandler(this.HandleColumnClick); + this.Layout += new LayoutEventHandler(this.HandleLayout); + this.ColumnWidthChanging += new ColumnWidthChangingEventHandler(this.HandleColumnWidthChanging); + this.ColumnWidthChanged += new ColumnWidthChangedEventHandler(this.HandleColumnWidthChanged); + + base.View = View.Details; + + // Turn on owner draw so that we are responsible for our own fates (and isolated from bugs in the underlying ListView) + this.OwnerDraw = true; + +// ReSharper disable DoNotCallOverridableMethodsInConstructor + this.DoubleBuffered = true; // kill nasty flickers. hiss... me hates 'em + this.ShowSortIndicators = true; + + // Setup the overlays that will be controlled by the IDE settings + this.InitializeStandardOverlays(); + this.InitializeEmptyListMsgOverlay(); +// ReSharper restore DoNotCallOverridableMethodsInConstructor + } + + /// + /// Dispose of any resources this instance has been using + /// + /// + protected override void Dispose(bool disposing) { + base.Dispose(disposing); + + if (!disposing) + return; + + foreach (GlassPanelForm glassPanel in this.glassPanels) { + glassPanel.Unbind(); + glassPanel.Dispose(); + } + this.glassPanels.Clear(); + + this.UnsubscribeNotifications(null); + } + + #endregion + + // TODO + //public CheckBoxSettings CheckBoxSettings { + // get { return checkBoxSettings; } + // private set { checkBoxSettings = value; } + //} + + #region Static properties + + /// + /// Gets whether or not the left mouse button is down at this very instant + /// + public static bool IsLeftMouseDown { + get { return (Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left; } + } + + /// + /// Gets whether the program running on Vista or later? + /// + public static bool IsVistaOrLater { + get { + if (!ObjectListView.sIsVistaOrLater.HasValue) + ObjectListView.sIsVistaOrLater = Environment.OSVersion.Version.Major >= 6; + return ObjectListView.sIsVistaOrLater.Value; + } + } + private static bool? sIsVistaOrLater; + + /// + /// Gets whether the program running on Win7 or later? + /// + public static bool IsWin7OrLater { + get { + if (!ObjectListView.sIsWin7OrLater.HasValue) { + // For some reason, Win7 is v6.1, not v7.0 + Version version = Environment.OSVersion.Version; + ObjectListView.sIsWin7OrLater = version.Major > 6 || (version.Major == 6 && version.Minor > 0); + } + return ObjectListView.sIsWin7OrLater.Value; + } + } + private static bool? sIsWin7OrLater; + + /// + /// Gets or sets how what smoothing mode will be applied to graphic operations. + /// + public static System.Drawing.Drawing2D.SmoothingMode SmoothingMode { + get { return ObjectListView.sSmoothingMode; } + set { ObjectListView.sSmoothingMode = value; } + } + private static System.Drawing.Drawing2D.SmoothingMode sSmoothingMode = + System.Drawing.Drawing2D.SmoothingMode.HighQuality; + + /// + /// Gets or sets how should text be rendered. + /// + public static System.Drawing.Text.TextRenderingHint TextRenderingHint { + get { return ObjectListView.sTextRendereringHint; } + set { ObjectListView.sTextRendereringHint = value; } + } + private static System.Drawing.Text.TextRenderingHint sTextRendereringHint = + System.Drawing.Text.TextRenderingHint.SystemDefault; + + /// + /// Gets or sets the string that will be used to title groups when the group key is null. + /// Exposed so it can be localized. + /// + public static string GroupTitleDefault { + get { return ObjectListView.sGroupTitleDefault; } + set { ObjectListView.sGroupTitleDefault = value ?? "{null}"; } + } + private static string sGroupTitleDefault = "{null}"; + + /// + /// Convert the given enumerable into an ArrayList as efficiently as possible + /// + /// The source collection + /// If true, this method will always create a new + /// collection. + /// An ArrayList with the same contents as the given collection. + /// + /// When we move to .NET 3.5, we can use LINQ and not need this method. + /// + public static ArrayList EnumerableToArray(IEnumerable collection, bool alwaysCreate) { + if (collection == null) + return new ArrayList(); + + if (!alwaysCreate) { + ArrayList array = collection as ArrayList; + if (array != null) + return array; + + IList iList = collection as IList; + if (iList != null) + return ArrayList.Adapter(iList); + } + + ICollection iCollection = collection as ICollection; + if (iCollection != null) + return new ArrayList(iCollection); + + ArrayList newObjects = new ArrayList(); + foreach (object x in collection) + newObjects.Add(x); + return newObjects; + } + + + /// + /// Return the count of items in the given enumerable + /// + /// + /// + /// When we move to .NET 3.5, we can use LINQ and not need this method. + public static int EnumerableCount(IEnumerable collection) { + if (collection == null) + return 0; + + ICollection iCollection = collection as ICollection; + if (iCollection != null) + return iCollection.Count; + + int i = 0; +// ReSharper disable once UnusedVariable + foreach (object x in collection) + i++; + return i; + } + + /// + /// Return whether or not the given enumerable is empty. A string is regarded as + /// an empty collection. + /// + /// + /// True if the given collection is null or empty + /// + /// When we move to .NET 3.5, we can use LINQ and not need this method. + /// + public static bool IsEnumerableEmpty(IEnumerable collection) { + return collection == null || (collection is string) || !collection.GetEnumerator().MoveNext(); + } + + /// + /// Gets or sets whether all ObjectListViews will silently ignore missing aspect errors. + /// + /// + /// + /// By default, if an ObjectListView is asked to display an aspect + /// (i.e. a field/property/method) + /// that does not exist from a model, it displays an error message in that cell, since that + /// condition is normally a programming error. There are some use cases where + /// this is not an error -- in those cases, set this to true and ObjectListView will + /// simply display an empty cell. + /// + /// Be warned: if you set this to true, it can be very difficult to track down + /// typing mistakes or name changes in AspectNames. + /// + public static bool IgnoreMissingAspects { + get { return Munger.IgnoreMissingAspects; } + set { Munger.IgnoreMissingAspects = value; } + } + + /// + /// Gets or sets whether the control will draw a rectangle in each cell showing the cell padding. + /// + /// + /// + /// This can help with debugging display problems from cell padding. + /// + /// As with all cell padding, this setting only takes effect when the control is owner drawn. + /// + public static bool ShowCellPaddingBounds { + get { return sShowCellPaddingBounds; } + set { sShowCellPaddingBounds = value; } + } + private static bool sShowCellPaddingBounds; + + /// + /// Gets the style that will be used by default to format disabled rows + /// + public static SimpleItemStyle DefaultDisabledItemStyle { + get { + if (sDefaultDisabledItemStyle == null) { + sDefaultDisabledItemStyle = new SimpleItemStyle(); + sDefaultDisabledItemStyle.ForeColor = Color.DarkGray; + } + return sDefaultDisabledItemStyle; + } + } + private static SimpleItemStyle sDefaultDisabledItemStyle; + + /// + /// Gets the style that will be used by default to format hot rows + /// + public static HotItemStyle DefaultHotItemStyle { + get { + if (sDefaultHotItemStyle == null) { + sDefaultHotItemStyle = new HotItemStyle(); + sDefaultHotItemStyle.BackColor = Color.FromArgb(224, 235, 253); + } + return sDefaultHotItemStyle; + } + } + private static HotItemStyle sDefaultHotItemStyle; + + #endregion + + #region Public properties + + /// + /// Gets or sets an model filter that is combined with any column filtering that the end-user specifies. + /// + /// This is different from the ModelFilter property, since setting that will replace + /// any column filtering, whereas setting this will combine this filter with the column filtering + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IModelFilter AdditionalFilter + { + get { return this.additionalFilter; } + set + { + if (this.additionalFilter == value) + return; + this.additionalFilter = value; + this.UpdateColumnFiltering(); + } + } + private IModelFilter additionalFilter; + + /// + /// Get or set all the columns that this control knows about. + /// Only those columns where IsVisible is true will be seen by the user. + /// + /// + /// + /// If you want to add new columns programmatically, add them to + /// AllColumns and then call RebuildColumns(). Normally, you do not have to + /// deal with this property directly. Just use the IDE. + /// + /// If you do add or remove columns from the AllColumns collection, + /// you have to call RebuildColumns() to make those changes take effect. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public virtual List AllColumns { + get { return this.allColumns; } + set { this.allColumns = value ?? new List(); } + } + private List allColumns = new List(); + + /// + /// Gets or sets whether or not ObjectListView will allow cell editors to response to mouse wheel events. Default is true. + /// If this is true, cell editors that respond to mouse wheel events (e.g. numeric edit, DateTimeEditor, combo boxes) will operate + /// as expected. + /// If this is false, a mouse wheel event is interpreted as a request to scroll the control vertically. This will automatically + /// finish any cell edit operation that was in flight. This was the default behaviour prior to v2.9. + /// + [Category("ObjectListView"), + Description("Should ObjectListView allow cell editors to response to mouse wheel events (default: true)"), + DefaultValue(true)] + public virtual bool AllowCellEditorsToProcessMouseWheel + { + get { return allowCellEditorsToProcessMouseWheel; } + set { allowCellEditorsToProcessMouseWheel = value; } + } + private bool allowCellEditorsToProcessMouseWheel = true; + + /// + /// Gets or sets the background color of every second row + /// + [Category("ObjectListView"), + Description("If using alternate colors, what color should the background of alternate rows be?"), + DefaultValue(typeof(Color), "")] + public Color AlternateRowBackColor { + get { return alternateRowBackColor; } + set { alternateRowBackColor = value; } + } + private Color alternateRowBackColor = Color.Empty; + + /// + /// Gets the alternate row background color that has been set, or the default color + /// + [Browsable(false)] + public virtual Color AlternateRowBackColorOrDefault { + get { + return this.alternateRowBackColor == Color.Empty ? Color.LemonChiffon : this.alternateRowBackColor; + } + } + + /// + /// This property forces the ObjectListView to always group items by the given column. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual OLVColumn AlwaysGroupByColumn { + get { return alwaysGroupByColumn; } + set { alwaysGroupByColumn = value; } + } + private OLVColumn alwaysGroupByColumn; + + /// + /// If AlwaysGroupByColumn is not null, this property will be used to decide how + /// those groups are sorted. If this property has the value SortOrder.None, then + /// the sort order will toggle according to the users last header click. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual SortOrder AlwaysGroupBySortOrder { + get { return alwaysGroupBySortOrder; } + set { alwaysGroupBySortOrder = value; } + } + private SortOrder alwaysGroupBySortOrder = SortOrder.None; + + /// + /// Give access to the image list that is actually being used by the control + /// + /// + /// Normally, it is preferable to use SmallImageList. Only use this property + /// if you know exactly what you are doing. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual ImageList BaseSmallImageList { + get { return base.SmallImageList; } + set { base.SmallImageList = value; } + } + + /// + /// How does the user indicate that they want to edit a cell? + /// None means that the listview cannot be edited. + /// + /// Columns can also be marked as editable. + [Category("ObjectListView"), + Description("How does the user indicate that they want to edit a cell?"), + DefaultValue(CellEditActivateMode.None)] + public virtual CellEditActivateMode CellEditActivation { + get { return cellEditActivation; } + set { + cellEditActivation = value; + if (this.Created) + this.Invalidate(); + } + } + private CellEditActivateMode cellEditActivation = CellEditActivateMode.None; + + /// + /// When a cell is edited, should the whole cell be used (minus any space used by checkbox or image)? + /// Defaults to true. + /// + /// + /// This is always treated as true when the control is NOT owner drawn. + /// + /// When this is false and the control is owner drawn, + /// ObjectListView will try to calculate the width of the cell's + /// actual contents, and then size the editing control to be just the right width. If this is true, + /// the whole width of the cell will be used, regardless of the cell's contents. + /// + /// Each column can have a different value for property. This value from the control is only + /// used when a column is not specified one way or another. + /// Regardless of this setting, developers can specify the exact size of the editing control + /// by listening for the CellEditStarting event. + /// + [Category("ObjectListView"), + Description("When a cell is edited, should the whole cell be used?"), + DefaultValue(true)] + public virtual bool CellEditUseWholeCell { + get { return cellEditUseWholeCell; } + set { cellEditUseWholeCell = value; } + } + private bool cellEditUseWholeCell = true; + + /// + /// Gets or sets the engine that will handle key presses during a cell edit operation. + /// Settings this to null will reset it to default value. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public CellEditKeyEngine CellEditKeyEngine { + get { return this.cellEditKeyEngine ?? (this.cellEditKeyEngine = new CellEditKeyEngine()); } + set { this.cellEditKeyEngine = value; } + } + private CellEditKeyEngine cellEditKeyEngine; + + /// + /// Gets the control that is currently being used for editing a cell. + /// + /// This will obviously be null if no cell is being edited. + [Browsable(false)] + public Control CellEditor { + get { + return this.cellEditor; + } + } + + /// + /// Gets or sets the behaviour of the Tab key when editing a cell on the left or right + /// edge of the control. If this is false (the default), pressing Tab will wrap to the other side + /// of the same row. If this is true, pressing Tab when editing the right most cell will advance + /// to the next row + /// and Shift-Tab when editing the left-most cell will change to the previous row. + /// + [Category("ObjectListView"), + Description("Should Tab/Shift-Tab change rows while cell editing?"), + DefaultValue(false)] + public virtual bool CellEditTabChangesRows { + get { return cellEditTabChangesRows; } + set { + cellEditTabChangesRows = value; + if (cellEditTabChangesRows) { + this.CellEditKeyEngine.SetKeyBehaviour(Keys.Tab, CellEditCharacterBehaviour.ChangeColumnRight, CellEditAtEdgeBehaviour.ChangeRow); + this.CellEditKeyEngine.SetKeyBehaviour(Keys.Tab|Keys.Shift, CellEditCharacterBehaviour.ChangeColumnLeft, CellEditAtEdgeBehaviour.ChangeRow); + } else { + this.CellEditKeyEngine.SetKeyBehaviour(Keys.Tab, CellEditCharacterBehaviour.ChangeColumnRight, CellEditAtEdgeBehaviour.Wrap); + this.CellEditKeyEngine.SetKeyBehaviour(Keys.Tab | Keys.Shift, CellEditCharacterBehaviour.ChangeColumnLeft, CellEditAtEdgeBehaviour.Wrap); + } + } + } + private bool cellEditTabChangesRows; + + /// + /// Gets or sets the behaviour of the Enter keys while editing a cell. + /// If this is false (the default), pressing Enter will simply finish the editing operation. + /// If this is true, Enter will finish the edit operation and start a new edit operation + /// on the cell below the current cell, wrapping to the top of the next row when at the bottom cell. + /// + [Category("ObjectListView"), + Description("Should Enter change rows while cell editing?"), + DefaultValue(false)] + public virtual bool CellEditEnterChangesRows { + get { return cellEditEnterChangesRows; } + set { + cellEditEnterChangesRows = value; + if (cellEditEnterChangesRows) { + this.CellEditKeyEngine.SetKeyBehaviour(Keys.Enter, CellEditCharacterBehaviour.ChangeRowDown, CellEditAtEdgeBehaviour.ChangeColumn); + this.CellEditKeyEngine.SetKeyBehaviour(Keys.Enter | Keys.Shift, CellEditCharacterBehaviour.ChangeRowUp, CellEditAtEdgeBehaviour.ChangeColumn); + } else { + this.CellEditKeyEngine.SetKeyBehaviour(Keys.Enter, CellEditCharacterBehaviour.EndEdit, CellEditAtEdgeBehaviour.EndEdit); + this.CellEditKeyEngine.SetKeyBehaviour(Keys.Enter | Keys.Shift, CellEditCharacterBehaviour.EndEdit, CellEditAtEdgeBehaviour.EndEdit); + } + } + } + private bool cellEditEnterChangesRows; + + /// + /// Gets the tool tip control that shows tips for the cells + /// + [Browsable(false)] + public ToolTipControl CellToolTip { + get { + if (this.cellToolTip == null) { + this.CreateCellToolTip(); + } + return this.cellToolTip; + } + } + private ToolTipControl cellToolTip; + + /// + /// Gets or sets how many pixels will be left blank around each cell of this item. + /// Cell contents are aligned after padding has been taken into account. + /// + /// + /// Each value of the given rectangle will be treated as an inset from + /// the corresponding side. The width of the rectangle is the padding for the + /// right cell edge. The height of the rectangle is the padding for the bottom + /// cell edge. + /// + /// + /// So, this.olv1.CellPadding = new Rectangle(1, 2, 3, 4); will leave one pixel + /// of space to the left of the cell, 2 pixels at the top, 3 pixels of space + /// on the right edge, and 4 pixels of space at the bottom of each cell. + /// + /// + /// This setting only takes effect when the control is owner drawn. + /// + /// This setting only affects the contents of the cell. The background is + /// not affected. + /// If you set this to a foolish value, your control will appear to be empty. + /// + [Category("ObjectListView"), + Description("How much padding will be applied to each cell in this control?"), + DefaultValue(null)] + public Rectangle? CellPadding { + get { return this.cellPadding; } + set { this.cellPadding = value; } + } + private Rectangle? cellPadding; + + /// + /// Gets or sets how cells will be vertically aligned by default. + /// + /// This setting only takes effect when the control is owner drawn. It will only be noticeable + /// when RowHeight has been set such that there is some vertical space in each row. + [Category("ObjectListView"), + Description("How will cell values be vertically aligned?"), + DefaultValue(StringAlignment.Center)] + public virtual StringAlignment CellVerticalAlignment { + get { return this.cellVerticalAlignment; } + set { this.cellVerticalAlignment = value; } + } + private StringAlignment cellVerticalAlignment = StringAlignment.Center; + + /// + /// Should this list show checkboxes? + /// + public new bool CheckBoxes { + get { return base.CheckBoxes; } + set { + // Due to code in the base ListView class, turning off CheckBoxes on a virtual + // list always throws an InvalidOperationException. We have to do some major hacking + // to get around that + if (this.VirtualMode) { + // Leave virtual mode + this.StateImageList = null; + this.VirtualListSize = 0; + this.VirtualMode = false; + + // Change the CheckBox setting while not in virtual mode + base.CheckBoxes = value; + + // Reinstate virtual mode + this.VirtualMode = true; + + // Re-enact the bits that we lost by switching to virtual mode + this.ShowGroups = this.ShowGroups; + this.BuildList(true); + } else { + base.CheckBoxes = value; + // Initialize the state image list so we can display indeterminate values. + this.InitializeStateImageList(); + } + } + } + + /// + /// Return the model object of the row that is checked or null if no row is checked + /// or more than one row is checked + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual Object CheckedObject { + get { + IList checkedObjects = this.CheckedObjects; + return checkedObjects.Count == 1 ? checkedObjects[0] : null; + } + set { + this.CheckedObjects = new ArrayList(new Object[] { value }); + } + } + + /// + /// Get or set the collection of model objects that are checked. + /// When setting this property, any row whose model object isn't + /// in the given collection will be unchecked. Setting to null is + /// equivalent to unchecking all. + /// + /// + /// + /// This property returns a simple collection. Changes made to the returned + /// collection do NOT affect the list. This is different to the behaviour of + /// CheckedIndicies collection. + /// + /// + /// .NET's CheckedItems property is not helpful. It is just a short-hand for + /// iterating through the list looking for items that are checked. + /// + /// + /// The performance of the get method is O(n), where n is the number of items + /// in the control. The performance of the set method is + /// O(n + m) where m is the number of objects being checked. Be careful on long lists. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IList CheckedObjects { + get { + ArrayList list = new ArrayList(); + if (this.CheckBoxes) { + for (int i = 0; i < this.GetItemCount(); i++) { + OLVListItem olvi = this.GetItem(i); + if (olvi.CheckState == CheckState.Checked) + list.Add(olvi.RowObject); + } + } + return list; + } + set { + if (!this.CheckBoxes) + return; + + Stopwatch sw = Stopwatch.StartNew(); + + // Set up an efficient way of testing for the presence of a particular model + Hashtable table = new Hashtable(this.GetItemCount()); + if (value != null) { + foreach (object x in value) + table[x] = true; + } + + this.BeginUpdate(); + foreach (Object x in this.Objects) { + this.SetObjectCheckedness(x, table.ContainsKey(x) ? CheckState.Checked : CheckState.Unchecked); + } + this.EndUpdate(); + + // Debug.WriteLine(String.Format("PERF - Setting CheckedObjects on {2} objects took {0}ms / {1} ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks, this.GetItemCount())); + + } + } + + /// + /// Gets or sets the checked objects from an enumerable. + /// + /// + /// Useful for checking all objects in the list. + /// + /// + /// this.olv1.CheckedObjectsEnumerable = this.olv1.Objects; + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IEnumerable CheckedObjectsEnumerable { + get { + return this.CheckedObjects; + } + set { + this.CheckedObjects = ObjectListView.EnumerableToArray(value, true); + } + } + + /// + /// Gets Columns for this list. We hide the original so we can associate + /// a specialised editor with it. + /// + [Editor("Kermalis.VGMusicStudio.WinForms.ObjectListView.Design.OLVColumnCollectionEditor", "System.Drawing.Design.UITypeEditor")] + public new ListView.ColumnHeaderCollection Columns { + get { + return base.Columns; + } + } + + /// + /// Get/set the list of columns that should be used when the list switches to tile view. + /// + [Browsable(false), + Obsolete("Use GetFilteredColumns() and OLVColumn.IsTileViewColumn instead"), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public List ColumnsForTileView { + get { return this.GetFilteredColumns(View.Tile); } + } + + /// + /// Return the visible columns in the order they are displayed to the user + /// + [Browsable(false)] + public virtual List ColumnsInDisplayOrder { + get { + OLVColumn[] columnsInDisplayOrder = new OLVColumn[this.Columns.Count]; + foreach (OLVColumn col in this.Columns) { + columnsInDisplayOrder[col.DisplayIndex] = col; + } + return new List(columnsInDisplayOrder); + } + } + + + /// + /// Get the area of the control that shows the list, minus any header control + /// + [Browsable(false)] + public Rectangle ContentRectangle { + get { + Rectangle r = this.ClientRectangle; + + // If the listview has a header control, remove the header from the control area + if ((this.View == View.Details || this.ShowHeaderInAllViews) && this.HeaderControl != null) { + Rectangle hdrBounds = new Rectangle(); + NativeMethods.GetClientRect(this.HeaderControl.Handle, ref hdrBounds); + r.Y = hdrBounds.Height; + r.Height = r.Height - hdrBounds.Height; + } + + return r; + } + } + + /// + /// Gets or sets if the selected rows should be copied to the clipboard when the user presses Ctrl-C + /// + [Category("ObjectListView"), + Description("Should the control copy the selection to the clipboard when the user presses Ctrl-C?"), + DefaultValue(true)] + public virtual bool CopySelectionOnControlC { + get { return copySelectionOnControlC; } + set { copySelectionOnControlC = value; } + } + private bool copySelectionOnControlC = true; + + + /// + /// Gets or sets whether the Control-C copy to clipboard functionality should use + /// the installed DragSource to create the data object that is placed onto the clipboard. + /// + /// This is normally what is desired, unless a custom DragSource is installed + /// that does some very specialized drag-drop behaviour. + [Category("ObjectListView"), + Description("Should the Ctrl-C copy process use the DragSource to create the Clipboard data object?"), + DefaultValue(true)] + public bool CopySelectionOnControlCUsesDragSource { + get { return this.copySelectionOnControlCUsesDragSource; } + set { this.copySelectionOnControlCUsesDragSource = value; } + } + private bool copySelectionOnControlCUsesDragSource = true; + + /// + /// Gets the list of decorations that will be drawn the ListView + /// + /// + /// + /// Do not modify the contents of this list directly. Use the AddDecoration() and RemoveDecoration() methods. + /// + /// + /// A decoration scrolls with the list contents. An overlay is fixed in place. + /// + /// + [Browsable(false)] + protected IList Decorations { + get { return this.decorations; } + } + private readonly List decorations = new List(); + + /// + /// When owner drawing, this renderer will draw columns that do not have specific renderer + /// given to them + /// + /// If you try to set this to null, it will revert to a HighlightTextRenderer + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IRenderer DefaultRenderer { + get { return this.defaultRenderer; } + set { this.defaultRenderer = value ?? new HighlightTextRenderer(); } + } + private IRenderer defaultRenderer = new HighlightTextRenderer(); + + /// + /// Get the renderer to be used to draw the given cell. + /// + /// The row model for the row + /// The column to be drawn + /// The renderer used for drawing a cell. Must not return null. + public IRenderer GetCellRenderer(object model, OLVColumn column) { + IRenderer renderer = this.CellRendererGetter == null ? null : this.CellRendererGetter(model, column); + return renderer ?? column.Renderer ?? this.DefaultRenderer; + } + + /// + /// Gets or sets the style that will be applied to disabled items. + /// + /// If this is not set explicitly, will be used. + [Category("ObjectListView"), + Description("The style that will be applied to disabled items"), + DefaultValue(null)] + public SimpleItemStyle DisabledItemStyle + { + get { return disabledItemStyle; } + set { disabledItemStyle = value; } + } + private SimpleItemStyle disabledItemStyle; + + /// + /// Gets or sets the list of model objects that are disabled. + /// Disabled objects cannot be selected or activated. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IEnumerable DisabledObjects + { + get + { + return disabledObjects.Keys; + } + set + { + this.disabledObjects.Clear(); + DisableObjects(value); + } + } + private readonly Hashtable disabledObjects = new Hashtable(); + + /// + /// Is this given model object disabled? + /// + /// + /// + public bool IsDisabled(object model) + { + return model != null && this.disabledObjects.ContainsKey(model); + } + + /// + /// Disable the given model object. + /// Disabled objects cannot be selected or activated. + /// + /// Must not be null + public void DisableObject(object model) { + ArrayList list = new ArrayList(); + list.Add(model); + this.DisableObjects(list); + } + + /// + /// Disable all the given model objects + /// + /// + public void DisableObjects(IEnumerable models) + { + if (models == null) + return; + ArrayList list = ObjectListView.EnumerableToArray(models, false); + foreach (object model in list) + { + if (model == null) + continue; + + this.disabledObjects[model] = true; + int modelIndex = this.IndexOf(model); + if (modelIndex >= 0) + NativeMethods.DeselectOneItem(this, modelIndex); + } + this.RefreshObjects(list); + } + + /// + /// Enable the given model object, so it can be selected and activated again. + /// + /// Must not be null + public void EnableObject(object model) + { + this.disabledObjects.Remove(model); + this.RefreshObject(model); + } + + /// + /// Enable all the given model objects + /// + /// + public void EnableObjects(IEnumerable models) + { + if (models == null) + return; + ArrayList list = ObjectListView.EnumerableToArray(models, false); + foreach (object model in list) + { + if (model != null) + this.disabledObjects.Remove(model); + } + this.RefreshObjects(list); + } + + /// + /// Forget all disabled objects. This does not trigger a redraw or rebuild + /// + protected void ClearDisabledObjects() + { + this.disabledObjects.Clear(); + } + + /// + /// Gets or sets the object that controls how drags start from this control + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IDragSource DragSource { + get { return this.dragSource; } + set { this.dragSource = value; } + } + private IDragSource dragSource; + + /// + /// Gets or sets the object that controls how drops are accepted and processed + /// by this ListView. + /// + /// + /// + /// If the given sink is an instance of SimpleDropSink, then events from the drop sink + /// will be automatically forwarded to the ObjectListView (which means that handlers + /// for those event can be configured within the IDE). + /// + /// If this is set to null, the control will not accept drops. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IDropSink DropSink { + get { return this.dropSink; } + set { + if (this.dropSink == value) + return; + + // Stop listening for events on the old sink + SimpleDropSink oldSink = this.dropSink as SimpleDropSink; + if (oldSink != null) { + oldSink.CanDrop -= new EventHandler(this.DropSinkCanDrop); + oldSink.Dropped -= new EventHandler(this.DropSinkDropped); + oldSink.ModelCanDrop -= new EventHandler(this.DropSinkModelCanDrop); + oldSink.ModelDropped -= new EventHandler(this.DropSinkModelDropped); + } + + this.dropSink = value; + this.AllowDrop = (value != null); + if (this.dropSink != null) + this.dropSink.ListView = this; + + // Start listening for events on the new sink + SimpleDropSink newSink = value as SimpleDropSink; + if (newSink != null) { + newSink.CanDrop += new EventHandler(this.DropSinkCanDrop); + newSink.Dropped += new EventHandler(this.DropSinkDropped); + newSink.ModelCanDrop += new EventHandler(this.DropSinkModelCanDrop); + newSink.ModelDropped += new EventHandler(this.DropSinkModelDropped); + } + } + } + private IDropSink dropSink; + + // Forward events from the drop sink to the control itself + void DropSinkCanDrop(object sender, OlvDropEventArgs e) { this.OnCanDrop(e); } + void DropSinkDropped(object sender, OlvDropEventArgs e) { this.OnDropped(e); } + void DropSinkModelCanDrop(object sender, ModelDropEventArgs e) { this.OnModelCanDrop(e); } + void DropSinkModelDropped(object sender, ModelDropEventArgs e) { this.OnModelDropped(e); } + + /// + /// This registry decides what control should be used to edit what cells, based + /// on the type of the value in the cell. + /// + /// + /// All instances of ObjectListView share the same editor registry. +// ReSharper disable FieldCanBeMadeReadOnly.Global + public static EditorRegistry EditorRegistry = new EditorRegistry(); +// ReSharper restore FieldCanBeMadeReadOnly.Global + + /// + /// Gets or sets the text that should be shown when there are no items in this list view. + /// + /// If the EmptyListMsgOverlay has been changed to something other than a TextOverlay, + /// this property does nothing + [Category("ObjectListView"), + Description("When the list has no items, show this message in the control"), + DefaultValue(null), + Localizable(true)] + public virtual String EmptyListMsg { + get { + TextOverlay overlay = this.EmptyListMsgOverlay as TextOverlay; + return overlay == null ? null : overlay.Text; + } + set { + TextOverlay overlay = this.EmptyListMsgOverlay as TextOverlay; + if (overlay != null) { + overlay.Text = value; + this.Invalidate(); + } + } + } + + /// + /// Gets or sets the font in which the List Empty message should be drawn + /// + /// If the EmptyListMsgOverlay has been changed to something other than a TextOverlay, + /// this property does nothing + [Category("ObjectListView"), + Description("What font should the 'list empty' message be drawn in?"), + DefaultValue(null)] + public virtual Font EmptyListMsgFont { + get { + TextOverlay overlay = this.EmptyListMsgOverlay as TextOverlay; + return overlay == null ? null : overlay.Font; + } + set { + TextOverlay overlay = this.EmptyListMsgOverlay as TextOverlay; + if (overlay != null) + overlay.Font = value; + } + } + + /// + /// Return the font for the 'list empty' message or a reasonable default + /// + [Browsable(false)] + public virtual Font EmptyListMsgFontOrDefault { + get { + return this.EmptyListMsgFont ?? new Font("Tahoma", 14); + } + } + + /// + /// Gets or sets the overlay responsible for drawing the List Empty msg. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IOverlay EmptyListMsgOverlay { + get { return this.emptyListMsgOverlay; } + set { + if (this.emptyListMsgOverlay != value) { + this.emptyListMsgOverlay = value; + this.Invalidate(); + } + } + } + private IOverlay emptyListMsgOverlay; + + /// + /// Gets the collection of objects that survive any filtering that may be in place. + /// + /// + /// + /// This collection is the result of filtering the current list of objects. + /// It is not a snapshot of the filtered list that was last used to build the control. + /// + /// + /// Normal warnings apply when using this with virtual lists. It will work, but it + /// may take a while. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IEnumerable FilteredObjects { + get { + if (this.UseFiltering) + return this.FilterObjects(this.Objects, this.ModelFilter, this.ListFilter); + + return this.Objects; + } + } + + /// + /// Gets or sets the strategy object that will be used to build the Filter menu + /// + /// If this is null, no filter menu will be built. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public FilterMenuBuilder FilterMenuBuildStrategy { + get { return filterMenuBuilder; } + set { filterMenuBuilder = value; } + } + private FilterMenuBuilder filterMenuBuilder = new FilterMenuBuilder(); + + /// + /// Gets or sets the row that has keyboard focus + /// + /// + /// + /// Setting an object to be focused does *not* select it. If you want to select and focus a row, + /// use . + /// + /// + /// This property is not generally used and is only useful in specialized situations. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual Object FocusedObject { + get { return this.FocusedItem == null ? null : ((OLVListItem)this.FocusedItem).RowObject; } + set { + OLVListItem item = this.ModelToItem(value); + if (item != null) + item.Focused = true; + } + } + + /// + /// Hide the Groups collection so it's not visible in the Properties grid. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new ListViewGroupCollection Groups { + get { return base.Groups; } + } + + /// + /// Gets or sets the image list from which group header will take their images + /// + /// If this is not set, then group headers will not show any images. + [Category("ObjectListView"), + Description("The image list from which group header will take their images"), + DefaultValue(null)] + public ImageList GroupImageList { + get { return this.groupImageList; } + set { + this.groupImageList = value; + if (this.Created) { + NativeMethods.SetGroupImageList(this, value); + } + } + } + private ImageList groupImageList; + + /// + /// Gets how the group label should be formatted when a group is empty or + /// contains more than one item + /// + /// + /// The given format string must have two placeholders: + /// + /// {0} - the original group title + /// {1} - the number of items in the group + /// + /// + /// "{0} [{1} items]" + [Category("ObjectListView"), + Description("The format to use when suffixing item counts to group titles"), + DefaultValue(null), + Localizable(true)] + public virtual string GroupWithItemCountFormat { + get { return groupWithItemCountFormat; } + set { groupWithItemCountFormat = value; } + } + private string groupWithItemCountFormat; + + /// + /// Return this.GroupWithItemCountFormat or a reasonable default + /// + [Browsable(false)] + public virtual string GroupWithItemCountFormatOrDefault { + get { + return String.IsNullOrEmpty(this.GroupWithItemCountFormat) ? "{0} [{1} items]" : this.GroupWithItemCountFormat; + } + } + + /// + /// Gets how the group label should be formatted when a group contains only a single item + /// + /// + /// The given format string must have two placeholders: + /// + /// {0} - the original group title + /// {1} - the number of items in the group (always 1) + /// + /// + /// "{0} [{1} item]" + [Category("ObjectListView"), + Description("The format to use when suffixing item counts to group titles"), + DefaultValue(null), + Localizable(true)] + public virtual string GroupWithItemCountSingularFormat { + get { return groupWithItemCountSingularFormat; } + set { groupWithItemCountSingularFormat = value; } + } + private string groupWithItemCountSingularFormat; + + /// + /// Gets GroupWithItemCountSingularFormat or a reasonable default + /// + [Browsable(false)] + public virtual string GroupWithItemCountSingularFormatOrDefault { + get { + return String.IsNullOrEmpty(this.GroupWithItemCountSingularFormat) ? "{0} [{1} item]" : this.GroupWithItemCountSingularFormat; + } + } + + /// + /// Gets or sets whether or not the groups in this ObjectListView should be collapsible. + /// + /// + /// This feature only works under Vista and later. + /// + [Browsable(true), + Category("ObjectListView"), + Description("Should the groups in this control be collapsible (Vista and later only)."), + DefaultValue(true)] + public bool HasCollapsibleGroups { + get { return hasCollapsibleGroups; } + set { hasCollapsibleGroups = value; } + } + private bool hasCollapsibleGroups = true; + + /// + /// Does this listview have a message that should be drawn when the list is empty? + /// + [Browsable(false)] + public virtual bool HasEmptyListMsg { + get { return !String.IsNullOrEmpty(this.EmptyListMsg); } + } + + /// + /// Get whether there are any overlays to be drawn + /// + [Browsable(false)] + public bool HasOverlays { + get { + return (this.Overlays.Count > 2 || + this.imageOverlay.Image != null || + !String.IsNullOrEmpty(this.textOverlay.Text)); + } + } + + /// + /// Gets the header control for the ListView + /// + [Browsable(false)] + public HeaderControl HeaderControl { + get { return this.headerControl ?? (this.headerControl = new HeaderControl(this)); } + } + private HeaderControl headerControl; + + /// + /// Gets or sets the font in which the text of the column headers will be drawn + /// + /// Individual columns can override this through their HeaderFormatStyle property. + [DefaultValue(null)] + [Browsable(false)] + [Obsolete("Use a HeaderFormatStyle instead", false)] + public Font HeaderFont { + get { return this.HeaderFormatStyle == null ? null : this.HeaderFormatStyle.Normal.Font; } + set { + if (value == null && this.HeaderFormatStyle == null) + return; + + if (this.HeaderFormatStyle == null) + this.HeaderFormatStyle = new HeaderFormatStyle(); + + this.HeaderFormatStyle.SetFont(value); + } + } + + /// + /// Gets or sets the style that will be used to draw the column headers of the listview + /// + /// + /// + /// This is only used when HeaderUsesThemes is false. + /// + /// + /// Individual columns can override this through their HeaderFormatStyle property. + /// + /// + [Category("ObjectListView"), + Description("What style will be used to draw the control's header"), + DefaultValue(null)] + public HeaderFormatStyle HeaderFormatStyle { + get { return this.headerFormatStyle; } + set { this.headerFormatStyle = value; } + } + private HeaderFormatStyle headerFormatStyle; + + /// + /// Gets or sets the maximum height of the header. -1 means no maximum. + /// + [Category("ObjectListView"), + Description("What is the maximum height of the header? -1 means no maximum"), + DefaultValue(-1)] + public int HeaderMaximumHeight + { + get { return headerMaximumHeight; } + set { headerMaximumHeight = value; } + } + private int headerMaximumHeight = -1; + + /// + /// Gets or sets the minimum height of the header. -1 means no minimum. + /// + [Category("ObjectListView"), + Description("What is the minimum height of the header? -1 means no minimum"), + DefaultValue(-1)] + public int HeaderMinimumHeight + { + get { return headerMinimumHeight; } + set { headerMinimumHeight = value; } + } + private int headerMinimumHeight = -1; + + /// + /// Gets or sets whether the header will be drawn strictly according to the OS's theme. + /// + /// + /// + /// If this is set to true, the header will be rendered completely by the system, without + /// any of ObjectListViews fancy processing -- no images in header, no filter indicators, + /// no word wrapping, no header styling, no checkboxes. + /// + /// If this is set to false, ObjectListView will render the header as it thinks best. + /// If no special features are required, then ObjectListView will delegate rendering to the OS. + /// Otherwise, ObjectListView will draw the header according to the configuration settings. + /// + /// + /// The effect of not being themed will be different from OS to OS. At + /// very least, the sort indicator will not be standard. + /// + /// + [Category("ObjectListView"), + Description("Will the column headers be drawn strictly according to OS theme?"), + DefaultValue(false)] + public bool HeaderUsesThemes { + get { return this.headerUsesThemes; } + set { this.headerUsesThemes = value; } + } + private bool headerUsesThemes; + + /// + /// Gets or sets the whether the text in the header will be word wrapped. + /// + /// + /// Line breaks will be applied between words. Words that are too long + /// will still be ellipsed. + /// + /// As with all settings that make the header look different, HeaderUsesThemes must be set to false, otherwise + /// the OS will be responsible for drawing the header, and it does not allow word wrapped text. + /// + /// + [Category("ObjectListView"), + Description("Will the text of the column headers be word wrapped?"), + DefaultValue(false)] + public bool HeaderWordWrap { + get { return this.headerWordWrap; } + set { + this.headerWordWrap = value; + if (this.headerControl != null) + this.headerControl.WordWrap = value; + } + } + private bool headerWordWrap; + + /// + /// Gets the tool tip that shows tips for the column headers + /// + [Browsable(false)] + public ToolTipControl HeaderToolTip { + get { + return this.HeaderControl.ToolTip; + } + } + + /// + /// Gets the index of the row that the mouse is currently over + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual int HotRowIndex { + get { return this.hotRowIndex; } + protected set { this.hotRowIndex = value; } + } + private int hotRowIndex; + + /// + /// Gets the index of the subitem that the mouse is currently over + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual int HotColumnIndex { + get { return this.hotColumnIndex; } + protected set { this.hotColumnIndex = value; } + } + private int hotColumnIndex; + + /// + /// Gets the part of the item/subitem that the mouse is currently over + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual HitTestLocation HotCellHitLocation { + get { return this.hotCellHitLocation; } + protected set { this.hotCellHitLocation = value; } + } + private HitTestLocation hotCellHitLocation; + + /// + /// Gets an extended indication of the part of item/subitem/group that the mouse is currently over + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual HitTestLocationEx HotCellHitLocationEx + { + get { return this.hotCellHitLocationEx; } + protected set { this.hotCellHitLocationEx = value; } + } + private HitTestLocationEx hotCellHitLocationEx; + + /// + /// Gets the group that the mouse is over + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public OLVGroup HotGroup + { + get { return hotGroup; } + internal set { hotGroup = value; } + } + private OLVGroup hotGroup; + + /// + /// The index of the item that is 'hot', i.e. under the cursor. -1 means no item. + /// + [Browsable(false), + Obsolete("Use HotRowIndex instead", false)] + public virtual int HotItemIndex { + get { return this.HotRowIndex; } + } + + /// + /// What sort of formatting should be applied to the row under the cursor? + /// + /// + /// + /// This only takes effect when UseHotItem is true. + /// + /// If the style has an overlay, it must be set + /// *before* assigning it to this property. Adding it afterwards will be ignored. + /// + [Category("ObjectListView"), + Description("How should the row under the cursor be highlighted"), + DefaultValue(null)] + public virtual HotItemStyle HotItemStyle { + get { return this.hotItemStyle; } + set { + if (this.HotItemStyle != null) + this.RemoveOverlay(this.HotItemStyle.Overlay); + this.hotItemStyle = value; + if (this.HotItemStyle != null) + this.AddOverlay(this.HotItemStyle.Overlay); + } + } + private HotItemStyle hotItemStyle; + + /// + /// Gets the installed hot item style or a reasonable default. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual HotItemStyle HotItemStyleOrDefault { + get { return this.HotItemStyle ?? ObjectListView.DefaultHotItemStyle; } + } + + /// + /// What sort of formatting should be applied to hyperlinks? + /// + [Category("ObjectListView"), + Description("How should hyperlinks be drawn"), + DefaultValue(null)] + public virtual HyperlinkStyle HyperlinkStyle { + get { return this.hyperlinkStyle; } + set { this.hyperlinkStyle = value; } + } + private HyperlinkStyle hyperlinkStyle; + + /// + /// What color should be used for the background of selected rows? + /// + [Category("ObjectListView"), + Description("The background of selected rows when the control is owner drawn"), + DefaultValue(typeof(Color), "")] + public virtual Color SelectedBackColor { + get { return this.selectedBackColor; } + set { this.selectedBackColor = value; } + } + private Color selectedBackColor = Color.Empty; + + /// + /// Return the color should be used for the background of selected rows or a reasonable default + /// + [Browsable(false)] + public virtual Color SelectedBackColorOrDefault { + get { + return this.SelectedBackColor.IsEmpty ? SystemColors.Highlight : this.SelectedBackColor; + } + } + + /// + /// What color should be used for the foreground of selected rows? + /// + [Category("ObjectListView"), + Description("The foreground color of selected rows (when the control is owner drawn)"), + DefaultValue(typeof(Color), "")] + public virtual Color SelectedForeColor { + get { return this.selectedForeColor; } + set { this.selectedForeColor = value; } + } + private Color selectedForeColor = Color.Empty; + + /// + /// Return the color should be used for the foreground of selected rows or a reasonable default + /// + [Browsable(false)] + public virtual Color SelectedForeColorOrDefault { + get { + return this.SelectedForeColor.IsEmpty ? SystemColors.HighlightText : this.SelectedForeColor; + } + } + + /// + /// + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Obsolete("Use SelectedBackColor instead")] + public virtual Color HighlightBackgroundColor { get { return this.SelectedBackColor; } set { this.SelectedBackColor = value; } } + + /// + /// + /// + [Obsolete("Use SelectedBackColorOrDefault instead")] + public virtual Color HighlightBackgroundColorOrDefault { get { return this.SelectedBackColorOrDefault; } } + + /// + /// + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Obsolete("Use SelectedForeColor instead")] + public virtual Color HighlightForegroundColor { get { return this.SelectedForeColor; } set { this.SelectedForeColor = value; } } + + /// + /// + /// + [Obsolete("Use SelectedForeColorOrDefault instead")] + public virtual Color HighlightForegroundColorOrDefault { get { return this.SelectedForeColorOrDefault; } } + + /// + /// + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Obsolete("Use UnfocusedSelectedBackColor instead")] + public virtual Color UnfocusedHighlightBackgroundColor { get { return this.UnfocusedSelectedBackColor; } set { this.UnfocusedSelectedBackColor = value; } } + + /// + /// + /// + [Obsolete("Use UnfocusedSelectedBackColorOrDefault instead")] + public virtual Color UnfocusedHighlightBackgroundColorOrDefault { get { return this.UnfocusedSelectedBackColorOrDefault; } } + + /// + /// + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Obsolete("Use UnfocusedSelectedForeColor instead")] + public virtual Color UnfocusedHighlightForegroundColor { get { return this.UnfocusedSelectedForeColor; } set { this.UnfocusedSelectedForeColor = value; } } + + /// + /// + /// + [Obsolete("Use UnfocusedSelectedForeColorOrDefault instead")] + public virtual Color UnfocusedHighlightForegroundColorOrDefault { get { return this.UnfocusedSelectedForeColorOrDefault; } } + + /// + /// Gets or sets whether or not hidden columns should be included in the text representation + /// of rows that are copied or dragged to another application. If this is false (the default), + /// only visible columns will be included. + /// + [Category("ObjectListView"), + Description("When rows are copied or dragged, will data in hidden columns be included in the text? If this is false, only visible columns will be included."), + DefaultValue(false)] + public virtual bool IncludeHiddenColumnsInDataTransfer + { + get { return includeHiddenColumnsInDataTransfer; } + set { includeHiddenColumnsInDataTransfer = value; } + } + private bool includeHiddenColumnsInDataTransfer; + + /// + /// Gets or sets whether or not hidden columns should be included in the text representation + /// of rows that are copied or dragged to another application. If this is false (the default), + /// only visible columns will be included. + /// + [Category("ObjectListView"), + Description("When rows are copied, will column headers be in the text?."), + DefaultValue(false)] + public virtual bool IncludeColumnHeadersInCopy + { + get { return includeColumnHeadersInCopy; } + set { includeColumnHeadersInCopy = value; } + } + private bool includeColumnHeadersInCopy; + + /// + /// Return true if a cell edit operation is currently happening + /// + [Browsable(false)] + public virtual bool IsCellEditing { + get { return this.cellEditor != null; } + } + + /// + /// Return true if the ObjectListView is being used within the development environment. + /// + [Browsable(false)] + public virtual bool IsDesignMode { + get { return this.DesignMode; } + } + + /// + /// Gets whether or not the current list is filtering its contents + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual bool IsFiltering { + get { return this.UseFiltering && (this.ModelFilter != null || this.ListFilter != null); } + } + + /// + /// When the user types into a list, should the values in the current sort column be searched to find a match? + /// If this is false, the primary column will always be used regardless of the sort column. + /// + /// When this is true, the behavior is like that of ITunes. + [Category("ObjectListView"), + Description("When the user types into a list, should the values in the current sort column be searched to find a match?"), + DefaultValue(true)] + public virtual bool IsSearchOnSortColumn { + get { return isSearchOnSortColumn; } + set { isSearchOnSortColumn = value; } + } + private bool isSearchOnSortColumn = true; + + /// + /// Gets or sets if this control will use a SimpleDropSink to receive drops + /// + /// + /// + /// Setting this replaces any previous DropSink. + /// + /// + /// After setting this to true, the SimpleDropSink will still need to be configured + /// to say when it can accept drops and what should happen when something is dropped. + /// The need to do these things makes this property mostly useless :( + /// + /// + [Category("ObjectListView"), + Description("Should this control will use a SimpleDropSink to receive drops."), + DefaultValue(false)] + public virtual bool IsSimpleDropSink { + get { return this.DropSink != null; } + set { + this.DropSink = value ? new SimpleDropSink() : null; + } + } + + /// + /// Gets or sets if this control will use a SimpleDragSource to initiate drags + /// + /// Setting this replaces any previous DragSource + [Category("ObjectListView"), + Description("Should this control use a SimpleDragSource to initiate drags out from this control"), + DefaultValue(false)] + public virtual bool IsSimpleDragSource { + get { return this.DragSource != null; } + set { + this.DragSource = value ? new SimpleDragSource() : null; + } + } + + /// + /// Hide the Items collection so it's not visible in the Properties grid. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new ListViewItemCollection Items { + get { return base.Items; } + } + + /// + /// This renderer draws the items when in the list is in non-details view. + /// In details view, the renderers for the individuals columns are responsible. + /// + [Category("ObjectListView"), + Description("The owner drawn renderer that draws items when the list is in non-Details view."), + DefaultValue(null)] + public IRenderer ItemRenderer { + get { return itemRenderer; } + set { itemRenderer = value; } + } + private IRenderer itemRenderer; + + /// + /// Which column did we last sort by + /// + /// This is an alias for PrimarySortColumn + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual OLVColumn LastSortColumn { + get { return this.PrimarySortColumn; } + set { this.PrimarySortColumn = value; } + } + + /// + /// Which direction did we last sort + /// + /// This is an alias for PrimarySortOrder + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual SortOrder LastSortOrder { + get { return this.PrimarySortOrder; } + set { this.PrimarySortOrder = value; } + } + + /// + /// Gets or sets the filter that is applied to our whole list of objects. + /// + /// + /// The list is updated immediately to reflect this filter. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IListFilter ListFilter { + get { return listFilter; } + set { + listFilter = value; + if (this.UseFiltering) + this.UpdateFiltering(); + } + } + private IListFilter listFilter; + + /// + /// Gets or sets the filter that is applied to each model objects in the list + /// + /// + /// You may want to consider using instead of this property, + /// since AdditionalFilter combines with column filtering at runtime. Setting this property simply + /// replaces any column filter the user may have given. + /// + /// The list is updated immediately to reflect this filter. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IModelFilter ModelFilter { + get { return modelFilter; } + set { + modelFilter = value; + this.NotifyNewModelFilter(); + if (this.UseFiltering) { + this.UpdateFiltering(); + + // When the filter changes, it's likely/possible that the selection has also changed. + // It's expensive to see if the selection has actually changed (for large virtual lists), + // so we just fake a selection changed event, just in case. SF #144 + this.OnSelectedIndexChanged(EventArgs.Empty); + } + } + } + private IModelFilter modelFilter; + + /// + /// Gets the hit test info last time the mouse was moved. + /// + /// Useful for hot item processing. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual OlvListViewHitTestInfo MouseMoveHitTest { + get { return mouseMoveHitTest; } + private set { mouseMoveHitTest = value; } + } + private OlvListViewHitTestInfo mouseMoveHitTest; + + /// + /// Gets or sets the list of groups shown by the listview. + /// + /// + /// This property does not work like the .NET Groups property. It should + /// be treated as a read-only property. + /// Changes made to the list are NOT reflected in the ListView itself -- it is pointless to add + /// or remove groups to/from this list. Such modifications will do nothing. + /// To do such things, you must listen for + /// BeforeCreatingGroups or AboutToCreateGroups events, and change the list of + /// groups in those events. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IList OLVGroups { + get { return this.olvGroups; } + set { this.olvGroups = value; } + } + private IList olvGroups; + + /// + /// Gets or sets the collection of OLVGroups that are collapsed. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IEnumerable CollapsedGroups { + get + { + if (this.OLVGroups != null) + { + foreach (OLVGroup group in this.OLVGroups) + { + if (group.Collapsed) + yield return group; + } + } + } + set + { + if (this.OLVGroups == null) + return; + + Hashtable shouldCollapse = new Hashtable(); + if (value != null) + { + foreach (OLVGroup group in value) + shouldCollapse[group.Key] = true; + } + foreach (OLVGroup group in this.OLVGroups) + { + group.Collapsed = shouldCollapse.ContainsKey(group.Key); + } + + } + } + + /// + /// Gets or sets whether the user wants to owner draw the header control + /// themselves. If this is false (the default), ObjectListView will use + /// custom drawing to render the header, if needed. + /// + /// + /// If you listen for the DrawColumnHeader event, you need to set this to true, + /// otherwise your event handler will not be called. + /// + [Category("ObjectListView"), + Description("Should the DrawColumnHeader event be triggered"), + DefaultValue(false)] + public bool OwnerDrawnHeader { + get { return ownerDrawnHeader; } + set { ownerDrawnHeader = value; } + } + private bool ownerDrawnHeader; + + /// + /// Get/set the collection of objects that this list will show + /// + /// + /// + /// The contents of the control will be updated immediately after setting this property. + /// + /// This method preserves selection, if possible. Use if + /// you do not want to preserve the selection. Preserving selection is the slowest part of this + /// code and performance is O(n) where n is the number of selected rows. + /// This method is not thread safe. + /// The property DOES work on virtual lists: setting is problem-free, but if you try to get it + /// and the list has 10 million objects, it may take some time to return. + /// This collection is unfiltered. Use to access just those objects + /// that survive any installed filters. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IEnumerable Objects { + get { return this.objects; } + set { this.SetObjects(value, true); } + } + private IEnumerable objects; + + /// + /// Gets the collection of objects that will be considered when creating clusters + /// (which are used to generate Excel-like column filters) + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IEnumerable ObjectsForClustering { + get { return this.Objects; } + } + + /// + /// Gets or sets the image that will be drawn over the top of the ListView + /// + [Category("ObjectListView"), + Description("The image that will be drawn over the top of the ListView"), + DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public ImageOverlay OverlayImage { + get { return this.imageOverlay; } + set { + if (this.imageOverlay == value) + return; + + this.RemoveOverlay(this.imageOverlay); + this.imageOverlay = value; + this.AddOverlay(this.imageOverlay); + } + } + private ImageOverlay imageOverlay; + + /// + /// Gets or sets the text that will be drawn over the top of the ListView + /// + [Category("ObjectListView"), + Description("The text that will be drawn over the top of the ListView"), + DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public TextOverlay OverlayText { + get { return this.textOverlay; } + set { + if (this.textOverlay == value) + return; + + this.RemoveOverlay(this.textOverlay); + this.textOverlay = value; + this.AddOverlay(this.textOverlay); + } + } + private TextOverlay textOverlay; + + /// + /// Gets or sets the transparency of all the overlays. + /// 0 is completely transparent, 255 is completely opaque. + /// + /// + /// This is obsolete. Use Transparency on each overlay. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int OverlayTransparency { + get { return this.overlayTransparency; } + set { this.overlayTransparency = Math.Min(255, Math.Max(0, value)); } + } + private int overlayTransparency = 128; + + /// + /// Gets the list of overlays that will be drawn on top of the ListView + /// + /// + /// You can add new overlays and remove overlays that you have added, but + /// don't mess with the overlays that you didn't create. + /// + [Browsable(false)] + protected IList Overlays { + get { return this.overlays; } + } + private readonly List overlays = new List(); + + /// + /// Gets or sets whether the ObjectListView will be owner drawn. Defaults to true. + /// + /// + /// + /// When this is true, all of ObjectListView's neat features are available. + /// + /// We have to reimplement this property, even though we just call the base + /// property, in order to change the [DefaultValue] to true. + /// + /// + [Category("Appearance"), + Description("Should the ListView do its own rendering"), + DefaultValue(true)] + public new bool OwnerDraw { + get { return base.OwnerDraw; } + set { base.OwnerDraw = value; } + } + + /// + /// Gets or sets whether or not primary checkboxes will persistent their values across list rebuild + /// and filtering operations. + /// + /// + /// + /// This property is only useful when you don't explicitly set CheckStateGetter/Putter. + /// If you use CheckStateGetter/Putter, the checkedness of a row will already be persisted + /// by those methods. + /// + /// This defaults to true. If this is false, checkboxes will lose their values when the + /// list if rebuild or filtered. + /// If you set it to false on virtual lists, + /// you have to install CheckStateGetter/Putters. + /// + [Category("ObjectListView"), + Description("Will primary checkboxes persistent their values across list rebuilds"), + DefaultValue(true)] + public virtual bool PersistentCheckBoxes { + get { return persistentCheckBoxes; } + set { + if (persistentCheckBoxes == value) + return; + persistentCheckBoxes = value; + this.ClearPersistentCheckState(); + } + } + private bool persistentCheckBoxes = true; + + /// + /// Gets or sets a dictionary that remembers the check state of model objects + /// + /// This is used when PersistentCheckBoxes is true and for virtual lists. + protected Dictionary CheckStateMap { + get { return checkStateMap ?? (checkStateMap = new Dictionary()); } + set { checkStateMap = value; } + } + private Dictionary checkStateMap; + + /// + /// Which column did we last sort by + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual OLVColumn PrimarySortColumn { + get { return this.primarySortColumn; } + set { + this.primarySortColumn = value; + if (this.TintSortColumn) + this.SelectedColumn = value; + } + } + private OLVColumn primarySortColumn; + + /// + /// Which direction did we last sort + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual SortOrder PrimarySortOrder { + get { return primarySortOrder; } + set { primarySortOrder = value; } + } + private SortOrder primarySortOrder; + + /// + /// Gets or sets if non-editable checkboxes are drawn as disabled. Default is false. + /// + /// + /// This only has effect in owner drawn mode. + /// + [Category("ObjectListView"), + Description("Should non-editable checkboxes be drawn as disabled?"), + DefaultValue(false)] + public virtual bool RenderNonEditableCheckboxesAsDisabled { + get { return renderNonEditableCheckboxesAsDisabled; } + set { renderNonEditableCheckboxesAsDisabled = value; } + } + private bool renderNonEditableCheckboxesAsDisabled; + + /// + /// Specify the height of each row in the control in pixels. + /// + /// The row height in a listview is normally determined by the font size and the small image list size. + /// This setting allows that calculation to be overridden (within reason: you still cannot set the line height to be + /// less than the line height of the font used in the control). + /// Setting it to -1 means use the normal calculation method. + /// This feature is experimental! Strange things may happen to your program, + /// your spouse or your pet if you use it. + /// + [Category("ObjectListView"), + Description("Specify the height of each row in pixels. -1 indicates default height"), + DefaultValue(-1)] + public virtual int RowHeight { + get { return rowHeight; } + set { + if (value < 1) + rowHeight = -1; + else + rowHeight = value; + if (this.DesignMode) + return; + this.SetupBaseImageList(); + if (this.CheckBoxes) + this.InitializeStateImageList(); + } + } + private int rowHeight = -1; + + /// + /// How many pixels high is each row? + /// + [Browsable(false)] + public virtual int RowHeightEffective { + get { + switch (this.View) { + case View.List: + case View.SmallIcon: + case View.Details: + return Math.Max(this.SmallImageSize.Height, this.Font.Height); + + case View.Tile: + return this.TileSize.Height; + + case View.LargeIcon: + if (this.LargeImageList == null) + return this.Font.Height; + + return Math.Max(this.LargeImageList.ImageSize.Height, this.Font.Height); + + default: + // This should never happen + return 0; + } + } + } + + /// + /// How many rows appear on each page of this control + /// + [Browsable(false)] + public virtual int RowsPerPage { + get { + return NativeMethods.GetCountPerPage(this); + } + } + + /// + /// Get/set the column that will be used to resolve comparisons that are equal when sorting. + /// + /// There is no user interface for this setting. It must be set programmatically. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual OLVColumn SecondarySortColumn { + get { return this.secondarySortColumn; } + set { this.secondarySortColumn = value; } + } + private OLVColumn secondarySortColumn; + + /// + /// When the SecondarySortColumn is used, in what order will it compare results? + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual SortOrder SecondarySortOrder { + get { return this.secondarySortOrder; } + set { this.secondarySortOrder = value; } + } + private SortOrder secondarySortOrder = SortOrder.None; + + /// + /// Gets or sets if all rows should be selected when the user presses Ctrl-A + /// + [Category("ObjectListView"), + Description("Should the control select all rows when the user presses Ctrl-A?"), + DefaultValue(true)] + public virtual bool SelectAllOnControlA { + get { return selectAllOnControlA; } + set { selectAllOnControlA = value; } + } + private bool selectAllOnControlA = true; + + /// + /// When the user right clicks on the column headers, should a menu be presented which will allow + /// them to choose which columns will be shown in the view? + /// + /// This is just a compatibility wrapper for the SelectColumnsOnRightClickBehaviour + /// property. + [Category("ObjectListView"), + Description("When the user right clicks on the column headers, should a menu be presented which will allow them to choose which columns will be shown in the view?"), + DefaultValue(true)] + public virtual bool SelectColumnsOnRightClick { + get { return this.SelectColumnsOnRightClickBehaviour != ColumnSelectBehaviour.None; } + set { + if (value) { + if (this.SelectColumnsOnRightClickBehaviour == ColumnSelectBehaviour.None) + this.SelectColumnsOnRightClickBehaviour = ColumnSelectBehaviour.InlineMenu; + } else { + this.SelectColumnsOnRightClickBehaviour = ColumnSelectBehaviour.None; + } + } + } + + /// + /// Gets or sets how the user will be able to select columns when the header is right clicked + /// + [Category("ObjectListView"), + Description("When the user right clicks on the column headers, how will the user be able to select columns?"), + DefaultValue(ColumnSelectBehaviour.InlineMenu)] + public virtual ColumnSelectBehaviour SelectColumnsOnRightClickBehaviour { + get { return selectColumnsOnRightClickBehaviour; } + set { selectColumnsOnRightClickBehaviour = value; } + } + private ColumnSelectBehaviour selectColumnsOnRightClickBehaviour = ColumnSelectBehaviour.InlineMenu; + + /// + /// When the column select menu is open, should it stay open after an item is selected? + /// Staying open allows the user to turn more than one column on or off at a time. + /// + /// This only works when SelectColumnsOnRightClickBehaviour is set to InlineMenu. + /// It has no effect when the behaviour is set to SubMenu. + [Category("ObjectListView"), + Description("When the column select inline menu is open, should it stay open after an item is selected?"), + DefaultValue(true)] + public virtual bool SelectColumnsMenuStaysOpen { + get { return selectColumnsMenuStaysOpen; } + set { selectColumnsMenuStaysOpen = value; } + } + private bool selectColumnsMenuStaysOpen = true; + + /// + /// Gets or sets the column that is drawn with a slight tint. + /// + /// + /// + /// If TintSortColumn is true, the sort column will automatically + /// be made the selected column. + /// + /// + /// The colour of the tint is controlled by SelectedColumnTint. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public OLVColumn SelectedColumn { + get { return this.selectedColumn; } + set { + this.selectedColumn = value; + if (value == null) { + this.RemoveDecoration(this.selectedColumnDecoration); + } else { + if (!this.HasDecoration(this.selectedColumnDecoration)) + this.AddDecoration(this.selectedColumnDecoration); + } + } + } + private OLVColumn selectedColumn; + private readonly TintedColumnDecoration selectedColumnDecoration = new TintedColumnDecoration(); + + /// + /// Gets or sets the decoration that will be drawn on all selected rows + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IDecoration SelectedRowDecoration { + get { return this.selectedRowDecoration; } + set { this.selectedRowDecoration = value; } + } + private IDecoration selectedRowDecoration; + + /// + /// What color should be used to tint the selected column? + /// + /// + /// The tint color must be alpha-blendable, so if the given color is solid + /// (i.e. alpha = 255), it will be changed to have a reasonable alpha value. + /// + [Category("ObjectListView"), + Description("The color that will be used to tint the selected column"), + DefaultValue(typeof(Color), "")] + public virtual Color SelectedColumnTint { + get { return selectedColumnTint; } + set { + this.selectedColumnTint = value.A == 255 ? Color.FromArgb(15, value) : value; + this.selectedColumnDecoration.Tint = this.selectedColumnTint; + } + } + private Color selectedColumnTint = Color.Empty; + + /// + /// Gets or sets the index of the row that is currently selected. + /// When getting the index, if no row is selected,or more than one is selected, return -1. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual int SelectedIndex { + get { return this.SelectedIndices.Count == 1 ? this.SelectedIndices[0] : -1; } + set { + this.SelectedIndices.Clear(); + if (value >= 0 && value < this.Items.Count) + this.SelectedIndices.Add(value); + } + } + + /// + /// Gets or sets the ListViewItem that is currently selected . If no row is selected, or more than one is selected, return null. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual OLVListItem SelectedItem { + get { + return this.SelectedIndices.Count == 1 ? this.GetItem(this.SelectedIndices[0]) : null; + } + set { + this.SelectedIndices.Clear(); + if (value != null) + this.SelectedIndices.Add(value.Index); + } + } + + /// + /// Gets the model object from the currently selected row, if there is only one row selected. + /// If no row is selected, or more than one is selected, returns null. + /// When setting, this will select the row that is displaying the given model object and focus on it. + /// All other rows are deselected. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual Object SelectedObject { + get { + return this.SelectedIndices.Count == 1 ? this.GetModelObject(this.SelectedIndices[0]) : null; + } + set { + // If the given model is already selected, don't do anything else (prevents an flicker) + object selectedObject = this.SelectedObject; + if (selectedObject != null && selectedObject.Equals(value)) + return; + + this.SelectedIndices.Clear(); + this.SelectObject(value, true); + } + } + + /// + /// Get the model objects from the currently selected rows. If no row is selected, the returned List will be empty. + /// When setting this value, select the rows that is displaying the given model objects. All other rows are deselected. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IList SelectedObjects { + get { + ArrayList list = new ArrayList(); + foreach (int index in this.SelectedIndices) + list.Add(this.GetModelObject(index)); + return list; + } + set { + this.SelectedIndices.Clear(); + this.SelectObjects(value); + } + } + + /// + /// When the user right clicks on the column headers, should a menu be presented which will allow + /// them to choose common tasks to perform on the listview? + /// + [Category("ObjectListView"), + Description("When the user right clicks on the column headers, should a menu be presented which will allow them to perform common tasks on the listview?"), + DefaultValue(false)] + public virtual bool ShowCommandMenuOnRightClick { + get { return showCommandMenuOnRightClick; } + set { showCommandMenuOnRightClick = value; } + } + private bool showCommandMenuOnRightClick; + + /// + /// Gets or sets whether this ObjectListView will show Excel like filtering + /// menus when the header control is right clicked + /// + [Category("ObjectListView"), + Description("If this is true, right clicking on a column header will show a Filter menu option"), + DefaultValue(true)] + public bool ShowFilterMenuOnRightClick { + get { return showFilterMenuOnRightClick; } + set { showFilterMenuOnRightClick = value; } + } + private bool showFilterMenuOnRightClick = true; + + /// + /// Should this list show its items in groups? + /// + [Category("Appearance"), + Description("Should the list view show items in groups?"), + DefaultValue(true)] + public new virtual bool ShowGroups { + get { return base.ShowGroups; } + set { + this.GroupImageList = this.GroupImageList; + base.ShowGroups = value; + } + } + + /// + /// Should the list view show a bitmap in the column header to show the sort direction? + /// + /// + /// The only reason for not wanting to have sort indicators is that, on pre-XP versions of + /// Windows, having sort indicators required the ListView to have a small image list, and + /// as soon as you give a ListView a SmallImageList, the text of column 0 is bumped 16 + /// pixels to the right, even if you never used an image. + /// + [Category("ObjectListView"), + Description("Should the list view show sort indicators in the column headers?"), + DefaultValue(true)] + public virtual bool ShowSortIndicators { + get { return showSortIndicators; } + set { showSortIndicators = value; } + } + private bool showSortIndicators; + + /// + /// Should the list view show images on subitems? + /// + /// + /// Virtual lists have to be owner drawn in order to show images on subitems + /// + [Category("ObjectListView"), + Description("Should the list view show images on subitems?"), + DefaultValue(false)] + public virtual bool ShowImagesOnSubItems { + get { return showImagesOnSubItems; } + set { + showImagesOnSubItems = value; + if (this.Created) + this.ApplyExtendedStyles(); + if (value && this.VirtualMode) + this.OwnerDraw = true; + } + } + private bool showImagesOnSubItems; + + /// + /// This property controls whether group labels will be suffixed with a count of items. + /// + /// + /// The format of the suffix is controlled by GroupWithItemCountFormat/GroupWithItemCountSingularFormat properties + /// + [Category("ObjectListView"), + Description("Will group titles be suffixed with a count of the items in the group?"), + DefaultValue(false)] + public virtual bool ShowItemCountOnGroups { + get { return showItemCountOnGroups; } + set { showItemCountOnGroups = value; } + } + private bool showItemCountOnGroups; + + /// + /// Gets or sets whether the control will show column headers in all + /// views (true), or only in Details view (false) + /// + /// + /// + /// This property is not working correctly. JPP 2010/04/06. + /// It works fine if it is set before the control is created. + /// But if it turned off once the control is created, the control + /// loses its checkboxes (weird!) + /// + /// + /// To changed this setting after the control is created, things + /// are complicated. If it is off and we want it on, we have + /// to change the View and the header will appear. If it is currently + /// on and we want to turn it off, we have to both change the view + /// AND recreate the handle. Recreating the handle is a problem + /// since it makes our checkbox style disappear. + /// + /// + /// This property doesn't work on XP. + /// + [Category("ObjectListView"), + Description("Will the control will show column headers in all views?"), + DefaultValue(true)] + public bool ShowHeaderInAllViews { + get { return ObjectListView.IsVistaOrLater && showHeaderInAllViews; } + set { + if (showHeaderInAllViews == value) + return; + + showHeaderInAllViews = value; + + // If the control isn't already created, everything is fine. + if (!this.Created) + return; + + // If the header is being hidden, we have to recreate the control + // to remove the style (not sure why this is) + if (showHeaderInAllViews) + this.ApplyExtendedStyles(); + else + this.RecreateHandle(); + + // Still more complications. The change doesn't become visible until the View is changed + if (this.View != View.Details) { + View temp = this.View; + this.View = View.Details; + this.View = temp; + } + } + } + private bool showHeaderInAllViews = true; + + /// + /// Override the SmallImageList property so we can correctly shadow its operations. + /// + /// If you use the RowHeight property to specify the row height, the SmallImageList + /// must be fully initialised before setting/changing the RowHeight. If you add new images to the image + /// list after setting the RowHeight, you must assign the imagelist to the control again. Something as simple + /// as this will work: + /// listView1.SmallImageList = listView1.SmallImageList; + /// + public new ImageList SmallImageList { + get { return this.shadowedImageList; } + set { + this.shadowedImageList = value; + if (this.UseSubItemCheckBoxes) + this.SetupSubItemCheckBoxes(); + this.SetupBaseImageList(); + } + } + private ImageList shadowedImageList; + + /// + /// Return the size of the images in the small image list or a reasonable default + /// + [Browsable(false)] + public virtual Size SmallImageSize { + get { + return this.BaseSmallImageList == null ? new Size(16, 16) : this.BaseSmallImageList.ImageSize; + } + } + + /// + /// When the listview is grouped, should the items be sorted by the primary column? + /// If this is false, the items will be sorted by the same column as they are grouped. + /// + /// + /// + /// The primary column is always column 0 and is unrelated to the PrimarySort column. + /// + /// + [Category("ObjectListView"), + Description("When the listview is grouped, should the items be sorted by the primary column? If this is false, the items will be sorted by the same column as they are grouped."), + DefaultValue(true)] + public virtual bool SortGroupItemsByPrimaryColumn { + get { return this.sortGroupItemsByPrimaryColumn; } + set { this.sortGroupItemsByPrimaryColumn = value; } + } + private bool sortGroupItemsByPrimaryColumn = true; + + /// + /// When the listview is grouped, how many pixels should exist between the end of one group and the + /// beginning of the next? + /// + [Category("ObjectListView"), + Description("How many pixels of space will be between groups"), + DefaultValue(0)] + public virtual int SpaceBetweenGroups { + get { return this.spaceBetweenGroups; } + set { + if (this.spaceBetweenGroups == value) + return; + + this.spaceBetweenGroups = value; + this.SetGroupSpacing(); + } + } + private int spaceBetweenGroups; + + private void SetGroupSpacing() { + if (!this.IsHandleCreated) + return; + + NativeMethods.LVGROUPMETRICS metrics = new NativeMethods.LVGROUPMETRICS(); + metrics.cbSize = ((uint)Marshal.SizeOf(typeof(NativeMethods.LVGROUPMETRICS))); + metrics.mask = (uint)GroupMetricsMask.LVGMF_BORDERSIZE; + metrics.Bottom = (uint)this.SpaceBetweenGroups; + NativeMethods.SetGroupMetrics(this, metrics); + } + + /// + /// Should the sort column show a slight tinge? + /// + [Category("ObjectListView"), + Description("Should the sort column show a slight tinting?"), + DefaultValue(false)] + public virtual bool TintSortColumn { + get { return this.tintSortColumn; } + set { + this.tintSortColumn = value; + if (value && this.PrimarySortColumn != null) + this.SelectedColumn = this.PrimarySortColumn; + else + this.SelectedColumn = null; + } + } + private bool tintSortColumn; + + /// + /// Should each row have a tri-state checkbox? + /// + /// + /// If this is true, the user can choose the third state (normally Indeterminate). Otherwise, user clicks + /// alternate between checked and unchecked. CheckStateGetter can still return Indeterminate when this + /// setting is false. + /// + [Category("ObjectListView"), + Description("Should the primary column have a checkbox that behaves as a tri-state checkbox?"), + DefaultValue(false)] + public virtual bool TriStateCheckBoxes { + get { return triStateCheckBoxes; } + set { + triStateCheckBoxes = value; + if (value && !this.CheckBoxes) + this.CheckBoxes = true; + this.InitializeStateImageList(); + } + } + private bool triStateCheckBoxes; + + /// + /// Get or set the index of the top item of this listview + /// + /// + /// + /// This property only works when the listview is in Details view and not showing groups. + /// + /// + /// The reason that it does not work when showing groups is that, when groups are enabled, + /// the Windows msg LVM_GETTOPINDEX always returns 0, regardless of the + /// scroll position. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual int TopItemIndex { + get { + if (this.View == View.Details && this.IsHandleCreated) + return NativeMethods.GetTopIndex(this); + + return -1; + } + set { + int newTopIndex = Math.Min(value, this.GetItemCount() - 1); + if (this.View != View.Details || newTopIndex < 0) + return; + + try { + this.TopItem = this.Items[newTopIndex]; + + // Setting the TopItem sometimes gives off by one errors, + // that (bizarrely) are correct on a second attempt + if (this.TopItem != null && this.TopItem.Index != newTopIndex) + this.TopItem = this.GetItem(newTopIndex); + } + catch (NullReferenceException) { + // There is a bug in the .NET code where setting the TopItem + // will sometimes throw null reference exceptions + // There is nothing we can do to get around it. + } + } + } + + /// + /// Gets or sets whether moving the mouse over the header will trigger CellOver events. + /// Defaults to true. + /// + /// + /// Moving the mouse over the header did not previously trigger CellOver events, since the + /// header is considered a separate control. + /// If this change in behaviour causes your application problems, set this to false. + /// If you are interested in knowing when the mouse moves over the header, set this property to true (the default). + /// + [Category("ObjectListView"), + Description("Should moving the mouse over the header trigger CellOver events?"), + DefaultValue(true)] + public bool TriggerCellOverEventsWhenOverHeader + { + get { return triggerCellOverEventsWhenOverHeader; } + set { triggerCellOverEventsWhenOverHeader = value; } + } + private bool triggerCellOverEventsWhenOverHeader = true; + + /// + /// When resizing a column by dragging its divider, should any space filling columns be + /// resized at each mouse move? If this is false, the filling columns will be + /// updated when the mouse is released. + /// + /// + /// + /// If you have a space filling column + /// is in the left of the column that is being resized, this will look odd: + /// the right edge of the column will be dragged, but + /// its left edge will move since the space filling column is shrinking. + /// + /// This is logical behaviour -- it just looks wrong. + /// + /// + /// Given the above behavior is probably best to turn this property off if your space filling + /// columns aren't the right-most columns. + /// + [Category("ObjectListView"), + Description("When resizing a column by dragging its divider, should any space filling columns be resized at each mouse move?"), + DefaultValue(true)] + public virtual bool UpdateSpaceFillingColumnsWhenDraggingColumnDivider { + get { return updateSpaceFillingColumnsWhenDraggingColumnDivider; } + set { updateSpaceFillingColumnsWhenDraggingColumnDivider = value; } + } + private bool updateSpaceFillingColumnsWhenDraggingColumnDivider = true; + + /// + /// What color should be used for the background of selected rows when the control doesn't have the focus? + /// + [Category("ObjectListView"), + Description("The background color of selected rows when the control doesn't have the focus"), + DefaultValue(typeof(Color), "")] + public virtual Color UnfocusedSelectedBackColor { + get { return this.unfocusedSelectedBackColor; } + set { this.unfocusedSelectedBackColor = value; } + } + private Color unfocusedSelectedBackColor = Color.Empty; + + /// + /// Return the color should be used for the background of selected rows when the control doesn't have the focus or a reasonable default + /// + [Browsable(false)] + public virtual Color UnfocusedSelectedBackColorOrDefault { + get { + return this.UnfocusedSelectedBackColor.IsEmpty ? SystemColors.Control : this.UnfocusedSelectedBackColor; + } + } + + /// + /// What color should be used for the foreground of selected rows when the control doesn't have the focus? + /// + [Category("ObjectListView"), + Description("The foreground color of selected rows when the control is owner drawn and doesn't have the focus"), + DefaultValue(typeof(Color), "")] + public virtual Color UnfocusedSelectedForeColor { + get { return this.unfocusedSelectedForeColor; } + set { this.unfocusedSelectedForeColor = value; } + } + private Color unfocusedSelectedForeColor = Color.Empty; + + /// + /// Return the color should be used for the foreground of selected rows when the control doesn't have the focus or a reasonable default + /// + [Browsable(false)] + public virtual Color UnfocusedSelectedForeColorOrDefault { + get { + return this.UnfocusedSelectedForeColor.IsEmpty ? SystemColors.ControlText : this.UnfocusedSelectedForeColor; + } + } + + /// + /// Gets or sets whether the list give a different background color to every second row? Defaults to false. + /// + /// The color of the alternate rows is given by AlternateRowBackColor. + /// There is a "feature" in .NET for listviews in non-full-row-select mode, where + /// selected rows are not drawn with their correct background color. + [Category("ObjectListView"), + Description("Should the list view use a different backcolor to alternate rows?"), + DefaultValue(false)] + public virtual bool UseAlternatingBackColors { + get { return useAlternatingBackColors; } + set { useAlternatingBackColors = value; } + } + private bool useAlternatingBackColors; + + /// + /// Should FormatCell events be called for each cell in the control? + /// + /// + /// In many situations, no cell level formatting is performed. ObjectListView + /// can run somewhat faster if it does not trigger a format cell event for every cell + /// unless it is required. So, by default, it does not raise an event for each cell. + /// + /// ObjectListView *does* raise a FormatRow event every time a row is rebuilt. + /// Individual rows can decide whether to raise FormatCell + /// events for every cell in row. + /// + /// + /// Regardless of this setting, FormatCell events are only raised when the ObjectListView + /// is in Details view. + /// + [Category("ObjectListView"), + Description("Should FormatCell events be triggered to every cell that is built?"), + DefaultValue(false)] + public bool UseCellFormatEvents { + get { return useCellFormatEvents; } + set { useCellFormatEvents = value; } + } + private bool useCellFormatEvents; + + /// + /// Should the selected row be drawn with non-standard foreground and background colors? + /// + /// v2.9 This property is no longer required + [Category("ObjectListView"), + Description("Should the selected row be drawn with non-standard foreground and background colors?"), + DefaultValue(false)] + public bool UseCustomSelectionColors { + get { return false; } + // ReSharper disable once ValueParameterNotUsed + set { } + } + + /// + /// Gets or sets whether this ObjectListView will use the same hot item and selection + /// mechanism that Vista Explorer does. + /// + /// + /// + /// This property has many imperfections: + /// + /// This only works on Vista and later + /// It does not work well with AlternateRowBackColors. + /// It does not play well with HotItemStyles. + /// It looks a little bit silly is FullRowSelect is false. + /// It doesn't work at all when the list is owner drawn (since the renderers + /// do all the drawing). As such, it won't work with TreeListView's since they *have to be* + /// owner drawn. You can still set it, but it's just not going to be happy. + /// + /// But if you absolutely have to look like Vista/Win7, this is your property. + /// Do not complain if settings this messes up other things. + /// + /// + /// When this property is set to true, the ObjectListView will be not owner drawn. This will + /// disable many of the pretty drawing-based features of ObjectListView. + /// + /// Because of the above, this property should never be set to true for TreeListViews, + /// since they *require* owner drawing to be rendered correctly. + /// + [Category("ObjectListView"), + Description("Should the list use the same hot item and selection mechanism as Vista?"), + DefaultValue(false)] + public bool UseExplorerTheme { + get { return useExplorerTheme; } + set { + useExplorerTheme = value; + if (this.Created) + NativeMethods.SetWindowTheme(this.Handle, value ? "explorer" : "", null); + + this.OwnerDraw = !value; + } + } + private bool useExplorerTheme; + + /// + /// Gets or sets whether the list should enable filtering + /// + [Category("ObjectListView"), + Description("Should the list enable filtering?"), + DefaultValue(false)] + public virtual bool UseFiltering { + get { return useFiltering; } + set { + if (useFiltering == value) + return; + useFiltering = value; + this.UpdateFiltering(); + } + } + private bool useFiltering; + + /// + /// Gets or sets whether the list should put an indicator into a column's header to show that + /// it is filtering on that column + /// + /// If you set this to true, HeaderUsesThemes is automatically set to false, since + /// we can only draw a filter indicator when not using a themed header. + [Category("ObjectListView"), + Description("Should an image be drawn in a column's header when that column is being used for filtering?"), + DefaultValue(false)] + public virtual bool UseFilterIndicator { + get { return useFilterIndicator; } + set { + if (this.useFilterIndicator == value) + return; + useFilterIndicator = value; + if (this.useFilterIndicator) + this.HeaderUsesThemes = false; + this.Invalidate(); + } + } + private bool useFilterIndicator; + + /// + /// Should controls (checkboxes or buttons) that are under the mouse be drawn "hot"? + /// + /// + /// If this is false, control will not be drawn differently when the mouse is over them. + /// + /// If this is false AND UseHotItem is false AND UseHyperlinks is false, then the ObjectListView + /// can skip some processing on mouse move. This make mouse move processing use almost no CPU. + /// + /// + [Category("ObjectListView"), + Description("Should controls (checkboxes or buttons) that are under the mouse be drawn hot?"), + DefaultValue(true)] + public bool UseHotControls { + get { return this.useHotControls; } + set { this.useHotControls = value; } + } + private bool useHotControls = true; + + /// + /// Should the item under the cursor be formatted in a special way? + /// + [Category("ObjectListView"), + Description("Should HotTracking be used? Hot tracking applies special formatting to the row under the cursor"), + DefaultValue(false)] + public bool UseHotItem { + get { return this.useHotItem; } + set { + this.useHotItem = value; + if (value) + this.AddOverlay(this.HotItemStyleOrDefault.Overlay); + else + this.RemoveOverlay(this.HotItemStyleOrDefault.Overlay); + } + } + private bool useHotItem; + + /// + /// Gets or sets whether this listview should show hyperlinks in the cells. + /// + [Category("ObjectListView"), + Description("Should hyperlinks be shown on this control?"), + DefaultValue(false)] + public bool UseHyperlinks { + get { return this.useHyperlinks; } + set { + this.useHyperlinks = value; + if (value && this.HyperlinkStyle == null) + this.HyperlinkStyle = new HyperlinkStyle(); + } + } + private bool useHyperlinks; + + /// + /// Should this control show overlays + /// + /// Overlays are enabled by default and would only need to be disabled + /// if they were causing problems in your development environment. + [Category("ObjectListView"), + Description("Should this control show overlays"), + DefaultValue(true)] + public bool UseOverlays { + get { return this.useOverlays; } + set { this.useOverlays = value; } + } + private bool useOverlays = true; + + /// + /// Should this control be configured to show check boxes on subitems? + /// + /// If this is set to True, the control will be given a SmallImageList if it + /// doesn't already have one. Also, if it is a virtual list, it will be set to owner + /// drawn, since virtual lists can't draw check boxes without being owner drawn. + [Category("ObjectListView"), + Description("Should this control be configured to show check boxes on subitems."), + DefaultValue(false)] + public bool UseSubItemCheckBoxes { + get { return this.useSubItemCheckBoxes; } + set { + this.useSubItemCheckBoxes = value; + if (value) + this.SetupSubItemCheckBoxes(); + } + } + private bool useSubItemCheckBoxes; + + /// + /// Gets or sets if the ObjectListView will use a translucent selection mechanism like Vista. + /// + /// + /// + /// Unlike UseExplorerTheme, this Vista-like scheme works on XP and for both + /// owner and non-owner drawn lists. + /// + /// + /// This will replace any SelectedRowDecoration that has been installed. + /// + /// + /// If you don't like the colours used for the selection, ignore this property and + /// just create your own RowBorderDecoration and assigned it to SelectedRowDecoration, + /// just like this property setter does. + /// + /// + [Category("ObjectListView"), + Description("Should the list use a translucent selection mechanism (like Vista)"), + DefaultValue(false)] + public bool UseTranslucentSelection { + get { return useTranslucentSelection; } + set { + useTranslucentSelection = value; + if (value) { + RowBorderDecoration rbd = new RowBorderDecoration(); + rbd.BorderPen = new Pen(Color.FromArgb(154, 223, 251)); + rbd.FillBrush = new SolidBrush(Color.FromArgb(48, 163, 217, 225)); + rbd.BoundsPadding = new Size(0, 0); + rbd.CornerRounding = 6.0f; + this.SelectedRowDecoration = rbd; + } else + this.SelectedRowDecoration = null; + } + } + private bool useTranslucentSelection; + + /// + /// Gets or sets if the ObjectListView will use a translucent hot row highlighting mechanism like Vista. + /// + /// + /// + /// Setting this will replace any HotItemStyle that has been installed. + /// + /// + /// If you don't like the colours used for the hot item, ignore this property and + /// just create your own HotItemStyle, fill in the values you want, and assigned it to HotItemStyle property, + /// just like this property setter does. + /// + /// + [Category("ObjectListView"), + Description("Should the list use a translucent hot row highlighting mechanism (like Vista)"), + DefaultValue(false)] + public bool UseTranslucentHotItem { + get { return useTranslucentHotItem; } + set { + useTranslucentHotItem = value; + if (value) { + RowBorderDecoration rbd = new RowBorderDecoration(); + rbd.BorderPen = new Pen(Color.FromArgb(154, 223, 251)); + rbd.BoundsPadding = new Size(0, 0); + rbd.CornerRounding = 6.0f; + rbd.FillGradientFrom = Color.FromArgb(0, 255, 255, 255); + rbd.FillGradientTo = Color.FromArgb(64, 183, 237, 240); + HotItemStyle his = new HotItemStyle(); + his.Decoration = rbd; + this.HotItemStyle = his; + } else + this.HotItemStyle = null; + this.UseHotItem = value; + } + } + private bool useTranslucentHotItem; + + /// + /// Get/set the style of view that this listview is using + /// + /// Switching to tile or details view installs the columns appropriate to that view. + /// Confusingly, in tile view, every column is shown as a row of information. + [Category("Appearance"), + Description("Select the layout of the items within this control)"), + DefaultValue(null)] + public new View View + { + get { return base.View; } + set { + if (base.View == value) + return; + + if (this.Frozen) { + base.View = value; + this.SetupBaseImageList(); + } else { + this.Freeze(); + + if (value == View.Tile) + this.CalculateReasonableTileSize(); + + base.View = value; + this.SetupBaseImageList(); + this.Unfreeze(); + } + } + } + + #endregion + + #region Callbacks + + /// + /// This delegate fetches the checkedness of an object as a boolean only. + /// + /// Use this if you never want to worry about the + /// Indeterminate state (which is fairly common). + /// + /// This is a convenience wrapper around the CheckStateGetter property. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual BooleanCheckStateGetterDelegate BooleanCheckStateGetter { + set { + if (value == null) + this.CheckStateGetter = null; + else + this.CheckStateGetter = delegate(Object x) { + return value(x) ? CheckState.Checked : CheckState.Unchecked; + }; + } + } + + /// + /// This delegate sets the checkedness of an object as a boolean only. It must return + /// true or false indicating if the object was checked or not. + /// + /// Use this if you never want to worry about the + /// Indeterminate state (which is fairly common). + /// + /// This is a convenience wrapper around the CheckStatePutter property. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual BooleanCheckStatePutterDelegate BooleanCheckStatePutter { + set { + if (value == null) + this.CheckStatePutter = null; + else + this.CheckStatePutter = delegate(Object x, CheckState state) { + bool isChecked = (state == CheckState.Checked); + return value(x, isChecked) ? CheckState.Checked : CheckState.Unchecked; + }; + } + } + + /// + /// Gets whether or not this listview is capable of showing groups + /// + [Browsable(false)] + public virtual bool CanShowGroups { + get { + return true; + } + } + + /// + /// Gets or sets whether ObjectListView can rely on Application.Idle events + /// being raised. + /// + /// In some host environments (e.g. when running as an extension within + /// VisualStudio and possibly Office), Application.Idle events are never raised. + /// Set this to false when Idle events will not be raised, and ObjectListView will + /// raise those events itself. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual bool CanUseApplicationIdle { + get { return this.canUseApplicationIdle; } + set { this.canUseApplicationIdle = value; } + } + private bool canUseApplicationIdle = true; + + /// + /// This delegate fetches the renderer for a particular cell. + /// + /// + /// + /// If this returns null (or is not installed), the renderer for the column will be used. + /// If the column renderer is null, then will be used. + /// + /// + /// This is called every time any cell is drawn. It must be efficient! + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual CellRendererGetterDelegate CellRendererGetter + { + get { return this.cellRendererGetter; } + set { this.cellRendererGetter = value; } + } + private CellRendererGetterDelegate cellRendererGetter; + + /// + /// This delegate is called when the list wants to show a tooltip for a particular cell. + /// The delegate should return the text to display, or null to use the default behavior + /// (which is to show the full text of truncated cell values). + /// + /// + /// Displaying the full text of truncated cell values only work for FullRowSelect listviews. + /// This is MS's behavior, not mine. Don't complain to me :) + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual CellToolTipGetterDelegate CellToolTipGetter { + get { return cellToolTipGetter; } + set { cellToolTipGetter = value; } + } + private CellToolTipGetterDelegate cellToolTipGetter; + + /// + /// The name of the property (or field) that holds whether or not a model is checked. + /// + /// + /// The property be modifiable. It must have a return type of bool (or of bool? if + /// TriStateCheckBoxes is true). + /// Setting this property replaces any CheckStateGetter or CheckStatePutter that have been installed. + /// Conversely, later setting the CheckStateGetter or CheckStatePutter properties will take precedence + /// over the behavior of this property. + /// + [Category("ObjectListView"), + Description("The name of the property or field that holds the 'checkedness' of the model"), + DefaultValue(null)] + public virtual string CheckedAspectName { + get { return checkedAspectName; } + set { + checkedAspectName = value; + if (String.IsNullOrEmpty(checkedAspectName)) { + this.checkedAspectMunger = null; + this.CheckStateGetter = null; + this.CheckStatePutter = null; + } else { + this.checkedAspectMunger = new Munger(checkedAspectName); + this.CheckStateGetter = delegate(Object modelObject) { + bool? result = this.checkedAspectMunger.GetValue(modelObject) as bool?; + if (result.HasValue) + return result.Value ? CheckState.Checked : CheckState.Unchecked; + return this.TriStateCheckBoxes ? CheckState.Indeterminate : CheckState.Unchecked; + }; + this.CheckStatePutter = delegate(Object modelObject, CheckState newValue) { + if (this.TriStateCheckBoxes && newValue == CheckState.Indeterminate) + this.checkedAspectMunger.PutValue(modelObject, null); + else + this.checkedAspectMunger.PutValue(modelObject, newValue == CheckState.Checked); + return this.CheckStateGetter(modelObject); + }; + } + } + } + private string checkedAspectName; + private Munger checkedAspectMunger; + + /// + /// This delegate will be called whenever the ObjectListView needs to know the check state + /// of the row associated with a given model object. + /// + /// + /// .NET has no support for indeterminate values, but as of v2.0, this class allows + /// indeterminate values. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual CheckStateGetterDelegate CheckStateGetter { + get { return checkStateGetter; } + set { checkStateGetter = value; } + } + private CheckStateGetterDelegate checkStateGetter; + + /// + /// This delegate will be called whenever the user tries to change the check state of a row. + /// The delegate should return the state that was actually set, which may be different + /// to the state given. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual CheckStatePutterDelegate CheckStatePutter { + get { return checkStatePutter; } + set { checkStatePutter = value; } + } + private CheckStatePutterDelegate checkStatePutter; + + /// + /// This delegate can be used to sort the table in a custom fashion. + /// + /// + /// + /// The delegate must install a ListViewItemSorter on the ObjectListView. + /// Installing the ItemSorter does the actual work of sorting the ListViewItems. + /// See ColumnComparer in the code for an example of what an ItemSorter has to do. + /// + /// + /// Do not install a CustomSorter on a VirtualObjectListView. Override the SortObjects() + /// method of the IVirtualListDataSource instead. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual SortDelegate CustomSorter { + get { return customSorter; } + set { customSorter = value; } + } + private SortDelegate customSorter; + + /// + /// This delegate is called when the list wants to show a tooltip for a particular header. + /// The delegate should return the text to display, or null to use the default behavior + /// (which is to not show any tooltip). + /// + /// + /// Installing a HeaderToolTipGetter takes precedence over any text in OLVColumn.ToolTipText. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual HeaderToolTipGetterDelegate HeaderToolTipGetter { + get { return headerToolTipGetter; } + set { headerToolTipGetter = value; } + } + private HeaderToolTipGetterDelegate headerToolTipGetter; + + /// + /// This delegate can be used to format a OLVListItem before it is added to the control. + /// + /// + /// The model object for the row can be found through the RowObject property of the OLVListItem object. + /// All subitems normally have the same style as list item, so setting the forecolor on one + /// subitem changes the forecolor of all subitems. + /// To allow subitems to have different attributes, do this: + /// myListViewItem.UseItemStyleForSubItems = false;. + /// + /// If UseAlternatingBackColors is true, the backcolor of the listitem will be calculated + /// by the control and cannot be controlled by the RowFormatter delegate. + /// In general, trying to use a RowFormatter + /// when UseAlternatingBackColors is true does not work well. + /// As it says in the summary, this is called before the item is added to the control. + /// Many properties of the OLVListItem itself are not available at that point, including: + /// Index, Selected, Focused, Bounds, Checked, DisplayIndex. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual RowFormatterDelegate RowFormatter { + get { return rowFormatter; } + set { rowFormatter = value; } + } + private RowFormatterDelegate rowFormatter; + + #endregion + + #region List commands + + /// + /// Add the given model object to this control. + /// + /// The model object to be displayed + /// See AddObjects() for more details + public virtual void AddObject(object modelObject) { + if (this.InvokeRequired) + this.Invoke((MethodInvoker)delegate() { this.AddObject(modelObject); }); + else + this.AddObjects(new object[] { modelObject }); + } + + /// + /// Add the given collection of model objects to this control. + /// + /// A collection of model objects + /// + /// The added objects will appear in their correct sort position, if sorting + /// is active (i.e. if PrimarySortColumn is not null). Otherwise, they will appear at the end of the list. + /// No check is performed to see if any of the objects are already in the ListView. + /// Null objects are silently ignored. + /// + public virtual void AddObjects(ICollection modelObjects) { + if (this.InvokeRequired) { + this.Invoke((MethodInvoker)delegate() { this.AddObjects(modelObjects); }); + return; + } + this.InsertObjects(ObjectListView.EnumerableCount(this.Objects), modelObjects); + this.Sort(this.PrimarySortColumn, this.PrimarySortOrder); + } + + /// + /// Resize the columns to the maximum of the header width and the data. + /// + public virtual void AutoResizeColumns() { + foreach (OLVColumn c in this.Columns) { + this.AutoResizeColumn(c.Index, ColumnHeaderAutoResizeStyle.HeaderSize); + } + } + + /// + /// Set up any automatically initialized column widths (columns that + /// have a width of 0 or -1 will be resized to the width of their + /// contents or header respectively). + /// + /// + /// Obviously, this will only work once. Once it runs, the columns widths will + /// be changed to something else (other than 0 or -1), so it wont do anything the + /// second time through. Use to force all columns + /// to change their size. + /// + public virtual void AutoSizeColumns() { + // If we are supposed to resize to content, but if there is no content, + // resize to the header size instead. + ColumnHeaderAutoResizeStyle resizeToContentStyle = this.GetItemCount() == 0 ? + ColumnHeaderAutoResizeStyle.HeaderSize : + ColumnHeaderAutoResizeStyle.ColumnContent; + foreach (ColumnHeader column in this.Columns) { + switch (column.Width) { + case 0: + this.AutoResizeColumn(column.Index, resizeToContentStyle); + break; + case -1: + this.AutoResizeColumn(column.Index, ColumnHeaderAutoResizeStyle.HeaderSize); + break; + } + } + } + + /// + /// Organise the view items into groups, based on the last sort column or the first column + /// if there is no last sort column + /// + public virtual void BuildGroups() { + this.BuildGroups(this.PrimarySortColumn, this.PrimarySortOrder == SortOrder.None ? SortOrder.Ascending : this.PrimarySortOrder); + } + + /// + /// Organise the view items into groups, based on the given column + /// + /// + /// + /// If the AlwaysGroupByColumn property is not null, + /// the list view items will be organised by that column, + /// and the 'column' parameter will be ignored. + /// + /// This method triggers sorting events: BeforeSorting and AfterSorting. + /// + /// The column whose values should be used for sorting. + /// + public virtual void BuildGroups(OLVColumn column, SortOrder order) { + // Sanity + if (this.GetItemCount() == 0 || this.Columns.Count == 0) + return; + + BeforeSortingEventArgs args = this.BuildBeforeSortingEventArgs(column, order); + this.OnBeforeSorting(args); + if (args.Canceled) + return; + + this.BuildGroups(args.ColumnToGroupBy, args.GroupByOrder, + args.ColumnToSort, args.SortOrder, args.SecondaryColumnToSort, args.SecondarySortOrder); + + this.OnAfterSorting(new AfterSortingEventArgs(args)); + } + + private BeforeSortingEventArgs BuildBeforeSortingEventArgs(OLVColumn column, SortOrder order) { + OLVColumn groupBy = this.AlwaysGroupByColumn ?? column ?? this.GetColumn(0); + SortOrder groupByOrder = this.AlwaysGroupBySortOrder; + if (order == SortOrder.None) { + order = this.Sorting; + if (order == SortOrder.None) + order = SortOrder.Ascending; + } + if (groupByOrder == SortOrder.None) + groupByOrder = order; + + BeforeSortingEventArgs args = new BeforeSortingEventArgs( + groupBy, groupByOrder, + column, order, + this.SecondarySortColumn ?? this.GetColumn(0), + this.SecondarySortOrder == SortOrder.None ? order : this.SecondarySortOrder); + if (column != null) + args.Canceled = !column.Sortable; + return args; + } + + /// + /// Organise the view items into groups, based on the given columns + /// + /// What column will be used for grouping + /// What ordering will be used for groups + /// The column whose values should be used for sorting. Cannot be null + /// The order in which the values from column will be sorted + /// When the values from 'column' are equal, use the values provided by this column + /// How will the secondary values be sorted + /// This method does not trigger sorting events. Use BuildGroups() to do that + public virtual void BuildGroups(OLVColumn groupByColumn, SortOrder groupByOrder, + OLVColumn column, SortOrder order, OLVColumn secondaryColumn, SortOrder secondaryOrder) { + // Sanity checks + if (groupByColumn == null) + return; + + // Getting the Count forces any internal cache of the ListView to be flushed. Without + // this, iterating over the Items will not work correctly if the ListView handle + // has not yet been created. +#pragma warning disable 168 +// ReSharper disable once UnusedVariable + int dummy = this.Items.Count; +#pragma warning restore 168 + + // Collect all the information that governs the creation of groups + GroupingParameters parms = this.CollectGroupingParameters(groupByColumn, groupByOrder, + column, order, secondaryColumn, secondaryOrder); + + // Trigger an event to let the world create groups if they want + CreateGroupsEventArgs args = new CreateGroupsEventArgs(parms); + if (parms.GroupByColumn != null) + args.Canceled = !parms.GroupByColumn.Groupable; + this.OnBeforeCreatingGroups(args); + if (args.Canceled) + return; + + // If the event didn't create them for us, use our default strategy + if (args.Groups == null) + args.Groups = this.MakeGroups(parms); + + // Give the world a chance to munge the groups before they are created + this.OnAboutToCreateGroups(args); + if (args.Canceled) + return; + + // Create the groups now + this.OLVGroups = args.Groups; + this.CreateGroups(args.Groups); + + // Tell the world that new groups have been created + this.OnAfterCreatingGroups(args); + lastGroupingParameters = args.Parameters; + } + private GroupingParameters lastGroupingParameters; + + /// + /// Collect and return all the variables that influence the creation of groups + /// + /// + protected virtual GroupingParameters CollectGroupingParameters(OLVColumn groupByColumn, SortOrder groupByOrder, + OLVColumn sortByColumn, SortOrder sortByOrder, OLVColumn secondaryColumn, SortOrder secondaryOrder) { + + // If the user tries to group by a non-groupable column, keep the current group by + // settings, but use the non-groupable column for sorting + if (!groupByColumn.Groupable && lastGroupingParameters != null) { + sortByColumn = groupByColumn; + sortByOrder = groupByOrder; + groupByColumn = lastGroupingParameters.GroupByColumn; + groupByOrder = lastGroupingParameters.GroupByOrder; + } + + string titleFormat = this.ShowItemCountOnGroups ? groupByColumn.GroupWithItemCountFormatOrDefault : null; + string titleSingularFormat = this.ShowItemCountOnGroups ? groupByColumn.GroupWithItemCountSingularFormatOrDefault : null; + GroupingParameters parms = new GroupingParameters(this, groupByColumn, groupByOrder, + sortByColumn, sortByOrder, secondaryColumn, secondaryOrder, + titleFormat, titleSingularFormat, + this.SortGroupItemsByPrimaryColumn && this.AlwaysGroupByColumn == null); + return parms; + } + + /// + /// Make a list of groups that should be shown according to the given parameters + /// + /// + /// The list of groups to be created + /// This should not change the state of the control. It is possible that the + /// groups created will not be used. They may simply be discarded. + protected virtual IList MakeGroups(GroupingParameters parms) { + + // There is a lot of overlap between this method and FastListGroupingStrategy.MakeGroups() + // Any changes made here may need to be reflected there + + // Separate the list view items into groups, using the group key as the descrimanent + NullableDictionary> map = new NullableDictionary>(); + foreach (OLVListItem olvi in parms.ListView.Items) { + object key = parms.GroupByColumn.GetGroupKey(olvi.RowObject); + if (!map.ContainsKey(key)) + map[key] = new List(); + map[key].Add(olvi); + } + + // Sort the items within each group (unless specifically turned off) + OLVColumn sortColumn = parms.SortItemsByPrimaryColumn ? parms.ListView.GetColumn(0) : parms.PrimarySort; + if (sortColumn != null && parms.PrimarySortOrder != SortOrder.None) { + IComparer itemSorter = parms.ItemComparer ?? + new ColumnComparer(sortColumn, parms.PrimarySortOrder, parms.SecondarySort, parms.SecondarySortOrder); + foreach (object key in map.Keys) { + map[key].Sort(itemSorter); + } + } + + // Make a list of the required groups + List groups = new List(); + foreach (object key in map.Keys) { + OLVGroup lvg = parms.CreateGroup(key, map[key].Count, HasCollapsibleGroups); + lvg.Items = map[key]; + if (parms.GroupByColumn.GroupFormatter != null) + parms.GroupByColumn.GroupFormatter(lvg, parms); + groups.Add(lvg); + } + + // Sort the groups + if (parms.GroupByOrder != SortOrder.None) + groups.Sort(parms.GroupComparer ?? new OLVGroupComparer(parms.GroupByOrder)); + + return groups; + } + + /// + /// Build/rebuild all the list view items in the list, preserving as much state as is possible + /// + public virtual void BuildList() { + if (this.InvokeRequired) + this.Invoke(new MethodInvoker(this.BuildList)); + else + this.BuildList(true); + } + + /// + /// Build/rebuild all the list view items in the list + /// + /// If this is true, the control will try to preserve the selection, + /// focused item, and the scroll position (see Remarks) + /// + /// + /// + /// Use this method in situations were the contents of the list is basically the same + /// as previously. + /// + /// + public virtual void BuildList(bool shouldPreserveState) { + if (this.Frozen) + return; + + Stopwatch sw = Stopwatch.StartNew(); + + this.ApplyExtendedStyles(); + this.ClearHotItem(); + int previousTopIndex = this.TopItemIndex; + Point currentScrollPosition = this.LowLevelScrollPosition; + + IList previousSelection = new ArrayList(); + Object previousFocus = null; + if (shouldPreserveState && this.objects != null) { + previousSelection = this.SelectedObjects; + OLVListItem focusedItem = this.FocusedItem as OLVListItem; + if (focusedItem != null) + previousFocus = focusedItem.RowObject; + } + + IEnumerable objectsToDisplay = this.FilteredObjects; + + this.BeginUpdate(); + try { + this.Items.Clear(); + this.ListViewItemSorter = null; + + if (objectsToDisplay != null) { + // Build a list of all our items and then display them. (Building + // a list and then doing one AddRange is about 10-15% faster than individual adds) + List itemList = new List(); // use ListViewItem to avoid co-variant conversion + foreach (object rowObject in objectsToDisplay) { + OLVListItem lvi = new OLVListItem(rowObject); + this.FillInValues(lvi, rowObject); + itemList.Add(lvi); + } + this.Items.AddRange(itemList.ToArray()); + this.Sort(); + + if (shouldPreserveState) { + this.SelectedObjects = previousSelection; + this.FocusedItem = this.ModelToItem(previousFocus); + } + } + } finally { + this.EndUpdate(); + } + + this.RefreshHotItem(); + + // We can only restore the scroll position after the EndUpdate() because + // of caching that the ListView does internally during a BeginUpdate/EndUpdate pair. + if (shouldPreserveState) { + // Restore the scroll position. TopItemIndex is best, but doesn't work + // when the control is grouped. + if (this.ShowGroups) + this.LowLevelScroll(currentScrollPosition.X, currentScrollPosition.Y); + else + this.TopItemIndex = previousTopIndex; + } + + // System.Diagnostics.Debug.WriteLine(String.Format("PERF - Building list for {2} objects took {0}ms / {1} ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks, this.GetItemCount())); + } + + /// + /// Clear any cached info this list may have been using + /// + public virtual void ClearCachedInfo() + { + // ObjectListView doesn't currently cache information but subclass do (or might) + } + + /// + /// Apply all required extended styles to our control. + /// + /// + /// + /// Whenever .NET code sets an extended style, it erases all other extended styles + /// that it doesn't use. So, we have to explicit reapply the styles that we have + /// added. + /// + /// + /// Normally, we would override CreateParms property and update + /// the ExStyle member, but ListView seems to ignore all ExStyles that + /// it doesn't already know about. Worse, when we set the LVS_EX_HEADERINALLVIEWS + /// value, bad things happen (the control crashes!). + /// + /// + protected virtual void ApplyExtendedStyles() { + const int LVS_EX_SUBITEMIMAGES = 0x00000002; + //const int LVS_EX_TRANSPARENTBKGND = 0x00400000; + const int LVS_EX_HEADERINALLVIEWS = 0x02000000; + + const int STYLE_MASK = LVS_EX_SUBITEMIMAGES | LVS_EX_HEADERINALLVIEWS; + int style = 0; + + if (this.ShowImagesOnSubItems && !this.VirtualMode) + style ^= LVS_EX_SUBITEMIMAGES; + + if (this.ShowHeaderInAllViews) + style ^= LVS_EX_HEADERINALLVIEWS; + + NativeMethods.SetExtendedStyle(this, style, STYLE_MASK); + } + + /// + /// Give the listview a reasonable size of its tiles, based on the number of lines of + /// information that each tile is going to display. + /// + public virtual void CalculateReasonableTileSize() { + if (this.Columns.Count <= 0) + return; + + List columns = this.AllColumns.FindAll(delegate(OLVColumn x) { + return (x.Index == 0) || x.IsTileViewColumn; + }); + + int imageHeight = (this.LargeImageList == null ? 16 : this.LargeImageList.ImageSize.Height); + int dataHeight = (this.Font.Height + 1) * columns.Count; + int tileWidth = (this.TileSize.Width == 0 ? 200 : this.TileSize.Width); + int tileHeight = Math.Max(this.TileSize.Height, Math.Max(imageHeight, dataHeight)); + this.TileSize = new Size(tileWidth, tileHeight); + } + + /// + /// Rebuild this list for the given view + /// + /// + public virtual void ChangeToFilteredColumns(View view) { + // Store the state + this.SuspendSelectionEvents(); + IList previousSelection = this.SelectedObjects; + int previousTopIndex = this.TopItemIndex; + + this.Freeze(); + this.Clear(); + List columns = this.GetFilteredColumns(view); + if (view == View.Details || this.ShowHeaderInAllViews) { + // Make sure all columns have a reasonable LastDisplayIndex + for (int index = 0; index < columns.Count; index++) + { + if (columns[index].LastDisplayIndex == -1) + columns[index].LastDisplayIndex = index; + } + // ListView will ignore DisplayIndex FOR ALL COLUMNS if there are any errors, + // e.g. duplicates (two columns with the same DisplayIndex) or gaps. + // LastDisplayIndex isn't guaranteed to be unique, so we just sort the columns by + // the last position they were displayed and use that to generate a sequence + // we can use for the DisplayIndex values. + List columnsInDisplayOrder = new List(columns); + columnsInDisplayOrder.Sort(delegate(OLVColumn x, OLVColumn y) { return (x.LastDisplayIndex - y.LastDisplayIndex); }); + int i = 0; + foreach (OLVColumn col in columnsInDisplayOrder) + col.DisplayIndex = i++; + } + +// ReSharper disable once CoVariantArrayConversion + this.Columns.AddRange(columns.ToArray()); + if (view == View.Details || this.ShowHeaderInAllViews) + this.ShowSortIndicator(); + this.UpdateFiltering(); + this.Unfreeze(); + + // Restore the state + this.SelectedObjects = previousSelection; + this.TopItemIndex = previousTopIndex; + this.ResumeSelectionEvents(); + } + + /// + /// Remove all items from this list + /// + /// This method can safely be called from background threads. + public virtual void ClearObjects() { + if (this.InvokeRequired) + this.Invoke(new MethodInvoker(this.ClearObjects)); + else + this.SetObjects(null); + } + + /// + /// Reset the memory of which URLs have been visited + /// + public virtual void ClearUrlVisited() { + this.visitedUrlMap = new Dictionary(); + } + + /// + /// Copy a text and html representation of the selected rows onto the clipboard. + /// + /// Be careful when using this with virtual lists. If the user has selected + /// 10,000,000 rows, this method will faithfully try to copy all of them to the clipboard. + /// From the user's point of view, your program will appear to have hung. + public virtual void CopySelectionToClipboard() { + IList selection = this.SelectedObjects; + if (selection.Count == 0) + return; + + // Use the DragSource object to create the data object, if so configured. + // This relies on the assumption that DragSource will handle the selected objects only. + // It is legal for StartDrag to return null. + object data = null; + if (this.CopySelectionOnControlCUsesDragSource && this.DragSource != null) + data = this.DragSource.StartDrag(this, MouseButtons.Left, this.ModelToItem(selection[0])); + + Clipboard.SetDataObject(data ?? new OLVDataObject(this, selection)); + } + + /// + /// Copy a text and html representation of the given objects onto the clipboard. + /// + public virtual void CopyObjectsToClipboard(IList objectsToCopy) { + if (objectsToCopy.Count == 0) + return; + + // We don't know where these objects came from, so we can't use the DragSource to create + // the data object, like we do with CopySelectionToClipboard() above. + OLVDataObject dataObject = new OLVDataObject(this, objectsToCopy); + dataObject.CreateTextFormats(); + Clipboard.SetDataObject(dataObject); + } + + /// + /// Return a html representation of the given objects + /// + public virtual string ObjectsToHtml(IList objectsToConvert) { + if (objectsToConvert.Count == 0) + return String.Empty; + + OLVExporter exporter = new OLVExporter(this, objectsToConvert); + return exporter.ExportTo(OLVExporter.ExportFormat.HTML); + } + + /// + /// Deselect all rows in the listview + /// + public virtual void DeselectAll() { + NativeMethods.DeselectAllItems(this); + } + + /// + /// Return the ListViewItem that appears immediately after the given item. + /// If the given item is null, the first item in the list will be returned. + /// Return null if the given item is the last item. + /// + /// The item that is before the item that is returned, or null + /// A ListViewItem + public virtual OLVListItem GetNextItem(OLVListItem itemToFind) { + if (this.ShowGroups) { + bool isFound = (itemToFind == null); + foreach (ListViewGroup group in this.Groups) { + foreach (OLVListItem olvi in group.Items) { + if (isFound) + return olvi; + isFound = (itemToFind == olvi); + } + } + return null; + } + if (this.GetItemCount() == 0) + return null; + if (itemToFind == null) + return this.GetItem(0); + if (itemToFind.Index == this.GetItemCount() - 1) + return null; + return this.GetItem(itemToFind.Index + 1); + } + + /// + /// Return the last item in the order they are shown to the user. + /// If the control is not grouped, the display order is the same as the + /// sorted list order. But if the list is grouped, the display order is different. + /// + /// + public virtual OLVListItem GetLastItemInDisplayOrder() { + if (!this.ShowGroups) + return this.GetItem(this.GetItemCount() - 1); + + if (this.Groups.Count > 0) { + ListViewGroup lastGroup = this.Groups[this.Groups.Count - 1]; + if (lastGroup.Items.Count > 0) + return (OLVListItem)lastGroup.Items[lastGroup.Items.Count - 1]; + } + + return null; + } + + /// + /// Return the n'th item (0-based) in the order they are shown to the user. + /// If the control is not grouped, the display order is the same as the + /// sorted list order. But if the list is grouped, the display order is different. + /// + /// + /// + public virtual OLVListItem GetNthItemInDisplayOrder(int n) { + if (!this.ShowGroups || this.Groups.Count == 0) + return this.GetItem(n); + + foreach (ListViewGroup group in this.Groups) { + if (n < group.Items.Count) + return (OLVListItem)group.Items[n]; + + n -= group.Items.Count; + } + + return null; + } + + /// + /// Return the display index of the given listviewitem index. + /// If the control is not grouped, the display order is the same as the + /// sorted list order. But if the list is grouped, the display order is different. + /// + /// + /// + public virtual int GetDisplayOrderOfItemIndex(int itemIndex) { + if (!this.ShowGroups || this.Groups.Count == 0) + return itemIndex; + + // TODO: This could be optimized + int i = 0; + foreach (ListViewGroup lvg in this.Groups) { + foreach (ListViewItem lvi in lvg.Items) { + if (lvi.Index == itemIndex) + return i; + i++; + } + } + + return -1; + } + + /// + /// Return the ListViewItem that appears immediately before the given item. + /// If the given item is null, the last item in the list will be returned. + /// Return null if the given item is the first item. + /// + /// The item that is before the item that is returned + /// A ListViewItem + public virtual OLVListItem GetPreviousItem(OLVListItem itemToFind) { + if (this.ShowGroups) { + OLVListItem previousItem = null; + foreach (ListViewGroup group in this.Groups) { + foreach (OLVListItem lvi in group.Items) { + if (lvi == itemToFind) + return previousItem; + + previousItem = lvi; + } + } + return itemToFind == null ? previousItem : null; + } + if (this.GetItemCount() == 0) + return null; + if (itemToFind == null) + return this.GetItem(this.GetItemCount() - 1); + if (itemToFind.Index == 0) + return null; + return this.GetItem(itemToFind.Index - 1); + } + + /// + /// Insert the given collection of objects before the given position + /// + /// Where to insert the objects + /// The objects to be inserted + /// + /// + /// This operation only makes sense of non-sorted, non-grouped + /// lists, since any subsequent sort/group operation will rearrange + /// the list. + /// + /// This method only works on ObjectListViews and FastObjectListViews. + /// + public virtual void InsertObjects(int index, ICollection modelObjects) { + if (this.InvokeRequired) { + this.Invoke((MethodInvoker)delegate() { + this.InsertObjects(index, modelObjects); + }); + return; + } + if (modelObjects == null) + return; + + this.BeginUpdate(); + try { + // Give the world a chance to cancel or change the added objects + ItemsAddingEventArgs args = new ItemsAddingEventArgs(modelObjects); + this.OnItemsAdding(args); + if (args.Canceled) + return; + modelObjects = args.ObjectsToAdd; + + this.TakeOwnershipOfObjects(); + ArrayList ourObjects = ObjectListView.EnumerableToArray(this.Objects, false); + + // If we are filtering the list, there is no way to efficiently + // insert the objects, so just put them into our collection and rebuild. + // Sigh -- yet another ListView anomoly. In every view except Details, an item + // inserted into the Items collection always appear at the end regardless of + // their actual insertion index. + if (this.IsFiltering || this.View != View.Details) { + index = Math.Max(0, Math.Min(index, ourObjects.Count)); + ourObjects.InsertRange(index, modelObjects); + this.BuildList(true); + } else { + this.ListViewItemSorter = null; + index = Math.Max(0, Math.Min(index, this.GetItemCount())); + int i = index; + foreach (object modelObject in modelObjects) { + if (modelObject != null) { + ourObjects.Insert(i, modelObject); + OLVListItem lvi = new OLVListItem(modelObject); + this.FillInValues(lvi, modelObject); + this.Items.Insert(i, lvi); + i++; + } + } + + for (i = index; i < this.GetItemCount(); i++) { + OLVListItem lvi = this.GetItem(i); + this.SetSubItemImages(lvi.Index, lvi); + } + + this.PostProcessRows(); + } + + // Tell the world that the list has changed + this.SubscribeNotifications(modelObjects); + this.OnItemsChanged(new ItemsChangedEventArgs()); + } finally { + this.EndUpdate(); + } + } + + /// + /// Return true if the row representing the given model is selected + /// + /// The model object to look for + /// Is the row selected + public bool IsSelected(object model) { + OLVListItem item = this.ModelToItem(model); + return item != null && item.Selected; + } + + /// + /// Has the given URL been visited? + /// + /// The string to be consider + /// Has it been visited + public virtual bool IsUrlVisited(string url) { + return this.visitedUrlMap.ContainsKey(url); + } + + /// + /// Scroll the ListView by the given deltas. + /// + /// Horizontal delta + /// Vertical delta + public void LowLevelScroll(int dx, int dy) { + NativeMethods.Scroll(this, dx, dy); + } + + /// + /// Return a point that represents the current horizontal and vertical scroll positions + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Point LowLevelScrollPosition + { + get { + return new Point(NativeMethods.GetScrollPosition(this, true), NativeMethods.GetScrollPosition(this, false)); + } + } + + /// + /// Remember that the given URL has been visited + /// + /// The url to be remembered + /// This does not cause the control be redrawn + public virtual void MarkUrlVisited(string url) { + this.visitedUrlMap[url] = true; + } + + /// + /// Move the given collection of objects to the given index. + /// + /// This operation only makes sense on non-grouped ObjectListViews. + /// + /// + public virtual void MoveObjects(int index, ICollection modelObjects) { + + // We are going to remove all the given objects from our list + // and then insert them at the given location + this.TakeOwnershipOfObjects(); + ArrayList ourObjects = ObjectListView.EnumerableToArray(this.Objects, false); + + List indicesToRemove = new List(); + foreach (object modelObject in modelObjects) { + if (modelObject != null) { + int i = this.IndexOf(modelObject); + if (i >= 0) { + indicesToRemove.Add(i); + ourObjects.Remove(modelObject); + if (i <= index) + index--; + } + } + } + + // Remove the objects in reverse order so earlier + // deletes don't change the index of later ones + indicesToRemove.Sort(); + indicesToRemove.Reverse(); + try { + this.BeginUpdate(); + foreach (int i in indicesToRemove) { + this.Items.RemoveAt(i); + } + this.InsertObjects(index, modelObjects); + } finally { + this.EndUpdate(); + } + } + + /// + /// Calculate what item is under the given point? + /// + /// + /// + /// + public new ListViewHitTestInfo HitTest(int x, int y) { + // Everything costs something. Playing with the layout of the header can cause problems + // with the hit testing. If the header shrinks, the underlying control can throw a tantrum. + try { + return base.HitTest(x, y); + } catch (ArgumentOutOfRangeException) { + return new ListViewHitTestInfo(null, null, ListViewHitTestLocations.None); + } + } + + /// + /// Perform a hit test using the Windows control's SUBITEMHITTEST message. + /// This provides information about group hits that the standard ListView.HitTest() does not. + /// + /// + /// + /// + protected OlvListViewHitTestInfo LowLevelHitTest(int x, int y) { + + // If it's not even in the control, don't bother with anything else + if (!this.ClientRectangle.Contains(x, y)) + return new OlvListViewHitTestInfo(null, null, 0, null, 0); + + // If there are no columns, also don't bother with anything else + if (this.Columns.Count == 0) + return new OlvListViewHitTestInfo(null, null, 0, null, 0); + + // Is the point over the header? + OlvListViewHitTestInfo.HeaderHitTestInfo headerHitTestInfo = this.HeaderControl.HitTest(x, y); + if (headerHitTestInfo != null) + return new OlvListViewHitTestInfo(this, headerHitTestInfo.ColumnIndex, headerHitTestInfo.IsOverCheckBox, headerHitTestInfo.OverDividerIndex); + + // Call the native hit test method, which is a little confusing. + NativeMethods.LVHITTESTINFO lParam = new NativeMethods.LVHITTESTINFO(); + lParam.pt_x = x; + lParam.pt_y = y; + int index = NativeMethods.HitTest(this, ref lParam); + + // Setup the various values we need to make our hit test structure + bool isGroupHit = (lParam.flags & (int)HitTestLocationEx.LVHT_EX_GROUP) != 0; + OLVListItem hitItem = isGroupHit || index == -1 ? null : this.GetItem(index); + OLVListSubItem subItem = (this.View == View.Details && hitItem != null) ? hitItem.GetSubItem(lParam.iSubItem) : null; + + // Figure out which group is involved in the hit test. This is a little complicated: + // If the list is virtual: + // - the returned value is list view item index + // - iGroup is the *index* of the hit group. + // If the list is not virtual: + // - iGroup is always -1. + // - if the point is over a group, the returned value is the *id* of the hit group. + // - if the point is not over a group, the returned value is list view item index. + OLVGroup group = null; + if (this.ShowGroups && this.OLVGroups != null) { + if (this.VirtualMode) { + group = lParam.iGroup >= 0 && lParam.iGroup < this.OLVGroups.Count ? this.OLVGroups[lParam.iGroup] : null; + } else { + if (isGroupHit) { + foreach (OLVGroup olvGroup in this.OLVGroups) { + if (olvGroup.GroupId == index) { + group = olvGroup; + break; + } + } + } + } + } + OlvListViewHitTestInfo olvListViewHitTest = new OlvListViewHitTestInfo(hitItem, subItem, lParam.flags, group, lParam.iSubItem); + // System.Diagnostics.Debug.WriteLine(String.Format("HitTest({0}, {1})=>{2}", x, y, olvListViewHitTest)); + return olvListViewHitTest; + } + + /// + /// What is under the given point? This takes the various parts of a cell into account, including + /// any custom parts that a custom renderer might use + /// + /// + /// + /// An information block about what is under the point + public virtual OlvListViewHitTestInfo OlvHitTest(int x, int y) { + OlvListViewHitTestInfo hti = this.LowLevelHitTest(x, y); + + // There is a bug/"feature" of the ListView concerning hit testing. + // If FullRowSelect is false and the point is over cell 0 but not on + // the text or icon, HitTest will not register a hit. We could turn + // FullRowSelect on, do the HitTest, and then turn it off again, but + // toggling FullRowSelect in that way messes up the tooltip in the + // underlying control. So we have to find another way. + // + // It's too hard to try to write the hit test from scratch. Grouping (for + // example) makes it just too complicated. So, we have to use HitTest + // but try to get around its limits. + // + // First step is to determine if the point was within column 0. + // If it was, then we only have to determine if there is an actual row + // under the point. If there is, then we know that the point is over cell 0. + // So we try a Battleship-style approach: is there a subcell to the right + // of cell 0? This will return a false negative if column 0 is the rightmost column, + // so we also check for a subcell to the left. But if only column 0 is visible, + // then that will fail too, so we check for something at the very left of the + // control. + // + // This will still fail under pathological conditions. If column 0 fills + // the whole listview and no part of the text column 0 is visible + // (because it is horizontally scrolled offscreen), then the hit test will fail. + + // Are we in the buggy context? Details view, not full row select, and + // failing to find anything + if (hti.Item == null && !this.FullRowSelect && this.View == View.Details) { + // Is the point within the column 0? If it is, maybe it should have been a hit. + // Let's test slightly to the right and then to left of column 0. Hopefully one + // of those will hit a subitem + Point sides = NativeMethods.GetScrolledColumnSides(this, 0); + if (x >= sides.X && x <= sides.Y) { + // We look for: + // - any subitem to the right of cell 0? + // - any subitem to the left of cell 0? + // - cell 0 at the left edge of the screen + hti = this.LowLevelHitTest(sides.Y + 4, y); + if (hti.Item == null) + hti = this.LowLevelHitTest(sides.X - 4, y); + if (hti.Item == null) + hti = this.LowLevelHitTest(4, y); + + if (hti.Item != null) { + // We hit something! So, the original point must have been in cell 0 + hti.ColumnIndex = 0; + hti.SubItem = hti.Item.GetSubItem(0); + hti.Location = ListViewHitTestLocations.None; + hti.HitTestLocation = HitTestLocation.InCell; + } + } + } + + if (this.OwnerDraw) + this.CalculateOwnerDrawnHitTest(hti, x, y); + else + this.CalculateStandardHitTest(hti, x, y); + + return hti; + } + + /// + /// Perform a hit test when the control is not owner drawn + /// + /// + /// + /// + protected virtual void CalculateStandardHitTest(OlvListViewHitTestInfo hti, int x, int y) { + + // Standard hit test works fine for the primary column + if (this.View != View.Details || hti.ColumnIndex == 0 || + hti.SubItem == null || hti.Column == null) + return; + + Rectangle cellBounds = hti.SubItem.Bounds; + bool hasImage = (this.GetActualImageIndex(hti.SubItem.ImageSelector) != -1); + + // Unless we say otherwise, it was an general incell hit + hti.HitTestLocation = HitTestLocation.InCell; + + // Check if the point is over where an image should be. + // If there is a checkbox or image there, tag it and exit. + Rectangle r = cellBounds; + r.Width = this.SmallImageSize.Width; + if (r.Contains(x, y)) { + if (hti.Column.CheckBoxes) { + hti.HitTestLocation = HitTestLocation.CheckBox; + return; + } + if (hasImage) { + hti.HitTestLocation = HitTestLocation.Image; + return; + } + } + + // Figure out where the text actually is and if the point is in it + // The standard HitTest assumes that any point inside a subitem is + // a hit on Text -- which is clearly not true. + Rectangle textBounds = cellBounds; + textBounds.X += 4; + if (hasImage) + textBounds.X += this.SmallImageSize.Width; + + Size proposedSize = new Size(textBounds.Width, textBounds.Height); + Size textSize = TextRenderer.MeasureText(hti.SubItem.Text, this.Font, proposedSize, TextFormatFlags.EndEllipsis | TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix); + textBounds.Width = textSize.Width; + + switch (hti.Column.TextAlign) { + case HorizontalAlignment.Center: + textBounds.X += (cellBounds.Right - cellBounds.Left - textSize.Width) / 2; + break; + case HorizontalAlignment.Right: + textBounds.X = cellBounds.Right - textSize.Width; + break; + } + if (textBounds.Contains(x, y)) { + hti.HitTestLocation = HitTestLocation.Text; + } + } + + /// + /// Perform a hit test when the control is owner drawn. This hands off responsibility + /// to the renderer. + /// + /// + /// + /// + protected virtual void CalculateOwnerDrawnHitTest(OlvListViewHitTestInfo hti, int x, int y) { + // If the click wasn't on an item, give up + if (hti.Item == null) + return; + + // If the list is showing column, but they clicked outside the columns, also give up + if (this.View == View.Details && hti.Column == null) + return; + + // Which renderer was responsible for drawing that point + IRenderer renderer = this.View == View.Details + ? this.GetCellRenderer(hti.RowObject, hti.Column) + : this.ItemRenderer; + + // We can't decide who was responsible. Give up + if (renderer == null) + return; + + // Ask the responsible renderer what is at that point + renderer.HitTest(hti, x, y); + } + + /// + /// Pause (or unpause) all animations in the list + /// + /// true to pause, false to unpause + public virtual void PauseAnimations(bool isPause) { + for (int i = 0; i < this.Columns.Count; i++) { + OLVColumn col = this.GetColumn(i); + ImageRenderer renderer = col.Renderer as ImageRenderer; + if (renderer != null) { + renderer.ListView = this; + renderer.Paused = isPause; + } + } + } + + /// + /// Rebuild the columns based upon its current view and column visibility settings + /// + public virtual void RebuildColumns() { + this.ChangeToFilteredColumns(this.View); + } + + /// + /// Remove the given model object from the ListView + /// + /// The model to be removed + /// See RemoveObjects() for more details + /// This method is thread-safe. + /// + public virtual void RemoveObject(object modelObject) { + if (this.InvokeRequired) + this.Invoke((MethodInvoker)delegate() { this.RemoveObject(modelObject); }); + else + this.RemoveObjects(new object[] { modelObject }); + } + + /// + /// Remove all of the given objects from the control. + /// + /// Collection of objects to be removed + /// + /// Nulls and model objects that are not in the ListView are silently ignored. + /// This method is thread-safe. + /// + public virtual void RemoveObjects(ICollection modelObjects) { + if (this.InvokeRequired) { + this.Invoke((MethodInvoker)delegate() { this.RemoveObjects(modelObjects); }); + return; + } + if (modelObjects == null) + return; + + this.BeginUpdate(); + try { + // Give the world a chance to cancel or change the added objects + ItemsRemovingEventArgs args = new ItemsRemovingEventArgs(modelObjects); + this.OnItemsRemoving(args); + if (args.Canceled) + return; + modelObjects = args.ObjectsToRemove; + + this.TakeOwnershipOfObjects(); + ArrayList ourObjects = ObjectListView.EnumerableToArray(this.Objects, false); + foreach (object modelObject in modelObjects) { + if (modelObject != null) { +// ReSharper disable PossibleMultipleEnumeration + int i = ourObjects.IndexOf(modelObject); + if (i >= 0) + ourObjects.RemoveAt(i); +// ReSharper restore PossibleMultipleEnumeration + i = this.IndexOf(modelObject); + if (i >= 0) + this.Items.RemoveAt(i); + } + } + this.PostProcessRows(); + + // Tell the world that the list has changed + this.UnsubscribeNotifications(modelObjects); + this.OnItemsChanged(new ItemsChangedEventArgs()); + } finally { + this.EndUpdate(); + } + } + + /// + /// Select all rows in the listview + /// + public virtual void SelectAll() { + NativeMethods.SelectAllItems(this); + } + + /// + /// Set the given image to be fixed in the bottom right of the list view. + /// This image will not scroll when the list view scrolls. + /// + /// + /// + /// This method uses ListView's native ability to display a background image. + /// It has a few limitations: + /// + /// + /// It doesn't work well with owner drawn mode. In owner drawn mode, each cell draws itself, + /// including its background, which covers the background image. + /// It doesn't look very good when grid lines are enabled, since the grid lines are drawn over the image. + /// It does not work at all on XP. + /// Obviously, it doesn't look good when alternate row background colors are enabled. + /// + /// + /// If you can live with these limitations, native watermarks are quite neat. They are true backgrounds, not + /// translucent overlays like the OverlayImage uses. They also have the decided advantage over overlays in that + /// they work correctly even in MDI applications. + /// + /// Setting this clears any background image. + /// + /// The image to be drawn. If null, any existing image will be removed. + public void SetNativeBackgroundWatermark(Image image) { + NativeMethods.SetBackgroundImage(this, image, true, false, 0, 0); + } + + /// + /// Set the given image to be background of the ListView so that it appears at the given + /// percentage offsets within the list. + /// + /// + /// This has the same limitations as described in . Make sure those limitations + /// are understood before using the method. + /// This is very similar to setting the property of the standard .NET ListView, except that the standard + /// BackgroundImage does not handle images with transparent areas properly -- it renders transparent areas as black. This + /// method does not have that problem. + /// Setting this clears any background watermark. + /// + /// The image to be drawn. If null, any existing image will be removed. + /// The horizontal percentage where the image will be placed. 0 is absolute left, 100 is absolute right. + /// The vertical percentage where the image will be placed. + public void SetNativeBackgroundImage(Image image, int xOffset, int yOffset) { + NativeMethods.SetBackgroundImage(this, image, false, false, xOffset, yOffset); + } + + /// + /// Set the given image to be the tiled background of the ListView. + /// + /// + /// This has the same limitations as described in and . + /// Make sure those limitations + /// are understood before using the method. + /// + /// The image to be drawn. If null, any existing image will be removed. + public void SetNativeBackgroundTiledImage(Image image) { + NativeMethods.SetBackgroundImage(this, image, false, true, 0, 0); + } + + /// + /// Set the collection of objects that will be shown in this list view. + /// + /// This method can safely be called from background threads. + /// The list is updated immediately + /// The objects to be displayed + public virtual void SetObjects(IEnumerable collection) { + this.SetObjects(collection, false); + } + + /// + /// Set the collection of objects that will be shown in this list view. + /// + /// This method can safely be called from background threads. + /// The list is updated immediately + /// The objects to be displayed + /// Should the state of the list be preserved as far as is possible. + public virtual void SetObjects(IEnumerable collection, bool preserveState) { + if (this.InvokeRequired) { + this.Invoke((MethodInvoker)delegate { this.SetObjects(collection, preserveState); }); + return; + } + + // Give the world a chance to cancel or change the assigned collection + ItemsChangingEventArgs args = new ItemsChangingEventArgs(this.objects, collection); + this.OnItemsChanging(args); + if (args.Canceled) + return; + collection = args.NewObjects; + + // If we own the current list and they change to another list, we don't own it any more + if (this.isOwnerOfObjects && !ReferenceEquals(this.objects, collection)) + this.isOwnerOfObjects = false; + this.objects = collection; + this.BuildList(preserveState); + + // Tell the world that the list has changed + this.UpdateNotificationSubscriptions(this.objects); + this.OnItemsChanged(new ItemsChangedEventArgs()); + } + + /// + /// Update the given model object into the ListView. The model will be added if it doesn't already exist. + /// + /// The model to be updated + /// + /// + /// See for more details + /// + /// This method is thread-safe. + /// This method will cause the list to be resorted. + /// This method only works on ObjectListViews and FastObjectListViews. + /// + public virtual void UpdateObject(object modelObject) { + if (this.InvokeRequired) + this.Invoke((MethodInvoker)delegate() { this.UpdateObject(modelObject); }); + else + this.UpdateObjects(new object[] { modelObject }); + } + + /// + /// Update the pre-existing models that are equal to the given objects. If any of the model doesn't + /// already exist in the control, they will be added. + /// + /// Collection of objects to be updated/added + /// + /// This method will cause the list to be resorted. + /// Nulls are silently ignored. + /// This method is thread-safe. + /// This method only works on ObjectListViews and FastObjectListViews. + /// + public virtual void UpdateObjects(ICollection modelObjects) { + if (this.InvokeRequired) { + this.Invoke((MethodInvoker)delegate() { this.UpdateObjects(modelObjects); }); + return; + } + if (modelObjects == null || modelObjects.Count == 0) + return; + + this.BeginUpdate(); + try { + this.UnsubscribeNotifications(modelObjects); + + ArrayList objectsToAdd = new ArrayList(); + + this.TakeOwnershipOfObjects(); + ArrayList ourObjects = ObjectListView.EnumerableToArray(this.Objects, false); + foreach (object modelObject in modelObjects) { + if (modelObject != null) { + int i = ourObjects.IndexOf(modelObject); + if (i < 0) + objectsToAdd.Add(modelObject); + else { + ourObjects[i] = modelObject; + OLVListItem olvi = this.ModelToItem(modelObject); + if (olvi != null) { + olvi.RowObject = modelObject; + this.RefreshItem(olvi); + } + } + } + } + this.PostProcessRows(); + + this.AddObjects(objectsToAdd); + + // Tell the world that the list has changed + this.SubscribeNotifications(modelObjects); + this.OnItemsChanged(new ItemsChangedEventArgs()); + } + finally { + this.EndUpdate(); + } + } + + /// + /// Change any subscriptions to INotifyPropertyChanged events on our current + /// model objects so that we no longer listen for events on the old models + /// and do listen for events on the given collection. + /// + /// This does nothing if UseNotifyPropertyChanged is false. + /// + protected virtual void UpdateNotificationSubscriptions(IEnumerable collection) { + if (!this.UseNotifyPropertyChanged) + return; + + // We could calculate a symmetric difference between the old models and the new models + // except that we don't have the previous models at this point. + + this.UnsubscribeNotifications(null); + this.SubscribeNotifications(collection ?? this.Objects); + } + + /// + /// Gets or sets whether or not ObjectListView should subscribe to INotifyPropertyChanged + /// events on the model objects that it is given. + /// + /// + /// + /// This should be set before calling SetObjects(). If you set this to false, + /// ObjectListView will unsubscribe to all current model objects. + /// + /// If you set this to true on a virtual list, the ObjectListView will + /// walk all the objects in the list trying to subscribe to change notifications. + /// If you have 10,000,000 items in your virtual list, this may take some time. + /// + [Category("ObjectListView"), + Description("Should ObjectListView listen for property changed events on the model objects?"), + DefaultValue(false)] + public bool UseNotifyPropertyChanged { + get { return this.useNotifyPropertyChanged; } + set { + if (this.useNotifyPropertyChanged == value) + return; + this.useNotifyPropertyChanged = value; + if (value) + this.SubscribeNotifications(this.Objects); + else + this.UnsubscribeNotifications(null); + } + } + private bool useNotifyPropertyChanged; + + /// + /// Subscribe to INotifyPropertyChanges on the given collection of objects. + /// + /// + protected void SubscribeNotifications(IEnumerable models) { + if (!this.UseNotifyPropertyChanged || models == null) + return; + foreach (object x in models) { + INotifyPropertyChanged notifier = x as INotifyPropertyChanged; + if (notifier != null && !subscribedModels.ContainsKey(notifier)) { + notifier.PropertyChanged += HandleModelOnPropertyChanged; + subscribedModels[notifier] = notifier; + } + } + } + + /// + /// Unsubscribe from INotifyPropertyChanges on the given collection of objects. + /// If the given collection is null, unsubscribe from all current subscriptions + /// + /// + protected void UnsubscribeNotifications(IEnumerable models) { + if (models == null) { + foreach (INotifyPropertyChanged notifier in this.subscribedModels.Keys) { + notifier.PropertyChanged -= HandleModelOnPropertyChanged; + } + subscribedModels = new Hashtable(); + } else { + foreach (object x in models) { + INotifyPropertyChanged notifier = x as INotifyPropertyChanged; + if (notifier != null) { + notifier.PropertyChanged -= HandleModelOnPropertyChanged; + subscribedModels.Remove(notifier); + } + } + } + } + + private void HandleModelOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) { + // System.Diagnostics.Debug.WriteLine(String.Format("PropertyChanged: '{0}' on '{1}", propertyChangedEventArgs.PropertyName, sender)); + this.RefreshObject(sender); + } + + private Hashtable subscribedModels = new Hashtable(); + + #endregion + + #region Save/Restore State + + /// + /// Return a byte array that represents the current state of the ObjectListView, such + /// that the state can be restored by RestoreState() + /// + /// + /// The state of an ObjectListView includes the attributes that the user can modify: + /// + /// current view (i.e. Details, Tile, Large Icon...) + /// sort column and direction + /// column order + /// column widths + /// column visibility + /// + /// + /// + /// It does not include selection or the scroll position. + /// + /// + /// A byte array representing the state of the ObjectListView + public virtual byte[] SaveState() { + ObjectListViewState olvState = new ObjectListViewState(); + olvState.VersionNumber = 1; + olvState.NumberOfColumns = this.AllColumns.Count; + olvState.CurrentView = this.View; + + // If we have a sort column, it is possible that it is not currently being shown, in which + // case, it's Index will be -1. So we calculate its index directly. Technically, the sort + // column does not even have to a member of AllColumns, in which case IndexOf will return -1, + // which is works fine since we have no way of restoring such a column anyway. + if (this.PrimarySortColumn != null) + olvState.SortColumn = this.AllColumns.IndexOf(this.PrimarySortColumn); + olvState.LastSortOrder = this.PrimarySortOrder; + olvState.IsShowingGroups = this.ShowGroups; + + if (this.AllColumns.Count > 0 && this.AllColumns[0].LastDisplayIndex == -1) + this.RememberDisplayIndicies(); + + foreach (OLVColumn column in this.AllColumns) { + olvState.ColumnIsVisible.Add(column.IsVisible); + olvState.ColumnDisplayIndicies.Add(column.LastDisplayIndex); + olvState.ColumnWidths.Add(column.Width); + } + + // Now that we have stored our state, convert it to a byte array + using (MemoryStream ms = new MemoryStream()) { + BinaryFormatter serializer = new BinaryFormatter(); + serializer.AssemblyFormat = FormatterAssemblyStyle.Simple; + serializer.Serialize(ms, olvState); + return ms.ToArray(); + } + } + + /// + /// Restore the state of the control from the given string, which must have been + /// produced by SaveState() + /// + /// A byte array returned from SaveState() + /// Returns true if the state was restored + public virtual bool RestoreState(byte[] state) { + using (MemoryStream ms = new MemoryStream(state)) { + BinaryFormatter deserializer = new BinaryFormatter(); + ObjectListViewState olvState; + try { + olvState = deserializer.Deserialize(ms) as ObjectListViewState; + } catch (System.Runtime.Serialization.SerializationException) { + return false; + } + // The number of columns has changed. We have no way to match old + // columns to the new ones, so we just give up. + if (olvState == null || olvState.NumberOfColumns != this.AllColumns.Count) + return false; + if (olvState.SortColumn == -1) { + this.PrimarySortColumn = null; + this.PrimarySortOrder = SortOrder.None; + } else { + this.PrimarySortColumn = this.AllColumns[olvState.SortColumn]; + this.PrimarySortOrder = olvState.LastSortOrder; + } + for (int i = 0; i < olvState.NumberOfColumns; i++) { + OLVColumn column = this.AllColumns[i]; + column.Width = (int)olvState.ColumnWidths[i]; + column.IsVisible = (bool)olvState.ColumnIsVisible[i]; + column.LastDisplayIndex = (int)olvState.ColumnDisplayIndicies[i]; + } +// ReSharper disable RedundantCheckBeforeAssignment + if (olvState.IsShowingGroups != this.ShowGroups) +// ReSharper restore RedundantCheckBeforeAssignment + this.ShowGroups = olvState.IsShowingGroups; + if (this.View == olvState.CurrentView) + this.RebuildColumns(); + else + this.View = olvState.CurrentView; + } + + return true; + } + + /// + /// Instances of this class are used to store the state of an ObjectListView. + /// + [Serializable] + internal class ObjectListViewState + { +// ReSharper disable NotAccessedField.Global + public int VersionNumber = 1; +// ReSharper restore NotAccessedField.Global + public int NumberOfColumns = 1; + public View CurrentView; + public int SortColumn = -1; + public bool IsShowingGroups; + public SortOrder LastSortOrder = SortOrder.None; +// ReSharper disable FieldCanBeMadeReadOnly.Global + public ArrayList ColumnIsVisible = new ArrayList(); + public ArrayList ColumnDisplayIndicies = new ArrayList(); + public ArrayList ColumnWidths = new ArrayList(); +// ReSharper restore FieldCanBeMadeReadOnly.Global + } + + #endregion + + #region Event handlers + + /// + /// The application is idle. Trigger a SelectionChanged event. + /// + /// + /// + protected virtual void HandleApplicationIdle(object sender, EventArgs e) { + // Remove the handler before triggering the event + Application.Idle -= new EventHandler(HandleApplicationIdle); + this.hasIdleHandler = false; + + this.OnSelectionChanged(new EventArgs()); + } + + /// + /// The application is idle. Handle the column resizing event. + /// + /// + /// + protected virtual void HandleApplicationIdleResizeColumns(object sender, EventArgs e) { + // Remove the handler before triggering the event + Application.Idle -= new EventHandler(this.HandleApplicationIdleResizeColumns); + this.hasResizeColumnsHandler = false; + + this.ResizeFreeSpaceFillingColumns(); + } + + /// + /// Handle the BeginScroll listview notification + /// + /// + /// True if the event was completely handled + protected virtual bool HandleBeginScroll(ref Message m) { + //System.Diagnostics.Debug.WriteLine("LVN_BEGINSCROLL"); + + NativeMethods.NMLVSCROLL nmlvscroll = (NativeMethods.NMLVSCROLL)m.GetLParam(typeof(NativeMethods.NMLVSCROLL)); + if (nmlvscroll.dx != 0) { + int scrollPositionH = NativeMethods.GetScrollPosition(this, true); + ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll, scrollPositionH - nmlvscroll.dx, scrollPositionH, ScrollOrientation.HorizontalScroll); + this.OnScroll(args); + + // Force any empty list msg to redraw when the list is scrolled horizontally + if (this.GetItemCount() == 0) + this.Invalidate(); + } + if (nmlvscroll.dy != 0) { + int scrollPositionV = NativeMethods.GetScrollPosition(this, false); + ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll, scrollPositionV - nmlvscroll.dy, scrollPositionV, ScrollOrientation.VerticalScroll); + this.OnScroll(args); + } + + return false; + } + + /// + /// Handle the EndScroll listview notification + /// + /// + /// True if the event was completely handled + protected virtual bool HandleEndScroll(ref Message m) { + //System.Diagnostics.Debug.WriteLine("LVN_BEGINSCROLL"); + + // There is a bug in ListView under XP that causes the gridlines to be incorrectly scrolled + // when the left button is clicked to scroll. This is supposedly documented at + // KB 813791, but I couldn't find it anywhere. You can follow this thread to see the discussion + // http://www.ureader.com/msg/1484143.aspx + + if (!ObjectListView.IsVistaOrLater && ObjectListView.IsLeftMouseDown && this.GridLines) { + this.Invalidate(); + this.Update(); + } + + return false; + } + + /// + /// Handle the LinkClick listview notification + /// + /// + /// True if the event was completely handled + protected virtual bool HandleLinkClick(ref Message m) { + //System.Diagnostics.Debug.WriteLine("HandleLinkClick"); + + NativeMethods.NMLVLINK nmlvlink = (NativeMethods.NMLVLINK)m.GetLParam(typeof(NativeMethods.NMLVLINK)); + + // Find the group that was clicked and trigger an event + foreach (OLVGroup x in this.OLVGroups) { + if (x.GroupId == nmlvlink.iSubItem) { + this.OnGroupTaskClicked(new GroupTaskClickedEventArgs(x)); + return true; + } + } + + return false; + } + + /// + /// The cell tooltip control wants information about the tool tip that it should show. + /// + /// + /// + protected virtual void HandleCellToolTipShowing(object sender, ToolTipShowingEventArgs e) { + this.BuildCellEvent(e, this.PointToClient(Cursor.Position)); + if (e.Item != null) { + e.Text = this.GetCellToolTip(e.ColumnIndex, e.RowIndex); + this.OnCellToolTip(e); + } + } + + /// + /// Allow the HeaderControl to call back into HandleHeaderToolTipShowing without making that method public + /// + /// + /// + internal void HeaderToolTipShowingCallback(object sender, ToolTipShowingEventArgs e) { + this.HandleHeaderToolTipShowing(sender, e); + } + + /// + /// The header tooltip control wants information about the tool tip that it should show. + /// + /// + /// + protected virtual void HandleHeaderToolTipShowing(object sender, ToolTipShowingEventArgs e) { + e.ColumnIndex = this.HeaderControl.ColumnIndexUnderCursor; + if (e.ColumnIndex < 0) + return; + + e.RowIndex = -1; + e.Model = null; + e.Column = this.GetColumn(e.ColumnIndex); + e.Text = this.GetHeaderToolTip(e.ColumnIndex); + e.ListView = this; + this.OnHeaderToolTip(e); + } + + /// + /// Event handler for the column click event + /// + protected virtual void HandleColumnClick(object sender, ColumnClickEventArgs e) { + if (!this.PossibleFinishCellEditing()) + return; + + // Toggle the sorting direction on successive clicks on the same column + if (this.PrimarySortColumn != null && e.Column == this.PrimarySortColumn.Index) + this.PrimarySortOrder = (this.PrimarySortOrder == SortOrder.Descending ? SortOrder.Ascending : SortOrder.Descending); + else + this.PrimarySortOrder = SortOrder.Ascending; + + this.BeginUpdate(); + try { + this.Sort(e.Column); + } finally { + this.EndUpdate(); + } + } + + #endregion + + #region Low level Windows Message handling + + /// + /// Override the basic message pump for this control + /// + /// + protected override void WndProc(ref Message m) + { + + // System.Diagnostics.Debug.WriteLine(m.Msg); + switch (m.Msg) { + case 2: // WM_DESTROY + if (!this.HandleDestroy(ref m)) + base.WndProc(ref m); + break; + //case 0x14: // WM_ERASEBKGND + // Can't do anything here since, when the control is double buffered, anything + // done here is immediately over-drawn + // break; + case 0x0F: // WM_PAINT + if (!this.HandlePaint(ref m)) + base.WndProc(ref m); + break; + case 0x46: // WM_WINDOWPOSCHANGING + if (this.PossibleFinishCellEditing() && !this.HandleWindowPosChanging(ref m)) + base.WndProc(ref m); + break; + case 0x4E: // WM_NOTIFY + if (!this.HandleNotify(ref m)) + base.WndProc(ref m); + break; + case 0x0100: // WM_KEY_DOWN + if (!this.HandleKeyDown(ref m)) + base.WndProc(ref m); + break; + case 0x0102: // WM_CHAR + if (!this.HandleChar(ref m)) + base.WndProc(ref m); + break; + case 0x0200: // WM_MOUSEMOVE + if (!this.HandleMouseMove(ref m)) + base.WndProc(ref m); + break; + case 0x0201: // WM_LBUTTONDOWN + // System.Diagnostics.Debug.WriteLine("WM_LBUTTONDOWN"); + if (this.PossibleFinishCellEditing() && !this.HandleLButtonDown(ref m)) + base.WndProc(ref m); + break; + case 0x202: // WM_LBUTTONUP + // System.Diagnostics.Debug.WriteLine("WM_LBUTTONUP"); + if (this.PossibleFinishCellEditing() && !this.HandleLButtonUp(ref m)) + base.WndProc(ref m); + break; + case 0x0203: // WM_LBUTTONDBLCLK + if (this.PossibleFinishCellEditing() && !this.HandleLButtonDoubleClick(ref m)) + base.WndProc(ref m); + break; + case 0x0204: // WM_RBUTTONDOWN + // System.Diagnostics.Debug.WriteLine("WM_RBUTTONDOWN"); + if (this.PossibleFinishCellEditing() && !this.HandleRButtonDown(ref m)) + base.WndProc(ref m); + break; + case 0x0205: // WM_RBUTTONUP + // System.Diagnostics.Debug.WriteLine("WM_RBUTTONUP"); + base.WndProc(ref m); + break; + case 0x0206: // WM_RBUTTONDBLCLK + if (this.PossibleFinishCellEditing() && !this.HandleRButtonDoubleClick(ref m)) + base.WndProc(ref m); + break; + case 0x204E: // WM_REFLECT_NOTIFY + if (!this.HandleReflectNotify(ref m)) + base.WndProc(ref m); + break; + case 0x114: // WM_HSCROLL: + case 0x115: // WM_VSCROLL: + //System.Diagnostics.Debug.WriteLine("WM_VSCROLL"); + if (this.PossibleFinishCellEditing()) + base.WndProc(ref m); + break; + case 0x20A: // WM_MOUSEWHEEL: + case 0x20E: // WM_MOUSEHWHEEL: + if (this.AllowCellEditorsToProcessMouseWheel && this.IsCellEditing) + break; + if (this.PossibleFinishCellEditing()) + base.WndProc(ref m); + break; + case 0x7B: // WM_CONTEXTMENU + if (!this.HandleContextMenu(ref m)) + base.WndProc(ref m); + break; + case 0x1000 + 18: // LVM_HITTEST: + //System.Diagnostics.Debug.WriteLine("LVM_HITTEST"); + if (this.skipNextHitTest) { + //System.Diagnostics.Debug.WriteLine("SKIPPING LVM_HITTEST"); + this.skipNextHitTest = false; + } else { + base.WndProc(ref m); + } + break; + default: + base.WndProc(ref m); + break; + } + } + + /// + /// Handle the search for item m if possible. + /// + /// The m to be processed + /// bool to indicate if the msg has been handled + protected virtual bool HandleChar(ref Message m) { + + // Trigger a normal KeyPress event, which listeners can handle if they want. + // Handling the event stops ObjectListView's fancy search-by-typing. + if (this.ProcessKeyEventArgs(ref m)) + return true; + + const int MILLISECONDS_BETWEEN_KEYPRESSES = 1000; + + // What character did the user type and was it part of a longer string? + char character = (char)m.WParam.ToInt32(); //TODO: Will this work on 64 bit or MBCS? + if (character == (char)Keys.Back) { + // Backspace forces the next key to be considered the start of a new search + this.timeLastCharEvent = 0; + return true; + } + + if (System.Environment.TickCount < (this.timeLastCharEvent + MILLISECONDS_BETWEEN_KEYPRESSES)) + this.lastSearchString += character; + else + this.lastSearchString = character.ToString(CultureInfo.InvariantCulture); + + // If this control is showing checkboxes, we want to ignore single space presses, + // since they are used to toggle the selected checkboxes. + if (this.CheckBoxes && this.lastSearchString == " ") { + this.timeLastCharEvent = 0; + return true; + } + + // Where should the search start? + int start = 0; + ListViewItem focused = this.FocusedItem; + if (focused != null) { + start = this.GetDisplayOrderOfItemIndex(focused.Index); + + // If the user presses a single key, we search from after the focused item, + // being careful not to march past the end of the list + if (this.lastSearchString.Length == 1) { + start += 1; + if (start == this.GetItemCount()) + start = 0; + } + } + + // Give the world a chance to fiddle with or completely avoid the searching process + BeforeSearchingEventArgs args = new BeforeSearchingEventArgs(this.lastSearchString, start); + this.OnBeforeSearching(args); + if (args.Canceled) + return true; + + // The parameters of the search may have been changed + string searchString = args.StringToFind; + start = args.StartSearchFrom; + + // Do the actual search + int found = this.FindMatchingRow(searchString, start, SearchDirectionHint.Down); + if (found >= 0) { + // Select and focus on the found item + this.BeginUpdate(); + try { + this.SelectedIndices.Clear(); + OLVListItem lvi = this.GetNthItemInDisplayOrder(found); + if (lvi != null) { + if (lvi.Enabled) + lvi.Selected = true; + lvi.Focused = true; + this.EnsureVisible(lvi.Index); + } + } finally { + this.EndUpdate(); + } + } + + // Tell the world that a search has occurred + AfterSearchingEventArgs args2 = new AfterSearchingEventArgs(searchString, found); + this.OnAfterSearching(args2); + if (!args2.Handled) { + if (found < 0) + System.Media.SystemSounds.Beep.Play(); + } + + // When did this event occur? + this.timeLastCharEvent = System.Environment.TickCount; + return true; + } + private int timeLastCharEvent; + private string lastSearchString; + + /// + /// The user wants to see the context menu. + /// + /// The windows m + /// A bool indicating if this m has been handled + /// + /// We want to ignore context menu requests that are triggered by right clicks on the header + /// + protected virtual bool HandleContextMenu(ref Message m) { + // Don't try to handle context menu commands at design time. + if (this.DesignMode) + return false; + + // If the context menu command was generated by the keyboard, LParam will be -1. + // We don't want to process these. + if (m.LParam == this.minusOne) + return false; + + // If the context menu came from somewhere other than the header control, + // we also don't want to ignore it + if (m.WParam != this.HeaderControl.Handle) + return false; + + // OK. Looks like a right click in the header + if (!this.PossibleFinishCellEditing()) + return true; + + int columnIndex = this.HeaderControl.ColumnIndexUnderCursor; + return this.HandleHeaderRightClick(columnIndex); + } + readonly IntPtr minusOne = new IntPtr(-1); + + /// + /// Handle the Custom draw series of notifications + /// + /// The message + /// True if the message has been handled + protected virtual bool HandleCustomDraw(ref Message m) { + const int CDDS_PREPAINT = 1; + const int CDDS_POSTPAINT = 2; + const int CDDS_PREERASE = 3; + const int CDDS_POSTERASE = 4; + //const int CDRF_NEWFONT = 2; + //const int CDRF_SKIPDEFAULT = 4; + const int CDDS_ITEM = 0x00010000; + const int CDDS_SUBITEM = 0x00020000; + const int CDDS_ITEMPREPAINT = (CDDS_ITEM | CDDS_PREPAINT); + const int CDDS_ITEMPOSTPAINT = (CDDS_ITEM | CDDS_POSTPAINT); + const int CDDS_ITEMPREERASE = (CDDS_ITEM | CDDS_PREERASE); + const int CDDS_ITEMPOSTERASE = (CDDS_ITEM | CDDS_POSTERASE); + const int CDDS_SUBITEMPREPAINT = (CDDS_SUBITEM | CDDS_ITEMPREPAINT); + const int CDDS_SUBITEMPOSTPAINT = (CDDS_SUBITEM | CDDS_ITEMPOSTPAINT); + const int CDRF_NOTIFYPOSTPAINT = 0x10; + //const int CDRF_NOTIFYITEMDRAW = 0x20; + //const int CDRF_NOTIFYSUBITEMDRAW = 0x20; // same value as above! + const int CDRF_NOTIFYPOSTERASE = 0x40; + + // There is a bug in owner drawn virtual lists which causes lots of custom draw messages + // to be sent to the control *outside* of a WmPaint event. AFAIK, these custom draw events + // are spurious and only serve to make the control flicker annoyingly. + // So, we ignore messages that are outside of a paint event. + if (!this.isInWmPaintEvent) + return true; + + // One more complication! Sometimes with owner drawn virtual lists, the act of drawing + // the overlays triggers a second attempt to paint the control -- which makes an annoying + // flicker. So, we only do the custom drawing once per WmPaint event. + if (!this.shouldDoCustomDrawing) + return true; + + NativeMethods.NMLVCUSTOMDRAW nmcustomdraw = (NativeMethods.NMLVCUSTOMDRAW)m.GetLParam(typeof(NativeMethods.NMLVCUSTOMDRAW)); + //System.Diagnostics.Debug.WriteLine(String.Format("cd: {0:x}, {1}, {2}", nmcustomdraw.nmcd.dwDrawStage, nmcustomdraw.dwItemType, nmcustomdraw.nmcd.dwItemSpec)); + + // Ignore drawing of group items + if (nmcustomdraw.dwItemType == 1) { + // This is the basis of an idea about how to owner draw group headers + + //nmcustomdraw.clrText = ColorTranslator.ToWin32(Color.DeepPink); + //nmcustomdraw.clrFace = ColorTranslator.ToWin32(Color.DeepPink); + //nmcustomdraw.clrTextBk = ColorTranslator.ToWin32(Color.DeepPink); + //Marshal.StructureToPtr(nmcustomdraw, m.LParam, false); + //using (Graphics g = Graphics.FromHdc(nmcustomdraw.nmcd.hdc)) { + // g.DrawRectangle(Pens.Red, Rectangle.FromLTRB(nmcustomdraw.rcText.left, nmcustomdraw.rcText.top, nmcustomdraw.rcText.right, nmcustomdraw.rcText.bottom)); + //} + //m.Result = (IntPtr)((int)m.Result | CDRF_SKIPDEFAULT); + return true; + } + + switch (nmcustomdraw.nmcd.dwDrawStage) { + case CDDS_PREPAINT: + //System.Diagnostics.Debug.WriteLine("CDDS_PREPAINT"); + // Remember which items were drawn during this paint cycle + if (this.prePaintLevel == 0) + this.drawnItems = new List(); + + // If there are any items, we have to wait until at least one has been painted + // before we draw the overlays. If there aren't any items, there will never be any + // item paint events, so we can draw the overlays whenever + this.isAfterItemPaint = (this.GetItemCount() == 0); + this.prePaintLevel++; + base.WndProc(ref m); + + // Make sure that we get postpaint notifications + m.Result = (IntPtr)((int)m.Result | CDRF_NOTIFYPOSTPAINT | CDRF_NOTIFYPOSTERASE); + return true; + + case CDDS_POSTPAINT: + //System.Diagnostics.Debug.WriteLine("CDDS_POSTPAINT"); + this.prePaintLevel--; + + // When in group view, we have two problems. On XP, the control sends + // a whole heap of PREPAINT/POSTPAINT messages before drawing any items. + // We have to wait until after the first item paint before we draw overlays. + // On Vista, we have a different problem. On Vista, the control nests calls + // to PREPAINT and POSTPAINT. We only want to draw overlays on the outermost + // POSTPAINT. + if (this.prePaintLevel == 0 && (this.isMarqueSelecting || this.isAfterItemPaint)) { + this.shouldDoCustomDrawing = false; + + // Draw our overlays after everything has been drawn + using (Graphics g = Graphics.FromHdc(nmcustomdraw.nmcd.hdc)) { + this.DrawAllDecorations(g, this.drawnItems); + } + } + break; + + case CDDS_ITEMPREPAINT: + //System.Diagnostics.Debug.WriteLine("CDDS_ITEMPREPAINT"); + + // When in group view on XP, the control send a whole heap of PREPAINT/POSTPAINT + // messages before drawing any items. + // We have to wait until after the first item paint before we draw overlays + this.isAfterItemPaint = true; + + // This scheme of catching custom draw msgs works fine, except + // for Tile view. Something in .NET's handling of Tile view causes lots + // of invalidates and erases. So, we just ignore completely + // .NET's handling of Tile view and let the underlying control + // do its stuff. Strangely, if the Tile view is + // completely owner drawn, those erasures don't happen. + if (this.View == View.Tile) { + if (this.OwnerDraw && this.ItemRenderer != null) + base.WndProc(ref m); + } else { + base.WndProc(ref m); + } + + m.Result = (IntPtr)((int)m.Result | CDRF_NOTIFYPOSTPAINT | CDRF_NOTIFYPOSTERASE); + return true; + + case CDDS_ITEMPOSTPAINT: + //System.Diagnostics.Debug.WriteLine("CDDS_ITEMPOSTPAINT"); + // Remember which items have been drawn so we can draw any decorations for them + // once all other painting is finished + if (this.Columns.Count > 0) { + OLVListItem olvi = this.GetItem((int)nmcustomdraw.nmcd.dwItemSpec); + if (olvi != null) + this.drawnItems.Add(olvi); + } + break; + + case CDDS_SUBITEMPREPAINT: + //System.Diagnostics.Debug.WriteLine(String.Format("CDDS_SUBITEMPREPAINT ({0},{1})", (int)nmcustomdraw.nmcd.dwItemSpec, nmcustomdraw.iSubItem)); + + // There is a bug in the .NET framework which appears when column 0 of an owner drawn listview + // is dragged to another column position. + // The bounds calculation always returns the left edge of column 0 as being 0. + // The effects of this bug become apparent + // when the listview is scrolled horizontally: the control can think that column 0 + // is no longer visible (the horizontal scroll position is subtracted from the bounds, giving a + // rectangle that is offscreen). In those circumstances, column 0 is not redraw because + // the control thinks it is not visible and so does not trigger a DrawSubItem event. + + // To fix this problem, we have to detected the situation -- owner drawing column 0 in any column except 0 -- + // trigger our own DrawSubItem, and then prevent the default processing from occurring. + + // Are we owner drawing column 0 when it's in any column except 0? + if (!this.OwnerDraw) + return false; + + int columnIndex = nmcustomdraw.iSubItem; + if (columnIndex != 0) + return false; + + int displayIndex = this.Columns[0].DisplayIndex; + if (displayIndex == 0) + return false; + + int rowIndex = (int)nmcustomdraw.nmcd.dwItemSpec; + OLVListItem item = this.GetItem(rowIndex); + if (item == null) + return false; + + // OK. We have the error condition, so lets do what the .NET framework should do. + // Trigger an event to draw column 0 when it is not at display index 0 + using (Graphics g = Graphics.FromHdc(nmcustomdraw.nmcd.hdc)) { + + // Correctly calculate the bounds of cell 0 + Rectangle r = item.GetSubItemBounds(0); + + // We can hardcode "0" here since we know we are only doing this for column 0 + DrawListViewSubItemEventArgs args = new DrawListViewSubItemEventArgs(g, r, item, item.SubItems[0], rowIndex, 0, + this.Columns[0], (ListViewItemStates)nmcustomdraw.nmcd.uItemState); + this.OnDrawSubItem(args); + + // If the event handler wants to do the default processing (i.e. DrawDefault = true), we are stuck. + // There is no way we can force the default drawing because of the bug in .NET we are trying to get around. + System.Diagnostics.Trace.Assert(!args.DrawDefault, "Default drawing is impossible in this situation"); + } + m.Result = (IntPtr)4; + + return true; + + case CDDS_SUBITEMPOSTPAINT: + //System.Diagnostics.Debug.WriteLine("CDDS_SUBITEMPOSTPAINT"); + break; + + // I have included these stages, but it doesn't seem that they are sent for ListViews. + // http://www.tech-archive.net/Archive/VC/microsoft.public.vc.mfc/2006-08/msg00220.html + + case CDDS_PREERASE: + //System.Diagnostics.Debug.WriteLine("CDDS_PREERASE"); + break; + + case CDDS_POSTERASE: + //System.Diagnostics.Debug.WriteLine("CDDS_POSTERASE"); + break; + + case CDDS_ITEMPREERASE: + //System.Diagnostics.Debug.WriteLine("CDDS_ITEMPREERASE"); + break; + + case CDDS_ITEMPOSTERASE: + //System.Diagnostics.Debug.WriteLine("CDDS_ITEMPOSTERASE"); + break; + } + + return false; + } + bool isAfterItemPaint; + List drawnItems; + + /// + /// Handle the underlying control being destroyed + /// + /// + /// + protected virtual bool HandleDestroy(ref Message m) { + //System.Diagnostics.Debug.WriteLine(String.Format("WM_DESTROY: Disposing={0}, IsDisposed={1}, VirtualMode={2}", Disposing, IsDisposed, VirtualMode)); + + // Recreate the header control when the listview control is destroyed + this.headerControl = null; + + // When the underlying control is destroyed, we need to recreate and reconfigure its tooltip + if (this.cellToolTip != null) { + this.cellToolTip.PushSettings(); + this.BeginInvoke((MethodInvoker)delegate { + this.UpdateCellToolTipHandle(); + this.cellToolTip.PopSettings(); + }); + } + + return false; + } + + /// + /// Handle the search for item m if possible. + /// + /// The m to be processed + /// bool to indicate if the msg has been handled + protected virtual bool HandleFindItem(ref Message m) { + // NOTE: As far as I can see, this message is never actually sent to the control, making this + // method redundant! + + const int LVFI_STRING = 0x0002; + + NativeMethods.LVFINDINFO findInfo = (NativeMethods.LVFINDINFO)m.GetLParam(typeof(NativeMethods.LVFINDINFO)); + + // We can only handle string searches + if ((findInfo.flags & LVFI_STRING) != LVFI_STRING) + return false; + + int start = m.WParam.ToInt32(); + m.Result = (IntPtr)this.FindMatchingRow(findInfo.psz, start, SearchDirectionHint.Down); + return true; + } + + /// + /// Find the first row after the given start in which the text value in the + /// comparison column begins with the given text. The comparison column is column 0, + /// unless IsSearchOnSortColumn is true, in which case the current sort column is used. + /// + /// The text to be prefix matched + /// The index of the first row to consider + /// Which direction should be searched? + /// The index of the first row that matched, or -1 + /// The text comparison is a case-insensitive, prefix match. The search will + /// search the every row until a match is found, wrapping at the end if needed. + public virtual int FindMatchingRow(string text, int start, SearchDirectionHint direction) { + // We also can't do anything if we don't have data + if (this.Columns.Count == 0) + return -1; + int rowCount = this.GetItemCount(); + if (rowCount == 0) + return -1; + + // Which column are we going to use for our comparing? + OLVColumn column = this.GetColumn(0); + if (this.IsSearchOnSortColumn && this.View == View.Details && this.PrimarySortColumn != null) + column = this.PrimarySortColumn; + + // Do two searches if necessary to find a match. The second search is the wrap-around part of searching + int i; + if (direction == SearchDirectionHint.Down) { + i = this.FindMatchInRange(text, start, rowCount - 1, column); + if (i == -1 && start > 0) + i = this.FindMatchInRange(text, 0, start - 1, column); + } else { + i = this.FindMatchInRange(text, start, 0, column); + if (i == -1 && start != rowCount) + i = this.FindMatchInRange(text, rowCount - 1, start + 1, column); + } + + return i; + } + + /// + /// Find the first row in the given range of rows that prefix matches the string value of the given column. + /// + /// + /// + /// + /// + /// The index of the matched row, or -1 + protected virtual int FindMatchInRange(string text, int first, int last, OLVColumn column) { + if (first <= last) { + for (int i = first; i <= last; i++) { + string data = column.GetStringValue(this.GetNthItemInDisplayOrder(i).RowObject); + if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase)) + return i; + } + } else { + for (int i = first; i >= last; i--) { + string data = column.GetStringValue(this.GetNthItemInDisplayOrder(i).RowObject); + if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase)) + return i; + } + } + + return -1; + } + + /// + /// Handle the Group Info series of notifications + /// + /// The message + /// True if the message has been handled + protected virtual bool HandleGroupInfo(ref Message m) + { + NativeMethods.NMLVGROUP nmlvgroup = (NativeMethods.NMLVGROUP)m.GetLParam(typeof(NativeMethods.NMLVGROUP)); + + //System.Diagnostics.Debug.WriteLine(String.Format("group: {0}, old state: {1}, new state: {2}", + // nmlvgroup.iGroupId, StateToString(nmlvgroup.uOldState), StateToString(nmlvgroup.uNewState))); + + // Ignore state changes that aren't related to selection, focus or collapsedness + const uint INTERESTING_STATES = (uint) (GroupState.LVGS_COLLAPSED | GroupState.LVGS_FOCUSED | GroupState.LVGS_SELECTED); + if ((nmlvgroup.uOldState & INTERESTING_STATES) == (nmlvgroup.uNewState & INTERESTING_STATES)) + return false; + + foreach (OLVGroup group in this.OLVGroups) { + if (group.GroupId == nmlvgroup.iGroupId) { + GroupStateChangedEventArgs args = new GroupStateChangedEventArgs(group, (GroupState)nmlvgroup.uOldState, (GroupState)nmlvgroup.uNewState); + this.OnGroupStateChanged(args); + break; + } + } + + return false; + } + + //private static string StateToString(uint state) + //{ + // if (state == 0) + // return Enum.GetName(typeof(GroupState), 0); + // + // List names = new List(); + // foreach (int value in Enum.GetValues(typeof(GroupState))) + // { + // if (value != 0 && (state & value) == value) + // { + // names.Add(Enum.GetName(typeof(GroupState), value)); + // } + // } + // return names.Count == 0 ? state.ToString("x") : String.Join("|", names.ToArray()); + //} + + /// + /// Handle a key down message + /// + /// + /// True if the msg has been handled + protected virtual bool HandleKeyDown(ref Message m) { + + // If this is a checkbox list, toggle the selected rows when the user presses Space + if (this.CheckBoxes && m.WParam.ToInt32() == (int)Keys.Space && this.SelectedIndices.Count > 0) { + this.ToggleSelectedRowCheckBoxes(); + return true; + } + + // Remember the scroll position so we can decide if the listview has scrolled in the + // handling of the event. + int scrollPositionH = NativeMethods.GetScrollPosition(this, true); + int scrollPositionV = NativeMethods.GetScrollPosition(this, false); + + base.WndProc(ref m); + + // It's possible that the processing in base.WndProc has actually destroyed this control + if (this.IsDisposed) + return true; + + // If the keydown processing changed the scroll position, trigger a Scroll event + int newScrollPositionH = NativeMethods.GetScrollPosition(this, true); + int newScrollPositionV = NativeMethods.GetScrollPosition(this, false); + + if (scrollPositionH != newScrollPositionH) { + ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll, + scrollPositionH, newScrollPositionH, ScrollOrientation.HorizontalScroll); + this.OnScroll(args); + } + if (scrollPositionV != newScrollPositionV) { + ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll, + scrollPositionV, newScrollPositionV, ScrollOrientation.VerticalScroll); + this.OnScroll(args); + } + + if (scrollPositionH != newScrollPositionH || scrollPositionV != newScrollPositionV) + this.RefreshHotItem(); + + return true; + } + + /// + /// Toggle the checkedness of the selected rows + /// + /// + /// + /// Actually, this doesn't actually toggle all rows. It toggles the first row, and + /// all other rows get the check state of that first row. This is actually a much + /// more useful behaviour. + /// + /// + /// If no rows are selected, this method does nothing. + /// + /// + public void ToggleSelectedRowCheckBoxes() { + if (this.SelectedIndices.Count == 0) + return; + Object primaryModel = this.GetItem(this.SelectedIndices[0]).RowObject; + this.ToggleCheckObject(primaryModel); + CheckState? state = this.GetCheckState(primaryModel); + if (state.HasValue) { + foreach (Object x in this.SelectedObjects) + this.SetObjectCheckedness(x, state.Value); + } + } + + /// + /// Catch the Left Button down event. + /// + /// The m to be processed + /// bool to indicate if the msg has been handled + protected virtual bool HandleLButtonDown(ref Message m) { + // If there are no columns, the base ListView class can throw OutOfRange exceptions. + if (this.Columns.Count == 0) + return true; + + // We have to intercept this low level message rather than the more natural + // overridding of OnMouseDown, since ListCtrl's internal mouse down behavior + // is to select (or deselect) rows when the mouse is released. We don't + // want the selection to change when the user checks or unchecks a checkbox, so if the + // mouse down event was to check/uncheck, we have to hide this mouse + // down event from the control. + + int x = m.LParam.ToInt32() & 0xFFFF; + int y = (m.LParam.ToInt32() >> 16) & 0xFFFF; + + return this.ProcessLButtonDown(this.OlvHitTest(x, y)); + } + + /// + /// Handle a left mouse down at the given hit test location + /// + /// Subclasses can override this to do something unique + /// + /// True if the message has been handled + protected virtual bool ProcessLButtonDown(OlvListViewHitTestInfo hti) { + + if (hti.Item == null) + return false; + + // If the click occurs on a button, ignore it so the row isn't selected + if (hti.HitTestLocation == HitTestLocation.Button) { + this.Invalidate(); + + return true; + } + + // If they didn't click checkbox, we can just return + if (hti.HitTestLocation != HitTestLocation.CheckBox) + return false; + + // Disabled rows cannot change checkboxes + if (!hti.Item.Enabled) + return true; + + // Did they click a sub item checkbox? + if (hti.Column != null && hti.Column.Index > 0) { + if (hti.Column.IsEditable && hti.Item.Enabled) + this.ToggleSubItemCheckBox(hti.RowObject, hti.Column); + return true; + } + + // They must have clicked the primary checkbox + this.ToggleCheckObject(hti.RowObject); + + // If they change the checkbox of a selected row, all the rows in the selection + // should be given the same state + if (hti.Item.Selected) { + CheckState? state = this.GetCheckState(hti.RowObject); + if (state.HasValue) { + foreach (Object x in this.SelectedObjects) + this.SetObjectCheckedness(x, state.Value); + } + } + + return true; + } + + /// + /// Catch the Left Button up event. + /// + /// The m to be processed + /// bool to indicate if the msg has been handled + protected virtual bool HandleLButtonUp(ref Message m) { + // If there are no columns, the base ListView class can throw OutOfRange exceptions. + if (this.Columns.Count == 0) + return true; + + if (this.MouseMoveHitTest == null) + return false; + + int x = m.LParam.ToInt32() & 0xFFFF; + int y = (m.LParam.ToInt32() >> 16) & 0xFFFF; + + // Did they click an enabled, non-empty button? + if (this.MouseMoveHitTest.HitTestLocation == HitTestLocation.Button) { + // If a button was hit, Item and Column must be non-null + if (this.MouseMoveHitTest.Item.Enabled || this.MouseMoveHitTest.Column.EnableButtonWhenItemIsDisabled) { + string buttonText = this.MouseMoveHitTest.Column.GetStringValue(this.MouseMoveHitTest.RowObject); + if (!String.IsNullOrEmpty(buttonText)) { + this.Invalidate(); + CellClickEventArgs args = new CellClickEventArgs(); + this.BuildCellEvent(args, new Point(x, y), this.MouseMoveHitTest); + this.OnButtonClick(args); + return true; + } + } + } + + // Are they trying to expand/collapse a group? + if (this.MouseMoveHitTest.HitTestLocation == HitTestLocation.GroupExpander) { + if (this.TriggerGroupExpandCollapse(this.MouseMoveHitTest.Group)) + return true; + } + + if (ObjectListView.IsVistaOrLater && this.HasCollapsibleGroups) + base.DefWndProc(ref m); + + return false; + } + + /// + /// Trigger a GroupExpandCollapse event and return true if the action was cancelled + /// + /// + /// + protected virtual bool TriggerGroupExpandCollapse(OLVGroup group) + { + GroupExpandingCollapsingEventArgs args = new GroupExpandingCollapsingEventArgs(group); + this.OnGroupExpandingCollapsing(args); + return args.Canceled; + } + + /// + /// Catch the Right Button down event. + /// + /// The m to be processed + /// bool to indicate if the msg has been handled + protected virtual bool HandleRButtonDown(ref Message m) { + // If there are no columns, the base ListView class can throw OutOfRange exceptions. + if (this.Columns.Count == 0) + return true; + + int x = m.LParam.ToInt32() & 0xFFFF; + int y = (m.LParam.ToInt32() >> 16) & 0xFFFF; + + return this.ProcessRButtonDown(this.OlvHitTest(x, y)); + } + + /// + /// Handle a left mouse down at the given hit test location + /// + /// Subclasses can override this to do something unique + /// + /// True if the message has been handled + protected virtual bool ProcessRButtonDown(OlvListViewHitTestInfo hti) { + if (hti.Item == null) + return false; + + // Ignore clicks on checkboxes + return (hti.HitTestLocation == HitTestLocation.CheckBox); + } + + /// + /// Catch the Left Button double click event. + /// + /// The m to be processed + /// bool to indicate if the msg has been handled + protected virtual bool HandleLButtonDoubleClick(ref Message m) { + // If there are no columns, the base ListView class can throw OutOfRange exceptions. + if (this.Columns.Count == 0) + return true; + + int x = m.LParam.ToInt32() & 0xFFFF; + int y = (m.LParam.ToInt32() >> 16) & 0xFFFF; + + return this.ProcessLButtonDoubleClick(this.OlvHitTest(x, y)); + } + + /// + /// Handle a mouse double click at the given hit test location + /// + /// Subclasses can override this to do something unique + /// + /// True if the message has been handled + protected virtual bool ProcessLButtonDoubleClick(OlvListViewHitTestInfo hti) { + // If the user double clicked on a checkbox, ignore it + return (hti.HitTestLocation == HitTestLocation.CheckBox); + } + + /// + /// Catch the right Button double click event. + /// + /// The m to be processed + /// bool to indicate if the msg has been handled + protected virtual bool HandleRButtonDoubleClick(ref Message m) { + + // If there are no columns, the base ListView class can throw OutOfRange exceptions. + if (this.Columns.Count == 0) + return true; + + int x = m.LParam.ToInt32() & 0xFFFF; + int y = (m.LParam.ToInt32() >> 16) & 0xFFFF; + + return this.ProcessRButtonDoubleClick(this.OlvHitTest(x, y)); + } + + /// + /// Handle a right mouse double click at the given hit test location + /// + /// Subclasses can override this to do something unique + /// + /// True if the message has been handled + protected virtual bool ProcessRButtonDoubleClick(OlvListViewHitTestInfo hti) { + + // If the user double clicked on a checkbox, ignore it + return (hti.HitTestLocation == HitTestLocation.CheckBox); + } + + /// + /// Catch the MouseMove event. + /// + /// The m to be processed + /// bool to indicate if the msg has been handled + protected virtual bool HandleMouseMove(ref Message m) + { + //int x = m.LParam.ToInt32() & 0xFFFF; + //int y = (m.LParam.ToInt32() >> 16) & 0xFFFF; + + //this.lastMouseMoveX = x; + //this.lastMouseMoveY = y; + + return false; + } + //private int lastMouseMoveX = -1; + //private int lastMouseMoveY = -1; + + /// + /// Handle notifications that have been reflected back from the parent window + /// + /// The m to be processed + /// bool to indicate if the msg has been handled + protected virtual bool HandleReflectNotify(ref Message m) { + const int NM_CLICK = -2; + const int NM_DBLCLK = -3; + const int NM_RDBLCLK = -6; + const int NM_CUSTOMDRAW = -12; + const int NM_RELEASEDCAPTURE = -16; + const int LVN_FIRST = -100; + const int LVN_ITEMCHANGED = LVN_FIRST - 1; + const int LVN_ITEMCHANGING = LVN_FIRST - 0; + const int LVN_HOTTRACK = LVN_FIRST - 21; + const int LVN_MARQUEEBEGIN = LVN_FIRST - 56; + const int LVN_GETINFOTIP = LVN_FIRST - 58; + const int LVN_GETDISPINFO = LVN_FIRST - 77; + const int LVN_BEGINSCROLL = LVN_FIRST - 80; + const int LVN_ENDSCROLL = LVN_FIRST - 81; + const int LVN_LINKCLICK = LVN_FIRST - 84; + const int LVN_GROUPINFO = LVN_FIRST - 88; // undocumented + const int LVIF_STATE = 8; + //const int LVIS_FOCUSED = 1; + const int LVIS_SELECTED = 2; + + bool isMsgHandled = false; + + // TODO: Don't do any logic in this method. Create separate methods for each message + + NativeMethods.NMHDR nmhdr = (NativeMethods.NMHDR)m.GetLParam(typeof(NativeMethods.NMHDR)); + //System.Diagnostics.Debug.WriteLine(String.Format("rn: {0}", nmhdr->code)); + + switch (nmhdr.code) { + case NM_CLICK: + // The standard ListView does some strange stuff here when the list has checkboxes. + // If you shift click on non-primary columns when FullRowSelect is true, the + // checkedness of the selected rows changes. + // We can't just not do the base class stuff because it sets up state that is used to + // determine mouse up events later on. + // So, we sabotage the base class's process of the click event. The base class does a HITTEST + // in order to determine which row was clicked -- if that fails, the base class does nothing. + // So when we get a CLICK, we know that the base class is going to send a HITTEST very soon, + // so we ignore the next HITTEST message, which will cause the click processing to fail. + //System.Diagnostics.Debug.WriteLine("NM_CLICK"); + this.skipNextHitTest = true; + break; + + case LVN_BEGINSCROLL: + //System.Diagnostics.Debug.WriteLine("LVN_BEGINSCROLL"); + isMsgHandled = this.HandleBeginScroll(ref m); + break; + + case LVN_ENDSCROLL: + isMsgHandled = this.HandleEndScroll(ref m); + break; + + case LVN_LINKCLICK: + isMsgHandled = this.HandleLinkClick(ref m); + break; + + case LVN_MARQUEEBEGIN: + //System.Diagnostics.Debug.WriteLine("LVN_MARQUEEBEGIN"); + this.isMarqueSelecting = true; + break; + + case LVN_GETINFOTIP: + //System.Diagnostics.Debug.WriteLine("LVN_GETINFOTIP"); + // When virtual lists are in SmallIcon view, they generates tooltip message with invalid item indices. + NativeMethods.NMLVGETINFOTIP nmGetInfoTip = (NativeMethods.NMLVGETINFOTIP)m.GetLParam(typeof(NativeMethods.NMLVGETINFOTIP)); + isMsgHandled = nmGetInfoTip.iItem >= this.GetItemCount() || this.Columns.Count == 0; + break; + + case NM_RELEASEDCAPTURE: + //System.Diagnostics.Debug.WriteLine("NM_RELEASEDCAPTURE"); + this.isMarqueSelecting = false; + this.Invalidate(); + break; + + case NM_CUSTOMDRAW: + //System.Diagnostics.Debug.WriteLine("NM_CUSTOMDRAW"); + isMsgHandled = this.HandleCustomDraw(ref m); + break; + + case NM_DBLCLK: + // The default behavior of a .NET ListView with checkboxes is to toggle the checkbox on + // double-click. That's just silly, if you ask me :) + if (this.CheckBoxes) { + // How do we make ListView not do that silliness? We could just ignore the message + // but the last part of the base code sets up state information, and without that + // state, the ListView doesn't trigger MouseDoubleClick events. So we fake a + // right button double click event, which sets up the same state, but without + // toggling the checkbox. + nmhdr.code = NM_RDBLCLK; + Marshal.StructureToPtr(nmhdr, m.LParam, false); + } + break; + + case LVN_ITEMCHANGED: + //System.Diagnostics.Debug.WriteLine("LVN_ITEMCHANGED"); + NativeMethods.NMLISTVIEW nmlistviewPtr2 = (NativeMethods.NMLISTVIEW)m.GetLParam(typeof(NativeMethods.NMLISTVIEW)); + if ((nmlistviewPtr2.uChanged & LVIF_STATE) != 0) { + CheckState currentValue = this.CalculateCheckState(nmlistviewPtr2.uOldState); + CheckState newCheckValue = this.CalculateCheckState(nmlistviewPtr2.uNewState); + if (currentValue != newCheckValue) + { + // Remove the state indices so that we don't trigger the OnItemChecked method + // when we call our base method after exiting this method + nmlistviewPtr2.uOldState = (nmlistviewPtr2.uOldState & 0x0FFF); + nmlistviewPtr2.uNewState = (nmlistviewPtr2.uNewState & 0x0FFF); + Marshal.StructureToPtr(nmlistviewPtr2, m.LParam, false); + } + else + { + bool isSelected = (nmlistviewPtr2.uNewState & LVIS_SELECTED) == LVIS_SELECTED; + + if (isSelected) + { + // System.Diagnostics.Debug.WriteLine(String.Format("Selected: {0}", nmlistviewPtr2.iItem)); + bool isShiftDown = (Control.ModifierKeys & Keys.Shift) == Keys.Shift; + + // -1 indicates that all rows are to be selected -- in fact, they already have been. + // We now have to deselect all the disabled objects. + if (nmlistviewPtr2.iItem == -1 || isShiftDown) { + Stopwatch sw = Stopwatch.StartNew(); + foreach (object disabledModel in this.DisabledObjects) + { + int modelIndex = this.IndexOf(disabledModel); + if (modelIndex >= 0) + NativeMethods.DeselectOneItem(this, modelIndex); + } + // System.Diagnostics.Debug.WriteLine(String.Format("PERF - Deselecting took {0}ms / {1} ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks)); + } + else + { + // If the object just selected is disabled, explicitly de-select it + OLVListItem olvi = this.GetItem(nmlistviewPtr2.iItem); + if (olvi != null && !olvi.Enabled) + NativeMethods.DeselectOneItem(this, nmlistviewPtr2.iItem); + } + } + } + } + break; + + case LVN_ITEMCHANGING: + //System.Diagnostics.Debug.WriteLine("LVN_ITEMCHANGING"); + NativeMethods.NMLISTVIEW nmlistviewPtr = (NativeMethods.NMLISTVIEW)m.GetLParam(typeof(NativeMethods.NMLISTVIEW)); + if ((nmlistviewPtr.uChanged & LVIF_STATE) != 0) { + CheckState currentValue = this.CalculateCheckState(nmlistviewPtr.uOldState); + CheckState newCheckValue = this.CalculateCheckState(nmlistviewPtr.uNewState); + + if (currentValue != newCheckValue) { + // Prevent the base method from seeing the state change, + // since we handled it elsewhere + nmlistviewPtr.uChanged &= ~LVIF_STATE; + Marshal.StructureToPtr(nmlistviewPtr, m.LParam, false); + } + } + break; + + case LVN_HOTTRACK: + break; + + case LVN_GETDISPINFO: + break; + + case LVN_GROUPINFO: + //System.Diagnostics.Debug.WriteLine("reflect notify: GROUP INFO"); + isMsgHandled = this.HandleGroupInfo(ref m); + break; + + //default: + //System.Diagnostics.Debug.WriteLine(String.Format("reflect notify: {0}", nmhdr.code)); + //break; + } + + return isMsgHandled; + } + private bool skipNextHitTest; + + private CheckState CalculateCheckState(int state) { + switch ((state & 0xf000) >> 12) { + case 1: + return CheckState.Unchecked; + case 2: + return CheckState.Checked; + case 3: + return CheckState.Indeterminate; + default: + return CheckState.Checked; + } + } + + /// + /// In the notification messages, we handle attempts to change the width of our columns + /// + /// The m to be processed + /// bool to indicate if the msg has been handled + protected bool HandleNotify(ref Message m) { + bool isMsgHandled = false; + + const int NM_CUSTOMDRAW = -12; + + const int HDN_FIRST = (0 - 300); + const int HDN_ITEMCHANGINGA = (HDN_FIRST - 0); + const int HDN_ITEMCHANGINGW = (HDN_FIRST - 20); + const int HDN_ITEMCLICKA = (HDN_FIRST - 2); + const int HDN_ITEMCLICKW = (HDN_FIRST - 22); + const int HDN_DIVIDERDBLCLICKA = (HDN_FIRST - 5); + const int HDN_DIVIDERDBLCLICKW = (HDN_FIRST - 25); + const int HDN_BEGINTRACKA = (HDN_FIRST - 6); + const int HDN_BEGINTRACKW = (HDN_FIRST - 26); + const int HDN_ENDTRACKA = (HDN_FIRST - 7); + const int HDN_ENDTRACKW = (HDN_FIRST - 27); + const int HDN_TRACKA = (HDN_FIRST - 8); + const int HDN_TRACKW = (HDN_FIRST - 28); + + // Handle the notification, remembering to handle both ANSI and Unicode versions + NativeMethods.NMHEADER nmheader = (NativeMethods.NMHEADER)m.GetLParam(typeof(NativeMethods.NMHEADER)); + //System.Diagnostics.Debug.WriteLine(String.Format("not: {0}", nmhdr->code)); + + //if (nmhdr.code < HDN_FIRST) + // System.Diagnostics.Debug.WriteLine(nmhdr.code); + + // In KB Article #183258, MS states that when a header control has the HDS_FULLDRAG style, it will receive + // ITEMCHANGING events rather than TRACK events. Under XP SP2 (at least) this is not always true, which may be + // why MS has withdrawn that particular KB article. It is true that the header is always given the HDS_FULLDRAG + // style. But even while window style set, the control doesn't always received ITEMCHANGING events. + // The controlling setting seems to be the Explorer option "Show Window Contents While Dragging"! + // In the category of "truly bizarre side effects", if the this option is turned on, we will receive + // ITEMCHANGING events instead of TRACK events. But if it is turned off, we receive lots of TRACK events and + // only one ITEMCHANGING event at the very end of the process. + // If we receive HDN_TRACK messages, it's harder to control the resizing process. If we return a result of 1, we + // cancel the whole drag operation, not just that particular track event, which is clearly not what we want. + // If we are willing to compile with unsafe code enabled, we can modify the size of the column in place, using the + // commented out code below. But without unsafe code, the best we can do is allow the user to drag the column to + // any width, and then spring it back to within bounds once they release the mouse button. UI-wise it's very ugly. + switch (nmheader.nhdr.code) { + + case NM_CUSTOMDRAW: + if (!this.OwnerDrawnHeader) + isMsgHandled = this.HeaderControl.HandleHeaderCustomDraw(ref m); + break; + + case HDN_ITEMCLICKA: + case HDN_ITEMCLICKW: + if (!this.PossibleFinishCellEditing()) + { + m.Result = (IntPtr)1; // prevent the change from happening + isMsgHandled = true; + } + break; + + case HDN_DIVIDERDBLCLICKA: + case HDN_DIVIDERDBLCLICKW: + case HDN_BEGINTRACKA: + case HDN_BEGINTRACKW: + if (!this.PossibleFinishCellEditing()) { + m.Result = (IntPtr)1; // prevent the change from happening + isMsgHandled = true; + break; + } + if (nmheader.iItem >= 0 && nmheader.iItem < this.Columns.Count) { + OLVColumn column = this.GetColumn(nmheader.iItem); + // Space filling columns can't be dragged or double-click resized + if (column.FillsFreeSpace) { + m.Result = (IntPtr)1; // prevent the change from happening + isMsgHandled = true; + } + } + break; + case HDN_ENDTRACKA: + case HDN_ENDTRACKW: + //if (this.ShowGroups) + // this.ResizeLastGroup(); + break; + case HDN_TRACKA: + case HDN_TRACKW: + if (nmheader.iItem >= 0 && nmheader.iItem < this.Columns.Count) { + NativeMethods.HDITEM hditem = (NativeMethods.HDITEM)Marshal.PtrToStructure(nmheader.pHDITEM, typeof(NativeMethods.HDITEM)); + OLVColumn column = this.GetColumn(nmheader.iItem); + if (hditem.cxy < column.MinimumWidth) + hditem.cxy = column.MinimumWidth; + else if (column.MaximumWidth != -1 && hditem.cxy > column.MaximumWidth) + hditem.cxy = column.MaximumWidth; + Marshal.StructureToPtr(hditem, nmheader.pHDITEM, false); + } + break; + + case HDN_ITEMCHANGINGA: + case HDN_ITEMCHANGINGW: + nmheader = (NativeMethods.NMHEADER)m.GetLParam(typeof(NativeMethods.NMHEADER)); + if (nmheader.iItem >= 0 && nmheader.iItem < this.Columns.Count) { + NativeMethods.HDITEM hditem = (NativeMethods.HDITEM)Marshal.PtrToStructure(nmheader.pHDITEM, typeof(NativeMethods.HDITEM)); + OLVColumn column = this.GetColumn(nmheader.iItem); + // Check the mask to see if the width field is valid, and if it is, make sure it's within range + if ((hditem.mask & 1) == 1) { + if (hditem.cxy < column.MinimumWidth || + (column.MaximumWidth != -1 && hditem.cxy > column.MaximumWidth)) { + m.Result = (IntPtr)1; // prevent the change from happening + isMsgHandled = true; + } + } + } + break; + + case ToolTipControl.TTN_SHOW: + //System.Diagnostics.Debug.WriteLine("olv TTN_SHOW"); + if (this.CellToolTip.Handle == nmheader.nhdr.hwndFrom) + isMsgHandled = this.CellToolTip.HandleShow(ref m); + break; + + case ToolTipControl.TTN_POP: + //System.Diagnostics.Debug.WriteLine("olv TTN_POP"); + if (this.CellToolTip.Handle == nmheader.nhdr.hwndFrom) + isMsgHandled = this.CellToolTip.HandlePop(ref m); + break; + + case ToolTipControl.TTN_GETDISPINFO: + //System.Diagnostics.Debug.WriteLine("olv TTN_GETDISPINFO"); + if (this.CellToolTip.Handle == nmheader.nhdr.hwndFrom) + isMsgHandled = this.CellToolTip.HandleGetDispInfo(ref m); + break; + +// default: +// System.Diagnostics.Debug.WriteLine(String.Format("notify: {0}", nmheader.nhdr.code)); +// break; + } + + return isMsgHandled; + } + + /// + /// Create a ToolTipControl to manage the tooltip control used by the listview control + /// + protected virtual void CreateCellToolTip() { + this.cellToolTip = new ToolTipControl(); + this.cellToolTip.AssignHandle(NativeMethods.GetTooltipControl(this)); + this.cellToolTip.Showing += new EventHandler(HandleCellToolTipShowing); + this.cellToolTip.SetMaxWidth(); + NativeMethods.MakeTopMost(this.cellToolTip); + } + + /// + /// Update the handle used by our cell tooltip to be the tooltip used by + /// the underlying Windows listview control. + /// + protected virtual void UpdateCellToolTipHandle() { + if (this.cellToolTip != null && this.cellToolTip.Handle == IntPtr.Zero) + this.cellToolTip.AssignHandle(NativeMethods.GetTooltipControl(this)); + } + + /// + /// Handle the WM_PAINT event + /// + /// + /// Return true if the msg has been handled and nothing further should be done + protected virtual bool HandlePaint(ref Message m) { + //System.Diagnostics.Debug.WriteLine("> WMPAINT"); + + // We only want to custom draw the control within WmPaint message and only + // once per paint event. We use these bools to insure this. + this.isInWmPaintEvent = true; + this.shouldDoCustomDrawing = true; + this.prePaintLevel = 0; + + this.ShowOverlays(); + + this.HandlePrePaint(); + base.WndProc(ref m); + this.HandlePostPaint(); + this.isInWmPaintEvent = false; + //System.Diagnostics.Debug.WriteLine("< WMPAINT"); + return true; + } + private int prePaintLevel; + + /// + /// Perform any steps needed before painting the control + /// + protected virtual void HandlePrePaint() { + // When we get a WM_PAINT msg, remember the rectangle that is being updated. + // We can't get this information later, since the BeginPaint call wipes it out. + // this.lastUpdateRectangle = NativeMethods.GetUpdateRect(this); // we no longer need this, but keep the code so we can see it later + + //// When the list is empty, we want to handle the drawing of the control by ourselves. + //// Unfortunately, there is no easy way to tell our superclass that we want to do this. + //// So we resort to guile and deception. We validate the list area of the control, which + //// effectively tells our superclass that this area does not need to be painted. + //// Our superclass will then not paint the control, leaving us free to do so ourselves. + //// Without doing this trickery, the superclass will draw the list as empty, + //// and then moments later, we will draw the empty list msg, giving a nasty flicker + //if (this.GetItemCount() == 0 && this.HasEmptyListMsg) + // NativeMethods.ValidateRect(this, this.ClientRectangle); + } + + /// + /// Perform any steps needed after painting the control + /// + protected virtual void HandlePostPaint() { + // This message is no longer necessary, but we keep it for compatibility + } + + /// + /// Handle the window position changing. + /// + /// The m to be processed + /// bool to indicate if the msg has been handled + protected virtual bool HandleWindowPosChanging(ref Message m) { + const int SWP_NOSIZE = 1; + + NativeMethods.WINDOWPOS pos = (NativeMethods.WINDOWPOS)m.GetLParam(typeof(NativeMethods.WINDOWPOS)); + if ((pos.flags & SWP_NOSIZE) == 0) { + if (pos.cx < this.Bounds.Width) // only when shrinking + // pos.cx is the window width, not the client area width, so we have to subtract the border widths + this.ResizeFreeSpaceFillingColumns(pos.cx - (this.Bounds.Width - this.ClientSize.Width)); + } + + return false; + } + + #endregion + + #region Column header clicking, column hiding and resizing + + /// + /// The user has right clicked on the column headers. Do whatever is required + /// + /// Return true if this event has been handle + protected virtual bool HandleHeaderRightClick(int columnIndex) { + ToolStripDropDown menu = this.MakeHeaderRightClickMenu(columnIndex); + ColumnRightClickEventArgs eventArgs = new ColumnRightClickEventArgs(columnIndex, menu, Cursor.Position); + this.OnColumnRightClick(eventArgs); + + // Did the event handler stop any further processing? + if (eventArgs.Cancel) + return false; + + return this.ShowHeaderRightClickMenu(columnIndex, eventArgs.MenuStrip, eventArgs.Location); + } + + /// + /// Show a menu that is appropriate when the given column header is clicked. + /// + /// The index of the header that was clicked. This + /// can be -1, indicating that the header was clicked outside of a column + /// Where should the menu be shown + /// True if a menu was displayed + protected virtual bool ShowHeaderRightClickMenu(int columnIndex, ToolStripDropDown menu, Point pt) { + if (menu.Items.Count > 0) { + menu.Show(pt); + return true; + } + + return false; + } + + /// + /// Create the menu that should be displayed when the user right clicks + /// on the given column header. + /// + /// Index of the column that was right clicked. + /// This can be negative, which indicates a click outside of any header. + /// The toolstrip that should be displayed + protected virtual ToolStripDropDown MakeHeaderRightClickMenu(int columnIndex) { + ToolStripDropDown m = new ContextMenuStrip(); + + if (columnIndex >= 0 && this.UseFiltering && this.ShowFilterMenuOnRightClick) + m = this.MakeFilteringMenu(m, columnIndex); + + if (columnIndex >= 0 && this.ShowCommandMenuOnRightClick) + m = this.MakeColumnCommandMenu(m, columnIndex); + + if (this.SelectColumnsOnRightClickBehaviour != ColumnSelectBehaviour.None) { + m = this.MakeColumnSelectMenu(m); + } + + return m; + } + + /// + /// The user has right clicked on the column headers. Do whatever is required + /// + /// Return true if this event has been handle + [Obsolete("Use HandleHeaderRightClick(int) instead")] + protected virtual bool HandleHeaderRightClick() { + return false; + } + + /// + /// Show a popup menu at the given point which will allow the user to choose which columns + /// are visible on this listview + /// + /// Where should the menu be placed + [Obsolete("Use ShowHeaderRightClickMenu instead")] + protected virtual void ShowColumnSelectMenu(Point pt) { + ToolStripDropDown m = this.MakeColumnSelectMenu(new ContextMenuStrip()); + m.Show(pt); + } + + /// + /// Show a popup menu at the given point which will allow the user to choose which columns + /// are visible on this listview + /// + /// + /// Where should the menu be placed + [Obsolete("Use ShowHeaderRightClickMenu instead")] + protected virtual void ShowColumnCommandMenu(int columnIndex, Point pt) { + ToolStripDropDown m = this.MakeColumnCommandMenu(new ContextMenuStrip(), columnIndex); + if (this.SelectColumnsOnRightClick) { + if (m.Items.Count > 0) + m.Items.Add(new ToolStripSeparator()); + this.MakeColumnSelectMenu(m); + } + m.Show(pt); + } + + /// + /// Gets or set the text to be used for the sorting ascending command + /// + [Category("Labels - ObjectListView"), DefaultValue("Sort ascending by '{0}'"), Localizable(true)] + public string MenuLabelSortAscending { + get { return this.menuLabelSortAscending; } + set { this.menuLabelSortAscending = value; } + } + private string menuLabelSortAscending = "Sort ascending by '{0}'"; + + /// + /// + /// + [Category("Labels - ObjectListView"), DefaultValue("Sort descending by '{0}'"), Localizable(true)] + public string MenuLabelSortDescending { + get { return this.menuLabelSortDescending; } + set { this.menuLabelSortDescending = value; } + } + private string menuLabelSortDescending = "Sort descending by '{0}'"; + + /// + /// + /// + [Category("Labels - ObjectListView"), DefaultValue("Group by '{0}'"), Localizable(true)] + public string MenuLabelGroupBy { + get { return this.menuLabelGroupBy; } + set { this.menuLabelGroupBy = value; } + } + private string menuLabelGroupBy = "Group by '{0}'"; + + /// + /// + /// + [Category("Labels - ObjectListView"), DefaultValue("Lock grouping on '{0}'"), Localizable(true)] + public string MenuLabelLockGroupingOn { + get { return this.menuLabelLockGroupingOn; } + set { this.menuLabelLockGroupingOn = value; } + } + private string menuLabelLockGroupingOn = "Lock grouping on '{0}'"; + + /// + /// + /// + [Category("Labels - ObjectListView"), DefaultValue("Unlock grouping from '{0}'"), Localizable(true)] + public string MenuLabelUnlockGroupingOn { + get { return this.menuLabelUnlockGroupingOn; } + set { this.menuLabelUnlockGroupingOn = value; } + } + private string menuLabelUnlockGroupingOn = "Unlock grouping from '{0}'"; + + /// + /// + /// + [Category("Labels - ObjectListView"), DefaultValue("Turn off groups"), Localizable(true)] + public string MenuLabelTurnOffGroups { + get { return this.menuLabelTurnOffGroups; } + set { this.menuLabelTurnOffGroups = value; } + } + private string menuLabelTurnOffGroups = "Turn off groups"; + + /// + /// + /// + [Category("Labels - ObjectListView"), DefaultValue("Unsort"), Localizable(true)] + public string MenuLabelUnsort { + get { return this.menuLabelUnsort; } + set { this.menuLabelUnsort = value; } + } + private string menuLabelUnsort = "Unsort"; + + /// + /// + /// + [Category("Labels - ObjectListView"), DefaultValue("Columns"), Localizable(true)] + public string MenuLabelColumns { + get { return this.menuLabelColumns; } + set { this.menuLabelColumns = value; } + } + private string menuLabelColumns = "Columns"; + + /// + /// + /// + [Category("Labels - ObjectListView"), DefaultValue("Select Columns..."), Localizable(true)] + public string MenuLabelSelectColumns { + get { return this.menuLabelSelectColumns; } + set { this.menuLabelSelectColumns = value; } + } + private string menuLabelSelectColumns = "Select Columns..."; + + /// + /// Gets or sets the image that will be place next to the Sort Ascending command + /// + public static Bitmap SortAscendingImage = Kermalis.VGMusicStudio.WinForms.ObjectListView.Properties.Resources.SortAscending; + + /// + /// Gets or sets the image that will be placed next to the Sort Descending command + /// + public static Bitmap SortDescendingImage = Kermalis.VGMusicStudio.WinForms.ObjectListView.Properties.Resources.SortDescending; + + /// + /// Append the column selection menu items to the given menu strip. + /// + /// The menu to which the items will be added. + /// + /// Return the menu to which the items were added + public virtual ToolStripDropDown MakeColumnCommandMenu(ToolStripDropDown strip, int columnIndex) { + OLVColumn column = this.GetColumn(columnIndex); + if (column == null) + return strip; + + if (strip.Items.Count > 0) + strip.Items.Add(new ToolStripSeparator()); + + string label = String.Format(this.MenuLabelSortAscending, column.Text); + if (column.Sortable && !String.IsNullOrEmpty(label)) { + strip.Items.Add(label, ObjectListView.SortAscendingImage, (EventHandler)delegate(object sender, EventArgs args) { + this.Sort(column, SortOrder.Ascending); + }); + } + label = String.Format(this.MenuLabelSortDescending, column.Text); + if (column.Sortable && !String.IsNullOrEmpty(label)) { + strip.Items.Add(label, ObjectListView.SortDescendingImage, (EventHandler)delegate(object sender, EventArgs args) { + this.Sort(column, SortOrder.Descending); + }); + } + if (this.CanShowGroups) { + label = String.Format(this.MenuLabelGroupBy, column.Text); + if (column.Groupable && !String.IsNullOrEmpty(label)) { + strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) { + this.ShowGroups = true; + this.PrimarySortColumn = column; + this.PrimarySortOrder = SortOrder.Ascending; + this.BuildList(); + }); + } + } + if (this.ShowGroups) { + if (this.AlwaysGroupByColumn == column) { + label = String.Format(this.MenuLabelUnlockGroupingOn, column.Text); + if (!String.IsNullOrEmpty(label)) { + strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) { + this.AlwaysGroupByColumn = null; + this.AlwaysGroupBySortOrder = SortOrder.None; + this.BuildList(); + }); + } + } else { + label = String.Format(this.MenuLabelLockGroupingOn, column.Text); + if (column.Groupable && !String.IsNullOrEmpty(label)) { + strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) { + this.ShowGroups = true; + this.AlwaysGroupByColumn = column; + this.AlwaysGroupBySortOrder = SortOrder.Ascending; + this.BuildList(); + }); + } + } + label = String.Format(this.MenuLabelTurnOffGroups, column.Text); + if (!String.IsNullOrEmpty(label)) { + strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) { + this.ShowGroups = false; + this.BuildList(); + }); + } + } else { + label = String.Format(this.MenuLabelUnsort, column.Text); + if (column.Sortable && !String.IsNullOrEmpty(label) && this.PrimarySortOrder != SortOrder.None) { + strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) { + this.Unsort(); + }); + } + } + + return strip; + } + + /// + /// Append the column selection menu items to the given menu strip. + /// + /// The menu to which the items will be added. + /// Return the menu to which the items were added + public virtual ToolStripDropDown MakeColumnSelectMenu(ToolStripDropDown strip) { + + System.Diagnostics.Debug.Assert(this.SelectColumnsOnRightClickBehaviour != ColumnSelectBehaviour.None); + + // Append a separator if the menu isn't empty and the last item isn't already a separator + if (strip.Items.Count > 0 && (!(strip.Items[strip.Items.Count-1] is ToolStripSeparator))) + strip.Items.Add(new ToolStripSeparator()); + + if (this.AllColumns.Count > 0 && this.AllColumns[0].LastDisplayIndex == -1) + this.RememberDisplayIndicies(); + + if (this.SelectColumnsOnRightClickBehaviour == ColumnSelectBehaviour.ModelDialog) { + strip.Items.Add(this.MenuLabelSelectColumns, null, delegate(object sender, EventArgs args) { + (new ColumnSelectionForm()).OpenOn(this); + }); + } + + if (this.SelectColumnsOnRightClickBehaviour == ColumnSelectBehaviour.Submenu) { + ToolStripMenuItem menu = new ToolStripMenuItem(this.MenuLabelColumns); + menu.DropDownItemClicked += new ToolStripItemClickedEventHandler(this.ColumnSelectMenuItemClicked); + strip.Items.Add(menu); + this.AddItemsToColumnSelectMenu(menu.DropDownItems); + } + + if (this.SelectColumnsOnRightClickBehaviour == ColumnSelectBehaviour.InlineMenu) { + strip.ItemClicked += new ToolStripItemClickedEventHandler(this.ColumnSelectMenuItemClicked); + strip.Closing += new ToolStripDropDownClosingEventHandler(this.ColumnSelectMenuClosing); + this.AddItemsToColumnSelectMenu(strip.Items); + } + + return strip; + } + + /// + /// Create the menu items that will allow columns to be choosen and add them to the + /// given collection + /// + /// + protected void AddItemsToColumnSelectMenu(ToolStripItemCollection items) { + + // Sort columns by display order + List columns = new List(this.AllColumns); + columns.Sort(delegate(OLVColumn x, OLVColumn y) { return (x.LastDisplayIndex - y.LastDisplayIndex); }); + + // Build menu from sorted columns + foreach (OLVColumn col in columns) { + ToolStripMenuItem mi = new ToolStripMenuItem(col.Text); + mi.Checked = col.IsVisible; + mi.Tag = col; + + // The 'Index' property returns -1 when the column is not visible, so if the + // column isn't visible we have to enable the item. Also the first column can't be turned off + mi.Enabled = !col.IsVisible || col.CanBeHidden; + items.Add(mi); + } + } + + private void ColumnSelectMenuItemClicked(object sender, ToolStripItemClickedEventArgs e) { + this.contextMenuStaysOpen = false; + ToolStripMenuItem menuItemClicked = e.ClickedItem as ToolStripMenuItem; + if (menuItemClicked == null) + return; + OLVColumn col = menuItemClicked.Tag as OLVColumn; + if (col == null) + return; + menuItemClicked.Checked = !menuItemClicked.Checked; + col.IsVisible = menuItemClicked.Checked; + this.contextMenuStaysOpen = this.SelectColumnsMenuStaysOpen; + this.BeginInvoke(new MethodInvoker(this.RebuildColumns)); + } + private bool contextMenuStaysOpen; + + private void ColumnSelectMenuClosing(object sender, ToolStripDropDownClosingEventArgs e) { + e.Cancel = this.contextMenuStaysOpen && e.CloseReason == ToolStripDropDownCloseReason.ItemClicked; + this.contextMenuStaysOpen = false; + } + + /// + /// Create a Filtering menu + /// + /// + /// + /// + public virtual ToolStripDropDown MakeFilteringMenu(ToolStripDropDown strip, int columnIndex) { + OLVColumn column = this.GetColumn(columnIndex); + if (column == null) + return strip; + + FilterMenuBuilder strategy = this.FilterMenuBuildStrategy; + if (strategy == null) + return strip; + + return strategy.MakeFilterMenu(strip, this, column); + } + + /// + /// Override the OnColumnReordered method to do what we want + /// + /// + protected override void OnColumnReordered(ColumnReorderedEventArgs e) { + base.OnColumnReordered(e); + + // The internal logic of the .NET code behind a ENDDRAG event means that, + // at this point, the DisplayIndex's of the columns are not yet as they are + // going to be. So we have to invoke a method to run later that will remember + // what the real DisplayIndex's are. + this.BeginInvoke(new MethodInvoker(this.RememberDisplayIndicies)); + } + + private void RememberDisplayIndicies() { + // Remember the display indexes so we can put them back at a later date + foreach (OLVColumn x in this.AllColumns) + x.LastDisplayIndex = x.DisplayIndex; + } + + /// + /// When the column widths are changing, resize the space filling columns + /// + /// + /// + protected virtual void HandleColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e) { + if (this.UpdateSpaceFillingColumnsWhenDraggingColumnDivider && !this.GetColumn(e.ColumnIndex).FillsFreeSpace) { + // If the width of a column is increasing, resize any space filling columns allowing the extra + // space that the new column width is going to consume + int oldWidth = this.GetColumn(e.ColumnIndex).Width; + if (e.NewWidth > oldWidth) + this.ResizeFreeSpaceFillingColumns(this.ClientSize.Width - (e.NewWidth - oldWidth)); + else + this.ResizeFreeSpaceFillingColumns(); + } + } + + /// + /// When the column widths change, resize the space filling columns + /// + /// + /// + protected virtual void HandleColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e) { + if (!this.GetColumn(e.ColumnIndex).FillsFreeSpace) + this.ResizeFreeSpaceFillingColumns(); + } + + /// + /// When the size of the control changes, we have to resize our space filling columns. + /// + /// + /// + protected virtual void HandleLayout(object sender, LayoutEventArgs e) { + // We have to delay executing the recalculation of the columns, since virtual lists + // get terribly confused if we resize the column widths during this event. + if (!this.hasResizeColumnsHandler) { + this.hasResizeColumnsHandler = true; + this.RunWhenIdle(this.HandleApplicationIdleResizeColumns); + } + } + + private void RunWhenIdle(EventHandler eventHandler) { + Application.Idle += eventHandler; + if (!this.CanUseApplicationIdle) { + SynchronizationContext.Current.Post(delegate(object x) { Application.RaiseIdle(EventArgs.Empty); }, null); + } + } + + /// + /// Resize our space filling columns so they fill any unoccupied width in the control + /// + protected virtual void ResizeFreeSpaceFillingColumns() { + this.ResizeFreeSpaceFillingColumns(this.ClientSize.Width); + } + + /// + /// Resize our space filling columns so they fill any unoccupied width in the control + /// + protected virtual void ResizeFreeSpaceFillingColumns(int freeSpace) { + // It's too confusing to dynamically resize columns at design time. + if (this.DesignMode) + return; + + if (this.Frozen) + return; + + this.BeginUpdate(); + + // Calculate the free space available + int totalProportion = 0; + List spaceFillingColumns = new List(); + for (int i = 0; i < this.Columns.Count; i++) { + OLVColumn col = this.GetColumn(i); + if (col.FillsFreeSpace) { + spaceFillingColumns.Add(col); + totalProportion += col.FreeSpaceProportion; + } else + freeSpace -= col.Width; + } + freeSpace = Math.Max(0, freeSpace); + + // Any space filling column that would hit it's Minimum or Maximum + // width must be treated as a fixed column. + foreach (OLVColumn col in spaceFillingColumns.ToArray()) { + int newWidth = (freeSpace * col.FreeSpaceProportion) / totalProportion; + + if (col.MinimumWidth != -1 && newWidth < col.MinimumWidth) + newWidth = col.MinimumWidth; + else if (col.MaximumWidth != -1 && newWidth > col.MaximumWidth) + newWidth = col.MaximumWidth; + else + newWidth = 0; + + if (newWidth > 0) { + col.Width = newWidth; + freeSpace -= newWidth; + totalProportion -= col.FreeSpaceProportion; + spaceFillingColumns.Remove(col); + } + } + + // Distribute the free space between the columns + foreach (OLVColumn col in spaceFillingColumns) { + col.Width = (freeSpace*col.FreeSpaceProportion)/totalProportion; + } + + this.EndUpdate(); + } + + #endregion + + #region Checkboxes + + /// + /// Check all rows + /// + public virtual void CheckAll() + { + this.CheckedObjects = EnumerableToArray(this.Objects, false); + } + + /// + /// Check the checkbox in the given column header + /// + /// If the given columns header check box is linked to the cell check boxes, + /// then checkboxes in all cells will also be checked. + /// + public virtual void CheckHeaderCheckBox(OLVColumn column) + { + if (column == null) + return; + + ChangeHeaderCheckBoxState(column, CheckState.Checked); + } + + /// + /// Mark the checkbox in the given column header as having an indeterminate value + /// + /// + public virtual void CheckIndeterminateHeaderCheckBox(OLVColumn column) + { + if (column == null) + return; + + ChangeHeaderCheckBoxState(column, CheckState.Indeterminate); + } + + /// + /// Mark the given object as indeterminate check state + /// + /// The model object to be marked indeterminate + public virtual void CheckIndeterminateObject(object modelObject) { + this.SetObjectCheckedness(modelObject, CheckState.Indeterminate); + } + + /// + /// Mark the given object as checked in the list + /// + /// The model object to be checked + public virtual void CheckObject(object modelObject) { + this.SetObjectCheckedness(modelObject, CheckState.Checked); + } + + /// + /// Mark the given objects as checked in the list + /// + /// The model object to be checked + public virtual void CheckObjects(IEnumerable modelObjects) { + foreach (object model in modelObjects) + this.CheckObject(model); + } + + /// + /// Put a check into the check box at the given cell + /// + /// + /// + public virtual void CheckSubItem(object rowObject, OLVColumn column) { + if (column == null || rowObject == null || !column.CheckBoxes) + return; + + column.PutCheckState(rowObject, CheckState.Checked); + this.RefreshObject(rowObject); + } + + /// + /// Put an indeterminate check into the check box at the given cell + /// + /// + /// + public virtual void CheckIndeterminateSubItem(object rowObject, OLVColumn column) { + if (column == null || rowObject == null || !column.CheckBoxes) + return; + + column.PutCheckState(rowObject, CheckState.Indeterminate); + this.RefreshObject(rowObject); + } + + /// + /// Return true of the given object is checked + /// + /// The model object whose checkedness is returned + /// Is the given object checked? + /// If the given object is not in the list, this method returns false. + public virtual bool IsChecked(object modelObject) { + return this.GetCheckState(modelObject) == CheckState.Checked; + } + + /// + /// Return true of the given object is indeterminately checked + /// + /// The model object whose checkedness is returned + /// Is the given object indeterminately checked? + /// If the given object is not in the list, this method returns false. + public virtual bool IsCheckedIndeterminate(object modelObject) { + return this.GetCheckState(modelObject) == CheckState.Indeterminate; + } + + /// + /// Is there a check at the check box at the given cell + /// + /// + /// + public virtual bool IsSubItemChecked(object rowObject, OLVColumn column) { + if (column == null || rowObject == null || !column.CheckBoxes) + return false; + return (column.GetCheckState(rowObject) == CheckState.Checked); + } + + /// + /// Get the checkedness of an object from the model. Returning null means the + /// model does not know and the value from the control will be used. + /// + /// + /// + protected virtual CheckState? GetCheckState(Object modelObject) { + if (this.CheckStateGetter != null) + return this.CheckStateGetter(modelObject); + return this.PersistentCheckBoxes ? this.GetPersistentCheckState(modelObject) : (CheckState?)null; + } + + /// + /// Record the change of checkstate for the given object in the model. + /// This does not update the UI -- only the model + /// + /// + /// + /// The check state that was recorded and that should be used to update + /// the control. + protected virtual CheckState PutCheckState(Object modelObject, CheckState state) { + if (this.CheckStatePutter != null) + return this.CheckStatePutter(modelObject, state); + return this.PersistentCheckBoxes ? this.SetPersistentCheckState(modelObject, state) : state; + } + + /// + /// Change the check state of the given object to be the given state. + /// + /// + /// If the given model object isn't in the list, we still try to remember + /// its state, in case it is referenced in the future. + /// + /// + /// True if the checkedness of the model changed + protected virtual bool SetObjectCheckedness(object modelObject, CheckState state) { + + if (GetCheckState(modelObject) == state) + return false; + + OLVListItem olvi = this.ModelToItem(modelObject); + + // If we didn't find the given, we still try to record the check state. + if (olvi == null) { + this.PutCheckState(modelObject, state); + return true; + } + + // Trigger checkbox changing event + ItemCheckEventArgs ice = new ItemCheckEventArgs(olvi.Index, state, olvi.CheckState); + this.OnItemCheck(ice); + if (ice.NewValue == olvi.CheckState) + return false; + + olvi.CheckState = this.PutCheckState(modelObject, state); + this.RefreshItem(olvi); + + // Trigger check changed event + this.OnItemChecked(new ItemCheckedEventArgs(olvi)); + return true; + } + + /// + /// Toggle the checkedness of the given object. A checked object becomes + /// unchecked; an unchecked or indeterminate object becomes checked. + /// If the list has tristate checkboxes, the order is: + /// unchecked -> checked -> indeterminate -> unchecked ... + /// + /// The model object to be checked + public virtual void ToggleCheckObject(object modelObject) { + OLVListItem olvi = this.ModelToItem(modelObject); + if (olvi == null) + return; + + CheckState newState = CheckState.Checked; + + if (olvi.CheckState == CheckState.Checked) { + newState = this.TriStateCheckBoxes ? CheckState.Indeterminate : CheckState.Unchecked; + } else { + if (olvi.CheckState == CheckState.Indeterminate && this.TriStateCheckBoxes) + newState = CheckState.Unchecked; + } + this.SetObjectCheckedness(modelObject, newState); + } + + /// + /// Toggle the checkbox in the header of the given column + /// + /// Obviously, this is only useful if the column actually has a header checkbox. + /// + public virtual void ToggleHeaderCheckBox(OLVColumn column) { + if (column == null) + return; + + CheckState newState = CalculateToggledCheckState(column.HeaderCheckState, column.HeaderTriStateCheckBox, column.HeaderCheckBoxDisabled); + ChangeHeaderCheckBoxState(column, newState); + } + + private void ChangeHeaderCheckBoxState(OLVColumn column, CheckState newState) { + // Tell the world the checkbox was clicked + HeaderCheckBoxChangingEventArgs args = new HeaderCheckBoxChangingEventArgs(); + args.Column = column; + args.NewCheckState = newState; + + this.OnHeaderCheckBoxChanging(args); + if (args.Cancel || column.HeaderCheckState == args.NewCheckState) + return; + + Stopwatch sw = Stopwatch.StartNew(); + + column.HeaderCheckState = args.NewCheckState; + this.HeaderControl.Invalidate(column); + + if (column.HeaderCheckBoxUpdatesRowCheckBoxes) { + if (column.Index == 0) + this.UpdateAllPrimaryCheckBoxes(column); + else + this.UpdateAllSubItemCheckBoxes(column); + } + + // Debug.WriteLine(String.Format("PERF - Changing row checkboxes on {2} objects took {0}ms / {1} ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks, this.GetItemCount())); + } + + private void UpdateAllPrimaryCheckBoxes(OLVColumn column) { + if (!this.CheckBoxes || column.HeaderCheckState == CheckState.Indeterminate) + return; + + if (column.HeaderCheckState == CheckState.Checked) + CheckAll(); + else + UncheckAll(); + } + + private void UpdateAllSubItemCheckBoxes(OLVColumn column) { + if (!column.CheckBoxes || column.HeaderCheckState == CheckState.Indeterminate) + return; + + foreach (object model in this.Objects) + column.PutCheckState(model, column.HeaderCheckState); + this.BuildList(true); + } + + /// + /// Toggle the check at the check box of the given cell + /// + /// + /// + public virtual void ToggleSubItemCheckBox(object rowObject, OLVColumn column) { + CheckState currentState = column.GetCheckState(rowObject); + CheckState newState = CalculateToggledCheckState(currentState, column.TriStateCheckBoxes, false); + + SubItemCheckingEventArgs args = new SubItemCheckingEventArgs(column, this.ModelToItem(rowObject), column.Index, currentState, newState); + this.OnSubItemChecking(args); + if (args.Canceled) + return; + + switch (args.NewValue) { + case CheckState.Checked: + this.CheckSubItem(rowObject, column); + break; + case CheckState.Indeterminate: + this.CheckIndeterminateSubItem(rowObject, column); + break; + case CheckState.Unchecked: + this.UncheckSubItem(rowObject, column); + break; + } + } + + /// + /// Uncheck all rows + /// + public virtual void UncheckAll() + { + this.CheckedObjects = null; + } + + /// + /// Mark the given object as unchecked in the list + /// + /// The model object to be unchecked + public virtual void UncheckObject(object modelObject) { + this.SetObjectCheckedness(modelObject, CheckState.Unchecked); + } + + /// + /// Mark the given objects as unchecked in the list + /// + /// The model object to be checked + public virtual void UncheckObjects(IEnumerable modelObjects) { + foreach (object model in modelObjects) + this.UncheckObject(model); + } + + /// + /// Uncheck the checkbox in the given column header + /// + /// + public virtual void UncheckHeaderCheckBox(OLVColumn column) + { + if (column == null) + return; + + ChangeHeaderCheckBoxState(column, CheckState.Unchecked); + } + + /// + /// Uncheck the check at the given cell + /// + /// + /// + public virtual void UncheckSubItem(object rowObject, OLVColumn column) + { + if (column == null || rowObject == null || !column.CheckBoxes) + return; + + column.PutCheckState(rowObject, CheckState.Unchecked); + this.RefreshObject(rowObject); + } + + #endregion + + #region OLV accessing + + /// + /// Return the column at the given index + /// + /// Index of the column to be returned + /// An OLVColumn, or null if the index is out of bounds + public virtual OLVColumn GetColumn(int index) { + return (index >=0 && index < this.Columns.Count) ? (OLVColumn)this.Columns[index] : null; + } + + /// + /// Return the column at the given title. + /// + /// Name of the column to be returned + /// An OLVColumn + public virtual OLVColumn GetColumn(string name) { + foreach (ColumnHeader column in this.Columns) { + if (column.Text == name) + return (OLVColumn)column; + } + return null; + } + + /// + /// Return a collection of columns that are visible to the given view. + /// Only Tile and Details have columns; all other views have 0 columns. + /// + /// Which view are the columns being calculate for? + /// A list of columns + public virtual List GetFilteredColumns(View view) { + // For both detail and tile view, the first column must be included. Normally, we would + // use the ColumnHeader.Index property, but if the header is not currently part of a ListView + // that property returns -1. So, we track the index of + // the column header, and always include the first header. + + int index = 0; + return this.AllColumns.FindAll(delegate(OLVColumn x) { + return (index++ == 0) || x.IsVisible; + }); + } + + /// + /// Return the number of items in the list + /// + /// the number of items in the list + /// If a filter is installed, this will return the number of items that match the filter. + public virtual int GetItemCount() { + return this.Items.Count; + } + + /// + /// Return the item at the given index + /// + /// Index of the item to be returned + /// An OLVListItem + public virtual OLVListItem GetItem(int index) { + if (index < 0 || index >= this.GetItemCount()) + return null; + + return (OLVListItem)this.Items[index]; + } + + /// + /// Return the model object at the given index + /// + /// Index of the model object to be returned + /// A model object + public virtual object GetModelObject(int index) { + OLVListItem item = this.GetItem(index); + return item == null ? null : item.RowObject; + } + + /// + /// Find the item and column that are under the given co-ords + /// + /// X co-ord + /// Y co-ord + /// The column under the given point + /// The item under the given point. Can be null. + public virtual OLVListItem GetItemAt(int x, int y, out OLVColumn hitColumn) { + hitColumn = null; + ListViewHitTestInfo info = this.HitTest(x, y); + if (info.Item == null) + return null; + + if (info.SubItem != null) { + int subItemIndex = info.Item.SubItems.IndexOf(info.SubItem); + hitColumn = this.GetColumn(subItemIndex); + } + + return (OLVListItem)info.Item; + } + + /// + /// Return the sub item at the given index/column + /// + /// Index of the item to be returned + /// Index of the subitem to be returned + /// An OLVListSubItem + public virtual OLVListSubItem GetSubItem(int index, int columnIndex) { + OLVListItem olvi = this.GetItem(index); + return olvi == null ? null : olvi.GetSubItem(columnIndex); + } + + #endregion + + #region Object manipulation + + /// + /// Scroll the listview so that the given group is at the top. + /// + /// The group to be revealed + /// + /// If the group is already visible, the list will still be scrolled to move + /// the group to the top, if that is possible. + /// + /// This only works when the list is showing groups (obviously). + /// This does not work on virtual lists, since virtual lists don't use ListViewGroups + /// for grouping. Use instead. + /// + public virtual void EnsureGroupVisible(ListViewGroup lvg) { + if (!this.ShowGroups || lvg == null) + return; + + int groupIndex = this.Groups.IndexOf(lvg); + if (groupIndex <= 0) { + // There is no easy way to scroll back to the beginning of the list + int delta = 0 - NativeMethods.GetScrollPosition(this, false); + NativeMethods.Scroll(this, 0, delta); + } else { + // Find the display rectangle of the last item in the previous group + ListViewGroup previousGroup = this.Groups[groupIndex - 1]; + ListViewItem lastItemInGroup = previousGroup.Items[previousGroup.Items.Count - 1]; + Rectangle r = this.GetItemRect(lastItemInGroup.Index); + + // Scroll so that the last item of the previous group is just out of sight, + // which will make the desired group header visible. + int delta = r.Y + r.Height / 2; + NativeMethods.Scroll(this, 0, delta); + } + } + + /// + /// Ensure that the given model object is visible + /// + /// The model object to be revealed + public virtual void EnsureModelVisible(Object modelObject) { + int index = this.IndexOf(modelObject); + if (index >= 0) + this.EnsureVisible(index); + } + + /// + /// Return the model object of the row that is selected or null if there is no selection or more than one selection + /// + /// Model object or null + [Obsolete("Use SelectedObject property instead of this method")] + public virtual object GetSelectedObject() { + return this.SelectedObject; + } + + /// + /// Return the model objects of the rows that are selected or an empty collection if there is no selection + /// + /// ArrayList + [Obsolete("Use SelectedObjects property instead of this method")] + public virtual ArrayList GetSelectedObjects() { + return ObjectListView.EnumerableToArray(this.SelectedObjects, false); + } + + /// + /// Return the model object of the row that is checked or null if no row is checked + /// or more than one row is checked + /// + /// Model object or null + /// Use CheckedObject property instead of this method + [Obsolete("Use CheckedObject property instead of this method")] + public virtual object GetCheckedObject() { + return this.CheckedObject; + } + + /// + /// Get the collection of model objects that are checked. + /// + /// Use CheckedObjects property instead of this method + [Obsolete("Use CheckedObjects property instead of this method")] + public virtual ArrayList GetCheckedObjects() { + return ObjectListView.EnumerableToArray(this.CheckedObjects, false); + } + + /// + /// Find the given model object within the listview and return its index + /// + /// The model object to be found + /// The index of the object. -1 means the object was not present + public virtual int IndexOf(Object modelObject) { + for (int i = 0; i < this.GetItemCount(); i++) { + if (this.GetModelObject(i).Equals(modelObject)) + return i; + } + return -1; + } + + /// + /// Rebuild the given ListViewItem with the data from its associated model. + /// + /// This method does not resort or regroup the view. It simply updates + /// the displayed data of the given item + public virtual void RefreshItem(OLVListItem olvi) { + olvi.UseItemStyleForSubItems = true; + olvi.SubItems.Clear(); + this.FillInValues(olvi, olvi.RowObject); + this.PostProcessOneRow(olvi.Index, this.GetDisplayOrderOfItemIndex(olvi.Index), olvi); + } + + /// + /// Rebuild the data on the row that is showing the given object. + /// + /// + /// + /// This method does not resort or regroup the view. + /// + /// + /// The given object is *not* used as the source of data for the rebuild. + /// It is only used to locate the matching model in the collection, + /// then that matching model is used as the data source. This distinction is + /// only important in model classes that have overridden the Equals() method. + /// + /// + /// If you want the given model object to replace the pre-existing model, + /// use . + /// + /// + public virtual void RefreshObject(object modelObject) { + this.RefreshObjects(new object[] { modelObject }); + } + + /// + /// Update the rows that are showing the given objects + /// + /// + /// This method does not resort or regroup the view. + /// This method can safely be called from background threads. + /// + public virtual void RefreshObjects(IList modelObjects) { + if (this.InvokeRequired) { + this.Invoke((MethodInvoker)delegate { this.RefreshObjects(modelObjects); }); + return; + } + foreach (object modelObject in modelObjects) { + OLVListItem olvi = this.ModelToItem(modelObject); + if (olvi != null) { + this.ReplaceModel(olvi, modelObject); + this.RefreshItem(olvi); + } + } + } + + private void ReplaceModel(OLVListItem olvi, object newModel) { + if (ReferenceEquals(olvi.RowObject, newModel)) + return; + + this.TakeOwnershipOfObjects(); + ArrayList array = ObjectListView.EnumerableToArray(this.Objects, false); + int i = array.IndexOf(olvi.RowObject); + if (i >= 0) + array[i] = newModel; + + olvi.RowObject = newModel; + } + + /// + /// Update the rows that are selected + /// + /// This method does not resort or regroup the view. + public virtual void RefreshSelectedObjects() { + foreach (ListViewItem lvi in this.SelectedItems) + this.RefreshItem((OLVListItem)lvi); + } + + /// + /// Select the row that is displaying the given model object, in addition to any current selection. + /// + /// The object to be selected + /// Use the property to deselect all other rows + public virtual void SelectObject(object modelObject) { + this.SelectObject(modelObject, false); + } + + /// + /// Select the row that is displaying the given model object, in addition to any current selection. + /// + /// The object to be selected + /// Should the object be focused as well? + /// Use the property to deselect all other rows + public virtual void SelectObject(object modelObject, bool setFocus) { + OLVListItem olvi = this.ModelToItem(modelObject); + if (olvi != null && olvi.Enabled) { + olvi.Selected = true; + if (setFocus) + olvi.Focused = true; + } + } + + /// + /// Select the rows that is displaying any of the given model object. All other rows are deselected. + /// + /// A collection of model objects + public virtual void SelectObjects(IList modelObjects) { + this.SelectedIndices.Clear(); + + if (modelObjects == null) + return; + + foreach (object modelObject in modelObjects) { + OLVListItem olvi = this.ModelToItem(modelObject); + if (olvi != null && olvi.Enabled) + olvi.Selected = true; + } + } + + #endregion + + #region Freezing/Suspending + + /// + /// Get or set whether or not the listview is frozen. When the listview is + /// frozen, it will not update itself. + /// + /// The Frozen property is similar to the methods Freeze()/Unfreeze() + /// except that setting Frozen property to false immediately unfreezes the control + /// regardless of the number of Freeze() calls outstanding. + /// objectListView1.Frozen = false; // unfreeze the control now! + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual bool Frozen { + get { return freezeCount > 0; } + set { + if (value) + Freeze(); + else if (freezeCount > 0) { + freezeCount = 1; + Unfreeze(); + } + } + } + private int freezeCount; + + /// + /// Freeze the listview so that it no longer updates itself. + /// + /// Freeze()/Unfreeze() calls nest correctly + public virtual void Freeze() { + if (freezeCount == 0) + DoFreeze(); + + freezeCount++; + this.OnFreezing(new FreezeEventArgs(freezeCount)); + } + + /// + /// Unfreeze the listview. If this call is the outermost Unfreeze(), + /// the contents of the listview will be rebuilt. + /// + /// Freeze()/Unfreeze() calls nest correctly + public virtual void Unfreeze() { + if (freezeCount <= 0) + return; + + freezeCount--; + if (freezeCount == 0) + DoUnfreeze(); + + this.OnFreezing(new FreezeEventArgs(freezeCount)); + } + + /// + /// Do the actual work required when the listview is frozen + /// + protected virtual void DoFreeze() { + this.BeginUpdate(); + } + + /// + /// Do the actual work required when the listview is unfrozen + /// + protected virtual void DoUnfreeze() + { + this.EndUpdate(); + this.ResizeFreeSpaceFillingColumns(); + this.BuildList(); + } + + /// + /// Returns true if selection events are currently suspended. + /// While selection events are suspended, neither SelectedIndexChanged + /// or SelectionChanged events will be raised. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + protected bool SelectionEventsSuspended { + get { return this.suspendSelectionEventCount > 0; } + } + + /// + /// Suspend selection events until a matching ResumeSelectionEvents() + /// is called. + /// + /// Calls to this method nest correctly. Every call to SuspendSelectionEvents() + /// must have a matching ResumeSelectionEvents(). + protected void SuspendSelectionEvents() { + this.suspendSelectionEventCount++; + } + + /// + /// Resume raising selection events. + /// + protected void ResumeSelectionEvents() { + Debug.Assert(this.SelectionEventsSuspended, "Mismatched called to ResumeSelectionEvents()"); + this.suspendSelectionEventCount--; + } + + /// + /// Returns a disposable that will disable selection events + /// during a using() block. + /// + /// + protected IDisposable SuspendSelectionEventsDuring() { + return new SuspendSelectionDisposable(this); + } + + /// + /// Implementation only class that suspends and resumes selection + /// events on instance creation and disposal. + /// + private class SuspendSelectionDisposable : IDisposable { + public SuspendSelectionDisposable(ObjectListView objectListView) { + this.objectListView = objectListView; + this.objectListView.SuspendSelectionEvents(); + } + + public void Dispose() { + this.objectListView.ResumeSelectionEvents(); + } + + private readonly ObjectListView objectListView; + } + + #endregion + + #region Column sorting + + /// + /// Sort the items by the last sort column and order + /// + public new void Sort() { + this.Sort(this.PrimarySortColumn, this.PrimarySortOrder); + } + + /// + /// Sort the items in the list view by the values in the given column and the last sort order + /// + /// The name of the column whose values will be used for the sorting + public virtual void Sort(string columnToSortName) { + this.Sort(this.GetColumn(columnToSortName), this.PrimarySortOrder); + } + + /// + /// Sort the items in the list view by the values in the given column and the last sort order + /// + /// The index of the column whose values will be used for the sorting + public virtual void Sort(int columnToSortIndex) { + if (columnToSortIndex >= 0 && columnToSortIndex < this.Columns.Count) + this.Sort(this.GetColumn(columnToSortIndex), this.PrimarySortOrder); + } + + /// + /// Sort the items in the list view by the values in the given column and the last sort order + /// + /// The column whose values will be used for the sorting + public virtual void Sort(OLVColumn columnToSort) { + if (this.InvokeRequired) { + this.Invoke((MethodInvoker)delegate { this.Sort(columnToSort); }); + } else { + this.Sort(columnToSort, this.PrimarySortOrder); + } + } + + /// + /// Sort the items in the list view by the values in the given column and by the given order. + /// + /// The column whose values will be used for the sorting. + /// If null, the first column will be used. + /// The ordering to be used for sorting. If this is None, + /// this.Sorting and then SortOrder.Ascending will be used + /// If ShowGroups is true, the rows will be grouped by the given column. + /// If AlwaysGroupsByColumn is not null, the rows will be grouped by that column, + /// and the rows within each group will be sorted by the given column. + public virtual void Sort(OLVColumn columnToSort, SortOrder order) { + if (this.InvokeRequired) { + this.Invoke((MethodInvoker)delegate { this.Sort(columnToSort, order); }); + } else { + this.DoSort(columnToSort, order); + this.PostProcessRows(); + } + } + + private void DoSort(OLVColumn columnToSort, SortOrder order) { + // Sanity checks + if (this.GetItemCount() == 0 || this.Columns.Count == 0) + return; + + // Fill in default values, if the parameters don't make sense + if (this.ShowGroups) { + columnToSort = columnToSort ?? this.GetColumn(0); + if (order == SortOrder.None) { + order = this.Sorting; + if (order == SortOrder.None) + order = SortOrder.Ascending; + } + } + + // Give the world a chance to fiddle with or completely avoid the sorting process + BeforeSortingEventArgs args = this.BuildBeforeSortingEventArgs(columnToSort, order); + this.OnBeforeSorting(args); + if (args.Canceled) + return; + + // Virtual lists don't preserve selection, so we have to do it specifically + // THINK: Do we need to preserve focus too? + IList selection = this.VirtualMode ? this.SelectedObjects : null; + this.SuspendSelectionEvents(); + + this.ClearHotItem(); + + // Finally, do the work of sorting, unless an event handler has already done the sorting for us + if (!args.Handled) { + // Sanity checks + if (args.ColumnToSort != null && args.SortOrder != SortOrder.None) { + if (this.ShowGroups) + this.BuildGroups(args.ColumnToGroupBy, args.GroupByOrder, args.ColumnToSort, args.SortOrder, + args.SecondaryColumnToSort, args.SecondarySortOrder); + else if (this.CustomSorter != null) + this.CustomSorter(args.ColumnToSort, args.SortOrder); + else + this.ListViewItemSorter = new ColumnComparer(args.ColumnToSort, args.SortOrder, + args.SecondaryColumnToSort, args.SecondarySortOrder); + } + } + + if (this.ShowSortIndicators) + this.ShowSortIndicator(args.ColumnToSort, args.SortOrder); + + this.PrimarySortColumn = args.ColumnToSort; + this.PrimarySortOrder = args.SortOrder; + + if (selection != null && selection.Count > 0) + this.SelectedObjects = selection; + this.ResumeSelectionEvents(); + + this.RefreshHotItem(); + + this.OnAfterSorting(new AfterSortingEventArgs(args)); + } + + /// + /// Put a sort indicator next to the text of the sort column + /// + public virtual void ShowSortIndicator() { + if (this.ShowSortIndicators && this.PrimarySortOrder != SortOrder.None) + this.ShowSortIndicator(this.PrimarySortColumn, this.PrimarySortOrder); + } + + /// + /// Put a sort indicator next to the text of the given column + /// + /// The column to be marked + /// The sort order in effect on that column + protected virtual void ShowSortIndicator(OLVColumn columnToSort, SortOrder sortOrder) { + int imageIndex = -1; + + if (!NativeMethods.HasBuiltinSortIndicators()) { + // If we can't use builtin image, we have to make and then locate the index of the + // sort indicator we want to use. SortOrder.None doesn't show an image. + if (this.SmallImageList == null || !this.SmallImageList.Images.ContainsKey(SORT_INDICATOR_UP_KEY)) + MakeSortIndicatorImages(); + + if (this.SmallImageList != null) + { + string key = sortOrder == SortOrder.Ascending ? SORT_INDICATOR_UP_KEY : SORT_INDICATOR_DOWN_KEY; + imageIndex = this.SmallImageList.Images.IndexOfKey(key); + } + } + + // Set the image for each column + for (int i = 0; i < this.Columns.Count; i++) { + if (columnToSort != null && i == columnToSort.Index) + NativeMethods.SetColumnImage(this, i, sortOrder, imageIndex); + else + NativeMethods.SetColumnImage(this, i, SortOrder.None, -1); + } + } + + /// + /// The name of the image used when a column is sorted ascending + /// + /// This image is only used on pre-XP systems. System images are used for XP and later + public const string SORT_INDICATOR_UP_KEY = "sort-indicator-up"; + + /// + /// The name of the image used when a column is sorted descending + /// + /// This image is only used on pre-XP systems. System images are used for XP and later + public const string SORT_INDICATOR_DOWN_KEY = "sort-indicator-down"; + + /// + /// If the sort indicator images don't already exist, this method will make and install them + /// + protected virtual void MakeSortIndicatorImages() { + // Don't mess with the image list in design mode + if (this.DesignMode) + return; + + ImageList il = this.SmallImageList; + if (il == null) { + il = new ImageList(); + il.ImageSize = new Size(16, 16); + il.ColorDepth = ColorDepth.Depth32Bit; + } + + // This arrangement of points works well with (16,16) images, and OK with others + int midX = il.ImageSize.Width / 2; + int midY = (il.ImageSize.Height / 2) - 1; + int deltaX = midX - 2; + int deltaY = deltaX / 2; + + if (il.Images.IndexOfKey(SORT_INDICATOR_UP_KEY) == -1) { + Point pt1 = new Point(midX - deltaX, midY + deltaY); + Point pt2 = new Point(midX, midY - deltaY - 1); + Point pt3 = new Point(midX + deltaX, midY + deltaY); + il.Images.Add(SORT_INDICATOR_UP_KEY, this.MakeTriangleBitmap(il.ImageSize, new Point[] { pt1, pt2, pt3 })); + } + + if (il.Images.IndexOfKey(SORT_INDICATOR_DOWN_KEY) == -1) { + Point pt1 = new Point(midX - deltaX, midY - deltaY); + Point pt2 = new Point(midX, midY + deltaY); + Point pt3 = new Point(midX + deltaX, midY - deltaY); + il.Images.Add(SORT_INDICATOR_DOWN_KEY, this.MakeTriangleBitmap(il.ImageSize, new Point[] { pt1, pt2, pt3 })); + } + + this.SmallImageList = il; + } + + private Bitmap MakeTriangleBitmap(Size sz, Point[] pts) { + Bitmap bm = new Bitmap(sz.Width, sz.Height); + Graphics g = Graphics.FromImage(bm); + g.FillPolygon(new SolidBrush(Color.Gray), pts); + return bm; + } + + /// + /// Remove any sorting and revert to the given order of the model objects + /// + public virtual void Unsort() { + this.ShowGroups = false; + this.PrimarySortColumn = null; + this.PrimarySortOrder = SortOrder.None; + this.BuildList(); + } + + #endregion + + #region Utilities + + private static CheckState CalculateToggledCheckState(CheckState currentState, bool isTriState, bool isDisabled) + { + if (isDisabled) + return currentState; + switch (currentState) + { + case CheckState.Checked: return isTriState ? CheckState.Indeterminate : CheckState.Unchecked; + case CheckState.Indeterminate: return CheckState.Unchecked; + default: return CheckState.Checked; + } + } + + /// + /// Do the actual work of creating the given list of groups + /// + /// + protected virtual void CreateGroups(IEnumerable groups) { + this.Groups.Clear(); + // The group must be added before it is given items, otherwise an exception is thrown (is this documented?) + foreach (OLVGroup group in groups) { + group.InsertGroupOldStyle(this); + group.SetItemsOldStyle(); + } + } + + /// + /// For some reason, UseItemStyleForSubItems doesn't work for the colors + /// when owner drawing the list, so we have to specifically give each subitem + /// the desired colors + /// + /// The item whose subitems are to be corrected + /// Cells drawn via BaseRenderer don't need this, but it is needed + /// when an owner drawn cell uses DrawDefault=true + protected virtual void CorrectSubItemColors(ListViewItem olvi) { + } + + /// + /// Fill in the given OLVListItem with values of the given row + /// + /// the OLVListItem that is to be stuff with values + /// the model object from which values will be taken + protected virtual void FillInValues(OLVListItem lvi, object rowObject) { + if (this.Columns.Count == 0) + return; + + OLVListSubItem subItem = this.MakeSubItem(rowObject, this.GetColumn(0)); + lvi.SubItems[0] = subItem; + lvi.ImageSelector = subItem.ImageSelector; + + // Give the item the same font/colors as the control + lvi.Font = this.Font; + lvi.BackColor = this.BackColor; + lvi.ForeColor = this.ForeColor; + + // Should the row be selectable? + lvi.Enabled = !this.IsDisabled(rowObject); + + // Only Details and Tile views have subitems + switch (this.View) { + case View.Details: + for (int i = 1; i < this.Columns.Count; i++) { + lvi.SubItems.Add(this.MakeSubItem(rowObject, this.GetColumn(i))); + } + break; + case View.Tile: + for (int i = 1; i < this.Columns.Count; i++) { + OLVColumn column = this.GetColumn(i); + if (column.IsTileViewColumn) + lvi.SubItems.Add(this.MakeSubItem(rowObject, column)); + } + break; + } + + // Should the row be selectable? + if (!lvi.Enabled) { + lvi.UseItemStyleForSubItems = false; + ApplyRowStyle(lvi, this.DisabledItemStyle ?? ObjectListView.DefaultDisabledItemStyle); + } + + // Set the check state of the row, if we are showing check boxes + if (this.CheckBoxes) { + CheckState? state = this.GetCheckState(lvi.RowObject); + if (state.HasValue) + lvi.CheckState = state.Value; + } + + // Give the RowFormatter a chance to mess with the item + if (this.RowFormatter != null) { + this.RowFormatter(lvi); + } + } + + private OLVListSubItem MakeSubItem(object rowObject, OLVColumn column) { + object cellValue = column.GetValue(rowObject); + OLVListSubItem subItem = new OLVListSubItem(cellValue, + column.ValueToString(cellValue), + column.GetImage(rowObject)); + if (this.UseHyperlinks && column.Hyperlink) { + IsHyperlinkEventArgs args = new IsHyperlinkEventArgs(); + args.ListView = this; + args.Model = rowObject; + args.Column = column; + args.Text = subItem.Text; + args.Url = subItem.Text; + args.IsHyperlink = !this.IsDisabled(rowObject); + this.OnIsHyperlink(args); + subItem.Url = args.IsHyperlink ? args.Url : null; + } + + return subItem; + } + + private void ApplyHyperlinkStyle(OLVListItem olvi) { + + for (int i = 0; i < this.Columns.Count; i++) { + OLVListSubItem subItem = olvi.GetSubItem(i); + if (subItem == null) + continue; + OLVColumn column = this.GetColumn(i); + if (column.Hyperlink && !String.IsNullOrEmpty(subItem.Url)) + this.ApplyCellStyle(olvi, i, this.IsUrlVisited(subItem.Url) ? this.HyperlinkStyle.Visited : this.HyperlinkStyle.Normal); + } + } + + + /// + /// Make sure the ListView has the extended style that says to display subitem images. + /// + /// This method must be called after any .NET call that update the extended styles + /// since they seem to erase this setting. + protected virtual void ForceSubItemImagesExStyle() { + // Virtual lists can't show subitem images natively, so don't turn on this flag + if (!this.VirtualMode) + NativeMethods.ForceSubItemImagesExStyle(this); + } + + /// + /// Convert the given image selector to an index into our image list. + /// Return -1 if that's not possible + /// + /// + /// Index of the image in the imageList, or -1 + protected virtual int GetActualImageIndex(Object imageSelector) { + if (imageSelector == null) + return -1; + + if (imageSelector is Int32) + return (int)imageSelector; + + String imageSelectorAsString = imageSelector as String; + if (imageSelectorAsString != null && this.SmallImageList != null) + return this.SmallImageList.Images.IndexOfKey(imageSelectorAsString); + + return -1; + } + + /// + /// Return the tooltip that should be shown when the mouse is hovered over the given column + /// + /// The column index whose tool tip is to be fetched + /// A string or null if no tool tip is to be shown + public virtual String GetHeaderToolTip(int columnIndex) { + OLVColumn column = this.GetColumn(columnIndex); + if (column == null) + return null; + String tooltip = column.ToolTipText; + if (this.HeaderToolTipGetter != null) + tooltip = this.HeaderToolTipGetter(column); + return tooltip; + } + + /// + /// Return the tooltip that should be shown when the mouse is hovered over the given cell + /// + /// The column index whose tool tip is to be fetched + /// The row index whose tool tip is to be fetched + /// A string or null if no tool tip is to be shown + public virtual String GetCellToolTip(int columnIndex, int rowIndex) { + if (this.CellToolTipGetter != null) + return this.CellToolTipGetter(this.GetColumn(columnIndex), this.GetModelObject(rowIndex)); + + // Show the URL in the tooltip if it's different to the text + if (columnIndex >= 0) { + OLVListSubItem subItem = this.GetSubItem(rowIndex, columnIndex); + if (subItem != null && !String.IsNullOrEmpty(subItem.Url) && subItem.Url != subItem.Text && + this.HotCellHitLocation == HitTestLocation.Text) + return subItem.Url; + } + + return null; + } + + /// + /// Return the OLVListItem that displays the given model object + /// + /// The modelObject whose item is to be found + /// The OLVListItem that displays the model, or null + /// This method has O(n) performance. + public virtual OLVListItem ModelToItem(object modelObject) { + if (modelObject == null) + return null; + + foreach (OLVListItem olvi in this.Items) { + if (olvi.RowObject != null && olvi.RowObject.Equals(modelObject)) + return olvi; + } + return null; + } + + /// + /// Do the work required after the items in a listview have been created + /// + protected virtual void PostProcessRows() { + // If this method is called during a BeginUpdate/EndUpdate pair, changes to the + // Items collection are cached. Getting the Count flushes that cache. +#pragma warning disable 168 +// ReSharper disable once UnusedVariable + int count = this.Items.Count; +#pragma warning restore 168 + + int i = 0; + if (this.ShowGroups) { + foreach (ListViewGroup group in this.Groups) { + foreach (OLVListItem olvi in group.Items) { + this.PostProcessOneRow(olvi.Index, i, olvi); + i++; + } + } + } else { + foreach (OLVListItem olvi in this.Items) { + this.PostProcessOneRow(olvi.Index, i, olvi); + i++; + } + } + } + + /// + /// Do the work required after one item in a listview have been created + /// + protected virtual void PostProcessOneRow(int rowIndex, int displayIndex, OLVListItem olvi) { + if (this.Columns.Count == 0) + return; + if (this.UseAlternatingBackColors && this.View == View.Details && olvi.Enabled) { + olvi.UseItemStyleForSubItems = true; + olvi.BackColor = displayIndex % 2 == 1 ? this.AlternateRowBackColorOrDefault : this.BackColor; + } + if (this.ShowImagesOnSubItems && !this.VirtualMode) + this.SetSubItemImages(rowIndex, olvi); + + bool needToTriggerFormatCellEvents = this.TriggerFormatRowEvent(rowIndex, displayIndex, olvi); + + // We only need cell level events if we are in details view + if (this.View != View.Details) + return; + + // If we're going to have per cell formatting, we need to copy the formatting + // of the item into each cell, before triggering the cell format events + if (needToTriggerFormatCellEvents) { + PropagateFormatFromRowToCells(olvi); + this.TriggerFormatCellEvents(rowIndex, displayIndex, olvi); + } + + // Similarly, if any cell in the row has hyperlinks, we have to copy formatting + // from the item into each cell before applying the hyperlink style + if (this.UseHyperlinks && olvi.HasAnyHyperlinks) { + PropagateFormatFromRowToCells(olvi); + this.ApplyHyperlinkStyle(olvi); + } + } + + /// + /// Prepare the listview to show alternate row backcolors + /// + /// We cannot rely on lvi.Index in this method. + /// In a straight list, lvi.Index is the display index, and can be used to determine + /// whether the row should be colored. But when organised by groups, lvi.Index is not + /// usable because it still refers to the position in the overall list, not the display order. + /// + [Obsolete("This method is no longer used. Override PostProcessOneRow() to achieve a similar result")] + protected virtual void PrepareAlternateBackColors() { + } + + /// + /// Setup all subitem images on all rows + /// + [Obsolete("This method is not longer maintained and will be removed", false)] + protected virtual void SetAllSubItemImages() { + //if (!this.ShowImagesOnSubItems || this.OwnerDraw) + // return; + + //this.ForceSubItemImagesExStyle(); + + //for (int rowIndex = 0; rowIndex < this.GetItemCount(); rowIndex++) + // SetSubItemImages(rowIndex, this.GetItem(rowIndex)); + } + + /// + /// Tell the underlying list control which images to show against the subitems + /// + /// the index at which the item occurs + /// the item whose subitems are to be set + protected virtual void SetSubItemImages(int rowIndex, OLVListItem item) { + this.SetSubItemImages(rowIndex, item, false); + } + + /// + /// Tell the underlying list control which images to show against the subitems + /// + /// the index at which the item occurs + /// the item whose subitems are to be set + /// will existing images be cleared if no new image is provided? + protected virtual void SetSubItemImages(int rowIndex, OLVListItem item, bool shouldClearImages) { + if (!this.ShowImagesOnSubItems || this.OwnerDraw) + return; + + for (int i = 1; i < item.SubItems.Count; i++) { + this.SetSubItemImage(rowIndex, i, item.GetSubItem(i), shouldClearImages); + } + } + + /// + /// Set the subitem image natively + /// + /// + /// + /// + /// + public virtual void SetSubItemImage(int rowIndex, int subItemIndex, OLVListSubItem subItem, bool shouldClearImages) { + int imageIndex = this.GetActualImageIndex(subItem.ImageSelector); + if (shouldClearImages || imageIndex != -1) + NativeMethods.SetSubItemImage(this, rowIndex, subItemIndex, imageIndex); + } + + /// + /// Take ownership of the 'objects' collection. This separates our collection from the source. + /// + /// + /// + /// This method + /// separates the 'objects' instance variable from its source, so that any AddObject/RemoveObject + /// calls will modify our collection and not the original collection. + /// + /// + /// This method has the intentional side-effect of converting our list of objects to an ArrayList. + /// + /// + protected virtual void TakeOwnershipOfObjects() { + if (this.isOwnerOfObjects) + return; + + this.isOwnerOfObjects = true; + + this.objects = ObjectListView.EnumerableToArray(this.objects, true); + } + + /// + /// Trigger FormatRow and possibly FormatCell events for the given item + /// + /// + /// + /// + protected virtual bool TriggerFormatRowEvent(int rowIndex, int displayIndex, OLVListItem olvi) { + FormatRowEventArgs args = new FormatRowEventArgs(); + args.ListView = this; + args.RowIndex = rowIndex; + args.DisplayIndex = displayIndex; + args.Item = olvi; + args.UseCellFormatEvents = this.UseCellFormatEvents; + this.OnFormatRow(args); + return args.UseCellFormatEvents; + } + + /// + /// Trigger FormatCell events for the given item + /// + /// + /// + /// + protected virtual void TriggerFormatCellEvents(int rowIndex, int displayIndex, OLVListItem olvi) { + + PropagateFormatFromRowToCells(olvi); + + // Fire one event per cell + FormatCellEventArgs args2 = new FormatCellEventArgs(); + args2.ListView = this; + args2.RowIndex = rowIndex; + args2.DisplayIndex = displayIndex; + args2.Item = olvi; + for (int i = 0; i < this.Columns.Count; i++) { + args2.ColumnIndex = i; + args2.Column = this.GetColumn(i); + args2.SubItem = olvi.GetSubItem(i); + this.OnFormatCell(args2); + } + } + + private static void PropagateFormatFromRowToCells(OLVListItem olvi) { + // If a cell isn't given its own colors, it *should* use the colors of the item. + // However, there is a bug in the .NET framework where the cell are given + // the colors of the ListView instead of the colors of the row. + + // If we've already done this, don't do it again + if (olvi.UseItemStyleForSubItems == false) + return; + + // So we have to explicitly give each cell the fore and back colors and the font that it should have. + olvi.UseItemStyleForSubItems = false; + Color backColor = olvi.BackColor; + Color foreColor = olvi.ForeColor; + Font font = olvi.Font; + foreach (ListViewItem.ListViewSubItem subitem in olvi.SubItems) { + subitem.BackColor = backColor; + subitem.ForeColor = foreColor; + subitem.Font = font; + } + } + + /// + /// Make the list forget everything -- all rows and all columns + /// + /// Use if you want to remove just the rows. + public virtual void Reset() { + this.Clear(); + this.AllColumns.Clear(); + this.ClearObjects(); + this.PrimarySortColumn = null; + this.SecondarySortColumn = null; + this.ClearDisabledObjects(); + this.ClearPersistentCheckState(); + this.ClearUrlVisited(); + this.ClearHotItem(); + } + + + #endregion + + #region ISupportInitialize Members + + void ISupportInitialize.BeginInit() { + this.Frozen = true; + } + + void ISupportInitialize.EndInit() { + if (this.RowHeight != -1) { + this.SmallImageList = this.SmallImageList; + if (this.CheckBoxes) + this.InitializeStateImageList(); + } + + if (this.UseSubItemCheckBoxes || (this.VirtualMode && this.CheckBoxes)) + this.SetupSubItemCheckBoxes(); + + this.Frozen = false; + } + + #endregion + + #region Image list manipulation + + /// + /// Update our externally visible image list so it holds the same images as our shadow list, but sized correctly + /// + private void SetupBaseImageList() { + // If a row height hasn't been set, or an image list has been give which is the required size, just assign it + if (rowHeight == -1 || + this.View != View.Details || + (this.shadowedImageList != null && this.shadowedImageList.ImageSize.Height == rowHeight)) + this.BaseSmallImageList = this.shadowedImageList; + else { + int width = (this.shadowedImageList == null ? 16 : this.shadowedImageList.ImageSize.Width); + this.BaseSmallImageList = this.MakeResizedImageList(width, rowHeight, shadowedImageList); + } + } + + /// + /// Return a copy of the given source image list, where each image has been resized to be height x height in size. + /// If source is null, an empty image list of the given size is returned + /// + /// Height and width of the new images + /// Height and width of the new images + /// Source of the images (can be null) + /// A new image list + private ImageList MakeResizedImageList(int width, int height, ImageList source) { + ImageList il = new ImageList(); + il.ImageSize = new Size(width, height); + + // If there's nothing to copy, just return the new list + if (source == null) + return il; + + il.TransparentColor = source.TransparentColor; + il.ColorDepth = source.ColorDepth; + + // Fill the imagelist with resized copies from the source + for (int i = 0; i < source.Images.Count; i++) { + Bitmap bm = this.MakeResizedImage(width, height, source.Images[i], source.TransparentColor); + il.Images.Add(bm); + } + + // Give each image the same key it has in the original + foreach (String key in source.Images.Keys) { + il.Images.SetKeyName(source.Images.IndexOfKey(key), key); + } + + return il; + } + + /// + /// Return a bitmap of the given height x height, which shows the given image, centred. + /// + /// Height and width of new bitmap + /// Height and width of new bitmap + /// Image to be centred + /// The background color + /// A new bitmap + private Bitmap MakeResizedImage(int width, int height, Image image, Color transparent) { + Bitmap bm = new Bitmap(width, height); + Graphics g = Graphics.FromImage(bm); + g.Clear(transparent); + int x = Math.Max(0, (bm.Size.Width - image.Size.Width) / 2); + int y = Math.Max(0, (bm.Size.Height - image.Size.Height) / 2); + g.DrawImage(image, x, y, image.Size.Width, image.Size.Height); + return bm; + } + + /// + /// Initialize the state image list with the required checkbox images + /// + protected virtual void InitializeStateImageList() { + if (this.DesignMode) + return; + + if (!this.CheckBoxes) + return; + + if (this.StateImageList == null) { + this.StateImageList = new ImageList(); + this.StateImageList.ImageSize = new Size(16, this.RowHeight == -1 ? 16 : this.RowHeight); + this.StateImageList.ColorDepth = ColorDepth.Depth32Bit; + } + + if (this.RowHeight != -1 && + this.View == View.Details && + this.StateImageList.ImageSize.Height != this.RowHeight) { + this.StateImageList = new ImageList(); + this.StateImageList.ImageSize = new Size(16, this.RowHeight); + this.StateImageList.ColorDepth = ColorDepth.Depth32Bit; + } + + // The internal logic of ListView cycles through the state images when the primary + // checkbox is clicked. So we have to get exactly the right number of images in the + // image list. + if (this.StateImageList.Images.Count == 0) + this.AddCheckStateBitmap(this.StateImageList, UNCHECKED_KEY, CheckBoxState.UncheckedNormal); + if (this.StateImageList.Images.Count <= 1) + this.AddCheckStateBitmap(this.StateImageList, CHECKED_KEY, CheckBoxState.CheckedNormal); + if (this.TriStateCheckBoxes && this.StateImageList.Images.Count <= 2) + this.AddCheckStateBitmap(this.StateImageList, INDETERMINATE_KEY, CheckBoxState.MixedNormal); + else { + if (this.StateImageList.Images.ContainsKey(INDETERMINATE_KEY)) + this.StateImageList.Images.RemoveByKey(INDETERMINATE_KEY); + } + } + + /// + /// The name of the image used when a check box is checked + /// + public const string CHECKED_KEY = "checkbox-checked"; + + /// + /// The name of the image used when a check box is unchecked + /// + public const string UNCHECKED_KEY = "checkbox-unchecked"; + + /// + /// The name of the image used when a check box is Indeterminate + /// + public const string INDETERMINATE_KEY = "checkbox-indeterminate"; + + /// + /// Setup this control so it can display check boxes on subitems + /// (or primary checkboxes in virtual mode) + /// + /// This gives the ListView a small image list, if it doesn't already have one. + public virtual void SetupSubItemCheckBoxes() { + this.ShowImagesOnSubItems = true; + if (this.SmallImageList == null || !this.SmallImageList.Images.ContainsKey(CHECKED_KEY)) + this.InitializeSubItemCheckBoxImages(); + } + + /// + /// Make sure the small image list for this control has checkbox images + /// (used for sub-item checkboxes). + /// + /// + /// + /// This gives the ListView a small image list, if it doesn't already have one. + /// + /// + /// ObjectListView has to manage checkboxes on subitems separate from the checkboxes on each row. + /// The underlying ListView knows about the per-row checkboxes, and to make them work, OLV has to + /// correctly configure the StateImageList. However, the ListView cannot do checkboxes in subitems, + /// so ObjectListView has to handle them in a different fashion. So, per-row checkboxes are controlled + /// by images in the StateImageList, but per-cell checkboxes are handled by images in the SmallImageList. + /// + /// + protected virtual void InitializeSubItemCheckBoxImages() { + // Don't mess with the image list in design mode + if (this.DesignMode) + return; + + ImageList il = this.SmallImageList; + if (il == null) { + il = new ImageList(); + il.ImageSize = new Size(16, 16); + il.ColorDepth = ColorDepth.Depth32Bit; + } + + this.AddCheckStateBitmap(il, CHECKED_KEY, CheckBoxState.CheckedNormal); + this.AddCheckStateBitmap(il, UNCHECKED_KEY, CheckBoxState.UncheckedNormal); + this.AddCheckStateBitmap(il, INDETERMINATE_KEY, CheckBoxState.MixedNormal); + + this.SmallImageList = il; + } + + private void AddCheckStateBitmap(ImageList il, string key, CheckBoxState boxState) { + Bitmap b = new Bitmap(il.ImageSize.Width, il.ImageSize.Height); + Graphics g = Graphics.FromImage(b); + g.Clear(il.TransparentColor); + Point location = new Point(b.Width / 2 - 5, b.Height / 2 - 6); + CheckBoxRenderer.DrawCheckBox(g, location, boxState); + il.Images.Add(key, b); + } + + #endregion + + #region Owner drawing + + /// + /// Owner draw the column header + /// + /// + protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e) { + e.DrawDefault = true; + base.OnDrawColumnHeader(e); + } + + /// + /// Owner draw the item + /// + /// + protected override void OnDrawItem(DrawListViewItemEventArgs e) { + if (this.View == View.Details) + e.DrawDefault = false; + else { + if (this.ItemRenderer == null) + e.DrawDefault = true; + else { + Object row = ((OLVListItem)e.Item).RowObject; + e.DrawDefault = !this.ItemRenderer.RenderItem(e, e.Graphics, e.Bounds, row); + } + } + + if (e.DrawDefault) + base.OnDrawItem(e); + } + + /// + /// Owner draw a single subitem + /// + /// + protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e) { + //System.Diagnostics.Debug.WriteLine(String.Format("OnDrawSubItem ({0}, {1})", e.ItemIndex, e.ColumnIndex)); + // Don't try to do owner drawing at design time + if (this.DesignMode) { + e.DrawDefault = true; + return; + } + + object rowObject = ((OLVListItem)e.Item).RowObject; + + // Calculate where the subitem should be drawn + Rectangle r = e.Bounds; + + // Get the special renderer for this column. If there isn't one, use the default draw mechanism. + OLVColumn column = this.GetColumn(e.ColumnIndex); + IRenderer renderer = this.GetCellRenderer(rowObject, column); + + // Get a graphics context for the renderer to use. + // But we have more complications. Virtual lists have a nasty habit of drawing column 0 + // whenever there is any mouse move events over a row, and doing it in an un-double-buffered manner, + // which results in nasty flickers! There are also some unbuffered draw when a mouse is first + // hovered over column 0 of a normal row. So, to avoid all complications, + // we always manually double-buffer the drawing. + // Except with Mono, which doesn't seem to handle double buffering at all :-( + BufferedGraphics buffer = BufferedGraphicsManager.Current.Allocate(e.Graphics, r); + Graphics g = buffer.Graphics; + + g.TextRenderingHint = ObjectListView.TextRenderingHint; + g.SmoothingMode = ObjectListView.SmoothingMode; + + // Finally, give the renderer a chance to draw something + e.DrawDefault = !renderer.RenderSubItem(e, g, r, rowObject); + + if (!e.DrawDefault) + buffer.Render(); + buffer.Dispose(); + } + + #endregion + + #region OnEvent Handling + + /// + /// We need the click count in the mouse up event, but that is always 1. + /// So we have to remember the click count from the preceding mouse down event. + /// + /// + protected override void OnMouseDown(MouseEventArgs e) { + //System.Diagnostics.Debug.WriteLine(String.Format("OnMouseDown: {0}, {1}", e.Button, e.Clicks)); + this.lastMouseDownClickCount = e.Clicks; + this.lastMouseDownButton = e.Button; + base.OnMouseDown(e); + } + private int lastMouseDownClickCount; + private MouseButtons lastMouseDownButton; + + /// + /// When the mouse leaves the control, remove any hot item highlighting + /// + /// + protected override void OnMouseLeave(EventArgs e) { + base.OnMouseLeave(e); + + if (!this.Created) + return; + + this.UpdateHotItem(new Point(-1,-1)); + } + + // We could change the hot item on the mouse hover event, but it looks wrong. + + //protected override void OnMouseHover(EventArgs e) { + // System.Diagnostics.Debug.WriteLine(String.Format("OnMouseHover")); + // base.OnMouseHover(e); + // this.UpdateHotItem(this.PointToClient(Cursor.Position)); + //} + + /// + /// When the mouse moves, we might need to change the hot item. + /// + /// + protected override void OnMouseMove(MouseEventArgs e) { + base.OnMouseMove(e); + + if (this.Created) + HandleMouseMove(e.Location); + } + + internal void HandleMouseMove(Point pt) { + //System.Diagnostics.Debug.WriteLine(String.Format("HandleMouseMove: {0}", pt)); + + CellOverEventArgs args = new CellOverEventArgs(); + this.BuildCellEvent(args, pt); + this.OnCellOver(args); + this.MouseMoveHitTest = args.HitTest; + + if (!args.Handled) + this.UpdateHotItem(args.HitTest); + } + + /// + /// Check to see if we need to start editing a cell + /// + /// + protected override void OnMouseUp(MouseEventArgs e) { + //System.Diagnostics.Debug.WriteLine(String.Format("OnMouseUp: {0}, {1}", e.Button, e.Clicks)); + + base.OnMouseUp(e); + + if (!this.Created) + return; + + // Sigh! More complexity. e.Button is not reliable when clicking on group headers. + // The mouse up event for first click on a group header always reports e.Button as None. + // Subsequent mouse up events report the button from the previous event. + // However, mouse down events are correctly reported, so we use the button value from + // the last mouse down event. + if (this.lastMouseDownButton == MouseButtons.Right) { + this.OnRightMouseUp(e); + return; + } + + // What event should we listen for to start cell editing? + // ------------------------------------------------------ + // + // We can't use OnMouseClick, OnMouseDoubleClick, OnClick, or OnDoubleClick + // since they are not triggered for clicks on subitems without Full Row Select. + // + // We could use OnMouseDown, but selecting rows is done in OnMouseUp. This means + // that if we start the editing during OnMouseDown, the editor will automatically + // lose focus when mouse up happens. + // + + // Tell the world about a cell click. If someone handles it, don't do anything else + CellClickEventArgs args = new CellClickEventArgs(); + this.BuildCellEvent(args, e.Location); + args.ClickCount = this.lastMouseDownClickCount; + this.OnCellClick(args); + if (args.Handled) + return; + + // Did the user click a hyperlink? + if (this.UseHyperlinks && + args.HitTest.HitTestLocation == HitTestLocation.Text && + args.SubItem != null && + !String.IsNullOrEmpty(args.SubItem.Url)) { + // We have to delay the running of this process otherwise we can generate + // a series of MouseUp events (don't ask me why) + this.BeginInvoke((MethodInvoker)delegate { this.ProcessHyperlinkClicked(args); }); + } + + // No one handled it so check to see if we should start editing. + if (!this.ShouldStartCellEdit(e)) + return; + + // We only start the edit if the user clicked on the image or text. + if (args.HitTest.HitTestLocation == HitTestLocation.Nothing) + return; + + // We don't edit the primary column by single clicks -- only subitems. + if (this.CellEditActivation == CellEditActivateMode.SingleClick && args.ColumnIndex <= 0) + return; + + // Don't start a cell edit operation when the user clicks on the background of a checkbox column -- it just looks wrong. + // If the user clicks on the actual checkbox, changing the checkbox state is handled elsewhere. + if (args.Column != null && args.Column.CheckBoxes) + return; + + this.EditSubItem(args.Item, args.ColumnIndex); + } + + /// + /// Tell the world that a hyperlink was clicked and if the event isn't handled, + /// do the default processing. + /// + /// + protected virtual void ProcessHyperlinkClicked(CellClickEventArgs e) { + HyperlinkClickedEventArgs args = new HyperlinkClickedEventArgs(); + args.HitTest = e.HitTest; + args.ListView = this; + args.Location = new Point(-1, -1); + args.Item = e.Item; + args.SubItem = e.SubItem; + args.Model = e.Model; + args.ColumnIndex = e.ColumnIndex; + args.Column = e.Column; + args.RowIndex = e.RowIndex; + args.ModifierKeys = Control.ModifierKeys; + args.Url = e.SubItem.Url; + this.OnHyperlinkClicked(args); + if (!args.Handled) { + this.StandardHyperlinkClickedProcessing(args); + } + } + + /// + /// Do the default processing for a hyperlink clicked event, which + /// is to try and open the url. + /// + /// + protected virtual void StandardHyperlinkClickedProcessing(HyperlinkClickedEventArgs args) { + Cursor originalCursor = this.Cursor; + try { + this.Cursor = Cursors.WaitCursor; + System.Diagnostics.Process.Start(args.Url); + } catch (Win32Exception) { + System.Media.SystemSounds.Beep.Play(); + // ignore it + } finally { + this.Cursor = originalCursor; + } + this.MarkUrlVisited(args.Url); + this.RefreshHotItem(); + } + + /// + /// The user right clicked on the control + /// + /// + protected virtual void OnRightMouseUp(MouseEventArgs e) { + CellRightClickEventArgs args = new CellRightClickEventArgs(); + this.BuildCellEvent(args, e.Location); + this.OnCellRightClick(args); + if (!args.Handled) { + if (args.MenuStrip != null) { + args.MenuStrip.Show(this, args.Location); + } + } + } + + internal void BuildCellEvent(CellEventArgs args, Point location) { + BuildCellEvent(args, location, this.OlvHitTest(location.X, location.Y)); + } + + internal void BuildCellEvent(CellEventArgs args, Point location, OlvListViewHitTestInfo hitTest) { + args.HitTest = hitTest; + args.ListView = this; + args.Location = location; + args.Item = hitTest.Item; + args.SubItem = hitTest.SubItem; + args.Model = hitTest.RowObject; + args.ColumnIndex = hitTest.ColumnIndex; + args.Column = hitTest.Column; + if (hitTest.Item != null) + args.RowIndex = hitTest.Item.Index; + args.ModifierKeys = Control.ModifierKeys; + + // In non-details view, we want any hit on an item to act as if it was a hit + // on column 0 -- which, effectively, it was. + if (args.Item != null && args.ListView.View != View.Details) { + args.ColumnIndex = 0; + args.Column = args.ListView.GetColumn(0); + args.SubItem = args.Item.GetSubItem(0); + } + } + + /// + /// This method is called every time a row is selected or deselected. This can be + /// a pain if the user shift-clicks 100 rows. We override this method so we can + /// trigger one event for any number of select/deselects that come from one user action + /// + /// + protected override void OnSelectedIndexChanged(EventArgs e) { + if (this.SelectionEventsSuspended) + return; + + base.OnSelectedIndexChanged(e); + + this.TriggerDeferredSelectionChangedEvent(); + } + + /// + /// Schedule a SelectionChanged event to happen after the next idle event, + /// unless we've already scheduled that to happen. + /// + protected virtual void TriggerDeferredSelectionChangedEvent() { + if (this.SelectionEventsSuspended) + return; + + // If we haven't already scheduled an event, schedule it to be triggered + // By using idle time, we will wait until all select events for the same + // user action have finished before triggering the event. + if (!this.hasIdleHandler) { + this.hasIdleHandler = true; + this.RunWhenIdle(HandleApplicationIdle); + } + } + + /// + /// Called when the handle of the underlying control is created + /// + /// + protected override void OnHandleCreated(EventArgs e) { + //Debug.WriteLine("OnHandleCreated"); + base.OnHandleCreated(e); + + this.Invoke((MethodInvoker)this.OnControlCreated); + } + + /// + /// This method is called after the control has been fully created. + /// + protected virtual void OnControlCreated() { + + //Debug.WriteLine("OnControlCreated"); + + // Force the header control to be created when the listview handle is + HeaderControl hc = this.HeaderControl; + hc.WordWrap = this.HeaderWordWrap; + + // Make sure any overlays that are set on the hot item style take effect + this.HotItemStyle = this.HotItemStyle; + + // Arrange for any group images to be installed after the control is created + NativeMethods.SetGroupImageList(this, this.GroupImageList); + + this.UseExplorerTheme = this.UseExplorerTheme; + + this.RememberDisplayIndicies(); + this.SetGroupSpacing(); + + if (this.VirtualMode) + this.ApplyExtendedStyles(); + } + + #endregion + + #region Cell editing + + /// + /// Should we start editing the cell in response to the given mouse button event? + /// + /// + /// + protected virtual bool ShouldStartCellEdit(MouseEventArgs e) { + if (this.IsCellEditing) + return false; + + if (e.Button != MouseButtons.Left && e.Button != MouseButtons.Right) + return false; + + if ((Control.ModifierKeys & (Keys.Shift | Keys.Control | Keys.Alt)) != 0) + return false; + + if (this.lastMouseDownClickCount == 1 && ( + this.CellEditActivation == CellEditActivateMode.SingleClick || + this.CellEditActivation == CellEditActivateMode.SingleClickAlways)) + return true; + + return (this.lastMouseDownClickCount == 2 && this.CellEditActivation == CellEditActivateMode.DoubleClick); + } + + /// + /// Handle a key press on this control. We specifically look for F2 which edits the primary column, + /// or a Tab character during an edit operation, which tries to start editing on the next (or previous) cell. + /// + /// + /// + protected override bool ProcessDialogKey(Keys keyData) { + + if (this.IsCellEditing) + return this.CellEditKeyEngine.HandleKey(this, keyData); + + // Treat F2 as a request to edit the primary column + if (keyData == Keys.F2) { + this.EditSubItem((OLVListItem)this.FocusedItem, 0); + return base.ProcessDialogKey(keyData); + } + + // Treat Ctrl-C as Copy To Clipboard. + if (this.CopySelectionOnControlC && keyData == (Keys.C | Keys.Control)) { + this.CopySelectionToClipboard(); + return true; + } + + // Treat Ctrl-A as Select All. + if (this.SelectAllOnControlA && keyData == (Keys.A | Keys.Control)) { + this.SelectAll(); + return true; + } + + return base.ProcessDialogKey(keyData); + } + + /// + /// Start an editing operation on the first editable column of the given model. + /// + /// + /// + /// + /// If the model doesn't exist, or there are no editable columns, this method + /// will do nothing. + /// + /// This will start an edit operation regardless of CellActivationMode. + /// + /// + public virtual void EditModel(object rowModel) { + OLVListItem olvItem = this.ModelToItem(rowModel); + if (olvItem == null) + return; + + for (int i = 0; i < olvItem.SubItems.Count; i++) { + var olvColumn = this.GetColumn(i); + if (olvColumn != null && olvColumn.IsEditable) { + this.StartCellEdit(olvItem, i); + return; + } + } + } + + /// + /// Begin an edit operation on the given cell. + /// + /// This performs various sanity checks and passes off the real work to StartCellEdit(). + /// The row to be edited + /// The index of the cell to be edited + public virtual void EditSubItem(OLVListItem item, int subItemIndex) { + if (item == null) + return; + + if (this.CellEditActivation == CellEditActivateMode.None) + return; + + OLVColumn olvColumn = this.GetColumn(subItemIndex); + if (olvColumn == null || !olvColumn.IsEditable) + return; + + if (!item.Enabled) + return; + + this.StartCellEdit(item, subItemIndex); + } + + /// + /// Really start an edit operation on a given cell. The parameters are assumed to be sane. + /// + /// The row to be edited + /// The index of the cell to be edited + public virtual void StartCellEdit(OLVListItem item, int subItemIndex) { + OLVColumn column = this.GetColumn(subItemIndex); + if (column == null) + return; + Control c = this.GetCellEditor(item, subItemIndex); + Rectangle cellBounds = this.CalculateCellBounds(item, subItemIndex); + c.Bounds = this.CalculateCellEditorBounds(item, subItemIndex, c.PreferredSize); + + // Try to align the control as the column is aligned. Not all controls support this property + Munger.PutProperty(c, "TextAlign", column.TextAlign); + + // Give the control the value from the model + this.SetControlValue(c, column.GetValue(item.RowObject), column.GetStringValue(item.RowObject)); + + // Give the outside world the chance to munge with the process + this.CellEditEventArgs = new CellEditEventArgs(column, c, cellBounds, item, subItemIndex); + this.OnCellEditStarting(this.CellEditEventArgs); + if (this.CellEditEventArgs.Cancel) + return; + + // The event handler may have completely changed the control, so we need to remember it + this.cellEditor = this.CellEditEventArgs.Control; + + this.Invalidate(); + this.Controls.Add(this.cellEditor); + this.ConfigureControl(); + this.PauseAnimations(true); + } + private Control cellEditor; + internal CellEditEventArgs CellEditEventArgs; + + /// + /// Calculate the bounds of the edit control for the given item/column + /// + /// + /// + /// + /// + public Rectangle CalculateCellEditorBounds(OLVListItem item, int subItemIndex, Size preferredSize) { + Rectangle r = CalculateCellBounds(item, subItemIndex); + + // Calculate the width of the cell's current contents + return this.OwnerDraw + ? CalculateCellEditorBoundsOwnerDrawn(item, subItemIndex, r, preferredSize) + : CalculateCellEditorBoundsStandard(item, subItemIndex, r, preferredSize); + } + + /// + /// Calculate the bounds of the edit control for the given item/column, when the listview + /// is being owner drawn. + /// + /// + /// + /// + /// + /// A rectangle that is the bounds of the cell editor + protected Rectangle CalculateCellEditorBoundsOwnerDrawn(OLVListItem item, int subItemIndex, Rectangle r, Size preferredSize) { + IRenderer renderer = this.View == View.Details + ? this.GetCellRenderer(item.RowObject, this.GetColumn(subItemIndex)) + : this.ItemRenderer; + + if (renderer == null) + return r; + + using (Graphics g = this.CreateGraphics()) { + return renderer.GetEditRectangle(g, r, item, subItemIndex, preferredSize); + } + } + + /// + /// Calculate the bounds of the edit control for the given item/column, when the listview + /// is not being owner drawn. + /// + /// + /// + /// + /// + /// A rectangle that is the bounds of the cell editor + protected Rectangle CalculateCellEditorBoundsStandard(OLVListItem item, int subItemIndex, Rectangle cellBounds, Size preferredSize) { + if (this.View == View.Tile) + return cellBounds; + + // Center the editor vertically + if (cellBounds.Height != preferredSize.Height) + cellBounds.Y += (cellBounds.Height - preferredSize.Height) / 2; + + // Only Details view needs more processing + if (this.View != View.Details) + return cellBounds; + + // Allow for image (if there is one). + int offset = 0; + object imageSelector = null; + if (subItemIndex == 0) + imageSelector = item.ImageSelector; + else { + // We only check for subitem images if we are owner drawn or showing subitem images + if (this.OwnerDraw || this.ShowImagesOnSubItems) + imageSelector = item.GetSubItem(subItemIndex).ImageSelector; + } + if (this.GetActualImageIndex(imageSelector) != -1) { + offset += this.SmallImageSize.Width + 2; + } + + // Allow for checkbox + if (this.CheckBoxes && this.StateImageList != null && subItemIndex == 0) { + offset += this.StateImageList.ImageSize.Width + 2; + } + + // Allow for indent (first column only) + if (subItemIndex == 0 && item.IndentCount > 0) { + offset += (this.SmallImageSize.Width * item.IndentCount); + } + + // Do the adjustment + if (offset > 0) { + cellBounds.X += offset; + cellBounds.Width -= offset; + } + + return cellBounds; + } + + /// + /// Try to give the given value to the provided control. Fall back to assigning a string + /// if the value assignment fails. + /// + /// A control + /// The value to be given to the control + /// The string to be given if the value doesn't work + protected virtual void SetControlValue(Control control, Object value, String stringValue) { + // Does the control implement our custom interface? + IOlvEditor olvEditor = control as IOlvEditor; + if (olvEditor != null) { + olvEditor.Value = value; + return; + } + + // Handle combobox explicitly + ComboBox cb = control as ComboBox; + if (cb != null) { + if (cb.Created) + cb.SelectedValue = value; + else + this.BeginInvoke(new MethodInvoker(delegate { + cb.SelectedValue = value; + })); + return; + } + + if (Munger.PutProperty(control, "Value", value)) + return; + + // There wasn't a Value property, or we couldn't set it, so set the text instead + try + { + String valueAsString = value as String; + control.Text = valueAsString ?? stringValue; + } + catch (ArgumentOutOfRangeException) { + // The value couldn't be set via the Text property. + } + } + + /// + /// Setup the given control to be a cell editor + /// + protected virtual void ConfigureControl() { + this.cellEditor.Validating += new CancelEventHandler(CellEditor_Validating); + this.cellEditor.Select(); + } + + /// + /// Return the value that the given control is showing + /// + /// + /// + protected virtual Object GetControlValue(Control control) { + if (control == null) + return null; + + IOlvEditor olvEditor = control as IOlvEditor; + if (olvEditor != null) + return olvEditor.Value; + + TextBox box = control as TextBox; + if (box != null) + return box.Text; + + ComboBox comboBox = control as ComboBox; + if (comboBox != null) + return comboBox.SelectedValue; + + CheckBox checkBox = control as CheckBox; + if (checkBox != null) + return checkBox.Checked; + + try { + return control.GetType().InvokeMember("Value", BindingFlags.GetProperty, null, control, null); + } catch (MissingMethodException) { // Microsoft throws this + return control.Text; + } catch (MissingFieldException) { // Mono throws this + return control.Text; + } + } + + /// + /// Called when the cell editor could be about to lose focus. Time to commit the change + /// + /// + /// + protected virtual void CellEditor_Validating(object sender, CancelEventArgs e) { + this.CellEditEventArgs.Cancel = false; + this.CellEditEventArgs.NewValue = this.GetControlValue(this.cellEditor); + this.OnCellEditorValidating(this.CellEditEventArgs); + + if (this.CellEditEventArgs.Cancel) { + this.CellEditEventArgs.Control.Select(); + e.Cancel = true; + } else + FinishCellEdit(); + } + + /// + /// Return the bounds of the given cell + /// + /// The row to be edited + /// The index of the cell to be edited + /// A Rectangle + public virtual Rectangle CalculateCellBounds(OLVListItem item, int subItemIndex) { + + // It seems on Win7, GetSubItemBounds() does not have the same problems with + // column 0 that it did previously. + + // TODO - Check on XP + + if (this.View != View.Details) + return this.GetItemRect(item.Index, ItemBoundsPortion.Label); + + Rectangle r = item.GetSubItemBounds(subItemIndex); + r.Width -= 1; + r.Height -= 1; + return r; + + // We use ItemBoundsPortion.Label rather than ItemBoundsPortion.Item + // since Label extends to the right edge of the cell, whereas Item gives just the + // current text width. + //return this.CalculateCellBounds(item, subItemIndex, ItemBoundsPortion.Label); + } + + /// + /// Return the bounds of the given cell only until the edge of the current text + /// + /// The row to be edited + /// The index of the cell to be edited + /// A Rectangle + public virtual Rectangle CalculateCellTextBounds(OLVListItem item, int subItemIndex) { + return this.CalculateCellBounds(item, subItemIndex, ItemBoundsPortion.ItemOnly); + } + + private Rectangle CalculateCellBounds(OLVListItem item, int subItemIndex, ItemBoundsPortion portion) { + // SubItem.Bounds works for every subitem, except the first. + if (subItemIndex > 0) + return item.GetSubItemBounds(subItemIndex); + + // For non detail views, we just use the requested portion + Rectangle r = this.GetItemRect(item.Index, portion); + if (r.Y < -10000000 || r.Y > 10000000) { + r.Y = item.Bounds.Y; + } + if (this.View != View.Details) + return r; + + // Finding the bounds of cell 0 should not be a difficult task, but it is. Problems: + // 1) item.SubItem[0].Bounds is always the full bounds of the entire row, not just cell 0. + // 2) if column 0 has been dragged to some other position, the bounds always has a left edge of 0. + + // We avoid both these problems by using the position of sides the column header to calculate + // the sides of the cell + Point sides = NativeMethods.GetScrolledColumnSides(this, 0); + r.X = sides.X + 4; + r.Width = sides.Y - sides.X - 5; + + return r; + } + + /// + /// Calculate the visible bounds of the given column. The column's bottom edge is + /// either the bottom of the last row or the bottom of the control. + /// + /// The bounds of the control itself + /// The column + /// A Rectangle + /// This returns an empty rectangle if the control isn't in Details mode, + /// OR has doesn't have any rows, OR if the given column is hidden. + public virtual Rectangle CalculateColumnVisibleBounds(Rectangle bounds, OLVColumn column) + { + // Sanity checks + if (column == null || + this.View != System.Windows.Forms.View.Details || + this.GetItemCount() == 0 || + !column.IsVisible) + return Rectangle.Empty; + + Point sides = NativeMethods.GetScrolledColumnSides(this, column.Index); + if (sides.X == -1) + return Rectangle.Empty; + + Rectangle columnBounds = new Rectangle(sides.X, bounds.Top, sides.Y - sides.X, bounds.Bottom); + + // Find the bottom of the last item. The column only extends to there. + OLVListItem lastItem = this.GetLastItemInDisplayOrder(); + if (lastItem != null) + { + Rectangle lastItemBounds = lastItem.Bounds; + if (!lastItemBounds.IsEmpty && lastItemBounds.Bottom < columnBounds.Bottom) + columnBounds.Height = lastItemBounds.Bottom - columnBounds.Top; + } + + return columnBounds; + } + + /// + /// Return a control that can be used to edit the value of the given cell. + /// + /// The row to be edited + /// The index of the cell to be edited + /// A Control to edit the given cell + protected virtual Control GetCellEditor(OLVListItem item, int subItemIndex) { + OLVColumn column = this.GetColumn(subItemIndex); + Object value = column.GetValue(item.RowObject); + + // Does the column have its own special way of creating cell editors? + if (column.EditorCreator != null) { + Control customEditor = column.EditorCreator(item.RowObject, column, value); + if (customEditor != null) + return customEditor; + } + + // Ask the registry for an instance of the appropriate editor. + // Use a default editor if the registry can't create one for us. + Control editor = ObjectListView.EditorRegistry.GetEditor(item.RowObject, column, value ?? this.GetFirstNonNullValue(column)); + return editor ?? this.MakeDefaultCellEditor(column); + } + + /// + /// Get the first non-null value of the given column. + /// At most 1000 rows will be considered. + /// + /// + /// The first non-null value, or null if no non-null values were found + internal object GetFirstNonNullValue(OLVColumn column) { + for (int i = 0; i < Math.Min(this.GetItemCount(), 1000); i++) { + object value = column.GetValue(this.GetModelObject(i)); + if (value != null) + return value; + } + return null; + } + + /// + /// Return a TextBox that can be used as a default cell editor. + /// + /// What column does the cell belong to? + /// + protected virtual Control MakeDefaultCellEditor(OLVColumn column) { + TextBox tb = new TextBox(); + if (column.AutoCompleteEditor) + this.ConfigureAutoComplete(tb, column); + return tb; + } + + /// + /// Configure the given text box to autocomplete unique values + /// from the given column. At most 1000 rows will be considered. + /// + /// The textbox to configure + /// The column used to calculate values + public void ConfigureAutoComplete(TextBox tb, OLVColumn column) { + this.ConfigureAutoComplete(tb, column, 1000); + } + + + /// + /// Configure the given text box to autocomplete unique values + /// from the given column. At most 1000 rows will be considered. + /// + /// The textbox to configure + /// The column used to calculate values + /// Consider only this many rows + public void ConfigureAutoComplete(TextBox tb, OLVColumn column, int maxRows) { + // Don't consider more rows than we actually have + maxRows = Math.Min(this.GetItemCount(), maxRows); + + // Reset any existing autocomplete + tb.AutoCompleteCustomSource.Clear(); + + // CONSIDER: Should we use ClusteringStrategy here? + + // Build a list of unique values, to be used as autocomplete on the editor + Dictionary alreadySeen = new Dictionary(); + List values = new List(); + for (int i = 0; i < maxRows; i++) { + string valueAsString = column.GetStringValue(this.GetModelObject(i)); + if (!String.IsNullOrEmpty(valueAsString) && !alreadySeen.ContainsKey(valueAsString)) { + values.Add(valueAsString); + alreadySeen[valueAsString] = true; + } + } + + tb.AutoCompleteCustomSource.AddRange(values.ToArray()); + tb.AutoCompleteSource = AutoCompleteSource.CustomSource; + tb.AutoCompleteMode = column.AutoCompleteEditorMode; + } + + /// + /// Stop editing a cell and throw away any changes. + /// + public virtual void CancelCellEdit() { + if (!this.IsCellEditing) + return; + + // Let the world know that the user has cancelled the edit operation + this.CellEditEventArgs.Cancel = true; + this.CellEditEventArgs.NewValue = this.GetControlValue(this.cellEditor); + this.OnCellEditFinishing(this.CellEditEventArgs); + + // Now cleanup the editing process + this.CleanupCellEdit(false, this.CellEditEventArgs.AutoDispose); + } + + /// + /// If a cell edit is in progress, finish the edit. + /// + /// Returns false if the finishing process was cancelled + /// (i.e. the cell editor is still on screen) + /// This method does not guarantee that the editing will finish. The validation + /// process can cause the finishing to be aborted. Developers should check the return value + /// or use IsCellEditing property after calling this method to see if the user is still + /// editing a cell. + public virtual bool PossibleFinishCellEditing() { + return this.PossibleFinishCellEditing(false); + } + + /// + /// If a cell edit is in progress, finish the edit. + /// + /// Returns false if the finishing process was cancelled + /// (i.e. the cell editor is still on screen) + /// This method does not guarantee that the editing will finish. The validation + /// process can cause the finishing to be aborted. Developers should check the return value + /// or use IsCellEditing property after calling this method to see if the user is still + /// editing a cell. + /// True if it is likely that another cell is going to be + /// edited immediately after this cell finishes editing + public virtual bool PossibleFinishCellEditing(bool expectingCellEdit) { + if (!this.IsCellEditing) + return true; + + this.CellEditEventArgs.Cancel = false; + this.CellEditEventArgs.NewValue = this.GetControlValue(this.cellEditor); + this.OnCellEditorValidating(this.CellEditEventArgs); + + if (this.CellEditEventArgs.Cancel) + return false; + + this.FinishCellEdit(expectingCellEdit); + + return true; + } + + /// + /// Finish the cell edit operation, writing changed data back to the model object + /// + /// This method does not trigger a Validating event, so it always finishes + /// the cell edit. + public virtual void FinishCellEdit() { + this.FinishCellEdit(false); + } + + /// + /// Finish the cell edit operation, writing changed data back to the model object + /// + /// This method does not trigger a Validating event, so it always finishes + /// the cell edit. + /// True if it is likely that another cell is going to be + /// edited immediately after this cell finishes editing + public virtual void FinishCellEdit(bool expectingCellEdit) { + if (!this.IsCellEditing) + return; + + this.CellEditEventArgs.Cancel = false; + this.CellEditEventArgs.NewValue = this.GetControlValue(this.cellEditor); + this.OnCellEditFinishing(this.CellEditEventArgs); + + // If someone doesn't cancel the editing process, write the value back into the model + if (!this.CellEditEventArgs.Cancel) { + this.CellEditEventArgs.Column.PutValue(this.CellEditEventArgs.RowObject, this.CellEditEventArgs.NewValue); + this.RefreshItem(this.CellEditEventArgs.ListViewItem); + } + + this.CleanupCellEdit(expectingCellEdit, this.CellEditEventArgs.AutoDispose); + + // Tell the world that the cell has been edited + this.OnCellEditFinished(this.CellEditEventArgs); + } + + /// + /// Remove all trace of any existing cell edit operation + /// + /// True if it is likely that another cell is going to be + /// edited immediately after this cell finishes editing + /// True if the cell editor should be disposed + protected virtual void CleanupCellEdit(bool expectingCellEdit, bool disposeOfCellEditor) { + if (this.cellEditor == null) + return; + + this.cellEditor.Validating -= new CancelEventHandler(CellEditor_Validating); + + Control soonToBeOldCellEditor = this.cellEditor; + this.cellEditor = null; + + // Delay cleaning up the cell editor so that if we are immediately going to + // start a new cell edit (because the user pressed Tab) the new cell editor + // has a chance to grab the focus. Without this, the ListView gains focus + // momentarily (after the cell editor is remove and before the new one is created) + // causing the list's selection to flash momentarily. + EventHandler toBeRun = null; + toBeRun = delegate(object sender, EventArgs e) { + Application.Idle -= toBeRun; + this.Controls.Remove(soonToBeOldCellEditor); + if (disposeOfCellEditor) + soonToBeOldCellEditor.Dispose(); + this.Invalidate(); + + if (!this.IsCellEditing) { + if (this.Focused) + this.Select(); + this.PauseAnimations(false); + } + }; + + // We only want to delay the removal of the control if we are expecting another cell + // to be edited. Otherwise, we remove the control immediately. + if (expectingCellEdit) + this.RunWhenIdle(toBeRun); + else + toBeRun(null, null); + } + + #endregion + + #region Hot row and cell handling + + /// + /// Force the hot item to be recalculated + /// + public virtual void ClearHotItem() { + this.UpdateHotItem(new Point(-1, -1)); + } + + /// + /// Force the hot item to be recalculated + /// + public virtual void RefreshHotItem() { + this.UpdateHotItem(this.PointToClient(Cursor.Position)); + } + + /// + /// The mouse has moved to the given pt. See if the hot item needs to be updated + /// + /// Where is the mouse? + /// This is the main entry point for hot item handling + protected virtual void UpdateHotItem(Point pt) { + this.UpdateHotItem(this.OlvHitTest(pt.X, pt.Y)); + } + + /// + /// The mouse has moved to the given pt. See if the hot item needs to be updated + /// + /// + /// This is the main entry point for hot item handling + protected virtual void UpdateHotItem(OlvListViewHitTestInfo hti) { + + // We only need to do the work of this method when the list has hot parts + // (i.e. some element whose visual appearance changes when under the mouse)? + // Hot item decorations and hyperlinks are obvious, but if we have checkboxes + // or buttons, those are also "hot". It's difficult to quickly detect if there are any + // columns that have checkboxes or buttons, so we just abdicate responsibility and + // provide a property (UseHotControls) which lets the programmer say whether to do + // the hot processing or not. + if (!this.UseHotItem && !this.UseHyperlinks && !this.UseHotControls) + return; + + int newHotRow = hti.RowIndex; + int newHotColumn = hti.ColumnIndex; + HitTestLocation newHotCellHitLocation = hti.HitTestLocation; + HitTestLocationEx newHotCellHitLocationEx = hti.HitTestLocationEx; + OLVGroup newHotGroup = hti.Group; + + // In non-details view, we treat any hit on a row as if it were a hit + // on column 0 -- which (effectively) it is! + if (newHotRow >= 0 && this.View != View.Details) + newHotColumn = 0; + + if (this.HotRowIndex == newHotRow && + this.HotColumnIndex == newHotColumn && + this.HotCellHitLocation == newHotCellHitLocation && + this.HotCellHitLocationEx == newHotCellHitLocationEx && + this.HotGroup == newHotGroup) { + return; + } + + // Trigger the hotitem changed event + HotItemChangedEventArgs args = new HotItemChangedEventArgs(); + args.HotCellHitLocation = newHotCellHitLocation; + args.HotCellHitLocationEx = newHotCellHitLocationEx; + args.HotColumnIndex = newHotColumn; + args.HotRowIndex = newHotRow; + args.HotGroup = newHotGroup; + args.OldHotCellHitLocation = this.HotCellHitLocation; + args.OldHotCellHitLocationEx = this.HotCellHitLocationEx; + args.OldHotColumnIndex = this.HotColumnIndex; + args.OldHotRowIndex = this.HotRowIndex; + args.OldHotGroup = this.HotGroup; + this.OnHotItemChanged(args); + + // Update the state of the control + this.HotRowIndex = newHotRow; + this.HotColumnIndex = newHotColumn; + this.HotCellHitLocation = newHotCellHitLocation; + this.HotCellHitLocationEx = newHotCellHitLocationEx; + this.HotGroup = newHotGroup; + + // If the event handler handled it complete, don't do anything else + if (args.Handled) + return; + +// System.Diagnostics.Debug.WriteLine(String.Format("Changed hot item: {0}", args)); + + this.BeginUpdate(); + try { + this.Invalidate(); + if (args.OldHotRowIndex != -1) + this.UnapplyHotItem(args.OldHotRowIndex); + + if (this.HotRowIndex != -1) { + // Virtual lists apply hot item style when fetching their rows + if (this.VirtualMode) { + this.ClearCachedInfo(); + this.RedrawItems(this.HotRowIndex, this.HotRowIndex, true); + } else { + this.UpdateHotRow(this.HotRowIndex, this.HotColumnIndex, this.HotCellHitLocation, hti.Item); + } + } + + if (this.UseHotItem && this.HotItemStyleOrDefault.Overlay != null) { + this.RefreshOverlays(); + } + } + finally { + this.EndUpdate(); + } + } + + /// + /// Update the given row using the current hot item information + /// + /// + protected virtual void UpdateHotRow(OLVListItem olvi) { + this.UpdateHotRow(this.HotRowIndex, this.HotColumnIndex, this.HotCellHitLocation, olvi); + } + + /// + /// Update the given row using the given hot item information + /// + /// + /// + /// + /// + protected virtual void UpdateHotRow(int rowIndex, int columnIndex, HitTestLocation hitLocation, OLVListItem olvi) { + if (rowIndex < 0 || columnIndex < 0) + return; + + // System.Diagnostics.Debug.WriteLine(String.Format("UpdateHotRow: {0}, {1}, {2}", rowIndex, columnIndex, hitLocation)); + + if (this.UseHyperlinks) { + OLVColumn column = this.GetColumn(columnIndex); + OLVListSubItem subItem = olvi.GetSubItem(columnIndex); + if (column != null && column.Hyperlink && hitLocation == HitTestLocation.Text && !String.IsNullOrEmpty(subItem.Url)) { + this.ApplyCellStyle(olvi, columnIndex, this.HyperlinkStyle.Over); + this.Cursor = this.HyperlinkStyle.OverCursor ?? Cursors.Default; + } else { + this.Cursor = Cursors.Default; + } + } + + if (this.UseHotItem) { + if (!olvi.Selected && olvi.Enabled) { + this.ApplyRowStyle(olvi, this.HotItemStyleOrDefault); + } + } + } + + /// + /// Apply a style to the given row + /// + /// + /// + public virtual void ApplyRowStyle(OLVListItem olvi, IItemStyle style) { + if (style == null) + return; + + Font font = style.Font ?? olvi.Font; + + if (style.FontStyle != FontStyle.Regular) + font = new Font(font ?? this.Font, style.FontStyle); + + if (!Equals(font, olvi.Font)) { + if (olvi.UseItemStyleForSubItems) + olvi.Font = font; + else { + foreach (ListViewItem.ListViewSubItem x in olvi.SubItems) + x.Font = font; + } + } + + if (!style.ForeColor.IsEmpty) { + if (olvi.UseItemStyleForSubItems) + olvi.ForeColor = style.ForeColor; + else { + foreach (ListViewItem.ListViewSubItem x in olvi.SubItems) + x.ForeColor = style.ForeColor; + } + } + + if (!style.BackColor.IsEmpty) { + if (olvi.UseItemStyleForSubItems) + olvi.BackColor = style.BackColor; + else { + foreach (ListViewItem.ListViewSubItem x in olvi.SubItems) + x.BackColor = style.BackColor; + } + } + } + + /// + /// Apply a style to a cell + /// + /// + /// + /// + protected virtual void ApplyCellStyle(OLVListItem olvi, int columnIndex, IItemStyle style) { + if (style == null) + return; + + // Don't apply formatting to subitems when not in Details view + if (this.View != View.Details && columnIndex > 0) + return; + + olvi.UseItemStyleForSubItems = false; + + ListViewItem.ListViewSubItem subItem = olvi.SubItems[columnIndex]; + if (style.Font != null) + subItem.Font = style.Font; + + if (style.FontStyle != FontStyle.Regular) + subItem.Font = new Font(subItem.Font ?? olvi.Font ?? this.Font, style.FontStyle); + + if (!style.ForeColor.IsEmpty) + subItem.ForeColor = style.ForeColor; + + if (!style.BackColor.IsEmpty) + subItem.BackColor = style.BackColor; + } + + /// + /// Remove hot item styling from the given row + /// + /// + protected virtual void UnapplyHotItem(int index) { + this.Cursor = Cursors.Default; + // Virtual lists will apply the appropriate formatting when the row is fetched + if (this.VirtualMode) { + if (index < this.VirtualListSize) + this.RedrawItems(index, index, true); + } else { + OLVListItem olvi = this.GetItem(index); + if (olvi != null) { + //this.PostProcessOneRow(index, index, olvi); + this.RefreshItem(olvi); + } + } + } + + + #endregion + + #region Drag and drop + + /// + /// + /// + /// + protected override void OnItemDrag(ItemDragEventArgs e) { + base.OnItemDrag(e); + + if (this.DragSource == null) + return; + + Object data = this.DragSource.StartDrag(this, e.Button, (OLVListItem)e.Item); + if (data != null) { + DragDropEffects effect = this.DoDragDrop(data, this.DragSource.GetAllowedEffects(data)); + this.DragSource.EndDrag(data, effect); + } + } + + /// + /// + /// + /// + protected override void OnDragEnter(DragEventArgs args) { + base.OnDragEnter(args); + + if (this.DropSink != null) + this.DropSink.Enter(args); + } + + /// + /// + /// + /// + protected override void OnDragOver(DragEventArgs args) { + base.OnDragOver(args); + + if (this.DropSink != null) + this.DropSink.Over(args); + } + + /// + /// + /// + /// + protected override void OnDragDrop(DragEventArgs args) { + base.OnDragDrop(args); + + this.lastMouseDownClickCount = 0; // prevent drop events from becoming cell edits + + if (this.DropSink != null) + this.DropSink.Drop(args); + } + + /// + /// + /// + /// + protected override void OnDragLeave(EventArgs e) { + base.OnDragLeave(e); + + if (this.DropSink != null) + this.DropSink.Leave(); + } + + /// + /// + /// + /// + protected override void OnGiveFeedback(GiveFeedbackEventArgs args) { + base.OnGiveFeedback(args); + + if (this.DropSink != null) + this.DropSink.GiveFeedback(args); + } + + /// + /// + /// + /// + protected override void OnQueryContinueDrag(QueryContinueDragEventArgs args) { + base.OnQueryContinueDrag(args); + + if (this.DropSink != null) + this.DropSink.QueryContinue(args); + } + + #endregion + + #region Decorations and Overlays + + /// + /// Add the given decoration to those on this list and make it appear + /// + /// The decoration + /// + /// A decoration scrolls with the listview. An overlay stays fixed in place. + /// + public virtual void AddDecoration(IDecoration decoration) { + if (decoration == null) + return; + this.Decorations.Add(decoration); + this.Invalidate(); + } + + /// + /// Add the given overlay to those on this list and make it appear + /// + /// The overlay + public virtual void AddOverlay(IOverlay overlay) { + if (overlay == null) + return; + this.Overlays.Add(overlay); + this.Invalidate(); + } + + /// + /// Draw all the decorations + /// + /// A Graphics + /// The items that were redrawn and whose decorations should also be redrawn + protected virtual void DrawAllDecorations(Graphics g, List itemsThatWereRedrawn) { + g.TextRenderingHint = ObjectListView.TextRenderingHint; + g.SmoothingMode = ObjectListView.SmoothingMode; + + Rectangle contentRectangle = this.ContentRectangle; + + if (this.HasEmptyListMsg && this.GetItemCount() == 0) { + this.EmptyListMsgOverlay.Draw(this, g, contentRectangle); + } + + // Let the drop sink draw whatever feedback it likes + if (this.DropSink != null) { + this.DropSink.DrawFeedback(g, contentRectangle); + } + + // Draw our item and subitem decorations + foreach (OLVListItem olvi in itemsThatWereRedrawn) { + if (olvi.HasDecoration) { + foreach (IDecoration d in olvi.Decorations) { + d.ListItem = olvi; + d.SubItem = null; + d.Draw(this, g, contentRectangle); + } + } + foreach (OLVListSubItem subItem in olvi.SubItems) { + if (subItem.HasDecoration) { + foreach (IDecoration d in subItem.Decorations) { + d.ListItem = olvi; + d.SubItem = subItem; + d.Draw(this, g, contentRectangle); + } + } + } + if (this.SelectedRowDecoration != null && olvi.Selected && olvi.Enabled) { + this.SelectedRowDecoration.ListItem = olvi; + this.SelectedRowDecoration.SubItem = null; + this.SelectedRowDecoration.Draw(this, g, contentRectangle); + } + } + + // Now draw the specifically registered decorations + foreach (IDecoration decoration in this.Decorations) { + decoration.ListItem = null; + decoration.SubItem = null; + decoration.Draw(this, g, contentRectangle); + } + + // Finally, draw any hot item decoration + if (this.UseHotItem) { + IDecoration hotItemDecoration = this.HotItemStyleOrDefault.Decoration; + if (hotItemDecoration != null) { + hotItemDecoration.ListItem = this.GetItem(this.HotRowIndex); + if (hotItemDecoration.ListItem == null || hotItemDecoration.ListItem.Enabled) { + hotItemDecoration.SubItem = hotItemDecoration.ListItem == null ? null : hotItemDecoration.ListItem.GetSubItem(this.HotColumnIndex); + hotItemDecoration.Draw(this, g, contentRectangle); + } + } + } + + // If we are in design mode, we don't want to use the glass panels, + // so we draw the background overlays here + if (this.DesignMode) { + foreach (IOverlay overlay in this.Overlays) { + overlay.Draw(this, g, contentRectangle); + } + } + } + + /// + /// Is the given decoration shown on this list + /// + /// The overlay + public virtual bool HasDecoration(IDecoration decoration) { + return this.Decorations.Contains(decoration); + } + + /// + /// Is the given overlay shown on this list? + /// + /// The overlay + public virtual bool HasOverlay(IOverlay overlay) { + return this.Overlays.Contains(overlay); + } + + /// + /// Hide any overlays. + /// + /// + /// This is only a temporary hiding -- the overlays will be shown + /// the next time the ObjectListView redraws. + /// + public virtual void HideOverlays() { + foreach (GlassPanelForm glassPanel in this.glassPanels) { + glassPanel.HideGlass(); + } + } + + /// + /// Create and configure the empty list msg overlay + /// + protected virtual void InitializeEmptyListMsgOverlay() { + TextOverlay overlay = new TextOverlay(); + overlay.Alignment = System.Drawing.ContentAlignment.MiddleCenter; + overlay.TextColor = SystemColors.ControlDarkDark; + overlay.BackColor = Color.BlanchedAlmond; + overlay.BorderColor = SystemColors.ControlDark; + overlay.BorderWidth = 2.0f; + this.EmptyListMsgOverlay = overlay; + } + + /// + /// Initialize the standard image and text overlays + /// + protected virtual void InitializeStandardOverlays() { + this.OverlayImage = new ImageOverlay(); + this.AddOverlay(this.OverlayImage); + this.OverlayText = new TextOverlay(); + this.AddOverlay(this.OverlayText); + } + + /// + /// Make sure that any overlays are visible. + /// + public virtual void ShowOverlays() { + // If we shouldn't show overlays, then don't create glass panels + if (!this.ShouldShowOverlays()) + return; + + // Make sure that each overlay has its own glass panels + if (this.Overlays.Count != this.glassPanels.Count) { + foreach (IOverlay overlay in this.Overlays) { + GlassPanelForm glassPanel = this.FindGlassPanelForOverlay(overlay); + if (glassPanel == null) { + glassPanel = new GlassPanelForm(); + glassPanel.Bind(this, overlay); + this.glassPanels.Add(glassPanel); + } + } + } + foreach (GlassPanelForm glassPanel in this.glassPanels) { + glassPanel.ShowGlass(); + } + } + + private bool ShouldShowOverlays() { + // If we are in design mode, we dont show the overlays + if (this.DesignMode) + return false; + + // If we are explicitly not using overlays, also don't show them + if (!this.UseOverlays) + return false; + + // If there are no overlays, guess... + if (!this.HasOverlays) + return false; + + // If we don't have 32-bit display, alpha blending doesn't work, so again, no overlays + // TODO: This should actually figure out which screen(s) the control is on, and make sure + // that each one is 32-bit. + if (Screen.PrimaryScreen.BitsPerPixel < 32) + return false; + + // Finally, we can show the overlays + return true; + } + + private GlassPanelForm FindGlassPanelForOverlay(IOverlay overlay) { + return this.glassPanels.Find(delegate(GlassPanelForm x) { return x.Overlay == overlay; }); + } + + /// + /// Refresh the display of the overlays + /// + public virtual void RefreshOverlays() { + foreach (GlassPanelForm glassPanel in this.glassPanels) { + glassPanel.Invalidate(); + } + } + + /// + /// Refresh the display of just one overlays + /// + public virtual void RefreshOverlay(IOverlay overlay) { + GlassPanelForm glassPanel = this.FindGlassPanelForOverlay(overlay); + if (glassPanel != null) + glassPanel.Invalidate(); + } + + /// + /// Remove the given decoration from this list + /// + /// The decoration to remove + public virtual void RemoveDecoration(IDecoration decoration) { + if (decoration == null) + return; + this.Decorations.Remove(decoration); + this.Invalidate(); + } + + /// + /// Remove the given overlay to those on this list + /// + /// The overlay + public virtual void RemoveOverlay(IOverlay overlay) { + if (overlay == null) + return; + this.Overlays.Remove(overlay); + GlassPanelForm glassPanel = this.FindGlassPanelForOverlay(overlay); + if (glassPanel != null) { + this.glassPanels.Remove(glassPanel); + glassPanel.Unbind(); + glassPanel.Dispose(); + } + } + + #endregion + + #region Filtering + + /// + /// Create a filter that will enact all the filtering currently installed + /// on the visible columns. + /// + public virtual IModelFilter CreateColumnFilter() { + List filters = new List(); + foreach (OLVColumn column in this.Columns) { + IModelFilter filter = column.ValueBasedFilter; + if (filter != null) + filters.Add(filter); + } + return (filters.Count == 0) ? null : new CompositeAllFilter(filters); + } + + /// + /// Do the actual work of filtering + /// + /// + /// + /// + /// + protected virtual IEnumerable FilterObjects(IEnumerable originalObjects, IModelFilter aModelFilter, IListFilter aListFilter) { + // Being cautious + originalObjects = originalObjects ?? new ArrayList(); + + // Tell the world to filter the objects. If they do so, don't do anything else +// ReSharper disable PossibleMultipleEnumeration + FilterEventArgs args = new FilterEventArgs(originalObjects); + this.OnFilter(args); + if (args.FilteredObjects != null) + return args.FilteredObjects; + + // Apply a filter to the list as a whole + if (aListFilter != null) + originalObjects = aListFilter.Filter(originalObjects); + + // Apply the object filter if there is one + if (aModelFilter != null) { + ArrayList filteredObjects = new ArrayList(); + foreach (object model in originalObjects) { + if (aModelFilter.Filter(model)) + filteredObjects.Add(model); + } + originalObjects = filteredObjects; + } + + return originalObjects; +// ReSharper restore PossibleMultipleEnumeration + } + + /// + /// Remove all column filtering. + /// + public virtual void ResetColumnFiltering() { + foreach (OLVColumn column in this.Columns) { + column.ValuesChosenForFiltering.Clear(); + } + this.UpdateColumnFiltering(); + } + + /// + /// Update the filtering of this ObjectListView based on the value filtering + /// defined in each column + /// + public virtual void UpdateColumnFiltering() { + //List filters = new List(); + //IModelFilter columnFilter = this.CreateColumnFilter(); + //if (columnFilter != null) + // filters.Add(columnFilter); + //if (this.AdditionalFilter != null) + // filters.Add(this.AdditionalFilter); + //this.ModelFilter = filters.Count == 0 ? null : new CompositeAllFilter(filters); + + if (this.AdditionalFilter == null) + this.ModelFilter = this.CreateColumnFilter(); + else { + IModelFilter columnFilter = this.CreateColumnFilter(); + if (columnFilter == null) + this.ModelFilter = this.AdditionalFilter; + else { + List filters = new List(); + filters.Add(columnFilter); + filters.Add(this.AdditionalFilter); + this.ModelFilter = new CompositeAllFilter(filters); + } + } + } + + /// + /// When some setting related to filtering changes, this method is called. + /// + protected virtual void UpdateFiltering() { + this.BuildList(true); + } + + /// + /// Update all renderers with the currently installed model filter + /// + protected virtual void NotifyNewModelFilter() { + IFilterAwareRenderer filterAware = this.DefaultRenderer as IFilterAwareRenderer; + if (filterAware != null) + filterAware.Filter = this.ModelFilter; + + foreach (OLVColumn column in this.AllColumns) { + filterAware = column.Renderer as IFilterAwareRenderer; + if (filterAware != null) + filterAware.Filter = this.ModelFilter; + } + } + + #endregion + + #region Persistent check state + + /// + /// Gets the checkedness of the given model. + /// + /// The model + /// The checkedness of the model. Defaults to unchecked. + protected virtual CheckState GetPersistentCheckState(object model) { + CheckState state; + if (model != null && this.CheckStateMap.TryGetValue(model, out state)) + return state; + return CheckState.Unchecked; + } + + /// + /// Remember the check state of the given model object + /// + /// The model to be remembered + /// The model's checkedness + /// The state given to the method + protected virtual CheckState SetPersistentCheckState(object model, CheckState state) { + if (model == null) + return CheckState.Unchecked; + + this.CheckStateMap[model] = state; + return state; + } + + /// + /// Forget any persistent checkbox state + /// + protected virtual void ClearPersistentCheckState() { + this.CheckStateMap = null; + } + + #endregion + + #region Implementation variables + + private bool isOwnerOfObjects; // does this ObjectListView own the Objects collection? + private bool hasIdleHandler; // has an Idle handler already been installed? + private bool hasResizeColumnsHandler; // has an idle handler been installed which will handle column resizing? + private bool isInWmPaintEvent; // is a WmPaint event currently being handled? + private bool shouldDoCustomDrawing; // should the list do its custom drawing? + private bool isMarqueSelecting; // Is a marque selection in progress? + private int suspendSelectionEventCount; // How many unmatched SuspendSelectionEvents() calls have been made? + + private readonly List glassPanels = new List(); // The transparent panel that draws overlays + private Dictionary visitedUrlMap = new Dictionary(); // Which urls have been visited? + + // TODO + //private CheckBoxSettings checkBoxSettings = new CheckBoxSettings(); + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/ObjectListView.shfb b/VG Music Studio - WinForms/ObjectListView/ObjectListView.shfb new file mode 100644 index 0000000..d90bb88 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/ObjectListView.shfb @@ -0,0 +1,47 @@ + + + + + + + All ObjectListView appears in this namespace + + + ObjectListViewDemo demonstrates helpful techniques when using an ObjectListView + Summary, Parameter, Returns, AutoDocumentCtors, Namespace + InheritedMembers, Protected, SealedProtected + + + .\Help\ + + + True + True + HtmlHelp1x + True + False + 2.0.50727 + True + False + True + False + + ObjectListView Reference + Documentation + en-US + + (c) Copyright 2006-2008 Phillip Piper All Rights Reserved + phillip.piper@gmail.com + + + Local + Msdn + Blank + Prototype + Guid + CSharp + False + AboveNamespaces + + + \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/Properties/Resources.Designer.cs b/VG Music Studio - WinForms/ObjectListView/Properties/Resources.Designer.cs new file mode 100644 index 0000000..8f9a7fc --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Properties/Resources.Designer.cs @@ -0,0 +1,113 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Kermalis.VGMusicStudio.WinForms.ObjectListView.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ClearFiltering { + get { + object obj = ResourceManager.GetObject("ClearFiltering", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ColumnFilterIndicator { + get { + object obj = ResourceManager.GetObject("ColumnFilterIndicator", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Filtering { + get { + object obj = ResourceManager.GetObject("Filtering", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap SortAscending { + get { + object obj = ResourceManager.GetObject("SortAscending", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap SortDescending { + get { + object obj = ResourceManager.GetObject("SortDescending", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Properties/Resources.resx b/VG Music Studio - WinForms/ObjectListView/Properties/Resources.resx new file mode 100644 index 0000000..b017d6a --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Properties/Resources.resx @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\clear-filter.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + + ..\Resources\filter-icons3.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\filter.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\sort-ascending.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\sort-descending.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/Rendering/Adornments.cs b/VG Music Studio - WinForms/ObjectListView/Rendering/Adornments.cs new file mode 100644 index 0000000..de53eb1 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Rendering/Adornments.cs @@ -0,0 +1,743 @@ +/* + * Adornments - Adornments are the basis for overlays and decorations -- things that can be rendered over the top of a ListView + * + * Author: Phillip Piper + * Date: 16/08/2009 1:02 AM + * + * Change log: + * v2.6 + * 2012-08-18 JPP - Correctly dispose of brush and pen resources + * v2.3 + * 2009-09-22 JPP - Added Wrap property to TextAdornment, to allow text wrapping to be disabled + * - Added ShrinkToWidth property to ImageAdornment + * 2009-08-17 JPP - Initial version + * + * To do: + * - Use IPointLocator rather than Corners + * - Add RotationCenter property rather than always using middle center + * + * Copyright (C) 2009-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// An adornment is the common base for overlays and decorations. + /// + public class GraphicAdornment + { + #region Public properties + + /// + /// Gets or sets the corner of the adornment that will be positioned at the reference corner + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public System.Drawing.ContentAlignment AdornmentCorner { + get { return this.adornmentCorner; } + set { this.adornmentCorner = value; } + } + private System.Drawing.ContentAlignment adornmentCorner = System.Drawing.ContentAlignment.MiddleCenter; + + /// + /// Gets or sets location within the reference rectangle where the adornment will be drawn + /// + /// This is a simplified interface to ReferenceCorner and AdornmentCorner + [Category("ObjectListView"), + Description("How will the adornment be aligned"), + DefaultValue(System.Drawing.ContentAlignment.BottomRight), + NotifyParentProperty(true)] + public System.Drawing.ContentAlignment Alignment { + get { return this.alignment; } + set { + this.alignment = value; + this.ReferenceCorner = value; + this.AdornmentCorner = value; + } + } + private System.Drawing.ContentAlignment alignment = System.Drawing.ContentAlignment.BottomRight; + + /// + /// Gets or sets the offset by which the position of the adornment will be adjusted + /// + [Category("ObjectListView"), + Description("The offset by which the position of the adornment will be adjusted"), + DefaultValue(typeof(Size), "0,0")] + public Size Offset { + get { return this.offset; } + set { this.offset = value; } + } + private Size offset = new Size(); + + /// + /// Gets or sets the point of the reference rectangle to which the adornment will be aligned. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public System.Drawing.ContentAlignment ReferenceCorner { + get { return this.referenceCorner; } + set { this.referenceCorner = value; } + } + private System.Drawing.ContentAlignment referenceCorner = System.Drawing.ContentAlignment.MiddleCenter; + + /// + /// Gets or sets the degree of rotation by which the adornment will be transformed. + /// The centre of rotation will be the center point of the adornment. + /// + [Category("ObjectListView"), + Description("The degree of rotation that will be applied to the adornment."), + DefaultValue(0), + NotifyParentProperty(true)] + public int Rotation { + get { return this.rotation; } + set { this.rotation = value; } + } + private int rotation; + + /// + /// Gets or sets the transparency of the overlay. + /// 0 is completely transparent, 255 is completely opaque. + /// + [Category("ObjectListView"), + Description("The transparency of this adornment. 0 is completely transparent, 255 is completely opaque."), + DefaultValue(128)] + public int Transparency { + get { return this.transparency; } + set { this.transparency = Math.Min(255, Math.Max(0, value)); } + } + private int transparency = 128; + + #endregion + + #region Calculations + + /// + /// Calculate the location of rectangle of the given size, + /// so that it's indicated corner would be at the given point. + /// + /// The point + /// + /// Which corner will be positioned at the reference point + /// + /// CalculateAlignedPosition(new Point(50, 100), new Size(10, 20), System.Drawing.ContentAlignment.TopLeft) -> Point(50, 100) + /// CalculateAlignedPosition(new Point(50, 100), new Size(10, 20), System.Drawing.ContentAlignment.MiddleCenter) -> Point(45, 90) + /// CalculateAlignedPosition(new Point(50, 100), new Size(10, 20), System.Drawing.ContentAlignment.BottomRight) -> Point(40, 80) + public virtual Point CalculateAlignedPosition(Point pt, Size size, System.Drawing.ContentAlignment corner) { + switch (corner) { + case System.Drawing.ContentAlignment.TopLeft: + return pt; + case System.Drawing.ContentAlignment.TopCenter: + return new Point(pt.X - (size.Width / 2), pt.Y); + case System.Drawing.ContentAlignment.TopRight: + return new Point(pt.X - size.Width, pt.Y); + case System.Drawing.ContentAlignment.MiddleLeft: + return new Point(pt.X, pt.Y - (size.Height / 2)); + case System.Drawing.ContentAlignment.MiddleCenter: + return new Point(pt.X - (size.Width / 2), pt.Y - (size.Height / 2)); + case System.Drawing.ContentAlignment.MiddleRight: + return new Point(pt.X - size.Width, pt.Y - (size.Height / 2)); + case System.Drawing.ContentAlignment.BottomLeft: + return new Point(pt.X, pt.Y - size.Height); + case System.Drawing.ContentAlignment.BottomCenter: + return new Point(pt.X - (size.Width / 2), pt.Y - size.Height); + case System.Drawing.ContentAlignment.BottomRight: + return new Point(pt.X - size.Width, pt.Y - size.Height); + } + + // Should never reach here + return pt; + } + + /// + /// Calculate a rectangle that has the given size which is positioned so that + /// its alignment point is at the reference location of the given rect. + /// + /// + /// + /// + public virtual Rectangle CreateAlignedRectangle(Rectangle r, Size sz) { + return this.CreateAlignedRectangle(r, sz, this.ReferenceCorner, this.AdornmentCorner, this.Offset); + } + + /// + /// Create a rectangle of the given size which is positioned so that + /// its indicated corner is at the indicated corner of the reference rect. + /// + /// + /// + /// + /// + /// + /// + /// + /// Creates a rectangle so that its bottom left is at the centre of the reference: + /// corner=BottomLeft, referenceCorner=MiddleCenter + /// This is a powerful concept that takes some getting used to, but is + /// very neat once you understand it. + /// + public virtual Rectangle CreateAlignedRectangle(Rectangle r, Size sz, + System.Drawing.ContentAlignment corner, System.Drawing.ContentAlignment referenceCorner, Size offset) { + Point referencePt = this.CalculateCorner(r, referenceCorner); + Point topLeft = this.CalculateAlignedPosition(referencePt, sz, corner); + return new Rectangle(topLeft + offset, sz); + } + + /// + /// Return the point at the indicated corner of the given rectangle (it doesn't + /// have to be a corner, but a named location) + /// + /// The reference rectangle + /// Which point of the rectangle should be returned? + /// A point + /// CalculateReferenceLocation(new Rectangle(0, 0, 50, 100), System.Drawing.ContentAlignment.TopLeft) -> Point(0, 0) + /// CalculateReferenceLocation(new Rectangle(0, 0, 50, 100), System.Drawing.ContentAlignment.MiddleCenter) -> Point(25, 50) + /// CalculateReferenceLocation(new Rectangle(0, 0, 50, 100), System.Drawing.ContentAlignment.BottomRight) -> Point(50, 100) + public virtual Point CalculateCorner(Rectangle r, System.Drawing.ContentAlignment corner) { + switch (corner) { + case System.Drawing.ContentAlignment.TopLeft: + return new Point(r.Left, r.Top); + case System.Drawing.ContentAlignment.TopCenter: + return new Point(r.X + (r.Width / 2), r.Top); + case System.Drawing.ContentAlignment.TopRight: + return new Point(r.Right, r.Top); + case System.Drawing.ContentAlignment.MiddleLeft: + return new Point(r.Left, r.Top + (r.Height / 2)); + case System.Drawing.ContentAlignment.MiddleCenter: + return new Point(r.X + (r.Width / 2), r.Top + (r.Height / 2)); + case System.Drawing.ContentAlignment.MiddleRight: + return new Point(r.Right, r.Top + (r.Height / 2)); + case System.Drawing.ContentAlignment.BottomLeft: + return new Point(r.Left, r.Bottom); + case System.Drawing.ContentAlignment.BottomCenter: + return new Point(r.X + (r.Width / 2), r.Bottom); + case System.Drawing.ContentAlignment.BottomRight: + return new Point(r.Right, r.Bottom); + } + + // Should never reach here + return r.Location; + } + + /// + /// Given the item and the subitem, calculate its bounds. + /// + /// + /// + /// + public virtual Rectangle CalculateItemBounds(OLVListItem item, OLVListSubItem subItem) { + if (item == null) + return Rectangle.Empty; + + if (subItem == null) + return item.Bounds; + + return item.GetSubItemBounds(item.SubItems.IndexOf(subItem)); + } + + #endregion + + #region Commands + + /// + /// Apply any specified rotation to the Graphic content. + /// + /// The Graphics to be transformed + /// The rotation will be around the centre of this rect + protected virtual void ApplyRotation(Graphics g, Rectangle r) { + if (this.Rotation == 0) + return; + + // THINK: Do we want to reset the transform? I think we want to push a new transform + g.ResetTransform(); + Matrix m = new Matrix(); + m.RotateAt(this.Rotation, new Point(r.Left + r.Width / 2, r.Top + r.Height / 2)); + g.Transform = m; + } + + /// + /// Reverse the rotation created by ApplyRotation() + /// + /// + protected virtual void UnapplyRotation(Graphics g) { + if (this.Rotation != 0) + g.ResetTransform(); + } + + #endregion + } + + /// + /// An overlay that will draw an image over the top of the ObjectListView + /// + public class ImageAdornment : GraphicAdornment + { + #region Public properties + + /// + /// Gets or sets the image that will be drawn + /// + [Category("ObjectListView"), + Description("The image that will be drawn"), + DefaultValue(null), + NotifyParentProperty(true)] + public Image Image { + get { return this.image; } + set { this.image = value; } + } + private Image image; + + /// + /// Gets or sets if the image will be shrunk to fit with its horizontal bounds + /// + [Category("ObjectListView"), + Description("Will the image be shrunk to fit within its width?"), + DefaultValue(false)] + public bool ShrinkToWidth { + get { return this.shrinkToWidth; } + set { this.shrinkToWidth = value; } + } + private bool shrinkToWidth; + + #endregion + + #region Commands + + /// + /// Draw the image in its specified location + /// + /// The Graphics used for drawing + /// The bounds of the rendering + public virtual void DrawImage(Graphics g, Rectangle r) { + if (this.ShrinkToWidth) + this.DrawScaledImage(g, r, this.Image, this.Transparency); + else + this.DrawImage(g, r, this.Image, this.Transparency); + } + + /// + /// Draw the image in its specified location + /// + /// The image to be drawn + /// The Graphics used for drawing + /// The bounds of the rendering + /// How transparent should the image be (0 is completely transparent, 255 is opaque) + public virtual void DrawImage(Graphics g, Rectangle r, Image image, int transparency) { + if (image != null) + this.DrawImage(g, r, image, image.Size, transparency); + } + + /// + /// Draw the image in its specified location + /// + /// The image to be drawn + /// The Graphics used for drawing + /// The bounds of the rendering + /// How big should the image be? + /// How transparent should the image be (0 is completely transparent, 255 is opaque) + public virtual void DrawImage(Graphics g, Rectangle r, Image image, Size sz, int transparency) { + if (image == null) + return; + + Rectangle adornmentBounds = this.CreateAlignedRectangle(r, sz); + try { + this.ApplyRotation(g, adornmentBounds); + this.DrawTransparentBitmap(g, adornmentBounds, image, transparency); + } + finally { + this.UnapplyRotation(g); + } + } + + /// + /// Draw the image in its specified location, scaled so that it is not wider + /// than the given rectangle. Height is scaled proportional to the width. + /// + /// The image to be drawn + /// The Graphics used for drawing + /// The bounds of the rendering + /// How transparent should the image be (0 is completely transparent, 255 is opaque) + public virtual void DrawScaledImage(Graphics g, Rectangle r, Image image, int transparency) { + if (image == null) + return; + + // If the image is too wide to be drawn in the space provided, proportionally scale it down. + // Too tall images are not scaled. + Size size = image.Size; + if (image.Width > r.Width) { + float scaleRatio = (float)r.Width / (float)image.Width; + size.Height = (int)((float)image.Height * scaleRatio); + size.Width = r.Width - 1; + } + + this.DrawImage(g, r, image, size, transparency); + } + + /// + /// Utility to draw a bitmap transparently. + /// + /// + /// + /// + /// + protected virtual void DrawTransparentBitmap(Graphics g, Rectangle r, Image image, int transparency) { + ImageAttributes imageAttributes = null; + if (transparency != 255) { + imageAttributes = new ImageAttributes(); + float a = (float)transparency / 255.0f; + float[][] colorMatrixElements = { + new float[] {1, 0, 0, 0, 0}, + new float[] {0, 1, 0, 0, 0}, + new float[] {0, 0, 1, 0, 0}, + new float[] {0, 0, 0, a, 0}, + new float[] {0, 0, 0, 0, 1}}; + + imageAttributes.SetColorMatrix(new ColorMatrix(colorMatrixElements)); + } + + g.DrawImage(image, + r, // destination rectangle + 0, 0, image.Size.Width, image.Size.Height, // source rectangle + GraphicsUnit.Pixel, + imageAttributes); + } + + #endregion + } + + /// + /// An adornment that will draw text + /// + public class TextAdornment : GraphicAdornment + { + #region Public properties + + /// + /// Gets or sets the background color of the text + /// Set this to Color.Empty to not draw a background + /// + [Category("ObjectListView"), + Description("The background color of the text"), + DefaultValue(typeof(Color), "")] + public Color BackColor { + get { return this.backColor; } + set { this.backColor = value; } + } + private Color backColor = Color.Empty; + + /// + /// Gets the brush that will be used to paint the text + /// + [Browsable(false)] + public Brush BackgroundBrush { + get { + return new SolidBrush(Color.FromArgb(this.workingTransparency, this.BackColor)); + } + } + + /// + /// Gets or sets the color of the border around the billboard. + /// Set this to Color.Empty to remove the border + /// + [Category("ObjectListView"), + Description("The color of the border around the text"), + DefaultValue(typeof(Color), "")] + public Color BorderColor { + get { return this.borderColor; } + set { this.borderColor = value; } + } + private Color borderColor = Color.Empty; + + /// + /// Gets the brush that will be used to paint the text + /// + [Browsable(false)] + public Pen BorderPen { + get { + return new Pen(Color.FromArgb(this.workingTransparency, this.BorderColor), this.BorderWidth); + } + } + + /// + /// Gets or sets the width of the border around the text + /// + [Category("ObjectListView"), + Description("The width of the border around the text"), + DefaultValue(0.0f)] + public float BorderWidth { + get { return this.borderWidth; } + set { this.borderWidth = value; } + } + private float borderWidth; + + /// + /// How rounded should the corners of the border be? 0 means no rounding. + /// + /// If this value is too large, the edges of the border will appear odd. + [Category("ObjectListView"), + Description("How rounded should the corners of the border be? 0 means no rounding."), + DefaultValue(16.0f), + NotifyParentProperty(true)] + public float CornerRounding { + get { return this.cornerRounding; } + set { this.cornerRounding = value; } + } + private float cornerRounding = 16.0f; + + /// + /// Gets or sets the font that will be used to draw the text + /// + [Category("ObjectListView"), + Description("The font that will be used to draw the text"), + DefaultValue(null), + NotifyParentProperty(true)] + public Font Font { + get { return this.font; } + set { this.font = value; } + } + private Font font; + + /// + /// Gets the font that will be used to draw the text or a reasonable default + /// + [Browsable(false)] + public Font FontOrDefault { + get { + return this.Font ?? new Font("Tahoma", 16); + } + } + + /// + /// Does this text have a background? + /// + [Browsable(false)] + public bool HasBackground { + get { + return this.BackColor != Color.Empty; + } + } + + /// + /// Does this overlay have a border? + /// + [Browsable(false)] + public bool HasBorder { + get { + return this.BorderColor != Color.Empty && this.BorderWidth > 0; + } + } + + /// + /// Gets or sets the maximum width of the text. Text longer than this will wrap. + /// 0 means no maximum. + /// + [Category("ObjectListView"), + Description("The maximum width the text (0 means no maximum). Text longer than this will wrap"), + DefaultValue(0)] + public int MaximumTextWidth { + get { return this.maximumTextWidth; } + set { this.maximumTextWidth = value; } + } + private int maximumTextWidth; + + /// + /// Gets or sets the formatting that should be used on the text + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual StringFormat StringFormat { + get { + if (this.stringFormat == null) { + this.stringFormat = new StringFormat(); + this.stringFormat.Alignment = StringAlignment.Center; + this.stringFormat.LineAlignment = StringAlignment.Center; + this.stringFormat.Trimming = StringTrimming.EllipsisCharacter; + if (!this.Wrap) + this.stringFormat.FormatFlags = StringFormatFlags.NoWrap; + } + return this.stringFormat; + } + set { this.stringFormat = value; } + } + private StringFormat stringFormat; + + /// + /// Gets or sets the text that will be drawn + /// + [Category("ObjectListView"), + Description("The text that will be drawn over the top of the ListView"), + DefaultValue(null), + NotifyParentProperty(true), + Localizable(true)] + public string Text { + get { return this.text; } + set { this.text = value; } + } + private string text; + + /// + /// Gets the brush that will be used to paint the text + /// + [Browsable(false)] + public Brush TextBrush { + get { + return new SolidBrush(Color.FromArgb(this.workingTransparency, this.TextColor)); + } + } + + /// + /// Gets or sets the color of the text + /// + [Category("ObjectListView"), + Description("The color of the text"), + DefaultValue(typeof(Color), "DarkBlue"), + NotifyParentProperty(true)] + public Color TextColor { + get { return this.textColor; } + set { this.textColor = value; } + } + private Color textColor = Color.DarkBlue; + + /// + /// Gets or sets whether the text will wrap when it exceeds its bounds + /// + [Category("ObjectListView"), + Description("Will the text wrap?"), + DefaultValue(true)] + public bool Wrap { + get { return this.wrap; } + set { this.wrap = value; } + } + private bool wrap = true; + + #endregion + + #region Implementation + + /// + /// Draw our text with our stored configuration in relation to the given + /// reference rectangle + /// + /// The Graphics used for drawing + /// The reference rectangle in relation to which the text will be drawn + public virtual void DrawText(Graphics g, Rectangle r) { + this.DrawText(g, r, this.Text, this.Transparency); + } + + /// + /// Draw the given text with our stored configuration + /// + /// The Graphics used for drawing + /// The reference rectangle in relation to which the text will be drawn + /// The text to draw + /// How opaque should be text be + public virtual void DrawText(Graphics g, Rectangle r, string s, int transparency) { + if (String.IsNullOrEmpty(s)) + return; + + Rectangle textRect = this.CalculateTextBounds(g, r, s); + this.DrawBorderedText(g, textRect, s, transparency); + } + + /// + /// Draw the text with a border + /// + /// The Graphics used for drawing + /// The bounds within which the text should be drawn + /// The text to draw + /// How opaque should be text be + protected virtual void DrawBorderedText(Graphics g, Rectangle textRect, string text, int transparency) { + Rectangle borderRect = textRect; + borderRect.Inflate((int)this.BorderWidth / 2, (int)this.BorderWidth / 2); + borderRect.Y -= 1; // Looker better a little higher + + try { + this.ApplyRotation(g, textRect); + using (GraphicsPath path = this.GetRoundedRect(borderRect, this.CornerRounding)) { + this.workingTransparency = transparency; + if (this.HasBackground) { + using (Brush b = this.BackgroundBrush) + g.FillPath(b, path); + } + + using (Brush b = this.TextBrush) + g.DrawString(text, this.FontOrDefault, b, textRect, this.StringFormat); + + if (this.HasBorder) { + using (Pen p = this.BorderPen) + g.DrawPath(p, path); + } + } + } + finally { + this.UnapplyRotation(g); + } + } + + /// + /// Return the rectangle that will be the precise bounds of the displayed text + /// + /// + /// + /// + /// The bounds of the text + protected virtual Rectangle CalculateTextBounds(Graphics g, Rectangle r, string s) { + int maxWidth = this.MaximumTextWidth <= 0 ? r.Width : this.MaximumTextWidth; + SizeF sizeF = g.MeasureString(s, this.FontOrDefault, maxWidth, this.StringFormat); + Size size = new Size(1 + (int)sizeF.Width, 1 + (int)sizeF.Height); + return this.CreateAlignedRectangle(r, size); + } + + /// + /// Return a GraphicPath that is a round cornered rectangle + /// + /// The rectangle + /// The diameter of the corners + /// A round cornered rectangle path + /// If I could rely on people using C# 3.0+, this should be + /// an extension method of GraphicsPath. + protected virtual GraphicsPath GetRoundedRect(Rectangle rect, float diameter) { + GraphicsPath path = new GraphicsPath(); + + if (diameter > 0) { + RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter); + path.AddArc(arc, 180, 90); + arc.X = rect.Right - diameter; + path.AddArc(arc, 270, 90); + arc.Y = rect.Bottom - diameter; + path.AddArc(arc, 0, 90); + arc.X = rect.Left; + path.AddArc(arc, 90, 90); + path.CloseFigure(); + } else { + path.AddRectangle(rect); + } + + return path; + } + + #endregion + + private int workingTransparency; + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Rendering/Decorations.cs b/VG Music Studio - WinForms/ObjectListView/Rendering/Decorations.cs new file mode 100644 index 0000000..3892532 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Rendering/Decorations.cs @@ -0,0 +1,973 @@ +/* + * Decorations - Images, text or other things that can be rendered onto an ObjectListView + * + * Author: Phillip Piper + * Date: 19/08/2009 10:56 PM + * + * Change log: + * 2018-04-30 JPP - Added ColumnEdgeDecoration. + * TintedColumnDecoration now uses common base class, ColumnDecoration. + * v2.5 + * 2011-04-04 JPP - Added ability to have a gradient background on BorderDecoration + * v2.4 + * 2010-04-15 JPP - Tweaked LightBoxDecoration a little + * v2.3 + * 2009-09-23 JPP - Added LeftColumn and RightColumn to RowBorderDecoration + * 2009-08-23 JPP - Added LightBoxDecoration + * 2009-08-19 JPP - Initial version. Separated from Overlays.cs + * + * To do: + * + * Copyright (C) 2009-2018 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// A decoration is an overlay that draws itself in relation to a given row or cell. + /// Decorations scroll when the listview scrolls. + /// + public interface IDecoration : IOverlay + { + /// + /// Gets or sets the row that is to be decorated + /// + OLVListItem ListItem { get; set; } + + /// + /// Gets or sets the subitem that is to be decorated + /// + OLVListSubItem SubItem { get; set; } + } + + /// + /// An AbstractDecoration is a safe do-nothing implementation of the IDecoration interface + /// + public class AbstractDecoration : IDecoration + { + #region IDecoration Members + + /// + /// Gets or sets the row that is to be decorated + /// + public OLVListItem ListItem { + get { return listItem; } + set { listItem = value; } + } + private OLVListItem listItem; + + /// + /// Gets or sets the subitem that is to be decorated + /// + public OLVListSubItem SubItem { + get { return subItem; } + set { subItem = value; } + } + private OLVListSubItem subItem; + + #endregion + + #region Public properties + + /// + /// Gets the bounds of the decorations row + /// + public Rectangle RowBounds { + get { + if (this.ListItem == null) + return Rectangle.Empty; + else + return this.ListItem.Bounds; + } + } + + /// + /// Get the bounds of the decorations cell + /// + public Rectangle CellBounds { + get { + if (this.ListItem == null || this.SubItem == null) + return Rectangle.Empty; + else + return this.ListItem.GetSubItemBounds(this.ListItem.SubItems.IndexOf(this.SubItem)); + } + } + + #endregion + + #region IOverlay Members + + /// + /// Draw the decoration + /// + /// + /// + /// + public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) { + } + + #endregion + } + + + /// + /// This decoration draws something over a given column. + /// Subclasses must override DrawDecoration() + /// + public class ColumnDecoration : AbstractDecoration { + #region Constructors + + /// + /// Create a ColumnDecoration + /// + public ColumnDecoration() { + } + + /// + /// Create a ColumnDecoration + /// + /// + public ColumnDecoration(OLVColumn column) + : this() { + this.ColumnToDecorate = column ?? throw new ArgumentNullException("column"); + } + + #endregion + + #region Properties + + /// + /// Gets or sets the column that will be decorated + /// + public OLVColumn ColumnToDecorate { + get { return this.columnToDecorate; } + set { this.columnToDecorate = value; } + } + private OLVColumn columnToDecorate; + + /// + /// Gets or sets the pen that will be used to draw the column decoration + /// + public Pen Pen { + get { + return this.pen ?? Pens.DarkSlateBlue; + } + set { + if (this.pen == value) + return; + + if (this.pen != null) { + this.pen.Dispose(); + } + + this.pen = value; + } + } + private Pen pen; + + #endregion + + #region IOverlay Members + + /// + /// Draw a decoration over our column + /// + /// + /// This overlay only works when: + /// - the list is in Details view + /// - there is at least one row + /// - there is a selected column (or a specified tint column) + /// + /// + /// + /// + public override void Draw(ObjectListView olv, Graphics g, Rectangle r) { + + if (olv.View != System.Windows.Forms.View.Details) + return; + + if (olv.GetItemCount() == 0) + return; + + if (this.ColumnToDecorate == null) + return; + + Point sides = NativeMethods.GetScrolledColumnSides(olv, this.ColumnToDecorate.Index); + if (sides.X == -1) + return; + + Rectangle columnBounds = new Rectangle(sides.X, r.Top, sides.Y - sides.X, r.Bottom); + + // Find the bottom of the last item. The decoration should extend only to there. + OLVListItem lastItem = olv.GetLastItemInDisplayOrder(); + if (lastItem != null) { + Rectangle lastItemBounds = lastItem.Bounds; + if (!lastItemBounds.IsEmpty && lastItemBounds.Bottom < columnBounds.Bottom) + columnBounds.Height = lastItemBounds.Bottom - columnBounds.Top; + } + + // Delegate the drawing of the actual decoration + this.DrawDecoration(olv, g, r, columnBounds); + } + + /// + /// Subclasses should override this to draw exactly what they want + /// + /// + /// + /// + /// + public virtual void DrawDecoration(ObjectListView olv, Graphics g, Rectangle r, Rectangle columnBounds) { + g.DrawRectangle(this.Pen, columnBounds); + } + + #endregion + } + + /// + /// This decoration draws a slight tint over a column of the + /// owning listview. If no column is explicitly set, the selected + /// column in the listview will be used. + /// The selected column is normally the sort column, but does not have to be. + /// + public class TintedColumnDecoration : ColumnDecoration { + #region Constructors + + /// + /// Create a TintedColumnDecoration + /// + public TintedColumnDecoration() { + this.Tint = Color.FromArgb(15, Color.Blue); + } + + /// + /// Create a TintedColumnDecoration + /// + /// + public TintedColumnDecoration(OLVColumn column) + : this() { + this.ColumnToDecorate = column; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the color that will be 'tinted' over the selected column + /// + public Color Tint { + get { return this.tint; } + set { + if (this.tint == value) + return; + + if (this.tintBrush != null) { + this.tintBrush.Dispose(); + this.tintBrush = null; + } + + this.tint = value; + this.tintBrush = new SolidBrush(this.tint); + } + } + private Color tint; + private SolidBrush tintBrush; + + #endregion + + #region IOverlay Members + + public override void DrawDecoration(ObjectListView olv, Graphics g, Rectangle r, Rectangle columnBounds) { + g.FillRectangle(this.tintBrush, columnBounds); + } + + #endregion + } + + /// + /// Specify on which side edge the decoration will be drawn + /// + public enum ColumnEdge { + Left, + Right + } + + /// + /// This decoration draws a line on the edge(s) of its given column + /// + /// + /// Like all decorations, this draws over the contents of list view. + /// If you set the Pen too wide enough, you may overwrite the contents + /// of the column (if alignment is Inside) or the surrounding columns (if alignment is Outside) + /// + public class ColumnEdgeDecoration : ColumnDecoration { + #region Constructors + + /// + /// Create a ColumnEdgeDecoration + /// + public ColumnEdgeDecoration() { + } + + /// + /// Create a ColumnEdgeDecoration which draws a line over the right edges of the column (by default) + /// + /// + /// + /// + /// + public ColumnEdgeDecoration(OLVColumn column, Pen pen = null, ColumnEdge edge = ColumnEdge.Right, float xOffset = 0) + : this() { + this.ColumnToDecorate = column; + this.Pen = pen; + this.Edge = edge; + this.XOffset = xOffset; + } + + #endregion + + #region Properties + + /// + /// Gets or sets whether this decoration will draw a line on the left or right edge of the column + /// + public ColumnEdge Edge { + get { return edge; } + set { edge = value; } + } + private ColumnEdge edge = ColumnEdge.Right; + + /// + /// Gets or sets the horizontal offset from centered at which the line will be drawn + /// + public float XOffset { + get { return xOffset; } + set { xOffset = value; } + } + private float xOffset; + + #endregion + + #region IOverlay Members + + public override void DrawDecoration(ObjectListView olv, Graphics g, Rectangle r, Rectangle columnBounds) { + float left = CalculateEdge(columnBounds); + g.DrawLine(this.Pen, left, columnBounds.Top, left, columnBounds.Bottom); + + } + + private float CalculateEdge(Rectangle columnBounds) { + float tweak = this.XOffset + (this.Pen.Width <= 2 ? 0 : 1); + int x = this.Edge == ColumnEdge.Left ? columnBounds.Left : columnBounds.Right; + return tweak + x - this.Pen.Width / 2; + } + + #endregion + } + + /// + /// This decoration draws an optionally filled border around a rectangle. + /// Subclasses must override CalculateBounds(). + /// + public class BorderDecoration : AbstractDecoration + { + #region Constructors + + /// + /// Create a BorderDecoration + /// + public BorderDecoration() + : this(new Pen(Color.FromArgb(64, Color.Blue), 1)) { + } + + /// + /// Create a BorderDecoration + /// + /// The pen used to draw the border + public BorderDecoration(Pen borderPen) { + this.BorderPen = borderPen; + } + + /// + /// Create a BorderDecoration + /// + /// The pen used to draw the border + /// The brush used to fill the rectangle + public BorderDecoration(Pen borderPen, Brush fill) { + this.BorderPen = borderPen; + this.FillBrush = fill; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the pen that will be used to draw the border + /// + public Pen BorderPen { + get { return this.borderPen; } + set { this.borderPen = value; } + } + private Pen borderPen; + + /// + /// Gets or sets the padding that will be added to the bounds of the item + /// before drawing the border and fill. + /// + public Size BoundsPadding { + get { return this.boundsPadding; } + set { this.boundsPadding = value; } + } + private Size boundsPadding = new Size(-1, 2); + + /// + /// How rounded should the corners of the border be? 0 means no rounding. + /// + /// If this value is too large, the edges of the border will appear odd. + public float CornerRounding { + get { return this.cornerRounding; } + set { this.cornerRounding = value; } + } + private float cornerRounding = 16.0f; + + /// + /// Gets or sets the brush that will be used to fill the border + /// + /// This value is ignored when using gradient brush + public Brush FillBrush { + get { return this.fillBrush; } + set { this.fillBrush = value; } + } + private Brush fillBrush = new SolidBrush(Color.FromArgb(64, Color.Blue)); + + /// + /// Gets or sets the color that will be used as the start of a gradient fill. + /// + /// This and FillGradientTo must be given value to show a gradient + public Color? FillGradientFrom { + get { return this.fillGradientFrom; } + set { this.fillGradientFrom = value; } + } + private Color? fillGradientFrom; + + /// + /// Gets or sets the color that will be used as the end of a gradient fill. + /// + /// This and FillGradientFrom must be given value to show a gradient + public Color? FillGradientTo { + get { return this.fillGradientTo; } + set { this.fillGradientTo = value; } + } + private Color? fillGradientTo; + + /// + /// Gets or sets the fill mode that will be used for the gradient. + /// + public LinearGradientMode FillGradientMode { + get { return this.fillGradientMode; } + set { this.fillGradientMode = value; } + } + private LinearGradientMode fillGradientMode = LinearGradientMode.Vertical; + + #endregion + + #region IOverlay Members + + /// + /// Draw a filled border + /// + /// + /// + /// + public override void Draw(ObjectListView olv, Graphics g, Rectangle r) { + Rectangle bounds = this.CalculateBounds(); + if (!bounds.IsEmpty) + this.DrawFilledBorder(g, bounds); + } + + #endregion + + #region Subclass responsibility + + /// + /// Subclasses should override this to say where the border should be drawn + /// + /// + protected virtual Rectangle CalculateBounds() { + return Rectangle.Empty; + } + + #endregion + + #region Implementation utilities + + /// + /// Do the actual work of drawing the filled border + /// + /// + /// + protected void DrawFilledBorder(Graphics g, Rectangle bounds) { + bounds.Inflate(this.BoundsPadding); + GraphicsPath path = this.GetRoundedRect(bounds, this.CornerRounding); + if (this.FillGradientFrom != null && this.FillGradientTo != null) { + if (this.FillBrush != null) + this.FillBrush.Dispose(); + this.FillBrush = new LinearGradientBrush(bounds, this.FillGradientFrom.Value, this.FillGradientTo.Value, this.FillGradientMode); + } + if (this.FillBrush != null) + g.FillPath(this.FillBrush, path); + if (this.BorderPen != null) + g.DrawPath(this.BorderPen, path); + } + + /// + /// Create a GraphicsPath that represents a round cornered rectangle. + /// + /// + /// If this is 0 or less, the rectangle will not be rounded. + /// + protected GraphicsPath GetRoundedRect(RectangleF rect, float diameter) { + GraphicsPath path = new GraphicsPath(); + + if (diameter <= 0.0f) { + path.AddRectangle(rect); + } else { + RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter); + path.AddArc(arc, 180, 90); + arc.X = rect.Right - diameter; + path.AddArc(arc, 270, 90); + arc.Y = rect.Bottom - diameter; + path.AddArc(arc, 0, 90); + arc.X = rect.Left; + path.AddArc(arc, 90, 90); + path.CloseFigure(); + } + + return path; + } + + #endregion + } + + /// + /// Instances of this class draw a border around the decorated row + /// + public class RowBorderDecoration : BorderDecoration + { + /// + /// Gets or sets the index of the left most column to be used for the border + /// + public int LeftColumn { + get { return leftColumn; } + set { leftColumn = value; } + } + private int leftColumn = -1; + + /// + /// Gets or sets the index of the right most column to be used for the border + /// + public int RightColumn { + get { return rightColumn; } + set { rightColumn = value; } + } + private int rightColumn = -1; + + /// + /// Calculate the boundaries of the border + /// + /// + protected override Rectangle CalculateBounds() { + Rectangle bounds = this.RowBounds; + if (this.ListItem == null) + return bounds; + + if (this.LeftColumn >= 0) { + Rectangle leftCellBounds = this.ListItem.GetSubItemBounds(this.LeftColumn); + if (!leftCellBounds.IsEmpty) { + bounds.Width = bounds.Right - leftCellBounds.Left; + bounds.X = leftCellBounds.Left; + } + } + + if (this.RightColumn >= 0) { + Rectangle rightCellBounds = this.ListItem.GetSubItemBounds(this.RightColumn); + if (!rightCellBounds.IsEmpty) { + bounds.Width = rightCellBounds.Right - bounds.Left; + } + } + + return bounds; + } + } + + /// + /// Instances of this class draw a border around the decorated subitem. + /// + public class CellBorderDecoration : BorderDecoration + { + /// + /// Calculate the boundaries of the border + /// + /// + protected override Rectangle CalculateBounds() { + return this.CellBounds; + } + } + + /// + /// This decoration puts a border around the cell being edited and + /// optionally "lightboxes" the cell (makes the rest of the control dark). + /// + public class EditingCellBorderDecoration : BorderDecoration + { + #region Life and death + + /// + /// Create a EditingCellBorderDecoration + /// + public EditingCellBorderDecoration() { + this.FillBrush = null; + this.BorderPen = new Pen(Color.DarkBlue, 2); + this.CornerRounding = 8; + this.BoundsPadding = new Size(10, 8); + + } + + /// + /// Create a EditingCellBorderDecoration + /// + /// Should the decoration use a lighbox display style? + public EditingCellBorderDecoration(bool useLightBox) : this() + { + this.UseLightbox = useLightbox; + } + + #endregion + + #region Configuration properties + + /// + /// Gets or set whether the decoration should make the rest of + /// the control dark when a cell is being edited + /// + /// If this is true, FillBrush is used to overpaint + /// the control. + public bool UseLightbox { + get { return this.useLightbox; } + set { + if (this.useLightbox == value) + return; + this.useLightbox = value; + if (this.useLightbox) { + if (this.FillBrush == null) + this.FillBrush = new SolidBrush(Color.FromArgb(64, Color.Black)); + } + } + } + private bool useLightbox; + + #endregion + + #region Implementation + + /// + /// Draw the decoration + /// + /// + /// + /// + public override void Draw(ObjectListView olv, Graphics g, Rectangle r) { + if (!olv.IsCellEditing) + return; + + Rectangle bounds = olv.CellEditor.Bounds; + if (bounds.IsEmpty) + return; + + bounds.Inflate(this.BoundsPadding); + GraphicsPath path = this.GetRoundedRect(bounds, this.CornerRounding); + if (this.FillBrush != null) { + if (this.UseLightbox) { + using (Region newClip = new Region(r)) { + newClip.Exclude(path); + Region originalClip = g.Clip; + g.Clip = newClip; + g.FillRectangle(this.FillBrush, r); + g.Clip = originalClip; + } + } else { + g.FillPath(this.FillBrush, path); + } + } + if (this.BorderPen != null) + g.DrawPath(this.BorderPen, path); + } + + #endregion + } + + /// + /// This decoration causes everything *except* the row under the mouse to be overpainted + /// with a tint, making the row under the mouse stand out in comparison. + /// The darker and more opaque the fill color, the more obvious the + /// decorated row becomes. + /// + public class LightBoxDecoration : BorderDecoration + { + /// + /// Create a LightBoxDecoration + /// + public LightBoxDecoration() { + this.BoundsPadding = new Size(-1, 4); + this.CornerRounding = 8.0f; + this.FillBrush = new SolidBrush(Color.FromArgb(72, Color.Black)); + } + + /// + /// Draw a tint over everything in the ObjectListView except the + /// row under the mouse. + /// + /// + /// + /// + public override void Draw(ObjectListView olv, Graphics g, Rectangle r) { + if (!r.Contains(olv.PointToClient(Cursor.Position))) + return; + + Rectangle bounds = this.RowBounds; + if (bounds.IsEmpty) { + if (olv.View == View.Tile) + g.FillRectangle(this.FillBrush, r); + return; + } + + using (Region newClip = new Region(r)) { + bounds.Inflate(this.BoundsPadding); + newClip.Exclude(this.GetRoundedRect(bounds, this.CornerRounding)); + Region originalClip = g.Clip; + g.Clip = newClip; + g.FillRectangle(this.FillBrush, r); + g.Clip = originalClip; + } + } + } + + /// + /// Instances of this class put an Image over the row/cell that it is decorating + /// + public class ImageDecoration : ImageAdornment, IDecoration + { + #region Constructors + + /// + /// Create an image decoration + /// + public ImageDecoration() { + this.Alignment = ContentAlignment.MiddleRight; + } + + /// + /// Create an image decoration + /// + /// + public ImageDecoration(Image image) + : this() { + this.Image = image; + } + + /// + /// Create an image decoration + /// + /// + /// + public ImageDecoration(Image image, int transparency) + : this() { + this.Image = image; + this.Transparency = transparency; + } + + /// + /// Create an image decoration + /// + /// + /// + public ImageDecoration(Image image, ContentAlignment alignment) + : this() { + this.Image = image; + this.Alignment = alignment; + } + + /// + /// Create an image decoration + /// + /// + /// + /// + public ImageDecoration(Image image, int transparency, ContentAlignment alignment) + : this() { + this.Image = image; + this.Transparency = transparency; + this.Alignment = alignment; + } + + #endregion + + #region IDecoration Members + + /// + /// Gets or sets the item being decorated + /// + public OLVListItem ListItem { + get { return listItem; } + set { listItem = value; } + } + private OLVListItem listItem; + + /// + /// Gets or sets the sub item being decorated + /// + public OLVListSubItem SubItem { + get { return subItem; } + set { subItem = value; } + } + private OLVListSubItem subItem; + + #endregion + + #region Commands + + /// + /// Draw this decoration + /// + /// The ObjectListView being decorated + /// The Graphics used for drawing + /// The bounds of the rendering + public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) { + this.DrawImage(g, this.CalculateItemBounds(this.ListItem, this.SubItem)); + } + + #endregion + } + + /// + /// Instances of this class draw some text over the row/cell that they are decorating + /// + public class TextDecoration : TextAdornment, IDecoration + { + #region Constructors + + /// + /// Create a TextDecoration + /// + public TextDecoration() { + this.Alignment = ContentAlignment.MiddleRight; + } + + /// + /// Create a TextDecoration + /// + /// + public TextDecoration(string text) + : this() { + this.Text = text; + } + + /// + /// Create a TextDecoration + /// + /// + /// + public TextDecoration(string text, int transparency) + : this() { + this.Text = text; + this.Transparency = transparency; + } + + /// + /// Create a TextDecoration + /// + /// + /// + public TextDecoration(string text, ContentAlignment alignment) + : this() { + this.Text = text; + this.Alignment = alignment; + } + + /// + /// Create a TextDecoration + /// + /// + /// + /// + public TextDecoration(string text, int transparency, ContentAlignment alignment) + : this() { + this.Text = text; + this.Transparency = transparency; + this.Alignment = alignment; + } + + #endregion + + #region IDecoration Members + + /// + /// Gets or sets the item being decorated + /// + public OLVListItem ListItem { + get { return listItem; } + set { listItem = value; } + } + private OLVListItem listItem; + + /// + /// Gets or sets the sub item being decorated + /// + public OLVListSubItem SubItem { + get { return subItem; } + set { subItem = value; } + } + private OLVListSubItem subItem; + + + #endregion + + #region Commands + + /// + /// Draw this decoration + /// + /// The ObjectListView being decorated + /// The Graphics used for drawing + /// The bounds of the rendering + public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) { + this.DrawText(g, this.CalculateItemBounds(this.ListItem, this.SubItem)); + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Rendering/Overlays.cs b/VG Music Studio - WinForms/ObjectListView/Rendering/Overlays.cs new file mode 100644 index 0000000..898b301 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Rendering/Overlays.cs @@ -0,0 +1,302 @@ +/* + * Overlays - Images, text or other things that can be rendered over the top of a ListView + * + * Author: Phillip Piper + * Date: 14/04/2009 4:36 PM + * + * Change log: + * v2.3 + * 2009-08-17 JPP - Overlays now use Adornments + * - Added ITransparentOverlay interface. Overlays can now have separate transparency levels + * 2009-08-10 JPP - Moved decoration related code to new file + * v2.2.1 + * 200-07-24 JPP - TintedColumnDecoration now works when last item is a member of a collapsed + * group (well, it no longer crashes). + * v2.2 + * 2009-06-01 JPP - Make sure that TintedColumnDecoration reaches to the last item in group view + * 2009-05-05 JPP - Unified BillboardOverlay text rendering with that of TextOverlay + * 2009-04-30 JPP - Added TintedColumnDecoration + * 2009-04-14 JPP - Initial version + * + * To do: + * + * Copyright (C) 2009-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// The interface for an object which can draw itself over the top of + /// an ObjectListView. + /// + public interface IOverlay + { + /// + /// Draw this overlay + /// + /// The ObjectListView that is being overlaid + /// The Graphics onto the given OLV + /// The content area of the OLV + void Draw(ObjectListView olv, Graphics g, Rectangle r); + } + + /// + /// An interface for an overlay that supports variable levels of transparency + /// + public interface ITransparentOverlay : IOverlay + { + /// + /// Gets or sets the transparency of the overlay. + /// 0 is completely transparent, 255 is completely opaque. + /// + int Transparency { get; set; } + } + + /// + /// A null implementation of the IOverlay interface + /// + public class AbstractOverlay : ITransparentOverlay + { + #region IOverlay Members + + /// + /// Draw this overlay + /// + /// The ObjectListView that is being overlaid + /// The Graphics onto the given OLV + /// The content area of the OLV + public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) { + } + + #endregion + + #region ITransparentOverlay Members + + /// + /// How transparent should this overlay be? + /// + [Category("ObjectListView"), + Description("How transparent should this overlay be"), + DefaultValue(128), + NotifyParentProperty(true)] + public int Transparency { + get { return this.transparency; } + set { this.transparency = Math.Min(255, Math.Max(0, value)); } + } + private int transparency = 128; + + #endregion + } + + /// + /// An overlay that will draw an image over the top of the ObjectListView + /// + [TypeConverter("Kermalis.VGMusicStudio.WinForms.ObjectListView.Design.OverlayConverter")] + public class ImageOverlay : ImageAdornment, ITransparentOverlay + { + /// + /// Create an ImageOverlay + /// + public ImageOverlay() { + this.Alignment = System.Drawing.ContentAlignment.BottomRight; + } + + #region Public properties + + /// + /// Gets or sets the horizontal inset by which the position of the overlay will be adjusted + /// + [Category("ObjectListView"), + Description("The horizontal inset by which the position of the overlay will be adjusted"), + DefaultValue(20), + NotifyParentProperty(true)] + public int InsetX { + get { return this.insetX; } + set { this.insetX = Math.Max(0, value); } + } + private int insetX = 20; + + /// + /// Gets or sets the vertical inset by which the position of the overlay will be adjusted + /// + [Category("ObjectListView"), + Description("Gets or sets the vertical inset by which the position of the overlay will be adjusted"), + DefaultValue(20), + NotifyParentProperty(true)] + public int InsetY { + get { return this.insetY; } + set { this.insetY = Math.Max(0, value); } + } + private int insetY = 20; + + #endregion + + #region Commands + + /// + /// Draw this overlay + /// + /// The ObjectListView being decorated + /// The Graphics used for drawing + /// The bounds of the rendering + public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) { + Rectangle insetRect = r; + insetRect.Inflate(-this.InsetX, -this.InsetY); + + // We hard code a transparency of 255 here since transparency is handled by the glass panel + this.DrawImage(g, insetRect, this.Image, 255); + } + + #endregion + } + + /// + /// An overlay that will draw text over the top of the ObjectListView + /// + [TypeConverter("Kermalis.VGMusicStudio.WinForms.ObjectListView.Design.OverlayConverter")] + public class TextOverlay : TextAdornment, ITransparentOverlay + { + /// + /// Create a TextOverlay + /// + public TextOverlay() { + this.Alignment = System.Drawing.ContentAlignment.BottomRight; + } + + #region Public properties + + /// + /// Gets or sets the horizontal inset by which the position of the overlay will be adjusted + /// + [Category("ObjectListView"), + Description("The horizontal inset by which the position of the overlay will be adjusted"), + DefaultValue(20), + NotifyParentProperty(true)] + public int InsetX { + get { return this.insetX; } + set { this.insetX = Math.Max(0, value); } + } + private int insetX = 20; + + /// + /// Gets or sets the vertical inset by which the position of the overlay will be adjusted + /// + [Category("ObjectListView"), + Description("Gets or sets the vertical inset by which the position of the overlay will be adjusted"), + DefaultValue(20), + NotifyParentProperty(true)] + public int InsetY { + get { return this.insetY; } + set { this.insetY = Math.Max(0, value); } + } + private int insetY = 20; + + /// + /// Gets or sets whether the border will be drawn with rounded corners + /// + [Browsable(false), + Obsolete("Use CornerRounding instead", false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool RoundCorneredBorder { + get { return this.CornerRounding > 0; } + set { + if (value) + this.CornerRounding = 16.0f; + else + this.CornerRounding = 0.0f; + } + } + + #endregion + + #region Commands + + /// + /// Draw this overlay + /// + /// The ObjectListView being decorated + /// The Graphics used for drawing + /// The bounds of the rendering + public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) { + if (String.IsNullOrEmpty(this.Text)) + return; + + Rectangle insetRect = r; + insetRect.Inflate(-this.InsetX, -this.InsetY); + // We hard code a transparency of 255 here since transparency is handled by the glass panel + this.DrawText(g, insetRect, this.Text, 255); + } + + #endregion + } + + /// + /// A Billboard overlay is a TextOverlay positioned at an absolute point + /// + public class BillboardOverlay : TextOverlay + { + /// + /// Create a BillboardOverlay + /// + public BillboardOverlay() { + this.Transparency = 255; + this.BackColor = Color.PeachPuff; + this.TextColor = Color.Black; + this.BorderColor = Color.Empty; + this.Font = new Font("Tahoma", 10); + } + + /// + /// Gets or sets where should the top left of the billboard be placed + /// + public Point Location { + get { return this.location; } + set { this.location = value; } + } + private Point location; + + /// + /// Draw this overlay + /// + /// The ObjectListView being decorated + /// The Graphics used for drawing + /// The bounds of the rendering + public override void Draw(ObjectListView olv, Graphics g, Rectangle r) { + if (String.IsNullOrEmpty(this.Text)) + return; + + // Calculate the bounds of the text, and then move it to where it should be + Rectangle textRect = this.CalculateTextBounds(g, r, this.Text); + textRect.Location = this.Location; + + // Make sure the billboard is within the bounds of the List, as far as is possible + if (textRect.Right > r.Width) + textRect.X = Math.Max(r.Left, r.Width - textRect.Width); + if (textRect.Bottom > r.Height) + textRect.Y = Math.Max(r.Top, r.Height - textRect.Height); + + this.DrawBorderedText(g, textRect, this.Text, 255); + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Rendering/Renderers.cs b/VG Music Studio - WinForms/ObjectListView/Rendering/Renderers.cs new file mode 100644 index 0000000..e232547 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Rendering/Renderers.cs @@ -0,0 +1,3887 @@ +/* + * Renderers - A collection of useful renderers that are used to owner draw a cell in an ObjectListView + * + * Author: Phillip Piper + * Date: 27/09/2008 9:15 AM + * + * Change log: + * 2018-10-06 JPP - Fix rendering so that OLVColumn.WordWrap works when using customised Renderers + * 2018-05-01 JPP - Use ITextMatchFilter interface rather than TextMatchFilter concrete class. + * v2.9.2 + * 2016-06-02 JPP - CalculateImageWidth() no longer adds 2 to the image width + * 2016-05-29 JPP - Fix calculation of cell edit boundaries on TreeListView controls + * v2.9 + * 2015-08-22 JPP - Allow selected row back/fore colours to be specified for each row + * 2015-06-23 JPP - Added ColumnButtonRenderer plus general support for Buttons + * 2015-06-22 JPP - Added BaseRenderer.ConfigureItem() and ConfigureSubItem() to easily allow + * other renderers to be chained for use within a primary renderer. + * - Lots of tightening of hit tests and edit rectangles + * 2015-05-15 JPP - Handle rendering an Image when that Image is returned as an aspect. + * v2.8 + * 2014-09-26 JPP - Dispose of animation timer in a more robust fashion. + * 2014-05-20 JPP - Handle rendering disabled rows + * v2.7 + * 2013-04-29 JPP - Fixed bug where Images were not vertically aligned + * v2.6 + * 2012-10-26 JPP - Hit detection will no longer report check box hits on columns without checkboxes. + * 2012-07-13 JPP - [Breaking change] Added preferedSize parameter to IRenderer.GetEditRectangle(). + * v2.5.1 + * 2012-07-14 JPP - Added CellPadding to various places. Replaced DescribedTaskRenderer.CellPadding. + * 2012-07-11 JPP - Added CellVerticalAlignment to various places allow cell contents to be vertically + * aligned (rather than always being centered). + * v2.5 + * 2010-08-24 JPP - CheckBoxRenderer handles hot boxes and correctly vertically centers the box. + * 2010-06-23 JPP - Major rework of HighlightTextRenderer. Now uses TextMatchFilter directly. + * Draw highlighting underneath text to improve legibility. Works with new + * TextMatchFilter capabilities. + * v2.4 + * 2009-10-30 JPP - Plugged possible resource leak by using using() with CreateGraphics() + * v2.3 + * 2009-09-28 JPP - Added DescribedTaskRenderer + * 2009-09-01 JPP - Correctly handle an ImageRenderer's handling of an aspect that holds + * the image to be displayed at Byte[]. + * 2009-08-29 JPP - Fixed bug where some of a cell's background was not erased. + * 2009-08-15 JPP - Correctly MeasureText() using the appropriate graphic context + * - Handle translucent selection setting + * v2.2.1 + * 2009-07-24 JPP - Try to honour CanWrap setting when GDI rendering text. + * 2009-07-11 JPP - Correctly calculate edit rectangle for subitems of a tree view + * (previously subitems were indented in the same way as the primary column) + * v2.2 + * 2009-06-06 JPP - Tweaked text rendering so that column 0 isn't ellipsed unnecessarily. + * 2009-05-05 JPP - Added Unfocused foreground and background colors + * (thanks to Christophe Hosten) + * 2009-04-21 JPP - Fixed off-by-1 error when calculating text widths. This caused + * middle and right aligned columns to always wrap one character + * when printed using ListViewPrinter (SF#2776634). + * 2009-04-11 JPP - Correctly renderer checkboxes when RowHeight is non-standard + * 2009-04-06 JPP - Allow for item indent when calculating edit rectangle + * v2.1 + * 2009-02-24 JPP - Work properly with ListViewPrinter again + * 2009-01-26 JPP - AUSTRALIA DAY (why aren't I on holidays!) + * - Major overhaul of renderers. Now uses IRenderer interface. + * - ImagesRenderer and FlagsRenderer are now defunct. + * The names are retained for backward compatibility. + * 2009-01-23 JPP - Align bitmap AND text according to column alignment (previously + * only text was aligned and bitmap was always to the left). + * 2009-01-21 JPP - Changed to use TextRenderer rather than native GDI routines. + * 2009-01-20 JPP - Draw images directly from image list if possible. 30% faster! + * - Tweaked some spacings to look more like native ListView + * - Text highlight for non FullRowSelect is now the right color + * when the control doesn't have focus. + * - Commented out experimental animations. Still needs work. + * 2009-01-19 JPP - Changed to draw text using GDI routines. Looks more like + * native control this way. Set UseGdiTextRendering to false to + * revert to previous behavior. + * 2009-01-15 JPP - Draw background correctly when control is disabled + * - Render checkboxes using CheckBoxRenderer + * v2.0.1 + * 2008-12-29 JPP - Render text correctly when HideSelection is true. + * 2008-12-26 JPP - BaseRenderer now works correctly in all Views + * 2008-12-23 JPP - Fixed two small bugs in BarRenderer + * v2.0 + * 2008-10-26 JPP - Don't owner draw when in Design mode + * 2008-09-27 JPP - Separated from ObjectListView.cs + * + * Copyright (C) 2006-2018 Phillip Piper + * + * TO DO: + * - Hit detection on renderers doesn't change the controls standard selection behavior + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; +using Timer = System.Threading.Timer; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + /// + /// Renderers are the mechanism used for owner drawing cells. As such, they can also handle + /// hit detection and positioning of cell editing rectangles. + /// + public interface IRenderer { + /// + /// Render the whole item within an ObjectListView. This is only used in non-Details views. + /// + /// The event + /// A Graphics for rendering + /// The bounds of the item + /// The model object to be drawn + /// Return true to indicate that the event was handled and no further processing is needed. + bool RenderItem(DrawListViewItemEventArgs e, Graphics g, Rectangle itemBounds, Object rowObject); + + /// + /// Render one cell within an ObjectListView when it is in Details mode. + /// + /// The event + /// A Graphics for rendering + /// The bounds of the cell + /// The model object to be drawn + /// Return true to indicate that the event was handled and no further processing is needed. + bool RenderSubItem(DrawListViewSubItemEventArgs e, Graphics g, Rectangle cellBounds, Object rowObject); + + /// + /// What is under the given point? + /// + /// + /// x co-ordinate + /// y co-ordinate + /// This method should only alter HitTestLocation and/or UserData. + void HitTest(OlvListViewHitTestInfo hti, int x, int y); + + /// + /// When the value in the given cell is to be edited, where should the edit rectangle be placed? + /// + /// + /// + /// + /// + /// + /// + Rectangle GetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize); + } + + /// + /// Renderers that implement this interface will have the filter property updated, + /// each time the filter on the ObjectListView is updated. + /// + public interface IFilterAwareRenderer + { + /// + /// Gets or sets the filter that is currently active + /// + IModelFilter Filter { get; set; } + } + + /// + /// An AbstractRenderer is a do-nothing implementation of the IRenderer interface. + /// + [Browsable(true), + ToolboxItem(false)] + public class AbstractRenderer : Component, IRenderer { + #region IRenderer Members + + /// + /// Render the whole item within an ObjectListView. This is only used in non-Details views. + /// + /// The event + /// A Graphics for rendering + /// The bounds of the item + /// The model object to be drawn + /// Return true to indicate that the event was handled and no further processing is needed. + public virtual bool RenderItem(DrawListViewItemEventArgs e, Graphics g, Rectangle itemBounds, object rowObject) { + return true; + } + + /// + /// Render one cell within an ObjectListView when it is in Details mode. + /// + /// The event + /// A Graphics for rendering + /// The bounds of the cell + /// The model object to be drawn + /// Return true to indicate that the event was handled and no further processing is needed. + public virtual bool RenderSubItem(DrawListViewSubItemEventArgs e, Graphics g, Rectangle cellBounds, object rowObject) { + return false; + } + + /// + /// What is under the given point? + /// + /// + /// x co-ordinate + /// y co-ordinate + /// This method should only alter HitTestLocation and/or UserData. + public virtual void HitTest(OlvListViewHitTestInfo hti, int x, int y) {} + + /// + /// When the value in the given cell is to be edited, where should the edit rectangle be placed? + /// + /// + /// + /// + /// + /// + /// + public virtual Rectangle GetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) { + return cellBounds; + } + + #endregion + } + + /// + /// This class provides compatibility for v1 RendererDelegates + /// + [ToolboxItem(false)] + internal class Version1Renderer : AbstractRenderer { + public Version1Renderer(RenderDelegate renderDelegate) { + this.RenderDelegate = renderDelegate; + } + + /// + /// The renderer delegate that this renderer wraps + /// + public RenderDelegate RenderDelegate; + + #region IRenderer Members + + public override bool RenderSubItem(DrawListViewSubItemEventArgs e, Graphics g, Rectangle cellBounds, object rowObject) { + if (this.RenderDelegate == null) + return base.RenderSubItem(e, g, cellBounds, rowObject); + else + return this.RenderDelegate(e, g, cellBounds, rowObject); + } + + #endregion + } + + /// + /// A BaseRenderer provides useful base level functionality for any custom renderer. + /// + /// + /// Subclasses will normally override the Render or OptionalRender method, and use the other + /// methods as helper functions. + /// + [Browsable(true), + ToolboxItem(true)] + public class BaseRenderer : AbstractRenderer { + internal const TextFormatFlags NormalTextFormatFlags = TextFormatFlags.NoPrefix | + TextFormatFlags.EndEllipsis | + TextFormatFlags.PreserveGraphicsTranslateTransform; + + #region Configuration Properties + + /// + /// Can the renderer wrap lines that do not fit completely within the cell? + /// + /// + /// If this is not set specifically, the value will be taken from Column.WordWrap + /// + /// Wrapping text doesn't work with the GDI renderer, so if this set to true, GDI+ rendering will used. + /// The difference between GDI and GDI+ rendering can give word wrapped columns a slight different appearance. + /// + /// + [Category("Appearance"), + Description("Can the renderer wrap text that does not fit completely within the cell"), + DefaultValue(null)] + public bool? CanWrap { + get { return canWrap; } + set { canWrap = value; } + } + + private bool? canWrap; + + /// + /// Get the actual value that should be used right now for CanWrap + /// + [Browsable(false)] + protected bool CanWrapOrDefault { + get { + return this.CanWrap ?? this.Column != null && this.Column.WordWrap; + } + } + /// + /// Gets or sets how many pixels will be left blank around this cell + /// + /// + /// + /// This setting only takes effect when the control is owner drawn. + /// + /// for more details. + /// + [Category("ObjectListView"), + Description("The number of pixels that renderer will leave empty around the edge of the cell"), + DefaultValue(null)] + public Rectangle? CellPadding { + get { return this.cellPadding; } + set { this.cellPadding = value; } + } + private Rectangle? cellPadding; + + /// + /// Gets the horizontal alignment of the column + /// + [Browsable(false)] + public HorizontalAlignment CellHorizontalAlignment + { + get { return this.Column == null ? HorizontalAlignment.Left : this.Column.TextAlign; } + } + + /// + /// Gets or sets how cells drawn by this renderer will be vertically aligned. + /// + /// + /// + /// If this is not set, the value from the column or control itself will be used. + /// + /// + [Category("ObjectListView"), + Description("How will cell values be vertically aligned?"), + DefaultValue(null)] + public virtual StringAlignment? CellVerticalAlignment { + get { return this.cellVerticalAlignment; } + set { this.cellVerticalAlignment = value; } + } + private StringAlignment? cellVerticalAlignment; + + /// + /// Gets the optional padding that this renderer should apply before drawing. + /// This property considers all possible sources of padding + /// + [Browsable(false)] + protected virtual Rectangle? EffectiveCellPadding { + get { + if (this.cellPadding.HasValue) + return this.cellPadding.Value; + + if (this.OLVSubItem != null && this.OLVSubItem.CellPadding.HasValue) + return this.OLVSubItem.CellPadding.Value; + + if (this.ListItem != null && this.ListItem.CellPadding.HasValue) + return this.ListItem.CellPadding.Value; + + if (this.Column != null && this.Column.CellPadding.HasValue) + return this.Column.CellPadding.Value; + + if (this.ListView != null && this.ListView.CellPadding.HasValue) + return this.ListView.CellPadding.Value; + + return null; + } + } + + /// + /// Gets the vertical cell alignment that should govern the rendering. + /// This property considers all possible sources. + /// + [Browsable(false)] + protected virtual StringAlignment EffectiveCellVerticalAlignment { + get { + if (this.cellVerticalAlignment.HasValue) + return this.cellVerticalAlignment.Value; + + if (this.OLVSubItem != null && this.OLVSubItem.CellVerticalAlignment.HasValue) + return this.OLVSubItem.CellVerticalAlignment.Value; + + if (this.ListItem != null && this.ListItem.CellVerticalAlignment.HasValue) + return this.ListItem.CellVerticalAlignment.Value; + + if (this.Column != null && this.Column.CellVerticalAlignment.HasValue) + return this.Column.CellVerticalAlignment.Value; + + if (this.ListView != null) + return this.ListView.CellVerticalAlignment; + + return StringAlignment.Center; + } + } + + /// + /// Gets or sets the image list from which keyed images will be fetched + /// + [Category("Appearance"), + Description("The image list from which keyed images will be fetched for drawing. If this is not given, the small ImageList from the ObjectListView will be used"), + DefaultValue(null)] + public ImageList ImageList { + get { return imageList; } + set { imageList = value; } + } + + private ImageList imageList; + + /// + /// When rendering multiple images, how many pixels should be between each image? + /// + [Category("Appearance"), + Description("When rendering multiple images, how many pixels should be between each image?"), + DefaultValue(1)] + public int Spacing { + get { return spacing; } + set { spacing = value; } + } + + private int spacing = 1; + + /// + /// Should text be rendered using GDI routines? This makes the text look more + /// like a native List view control. + /// + /// Even if this is set to true, it will return false if the renderer + /// is set to word wrap, since GDI doesn't handle wrapping. + [Category("Appearance"), + Description("Should text be rendered using GDI routines?"), + DefaultValue(true)] + public virtual bool UseGdiTextRendering { + get { + // Can't use GDI routines on a GDI+ printer context or when word wrapping is required + return !this.IsPrinting && !this.CanWrapOrDefault && useGdiTextRendering; + } + set { useGdiTextRendering = value; } + } + private bool useGdiTextRendering = true; + + #endregion + + #region State Properties + + /// + /// Get or set the aspect of the model object that this renderer should draw + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Object Aspect { + get { + if (aspect == null) + aspect = column.GetValue(this.rowObject); + return aspect; + } + set { aspect = value; } + } + + private Object aspect; + + /// + /// What are the bounds of the cell that is being drawn? + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Rectangle Bounds { + get { return bounds; } + set { bounds = value; } + } + + private Rectangle bounds; + + /// + /// Get or set the OLVColumn that this renderer will draw + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public OLVColumn Column { + get { return column; } + set { column = value; } + } + + private OLVColumn column; + + /// + /// Get/set the event that caused this renderer to be called + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public DrawListViewItemEventArgs DrawItemEvent { + get { return drawItemEventArgs; } + set { drawItemEventArgs = value; } + } + + private DrawListViewItemEventArgs drawItemEventArgs; + + /// + /// Get/set the event that caused this renderer to be called + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public DrawListViewSubItemEventArgs Event { + get { return eventArgs; } + set { eventArgs = value; } + } + + private DrawListViewSubItemEventArgs eventArgs; + + /// + /// Gets or sets the font to be used for text in this cell + /// + /// + /// + /// In general, this property should be treated as internal. + /// If you do set this, the given font will be used without any other consideration. + /// All other factors -- selection state, hot item, hyperlinks -- will be ignored. + /// + /// + /// A better way to set the font is to change either ListItem.Font or SubItem.Font + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Font Font { + get { + if (this.font != null || this.ListItem == null) + return this.font; + + if (this.SubItem == null || this.ListItem.UseItemStyleForSubItems) + return this.ListItem.Font; + + return this.SubItem.Font; + } + set { this.font = value; } + } + + private Font font; + + /// + /// Gets the image list from which keyed images will be fetched + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ImageList ImageListOrDefault { + get { return this.ImageList ?? this.ListView.SmallImageList; } + } + + /// + /// Should this renderer fill in the background before drawing? + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool IsDrawBackground { + get { return !this.IsPrinting; } + } + + /// + /// Cache whether or not our item is selected + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool IsItemSelected { + get { return isItemSelected; } + set { isItemSelected = value; } + } + + private bool isItemSelected; + + /// + /// Is this renderer being used on a printer context? + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool IsPrinting { + get { return isPrinting; } + set { isPrinting = value; } + } + + private bool isPrinting; + + /// + /// Get or set the listitem that this renderer will be drawing + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public OLVListItem ListItem { + get { return listItem; } + set { listItem = value; } + } + + private OLVListItem listItem; + + /// + /// Get/set the listview for which the drawing is to be done + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ObjectListView ListView { + get { return objectListView; } + set { objectListView = value; } + } + + private ObjectListView objectListView; + + /// + /// Get the specialized OLVSubItem that this renderer is drawing + /// + /// This returns null for column 0. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public OLVListSubItem OLVSubItem { + get { return listSubItem as OLVListSubItem; } + } + + /// + /// Get or set the model object that this renderer should draw + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Object RowObject { + get { return rowObject; } + set { rowObject = value; } + } + + private Object rowObject; + + /// + /// Get or set the list subitem that this renderer will be drawing + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public OLVListSubItem SubItem { + get { return listSubItem; } + set { listSubItem = value; } + } + + private OLVListSubItem listSubItem; + + /// + /// The brush that will be used to paint the text + /// + /// + /// + /// In general, this property should be treated as internal. It will be reset after each render. + /// + /// + /// + /// In particular, don't set it to configure the color of the text on the control. That should be done via SubItem.ForeColor + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Brush TextBrush { + get { + if (textBrush == null) + return new SolidBrush(this.GetForegroundColor()); + else + return this.textBrush; + } + set { textBrush = value; } + } + + private Brush textBrush; + + /// + /// Will this renderer use the custom images from the parent ObjectListView + /// to draw the checkbox images. + /// + /// + /// + /// If this is true, the renderer will use the images from the + /// StateImageList to represent checkboxes. 0 - unchecked, 1 - checked, 2 - indeterminate. + /// + /// If this is false (the default), then the renderer will use .NET's standard + /// CheckBoxRenderer. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool UseCustomCheckboxImages { + get { return useCustomCheckboxImages; } + set { useCustomCheckboxImages = value; } + } + + private bool useCustomCheckboxImages; + + private void ClearState() { + this.Event = null; + this.DrawItemEvent = null; + this.Aspect = null; + this.TextBrush = null; + } + + #endregion + + #region Utilities + + /// + /// Align the second rectangle with the first rectangle, + /// according to the alignment of the column + /// + /// The cell's bounds + /// The rectangle to be aligned within the bounds + /// An aligned rectangle + protected virtual Rectangle AlignRectangle(Rectangle outer, Rectangle inner) { + Rectangle r = new Rectangle(outer.Location, inner.Size); + + // Align horizontally depending on the column alignment + if (inner.Width < outer.Width) { + r.X = AlignHorizontally(outer, inner); + } + + // Align vertically too + if (inner.Height < outer.Height) { + r.Y = AlignVertically(outer, inner); + } + + return r; + } + + /// + /// Calculate the left edge of the rectangle that aligns the outer rectangle with the inner one + /// according to this renderer's horizontal alignment + /// + /// + /// + /// + protected int AlignHorizontally(Rectangle outer, Rectangle inner) { + HorizontalAlignment alignment = this.CellHorizontalAlignment; + switch (alignment) { + case HorizontalAlignment.Left: + return outer.Left + 1; + case HorizontalAlignment.Center: + return outer.Left + ((outer.Width - inner.Width) / 2); + case HorizontalAlignment.Right: + return outer.Right - inner.Width - 1; + default: + throw new ArgumentOutOfRangeException(); + } + } + + + /// + /// Calculate the top of the rectangle that aligns the outer rectangle with the inner rectangle + /// according to this renders vertical alignment + /// + /// + /// + /// + protected int AlignVertically(Rectangle outer, Rectangle inner) { + return AlignVertically(outer, inner.Height); + } + + /// + /// Calculate the top of the rectangle that aligns the outer rectangle with a rectangle of the given height + /// according to this renderer's vertical alignment + /// + /// + /// + /// + protected int AlignVertically(Rectangle outer, int innerHeight) { + switch (this.EffectiveCellVerticalAlignment) { + case StringAlignment.Near: + return outer.Top + 1; + case StringAlignment.Center: + return outer.Top + ((outer.Height - innerHeight) / 2); + case StringAlignment.Far: + return outer.Bottom - innerHeight - 1; + default: + throw new ArgumentOutOfRangeException(); + } + } + + /// + /// Calculate the space that our rendering will occupy and then align that space + /// with the given rectangle, according to the Column alignment + /// + /// + /// Pre-padded bounds of the cell + /// + protected virtual Rectangle CalculateAlignedRectangle(Graphics g, Rectangle r) { + if (this.Column == null) + return r; + + Rectangle contentRectangle = new Rectangle(Point.Empty, this.CalculateContentSize(g, r)); + return this.AlignRectangle(r, contentRectangle); + } + + /// + /// Calculate the size of the content of this cell. + /// + /// + /// Pre-padded bounds of the cell + /// The width and height of the content + protected virtual Size CalculateContentSize(Graphics g, Rectangle r) + { + Size checkBoxSize = this.CalculatePrimaryCheckBoxSize(g); + Size imageSize = this.CalculateImageSize(g, this.GetImageSelector()); + Size textSize = this.CalculateTextSize(g, this.GetText(), r.Width - (checkBoxSize.Width + imageSize.Width)); + + // If the combined width is greater than the whole cell, we just use the cell itself + + int width = Math.Min(r.Width, checkBoxSize.Width + imageSize.Width + textSize.Width); + int componentMaxHeight = Math.Max(checkBoxSize.Height, Math.Max(imageSize.Height, textSize.Height)); + int height = Math.Min(r.Height, componentMaxHeight); + + return new Size(width, height); + } + + /// + /// Calculate the bounds of a checkbox given the (pre-padded) cell bounds + /// + /// + /// Pre-padded cell bounds + /// + protected Rectangle CalculateCheckBoxBounds(Graphics g, Rectangle cellBounds) { + Size checkBoxSize = this.CalculateCheckBoxSize(g); + return this.AlignRectangle(cellBounds, new Rectangle(0, 0, checkBoxSize.Width, checkBoxSize.Height)); + } + + + /// + /// How much space will the check box for this cell occupy? + /// + /// Only column 0 can have check boxes. Sub item checkboxes are + /// treated as images + /// + /// + protected virtual Size CalculateCheckBoxSize(Graphics g) + { + if (UseCustomCheckboxImages && this.ListView.StateImageList != null) + return this.ListView.StateImageList.ImageSize; + + return CheckBoxRenderer.GetGlyphSize(g, CheckBoxState.UncheckedNormal); + } + + /// + /// How much space will the check box for this row occupy? + /// If the list doesn't have checkboxes, or this isn't the primary column, + /// this returns an empty size. + /// + /// + /// + protected virtual Size CalculatePrimaryCheckBoxSize(Graphics g) { + if (!this.ListView.CheckBoxes || !this.ColumnIsPrimary) + return Size.Empty; + + Size size = this.CalculateCheckBoxSize(g); + size.Width += 6; + return size; + } + + /// + /// How much horizontal space will the image of this cell occupy? + /// + /// + /// + /// + protected virtual int CalculateImageWidth(Graphics g, object imageSelector) + { + return this.CalculateImageSize(g, imageSelector).Width; + } + + /// + /// How much vertical space will the image of this cell occupy? + /// + /// + /// + /// + protected virtual int CalculateImageHeight(Graphics g, object imageSelector) + { + return this.CalculateImageSize(g, imageSelector).Height; + } + + /// + /// How much space will the image of this cell occupy? + /// + /// + /// + /// + protected virtual Size CalculateImageSize(Graphics g, object imageSelector) + { + if (imageSelector == null || imageSelector == DBNull.Value) + return Size.Empty; + + // Check for the image in the image list (most common case) + ImageList il = this.ImageListOrDefault; + if (il != null) + { + int selectorAsInt = -1; + + if (imageSelector is Int32) + selectorAsInt = (Int32)imageSelector; + else + { + String selectorAsString = imageSelector as String; + if (selectorAsString != null) + selectorAsInt = il.Images.IndexOfKey(selectorAsString); + } + if (selectorAsInt >= 0) + return il.ImageSize; + } + + // Is the selector actually an image? + Image image = imageSelector as Image; + if (image != null) + return image.Size; + + return Size.Empty; + } + + /// + /// How much horizontal space will the text of this cell occupy? + /// + /// + /// + /// + /// + protected virtual int CalculateTextWidth(Graphics g, string txt, int width) + { + if (String.IsNullOrEmpty(txt)) + return 0; + + return CalculateTextSize(g, txt, width).Width; + } + + /// + /// How much space will the text of this cell occupy? + /// + /// + /// + /// + /// + protected virtual Size CalculateTextSize(Graphics g, string txt, int width) + { + if (String.IsNullOrEmpty(txt)) + return Size.Empty; + + if (this.UseGdiTextRendering) + { + Size proposedSize = new Size(width, Int32.MaxValue); + return TextRenderer.MeasureText(g, txt, this.Font, proposedSize, NormalTextFormatFlags); + } + + // Using GDI+ rendering + using (StringFormat fmt = new StringFormat()) { + fmt.Trimming = StringTrimming.EllipsisCharacter; + SizeF sizeF = g.MeasureString(txt, this.Font, width, fmt); + return new Size(1 + (int)sizeF.Width, 1 + (int)sizeF.Height); + } + } + + /// + /// Return the Color that is the background color for this item's cell + /// + /// The background color of the subitem + public virtual Color GetBackgroundColor() { + if (!this.ListView.Enabled) + return SystemColors.Control; + + if (this.IsItemSelected && !this.ListView.UseTranslucentSelection && this.ListView.FullRowSelect) + return this.GetSelectedBackgroundColor(); + + if (this.SubItem == null || this.ListItem.UseItemStyleForSubItems) + return this.ListItem.BackColor; + + return this.SubItem.BackColor; + } + + /// + /// Return the color of the background color when the item is selected + /// + /// The background color of the subitem + public virtual Color GetSelectedBackgroundColor() { + if (this.ListView.Focused) + return this.ListItem.SelectedBackColor ?? this.ListView.SelectedBackColorOrDefault; + + if (!this.ListView.HideSelection) + return this.ListView.UnfocusedSelectedBackColorOrDefault; + + return this.ListItem.BackColor; + } + + /// + /// Return the color to be used for text in this cell + /// + /// The text color of the subitem + public virtual Color GetForegroundColor() { + if (this.IsItemSelected && + !this.ListView.UseTranslucentSelection && + (this.ColumnIsPrimary || this.ListView.FullRowSelect)) + return this.GetSelectedForegroundColor(); + + return this.SubItem == null || this.ListItem.UseItemStyleForSubItems ? this.ListItem.ForeColor : this.SubItem.ForeColor; + } + + /// + /// Return the color of the foreground color when the item is selected + /// + /// The foreground color of the subitem + public virtual Color GetSelectedForegroundColor() + { + if (this.ListView.Focused) + return this.ListItem.SelectedForeColor ?? this.ListView.SelectedForeColorOrDefault; + + if (!this.ListView.HideSelection) + return this.ListView.UnfocusedSelectedForeColorOrDefault; + + return this.SubItem == null || this.ListItem.UseItemStyleForSubItems ? this.ListItem.ForeColor : this.SubItem.ForeColor; + } + + /// + /// Return the image that should be drawn against this subitem + /// + /// An Image or null if no image should be drawn. + protected virtual Image GetImage() { + return this.GetImage(this.GetImageSelector()); + } + + /// + /// Return the actual image that should be drawn when keyed by the given image selector. + /// An image selector can be: + /// an int, giving the index into the image list + /// a string, giving the image key into the image list + /// an Image, being the image itself + /// + /// + /// The value that indicates the image to be used + /// An Image or null + protected virtual Image GetImage(Object imageSelector) { + if (imageSelector == null || imageSelector == DBNull.Value) + return null; + + ImageList il = this.ImageListOrDefault; + if (il != null) { + if (imageSelector is Int32) { + Int32 index = (Int32) imageSelector; + if (index < 0 || index >= il.Images.Count) + return null; + + return il.Images[index]; + } + + String str = imageSelector as String; + if (str != null) { + if (il.Images.ContainsKey(str)) + return il.Images[str]; + + return null; + } + } + + return imageSelector as Image; + } + + /// + /// + protected virtual Object GetImageSelector() { + return this.ColumnIsPrimary ? this.ListItem.ImageSelector : this.OLVSubItem.ImageSelector; + } + + /// + /// Return the string that should be drawn within this + /// + /// + protected virtual string GetText() { + return this.SubItem == null ? this.ListItem.Text : this.SubItem.Text; + } + + /// + /// Return the Color that is the background color for this item's text + /// + /// The background color of the subitem's text + [Obsolete("Use GetBackgroundColor() instead")] + protected virtual Color GetTextBackgroundColor() { + return Color.Red; // just so it shows up if it is used + } + + #endregion + + #region IRenderer members + + /// + /// Render the whole item in a non-details view. + /// + /// + /// + /// + /// + /// + public override bool RenderItem(DrawListViewItemEventArgs e, Graphics g, Rectangle itemBounds, object model) { + this.ConfigureItem(e, itemBounds, model); + return this.OptionalRender(g, itemBounds); + } + + /// + /// Prepare this renderer to draw in response to the given event + /// + /// + /// + /// + /// Use this if you want to chain a second renderer within a primary renderer. + public virtual void ConfigureItem(DrawListViewItemEventArgs e, Rectangle itemBounds, object model) + { + this.ClearState(); + + this.DrawItemEvent = e; + this.ListItem = (OLVListItem)e.Item; + this.SubItem = null; + this.ListView = (ObjectListView)this.ListItem.ListView; + this.Column = this.ListView.GetColumn(0); + this.RowObject = model; + this.Bounds = itemBounds; + this.IsItemSelected = this.ListItem.Selected && this.ListItem.Enabled; + } + + /// + /// Render one cell + /// + /// + /// + /// + /// + /// + public override bool RenderSubItem(DrawListViewSubItemEventArgs e, Graphics g, Rectangle cellBounds, object model) { + this.ConfigureSubItem(e, cellBounds, model); + return this.OptionalRender(g, cellBounds); + } + + /// + /// Prepare this renderer to draw in response to the given event + /// + /// + /// + /// + /// Use this if you want to chain a second renderer within a primary renderer. + public virtual void ConfigureSubItem(DrawListViewSubItemEventArgs e, Rectangle cellBounds, object model) { + this.ClearState(); + + this.Event = e; + this.ListItem = (OLVListItem)e.Item; + this.SubItem = (OLVListSubItem)e.SubItem; + this.ListView = (ObjectListView)this.ListItem.ListView; + this.Column = (OLVColumn)e.Header; + this.RowObject = model; + this.Bounds = cellBounds; + this.IsItemSelected = this.ListItem.Selected && this.ListItem.Enabled; + } + + /// + /// Calculate which part of this cell was hit + /// + /// + /// + /// + public override void HitTest(OlvListViewHitTestInfo hti, int x, int y) { + this.ClearState(); + + this.ListView = hti.ListView; + this.ListItem = hti.Item; + this.SubItem = hti.SubItem; + this.Column = hti.Column; + this.RowObject = hti.RowObject; + this.IsItemSelected = this.ListItem.Selected && this.ListItem.Enabled; + if (this.SubItem == null) + this.Bounds = this.ListItem.Bounds; + else + this.Bounds = this.ListItem.GetSubItemBounds(this.Column.Index); + + using (Graphics g = this.ListView.CreateGraphics()) { + this.HandleHitTest(g, hti, x, y); + } + } + + /// + /// Calculate the edit rectangle + /// + /// + /// + /// + /// + /// + /// + public override Rectangle GetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) { + this.ClearState(); + + this.ListView = (ObjectListView) item.ListView; + this.ListItem = item; + this.SubItem = item.GetSubItem(subItemIndex); + this.Column = this.ListView.GetColumn(subItemIndex); + this.RowObject = item.RowObject; + this.IsItemSelected = this.ListItem.Selected && this.ListItem.Enabled; + this.Bounds = cellBounds; + + return this.HandleGetEditRectangle(g, cellBounds, item, subItemIndex, preferredSize); + } + + #endregion + + #region IRenderer implementation + + // Subclasses will probably want to override these methods rather than the IRenderer + // interface methods. + + /// + /// Draw our data into the given rectangle using the given graphics context. + /// + /// + /// Subclasses should override this method. + /// The graphics context that should be used for drawing + /// The bounds of the subitem cell + /// Returns whether the rendering has already taken place. + /// If this returns false, the default processing will take over. + /// + public virtual bool OptionalRender(Graphics g, Rectangle r) { + if (this.ListView.View != View.Details) + return false; + + this.Render(g, r); + return true; + } + + /// + /// Draw our data into the given rectangle using the given graphics context. + /// + /// + /// Subclasses should override this method if they never want + /// to fall back on the default processing + /// The graphics context that should be used for drawing + /// The bounds of the subitem cell + public virtual void Render(Graphics g, Rectangle r) { + this.StandardRender(g, r); + } + + /// + /// Do the actual work of hit testing. Subclasses should override this rather than HitTest() + /// + /// + /// + /// + /// + protected virtual void HandleHitTest(Graphics g, OlvListViewHitTestInfo hti, int x, int y) { + Rectangle r = this.CalculateAlignedRectangle(g, ApplyCellPadding(this.Bounds)); + this.StandardHitTest(g, hti, r, x, y); + } + + /// + /// Handle a HitTest request after all state information has been initialized + /// + /// + /// + /// + /// + /// + /// + protected virtual Rectangle HandleGetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) { + // MAINTAINER NOTE: This type testing is wrong (design-wise). The base class should return cell bounds, + // and a more specialized class should return StandardGetEditRectangle(). But BaseRenderer is used directly + // to draw most normal cells, as well as being directly subclassed for user implemented renderers. And this + // method needs to return different bounds in each of those cases. We should have a StandardRenderer and make + // BaseRenderer into an ABC -- but that would break too much existing code. And so we have this hack :( + + // If we are a standard renderer, return the position of the text, otherwise, use the whole cell. + if (this.GetType() == typeof (BaseRenderer)) + return this.StandardGetEditRectangle(g, cellBounds, preferredSize); + + // Center the editor vertically + if (cellBounds.Height != preferredSize.Height) + cellBounds.Y += (cellBounds.Height - preferredSize.Height) / 2; + + return cellBounds; + } + + #endregion + + #region Standard IRenderer implementations + + /// + /// Draw the standard "[checkbox] [image] [text]" cell after the state properties have been initialized. + /// + /// + /// + protected void StandardRender(Graphics g, Rectangle r) { + this.DrawBackground(g, r); + + // Adjust the first columns rectangle to match the padding used by the native mode of the ListView + if (this.ColumnIsPrimary && this.CellHorizontalAlignment == HorizontalAlignment.Left ) { + r.X += 3; + r.Width -= 1; + } + r = this.ApplyCellPadding(r); + this.DrawAlignedImageAndText(g, r); + + // Show where the bounds of the cell padding are (debugging) + if (ObjectListView.ShowCellPaddingBounds) + g.DrawRectangle(Pens.Purple, r); + } + + /// + /// Change the bounds of the given rectangle to take any cell padding into account + /// + /// + /// + public virtual Rectangle ApplyCellPadding(Rectangle r) { + Rectangle? padding = this.EffectiveCellPadding; + if (!padding.HasValue) + return r; + // The two subtractions below look wrong, but are correct! + Rectangle paddingRectangle = padding.Value; + r.Width -= paddingRectangle.Right; + r.Height -= paddingRectangle.Bottom; + r.Offset(paddingRectangle.Location); + return r; + } + + /// + /// Perform normal hit testing relative to the given aligned content bounds + /// + /// + /// + /// + /// + /// + protected virtual void StandardHitTest(Graphics g, OlvListViewHitTestInfo hti, Rectangle alignedContentRectangle, int x, int y) { + Rectangle r = alignedContentRectangle; + + // Match tweaking from renderer + if (this.ColumnIsPrimary && this.CellHorizontalAlignment == HorizontalAlignment.Left && !(this is TreeListView.TreeRenderer)) { + r.X += 3; + r.Width -= 1; + } + int width = 0; + + // Did they hit a check box on the primary column? + if (this.ColumnIsPrimary && this.ListView.CheckBoxes) { + Size checkBoxSize = this.CalculateCheckBoxSize(g); + int checkBoxTop = this.AlignVertically(r, checkBoxSize.Height); + Rectangle r3 = new Rectangle(r.X, checkBoxTop, checkBoxSize.Width, checkBoxSize.Height); + width = r3.Width + 6; + // g.DrawRectangle(Pens.DarkGreen, r3); + if (r3.Contains(x, y)) { + hti.HitTestLocation = HitTestLocation.CheckBox; + return; + } + } + + // Did they hit the image? If they hit the image of a + // non-primary column that has a checkbox, it counts as a + // checkbox hit + r.X += width; + r.Width -= width; + width = this.CalculateImageWidth(g, this.GetImageSelector()); + Rectangle rTwo = r; + rTwo.Width = width; + //g.DrawRectangle(Pens.Red, rTwo); + if (rTwo.Contains(x, y)) { + if (this.Column != null && (this.Column.Index > 0 && this.Column.CheckBoxes)) + hti.HitTestLocation = HitTestLocation.CheckBox; + else + hti.HitTestLocation = HitTestLocation.Image; + return; + } + + // Did they hit the text? + r.X += width; + r.Width -= width; + width = this.CalculateTextWidth(g, this.GetText(), r.Width); + rTwo = r; + rTwo.Width = width; + // g.DrawRectangle(Pens.Blue, rTwo); + if (rTwo.Contains(x, y)) { + hti.HitTestLocation = HitTestLocation.Text; + return; + } + + hti.HitTestLocation = HitTestLocation.InCell; + } + + /// + /// This method calculates the bounds of the text within a standard layout + /// (i.e. optional checkbox, optional image, text) + /// + /// This method only works correctly if the state of the renderer + /// has been fully initialized (see BaseRenderer.GetEditRectangle) + /// + /// + /// + /// + protected virtual Rectangle StandardGetEditRectangle(Graphics g, Rectangle cellBounds, Size preferredSize) { + + Size contentSize = this.CalculateContentSize(g, cellBounds); + int contentWidth = this.Column.CellEditUseWholeCellEffective ? cellBounds.Width : contentSize.Width; + Rectangle editControlBounds = this.CalculatePaddedAlignedBounds(g, cellBounds, new Size(contentWidth, preferredSize.Height)); + + Size checkBoxSize = this.CalculatePrimaryCheckBoxSize(g); + int imageWidth = this.CalculateImageWidth(g, this.GetImageSelector()); + + int width = checkBoxSize.Width + imageWidth + 2; + + // Indent the primary column by the required amount + if (this.ColumnIsPrimary && this.ListItem.IndentCount > 0) { + int indentWidth = this.ListView.SmallImageSize.Width * this.ListItem.IndentCount; + editControlBounds.X += indentWidth; + } + + editControlBounds.X += width; + editControlBounds.Width -= width; + + if (editControlBounds.Width < 50) + editControlBounds.Width = 50; + if (editControlBounds.Right > cellBounds.Right) + editControlBounds.Width = cellBounds.Right - editControlBounds.Left; + + return editControlBounds; + } + + /// + /// Apply any padding to the given bounds, and then align a rectangle of the given + /// size within that padded area. + /// + /// + /// + /// + /// + protected Rectangle CalculatePaddedAlignedBounds(Graphics g, Rectangle cellBounds, Size preferredSize) { + Rectangle r = ApplyCellPadding(cellBounds); + r = this.AlignRectangle(r, new Rectangle(Point.Empty, preferredSize)); + return r; + } + + #endregion + + #region Drawing routines + + /// + /// Draw the given image aligned horizontally within the column. + /// + /// + /// Over tall images are scaled to fit. Over-wide images are + /// truncated. This is by design! + /// + /// Graphics context to use for drawing + /// Bounds of the cell + /// The image to be drawn + protected virtual void DrawAlignedImage(Graphics g, Rectangle r, Image image) { + if (image == null) + return; + + // By default, the image goes in the top left of the rectangle + Rectangle imageBounds = new Rectangle(r.Location, image.Size); + + // If the image is too tall to be drawn in the space provided, proportionally scale it down. + // Too wide images are not scaled. + if (image.Height > r.Height) { + float scaleRatio = (float) r.Height / (float) image.Height; + imageBounds.Width = (int) ((float) image.Width * scaleRatio); + imageBounds.Height = r.Height - 1; + } + + // Align and draw our (possibly scaled) image + Rectangle alignRectangle = this.AlignRectangle(r, imageBounds); + if (this.ListItem.Enabled) + g.DrawImage(image, alignRectangle); + else + ControlPaint.DrawImageDisabled(g, image, alignRectangle.X, alignRectangle.Y, GetBackgroundColor()); + } + + /// + /// Draw our subitems image and text + /// + /// Graphics context to use for drawing + /// Pre-padded bounds of the cell + protected virtual void DrawAlignedImageAndText(Graphics g, Rectangle r) { + this.DrawImageAndText(g, this.CalculateAlignedRectangle(g, r)); + } + + /// + /// Fill in the background of this cell + /// + /// Graphics context to use for drawing + /// Bounds of the cell + protected virtual void DrawBackground(Graphics g, Rectangle r) { + if (!this.IsDrawBackground) + return; + + Color backgroundColor = this.GetBackgroundColor(); + + using (Brush brush = new SolidBrush(backgroundColor)) { + g.FillRectangle(brush, r.X - 1, r.Y - 1, r.Width + 2, r.Height + 2); + } + } + + /// + /// Draw the primary check box of this row (checkboxes in other sub items use a different method) + /// + /// Graphics context to use for drawing + /// The pre-aligned and padded target rectangle + protected virtual int DrawCheckBox(Graphics g, Rectangle r) { + // The odd constants are to match checkbox placement in native mode (on XP at least) + // TODO: Unify this with CheckStateRenderer + + // The rectangle r is already horizontally aligned. We still need to align it vertically. + Size checkBoxSize = this.CalculateCheckBoxSize(g); + Point checkBoxLocation = new Point(r.X, this.AlignVertically(r, checkBoxSize.Height)); + + if (this.IsPrinting || this.UseCustomCheckboxImages) { + int imageIndex = this.ListItem.StateImageIndex; + if (this.ListView.StateImageList == null || imageIndex < 0 || imageIndex >= this.ListView.StateImageList.Images.Count) + return 0; + + return this.DrawImage(g, new Rectangle(checkBoxLocation, checkBoxSize), this.ListView.StateImageList.Images[imageIndex]) + 4; + } + + CheckBoxState boxState = this.GetCheckBoxState(this.ListItem.CheckState); + CheckBoxRenderer.DrawCheckBox(g, checkBoxLocation, boxState); + return checkBoxSize.Width; + } + + /// + /// Calculate the CheckBoxState we need to correctly draw the given state + /// + /// + /// + protected virtual CheckBoxState GetCheckBoxState(CheckState checkState) { + + // Should the checkbox be drawn as disabled? + if (this.IsCheckBoxDisabled) { + switch (checkState) { + case CheckState.Checked: + return CheckBoxState.CheckedDisabled; + case CheckState.Unchecked: + return CheckBoxState.UncheckedDisabled; + default: + return CheckBoxState.MixedDisabled; + } + } + + // Is the cursor currently over this checkbox? + if (this.IsCheckboxHot) { + switch (checkState) { + case CheckState.Checked: + return CheckBoxState.CheckedHot; + case CheckState.Unchecked: + return CheckBoxState.UncheckedHot; + default: + return CheckBoxState.MixedHot; + } + } + + // Not hot and not disabled -- just draw it normally + switch (checkState) { + case CheckState.Checked: + return CheckBoxState.CheckedNormal; + case CheckState.Unchecked: + return CheckBoxState.UncheckedNormal; + default: + return CheckBoxState.MixedNormal; + } + + } + + /// + /// Should this checkbox be drawn as disabled? + /// + protected virtual bool IsCheckBoxDisabled { + get { + if (this.ListItem != null && !this.ListItem.Enabled) + return true; + + if (!this.ListView.RenderNonEditableCheckboxesAsDisabled) + return false; + + return (this.ListView.CellEditActivation == ObjectListView.CellEditActivateMode.None || + (this.Column != null && !this.Column.IsEditable)); + } + } + + /// + /// Is the current item hot (i.e. under the mouse)? + /// + protected bool IsCellHot { + get { + return this.ListView != null && + this.ListView.HotRowIndex == this.ListItem.Index && + this.ListView.HotColumnIndex == (this.Column == null ? 0 : this.Column.Index); + } + } + + /// + /// Is the mouse over a checkbox in this cell? + /// + protected bool IsCheckboxHot { + get { + return this.IsCellHot && this.ListView.HotCellHitLocation == HitTestLocation.CheckBox; + } + } + + /// + /// Draw the given text and optional image in the "normal" fashion + /// + /// Graphics context to use for drawing + /// Bounds of the cell + /// The optional image to be drawn + protected virtual int DrawImage(Graphics g, Rectangle r, Object imageSelector) { + if (imageSelector == null || imageSelector == DBNull.Value) + return 0; + + // Draw from the image list (most common case) + ImageList il = this.ImageListOrDefault; + if (il != null) { + + // Try to translate our imageSelector into a valid ImageList index + int selectorAsInt = -1; + if (imageSelector is Int32) { + selectorAsInt = (Int32) imageSelector; + if (selectorAsInt >= il.Images.Count) + selectorAsInt = -1; + } else { + String selectorAsString = imageSelector as String; + if (selectorAsString != null) + selectorAsInt = il.Images.IndexOfKey(selectorAsString); + } + + // If we found a valid index into the ImageList, draw it. + // We want to draw using the native DrawImageList calls, since that let's us do some nice effects + // But the native call does not work on PrinterDCs, so if we're printing we have to skip this bit. + if (selectorAsInt >= 0) { + if (!this.IsPrinting) { + if (il.ImageSize.Height < r.Height) + r.Y = this.AlignVertically(r, new Rectangle(Point.Empty, il.ImageSize)); + + // If we are not printing, it's probable that the given Graphics object is double buffered using a BufferedGraphics object. + // But the ImageList.Draw method doesn't honor the Translation matrix that's probably in effect on the buffered + // graphics. So we have to calculate our drawing rectangle, relative to the cells natural boundaries. + // This effectively simulates the Translation matrix. + + Rectangle r2 = new Rectangle(r.X - this.Bounds.X, r.Y - this.Bounds.Y, r.Width, r.Height); + NativeMethods.DrawImageList(g, il, selectorAsInt, r2.X, r2.Y, this.IsItemSelected, !this.ListItem.Enabled); + return il.ImageSize.Width; + } + + // For some reason, printing from an image list doesn't work onto a printer context + // So get the image from the list and FALL THROUGH to the "print an image" case + imageSelector = il.Images[selectorAsInt]; + } + } + + // Is the selector actually an image? + Image image = imageSelector as Image; + if (image == null) + return 0; // no, give up + + if (image.Size.Height < r.Height) + r.Y = this.AlignVertically(r, new Rectangle(Point.Empty, image.Size)); + + if (this.ListItem.Enabled) + g.DrawImageUnscaled(image, r.X, r.Y); + else + ControlPaint.DrawImageDisabled(g, image, r.X, r.Y, GetBackgroundColor()); + + return image.Width; + } + + /// + /// Draw our subitems image and text + /// + /// Graphics context to use for drawing + /// Bounds of the cell + protected virtual void DrawImageAndText(Graphics g, Rectangle r) { + int offset = 0; + if (this.ListView.CheckBoxes && this.ColumnIsPrimary) { + offset = this.DrawCheckBox(g, r) + 6; + r.X += offset; + r.Width -= offset; + } + + offset = this.DrawImage(g, r, this.GetImageSelector()); + r.X += offset; + r.Width -= offset; + + this.DrawText(g, r, this.GetText()); + } + + /// + /// Draw the given collection of image selectors + /// + /// + /// + /// + protected virtual int DrawImages(Graphics g, Rectangle r, ICollection imageSelectors) { + // Collect the non-null images + List images = new List(); + foreach (Object selector in imageSelectors) { + Image image = this.GetImage(selector); + if (image != null) + images.Add(image); + } + + // Figure out how much space they will occupy + int width = 0; + int height = 0; + foreach (Image image in images) { + width += (image.Width + this.Spacing); + height = Math.Max(height, image.Height); + } + + // Align the collection of images within the cell + Rectangle r2 = this.AlignRectangle(r, new Rectangle(0, 0, width, height)); + + // Finally, draw all the images in their correct location + Color backgroundColor = GetBackgroundColor(); + Point pt = r2.Location; + foreach (Image image in images) { + if (this.ListItem.Enabled) + g.DrawImage(image, pt); + else + ControlPaint.DrawImageDisabled(g, image, pt.X, pt.Y, backgroundColor); + pt.X += (image.Width + this.Spacing); + } + + // Return the width that the images occupy + return width; + } + + /// + /// Draw the given text and optional image in the "normal" fashion + /// + /// Graphics context to use for drawing + /// Bounds of the cell + /// The string to be drawn + public virtual void DrawText(Graphics g, Rectangle r, String txt) { + if (String.IsNullOrEmpty(txt)) + return; + + if (this.UseGdiTextRendering) + this.DrawTextGdi(g, r, txt); + else + this.DrawTextGdiPlus(g, r, txt); + } + + /// + /// Print the given text in the given rectangle using only GDI routines + /// + /// + /// + /// + /// + /// The native list control uses GDI routines to do its drawing, so using them + /// here makes the owner drawn mode looks more natural. + /// This method doesn't honour the CanWrap setting on the renderer. All + /// text is single line + /// + protected virtual void DrawTextGdi(Graphics g, Rectangle r, String txt) { + Color backColor = Color.Transparent; + if (this.IsDrawBackground && this.IsItemSelected && ColumnIsPrimary && !this.ListView.FullRowSelect) + backColor = this.GetSelectedBackgroundColor(); + + TextFormatFlags flags = NormalTextFormatFlags | this.CellVerticalAlignmentAsTextFormatFlag; + + // I think there is a bug in the TextRenderer. Setting or not setting SingleLine doesn't make + // any difference -- it is always single line. + if (!this.CanWrapOrDefault) + flags |= TextFormatFlags.SingleLine; + TextRenderer.DrawText(g, txt, this.Font, r, this.GetForegroundColor(), backColor, flags); + } + + private bool ColumnIsPrimary { + get { return this.Column != null && this.Column.Index == 0; } + } + + /// + /// Gets the cell's vertical alignment as a TextFormatFlag + /// + /// + protected TextFormatFlags CellVerticalAlignmentAsTextFormatFlag { + get { + switch (this.EffectiveCellVerticalAlignment) { + case StringAlignment.Near: + return TextFormatFlags.Top; + case StringAlignment.Center: + return TextFormatFlags.VerticalCenter; + case StringAlignment.Far: + return TextFormatFlags.Bottom; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + /// + /// Gets the StringFormat needed when drawing text using GDI+ + /// + protected virtual StringFormat StringFormatForGdiPlus { + get { + StringFormat fmt = new StringFormat(); + fmt.LineAlignment = this.EffectiveCellVerticalAlignment; + fmt.Trimming = StringTrimming.EllipsisCharacter; + fmt.Alignment = this.Column == null ? StringAlignment.Near : this.Column.TextStringAlign; + if (!this.CanWrapOrDefault) + fmt.FormatFlags = StringFormatFlags.NoWrap; + return fmt; + } + } + + /// + /// Print the given text in the given rectangle using normal GDI+ .NET methods + /// + /// Printing to a printer dc has to be done using this method. + protected virtual void DrawTextGdiPlus(Graphics g, Rectangle r, String txt) { + using (StringFormat fmt = this.StringFormatForGdiPlus) { + // Draw the background of the text as selected, if it's the primary column + // and it's selected and it's not in FullRowSelect mode. + Font f = this.Font; + if (this.IsDrawBackground && this.IsItemSelected && this.ColumnIsPrimary && !this.ListView.FullRowSelect) { + SizeF size = g.MeasureString(txt, f, r.Width, fmt); + Rectangle r2 = r; + r2.Width = (int) size.Width + 1; + using (Brush brush = new SolidBrush(this.GetSelectedBackgroundColor())) { + g.FillRectangle(brush, r2); + } + } + RectangleF rf = r; + g.DrawString(txt, f, this.TextBrush, rf, fmt); + } + + // We should put a focus rectangle around the column 0 text if it's selected -- + // but we don't because: + // - I really dislike this UI convention + // - we are using buffered graphics, so the DrawFocusRecatangle method of the event doesn't work + + //if (this.ColumnIsPrimary) { + // Size size = TextRenderer.MeasureText(this.SubItem.Text, this.ListView.ListFont); + // if (r.Width > size.Width) + // r.Width = size.Width; + // this.Event.DrawFocusRectangle(r); + //} + } + + #endregion + } + + /// + /// This renderer highlights substrings that match a given text filter. + /// + /// + /// Implementation note: + /// This renderer uses the functionality of BaseRenderer to draw the text, and + /// then draws a translucent frame over the top of the already rendered text glyphs. + /// There's no way to draw the matching text in a different font or color in this + /// implementation. + /// + public class HighlightTextRenderer : BaseRenderer, IFilterAwareRenderer { + #region Life and death + + /// + /// Create a HighlightTextRenderer + /// + public HighlightTextRenderer() { + this.FramePen = Pens.DarkGreen; + this.FillBrush = Brushes.Yellow; + } + + /// + /// Create a HighlightTextRenderer + /// + /// + public HighlightTextRenderer(ITextMatchFilter filter) + : this() { + this.Filter = filter; + } + + /// + /// Create a HighlightTextRenderer + /// + /// + [Obsolete("Use HighlightTextRenderer(TextMatchFilter) instead", true)] + public HighlightTextRenderer(string text) {} + + #endregion + + #region Configuration properties + + /// + /// Gets or set how rounded will be the corners of the text match frame + /// + [Category("Appearance"), + DefaultValue(3.0f), + Description("How rounded will be the corners of the text match frame?")] + public float CornerRoundness { + get { return cornerRoundness; } + set { cornerRoundness = value; } + } + + private float cornerRoundness = 3.0f; + + /// + /// Gets or set the brush will be used to paint behind the matched substrings. + /// Set this to null to not fill the frame. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Brush FillBrush { + get { return fillBrush; } + set { fillBrush = value; } + } + + private Brush fillBrush; + + /// + /// Gets or sets the filter that is filtering the ObjectListView and for + /// which this renderer should highlight text + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ITextMatchFilter Filter { + get { return filter; } + set { filter = value; } + } + private ITextMatchFilter filter; + + /// + /// When a filter changes, keep track of the text matching filters + /// + IModelFilter IFilterAwareRenderer.Filter + { + get { return filter; } + set { RegisterNewFilter(value); } + } + + internal void RegisterNewFilter(IModelFilter newFilter) { + TextMatchFilter textFilter = newFilter as TextMatchFilter; + if (textFilter != null) + { + Filter = textFilter; + return; + } + CompositeFilter composite = newFilter as CompositeFilter; + if (composite != null) + { + foreach (TextMatchFilter textSubFilter in composite.TextFilters) + { + Filter = textSubFilter; + return; + } + } + Filter = null; + } + + /// + /// Gets or set the pen will be used to frame the matched substrings. + /// Set this to null to not draw a frame. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Pen FramePen { + get { return framePen; } + set { framePen = value; } + } + + private Pen framePen; + + /// + /// Gets or sets whether the frame around a text match will have rounded corners + /// + [Category("Appearance"), + DefaultValue(true), + Description("Will the frame around a text match will have rounded corners?")] + public bool UseRoundedRectangle { + get { return useRoundedRectangle; } + set { useRoundedRectangle = value; } + } + + private bool useRoundedRectangle = true; + + #endregion + + #region Compatibility properties + + /// + /// Gets or set the text that will be highlighted + /// + [Obsolete("Set the Filter directly rather than just the text", true)] + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public string TextToHighlight { + get { return String.Empty; } + set { } + } + + /// + /// Gets or sets the manner in which substring will be compared. + /// + /// + /// Use this to control if substring matches are case sensitive or insensitive. + [Obsolete("Set the Filter directly rather than just this setting", true)] + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public StringComparison StringComparison { + get { return StringComparison.CurrentCultureIgnoreCase; } + set { } + } + + #endregion + + #region IRenderer interface overrides + + /// + /// Handle a HitTest request after all state information has been initialized + /// + /// + /// + /// + /// + /// + /// + protected override Rectangle HandleGetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) { + return this.StandardGetEditRectangle(g, cellBounds, preferredSize); + } + + #endregion + + #region Rendering + + // This class has two implement two highlighting schemes: one for GDI, another for GDI+. + // Naturally, GDI+ makes the task easier, but we have to provide something for GDI + // since that it is what is normally used. + + /// + /// Draw text using GDI + /// + /// + /// + /// + protected override void DrawTextGdi(Graphics g, Rectangle r, string txt) { + if (this.ShouldDrawHighlighting) + this.DrawGdiTextHighlighting(g, r, txt); + + base.DrawTextGdi(g, r, txt); + } + + /// + /// Draw the highlighted text using GDI + /// + /// + /// + /// + protected virtual void DrawGdiTextHighlighting(Graphics g, Rectangle r, string txt) { + + // TextRenderer puts horizontal padding around the strings, so we need to take + // that into account when measuring strings + const int paddingAdjustment = 6; + + // Cache the font + Font f = this.Font; + + foreach (CharacterRange range in this.Filter.FindAllMatchedRanges(txt)) { + // Measure the text that comes before our substring + Size precedingTextSize = Size.Empty; + if (range.First > 0) { + string precedingText = txt.Substring(0, range.First); + precedingTextSize = TextRenderer.MeasureText(g, precedingText, f, r.Size, NormalTextFormatFlags); + precedingTextSize.Width -= paddingAdjustment; + } + + // Measure the length of our substring (may be different each time due to case differences) + string highlightText = txt.Substring(range.First, range.Length); + Size textToHighlightSize = TextRenderer.MeasureText(g, highlightText, f, r.Size, NormalTextFormatFlags); + textToHighlightSize.Width -= paddingAdjustment; + + float textToHighlightLeft = r.X + precedingTextSize.Width + 1; + float textToHighlightTop = this.AlignVertically(r, textToHighlightSize.Height); + + // Draw a filled frame around our substring + this.DrawSubstringFrame(g, textToHighlightLeft, textToHighlightTop, textToHighlightSize.Width, textToHighlightSize.Height); + } + } + + /// + /// Draw an indication around the given frame that shows a text match + /// + /// + /// + /// + /// + /// + protected virtual void DrawSubstringFrame(Graphics g, float x, float y, float width, float height) { + if (this.UseRoundedRectangle) { + using (GraphicsPath path = this.GetRoundedRect(x, y, width, height, 3.0f)) { + if (this.FillBrush != null) + g.FillPath(this.FillBrush, path); + if (this.FramePen != null) + g.DrawPath(this.FramePen, path); + } + } else { + if (this.FillBrush != null) + g.FillRectangle(this.FillBrush, x, y, width, height); + if (this.FramePen != null) + g.DrawRectangle(this.FramePen, x, y, width, height); + } + } + + /// + /// Draw the text using GDI+ + /// + /// + /// + /// + protected override void DrawTextGdiPlus(Graphics g, Rectangle r, string txt) { + if (this.ShouldDrawHighlighting) + this.DrawGdiPlusTextHighlighting(g, r, txt); + + base.DrawTextGdiPlus(g, r, txt); + } + + /// + /// Draw the highlighted text using GDI+ + /// + /// + /// + /// + protected virtual void DrawGdiPlusTextHighlighting(Graphics g, Rectangle r, string txt) { + // Find the substrings we want to highlight + List ranges = new List(this.Filter.FindAllMatchedRanges(txt)); + + if (ranges.Count == 0) + return; + + using (StringFormat fmt = this.StringFormatForGdiPlus) { + RectangleF rf = r; + fmt.SetMeasurableCharacterRanges(ranges.ToArray()); + Region[] stringRegions = g.MeasureCharacterRanges(txt, this.Font, rf, fmt); + + foreach (Region region in stringRegions) { + RectangleF bounds = region.GetBounds(g); + this.DrawSubstringFrame(g, bounds.X - 1, bounds.Y - 1, bounds.Width + 2, bounds.Height); + } + } + } + + #endregion + + #region Utilities + + /// + /// Gets whether the renderer should actually draw highlighting + /// + protected bool ShouldDrawHighlighting { + get { return this.Column == null || (this.Column.Searchable && this.Filter != null); } + } + + /// + /// Return a GraphicPath that is a round cornered rectangle + /// + /// A round cornered rectangle path + /// If I could rely on people using C# 3.0+, this should be + /// an extension method of GraphicsPath. + /// + /// + /// + /// + /// + protected GraphicsPath GetRoundedRect(float x, float y, float width, float height, float diameter) { + return GetRoundedRect(new RectangleF(x, y, width, height), diameter); + } + + /// + /// Return a GraphicPath that is a round cornered rectangle + /// + /// The rectangle + /// The diameter of the corners + /// A round cornered rectangle path + /// If I could rely on people using C# 3.0+, this should be + /// an extension method of GraphicsPath. + protected GraphicsPath GetRoundedRect(RectangleF rect, float diameter) { + GraphicsPath path = new GraphicsPath(); + + if (diameter > 0) { + RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter); + path.AddArc(arc, 180, 90); + arc.X = rect.Right - diameter; + path.AddArc(arc, 270, 90); + arc.Y = rect.Bottom - diameter; + path.AddArc(arc, 0, 90); + arc.X = rect.Left; + path.AddArc(arc, 90, 90); + path.CloseFigure(); + } else { + path.AddRectangle(rect); + } + + return path; + } + + #endregion + } + + /// + /// This class maps a data value to an image that should be drawn for that value. + /// + /// It is useful for drawing data that is represented as an enum or boolean. + public class MappedImageRenderer : BaseRenderer { + /// + /// Return a renderer that draw boolean values using the given images + /// + /// Draw this when our data value is true + /// Draw this when our data value is false + /// A Renderer + public static MappedImageRenderer Boolean(Object trueImage, Object falseImage) { + return new MappedImageRenderer(true, trueImage, false, falseImage); + } + + /// + /// Return a renderer that draw tristate boolean values using the given images + /// + /// Draw this when our data value is true + /// Draw this when our data value is false + /// Draw this when our data value is null + /// A Renderer + public static MappedImageRenderer TriState(Object trueImage, Object falseImage, Object nullImage) { + return new MappedImageRenderer(new Object[] {true, trueImage, false, falseImage, null, nullImage}); + } + + /// + /// Make a new empty renderer + /// + public MappedImageRenderer() { + map = new System.Collections.Hashtable(); + } + + /// + /// Make a new renderer that will show the given image when the given key is the aspect value + /// + /// The data value to be matched + /// The image to be shown when the key is matched + public MappedImageRenderer(Object key, Object image) + : this() { + this.Add(key, image); + } + + /// + /// Make a new renderer that will show the given images when it receives the given keys + /// + /// + /// + /// + /// + public MappedImageRenderer(Object key1, Object image1, Object key2, Object image2) + : this() { + this.Add(key1, image1); + this.Add(key2, image2); + } + + /// + /// Build a renderer from the given array of keys and their matching images + /// + /// An array of key/image pairs + public MappedImageRenderer(Object[] keysAndImages) + : this() { + if ((keysAndImages.GetLength(0) % 2) != 0) + throw new ArgumentException("Array must have key/image pairs"); + + for (int i = 0; i < keysAndImages.GetLength(0); i += 2) + this.Add(keysAndImages[i], keysAndImages[i + 1]); + } + + /// + /// Register the image that should be drawn when our Aspect has the data value. + /// + /// Value that the Aspect must match + /// An ImageSelector -- an int, string or image + public void Add(Object value, Object image) { + if (value == null) + this.nullImage = image; + else + map[value] = image; + } + + /// + /// Render our value + /// + /// + /// + public override void Render(Graphics g, Rectangle r) { + this.DrawBackground(g, r); + r = this.ApplyCellPadding(r); + + ICollection aspectAsCollection = this.Aspect as ICollection; + if (aspectAsCollection == null) + this.RenderOne(g, r, this.Aspect); + else + this.RenderCollection(g, r, aspectAsCollection); + } + + /// + /// Draw a collection of images + /// + /// + /// + /// + protected void RenderCollection(Graphics g, Rectangle r, ICollection imageSelectors) { + ArrayList images = new ArrayList(); + Image image = null; + foreach (Object selector in imageSelectors) { + if (selector == null) + image = this.GetImage(this.nullImage); + else if (map.ContainsKey(selector)) + image = this.GetImage(map[selector]); + else + image = null; + + if (image != null) + images.Add(image); + } + + this.DrawImages(g, r, images); + } + + /// + /// Draw one image + /// + /// + /// + /// + protected void RenderOne(Graphics g, Rectangle r, Object selector) { + Image image = null; + if (selector == null) + image = this.GetImage(this.nullImage); + else if (map.ContainsKey(selector)) + image = this.GetImage(map[selector]); + + if (image != null) + this.DrawAlignedImage(g, r, image); + } + + #region Private variables + + private Hashtable map; // Track the association between values and images + private Object nullImage; // image to be drawn for null values (since null can't be a key) + + #endregion + } + + /// + /// This renderer draws just a checkbox to match the check state of our model object. + /// + public class CheckStateRenderer : BaseRenderer { + /// + /// Draw our cell + /// + /// + /// + public override void Render(Graphics g, Rectangle r) { + this.DrawBackground(g, r); + if (this.Column == null) + return; + r = this.ApplyCellPadding(r); + CheckState state = this.Column.GetCheckState(this.RowObject); + if (this.IsPrinting) { + // Renderers don't work onto printer DCs, so we have to draw the image ourselves + string key = ObjectListView.CHECKED_KEY; + if (state == CheckState.Unchecked) + key = ObjectListView.UNCHECKED_KEY; + if (state == CheckState.Indeterminate) + key = ObjectListView.INDETERMINATE_KEY; + this.DrawAlignedImage(g, r, this.ImageListOrDefault.Images[key]); + } else { + r = this.CalculateCheckBoxBounds(g, r); + CheckBoxRenderer.DrawCheckBox(g, r.Location, this.GetCheckBoxState(state)); + } + } + + + /// + /// Handle the GetEditRectangle request + /// + /// + /// + /// + /// + /// + /// + protected override Rectangle HandleGetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) { + return this.CalculatePaddedAlignedBounds(g, cellBounds, preferredSize); + } + + /// + /// Handle the HitTest request + /// + /// + /// + /// + /// + protected override void HandleHitTest(Graphics g, OlvListViewHitTestInfo hti, int x, int y) { + Rectangle r = this.CalculateCheckBoxBounds(g, this.Bounds); + if (r.Contains(x, y)) + hti.HitTestLocation = HitTestLocation.CheckBox; + } + } + + /// + /// Render an image that comes from our data source. + /// + /// The image can be sourced from: + /// + /// a byte-array (normally when the image to be shown is + /// stored as a value in a database) + /// an int, which is treated as an index into the image list + /// a string, which is treated first as a file name, and failing that as an index into the image list + /// an ICollection of ints or strings, which will be drawn as consecutive images + /// + /// If an image is an animated GIF, it's state is stored in the SubItem object. + /// By default, the image renderer does not render animations (it begins life with animations paused). + /// To enable animations, you must call Unpause(). + /// In the current implementation (2009-09), each column showing animated gifs must have a + /// different instance of ImageRenderer assigned to it. You cannot share the same instance of + /// an image renderer between two animated gif columns. If you do, only the last column will be + /// animated. + /// + public class ImageRenderer : BaseRenderer { + /// + /// Make an empty image renderer + /// + public ImageRenderer() { + this.stopwatch = new Stopwatch(); + } + + /// + /// Make an empty image renderer that begins life ready for animations + /// + public ImageRenderer(bool startAnimations) + : this() { + this.Paused = !startAnimations; + } + + /// + /// Finalizer + /// + protected override void Dispose(bool disposing) { + Paused = true; + base.Dispose(disposing); + } + + #region Properties + + /// + /// Should the animations in this renderer be paused? + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool Paused { + get { return isPaused; } + set { + if (this.isPaused == value) + return; + + this.isPaused = value; + if (this.isPaused) { + this.StopTickler(); + this.stopwatch.Stop(); + } else { + this.Tickler.Change(1, Timeout.Infinite); + this.stopwatch.Start(); + } + } + } + + private bool isPaused = true; + + private void StopTickler() { + if (this.tickler == null) + return; + + this.tickler.Dispose(); + this.tickler = null; + } + + /// + /// Gets a timer that can be used to trigger redraws on animations + /// + protected Timer Tickler { + get { + if (this.tickler == null) + this.tickler = new System.Threading.Timer(new TimerCallback(this.OnTimer), null, Timeout.Infinite, Timeout.Infinite); + return this.tickler; + } + } + + #endregion + + #region Commands + + /// + /// Pause any animations + /// + public void Pause() { + this.Paused = true; + } + + /// + /// Unpause any animations + /// + public void Unpause() { + this.Paused = false; + } + + #endregion + + #region Drawing + + /// + /// Draw our image + /// + /// + /// + public override void Render(Graphics g, Rectangle r) { + this.DrawBackground(g, r); + + if (this.Aspect == null || this.Aspect == System.DBNull.Value) + return; + r = this.ApplyCellPadding(r); + + if (this.Aspect is System.Byte[]) { + this.DrawAlignedImage(g, r, this.GetImageFromAspect()); + } else { + ICollection imageSelectors = this.Aspect as ICollection; + if (imageSelectors == null) + this.DrawAlignedImage(g, r, this.GetImageFromAspect()); + else + this.DrawImages(g, r, imageSelectors); + } + } + + /// + /// Translate our Aspect into an image. + /// + /// The strategy is: + /// If its a byte array, we treat it as an in-memory image + /// If it's an int, we use that as an index into our image list + /// If it's a string, we try to load a file by that name. If we can't, + /// we use the string as an index into our image list. + /// + /// An image + protected Image GetImageFromAspect() { + // If we've already figured out the image, don't do it again + if (this.OLVSubItem != null && this.OLVSubItem.ImageSelector is Image) { + if (this.OLVSubItem.AnimationState == null) + return (Image) this.OLVSubItem.ImageSelector; + else + return this.OLVSubItem.AnimationState.image; + } + + // Try to convert our Aspect into an Image + // If its a byte array, we treat it as an in-memory image + // If it's an int, we use that as an index into our image list + // If it's a string, we try to find a file by that name. + // If we can't, we use the string as an index into our image list. + Image image = this.Aspect as Image; + if (image != null) { + // Don't do anything else + } else if (this.Aspect is System.Byte[]) { + using (MemoryStream stream = new MemoryStream((System.Byte[]) this.Aspect)) { + try { + image = Image.FromStream(stream); + } + catch (ArgumentException) { + // ignore + } + } + } else if (this.Aspect is Int32) { + image = this.GetImage(this.Aspect); + } else { + String str = this.Aspect as String; + if (!String.IsNullOrEmpty(str)) { + try { + image = Image.FromFile(str); + } + catch (FileNotFoundException) { + image = this.GetImage(this.Aspect); + } + catch (OutOfMemoryException) { + image = this.GetImage(this.Aspect); + } + } + } + + // If this image is an animation, initialize the animation process + if (this.OLVSubItem != null && AnimationState.IsAnimation(image)) { + this.OLVSubItem.AnimationState = new AnimationState(image); + } + + // Cache the image so we don't repeat this dreary process + if (this.OLVSubItem != null) + this.OLVSubItem.ImageSelector = image; + + return image; + } + + #endregion + + #region Events + + /// + /// This is the method that is invoked by the timer. It basically switches control to the listview thread. + /// + /// not used + public void OnTimer(Object state) { + + if (this.IsListViewDead) + return; + + if (this.Paused) + return; + + if (this.ListView.InvokeRequired) + this.ListView.Invoke((MethodInvoker) delegate { this.OnTimer(state); }); + else + this.OnTimerInThread(); + } + + private bool IsListViewDead { + get { + // Apply a whole heap of sanity checks, which basically ensure that the ListView is still alive + return this.ListView == null || + this.ListView.Disposing || + this.ListView.IsDisposed || + !this.ListView.IsHandleCreated; + } + } + + /// + /// This is the OnTimer callback, but invoked in the same thread as the creator of the ListView. + /// This method can use all of ListViews methods without creating a CrossThread exception. + /// + protected void OnTimerInThread() { + // MAINTAINER NOTE: This method must renew the tickler. If it doesn't the animations will stop. + + // If this listview has been destroyed, we can't do anything, so we return without + // renewing the tickler, effectively killing all animations on this renderer + + if (this.IsListViewDead) + return; + + if (this.Paused) + return; + + // If we're not in Detail view or our column has been removed from the list, + // we can't do anything at the moment, but we still renew the tickler because the view may change later. + if (this.ListView.View != System.Windows.Forms.View.Details || this.Column == null || this.Column.Index < 0) { + this.Tickler.Change(1000, Timeout.Infinite); + return; + } + + long elapsedMilliseconds = this.stopwatch.ElapsedMilliseconds; + int subItemIndex = this.Column.Index; + long nextCheckAt = elapsedMilliseconds + 1000; // wait at most one second before checking again + Rectangle updateRect = new Rectangle(); // what part of the view must be updated to draw the changed gifs? + + // Run through all the subitems in the view for our column, and for each one that + // has an animation attached to it, see if the frame needs updating. + + for (int i = 0; i < this.ListView.GetItemCount(); i++) { + OLVListItem lvi = this.ListView.GetItem(i); + + // Get the animation state from the subitem. If there isn't an animation state, skip this row. + OLVListSubItem lvsi = lvi.GetSubItem(subItemIndex); + AnimationState state = lvsi.AnimationState; + if (state == null || !state.IsValid) + continue; + + // Has this frame of the animation expired? + if (elapsedMilliseconds >= state.currentFrameExpiresAt) { + state.AdvanceFrame(elapsedMilliseconds); + + // Track the area of the view that needs to be redrawn to show the changed images + if (updateRect.IsEmpty) + updateRect = lvsi.Bounds; + else + updateRect = Rectangle.Union(updateRect, lvsi.Bounds); + } + + // Remember the minimum time at which a frame is next due to change + nextCheckAt = Math.Min(nextCheckAt, state.currentFrameExpiresAt); + } + + // Update the part of the listview where frames have changed + if (!updateRect.IsEmpty) + this.ListView.Invalidate(updateRect); + + // Renew the tickler in time for the next frame change + this.Tickler.Change(nextCheckAt - elapsedMilliseconds, Timeout.Infinite); + } + + #endregion + + /// + /// Instances of this class kept track of the animation state of a single image. + /// + internal class AnimationState { + private const int PropertyTagTypeShort = 3; + private const int PropertyTagTypeLong = 4; + private const int PropertyTagFrameDelay = 0x5100; + private const int PropertyTagLoopCount = 0x5101; + + /// + /// Is the given image an animation + /// + /// The image to be tested + /// Is the image an animation? + public static bool IsAnimation(Image image) { + if (image == null) + return false; + else + return (new List(image.FrameDimensionsList)).Contains(FrameDimension.Time.Guid); + } + + /// + /// Create an AnimationState in a quiet state + /// + public AnimationState() { + this.imageDuration = new List(); + } + + /// + /// Create an animation state for the given image, which may or may not + /// be an animation + /// + /// The image to be rendered + public AnimationState(Image image) + : this() { + if (!AnimationState.IsAnimation(image)) + return; + + // How many frames in the animation? + this.image = image; + this.frameCount = this.image.GetFrameCount(FrameDimension.Time); + + // Find the delay between each frame. + // The delays are stored an array of 4-byte ints. Each int is the + // number of 1/100th of a second that should elapsed before the frame expires + foreach (PropertyItem pi in this.image.PropertyItems) { + if (pi.Id == PropertyTagFrameDelay) { + for (int i = 0; i < pi.Len; i += 4) { + //TODO: There must be a better way to convert 4-bytes to an int + int delay = (pi.Value[i + 3] << 24) + (pi.Value[i + 2] << 16) + (pi.Value[i + 1] << 8) + pi.Value[i]; + this.imageDuration.Add(delay * 10); // store delays as milliseconds + } + break; + } + } + + // There should be as many frame durations as frames + Debug.Assert(this.imageDuration.Count == this.frameCount, "There should be as many frame durations as there are frames."); + } + + /// + /// Does this state represent a valid animation + /// + public bool IsValid { + get { return (this.image != null && this.frameCount > 0); } + } + + /// + /// Advance our images current frame and calculate when it will expire + /// + public void AdvanceFrame(long millisecondsNow) { + this.currentFrame = (this.currentFrame + 1) % this.frameCount; + this.currentFrameExpiresAt = millisecondsNow + this.imageDuration[this.currentFrame]; + this.image.SelectActiveFrame(FrameDimension.Time, this.currentFrame); + } + + internal int currentFrame; + internal long currentFrameExpiresAt; + internal Image image; + internal List imageDuration; + internal int frameCount; + } + + #region Private variables + + private System.Threading.Timer tickler; // timer used to tickle the animations + private Stopwatch stopwatch; // clock used to time the animation frame changes + + #endregion + } + + /// + /// Render our Aspect as a progress bar + /// + public class BarRenderer : BaseRenderer { + #region Constructors + + /// + /// Make a BarRenderer + /// + public BarRenderer() + : base() {} + + /// + /// Make a BarRenderer for the given range of data values + /// + public BarRenderer(int minimum, int maximum) + : this() { + this.MinimumValue = minimum; + this.MaximumValue = maximum; + } + + /// + /// Make a BarRenderer using a custom bar scheme + /// + public BarRenderer(Pen pen, Brush brush) + : this() { + this.Pen = pen; + this.Brush = brush; + this.UseStandardBar = false; + } + + /// + /// Make a BarRenderer using a custom bar scheme + /// + public BarRenderer(int minimum, int maximum, Pen pen, Brush brush) + : this(minimum, maximum) { + this.Pen = pen; + this.Brush = brush; + this.UseStandardBar = false; + } + + /// + /// Make a BarRenderer that uses a horizontal gradient + /// + public BarRenderer(Pen pen, Color start, Color end) + : this() { + this.Pen = pen; + this.SetGradient(start, end); + } + + /// + /// Make a BarRenderer that uses a horizontal gradient + /// + public BarRenderer(int minimum, int maximum, Pen pen, Color start, Color end) + : this(minimum, maximum) { + this.Pen = pen; + this.SetGradient(start, end); + } + + #endregion + + #region Configuration Properties + + /// + /// Should this bar be drawn in the system style? + /// + [Category("ObjectListView"), + Description("Should this bar be drawn in the system style?"), + DefaultValue(true)] + public bool UseStandardBar { + get { return useStandardBar; } + set { useStandardBar = value; } + } + + private bool useStandardBar = true; + + /// + /// How many pixels in from our cell border will this bar be drawn + /// + [Category("ObjectListView"), + Description("How many pixels in from our cell border will this bar be drawn"), + DefaultValue(2)] + public int Padding { + get { return padding; } + set { padding = value; } + } + + private int padding = 2; + + /// + /// What color will be used to fill the interior of the control before the + /// progress bar is drawn? + /// + [Category("ObjectListView"), + Description("The color of the interior of the bar"), + DefaultValue(typeof (Color), "AliceBlue")] + public Color BackgroundColor { + get { return backgroundColor; } + set { backgroundColor = value; } + } + + private Color backgroundColor = Color.AliceBlue; + + /// + /// What color should the frame of the progress bar be? + /// + [Category("ObjectListView"), + Description("What color should the frame of the progress bar be"), + DefaultValue(typeof (Color), "Black")] + public Color FrameColor { + get { return frameColor; } + set { frameColor = value; } + } + + private Color frameColor = Color.Black; + + /// + /// How many pixels wide should the frame of the progress bar be? + /// + [Category("ObjectListView"), + Description("How many pixels wide should the frame of the progress bar be"), + DefaultValue(1.0f)] + public float FrameWidth { + get { return frameWidth; } + set { frameWidth = value; } + } + + private float frameWidth = 1.0f; + + /// + /// What color should the 'filled in' part of the progress bar be? + /// + /// This is only used if GradientStartColor is Color.Empty + [Category("ObjectListView"), + Description("What color should the 'filled in' part of the progress bar be"), + DefaultValue(typeof (Color), "BlueViolet")] + public Color FillColor { + get { return fillColor; } + set { fillColor = value; } + } + + private Color fillColor = Color.BlueViolet; + + /// + /// Use a gradient to fill the progress bar starting with this color + /// + [Category("ObjectListView"), + Description("Use a gradient to fill the progress bar starting with this color"), + DefaultValue(typeof (Color), "CornflowerBlue")] + public Color GradientStartColor { + get { return startColor; } + set { startColor = value; } + } + + private Color startColor = Color.CornflowerBlue; + + /// + /// Use a gradient to fill the progress bar ending with this color + /// + [Category("ObjectListView"), + Description("Use a gradient to fill the progress bar ending with this color"), + DefaultValue(typeof (Color), "DarkBlue")] + public Color GradientEndColor { + get { return endColor; } + set { endColor = value; } + } + + private Color endColor = Color.DarkBlue; + + /// + /// Regardless of how wide the column become the progress bar will never be wider than this + /// + [Category("Behavior"), + Description("The progress bar will never be wider than this"), + DefaultValue(100)] + public int MaximumWidth { + get { return maximumWidth; } + set { maximumWidth = value; } + } + + private int maximumWidth = 100; + + /// + /// Regardless of how high the cell is the progress bar will never be taller than this + /// + [Category("Behavior"), + Description("The progress bar will never be taller than this"), + DefaultValue(16)] + public int MaximumHeight { + get { return maximumHeight; } + set { maximumHeight = value; } + } + + private int maximumHeight = 16; + + /// + /// The minimum data value expected. Values less than this will given an empty bar + /// + [Category("Behavior"), + Description("The minimum data value expected. Values less than this will given an empty bar"), + DefaultValue(0.0)] + public double MinimumValue { + get { return minimumValue; } + set { minimumValue = value; } + } + + private double minimumValue = 0.0; + + /// + /// The maximum value for the range. Values greater than this will give a full bar + /// + [Category("Behavior"), + Description("The maximum value for the range. Values greater than this will give a full bar"), + DefaultValue(100.0)] + public double MaximumValue { + get { return maximumValue; } + set { maximumValue = value; } + } + + private double maximumValue = 100.0; + + #endregion + + #region Public Properties (non-IDE) + + /// + /// The Pen that will draw the frame surrounding this bar + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Pen Pen { + get { + if (this.pen == null && !this.FrameColor.IsEmpty) + return new Pen(this.FrameColor, this.FrameWidth); + else + return this.pen; + } + set { this.pen = value; } + } + + private Pen pen; + + /// + /// The brush that will be used to fill the bar + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Brush Brush { + get { + if (this.brush == null && !this.FillColor.IsEmpty) + return new SolidBrush(this.FillColor); + else + return this.brush; + } + set { this.brush = value; } + } + + private Brush brush; + + /// + /// The brush that will be used to fill the background of the bar + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Brush BackgroundBrush { + get { + if (this.backgroundBrush == null && !this.BackgroundColor.IsEmpty) + return new SolidBrush(this.BackgroundColor); + else + return this.backgroundBrush; + } + set { this.backgroundBrush = value; } + } + + private Brush backgroundBrush; + + #endregion + + /// + /// Draw this progress bar using a gradient + /// + /// + /// + public void SetGradient(Color start, Color end) { + this.GradientStartColor = start; + this.GradientEndColor = end; + } + + /// + /// Draw our aspect + /// + /// + /// + public override void Render(Graphics g, Rectangle r) { + this.DrawBackground(g, r); + + r = this.ApplyCellPadding(r); + + Rectangle frameRect = Rectangle.Inflate(r, 0 - this.Padding, 0 - this.Padding); + frameRect.Width = Math.Min(frameRect.Width, this.MaximumWidth); + frameRect.Height = Math.Min(frameRect.Height, this.MaximumHeight); + frameRect = this.AlignRectangle(r, frameRect); + + // Convert our aspect to a numeric value + IConvertible convertable = this.Aspect as IConvertible; + if (convertable == null) + return; + double aspectValue = convertable.ToDouble(NumberFormatInfo.InvariantInfo); + + Rectangle fillRect = Rectangle.Inflate(frameRect, -1, -1); + if (aspectValue <= this.MinimumValue) + fillRect.Width = 0; + else if (aspectValue < this.MaximumValue) + fillRect.Width = (int) (fillRect.Width * (aspectValue - this.MinimumValue) / this.MaximumValue); + + // MS-themed progress bars don't work when printing + if (this.UseStandardBar && ProgressBarRenderer.IsSupported && !this.IsPrinting) { + ProgressBarRenderer.DrawHorizontalBar(g, frameRect); + ProgressBarRenderer.DrawHorizontalChunks(g, fillRect); + } else { + g.FillRectangle(this.BackgroundBrush, frameRect); + if (fillRect.Width > 0) { + // FillRectangle fills inside the given rectangle, so expand it a little + fillRect.Width++; + fillRect.Height++; + if (this.GradientStartColor == Color.Empty) + g.FillRectangle(this.Brush, fillRect); + else { + using (LinearGradientBrush gradient = new LinearGradientBrush(frameRect, this.GradientStartColor, this.GradientEndColor, LinearGradientMode.Horizontal)) { + g.FillRectangle(gradient, fillRect); + } + } + } + g.DrawRectangle(this.Pen, frameRect); + } + } + + /// + /// Handle the GetEditRectangle request + /// + /// + /// + /// + /// + /// + /// + protected override Rectangle HandleGetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) { + return this.CalculatePaddedAlignedBounds(g, cellBounds, preferredSize); + } + } + + + /// + /// An ImagesRenderer draws zero or more images depending on the data returned by its Aspect. + /// + /// This renderer's Aspect must return a ICollection of ints, strings or Images, + /// each of which will be drawn horizontally one after the other. + /// As of v2.1, this functionality has been absorbed into ImageRenderer and this is now an + /// empty shell, solely for backwards compatibility. + /// + [ToolboxItem(false)] + public class ImagesRenderer : ImageRenderer {} + + /// + /// A MultiImageRenderer draws the same image a number of times based on our data value + /// + /// The stars in the Rating column of iTunes is a good example of this type of renderer. + public class MultiImageRenderer : BaseRenderer { + /// + /// Make a quiet renderer + /// + public MultiImageRenderer() + : base() {} + + /// + /// Make an image renderer that will draw the indicated image, at most maxImages times. + /// + /// + /// + /// + /// + public MultiImageRenderer(Object imageSelector, int maxImages, int minValue, int maxValue) + : this() { + this.ImageSelector = imageSelector; + this.MaxNumberImages = maxImages; + this.MinimumValue = minValue; + this.MaximumValue = maxValue; + } + + #region Configuration Properties + + /// + /// The index of the image that should be drawn + /// + [Category("Behavior"), + Description("The index of the image that should be drawn"), + DefaultValue(-1)] + public int ImageIndex { + get { + if (imageSelector is Int32) + return (Int32) imageSelector; + else + return -1; + } + set { imageSelector = value; } + } + + /// + /// The name of the image that should be drawn + /// + [Category("Behavior"), + Description("The index of the image that should be drawn"), + DefaultValue(null)] + public string ImageName { + get { return imageSelector as String; } + set { imageSelector = value; } + } + + /// + /// The image selector that will give the image to be drawn + /// + /// Like all image selectors, this can be an int, string or Image + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Object ImageSelector { + get { return imageSelector; } + set { imageSelector = value; } + } + + private Object imageSelector; + + /// + /// What is the maximum number of images that this renderer should draw? + /// + [Category("Behavior"), + Description("The maximum number of images that this renderer should draw"), + DefaultValue(10)] + public int MaxNumberImages { + get { return maxNumberImages; } + set { maxNumberImages = value; } + } + + private int maxNumberImages = 10; + + /// + /// Values less than or equal to this will have 0 images drawn + /// + [Category("Behavior"), + Description("Values less than or equal to this will have 0 images drawn"), + DefaultValue(0)] + public int MinimumValue { + get { return minimumValue; } + set { minimumValue = value; } + } + + private int minimumValue = 0; + + /// + /// Values greater than or equal to this will have MaxNumberImages images drawn + /// + [Category("Behavior"), + Description("Values greater than or equal to this will have MaxNumberImages images drawn"), + DefaultValue(100)] + public int MaximumValue { + get { return maximumValue; } + set { maximumValue = value; } + } + + private int maximumValue = 100; + + #endregion + + /// + /// Draw our data value + /// + /// + /// + public override void Render(Graphics g, Rectangle r) { + this.DrawBackground(g, r); + r = this.ApplyCellPadding(r); + + Image image = this.GetImage(this.ImageSelector); + if (image == null) + return; + + // Convert our aspect to a numeric value + IConvertible convertable = this.Aspect as IConvertible; + if (convertable == null) + return; + double aspectValue = convertable.ToDouble(NumberFormatInfo.InvariantInfo); + + // Calculate how many images we need to draw to represent our aspect value + int numberOfImages; + if (aspectValue <= this.MinimumValue) + numberOfImages = 0; + else if (aspectValue < this.MaximumValue) + numberOfImages = 1 + (int) (this.MaxNumberImages * (aspectValue - this.MinimumValue) / this.MaximumValue); + else + numberOfImages = this.MaxNumberImages; + + // If we need to shrink the image, what will its on-screen dimensions be? + int imageScaledWidth = image.Width; + int imageScaledHeight = image.Height; + if (r.Height < image.Height) { + imageScaledWidth = (int) ((float) image.Width * (float) r.Height / (float) image.Height); + imageScaledHeight = r.Height; + } + // Calculate where the images should be drawn + Rectangle imageBounds = r; + imageBounds.Width = (this.MaxNumberImages * (imageScaledWidth + this.Spacing)) - this.Spacing; + imageBounds.Height = imageScaledHeight; + imageBounds = this.AlignRectangle(r, imageBounds); + + // Finally, draw the images + Rectangle singleImageRect = new Rectangle(imageBounds.X, imageBounds.Y, imageScaledWidth, imageScaledHeight); + Color backgroundColor = GetBackgroundColor(); + for (int i = 0; i < numberOfImages; i++) { + if (this.ListItem.Enabled) { + this.DrawImage(g, singleImageRect, this.ImageSelector); + } else + ControlPaint.DrawImageDisabled(g, image, singleImageRect.X, singleImageRect.Y, backgroundColor); + singleImageRect.X += (imageScaledWidth + this.Spacing); + } + } + } + + + /// + /// A class to render a value that contains a bitwise-OR'ed collection of values. + /// + public class FlagRenderer : BaseRenderer { + /// + /// Register the given image to the given value + /// + /// When this flag is present... + /// ...draw this image + public void Add(Object key, Object imageSelector) { + Int32 k2 = ((IConvertible) key).ToInt32(NumberFormatInfo.InvariantInfo); + + this.imageMap[k2] = imageSelector; + this.keysInOrder.Remove(k2); + this.keysInOrder.Add(k2); + } + + /// + /// Draw the flags + /// + /// + /// + public override void Render(Graphics g, Rectangle r) { + this.DrawBackground(g, r); + + IConvertible convertable = this.Aspect as IConvertible; + if (convertable == null) + return; + + r = this.ApplyCellPadding(r); + + Int32 v2 = convertable.ToInt32(NumberFormatInfo.InvariantInfo); + ArrayList images = new ArrayList(); + foreach (Int32 key in this.keysInOrder) { + if ((v2 & key) == key) { + Image image = this.GetImage(this.imageMap[key]); + if (image != null) + images.Add(image); + } + } + if (images.Count > 0) + this.DrawImages(g, r, images); + } + + /// + /// Do the actual work of hit testing. Subclasses should override this rather than HitTest() + /// + /// + /// + /// + /// + protected override void HandleHitTest(Graphics g, OlvListViewHitTestInfo hti, int x, int y) { + IConvertible convertable = this.Aspect as IConvertible; + if (convertable == null) + return; + + Int32 v2 = convertable.ToInt32(NumberFormatInfo.InvariantInfo); + + Point pt = this.Bounds.Location; + foreach (Int32 key in this.keysInOrder) { + if ((v2 & key) == key) { + Image image = this.GetImage(this.imageMap[key]); + if (image != null) { + Rectangle imageRect = new Rectangle(pt, image.Size); + if (imageRect.Contains(x, y)) { + hti.UserData = key; + return; + } + pt.X += (image.Width + this.Spacing); + } + } + } + } + + private List keysInOrder = new List(); + private Dictionary imageMap = new Dictionary(); + } + + /// + /// This renderer draws an image, a single line title, and then multi-line description + /// under the title. + /// + /// + /// This class works best with FullRowSelect = true. + /// It's not designed to work with cell editing -- it will work but will look odd. + /// + /// It's not RightToLeft friendly. + /// + /// + public class DescribedTaskRenderer : BaseRenderer, IFilterAwareRenderer + { + private readonly StringFormat noWrapStringFormat; + private readonly HighlightTextRenderer highlightTextRenderer = new HighlightTextRenderer(); + + /// + /// Create a DescribedTaskRenderer + /// + public DescribedTaskRenderer() { + this.noWrapStringFormat = new StringFormat(StringFormatFlags.NoWrap); + this.noWrapStringFormat.Trimming = StringTrimming.EllipsisCharacter; + this.noWrapStringFormat.Alignment = StringAlignment.Near; + this.noWrapStringFormat.LineAlignment = StringAlignment.Near; + this.highlightTextRenderer.CellVerticalAlignment = StringAlignment.Near; + } + + #region Configuration properties + + /// + /// Should text be rendered using GDI routines? This makes the text look more + /// like a native List view control. + /// + public override bool UseGdiTextRendering + { + get { return base.UseGdiTextRendering; } + set + { + base.UseGdiTextRendering = value; + this.highlightTextRenderer.UseGdiTextRendering = value; + } + } + + /// + /// Gets or set the font that will be used to draw the title of the task + /// + /// If this is null, the ListView's font will be used + [Category("ObjectListView"), + Description("The font that will be used to draw the title of the task"), + DefaultValue(null)] + public Font TitleFont { + get { return titleFont; } + set { titleFont = value; } + } + + private Font titleFont; + + /// + /// Return a font that has been set for the title or a reasonable default + /// + [Browsable(false)] + public Font TitleFontOrDefault { + get { return this.TitleFont ?? this.ListView.Font; } + } + + /// + /// Gets or set the color of the title of the task + /// + /// This color is used when the task is not selected or when the listview + /// has a translucent selection mechanism. + [Category("ObjectListView"), + Description("The color of the title"), + DefaultValue(typeof (Color), "")] + public Color TitleColor { + get { return titleColor; } + set { titleColor = value; } + } + + private Color titleColor; + + /// + /// Return the color of the title of the task or a reasonable default + /// + [Browsable(false)] + public Color TitleColorOrDefault { + get { + if (!this.ListItem.Enabled) + return this.SubItem.ForeColor; + if (this.IsItemSelected || this.TitleColor.IsEmpty) + return this.GetForegroundColor(); + + return this.TitleColor; + } + } + + /// + /// Gets or set the font that will be used to draw the description of the task + /// + /// If this is null, the ListView's font will be used + [Category("ObjectListView"), + Description("The font that will be used to draw the description of the task"), + DefaultValue(null)] + public Font DescriptionFont { + get { return descriptionFont; } + set { descriptionFont = value; } + } + + private Font descriptionFont; + + /// + /// Return a font that has been set for the title or a reasonable default + /// + [Browsable(false)] + public Font DescriptionFontOrDefault { + get { return this.DescriptionFont ?? this.ListView.Font; } + } + + /// + /// Gets or set the color of the description of the task + /// + /// This color is used when the task is not selected or when the listview + /// has a translucent selection mechanism. + [Category("ObjectListView"), + Description("The color of the description"), + DefaultValue(typeof (Color), "")] + public Color DescriptionColor { + get { return descriptionColor; } + set { descriptionColor = value; } + } + private Color descriptionColor = Color.Empty; + + /// + /// Return the color of the description of the task or a reasonable default + /// + [Browsable(false)] + public Color DescriptionColorOrDefault { + get { + if (!this.ListItem.Enabled) + return this.SubItem.ForeColor; + if (this.IsItemSelected && !this.ListView.UseTranslucentSelection) + return this.GetForegroundColor(); + return this.DescriptionColor.IsEmpty ? defaultDescriptionColor : this.DescriptionColor; + } + } + private static Color defaultDescriptionColor = Color.FromArgb(45, 46, 49); + + /// + /// Gets or sets the number of pixels that will be left between the image and the text + /// + [Category("ObjectListView"), + Description("The number of pixels that will be left between the image and the text"), + DefaultValue(4)] + public int ImageTextSpace + { + get { return imageTextSpace; } + set { imageTextSpace = value; } + } + private int imageTextSpace = 4; + + /// + /// Gets or sets the number of pixels that will be left between the title and the description + /// + [Category("ObjectListView"), + Description("The number of pixels that that will be left between the title and the description"), + DefaultValue(2)] + public int TitleDescriptionSpace + { + get { return titleDescriptionSpace; } + set { titleDescriptionSpace = value; } + } + private int titleDescriptionSpace = 2; + + /// + /// Gets or sets the name of the aspect of the model object that contains the task description + /// + [Category("ObjectListView"), + Description("The name of the aspect of the model object that contains the task description"), + DefaultValue(null)] + public string DescriptionAspectName { + get { return descriptionAspectName; } + set { descriptionAspectName = value; } + } + private string descriptionAspectName; + + #endregion + + #region Text highlighting + + /// + /// Gets or sets the filter that is filtering the ObjectListView and for + /// which this renderer should highlight text + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ITextMatchFilter Filter + { + get { return this.highlightTextRenderer.Filter; } + set { this.highlightTextRenderer.Filter = value; } + } + + /// + /// When a filter changes, keep track of the text matching filters + /// + IModelFilter IFilterAwareRenderer.Filter { + get { return this.Filter; } + set { this.highlightTextRenderer.RegisterNewFilter(value); } + } + + #endregion + + #region Calculating + + /// + /// Fetch the description from the model class + /// + /// + /// + public virtual string GetDescription(object model) { + if (String.IsNullOrEmpty(this.DescriptionAspectName)) + return String.Empty; + + if (this.descriptionGetter == null) + this.descriptionGetter = new Munger(this.DescriptionAspectName); + + return this.descriptionGetter.GetValue(model) as string; + } + private Munger descriptionGetter; + + #endregion + + #region Rendering + + /// + /// + /// + /// + /// + /// + public override void ConfigureSubItem(DrawListViewSubItemEventArgs e, Rectangle cellBounds, object model) { + base.ConfigureSubItem(e, cellBounds, model); + this.highlightTextRenderer.ConfigureSubItem(e, cellBounds, model); + } + + /// + /// Draw our item + /// + /// + /// + public override void Render(Graphics g, Rectangle r) { + this.DrawBackground(g, r); + r = this.ApplyCellPadding(r); + this.DrawDescribedTask(g, r, this.GetText(), this.GetDescription(this.RowObject), this.GetImageSelector()); + } + + /// + /// Draw the task + /// + /// + /// + /// + /// + /// + protected virtual void DrawDescribedTask(Graphics g, Rectangle r, string title, string description, object imageSelector) { + + //Debug.WriteLine(String.Format("DrawDescribedTask({0}, {1}, {2}, {3})", r, title, description, imageSelector)); + + // Draw the image if one's been given + Rectangle textBounds = r; + if (imageSelector != null) { + int imageWidth = this.DrawImage(g, r, imageSelector); + int gapToText = imageWidth + this.ImageTextSpace; + textBounds.X += gapToText; + textBounds.Width -= gapToText; + } + + // Draw the title + if (!String.IsNullOrEmpty(title)) { + using (SolidBrush b = new SolidBrush(this.TitleColorOrDefault)) { + this.highlightTextRenderer.CanWrap = false; + this.highlightTextRenderer.Font = this.TitleFontOrDefault; + this.highlightTextRenderer.TextBrush = b; + this.highlightTextRenderer.DrawText(g, textBounds, title); + } + + // How tall was the title? + SizeF size = g.MeasureString(title, this.TitleFontOrDefault, textBounds.Width, this.noWrapStringFormat); + int pixelsToDescription = this.TitleDescriptionSpace + (int)size.Height; + textBounds.Y += pixelsToDescription; + textBounds.Height -= pixelsToDescription; + } + + // Draw the description + if (!String.IsNullOrEmpty(description)) { + using (SolidBrush b = new SolidBrush(this.DescriptionColorOrDefault)) { + this.highlightTextRenderer.CanWrap = true; + this.highlightTextRenderer.Font = this.DescriptionFontOrDefault; + this.highlightTextRenderer.TextBrush = b; + this.highlightTextRenderer.DrawText(g, textBounds, description); + } + } + + //g.DrawRectangle(Pens.OrangeRed, r); + } + + #endregion + + #region Hit Testing + + /// + /// Handle the HitTest request + /// + /// + /// + /// + /// + protected override void HandleHitTest(Graphics g, OlvListViewHitTestInfo hti, int x, int y) { + if (this.Bounds.Contains(x, y)) + hti.HitTestLocation = HitTestLocation.Text; + } + + #endregion + } + + /// + /// This renderer draws a functioning button in its cell + /// + public class ColumnButtonRenderer : BaseRenderer { + + #region Properties + + /// + /// Gets or sets how each button will be sized + /// + [Category("ObjectListView"), + Description("How each button will be sized"), + DefaultValue(OLVColumn.ButtonSizingMode.TextBounds)] + public OLVColumn.ButtonSizingMode SizingMode + { + get { return this.sizingMode; } + set { this.sizingMode = value; } + } + private OLVColumn.ButtonSizingMode sizingMode = OLVColumn.ButtonSizingMode.TextBounds; + + /// + /// Gets or sets the size of the button when the SizingMode is FixedBounds + /// + /// If this is not set, the bounds of the cell will be used + [Category("ObjectListView"), + Description("The size of the button when the SizingMode is FixedBounds"), + DefaultValue(null)] + public Size? ButtonSize + { + get { return this.buttonSize; } + set { this.buttonSize = value; } + } + private Size? buttonSize; + + /// + /// Gets or sets the extra space that surrounds the cell when the SizingMode is TextBounds + /// + [Category("ObjectListView"), + Description("The extra space that surrounds the cell when the SizingMode is TextBounds")] + public Size? ButtonPadding + { + get { return this.buttonPadding; } + set { this.buttonPadding = value; } + } + private Size? buttonPadding = new Size(10, 10); + + private Size ButtonPaddingOrDefault { + get { return this.ButtonPadding ?? new Size(10, 10); } + } + + /// + /// Gets or sets the maximum width that a button can occupy. + /// -1 means there is no maximum width. + /// + /// This is only considered when the SizingMode is TextBounds + [Category("ObjectListView"), + Description("The maximum width that a button can occupy when the SizingMode is TextBounds"), + DefaultValue(-1)] + public int MaxButtonWidth + { + get { return this.maxButtonWidth; } + set { this.maxButtonWidth = value; } + } + private int maxButtonWidth = -1; + + /// + /// Gets or sets the minimum width that a button can occupy. + /// -1 means there is no minimum width. + /// + /// This is only considered when the SizingMode is TextBounds + [Category("ObjectListView"), + Description("The minimum width that a button can be when the SizingMode is TextBounds"), + DefaultValue(-1)] + public int MinButtonWidth { + get { return this.minButtonWidth; } + set { this.minButtonWidth = value; } + } + private int minButtonWidth = -1; + + #endregion + + #region Rendering + + /// + /// Calculate the size of the contents + /// + /// + /// + /// + protected override Size CalculateContentSize(Graphics g, Rectangle r) { + if (this.SizingMode == OLVColumn.ButtonSizingMode.CellBounds) + return r.Size; + + if (this.SizingMode == OLVColumn.ButtonSizingMode.FixedBounds) + return this.ButtonSize ?? r.Size; + + // Ok, SizingMode must be TextBounds. So figure out the size of the text + Size textSize = this.CalculateTextSize(g, this.GetText(), r.Width); + + // Allow for padding and max width + textSize.Height += this.ButtonPaddingOrDefault.Height * 2; + textSize.Width += this.ButtonPaddingOrDefault.Width * 2; + if (this.MaxButtonWidth != -1 && textSize.Width > this.MaxButtonWidth) + textSize.Width = this.MaxButtonWidth; + if (textSize.Width < this.MinButtonWidth) + textSize.Width = this.MinButtonWidth; + + return textSize; + } + + /// + /// Draw the button + /// + /// + /// + protected override void DrawImageAndText(Graphics g, Rectangle r) { + TextFormatFlags textFormatFlags = TextFormatFlags.HorizontalCenter | + TextFormatFlags.VerticalCenter | + TextFormatFlags.EndEllipsis | + TextFormatFlags.NoPadding | + TextFormatFlags.SingleLine | + TextFormatFlags.PreserveGraphicsTranslateTransform; + if (this.ListView.RightToLeftLayout) + textFormatFlags |= TextFormatFlags.RightToLeft; + + string buttonText = GetText(); + if (!String.IsNullOrEmpty(buttonText)) + ButtonRenderer.DrawButton(g, r, buttonText, this.Font, textFormatFlags, false, CalculatePushButtonState()); + } + + /// + /// What part of the control is under the given point? + /// + /// + /// + /// + /// + /// + protected override void StandardHitTest(Graphics g, OlvListViewHitTestInfo hti, Rectangle bounds, int x, int y) { + Rectangle r = ApplyCellPadding(bounds); + if (r.Contains(x, y)) + hti.HitTestLocation = HitTestLocation.Button; + } + + /// + /// What is the state of the button? + /// + /// + protected PushButtonState CalculatePushButtonState() { + if (!this.ListItem.Enabled && !this.Column.EnableButtonWhenItemIsDisabled) + return PushButtonState.Disabled; + + if (this.IsButtonHot) + return ObjectListView.IsLeftMouseDown ? PushButtonState.Pressed : PushButtonState.Hot; + + return PushButtonState.Normal; + } + + /// + /// Is the mouse over the button? + /// + protected bool IsButtonHot { + get { + return this.IsCellHot && this.ListView.HotCellHitLocation == HitTestLocation.Button; + } + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Rendering/Styles.cs b/VG Music Studio - WinForms/ObjectListView/Rendering/Styles.cs new file mode 100644 index 0000000..8498193 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Rendering/Styles.cs @@ -0,0 +1,400 @@ +/* + * Styles - A style is a group of formatting attributes that can be applied to a row or a cell + * + * Author: Phillip Piper + * Date: 29/07/2009 23:09 + * + * Change log: + * v2.4 + * 2010-03-23 JPP - Added HeaderFormatStyle and support + * v2.3 + * 2009-08-15 JPP - Added Decoration and Overlay properties to HotItemStyle + * 2009-07-29 JPP - Initial version + * + * To do: + * - These should be more generally available. It should be possible to do something like this: + * this.olv.GetItem(i).Style = new ItemStyle(); + * this.olv.GetItem(i).GetSubItem(j).Style = new CellStyle(); + * + * Copyright (C) 2009-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// The common interface supported by all style objects + /// + public interface IItemStyle + { + /// + /// Gets or set the font that will be used by this style + /// + Font Font { get; set; } + + /// + /// Gets or set the font style + /// + FontStyle FontStyle { get; set; } + + /// + /// Gets or sets the ForeColor + /// + Color ForeColor { get; set; } + + /// + /// Gets or sets the BackColor + /// + Color BackColor { get; set; } + } + + /// + /// Basic implementation of IItemStyle + /// + public class SimpleItemStyle : System.ComponentModel.Component, IItemStyle + { + /// + /// Gets or sets the font that will be applied by this style + /// + [DefaultValue(null)] + public Font Font + { + get { return this.font; } + set { this.font = value; } + } + + private Font font; + + /// + /// Gets or sets the style of font that will be applied by this style + /// + [DefaultValue(FontStyle.Regular)] + public FontStyle FontStyle + { + get { return this.fontStyle; } + set { this.fontStyle = value; } + } + + private FontStyle fontStyle; + + /// + /// Gets or sets the color of the text that will be applied by this style + /// + [DefaultValue(typeof (Color), "")] + public Color ForeColor + { + get { return this.foreColor; } + set { this.foreColor = value; } + } + + private Color foreColor; + + /// + /// Gets or sets the background color that will be applied by this style + /// + [DefaultValue(typeof (Color), "")] + public Color BackColor + { + get { return this.backColor; } + set { this.backColor = value; } + } + + private Color backColor; + } + + + /// + /// Instances of this class specify how should "hot items" (non-selected + /// rows under the cursor) be rendered. + /// + public class HotItemStyle : SimpleItemStyle + { + /// + /// Gets or sets the overlay that should be drawn as part of the hot item + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IOverlay Overlay { + get { return this.overlay; } + set { this.overlay = value; } + } + private IOverlay overlay; + + /// + /// Gets or sets the decoration that should be drawn as part of the hot item + /// + /// A decoration is different from an overlay in that an decoration + /// scrolls with the listview contents, whilst an overlay does not. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IDecoration Decoration { + get { return this.decoration; } + set { this.decoration = value; } + } + private IDecoration decoration; + } + + /// + /// This class defines how a cell should be formatted + /// + [TypeConverter(typeof(ExpandableObjectConverter))] + public class CellStyle : IItemStyle + { + /// + /// Gets or sets the font that will be applied by this style + /// + public Font Font { + get { return this.font; } + set { this.font = value; } + } + private Font font; + + /// + /// Gets or sets the style of font that will be applied by this style + /// + [DefaultValue(FontStyle.Regular)] + public FontStyle FontStyle { + get { return this.fontStyle; } + set { this.fontStyle = value; } + } + private FontStyle fontStyle; + + /// + /// Gets or sets the color of the text that will be applied by this style + /// + [DefaultValue(typeof(Color), "")] + public Color ForeColor { + get { return this.foreColor; } + set { this.foreColor = value; } + } + private Color foreColor; + + /// + /// Gets or sets the background color that will be applied by this style + /// + [DefaultValue(typeof(Color), "")] + public Color BackColor { + get { return this.backColor; } + set { this.backColor = value; } + } + private Color backColor; + } + + /// + /// Instances of this class describe how hyperlinks will appear + /// + public class HyperlinkStyle : System.ComponentModel.Component + { + /// + /// Create a HyperlinkStyle + /// + public HyperlinkStyle() { + this.Normal = new CellStyle(); + this.Normal.ForeColor = Color.Blue; + this.Over = new CellStyle(); + this.Over.FontStyle = FontStyle.Underline; + this.Visited = new CellStyle(); + this.Visited.ForeColor = Color.Purple; + this.OverCursor = Cursors.Hand; + } + + /// + /// What sort of formatting should be applied to hyperlinks in their normal state? + /// + [Category("Appearance"), + Description("How should hyperlinks be drawn")] + public CellStyle Normal { + get { return this.normalStyle; } + set { this.normalStyle = value; } + } + private CellStyle normalStyle; + + /// + /// What sort of formatting should be applied to hyperlinks when the mouse is over them? + /// + [Category("Appearance"), + Description("How should hyperlinks be drawn when the mouse is over them?")] + public CellStyle Over { + get { return this.overStyle; } + set { this.overStyle = value; } + } + private CellStyle overStyle; + + /// + /// What sort of formatting should be applied to hyperlinks after they have been clicked? + /// + [Category("Appearance"), + Description("How should hyperlinks be drawn after they have been clicked")] + public CellStyle Visited { + get { return this.visitedStyle; } + set { this.visitedStyle = value; } + } + private CellStyle visitedStyle; + + /// + /// Gets or sets the cursor that should be shown when the mouse is over a hyperlink. + /// + [Category("Appearance"), + Description("What cursor should be shown when the mouse is over a link?")] + public Cursor OverCursor { + get { return this.overCursor; } + set { this.overCursor = value; } + } + private Cursor overCursor; + } + + /// + /// Instances of this class control one the styling of one particular state + /// (normal, hot, pressed) of a header control + /// + [TypeConverter(typeof(ExpandableObjectConverter))] + public class HeaderStateStyle + { + /// + /// Gets or sets the font that will be applied by this style + /// + [DefaultValue(null)] + public Font Font { + get { return this.font; } + set { this.font = value; } + } + private Font font; + + /// + /// Gets or sets the color of the text that will be applied by this style + /// + [DefaultValue(typeof(Color), "")] + public Color ForeColor { + get { return this.foreColor; } + set { this.foreColor = value; } + } + private Color foreColor; + + /// + /// Gets or sets the background color that will be applied by this style + /// + [DefaultValue(typeof(Color), "")] + public Color BackColor { + get { return this.backColor; } + set { this.backColor = value; } + } + private Color backColor; + + /// + /// Gets or sets the color in which a frame will be drawn around the header for this column + /// + [DefaultValue(typeof(Color), "")] + public Color FrameColor { + get { return this.frameColor; } + set { this.frameColor = value; } + } + private Color frameColor; + + /// + /// Gets or sets the width of the frame that will be drawn around the header for this column + /// + [DefaultValue(0.0f)] + public float FrameWidth { + get { return this.frameWidth; } + set { this.frameWidth = value; } + } + private float frameWidth; + } + + /// + /// This class defines how a header should be formatted in its various states. + /// + public class HeaderFormatStyle : System.ComponentModel.Component + { + /// + /// Create a new HeaderFormatStyle + /// + public HeaderFormatStyle() { + this.Hot = new HeaderStateStyle(); + this.Normal = new HeaderStateStyle(); + this.Pressed = new HeaderStateStyle(); + } + + /// + /// What sort of formatting should be applied to a column header when the mouse is over it? + /// + [Category("Appearance"), + Description("How should the header be drawn when the mouse is over it?")] + public HeaderStateStyle Hot { + get { return this.hotStyle; } + set { this.hotStyle = value; } + } + private HeaderStateStyle hotStyle; + + /// + /// What sort of formatting should be applied to a column header in its normal state? + /// + [Category("Appearance"), + Description("How should a column header normally be drawn")] + public HeaderStateStyle Normal { + get { return this.normalStyle; } + set { this.normalStyle = value; } + } + private HeaderStateStyle normalStyle; + + /// + /// What sort of formatting should be applied to a column header when pressed? + /// + [Category("Appearance"), + Description("How should a column header be drawn when it is pressed")] + public HeaderStateStyle Pressed { + get { return this.pressedStyle; } + set { this.pressedStyle = value; } + } + private HeaderStateStyle pressedStyle; + + /// + /// Set the font for all three states + /// + /// + public void SetFont(Font font) { + this.Normal.Font = font; + this.Hot.Font = font; + this.Pressed.Font = font; + } + + /// + /// Set the fore color for all three states + /// + /// + public void SetForeColor(Color color) { + this.Normal.ForeColor = color; + this.Hot.ForeColor = color; + this.Pressed.ForeColor = color; + } + + /// + /// Set the back color for all three states + /// + /// + public void SetBackColor(Color color) { + this.Normal.BackColor = color; + this.Hot.BackColor = color; + this.Pressed.BackColor = color; + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Rendering/TreeRenderer.cs b/VG Music Studio - WinForms/ObjectListView/Rendering/TreeRenderer.cs new file mode 100644 index 0000000..099391f --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Rendering/TreeRenderer.cs @@ -0,0 +1,309 @@ +/* + * TreeRenderer - Draw the major column in a TreeListView + * + * Author: Phillip Piper + * Date: 27/06/2015 + * + * Change log: + * 2016-07-17 JPP - Added TreeRenderer.UseTriangles and IsShowGlyphs + * 2015-06-27 JPP - Split out from TreeListView.cs + * + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; +using System.Drawing.Drawing2D; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + public partial class TreeListView { + /// + /// This class handles drawing the tree structure of the primary column. + /// + public class TreeRenderer : HighlightTextRenderer { + /// + /// Create a TreeRenderer + /// + public TreeRenderer() { + this.LinePen = new Pen(Color.Blue, 1.0f); + this.LinePen.DashStyle = DashStyle.Dot; + } + + #region Configuration properties + + /// + /// Should the renderer draw glyphs at the expansion points? + /// + /// The expansion points will still function to expand/collapse even if this is false. + public bool IsShowGlyphs + { + get { return isShowGlyphs; } + set { isShowGlyphs = value; } + } + private bool isShowGlyphs = true; + + /// + /// Should the renderer draw lines connecting siblings? + /// + public bool IsShowLines + { + get { return isShowLines; } + set { isShowLines = value; } + } + private bool isShowLines = true; + + /// + /// Return the pen that will be used to draw the lines between branches + /// + public Pen LinePen + { + get { return linePen; } + set { linePen = value; } + } + private Pen linePen; + + /// + /// Should the renderer draw triangles as the expansion glyphs? + /// + /// + /// This looks best with ShowLines = false + /// + public bool UseTriangles + { + get { return useTriangles; } + set { useTriangles = value; } + } + private bool useTriangles = false; + + #endregion + + /// + /// Return the branch that the renderer is currently drawing. + /// + private Branch Branch { + get { + return this.TreeListView.TreeModel.GetBranch(this.RowObject); + } + } + + /// + /// Return the TreeListView for which the renderer is being used. + /// + public TreeListView TreeListView { + get { + return (TreeListView)this.ListView; + } + } + + /// + /// How many pixels will be reserved for each level of indentation? + /// + public static int PIXELS_PER_LEVEL = 16 + 1; + + /// + /// The real work of drawing the tree is done in this method + /// + /// + /// + public override void Render(System.Drawing.Graphics g, System.Drawing.Rectangle r) { + this.DrawBackground(g, r); + + Branch br = this.Branch; + + Rectangle paddedRectangle = this.ApplyCellPadding(r); + + Rectangle expandGlyphRectangle = paddedRectangle; + expandGlyphRectangle.Offset((br.Level - 1) * PIXELS_PER_LEVEL, 0); + expandGlyphRectangle.Width = PIXELS_PER_LEVEL; + expandGlyphRectangle.Height = PIXELS_PER_LEVEL; + expandGlyphRectangle.Y = this.AlignVertically(paddedRectangle, expandGlyphRectangle); + int expandGlyphRectangleMidVertical = expandGlyphRectangle.Y + (expandGlyphRectangle.Height/2); + + if (this.IsShowLines) + this.DrawLines(g, r, this.LinePen, br, expandGlyphRectangleMidVertical); + + if (br.CanExpand && this.IsShowGlyphs) + this.DrawExpansionGlyph(g, expandGlyphRectangle, br.IsExpanded); + + int indent = br.Level * PIXELS_PER_LEVEL; + paddedRectangle.Offset(indent, 0); + paddedRectangle.Width -= indent; + + this.DrawImageAndText(g, paddedRectangle); + } + + /// + /// Draw the expansion indicator + /// + /// + /// + /// + protected virtual void DrawExpansionGlyph(Graphics g, Rectangle r, bool isExpanded) { + if (this.UseStyles) { + this.DrawExpansionGlyphStyled(g, r, isExpanded); + } else { + this.DrawExpansionGlyphManual(g, r, isExpanded); + } + } + + /// + /// Gets whether or not we should render using styles + /// + protected virtual bool UseStyles { + get { + return !this.IsPrinting && Application.RenderWithVisualStyles; + } + } + + /// + /// Draw the expansion indicator using styles + /// + /// + /// + /// + protected virtual void DrawExpansionGlyphStyled(Graphics g, Rectangle r, bool isExpanded) { + if (this.UseTriangles && this.IsShowLines) { + using (SolidBrush b = new SolidBrush(GetBackgroundColor())) { + Rectangle r2 = r; + r2.Inflate(-2, -2); + g.FillRectangle(b, r2); + } + } + + VisualStyleRenderer renderer = new VisualStyleRenderer(DecideVisualElement(isExpanded)); + renderer.DrawBackground(g, r); + } + + private VisualStyleElement DecideVisualElement(bool isExpanded) { + string klass = this.UseTriangles ? "Explorer::TreeView" : "TREEVIEW"; + int part = this.UseTriangles && this.IsExpansionHot ? 4 : 2; + int state = isExpanded ? 2 : 1; + return VisualStyleElement.CreateElement(klass, part, state); + } + + /// + /// Is the mouse over a checkbox in this cell? + /// + protected bool IsExpansionHot { + get { return this.IsCellHot && this.ListView.HotCellHitLocation == HitTestLocation.ExpandButton; } + } + + /// + /// Draw the expansion indicator without using styles + /// + /// + /// + /// + protected virtual void DrawExpansionGlyphManual(Graphics g, Rectangle r, bool isExpanded) { + int h = 8; + int w = 8; + int x = r.X + 4; + int y = r.Y + (r.Height / 2) - 4; + + g.DrawRectangle(new Pen(SystemBrushes.ControlDark), x, y, w, h); + g.FillRectangle(Brushes.White, x + 1, y + 1, w - 1, h - 1); + g.DrawLine(Pens.Black, x + 2, y + 4, x + w - 2, y + 4); + + if (!isExpanded) + g.DrawLine(Pens.Black, x + 4, y + 2, x + 4, y + h - 2); + } + + /// + /// Draw the lines of the tree + /// + /// + /// + /// + /// + /// + protected virtual void DrawLines(Graphics g, Rectangle r, Pen p, Branch br, int glyphMidVertical) { + Rectangle r2 = r; + r2.Width = PIXELS_PER_LEVEL; + + // Vertical lines have to start on even points, otherwise the dotted line looks wrong. + // This is only needed if pen is dotted. + int top = r2.Top; + //if (p.DashStyle == DashStyle.Dot && (top & 1) == 0) + // top += 1; + + // Draw lines for ancestors + int midX; + IList ancestors = br.Ancestors; + foreach (Branch ancestor in ancestors) { + if (!ancestor.IsLastChild && !ancestor.IsOnlyBranch) { + midX = r2.Left + r2.Width / 2; + g.DrawLine(p, midX, top, midX, r2.Bottom); + } + r2.Offset(PIXELS_PER_LEVEL, 0); + } + + // Draw lines for this branch + midX = r2.Left + r2.Width / 2; + + // Horizontal line first + g.DrawLine(p, midX, glyphMidVertical, r2.Right, glyphMidVertical); + + // Vertical line second + if (br.IsFirstBranch) { + if (!br.IsLastChild && !br.IsOnlyBranch) + g.DrawLine(p, midX, glyphMidVertical, midX, r2.Bottom); + } else { + if (br.IsLastChild) + g.DrawLine(p, midX, top, midX, glyphMidVertical); + else + g.DrawLine(p, midX, top, midX, r2.Bottom); + } + } + + /// + /// Do the hit test + /// + /// + /// + /// + /// + protected override void HandleHitTest(Graphics g, OlvListViewHitTestInfo hti, int x, int y) { + Branch br = this.Branch; + + Rectangle r = this.ApplyCellPadding(this.Bounds); + if (br.CanExpand) { + r.Offset((br.Level - 1) * PIXELS_PER_LEVEL, 0); + r.Width = PIXELS_PER_LEVEL; + if (r.Contains(x, y)) { + hti.HitTestLocation = HitTestLocation.ExpandButton; + return; + } + } + + r = this.Bounds; + int indent = br.Level * PIXELS_PER_LEVEL; + r.X += indent; + r.Width -= indent; + + // Ignore events in the indent zone + if (x < r.Left) { + hti.HitTestLocation = HitTestLocation.Nothing; + } else { + this.StandardHitTest(g, hti, r, x, y); + } + } + + /// + /// Calculate the edit rect + /// + /// + /// + /// + /// + /// + /// + protected override Rectangle HandleGetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) { + return this.StandardGetEditRectangle(g, cellBounds, preferredSize); + } + } + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/Resources/clear-filter.png b/VG Music Studio - WinForms/ObjectListView/Resources/clear-filter.png new file mode 100644 index 0000000000000000000000000000000000000000..2ddf7073b0c3de5791448e7a8effdf05f2c25e77 GIT binary patch literal 1381 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstPBjy3;{kN zu3$w13IYNSK;YpJ;1K`>2|$pMP*6~?L4aXGMZtoMfCCZ?4-^DGXfXWOVECXR@E?dQ z1pYfH{P$4!A7F4Hz~MoF!-oim{|OHNGaUXG1pKcE_)wAXABY+fK6DiP? zKrmy%f&~jUtk?hoJ2qU{vEjpnh7U6u{?BN50A#ON@L|J(4?8v-*m2;)feiRR|N(hXMsm#F#`kNArNL1)$nQn3QCr^MwA5Sr_kFdQ zoeld1Cq2lBJ3DomuG-I@|6fF&{HffzJymf>EyJTdrCUYrF&?;bSL@WRXdYXZ8L_8& z<{G4lv8T+tYq*RrCy!C_(dAdCziTk7Os_f-vdCUx*~%@3S$j_~ZHe|x2$B30aO11n q@yq4if(I79Qcsd^-jTfMt#}vzTCZ0dwIzWLW$<+Mb6Mw<&;$TKY4H>Q literal 0 HcmV?d00001 diff --git a/VG Music Studio - WinForms/ObjectListView/Resources/coffee.jpg b/VG Music Studio - WinForms/ObjectListView/Resources/coffee.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6032d83fc7a5e1db6b76067d8de98e2bce6619c4 GIT binary patch literal 73464 zcmeFY1yG#Lwl@0W7TkinTX2T}!G;Wk!96&GyGsN@u;A|Q5Zoa_gS!R@5+Jy{-O0Dl z+50=E&i~(g{#$jcZk;sD@O1a;?zNs?Q>=G&&*R+V3V;KWQA? z3AF$KFc@G3000$$4~7DWFa!sy@a&KSD6qdzV0A{=3n1Tty`la_f65;Q{xI-|fjD!z zOIcVl0~SdbyoB02z~caboxQ6wL|&R&TSu1~Wds&`paIALHvkx$x;TQ>T1GD`l^A9Zf8=KkLo55t%VA#>j&g?htg5i(uZsstI zpajDo+@a>zXewZed{|Y6Wu-TXR=ej#tK}Hpb3o z)G`isj>h(0z&}j>JqW=2O)WKyV*zeq0RavkP8j?DEdSfie@gxD!SAj8hs3GsUt0!8 zH1w~we~tZDn?nu&2*GGUi27HXNiqO51OdR)g@3itX8{0qAOO^l{>>hu-|k}N>gp)M z#pU7Q!3i}t<^0X(Kg<8Ez&|Db*WhpKasFQKpWIQuFt;#vvvsBZO{%Gbt%I90wTq*% zsW~;rfA-=3u-9nL(DDAoz3lGOKHNK3~Fx)+iiO@s4LXLo*HWZzuMvd!(o5Z z0RaBt)UYP}ya#~5h!emWB>)iihX6!;3;;nt1J(og&s}?orV0H1*lAI1{ln@1n(D8Q zGVB8r39J2nNcm@Rfm%}kuE;>tsZHIS-T%>n`1=nMfDT~89%o1ZN`MYv1~>p7KoAfG zo&&M~2v7mk0Uf{)Fa@jtJHQ$60DOVBzy}}wpt6ciE^HWYCb6%=C>SCkJZ$tYzg9Vinh8z@(( z7^qaJJg9Q0x~TT3?@$v^OHkWUCs4OgZ_#kj7|=w}RM5=OywD=i^3a;mM$tCVZqaek znb5`2)zPid-=Zg>SD^QxFQT7fU|`T<2xF*XKr!B6Bw>8U_=d5Has34E3F{M?C;Cs^ zo_u;z_~grz`6s8Cn3#;1&oOl|T`@ml7Gri{E@NI{;bU=PDPoyn1z@FO)niRy9bltj zGhj<$8)AE7Ct%lLk7DoPpy4p$$lw^`_~WGFG~vwRoa5r-a^tGt+Tn)dmf-f|{=!4S zW5koiGs6qS%f;)$TgOMhr^A=QH^qOCpNHRrzd?XZz(k-(U`-H4P);yHa72hl$VaG2 z=t-DL*haWQgh0edq)22-6iHM~G(&VlOhGI~Y)%|PTtPfReEF2@>GP-NPeY$pKAnDg zLqbg=OJYM3P0~QJM2bktN~%igL7G9@L%K&sKqg9NN)}31O}0P|PtHmXA@?EAB_AR` zr=X;er*Nc5rs$&Bqa>nyPH9aUN7+ufO@&V-K?S9Xqw1jAp(do3qPC?@qVA?Xq9LbI zpmC$grWv8RrDdYkpnXSMNxMXcPA5cXK^I5YNq0m~MGvO;r7xvlU_fIKVz6RJWawkK zWMpF0Vhm<%VEo1Olu41vo2is(kr|U&lG%wlhk1$xnMH`jh9#Y4j1``hpVf*rm35R2 zo=t!a%9hSH&W^+`%x=$~!#>OLghPtMouiavm6M3`CFeWN2F?R6dM;hAXs$l4hiClH zY@g*l`_7HW4dQ;s-OPQ$!^&gIlgcy2i^(g;8^GJhd(6kiXU>|pzv2}4;abxjp@f8VbiPsWY5-XB4lE#ubl55ZDo|`={c)lgYB4sUACUqeFOxjtx zPWnQ6*Mo=@ru}r&lei z$f~bYQ&l%1+z?+#pBlcJky?q`g}StQr22OaW(_xuPEBl0ea&LcORX1LFLfj(8;+LhkWvu14m84aI)gDv^ zngTtxRrzO+%XDYSXC)v~R$L$!Nt*JO`pZ)4x>KBw2h zxxfX^#lWS}l>in=4Y@J91-Y%dOSq@HUwLSH)Og~0+ItRpv3iAgZF$Ri=ldY~nEG`3 z()hmhUGwR+|Mc|vj^&t76 z(hpc4oIlJ4iw0+YMEnT-I1<7ek{t37Y8E;W_AD$R>@M6id?125A}QkWlf|czNP)q6_{>b>fZ8mt;t8g&}Sn?OyS&7#e9Eu1Zdtu(EvZG>%+ z?da`+?T;Pa9p_&hzwCXr{JP$0)cL(jyKAajwR@yTspng-TyIyORA0w8@o%mDBK=JR zLIVwh0)zEK{6lra{KIu40weXKf}@RN!ehd&ssna%y0x1B#;a9_Cn{$>$rF?0!MDPfs>Ip+tAgFbz5$GY)5ZrW7l!_cJISJ?ta<<^Fi(5^TVMd?W2uj=i|qd z@Kci0!ZW_J&huC2%NKSRx0j(;Bv(b(Lf3saS~pv_9(O2rN%t)G%@0Zsi;s4XkAGr; zKe51{Sl~}A@Fy1d6AS!_1^&bWfARu<@&bSI0)O%XfARu<@&bSI0{?UK0*|Zce*bp* zWADOgZ02BM&S~mk$K`44$i>b1j0+Hx^n{(q*qFOgo0wZd?Zuf6TRNDip=RPtTKwQ= zU`H8qE2x6Ev$>kLvbw3ajj6C1lcWSDx|pYkr=6pnxvMd?r=6|6i-@NcQy9puy>*R zw+UXrjw79+j(?ro{GQO*#KFx~oYu_^Y9?Y~Y{740X2#E9V$5sC!Od-E%wcTC%gtfN z%WEdc&n?7b!EHkO?+V&E{&V^Nk_y;*Fq1@NoXw40&0oM?62DJyIiB%x@IF)j_fuUl zuKy$>#`RmNzbW|-v;WpW{r^U5Fx1r9!NtMC^_7)_tHa+7u>1F(zxLDdclTdA3NzzB z4ifooi|c=*M*{&xfZ3j_Z$8Md(!FgE_(4PZV& z{r^Gvv&eso%Yaoe@pm}cm4m2u78D+=Jv4o(E}DJK7I#e zet%!@Uj-g^_v3d#L_mN?Kte=9LPA7DL_$VEMnXbCMnptLLq`R6v7uzksOJ*z&kEh}=kcQmV#B$oRBQZ=c_-FTiA}0IiRc;M<;XlP08bELi{K*Q0+PVpBd~%9yupD7o}usB;B3#c zTUs+baIb-ICgn0L$Z4^QyGQ^0#twgETIpC(a$+?It^T>e{ugR;a^=?$eW+`n> zE|I6+tg-K7p=wIMT9Q}7f#jB)V0K^LBhc9%@U>V?F6Z*}ltF!Dq?mMSFG3)`J55Gh zoNrAx5n6(wq_-;qL@^3J5R22!9TzOc`FDJ|&6ww+rsx9Iy7uf#VCjO=LAe>v) z45^utnql1_v&XK7n@n-F?7FgHtRYyMSgHIGU?+=wzrd=jm*BKKs?Ql;#NRV;lX_dC z0hb-Xb+<`-&lvFY<`F<*``~#m8M9z&sB$EM5zje%tXq3G4j%iKQ;RnFAa-LzzT{BI znpi{X;ZP_zYuc1`Cg-*kSAO)Mq$jgcS0?o=AHeO1ps?}1hI8J!)HvrfOU-NfW$}y5K($98 znRjzTUE>j$f(>I0&caPg3DR;ndjuGWgxzBrAywkC1=Bga3I#g??O%scT6MT;lU;Kv zA1ZH^TO|h89sxp&j{ATE;S58oB6Pi^0l!*IPBYcw#mMt$!iz|q)mK`NfYyCp;=CPW zMQO)HaT81q^5ubi!qHyBCtMpkMLiBlDp}M!z>ubbH;z2pj(Xu&BCFYaJ`-rJ`fe-Iu%dmhAO~fu?jq!iUm_ zV9tgtSK9sEOqlf?w^#mY^EDc1bvBnt2v>df8SS}RPDYX|o0@0Jft1!A7moma-Husq zz>~>*t+^~~asG&p)@GOTJROMHR9&ifS|{H$6sz%2-6pDCl~s#QmpL?2g(t_{+NZc!)>lJ*zat}GJ4uNhLn*A8OmI!$f6P@mzNPQ8OJ?>$ijP@fBIR)YUX>E#e@_lsoh`gP_pe{MAElwU^jZzeOm9?~VuY=r#5ou5v;e!{!e zm|hNmG4i<{f$2w}E?`O%vPjo;8WA*T(!WvCeQ6ylO}xjs5pvy!*HFN}a$mx_be(E+ z>*%0*1|L|(Is$VaqjOyOcX3lV#2>dhLlBtbWWP2T)!wZIZ4b-WF+3dJf=BP_vl}V{ z_F;lCcTaK~pSuu_QpbQ}8Dk*PO_I>!A5w%p9Nm+jTd6QrCi&c{T3&>>vYONIUNuj z!zbR#dse}j$}cnCL=6_&jw)67AMOm|oO({B!mVOIzETR$MN(=rkv(JdlWeB)KBD7b zsBsbad^rAbHQudYSxbamIzfs^m0UPZQ}&wbEGwiB(i}fEDHKokLpJlnP)T39KTR=F z+au6loAn5ko2XUV2U*9`SV)8gV?F(zPD#gB(JB~6bzY`^ZiwusC}o13E8De0-@NRQ z#vZ`4a$B;xmlLqd`!u0EKhTzg)OtIE&n0!fLvkR<;yGx&xn8idQbHIk$(VZ z$0furyXRt$dJ?*A)AV^ykf=Ujm9ZgMX(T3F@_w!nRi(#&?oBG(4=ypH{k;zn;#q+(bj$LUtpW6-~EwS-hs*kWV+48C_D zJ((S>vgIpo8+y7K_G<&=$b~rLq>Q?$^3(~>UA1~8KIJRViPRv*okFH-WG92lM;BN2 z+pq&0-tcGXdPpYd8RTW3f3}|0l-Nm0&sHsuw<+hu@Ry7`nR62neEd)G2V5i5_#4ai z)ehlr2t1!?I|1xR0ID59q3*zM*Z<`nqY1j2SBUCqm?oud+~g~xY6X62L8PWoH}>Nj zS*8XRvlIBG>r@lbBT!uP)MYfDe3nCj>&P=Pde!@;%}e6$|Kp1+6`qP7Ort{-~q4anx!|V z;ML~)h1Y&b@)Kk1j1k)J9jl<%oY&5-uYCm8LTJ&o1XgI5*3MCpXQ)KGUhR7;9gE*d zHnkMylP&wuel;1$X6Uyt(2Lk&bL$~1zQo$q$`**gC@N*Aw-u5Vv~Ty|{y}Bx?T?!$ zDaunYak&VR?=E+3wQ|BVDv8Lw%IH)1aI~fAZiJ7X5?gl6*l6uWyPAlB{EevE;q(1+ z)lX|{aXqN$@WE*Ac)oIwfstW-XbDyq;_;!$xZ$BKZ{QehexQe z)QfalGTZ#{u#+WJ-ALIIPUhM*OvxAe3EDhJjrHjZ=_lDoV3+w`i0q{IGtX1G5gl`t zPjS>A|KcOZs?S~MpqHo|jr_K;)7brbp&;D3Crzfzgk_e|I@Vs~AQ=*5F7p~50Lg^_ zc2qK}{*e>eh*=#j-)ydau$L{(+ZOCph~ys@cl5ZNMk>m`j1iOTh#UZax7?y{>dz@x zE}iQgvf>RZTGJZ*F>0EvCP*S}H?zdCOC%RkEbd~H@ZD>4PpWutRx+2Encihb!`ZIp zmn)vcFdf(dOO3P8)!l+Rh=2^CE!$RmQ2EkopOVhCR%_;>>XKWndC+3wB7-hK!WV1i zFlE4o)voE}Jan_w+3r=dyHlSt2X(oJSu?s?av_$_?hjsO6no z(jr$qo<@bKu_gZ0>1H&7I@0cnA{dzKpbo4;v2zh`34TFJ9d`eZO?*QZ`Ufd#+RO@w zLD)E-C;H~>D2=qfOtidPJX5cmsQ2%XuD5PrOn6@|7R2KLYzVaHQNE$d$f)Y{mGG!- zIK57{_P#neQ9nrvWKh6x>s=ax2caQGpf;gocN~7wFeIN*Z4;@AslB@$N?HwgYLJ)` zZ*0$E$Jw37tRpkz{k=@5%Wb+=!;b0wx8r48($CF$W+S@%-kRC$-Q6uA8olOvdR~!- zk>yZ+)ly0`-}*LUG@+qEe|^s^M+uRFA!P|9mMy&Q(XdrTYNY7;U;`0+vX`^z>Cx3( z=$n`(at7CKbC<7+@_v1mzjWBgwW&g#uyb%>W%2ZKBavBUc#VXxI_3R+xmSR0pN;f9 zdypNc?g?wHQuBKDRAbyiKjP+W@x5EjWf-RQikQkNiMlK-#NqrdEZRL1 zT(3#2_gS;qxLJdfosiHK!JWZz)*!L6cFtd8hxJO)AsLh=^UDmYAV@6O$y%>%fn9J) z?jC){gRN2}S$o#2dqbm4USWsPnTbr4!YSrIJuD)41!OmL(I0axJ3dW&E&ig=gq%WB z+{rPoK(i_yvOY-ADthbi}m_{W0de1BJ zwJZbO_|&~LmN)ZSkB^%zoNnGf873xo0x1wrENoAnd#*{d4BTU-LfI&(W@gSIZf0d{ zyr~;=nKTUwJDogQRugf0o}BWiN0^+Y+DYYW?g?qc3TDbvagwWL{=*S&4Aw-Se_=)Y z{>*CHm^5G0E65)@p6nYQcu%=T8e~x_sg^3fB<=d9VA45R$C3Rz8U=tm1aWuvX&b&7 zugkgMRwC8hC=MT19%`Mmt&Yhry793OFrZUWD~S*iKI52xrfm#jOZZ}oRq(@1)d7vj_p(Ih(+G`nYj$!E?waZ^6urZzn$54= zJbRzdgWN@rP$T4I#jjl@1|L$S9(?EcC-f%;UUnmhT~~UO;RV zXKi>5QY|MuRkG`6!#H1Vb4Hmlet6a_OKAGl$zEhJ{A(i+%TYU%qDb)xyqcBU*zQ8QQP`Gn{u&J58}dJsPx_^^+uQN zXVXMsSvYA+Lq-B?3E0l=9r|FzTo85YFORmv31QtfbRXl4M48G6Z66i;jDVQ+$&x1$ zQ71%e3Z!Wb&3?_kADa7;rl1>C=DFjqW;ksWx6YWV<2AB-mqDWWjPuU=h|yIKyS+hv zq|n^#;o++eI*vxkUQ6gXGPEwy;7J)n>#o6mTOBM8TjVuRlhqtvPQ9Tt`gA0#OFk8! za+C3P_2yQkKo?pFsUstc%`ZQ^?we$uQUl2k+e|`l4ThsyM1`ukmr>*M!9(Q(ZOl2~ zqkPXaIiniK+dOk|&ZHC?z6`NSrvo|soZ6o?T(xnF^c(@*Z}GyQO9YL@jG59N`dvfy zWFQ@@e+N!?(=7ci3&VW-8z;N;?Pvr z9a9SfmG7Ux<&<`HxK$Tn19~OZ-wdwsb`Iq?skIEw|q3hlpE!aQyz35`CXG!ySvLUQ&YJ+-;x-o%4 zR&I2rGXk~SMvT_V7l!^w?+a9wV!s)GW~`2iU5bpbSb&@TM&Z!vO5@{sm9m(;5fV=r zs`peBbMl!C2$ab25M6e~lejf~B%nC;VGNQ=nX|@M$1hwEGw*LH7tiVx8JQ{m(yAxc zpo_PLi>;M*^P;}_K|)m)R3Y5NFY8Y+zi~;yJvD7ou#A;2z8qh2${;eAk%)3;N2&60 zSKK!B5kT~>yb|96R<|X**rWL7bvVh49Tpe@MKzNYUbaoUS)Q`g?IAzp&Is{a3WE+k zj;a>Y;&q&J249Z690rRD^5lu~HP{>IjNy5-)rvmUMtuCL+n@1XrEueuAWdQXdHois z;7J6_2lOpQl&}^=J!Uc%5beCYUTjv_SzHV>-%Bviq&3cDqx$A5X*-O2>Rt*hMmjY% zzZ%uTfC>=T%)LzTj;$+WN6OvyWqUgrTzksYaxdN&v!-v*dzwI{yzDQrHmahopEOo1 zQgE$SU$t8Vol;42Qs!k1o!zcJrXPt?&nH8$Llu?V!=F3V4JkeKe?e(eDs^?;(;H?i zxAMa|0QIR*D%1*LCqWWkJHP(&9UM&q1+1jSDJ=5fp@nU(?DX35&6Pw`e z9cVjb34zupuCS0)7cIBA26g^95la z-l-s!wmVDGLq#aeop)elv;=MpABj$)m@b7ucKFN3;i|Bet9l~LZxd`yCVt;Ynh+%> zLKIl3e}LBodp;`nvO4)7qDP3>5>>j!)@leP2S#wWSPBc=*MTzoYDJ6*(ewu$vqdAv z!mKo)F>&@~!}42Zu*^@dfo=T`0VK-icewY)pCe)soLR&yJ7HyicI4bZ5Ex*~mSMM|W?xbnOn zS!_gJHu2Vqu>)0=sizU9%1cXhx95RKUb_yng=x-0BbC#T=B1Tw#~q$x-F~lQvE|i$ z2929(c>=8hf$!eDwI}xYwdQ2PMrPG}Qtk%ClrHJ?5cYc3(1Q z|29MZ<8HPkix?f1HE(~C*9$xk!L|d_U!>lcbQ@x(A-#D^nHk+_O(yoG_)QUYgw0~R zB`4n(cbv?YH#d*@%G87n7PrHr{(d5avLxFLH}^iFbBArqy~U?JPg=>?L)V!?k$T&ze1mK% zpWxtlmNI~y0x@2jS$Br|JY+vs89Z$64wPeHkVsAaa>aq_cBbW%xDXPORyPUasPFge zDK|9y>itwHS4i>c5_>I4HFebY+Pv4o9MujxzMdi8)JFS<&Ful*kaDU+!n}AzqQ+27 z7-2nGH@>=cUY)e2X>+plc-$L4g^Awpi>MpcXP1T}q@&$SadeuPV8gBQR^&Bl8)rW6 zyxgUs6Ld}I7!3Qoh_;Bx)kDg{_fFUbDhpoPSDBn^&BCwB)+Y-a@8O&9*WQcit$1Kn zr8)@Q4NZ$zmLsO)^1O}y60kv^ndY_{=WKhEkuq-HvWd>~K3Oy)^9LS{5Upb9vjLFk zjNB{PvypQ0jnD(fI1g-r&dWSur8@W?sC8N zq8+&B3m3J^4Br?CbqM6FDJp7|R|dx3=4OYz&Q`MD8$c7W%6)Ass-R>e6VEiOn>vI! z$c*$PXXcERYF}2vEIzkE%|6_Y?czFNra8WJmy@}Sb@Qj5%;D7oZqbHs>V)E8^7-*K zlq&nYdm=~?I87>r6XU$^P~6K3UzlfGfSrB z3?Al9OJG_03nQL{9*wirJ&9j6F>~(<1|Ve(ppCq*%@99L**y^oZ~2^+JIPrMB~Pi_ z$$+n2v$LuOEf2lS(W>BO{M>V=_w@meBod8U+~pCOv=k zP=fO1F6M&iV|{kX+63XvO@Ko{;WRR;{Y8 zux)yJoZDmvO~kHB^j0ok*0dqFwf^Dfb3_({UyNVU!9n+HEf<1Kq_LZ$lRIv{^oxr> z7WF3RRc*IMGu}Rj#tMKrv1{=OX)E{koD?9^>Vz%5B}vcQh+{gH6smTp+W!V$zKWL-8fr&O$_ zv?NGWS9x<5Y9sS>v6;NzU}fTat$Yw(br-tHc9T5gER9-fLF-%JpF*}#xnqR2^ROf} zmC*R=w0N;p+-`1=;8?D(PS5F_zkzz^WVIbmd^Y`eu=`m=`Ae4}HIP!=IUS-&!PYb< z1;l4C;w5B3wuB{jE<^b$kzcU{^s1nMz+Astt;`~`hOInAN)Pp-^Igs@rcfrH0_j3;?rWbB%u;jMaGq+xLi|~xy@ufd}GtuWpop;zzat8D87&SU;CZ4hzG6U zGm0ip>p$oHlG=uM%5*`$+I_1g2~ML)U8IZ#t6$1~oG7fXjOW$va`D_rRAKa2k7rSo zYnF4Z^Rjf8tM$q?Rn^kdQx=+-fYcV{y&vn#Q@$2*)^rzHXZ-sm2e_upai8K82lk~L zbPQ&;?ndf>F`JGNv4*^fJ1VNNUq@RO56IG|+^YB4;Y<(}PlsqtYAeOY^KDA&3|$eI z1okkJAji`>#cN@rR1CdAyEw!%)>jWsjoBWSQ{A)&W}KWiEp?>t7^Gk1(hzLh@g1Zn zt7dIG1*BQe_zn*?P3274F8mn2o2wG~+PhJFI$8`>R>xDM;J9BhRnyRs#&R&TE%BdF zqNi;g=UJ2Er~q5)*Kn3khv%h*h%nx4v^Cs?`xd0-L&zV2%@&D#MOHp_dmZ$% z!E^7HSY;ue#(XwrDqx|1GK&lP-EHsa`ju2Qz4a_4%d;>|fc23o;8P;?u*aQ#DaocwuVa!NZOb_Xb_nXJM;rn~-x0#Ky|4X+$Nd79!0F zf&-r`G?X{o84nGl(GBmF8jU3OrlQ?2k?uM6 zDK+aT6&o{lgQpW_>IObVt0ff6Lcez}Dg+BClo{2JF+h{te@$$g{N531!ty!<#@8B%lAXA@|h4@ooUxbQ#ST-1}@ zu@H7E6RWq;_fQY zgu+MxOG#uLGaxBb;PBD_MSD*iom5`skEe2`#PJw!JrU;acHPO#muu_U!%%{P=WPf2 z!6VGO*@r|=!ELgAWo{+Yg<*?R5dufel(|Qiu|v43-YOTBl#Ka!Jn`je66|8%Sd(j^ z;u`ez0Wy4NCkH;2gASIK=!OIf7i62H9Ulvl2kMj_1`?ps%jr9{=eQl&r3K}CrG@MF zp*AdC0$+EAiQi7$ylCOsu2gxMjfOTIhcPFwcTja;!=WEjr7742vW5c{7A#V_D?H#* zHOrd$?O)Imc9n1wsdvv`7hb<$kj~U3sxbH{>v$2kd|CtDHBr0E)y&N$yb9^9UH%q3 zVmu_sNSIN#n8^^AxkR9%2AT_yzGAI(edV^jfJDyRq!|xEzC4OP;VwCM((xhlFK>+@ zVq%F`w)3K)IByE$!9gX_CsBImW1H^u$JV zq-vw}-|uZ9IH=@Ws`7}{x*W{0Qf#t()oRF#a26}fD}8qL5Uds~yV9gOqX1<2(vxi;eGx z1eR$MqA4j=*9KvthlWfM+5~7%RCOrpHzt1Rp?tlI-qna#&Nr{590eiJutDh92$-q* zdCxf`A|lLpvea&wx1#Hzr}~tV{@%%1+Z1O?*`k1H)4x1nbBklDrv7_?INi5bAI9`d zFC~JDgLZmTW%Co&zRnk-=~C)CXT6KUd)_uVThv}ySmwb>KVbm7F<>;dBP+z1JJpa> zlJ)ZgmCm8cOi38q5TP5R26DgrEvr^&*G-PN5&C4YW`jb&flr6Gw%9{+sfyoCQiE0+ zXZvRI8+xNby`-CjPw_dvkrNaS;#y>eKbln7zxTp5fKBrfu&IPwmeMgA5(>-LWaUwx z{)Y#n@02NDC_W;wDM~WqYbpOUaxZ00TcMDx4>gdn=c6=Ko|zDX|5B;uV>okq61qWW z5e}&iZ8^NwKX7Ped!--LLtExMDeuB)=tHW;VB*w#TRkXZJbg5x^|QfAwGEEbaXV)0xu8l9<(7* zag73gP&TW(uDgk$@%0CS_4%lsC@I0Z58B&mWifTpBk?4PtM{Plpdowa{Ng%pb!+Xs z_ZCUg&XF9gYbEjYlkFJL2~|b*vvo^cBTfZ`lypC8QmEVxBJuoi9$d zG&O+>o2n&Q((|t*!=3S5pBM=$jucbs&T#xEG@E`I zU2Ehwj(Sd(S3Z{-Zgm({)i~#$2}Q!!S*PRKX0l5JQBnEPN3J$2VC#> zxKe4DF;}B-n0KkMNhj*LQyo1A6Kew~Ca=gVh(6+L=)Tax9LEJK-H#ESrjRcENyq#& z9Yr~K}tP!59KEswt&%vrTI8^?e^2@EmgxEgw z;xEB{v*s)0j6s5M8NSx*sX^zJ2sLvqZpn875uu-s+_~~e?1@l zw)WEbDRJIjmKkEdz%*q9450})h4`k8s%_=PyG~RhXBTzbotY20%id&#?Zra-roG`R zJUc9B{lxYYB%mcty;K&nOq?A)cwt|`1-k>Eq|LHI%X5x=*~8L_We#eAItD3{w@s=7 z4Qr<3+s4BhOjQ;oT7^OHdX6OuRx+Ixb0AYWxXf=btK*o|Dk1i!zYN2-I|@paybNGB zeN8)^(rr`|*}hXzWk;T}LDF33Vx`$Yof%8r znH%(bJ~Lj<(!OOFgxYc|B)VYBpv0=EUGL-B=G4tD@I>> zv{RrV{aa{i)qJ7UR6$%t>i5Ept;H9J1o=qamf(ZvM5q0$9^Y9{i$$oYA!ygN{4RYS z{oMyWj8u5O>+4?O_~9goJqN=<^WE*8Bhk2<`SZBIR6I3j!PDy(>J!rSnHgFt7$k-d zj{xRJ%{kqckyA#OD&}dKtY!AN2~R#Nt3pn5RkquPBiLD?G79=cR1p5_*x8|N{7o=v z%%n4EL<~~Paou3vtfNTGS}{R@#9NVZG-x% zi7`5wqhB&Bid23_pqHHq=-B`CJCuGw%ZukUe(3cD-Z~agn%$Smp{_Gh?oY_ZS#WFg zLzU9HMZL7MQfY2+EzZwA!)b)azst9IrXx>G;0xhYG3(27Qjp1tuk6dL*z3M=lS#$J ztIb@rWU;ralumQG*k&tn8{YcD>^s#++H!FXg03;6EWxdy1QqvfIJB|wzH`rrhu1}y%sOz>)MdGPO8?v5fz^t$nVdtUc$>e2}*X_p2pN?B_h?{cuoYr;1=q?&$d)!bEqgE zjqj_cn_t|@xj|~;Z`=bJOFkD{BZS6@?Z`dP#M5KH*Zk_w7UZ{BUpalH6B92>wBL|E zs?Gi~i-h!nvUm7{VmE~u7Bm^S2s7^CeFsF_{=lz*tw&*}iWTl~9D5_YZ zGHIkNuk8jd^3v|BeBa#kC8fqHq5Iq4Zq|{R-BUQ`JAWUwLPL_A5TkL~crewC@#`il;5--y^lhH(F+qKgi32P$8}(P{B3 zRrDl<-J^WL*<5QjNq zS@VTz6JtslAJ~{PyJV=CZl9MkPZu8FMdo{>A?@a~xr*6zZ(~p07E-v-D(lhfuHUvj zu?$iy(I0g85#c*znjLM-Vq;CT&whzTo&9;sD+@@=D)|v#7pa&Po-VA%b@_E@p%Z;vn~1wjx83!e@$DgzPOZI7+dRmcb8ko zvjGL3G~ejEhE6N-x*TwVMya?wz~8z-W~#RD2xM!=ki2eXA;m7GO3&A>CF>pm6At!9 zLdST5MZ^BOy8{*N;(oHYvuXUc7TXS=Xr6v%H*14$C{pG9r*}Do0#$7}IjaH-S6;o{ z;~(PcJiyczz>jW@0Ee0JVn|NichR`)4E6G@hLG=zghHPV4lL@1_fQItLhtQ)j94|H zeU+&JCgLH_tIUxs(Fe`KQjI)w+-z-c$^@rzc4Cj4qjp@14K`%l*+AV^vA&k+*iO=wNggB2Gc` z4KXoj9bvNATtA59Qeyh`QrkMzm>Xq=?6gqS^DG#4us*L*lBko{8)iig@Ucw@tQ!@| z{P=|6(gR*2A~TUISGT#5?s_h~4*T8+?!ArIbvMh5d(GlPY)kW8zGqusF9tsJ#SQLj zx6z|*+)|$TenG{v>U{gCZsFKF!!sM47iuKeE4vwXavHB-FGWO34orvNj}lXLUt z?DFm*FK2$YP-;IQpK_Q-M8@wC+>6tv0%|hz`6S%;>78mfnQ8=*^HI#wau>8S;#VDqJXW<>c|f) zvSKyvC#e(L9AXn=S#l}w$p;!_r$_qxoJh6xxFeOiN=~1z z@0voB3Mmj?{w;a^J>b`>(!FUSmM@O2RClAWK$S=`>5n)s2Zc#{j2|}?5>!*e;i`=5 zo&~lRL!yIWWO@dIk&=qHnCX0F{})kb`OxJ1zkN(fL6A_8P;#WyC;$mNyJ?<*qYy++YVKse z1*!idPUn)~&2BwByl=+n$7CelsG_|eD5d1xkgX_NHdE?_80u@Ss*8HZs{DmxrPpaw zXvILe(?vRGhM6NZy4mMs0wTD++Bg*#mi-4DR8+?oy=P}Juy*TG&Bg|%h%SoK+eK#+ zKl90Xr*X9P;N^2~2KKAF_*C%LrEnf))?9>_VV@d;*;6H_XA?&U6<_;T<#57;Rg+NfIa^LFSE!lZBckjjpQM<_;d>{rRBzi=a%}&yPktHpC>$~0CQVhc41bsx|6y@CctFVzsSPu7khkGN8ZaAy3Knzj|nrBlA zc-57-J5a|)Z&en9^wTi5!efj`wGw*D^jffgQyNUj(l&hDmP*Fe{}}LyKhX((c6nd8 z4ceZc%kCIA!=zqZE>+NXaPz*?oF$(*`m6zH8EQ>ZCe%{_@Otcje#x7$+K$kj{;J$^ zKmkV8iH(f87)>iJowh+(2p$9O-Qt*DV10N2PgrV2@GY9VP3X$2;uTALRvw1O!BNJK zw?4{4{aly@q(A6z?fKG6%(*?;l}+HuM>--zD$XZnvH}IutZYoJ1~}`@G01+Oh^l8Fvl6URbgfSH*cXwC`LtnpBqz~dHfnC*1qGbkE5 z(>uqfon33#mUbe5Dw+QRJxfkNr|dzu-$@@iFEb^^eH|H zN~(pb)S_Z30Hf{mrOQtpB-!#_)c`~uJ4cUt($aNKG#f9jHq(SgWHv`d{BpkaLI}lU z18uc2$Zzl5LH&pK7B^0+dwD>cp0zs+?J)MT9PYezynbusb~kLVZjeGCz%bJLn4I7p z4y0U-{X#ku>1!xb#wK z*zBr44_WPei)L9V`AQ!DtKmPqAIhi4k=A)F`ii3yOOve|ugCdTooB)kBeaHKCgD|D z^>Ckg6s(lkLwiNADO~MpmbQF^(wA)(i2DtL_odfDDSVpckut-Vi?*>jpt^ysK_%A` zFux%P5&Cj$R0a#22p%fJi%C)}^0M#W(!~TF!ML6bxWS$c2n&BM66bzD_FEc^F%grG z0)vdHG%!|2l}9EdcE$Bb&3FD;kQyjZ=0i?U`Z=)S5(y^K30pFB1-Lw%2|gA93Af2& zFMqUj6(krz|HG5Y_ITeaDjNa+Rb8K*12;`w1jIYgQgy#6-Fqz&;v{AP*lcvn+5j<= zIl$7aSo(I8Yw#ng4VvkGJPwgu?$S!~sWZ2&J|B}u0+Qjd_II1E`xd;}S1NuQXVEY7 z8$Z5<{EAS>$>r=4(AV|&5V3Lm$j=6xv+1Z3UK@u&hjT?X<{r=NB`+&>yaB~_arGf% zufny9bzjoX)8}uEuW%A(yy?jjmHA(*&&PFKtMMgk_%Npa()VLlOVZWewRrnFo{!2|wj3gx>$hz0L2{TwPKUv$8oc0gR)xR9N z0FJwJ2P@ZbFi!hZOO77Fsv8}ywDU6hvR zZ~i__XqD}?PkO5y$bEf|yb^an5$HmBu)*aWqr2YJd#t=~hb@TKlzZ|)yk|qHSm9&l z*I+v?zuTNr-Md%&hMf7C*kO&pd;||y;MTwGVrxu`R68r$H{vb+p%|UD%0SIub9=^& zU2{T+$`tj<(Uu9=x7_A>UtxdLS!KrXO7cIv>8-x8$wY3>p}@T92!qRMoK<`Rc;QuI z1BT_8$tO1GY12t)m=8>5G3?NUAuqP_np>=O9+xmb5xmblFUF@sy+fOP`sWhU8<_>RE6mRT<@izv4-n zQTuhz6jQ~qK+zZG%D-9|`wErffAP^9Kqq##?PJzu)*jDIVM_~l?zEI;_t*(pO})v$ z2xApu*Zb?-!x+)-B-l%jk1YiQRtrNUe4pxGR*V^VqODV8gtQy^zq;NAyvhh4N~u5< z-KbV<=kr|}Sh-}C&V@8< zUHC`LdKom6Y!ZMB0amgW^__Eq6<%8CLP4I5v4Tq%-54VVp5ViAPWGC>+huxgLG78V zuEv>9HWEw;VvuNNdl_}7o(=Owf|csVb#h8zULreQymWo*VLzHA{drgIhUyms(`y}a zV^LQTgP&R|+MNtxhTCwOu_z>v<*7ef*F;(M%Nr4$w(-=K+uSkhRjZsZNz=z(sU23y zsNsjxOn_jwZ;$bw_?#1g_B6&Z2=;r*QQK8ePsg@vwO(atiL?t8x;kRS}VXK}XC z*CO8{VtBrnE&0;FwRN%f2wRyLhy9%G*mplw(ybpTaOQNLfF9fUVRkOMbhi6(A98%O z9yPy?Rx4hdjcU$G?r9j!$2NK@q|I+b=2F|oi^BUJuG>84S+%7D9!*M`o<+&t53XRj z(K}Dc;4>e?7HIIf>hQPaP;yjKr7cm@2Ag zKpY_Y#)G4~iw=L5lU$2b?Ze|MJB6o^Ed}@4aL~43+s~8m%cgX#0*$Ij$2*;W|8_s? z$Ge#U)B1>j00~K0q^+u2+;~B5K*+`3rWC|#P^ukIKck_qqz)u@c`1)FRIzu^7}XbF zXp?$aoRCvnTY{Wxg<6O$VQ-kCU{KD*O_jt~GsgD?2V-8w=YrKJ=Eq-CiNE<65yLXA zle&rjb&+xRN-=RQ>w}HdiD)9oN-H#Xx}Cc_FP+`1xcXxN6LqmDppol5DXjnKhe}C& zB{1<-wcJ2s;@fI;-B*H&yWX}=xA}PPkuKL|i-mskr&o3!7(xyeIaGgSuw2vZ0th$d zVim*^6*j{fzi=Ut3a7+$7NX#Zx-lDFAiaa5*6^tGWG_NMD+u~vBn2&mpw}8EX_IXI@ORs3wZF^yEv>oczoy6% zYFHg;YdBN_%@3lj1Fd5Tyj)dOa}}yZ)U}st&(hHPvF- zKCBpuS!qBtQ+BAXmiT=Cmtfwds>N~SxmSQV!k*1OP} z6PuWl361c<*_+|%0))4?pyczv+nTVA&1G%5V|pJ-P*1M~+yp2Ol*|TP zC*L1DYxH1|+WxHCJ+tJMl4i1l(Cz&VAXT`KR8Zf1HxK6^OBi8sQ3n);zGD>~G}x(O zJ^NjBZau_H4tCdapnXzOIy=w8d{fJ}Uf)^9Nhk5hV?%KyG>vA^MkBdZ+&HB_>YaUJ zI2*bcUb88q#JL}aUs}kwRedF?E3Yc7KvQG3$+lt=)X(9Ebp{W2a=G4z`HEZ8N4a|I z@jZETBu{@;1x+(G?GA4OjJg+Bp3!>cqYA~ zU?yz6MA&rBH^pf1cg#!==a zorY74V4lXGiPO{2E8#9mAF1D#Us2RBd_)vp6C7i)H3!0%jhSU#93Mf(SDJ~HYe^p~ zaH!SFhLSir(R>rwd&H*}#SGn#6OU*4G)&gwr?I&;wvXO5bu{}h>JOfga9NKNY>p)s zv0B*t6J#syOQu1|D1Qu`>sR<|I@7MB^Wl;w?0@h#?Jqy_&*Bnc^>nk;Dh2(Yl?MYr zj{NZj7~9-O<+Cvn11Hq0-j%7_=eLgy^2Q|OHog-e7YYZ7&1I@A9|2P>TFxr) zZ%hZZY-}4wPJLT@!#wq#Xig}5Y`J|3nYV-OhJR#?^`89?FDB3CSkHO7mdqVumpPvP z517ZvAyIUb_kC=rw8z6=8WPrw5rw4KkLrqPD)$PL*$A2YtZg7@=i@vh;zY1R7vibq%F*F% zu=#fd2^I+;OPAJPc_`rB$h1EzXPb*lA~fmZ>Rd9r8i8rvR*p1r_DM#`iMOA<)1(L{ z-f?pAFPNI-{N2F3vU^psm(cbu%nqMW{fLG=KqPpyI_nmJy0pB!e3Nu$)ihMBUJiQg z7irAd{4!NNYP&zcAMc3`foc@vyhFjz``$a@Kb5V_y2zm{?W1qP541<_7$jDB+@}}x zRV=-f=C3@iJZ}h7`K_;oa<$e+wOiUce@vo{dTf=lb!~kdHX8)Z(+0Q6TmB4W)Gp~_ z(Hd;p6kKJ@t)F_~jbS=koN+x9iOMKzQMG_-`FtTn&PKz(#(S!$fF?R2k*c(|b%{Rj zEMtz-G3k*sNO=M-6CJ9mTo>)052-n1-+50^%de>oiCmRn?>HNE&qLl|aZaIlW%UGdy51yHu_`@3 z(w4ow`>119|E{J62-i^IGiYFWs-7Pnm;AuXzPQdrT0G&>TYlG#9I1rHHMlsNp30{q zp;iOIkH5MSOJRT~QL6gm;~CxKI8Nu>K<19H7VJWq(Yo3ETw&Y4$i_Wmhk57o9qjNH zcs?G2yi$Y8h*z0rt21F@RGnxe)GAycn)>h()|O4%_y*?<2qI!Pp}YbMTb|^j!R^Xp zC~ZqFk1LqdW#VTm14GKbINw8-pYHIj7IK-F!g|Lycz*>VEI-WW1Xk8-sNfA;MZQ^x`rwC7eI>85bo#?F0bn{)k z*XG#FgVdL`jOHi9FWP1VaQ@${lNS9ms~)qQAU;^{)t4(Q zYK{B*vtLusxF=%&rYM3eX%_TYYFb$d5C+9JR^sj=_bw4$Qv$l_-i;#rB z4Szayr(_}XhuC8Y;0hxLC05|2uoJP${>J%xx+{UcchHGSU&h(G`j6nT{lHF@;ntAK ztT}NELy_+(Nol+dUtEy`iI>oQ7%tu{(=%_KXN-Qs_Wh9cUE$;gpCp?bB>stp0MCa{ zl`=|Ln+Lz|(4wxj#?3xriJIgP_gYe5!nXI!@77)wwQ?F3HqXKE!(sXzD+X)k@t$28 zQStSC)Hu2TzbI3oyxC@0Z<2HKnhbm`Fvp{}lh*3oeo{40@OSvBh=jqz$;32?uBEXyG3YFMf?i-?&s0U9Z8Rya zStE4g0ry~ky|7q-$ZQOmp*m^9fOxp$HNjhr0pPcQVC~z{q)y0|zu zXvR~>ghPnvRbe-z!B4zf*e5ePbuTdC3qImwI^ZX++uL%|Dgg=78Zjl;(H4#$9MtfU zcZ5x`Pv__Nx`^{X5PQ^i_APBDzrjM_XAUk#>{FENR3K@ z7tVXxv!y}|kD=`1wREB3dzKJi#S;vyW>4pFJG(hxP+LA7&q|Tx#IZ(o+Z*0Y7gR=M zi9GV-VhWh|+LAL?#Rn&=3lrAk>!#VzB)Sf%Y+XE{iGM(E!MyHE&_1*&$n<_7=j9%m zb1j*f)v?7Vuo?lqe`X)RS@^9ols3Jv9;l9Q>d5nsu-I4AxOaLBWQ81=#q)0rIH2~f76rE_D@L(f zySKNsS7e(_N>=+F#cHB%pz%Fh>j04n>m}9`QM(3*>0P+Z(6at6$;M(TDX&N zuY^v=87p^V`K%H=7G>*{Mc>*cGKJ9a0&3Fs&!Y<=s_5VEH$6ip9LR?jztTChVBQS+ zd6t>mN8m;rOzNRex_xRGCAiAtOr8hVye?~?lL+#f)~+t|5p-pef4&Jw@c#5Y#27X3 zG_#q7f&W5*^?rk3+(xn%(9enbTO^Cvwr!aH@z z;W!?%xEgSLEx(c)YJjC=+n~Aq=82VBBYfO~zUlJEOl3qjaZ|y(dCj6lRc)1C;w$HtEVDi!D zNYt^H`_4gUSn4{U)V%*OKOROjSftQVb28Vjd>RHxi8}sH9^SInd}m2oM7gC#>b^ZB9K(KL@lm#%&SCbBTK^Ey@)((bwnw-q4l01fv4;eFQ`Z;cop zNEoVcAK%a(zuQ=ai+yYDlt0VJhmWk=nCz`6ZwZ1t%=J=awEt2|-j~-~d|9vO{!I$i zN7sVtmwv5}W~H{7+GOTt_Rl5geviEIDH3|$L2?UwIEr{7(0JS@du$6gwSC-I3orZe z<7VcpqwIRnPwLtCfegFrW!}LX)hch=yzA1Cd{o(+v*-Y+e)JXhmqvZ|6B;*B!oDzf zT4Sa^EN`XSrEIUHay8kz5$@qiYbE}ytNL{?4Y9Ht<+juY?fZ!RQ+xyet3;g$K*gkB z4P-E4OMdq_AbCLR8u(4e%jGqmd4LaGtcO>_oNDO)E^mx2^#%rr5WyOxpn(H}M4$;N z;M{EXBvg%V27ez7mf`ZipZrjpemuG1Z|CMlu9g!)sk>p=ReOIWjMPI~zpb6M!wVS^ z1iy&;g7|vB)cSs_tprije|TgY35vNo1G48DMd^?+S4N%%2SVobkqsyre{=VTMY-2g zidrXZFoYbom&dJ=hn{XD#a(V=Lod}y4v4ogcWO*A3|CwpDm^?Zq|NIN!fdXKVZ96^_CBND^NkgFURE3ffN>_vh_wPDuD<0AP|pNh(p z+QZ)Q_OV7@#hc}ZnXGV920kBviY8{IqM*dvo|Y4?B?QZ_l(R>MKk6Aa5|?jGle$bj*;e4zyaQv~J~{*b&xo%M)gE+ih!yZh#VFZG zxyNnpyGisUbD&C9UrgYVIA4y1-;XZcBsHZUkA}?)Hmyo?8mo+0P2&BJ$n~x3rnR-| zr4<37TiZ7?9q2kxUic&OzS`XqEV$xP_G>rcyNju!GFq=oTFum;#N-tPib8^pd6_N> zAr|Sp0qRsbV(?@q)Hd%f(%*dLN~&?&5(KB3+B=Z~UkQ}zN4;TP)W3-l6G4SMVMRf{ zRBn^Ek-x0yQ|=0FsP_9l$^+Ozck4{AI;Bvx3@CMu<9t9v$-&w77D`dYuCw<{Yiq?B z4`pG(MsPVMVeQ64j!;Mx+;6fJ8;^U}2l0jl=pJy+v3>V#p2lr#3I)Ef=94nEVT+;^W()UN4Gz+=TI`ychmkk{`Et3t;R~ z%ChThN(xUn7nbUF8x2ya`oZe_J7B!)Ce8sU=IkU&q(2qiJXf=~G#GuV0rTNN%)kkO zPp5XOwQI<9RasZE%9CGB2m-&KnHZaEKRh4!aB$${pqQhdw}2!bw<`~i3ina|iX5JO z>4O#qiTPJYyPiH@PZh-FPlO}2+KQMx9w-C(XaCMmZ!5HPhc1e0p$2YC_8msD`x-ET zIdFJ(w@bY=(|Z=8a=Dv*JmK25Nat~=O8<3gDL{{Nspp){d7V6cE<%GBgNIUKj^KLn zJh-ejNsLr+4DGed2`k84n=er2lKeKIbpP0YRg#mHHe1K$)D700|L90bOr5VK?1HlC ze$H!u{p<<9CfJ@l56ybU7!|KrUNPN?O>-E`EIVg0yMGgd04VYr#|*dympmVa%S6qo zKPv6pDO;u(*1iu@9Hbsv8|wZ2?kvakmS^B0-3UfvB@k5GAfIs_kz2UoTYl3fz_Z6X zT*?^AQ~hknraC5Aj1Jz0{J!L=5nq!e`?9)DVa6iUtvP|inkUZ?JpaUBoBq~RC?C-h zmAR(%s^3{wNBv+<$z0r=ne=tN@MG9*s)2&6OiNP0>d;1uw|i?qz(6kRix)MKLz}Ka zc@8U=yY6HQdHXFrnHaN$tE8N$w?cx}yM#LVbRZ=~SsP+#@i+@WaoZm6(SXmr{KC6l ze*dg{H3Y|^p93;qX2|0;bKqe6YVdA-^*h$}?}WV!(n+3~B}ZU96JqGb0%gzcsvQyA zno_(u-$F(kxQuPP<);=rUH3;u#p}$GBJXbI@W)ytCb7%SL?{k8oB0!qvebq1;hHvI zTEL&m*GRZ{s?p)~u^g>5!w-(WBfH3`%F3lxy|oJV74Q!rUmqsfU?H2@w38&D9x?Q~ zowBP4PWTqJ)KwZNG7qkd^is*lc;2f!s+zLM8LmQynWDNdlzpM|AD*QW8s@F7_0YoD zXkq8%#23K?4Z>ZPv8qO>B1fP;F(ULUh}DSt6m_;!tG&h-ek63!2q&H|#r&E00~V2e z4@}vFFOuH}mEeyd=!ECS__Ka+6_~CXXs0^dcLb!Is3!LkZWrTtKGibzu9eX&AR$IS zuev_6$i8>#GKc+yK%}K2rLoHM17;D&-#nXocsN%GX*>uo6n>O5qIKP6E7!3xR9K}mZSk;!>@XxJk!8xNT02p-09K?~ zsZ`2VuuvZnAFln0eBW-Of0*^A?x-fj%(39Hh zMZ$-_tnd-f7`~8(>SCm!j5$sbPx>^+6x)J+mTak6G%jSF`~oDpQDg?o3kq3`8>tSl zRp%g>MUEwpG!l6pU9VQP!%l`&>JBs9dvvk?N7p=PV;gBG2u~sw2cv!vsqWbkB)auADtOUp)ME>Zgv4_mgT^g;6`Ep7a${ z625kyO-=IT&Wg)Y6Mi%&Bk5mZkaOlk?l9Z5WqKS9oL}isZsCcqtPD0V14kRX6of)y zW1@+su7f{%Vb}2~9e}qgw1`4;^$`ajpPlQKFb&-Je3XLkm!qCkU&e-K3ZSmay$?a3 zg-=0gS$G)NwnE{UaKG$-i{KqKBx}_Htl>yX%dp0G9{cf~*+j1tU&?T6#i71QIGj`k8Lx%(!J`Y4abg}cjS}O5*r9>@hT9R!O zPP-(v)VWg`8l_DI9JwQVhZ?kPseP-+9E>Qr^vWFK{wJ8mi)?eLW@cgC`0EX)YykFO zikfAez7CjDnU^_{H2?o*xJ}VwRm!f?MsQNSt?7c zGadd9uj4evkY>N&WbeWS-sE}UuDOM<3Xw^7>RNvs(yOKuK6OI9GbYNP+Fv(}pC2U@ zv9Dsj{!^*VuDO))k;`&}xc+P^G|yV41hcz)a~++|@m|;Agq5p>E9B)$Dn}y^gbt`tG29@v3Mi^=BGVqQ}YH9*K1YRLbo>v={R^6+Q<3IA$-#j0%yM zn@Q7f?zOy6RHg>08R-xLwMrh~&BL(AW4zZV`r1(yjmINJn#}2`EC<+$g+}t6GBTNV zq57$Id!Q6%)gl^o-|zcdq^3-fZfM>mlJ`FP1jeUzfs8>ziLY3pf_ldbv@(skX%Fy=D5B!MZhp8VkOlaJRX|<;`&A3LD#GvPF1TjPqn(>M4g-OM-pe*!}aOko!_|#|hjo z#K3W`5KvtA>#}FAC(kKS-&ZoC`Ag~EBCOk=)#tfj%yfmWl+YXPEJVDC5M3+wo#N1vSc5iiATK67e`SJC5_A5N6cBN#^iKSW#sD$p5>tX1 zhJ5Zs{%dhJK3hkb=WozE&Jf6eI3({WB~Jcr$ff3xXtDUQ*jR0$23!VH*f zv4eYZ-V~h)9n%h+f{l#yF~N?Sfq$B+4ggAzMhPBgcFJZ5muu$Pf%=C>#g~OI6P2QG zew#j%&mk``+k!-aYRh)diw~$BV#B(XvhA$qT;?V5&Yrj%J1T7pI2+V0H~Z;Y1+qCQ zl}>0Y?j){L_4JbIl==}b=f{{%89e&ENafggT;%BoLI?kqI-0A*T_nWa1K71%O2vC?+{wsuKDOX~ z1B`yNYH2Zq5ku$p*zubtu)eovqbu zcgGfA1qsWHPeG<6af_vflIJ}tuS{jrNV7F9ke!s0&?v;Irie4-x?;5$vF}O4FTlpS%;;*mQ(q7yIkmKgk=dIzTVnwy&elP-$xyp7#4dMFVg)JD9J z!PGV?ciUG$b6e)Xt>MUDDm&^DGdJ9vuZ{OsSpVyzlTWViuF_~?K{F@E389GyywkAh zDR=iO(O9^X9~bFGH?cW=?cC7jJW9LVu=`5>VT84A*mGip?gQVT@3udKXIhBJJZmL& zjvL;80hlFGJiI^1nBeyxg>MXXBS}=}ygar4UHA#J9kF|j2WpaMWf4C7|Lt7{&kY;q z1PPTWO~>XcUa2JiwafC#+SvvGK?R7ZGk5YD=P)FCT$3uSX|a~R8~@MWZ|+U$${XYZ zWK)Z5UMBg$KSZJRrhj>*b<<|SjJASpYF~J3J5+7?#I^cHZ~`+8;0G+9e9E z8C&qpO*gXc;N85UCCg2f^Zn6HJ~)npI&g!Y=qLP)svBRQT_8DBRP_1gZVHkZazX6m2L-VFk#9$&}hs|%2#aB)AG@6)vxd2}BS z9U5!rV7il9^GyBEymsNY6DnZm89?W8J6CJCkogGnA)e~+npRAo|BCYyyd=t z__Xt*EpTupqS>j|#?vT_C0X3ZB zZ~R~Zk}N&TY=jl~FAv@6ev24I2>&<~(stK}sDOeNW|Fka5ln1xj9zxiS3R6DPU{8V z(LVZj>KW7>=ICMvsp(r9AE^ee+@p$LAMk&PX*kzBb5M6{m{$_!iY&@Re+b%;_v@pf zQj~Z|a<2;Y>@L>mxh^#k%+wZN8(!Tn4|3&l~ za|wa6|DfJMAco|FGmI95R2=1Z6$AcGw=YgOze1L0iS+ehA%wgOH%6-Mi}8mkDSKw8 zLc$5~8*g9AI0J?950`8h6;_CH7dbCHS3`@-IcX$#X2Sv4Y#jIdl}v-0w-vv>wTCDs zN!D4S@k)VK3x=c?d!LK2!gSqnMt?YlG?rj_wcpp=C%$tU@H`9B5Gu*iUYsQZ_oJ4v zB$D0m#^cRxz~mhBpH{tTkPFaHH%g~3=B>>YG}R(_a;&0)SzXR z&|nBcH6_y9+q>|?8`Y*#XyW)L*UiH`Rb#P@C%-9dpeL6>p<^L|RXetqT{I~dl>#8S znLLY_m;dNr<$j^)n<_u4;ZM{+5+~dng;AX4Bd^BX%x18dg7|MyZz`Q6(Z8DmIF){; zIFZZ!)ipACi1o`kA5&aI^@hOX`Zp=eoQ7!fhxveW(Nx%E#w+=87B^>uKLb~?%bo9f z-Y+S}*|GL@RXK`1YJSA32m;V_Gtq~n%Qjo#MPyg#%?9CICb_`t0v?iU^cF0COp|y0 z0FUvQ-jw{BC&R2#Jr1uHP?Qvsd}&o`w~+0@MNf1VTpRZh6DatnaDDsu`@Ldd=uwUI z0|WVZ7wCpsTbio~hr6HB@$9MC8|4oabTTZ63-wF+OHPiK!`Ay0G_KJ@B5rY#j zdfIn=UxkmFZMIn|x$+%&J9`28EfVK*PDD)}M33N1>DuW-!EBZmUg}-D%+x~S?_Vx5 zZ$EE)F6JX2!})o7qjF>Giwf}fmh1++R~6IGO=Mz@hi72zO$+UpBi&CNFw4E=_tdy+w4sf=E-;&UqF*k|YS~(6Ca<4MH-EBE z5mxoH!4{K?s4sI3DPj7?%h zg$S~K7*)BR(^mAagcIg?^@WkD{_qVO7l3lrYnMrY9&4}+1NBO;j^pMT*0pE zTp4Wiky8Y56|iG9cO2a(`B?W{4P{a`)R2q&!*;jg=S=ZIv0pXjcUBaIr-lv<(o5Z} zArPh1nW6qRpUok{&Q@2E#ZP67Y*2q4W{2ZwHzmP+Z%RP1JTc+*e|TDLJ@cBCYhoo0 zfdS{d$hTdM~+|nQa);Q@kDlr81{kY z^XH(pJ2VX~SF`}=^Ed!BC>!;Dj`v_$YYbmDfd3r{p<#im{b&Jeyv^{i-@bcaKX+$A zvxe0JD&yi={fS&XKmnMRiNtIV>bMH;Mq1@niG??`9;PZET*`fxzPaZ%?1(wC&0lMQZ0gsHa ztz-nK>`i+3D00&hKltI??6vKs+-L+yL?EUC_5gaGz?QFx*IbQ*un0cb$@T z&4&Jm2e4dgW$X5zzU2b*ExVe#mTdlq*CjQWTpGku{k9qp!K_Qlew#_aUd(j-9o|$G z$M{eska?lhP4RL&%O(rC2bm z7lElVk6?a2;gH@&aQs2O$$3pd10-EM@S9}dPJO8>4N!P!Nw4@}5JsgO)@zYY1u;{j z2m#Sv1kM|_orR7GS5r{v)QnF}h_@OA1&E|QD04nnGt_ymJd7w*KzjYO(R{5|-!a^va*LoStqYWjt$r(hW z_2766bPEn9vEa*Lz((PFJ=w1~j>J z9E7W69BxQtu8}5tSi4Yc3#mAPY~M>if!fl(@GY5$0IL4djn}Sv3lQbmHk28oAdrl! z1@p4(Ak>@~qq6#$cU7QN$~jJoV;P%vD!^KT^gWy{j+SCIFwEY4?MO_n?JmADx2HU!HM2IuB0u{iHrAb=NQg2H=lXcT zWq_Y((VA+)zIM_wKCUP>R361h!+{gsU%po}RE(>GH}Fg%Ic_2%jN$e92!U+t3UIu9 z5E)i?Wg;k{P1c0GwVT>$GlZkt)50bdQ6&;`+}9fC_9RfTVXCYk&8rOL$t5)FLF}W% zxpXZsb|ukm6r)yD5M7X{h2vkELUAbjft`|w1qitd%{+1t4_Xipj_FK~5nJOZVJZys zQOg49XtS{1YpjOKp^w0`i;9kk<^bE#kxk#s>Pm+~hUGm5trOufp(oq6%>pq!7nUUk zJ1)=oxKd`-(#?bV^U|V&fjMgXb@jAXgxE>R&GsEbXV6kz{Uz_KoWBjY8EB)!5RuUH zxz3QWJjAv;Rb8gy?)>pMPTU9}IMuN^vP)3Oj}Zmc){e@Sh#|e3WX81HCg?D=2#Rwn zNVE3qGACRBdr+pkVSZlD)Vo&iC5E1DN6(m-aQ5oSUd0{wD&OpZvsjpMH~oM&cgvl# zoUqxNIZ14VNvvDjJ&&J5DS(WQPM#@JLwXoGoDMg+1)46hbDV4Exz!D0uXB~91!{Ld zQhsd>8h;j~>1KGi!42ZyH?K~jjdJhnKRqg`jm>%ziB<#Ev-3Qm35kr9kC(XjDr#=) z>Z6Ea5^Lz9!e2P|cd(LQQkWkshCot-=T@2Z*J#Q1@@zF5RpwI1b24D6qDFR~6f=$m zln_q2W*3?+7Iey2y8*94@^OZWOV~k)Hs>s4k{z{daCI#RqB$e^!A?ZDkecHs`}t5Zd}EN(Zswu!xqBCO7k zVqLT9yblQh-#dP$e`u4~XM(C5T3iJ1Z>u|d9v{8aVSC^AP_vgOFOMC{S998yfxI#K zx&4;S^>A~|?y3pA;GZ{{$R>Exk^R8hF_kRGj_b+p)5SaWf|Bv}7lu)L61-9}RSB{1 z+KyVsMu-5w3aGyGlTOOj(Tyf#y&pT&eanuSyknNO&O;gET;DQZgyAu6Jd#F>pWrAL zRUJftZy2C0t9J3{2-)3rS+2U2 zq50{#Eak=oznfK)ln(oYx^0{BL{wBXdJ2Uqjep}TA{IBMXcd zyP*gB#_QBF)qX76wRZ+RwX_el2DG}l;bU$8!wd2jbA97sdwTJPuooCtrDNt{CZzOg zrI$y*1^;6_@|W}HP%}U&d-fITlZcR44dc3bEM+~BJ~E!`rRQZ^!Trmfw-LH-kc-7V zWw5NOXLL7tdf7MDsFLSM*|i0Ey>-2WIUFe^aA*6#OGA9;+Ezl;QQF9b+$~TmnlWO;l1g8bJd8c6phAkOc<`S&iTz9 z2fER9d_rj35ES%rPn0lEAJBLg)oSx(t0rftyJQb=3xs;AG>HnR!-L-@mah8{TS-NF z4Z5S`fbLdUL?$5nOtMtR>t^FN^2IvDL?iUbfhNk|^I>%_WES>A|L+qqyIHpejyWX- zY6TUI)U-1(v6PA+FYTq|THXg{jvbm}luV@k{eK|qEOSXnV{yST$U}(4*GE(S6*%h; zx@%zNrbG`hgBGR3%%l<}PEKZql3pbq6GLf9MhNdc4%`bUrm2FPGo;mtoCN2-&|1qU zUn_l^|Cu}K8g!4`r}8YEF~H1_4@@BVMn4em{E; zG(EN*0}`^iYQtkSoTxfj(r~RGFk-YQnFOIoaT}9IY+gjG!ay>A-Z6fs?dfuOnV^ zmv9r|ZOLIxfbaeuye?43W`6K~olqBMDT3=r;SntyGM%NTkIMpUDLQ*6bB!nohKDiO zU>QkIi5VM5x2W>ypv=b{xK-+?HqL_^_c>F+jf4L64EsabAWE>Q1BYQEkD8$o#df^d z_bc3+M#MNwDC&#voS-iS>!lVE2o_(=hrlq{^>qPoCVPoW4 zOs?0TUzJV$_2V5`Qe0J;wVC^WRQ+XJ8(i0g3sa>)k>c(aw765CMG^>-;O-LK9ohoL zonXN&1b3I>#oZ|s_u@{uUf$=!-uM0i>*Jd9SZj=N4vF^NtXWYkYZ@ISiuZh6ka%|9 zT==_((4t-e(vgP-P+01G4YL{;+g(B}73lI%gs({QR)94CQ{P;MTU|L=iL8LDza2Q8 zd}uQKJtsc_MH2Egb@U`xeQv}9DgNAC=Tw>eR*q-}uGK&tc>RFY`&&7J8g+I4mEdw{ zXnpf}yVg7B((yMj&xCbFD@a|gwZZ)h(|6Nezk9Ug*^>(_Y&f2R`|4Ta(=e%ZR8*`> zY|%|46BFzm>szV($IM?ZTC-?SXh-`{1zTnHam%_7)&>v3!}*yTz+ zM_WcT4}RZL@Ye|zWu(;C&l!4*DJ`kYwYA zvvSqO)KF5LP-e|i8RC_(T=)Eptyff4vw6i?K&cq1;Y@Shjl)9iKz2Bf#rc#XI}WkB zZ+Ct0%CTS1snB78tup+)QM(}^R3`KOxmD2HX)7B(G*p|F~dmS*Xdy?Q$@_=)v` zJOb*u0~T-%K!7*2*nULVArVYlYixcLi0G60cE6SZX4^;Zx5uwf+)iZ#xUXE2LHgMI zaT_lFk9}HWoW=7+5Bt^~g${wXWmW&7{jgUHHgm+CiHOVa<`$on*c&4xeh!Dd@OPVM zZBn?TNG*lRZ~{026d(TD?e9C>+{5|Y`I9r)-ILI=KL`%{61eVOtWFMO!7msS2b+PV zyaM~p?r8fEyrmzqD@k7~Ld)x`BFyBBLQz!*r7SD(e-;+fy<(YD8!x*@=Yo==#bYm` zJ2hf7tJiDCAz4jw}z6*`l2GjD$Of z?fLHCx{S*Q@$PUvy>C^|BsG5tGgz&2bmKkc;CQ;8irzYoDp0QlWjOHt~7^sx4|W<{h&<|xOao?r($HKt9Y;aEDJkY zIhOh|Z3gYOnRb+h*&1O?w90+Du&RkTq`29)UmlRN`}l*pbe_so#$d3ecXP<_s})yE z+rb$cmwIlRq2&S3?Pip-ff3mTG@DxhR%vnC{V%}oUNaK>IdspAOj1{Ue$sp3*Z5yz zr@wlyCe%3U{Y{JZaYoxG9o#^HRhgk_&9N)l&-NB8^(L-xz)o2$%kOJLEQ>YQ^ZBBu zWR(cmZ`laj4~A50k%#A8ZT?@+wX}D^7Dne1g z9+Rc-{^G;WS(s4#Od)8HT=4qm#Su(-?$jTrB07~JtVQ2hPELMCpXM* z^B~E%uFYPX>BX*7;7)!;A(EE5@aDQ-a&|Z4LE7{0o!@?Cod4Lp?4NbqOXd2K({W7D zW>zl&wi9fmK$93TjwTY1==buFUfaubx3Ex9`AS3)^w4W><|;b?-cw1Bee zx73CdL^GJkrwe_tH(SQhX!_IAkCxFjU5k+cDvVGKVY^GT4=s)%(r!}THBy15iH(Td5bpk_@Wz9R70RZfmH^_jh(nudAm z_jkn6#T?{TFo^W){$4X<54m0vReIx%%HaOC2|$^Q%e3X_kxT0CD21+PqJG7+XpYr; zEk7VSeR=t2Y*~$H^}^$tm@Hei!3IqC?K+d%%%4MaG2Qc6k^H9rQP5UC@3kor3tNm8 z!jm#&gZ@MN&CY`?*~2czmVQB7=y}lwBNj3T7B+q3=~)_rMbdfcy3gb}QEbEgzo6A_ z!4WTgxCTy07jtcV@egS4`RKHJ@n_xmFH2Ew#aORPWoctFJrDQEXHh^gz5qVWE`c;; zUS7iH7dl4W0kG$?EBUe5Li3tA2+=bY+%mJ$=*@B9+Z>67A78Q7LZ>ObAiYg|LgAz< zV}m3f29JL1#Mj4YQazWHzkRk^_jGKxEsBqAuCejKU@nDHNMxHTJMF3J6 ze@`-rnoWN?+x+V4#apSK?T{_NE$u#qT68$tLAK6bGB060%B)tO1uFV~xPm;4W%}aW z7(oBU*4)$VYl#z)|DDp4e8Ro_=t{JXB=x^@EHQNh9%v?8dgiyNx->e)>$R1>*~&u z1Hk3(36ubGE=_c&qthcINcDB28Q-QHy3GEZ4=k)7+;rgiICWlLaTLG$I%-e?*xBCV zNY2F@`Pf1+8DYy!!y7Pozwul9J>9bYcP>bN&g;vjSEn)VM*9V|j-TRZbwTNi*Y;<^?nbX zjhLt`GaM92^c%2U*u(8Z=Bx^nhdp>K98VjL&gozY&a^O3rKGt5q-D|BBA~exNBmU{ zDV@+VBRLnRmcW#oz+As1(-VgBULp36^KMfy5W?Ubs$aCrE@<J_&3?I}H3y-sx4lumlva`H<(MHZ#ImpHmkZ^1Cepl)&Nt zbLClrs=Tr3>k~-Xbk81IKhV_eQfW2BfMkA-K-e*6+}Ru)TO>fXLc7$U zeO};i`C7xZFWH7J=sa?IKRv1--Nh5cUS95KTc6!W5hFNr^f--=4>^!I3fI&#)fXHF zy#2jwm(MEI@C`8GK4E(|JcX;^{%C8&2G@fF`4jZGlGVD6-b*Yt=1kbQk*mX#%sehq zHntJFoj*ta;+~e5vCz&cmrW7rI}tR0(gHp_H%#k|Jmz2RF5E2YuFnwH>UNfLKiB3w z#*PJAOJ*pBX}Y5JSKfkY#7)20SBhk zF>MdjyY*zG*+j~{Hemvu9zvIw_LleQH6Hob5zvoJyhy2nxkU$epsE7sdQV+mjrvoV zZN~0F?wJHvs9dvm0gOS`0Xd3G#LScFy_oW`ENUuSF?e72K;vxL6yJ>svmM{eHFU3; zgg+VVz<_Gmmd|k^cvn>S%xZe2Bp8=eK!x`5XMo!8D1rm;aFY})Y_l6wA~uSflM&B> zo?yBPxpLW)!ew)y4RcYzxGF@8#;l9B>FPtcmFPDalw9i5jghdzb!#lP*ZM9kv5tlD zG0(sG;+k?()Ug@pq|QLW`j2PAH7K2_ty0u`4f7Q!SETlD=E=swHiNWMrtfOq%G?^l zt~v?n_Pb&~<}gV1{Y`yFNhH-oi86Fj+AL54Mn$T(gFzM4QlOnzK6+!{R-cMem9x3F*6!tW=-7ao1@9Ks!R%Lpwp*d;Bvn_YX>6BwN;OF@6SV5o zgk2E)yO9(Mr}@;=XCat!9vYXz@Ej`at2z*Oae9}JHoYhzHo)PF+*W=R=?7N3*9w$c zr>S}QRjecWr?6OyTKw)-G*Hc&dBdFjlVIw&L**% zB1)y-T-H8z3VR?C(H%G@HK8(7{eLKMZ&#lICIYhJ{JY{WrJv%~X>LHhHn5v7`4osR zxf}Rp&mM+c5f(fAyG1#+gv6w~I}a=KEPHUvP~X&sLN)r-1kW#>HlaT9$~VsNPodiV z4kknI_wy&R5x1ilMI?2)a@YJdj6BI8}c`ZA31Na z&8W*8M?iC$N75R?fl8%{sFcFEx?(&+e1T|Ki*0G%!g%iP0MAOas#g%Z01tpMjV%}W z&cDvOl2%@A&tvb)^ZL6>;fP!pqAao{8>L}t8VG`m^p33$PD|-UKm)a;RgG6N&UY-1~tN5`BgU;ezAtsyW;T7!uH zGdq-w_A$-NSFTvrKlgL|T9lF3@fM(tB5=DxHC!|lu&Wopl)Cy9Go8`vxYskH)p0V>QqT;sCFu^<%d{l) zsQkYBQ2dH5(ySlYBpTfkmZ!J1hGel!$@U&h5>Dbh%se=c7OEdSr`aS$XxR|kOxL*D z*R{0<7y#>P3s7WKBN=nAt$kLGD;o=7Ge2%G7S@7V+7~}1r9!`;WbK1Yp|*O;={}i4 zG43$npVjF(TP8Ky14Gc7y&VAK1>JAX3o$+C6UuURjv0$U=jnn?){FJq|Ij4uv>=D~ zJ+{^*$Ed0Wh}0?K#8hD4WNv3u;bN93-Crv!RE{hf(M;X@oeYvRrJCmQ);uUKCe;4L zL}sw<%uKV%$%P;tmio`^E%Zu~7-r_wv1{9R{I!( zonO~mYoDi+%tWd{!Ssqv6^$4K!7R@qVeXcM(zH8MDOpb{yEDa{G)|hWxCo?|xB(Q^ zP#zIuKAiudNyKNgajn13!Z#_2)@sf!E7oV{ocehVJP546wV|nz9O~$|b}Hu7YzEBj zFG-%Pq3#-|W9!J<Nb*J%pD#TukG0k-w2yn-cQP_Pra$92Uiza=_>F5{;*fZC?UrcR!>6wlt>OAu z?veY@np%OPFmyI$CL3L#lA)%48$rY_QiP)5m|#{a!(-b!|FQpm zRp>>Ac(Iu-<6{NGx&tS`2jm^%uO-=_Mm!|m*yb#pNt})%$jwjTbz8g%7T&2J&Ze0E z?0=)1Ut!BSq6|rJlbm!j5+|H2)x_a-wj6y#-)L$lZ&uEDK!=3Bz>HXd_OP|eP5XVn zkPL4C%z!eUjAf)ois!bREsqQ)Pc$$KW>fz{ZOo@o5uF8XUcN0hI z^{zXKoL_Z$ck2T>$0TTQqEQ+CP-Ta{uhAF!|FevjWo+48zZMz)ko4as?n9?Yr{zdOpOJjWddWeH&jN$fhM^Gm_Dafqvw$BXj9U-2 zYyiGvW&Yt}1?+vt?z0fel?3~B68z*w{J2E`tC#_uRJO_xZ!(7rZ#Cqp2ae)xC?W@S zNf4Z7E0h%S-w1jq|HdMEF8(>q{s7>s^OKf~M-8`SlqmjsgX4Qg!|;%wt))UzZEBys zc@evCXjCw&$z%1pU@24PLPS6s!S>S31PqqA5zEk5NeeX=`J88;mO)G1SA#AyLeK_P{pX^#8&4l*w+Dd~__iozP{iP&# z=>*J8z<>=^DR!HG+{PWFIt$%V-oN_yKx!6yYob(vUWBw5CP01}u>{XZoqxjX+@!9Y zTmv229FvL-RBecpXKMb2Kcsp_R8eW+DrJ`q12`>*2nAj*{dxXuDT=?Yz}?5@RFViy z+|`#49?K}_jP%>8T_50Z2#)t_1cjDI`6y__a5wk3!CVDMV&4nwB1b~Fi8Ey8kX&!d z9fO~xHg+hAPh+bY*ZP_mp zsEhx6)1RE8&S^I%bXeGOz@XNx$5(z6S!SL(s3JMvwJD)iY%5t3I)bipZvEqxYcEX{6~JCoCj}d6OUFD;ABk>qVl~V+J7D7 zYDUxg+&_LzCbOqyqN8OJT(NOf4B9O)CD&{1*btblP*_0;KSbKRW@2-gXPq!Y?Nhzm zC)5&XNq!E;pI+iBiO;mJ;wn)Q_$Fqo?)=gHvtl#Fg9(H5zZ)x033L`JQqU|7GvQ5$ zCC1A^QaaHEGs2`HgJf>2_claxSLy|K>8;B`|1k575O%e^A}(9w5#s%(ckju%A2~HQ z9N3+0^o~I}^-9RRjUAX>oQ&*vPx8&gWh}{nKhLxcgYDwu;>!98e!0aVto3v2KN6@L z&UnYbD$aR6cc^empT>k&c7K15V^!U-MUm>9l$Iyke~u|(_xRwTCtxe^td13<9d-X? zH!8fxl}2`)J@`(1s+FYqiPDYoE|J)}vD*0r{KqwW25Otlu4z!T*ll+DS?5oCj_d=l zz5%I%{6^%UYFd_%jt{WvH9WwA5BN&# zF11W7hRGUW1tYg<$Co*!?pKfbgJ;x$=bP->TrdTYvMqdc)-Pk{H zLd{1E#dCaj!$3cw4yxFd{=Y4Cpp=5Qffup;EzS>TedfoD!eL2^&(K8<{CsyooPy#j z9nr+|SmkYtL8@3jw(_A9X0U)*YN2<{b312I5Im-LT(~|HNODM=)aW5w2+~n*{Yj$o zZZHR}o{Ap2LR8SFF^JCBP;b*vjUQv_EwBXcsoS_GERu*bYs*ZToPFV1V5io@5mn)3 z8u)IHfJHcvrRdi%&h{dk)M#9^b;Y_0@N%dm)?!<0kt@4Y+*iUDm2ExIX5`&sf{Z5T|uJ zaGlZq@#g&29KQL9<=Oqp{Wt=mj|^K@@duXHX_h#!#9=?|DZ>L{7mX{hdLV=CwcfjwTkt%|q|6l9z@%Yj<4~XQsIP0EST`zW>tONBm-={nKZQeuLjvuGij5WcLru z3ga$`f}yjeUgaI^$oW5B7=4ENJSjM^=~T~f;kT`yv+NdKT+btu^?uwmXna-2ZX~%I z_~u^k1FFDpV{Ba;-}C4F(WzJ#%deZ@A4T8q*E!g7zUy=f7I|Y_i2U{|+SQ_I#%*HX z)_Y*0iDrrEOS6E;ncl+~4c?_D-!*D;&y1P2O91q_1Xc>pNLD3pN&cIloK6+SgVbYK@Y7^J(Ba(ZEbDR_LsRuoj6Sx91@4CJ7wpg8gZdm*%_w`b z63uuB1kFNer1Ik%fHOdwF}nr)>wNm=%OPw(Fp>t{j2uQ}+N{@l>JhkHEvjB{pUQ^K zS%09BWK>>$e;!Swn+{J~h+5M2u$r?tAYc)L@RjP^P9?*ga`)aGlfgmGy#*<~4RB?< zzEAO5zori_6lBzZS=_7{;T)`pJLKuA@GNXHs(duoyAxUI?mmGs{}{kvEVyAkolqgX z>s8-kSID}w^@ifHVRcn4zj%HsnX;nX+|Fjj^dG#nQ>(M{e4$vcyI!`)##I1QJg17K zBLh5sZfVJBz^^<7A#*SN=BF(JUszw>=ipo1x|j zLE?96vwXOE3~bZuFXW!o&H@!AC;p{iYVZ5Dd2xkZEpZWnlFF6E&rhbe=g$SRws(^= z06ml}bUhV%UY`}Zyu6cs%zg})n%cn*S@AvzpK?F2-==2x;l#zW|3*tiR(7~8i==&U zbFH&!Zd2b;y5~Q%(%wV%|Ipw8KsO1p^%Kqn{;{3>=w}L!g9;XV6UqCc<@c==wvYLj z^UoiCw=UB*=`+r0_ly7k6GSH`yXd@+!;ij8oVjYlnFkux@YzEZT-wfOIv3zr}$%qG0ZB0h7rkYT33-CoJ0 zSy;L~%4-8j&e|lGBE%yiI#Q}2%{Edc`fO}0YZ2O0ZCFmPzD~aGTsI_x*v@bW>02CB z$mRmf7^&V7G{jb`8E(dAS+yom1=efh==j_$ZeMN*Szy?8s>h_rEGms>8Td zq|<{yW$S7ztWh-ggcH&3nJOtF49u>TnHRvjc0V@fg$V%ac4#mXI1GK`Hu z5_c@krnAwQM81OSqBSI=zNz!^CMNcBS}z2*y!2+y*@#H+xI84rtB)78(S63qeb8Dd z+jp-lEo4P!jno&-gFM%gM0~d-g+#_DMNFFB7JfD(UT6FL*5buX%TUdC?u~ttehl*= zZ-J%_$>%{#+EFd%UoV&>o);Nu<4OTcA(WJX5OUit@xIUBzM&zvg;UdL>f=nZvC_S; z`cf91AKhzRd55xdoN!T@9Y(#9EuIx7>)XjP86ZQVPIKhGDX<{xHx^Xppil#BpQJkr zGc%kQXX5GwtnQ1zf~HW_?=^6{QaRIUSw5ttUF9zpo(fgiOqwS$%tjU<$*nzuqSkV? zOgb}rj z(xfD0K6#X;dvdtssKVV^?$_gkn1OIoqEI9zGkORYOQ6^QTf$CByuZ~^tx8Mc4lGyf zBY5aHq%u(ZT2)j2{kQ)`8yfmkl1FTLS&fn71&BTovTU3jr|`^6SLkP=Y_Y>Y19{DU}Q8New^XTbn7={6+!&{S_&zkbEhwIe*n z|Fjd2(Z*V!*`nN9VfLph?&kbMzFawumJI_6a|p=dF>dk^muO#Lj`Y6m!B*#mH0|T( z+-c-4p~zNfspqv)Mvqq^=KHukVo9r;FRMVKG?%}R*WA(gWy9r#Kwr_uTTfB9h;3p` zgf^EwuSIB_t}A__zig#fZXf4p2#c+|yuJKeRd##AQJ`Ww0woo8Uvvt&;xx%85+41v zhJHIBkI3J6qzmQYl;}R z>T)zYKaTCMl6#t6S1!vg*8+|GLALHmBlfzv^(R+Hn*g|LMWc{bn=W)DYVeg~by}_E z!>xf@ELq9rr^vnbCizUyA4bEtTd}!&|0LI23I{dLg`O<#x6VIDv}dhY{baVM{IWeC zNy-H5^cL(R+bu{y!~vbFiiQMdxYqh3s8bFur?xZF8-YlYE7AD%bOSl-LH-Ace|H~! zT7T+hL`{b!d+5;VGl9-O^?B*QggY)OWwjlJR8uDo)~|)R zo(dPuVvZE#u|PUq>K-GRRHrnVg*&)k7I_A%O@VUm4TsCub+-9muY8X_;aYEKZcMbo z%VMIbNi+>d=pV>fdY*tHQJWMsG+WPVK$~%YG}nR6AUWk=vd#r`2&6|NpgXyHaBp9f zOX4kSRQ?~DZDzV*)B#mR`ALlg)t_AxM@}DhF)*`IcI)s>NVkl3 z{w{NEQ126;Y1YegIzV@s_{V3*VUzsVCbA#uaaij6n!VHczD?WTlgkxal1i`Ie7IGql1??-*r^X`q(jo1s<)2JyT{#BkvisfC z_?|$tYy49Bn%w&;w$_K~KeW&%?1Fj&b1L2J`*ho_vQECO)p-A^Gfs{D{Usy~_LHNe z2QQmU8l9pA?>T-ssP4^Ig(tDphlUhcP%x3`^CsJ?^2=94p)~cc4A#P_C}h#QrDhQC zq6(7#6dtCQWXf9%PL{{gtqfftzcs0RE*_t=J%^;>yK$j; z?3X#!r|%6c+3l-Nt>N(x98(^b7H`n(za?3(x92@C1_ezUoPzMOKl)^Sea>dRc!rj? zgB3wypVAfzWeB+erM2oA_OZ^DRMi~6Va=MTW;tgIkpI=IlR3wX-u}#I#6aT-JXED=f!Vci=w7{?N28biQ6lP819Lu|vP~OEvIi$t} z;-PO6>B`xvohBgOi5vEf;K~}1=xuX_rsB!>$Eqr=RH=`AdG7>k&8nN}doX-)t}8rD3wIn(x?k z?V32eMYZ}Cxr6*c?zga+l0q}HRlTOXD`KQF#8ROq!7(5{qHbD;J+f8^cZ)ry%yfj0 zK|T$E;lm$0n1o4dU|KEH*1lBqm(GE& z=e>M;+W?>YQw2-8P&x>(`&pZjyZ3wW2a& zXPjw1c&WoHEN#)twxx4>d9|3)1YAD9=PEwU`gq*f^>uY zfN&_1>qj=sI7xUpTr`AnVdcYIuR9(pAMDDJ?~v)X^w;j&@QFAnJH;=M=%|OTTQTFU zK8f=3U4P_`j$lzm#f^mU&kR+h#`Z#5TrvNHBHE*2<`?ZW!aG-{ASPDf`p_vj(P%LK@CQi6i8MBs<(0kj$)0*{RM zT2&2hWN&^UBwskrq>%C4UkD@?nRqX~2apVztQ4r_Jl6Z(##gq(=cSnylHE4(clNeu zvQ#e!lD?g+6`j3fq!_00!}R#_F_J9e4u5-@bXOpQ1e^hBydTLY_&hY{U%$Uk4b{4K z>^_vVQ4M)GU;M}6dAW)>6V(o};g0>Q4O%mgWx1*+0z2i0R3e_s|5`w!kAIW#9djNT zg7l6YgTW0sn}0J>4m_>>OU_`hm7ALawV@;9t#U;Oyh|CKgUGU_ia%a+uKvWaT$&{d$+)DjD4uv^`lNmg{% zs=7$$Ea213*4%^oCbSpk@{qvAN$}l|JI8|mhz2Cn)nQRk@{CDEN>53IVqd=czIRbH z)y>___Dp@%zqlV)5-!RovK_4S<@%B-nyn)5$-o3)j%7x?qlt^v!c>c>(}>?iLLi=r z%@o9Vc<9|Z0*vpXijw~%s?6_CVV66M2ss}x$RY6}dKL3!R9wlMvPS~&yC6_LBJz<8 z1S=?f6`hZ!BWeTFwVODq555d^44f0_7f-lT*a?0 z0v)Y)8vXNOgWBl~NVtccazoVlHE*FAnecuvQ*{h>$)NBr_btIf#*_08zTcg@G}vPJ zIzM-1d{#=$(?k}~*wq~>0|f8-)K{_ZT3ZwJceb*s1!Z|por1_kLJzU>M_a)3o=3?E zZkVD0&LbPT4%Qc(lZ^$XLr3?SO)XVIo~FYLH|n16=;xy)XJ#801f=1l@mX zX(03IAze4%A<-B(Srv&$U%SqBwvUh`E30FkN>I%axQtOQ%&(-19YfDv2`E#HT}!av z7XEsWhw$5*#?CM_AR_47%)uZtLh(#~a7H{_MMT>5IM?LiLY*S626^6gF3Ntg8 zOhm4xK&3GboB0CR;4{6v#FkA{F;dPnieGD{j$KshbuUFLuK~H3JfC-hV?+Gcb3-58 zZ{YZ{7v8L;8u9ma?=QJ1OQsrz9SXTS+VPo!_Zu*eeJ=S8Yd129t0e84Nja z!_#FHoP|CuwQqo2Vm@G~MK!QBuqDLCzCwvEYcC>86ISLj-!?EL91zufi%x}Y40-E} zuu@ms*~%UnKM?F1{0!HBr~j@e;pOHS!y&^8B~}!Wv5mf;nKdr2)JmxUN$`5S5$S{fxTB!_o>5K$<4jA?pm&UX1bs% z_I*=O-;qr$+w%;IDA*k1Islwts8Y8q1pH%|*cawtxUF$^_j?|R;fm{JpM-=@)AZ;X zq(?K&0@Y^a#4rWBeq|s$Ex>wdmOAtXAt?D1wlhWq`x`pQWHe4ekpBF5+Gf*~ zcBu#UCnq5Mj9Qr3YPGE!5aEA}tScq>V_2pSFpJb*E-co zE+fT4_uN2k&{xwJP}o)YE97-licr+18Ap@bkO>g@?o~E$D z6t^Moni?h%8$VmzB9U}UA33yKA}2^++I^Q%hB%@r!s=j#bO`|nJtS~uD-$TQ>|a%f zV$}YQv+p4_A+ev5v>loUMb=;@UZ`>=&`054JmZ1-*ASnp zNCvesH@d-vd5RdSl=l3`bd91CQo(qQjj<0FAU)`jQdFQo2*L;Tg%}fU)ktx z-%{R%*$fX>9PI0w3xY1u#h!&`jUOe?P1cW54-P^fyI%W&MXd1M#KLm-rnDCpbnCV~ zVqs0MF26cm6eq#^#+9JV%E7yIV$X4kcQbA!9M57xQraOpwwRTdk#7F57H4qtH)@jwtIyBtV4%8f9Om3uxaM1Vgg=P;bjK`nLS*Hi_`PC_s%f-as_OPq^JBb}LTHxo zZT~|{!FNw_X`WE4GNSsJhp&#>!Ta=#8@=Lgib>}dOR85%^CO}qq%57Dkggy8zPTdn zGOI_zp{vjlQq`+};4~FA9qCF4#VgpWtXG&n3-dqLW1LF|urm znMh6xw-OuMV2WYSaGa0RLbePee+93voIJ4=$N4~&^}@H97!X5}o7ZxP4eV0R(bRp> zVe}tuzy8trL3LrdOu?4Ht(b3EH61_vgPDfk-=!IOMh-Wx8<9PLZXf!aQALSHmEhdr z(iRH~DO|&#T?+oDFT1zDW;Ws@uI-GInJYK$4=a>SBW9)yRfX(vtaLilnqOVid0ajU z0<^d1H=g6yM}x}Xa{FJyX&$q!5khR#C8F8Z2i@=(^PN>4D5R<^Dz9f6q%_?4c8HoV zTbg9>8lb_KfXv8JN~-y)s?hLN)`lgaZ>65^q9ipHQp>v1`>UDWR>4MDd}8L12qYoT z^}hV)REecQD+~BvUU*x4qv_)`p(>tCg!lxTk!1(A5k zd7SjrTas|qx~slgZP`h4MtFrh`cH60j_KBMwXQH(qW$qjc#&~%fW20Obp^DAWq2W3 zOc0`=Q9eZeoYJMVNr@};aar2cWuFZ=T&BJ0?&4-)W!68isNXtP^@q+{4M<(B z*5EEh@+y=xXFcOHS0nr>*6&9)x-C_BG^aa`mw`icw16Ryh?}4otNS)V>%-)X8uNY6 zaXHH$!~f7Spg9#KpW%PQVa4aIC>E-(KgX2i_GG_i>B_5U=N z6?DWBFpW`PnCEcA{u2L(=3dgDRhr9MYe8<8mkf2DW0UP0C>Y%4+;iLikZ@o@e=x<^ zg(2A3CbQ7w$kZE`U_*wZ9sa#J-j-R<_LU%ex}%gacO+3+>LmSJj6>k-0`Jt9#^J`f z4Oz~K80dxHz`w}aj!Ai;S8Q%|(^#e!$%fJ^oI>c^XvX6_kI62}Bb*{bfh1nQZ)b-L ze+0;VsGYNK8|%C~@353CfN@UGZ8Ncx=i9a)M!@5zLC>4=kA$pw(okF7oDE9jl8hPC zNJenFlgFyuUMFr1J-z8av?{Ia_b+L(|C|suN%Y;jdO`@ep#ib{b$7&TGU?3?zb_g8 z+{jjCJ)u2`RPF{*O#ms?DgkbagUJ; zEWNt! zxes}Y!Hg9PesWqZI;YIFE3c0k`_3z7c|KvDb-XO#)KVV4rC~e{OtF>dtOJH z_3Rf#Z1)!ZUT(~mzygHbzL1b|-W4wG*P=;RXin&HVfWiTfP=xr2@+FF{nd>sDzC5k zOKd6hl0lWBUbc+8o&a!K>hphfaPc^s#cVb&KHl0ddcoY!kHvXKxs4u!l>M~GstI_U zNkbY51_&Q7{+wL)5X?EmL?4H616Q_r16YQZ`wl3}P{TslU$MWU>3-~ij~!i8Ea-mJ zF~4CwxRM4j<>X_LXJV1bomy)QUsb-*pa0s96i=uy9SCO@WrgXp2h=$B|U( zS6&v(7U7^E%c_y7$a)>~e-SaezXotx+-zM3EZuCLv%cBf>d-dE0e1%yvt&xhD%@J) zw~`=`47`crFKEtlEuXl?mnR;cx8c?B7W%&I{lbvImpV`2?=KKrA4|CH7$4h4P_oWj zCJJ{tztsBh!$ao*45!3t?GU*JE*_Q$qN`ESl?X~kBh~kcV64`Jqzav;HG10rog^gz zF}5%MQeU{>g8mZy-ymto&lToC@caV(2&=BM-KuMvvHXkAyK^V&x`GH8blJz>B3!eM z{zY^dRc)x}NXi@00xqQQo|mlRg(rhnle^(5p9A9PwUQQ@nq+^(Rk0PJB8cCh>GFQ# z`I0^r_tF?A#o~E`F#W=KebihNM&A3vmIvz%ot1&-)Qi;47{-f~vPf)yTGgAuu!TbR zbiFa9sa@p$G};Q_nFo3sy|JBSVq6b>>^}3v1o)3CLu#f1wk}%?Mjel_*e}AnBh1nl zS?@Z$cOGg1(O%#Xo>wo|HJznc-A~InzC0anP+80p4>gV$F_Px}J;O@kqEV;Pd5~=m zwW(!1nFYU*qfRz>UlRYDxCRyOO9lgVuxuUvNH`E0{SVFOeZGNDO+l9p%+a`{e-VHV zqxx2s(A=SQ8_2d-MXPG%d5x=!tEg3N&Nls57TVOaz#cTe2E8*_SDGH2mDqX1$D(q( zQ5;n_YxMc!%(Mze6TSqh9sbBovOdl!teJjjdYta@qHu_nhPLc$&_$I07}6A1?d6=a z{Qeu`lf{^6VT-PzO->!4Js)(?B=`9So9{QI+vDE)Ix7lM5-%US|trIv!K--}-58LuP*KUl`$=`Kg3i>Zg{>GViQt7NF#{7k^~2=js)w zIa*RWgCNQ3%1Hl>9nBiILY)~dAP+_v0eymJN8Y5W*rbbWUL$4j=dm_j8}o(iHN0)} zb<>rx7Nh~2q_Y4;S%T}46Wkj81xp#YG)_f?ixFf0 z!3`giUy5b+h5|E#Wfo>cKkz61+erlPEPi2%m*vDtFZ@2n@EQJaCn8NN8_CWH{qh@= zcxIn0Xiv5^^WP~k?ZP|67y30Q%JHsPm*e{plS4uKZsWW{B0TKA279+6mT!tTuMub8 z3m?hM+pqSv%w#|ym9C{7y9~(si;vS!^Mk=>)2IsXCfT|}JZ?!TqL|lx-0SawDy0p1 zWT0+Cg@<2a{S}oehjgX~L?Y&Cp2FK9+mtS!sV2&{1)VXt3GLwVxzW^Q2FtUxGR1XfzMsgWJG-ub-8~O>hrN*u0zvK3l3d73yg`B5NwccDWSIa z1^SpuC2eHW)}&naJaKV|B0kS5wq%HqX#b0kU#gsJs=kV5#LLLkku32!L2DDFz9V#r zn>e>&ag_FEUOzdQ*}F?U+a!KJsS@)Tkr{`QtS05`2PRpF5e_n*no^7W9AobQ=?HZu z8Iifuo8s`VkMw(DlBqqjrcz9KwJdoy#;8Yg8 zn0(eqW?bQ$su!_8#(&YgbQ%lJbE>Z79R94Dq5z4}qp_~oE;UU|w|klQraf<-B0;N~ zHtiMXSP>6SUzIkXxfhF7j``X48UcgN)AJC1i5Goa(5tj^Km283Ss_x3BWO>KDXRaU z@oYSWMIKMH@9NK0I>6dnbuzG4lB!shLYt|iMi^^ml^TNAUlFW|`y-8#dXuX{FLnjNU?aY_`4`0+^?ctC4KP{9e!+ANf z1$gQdm|X07hwO8iS4CVVd!{Hm#bcg=lVoB*nLvYXB$_>S9F;Q-~YRDFr2-W|uyYGx@s@vKP7b_NMaHQC4`Ph z3(}DyO;EZ(0zydWNH3vD7Z4SsNLNA?0hKDC*io_FoHOqGp7)%4&i(H9{k;EH*4P<) zkGTDJBQt^fkM; zDj1W%G(cIv^GMD`U$TL1k2a#Vs78r5oU731fYZU;2Wx~q|I5!77Ob)d~x{x zS^#hJ`!r1^lnP@9vd@IS#tBASj3*Ze$e@QuC3U9;O?rKAas6Oq%a?ap)?eV72^MBz zu^XfYD>B{vtZxXi;C&ISCO*1w%Or?VMMr&B;9bAL8ozSrZR9vU5UT5*$tRb`MxH!g zh604CXVdS=ck{nUV6>YT)mtoaxX&qb^!*ih zCJdjj|FV9$8n+w@%vjeJ!m7n`d^8!>IFO3Sgh)OY{g5#s86Uj4uW_(+gU<{*KeemW=x4KS}-nzSDYk z{?&ZtS?;LePq*F9esQF;Rt9@Hm@Vs*2bknnuDzn&o7g-O;xo1cR%KU-@YRyMcR-j8 zX7=s;>Z#YV?~dMSU12OWWK^$dnPvyYF-E(2pr2y6shb6R5k0n<;Q=U_ac`*D);{n=4m z1@hTT;zteTFtx|xAuB`g!_u^ho*xWVT2+Xr4mVnjM;36$n~Og89Nx!%&kCD#9Qlzu z)%$EBFUwy4b$*vezxU z%Zg5T@Dr9g`IiC~C=_qIN%;Nt{zqU)O{($Zr}@!?r&9Ajx%0^p)zJ z_lyh2Q)`quKe!LqJsQPaOVgORF9YE#rSBHaOEx^zN;}+eg9>@E3%0CSoJr_%7k510 zWOuZKFXm&S=q%Nzgk3Z+8q+i?h8TFPa+@=_d~h}tN1bf8hSIRc_xGmrG9!3qL#wrW zb+jlw7Zsn9Q->O}-wJ~l&Rt~VN+LK7ExfAPr$`&a~#YXw`9+nmInJ81l+iE*D&Qso^ zp2EIXq`Zzq{*VSX#ik$KdSdQUYf;QfX(Zs$pcbBPMJP{5;$jubpKYiOrxe5s+)8#_ zb;Y;U=X|xaX?o#G#Xn&M`T@*L${fR)0lODYWrfAb1Yr{n&{AvyTMUXU{jd*la?zP0 z_xc0hNskgPx(xhW#|b={jFp1CCTKdooCsC4vIacI+=)*e-)gCU7n_H^Id6R5lADQ3 zr{8OJ>X&!P_4Z@p$s%VbDvwjvPzsW-`yYdqY791y@-C1H@#E*bc46OjrzS&AKtuJ$ zln#XpOY-xodsg_6C%egb_k#Wpijwa_+;jIwaa2s&u9(`~>2%o>DQ7KQJ6oz5XAZk& zTIy?Ts{=+MmK?TBP|c|#&_~y_pSjs(jK5|I(8Ip;9!GR?Dott|tzqH5;vS>b2I{CR zR}ZT~y0BDzJkh zuNZRF+EA-+RcZXG&X^uS`?nR|Iioyx_z< zYS3LUC~`nUuRiCVZN=#gkOH|tBahk}VFl*mHGjqr-GjSECjVTI>F&`v#Sf~@ZDP!L zu@x%5Ii(4ywC1y2if2&Um7@1pwnK8DyY_YuBhm(z5(x+;kNS&q3eCDnS<6#zcSH;G zzuW12`q1ZZHF>`)6c44-Phw0%uccJr-UilMf;Y1xDe1WO@EBkMD@;Atgz8eN?HKJ4 z>Ed>eBI?{ndSJYxBlna%^JG~EZISg470JI@s#s*K2=wZj=*Pm_O6QWUuPXA^)~PPy{>#Rs+kZBiC^SOm35 z+Q@kFIX!5yeTJ)RTu2tB?koHSoA@vOfOi(*XIVJ}%(hwBtxnHR{V1E1tFY_?JgcA2 zI9ZSQ?Wj^*nd`70Wu(!!>7-4nO z%$Vj`^$g}KlvyY1r-DJ;4~Y0J*Fih&Gm53u5m zZv!SIFiHG92A4Q9eP-z!`O%>>)@2YE=4jr@DLH%oJH@E4ys{r=8Bn52%V0FjcXOtP zbWJ*v^iF+ZPI9Q}toFT1ZSabzocUx+ z@MC>2z)&KiyJk*+biT9^fqwCIJ%Nl^XqR zhzfVq)KtHER(Qq8vb_oPbqi}p7m1-mYVvNFv=f;_G|n9xt`?rIsN(I!vL%SNgHlTo zlEw_}uHO)1Y8%Cpq7_p0t4j~MW)eml_-pD=<81H>6(hv-j`qv#+!Ac*L-py@aa6^v z2G_Bulw3-i0BhHb?nGUa-zonkMn)H?Rx@ zZ}8%Us1axh5miv2$iuHSr%%%05H5@z?XaA8mQmIaFh#MyiXKk4)p$*~XxlHzdoKFw zbvaVQQaTC$pmd;JY3F3S8@&^|EZn>5d1W7sOz8*&qh4lhU*%CjD;rcBYf2D1o}IK& z4N@0*g{hv~wN~i5dvRI=;p8A`xtuX}@5o3|P!DDz0||7+H&+>Rr+iUU4^h82`*rAc zLal78h8wNO>D&&lQt}MUXWbCE%#`U*Kp&jG9)qWtx=Vac$Z7hi${*6>XeY;MxX!Qg z!MPQ9qP3;4^_Hzi@v^bk1-9k)Q>1qN;H8vQ(!a#czcr3=4gMkwMt!|BUH%*J5p-Pg z6ME9uw-;m$>mpyW%&Dk!%414)0FPixRI6;+$Bi1ulJK7z|HPKFOyr)ZSoDk=p5S+7z zV9sJ{1mBOz48H<(5PAD`Gegn2H?meEXC*ghrS})9BE~yX-j+7$@`iYF( z5_@pIq$nU{9G)hI*o0WitPHHc&ubhs$pFOipE`-Y0Ufwi&?tN>8HM8~Uu$&>5&7=w zC6QFL<8w3f@DCrQ>6>hb<;w(F7ulA%fIKgpa?LS&QoX|bhVb2F17wiP%pB|Oi5Ei%r30#6x7pcF%=kkJJxdO(`R%;|s;}4ERI24_ z7zcckNg;l}+EoTdBOgIeA%p6=+30+sB2vK`)5q%QayHCLCMPRvQr>~r;a#-kj7lZB zHsy|@wL|qpO*?~+O8!#kRYSP(uhXn>=(iqqJA3EXB+adcVeUQ=T)L@6MTSw%9|lReZy zEow~04+WCqq*y;CsD?%kA0GgVoAd{(? zS3P9@YYdCby+5i0qMclKJFf%zs8n^*_@Qde>`IP*mf3jq)v5r{um=!VxRPaVy=>WP zI9p{_TpgS4>y@tjtJ$y8td<$zEGF>inV}1GZcpjmu(citI7oJ|2H;O;hezQ^3kA ztyKL8jRK|8oIsx&GOOmU@Xic0`3-4wd?%~;Y7*qVqynM9rzO zcTvl5L>UFW*>)4TRBcc}RXky{;jHnxW8(Rx+@Xw0cV%$edHNv+#57Rxa?JX`w5dTG zcWl#_Fh zMT{w2hIn83X7bH{^j_Mj<&cD+*Z`1BNcZ@#rdo_kwvZx-Rk6VnSTBuD4sa(Y#Fv#*V02%aIwpUKVb>;NS) zIa&Bin`bA=3x;xeVGcMs`3r3Qvv73(0ppsQDvO^u`O1W0Wm!vT0Z;&Az@#oBk3Bev zNn!xaIubHm*(%v)O@{=FSTh3>!U0Fd=F{6w&=0TX7`#gcZ55(qc(6Gh0vD+%>B;)F zjX`i1rSi%ItLCl`>Ev^sXcqgW1hsI)q{n4&pnN>9C5t1CRUfV`zXIBkbdxu;GAYi^ z_u*zL%Q#>2ezFPED1@-nzPWQZj|nTg*RG7bwFNhEP&c`#sP-|;&u7F%vbjhP#=!)F z9wF9a(Vopj$ZeV_cQ-rN+sO9fB%zSRXa{)*5N*cb@MP;8fN?>Hn6JMI1AHP@EF83$ zu2@16-9R7ix;78hU-IxK>ZqZ_VLDQ4i-dr=D?PF?&mK2Amwa$HAIkOJ6f5GYc;zyz z>tExAX%BSg&6v2wD;?z#q68jh?6mHI#>T4h@u9!U9M>i9Up34);>+9SQK@KP<@2MS zpI2n_Q500{BaP|1B zY6~63LGy^G!G@QH^0hJoI!gS$joi9j9steJ0cLYi<>lXBZ1l-B_^j{^7KTY(U=YMr z_4?mRiW;${hYc583;P>Vm{&G$0ygSXmE<#E!5@ZroB*PGtJ%(yqaq5aa& zc5fqzb|EO0Dw+*Yv1#3UX)tihND0s?`BETXfteLhRW$OGYuu|X1RHaXrY`51wpN;i z7&h?dw(6kQBY9Kexj(uY9|EsrD116VD-BHhMXJhYw%jwCLsvf<^aVfsDP4e-shiU& z3S>KLLY;f!xj>>Q9(;e8VNOh{h|4dlc}k}$8)t2*~veWM_`{^(WMB%)OdVOlO1d(G8pT2^PVoI?Yy(QqYg zmf#2U<#V{9W$WtUSyuyHZA%p!K1IB54L_@~m6D<&wV6AVyp!1~*0400%f`7$vt)xl zly&bU-Dp_gOQOn$%$V&hJ($a_Z+8arfMOmzRKVU?-)^6riw4pOZf9Hiqn>(1i2vm>jj#oZ2t5?Gp z6xjNh)DA0q=ZQZNl}7HUYUF(@te^rZN3uN;p0rdLql-nb3emHMTwBXJXI93rNqV2w zGN7dh+gOJGnuTsa|73|^#mAa zTQ-a#zY4Iy%)Bx7ZQG6F(Jzb0)NlI}ctGi0F|D02?1l%uRqZ_PZf3%2#+x@{yk32G zlp&iB!f{HdK6@h6`t3Ff20Ah0X*0=phe8GEq@BN-3@uP}=!{>Uakt|lEsieyvbvHx z!jC_#VPrEhn`+Sl2nWP#lt+5tm24ZI|j)U$bWeY62=NFfp zb6qtQZfel5!uK`W8KYBz?1!pD)iMYeyCw?Nja}zUI`nO8o8{wEcXDynbbRBSgnHH= z#S-!HFd#y;h%b0}nwJEoZ(#SmtJyC5=I~&r;%E5($N?B$NuG3GnbUI}7TgVAaSAIO z8mepu!9)s7sTP|@6{9HS3HfIp!DnpsHU`Cws%O3MiWvEM8Aw&PYGlp*+`aeEp30P~ zvJclZN9RN-eaqs^&!hE7i6-mw#N|QzLUh#xI^ao#3EY51Ofk@CdGA5L0=|{zR-_5YjS9kof7!D$8>aN?tnF5(niQole>oQUi{0 z#%VDE-kP%8U5G8?URaWLN@V68+-G?pJjkd3TwiPwK75;8g{3+*E5eh6E`0k+y$85vg#{n zk_qM8$4X30yyFdtMm&Ve9MespD9D12ggq&5j;A90k3_Bk3*AgOAMJ<*r(t_!V<%n* z@DW4MSV%huvIo}Zbk&Vg9a2zI$j07E99{*5bhaWMPnDoke+7e8xwlww4PB!)sp&gP zFN!Zr*x3(BF~zlTcP%JVVGic3oi>iktJv2S?)SY8m93n}MVZux=*;(WBO~VBS${@& z240oS_izdG8JqL|MTNSd6FEH#`@TMK4v@O;n0AWw&nm3s6I6N0dT-J{V@ zTsAruZl-NfNjB&<{7xJi2fEAWp)EHO_-e@Y3!S$8qP9TqET6eH?%bHA#abrbm)jtR zcoXwZ7Ua_z)>5gJ%u2S5eTD0E10feV~e!^iW-QIgzRoHC8R*(4z z-@}_hlz8R64fGREU*U&3POQ&K%3%^Nk6-&_%SLcnpuyaF*s{~UngoF>$$t>HY-vIG;{BPJaA`q239hb|#deS4O_^NYQq?XMB-|sLse+jyFCv9p)?_*dh+7?>!&WUZWeOAGSY)EvtH^J8i*@8Qf+)*ND z5QcxOs}zvoxk}$nSd&hNosEmq)0z3rx+4R!&PH%-CWyZE9U^TCFa)? z#JyM<$~s8rbQ>L{p)F87kIRE&=MJ~Ksjv{S|+VqAYI(CVACtYE8;4=Yag-`7h2 z)Hlkc0Ds*4?xd1yb2SuOCRm{7>RPw8YV>{3o{`RmY$Vzb4u4{rXe`BY85Ph`&}^%k}xxZT4IZaDX}h$4pd zBIH_q6fA)JvG=3{**A;A2TUxcyNH8oC<32iM5J7A#)TSM!)>`Qi6~O$aA%`?P0AZ0uQK$woRvxi8RjCUO5T z3%a{x1QZvRhnS4_vZpSMl&&^emq3O>NuHR2 zw;+Fsew8QecnpK>PlTr$+!0 z^^{k+@`C#j`gQgACK{ex87;5LvefaH#>BIW%!k6Ej{ORNg7U zFD_xD^Q2dmTKyZKyQ1MDRzIJ$AweTmb6I;&-->KMshu;V0-n61sQW=Vcj%FE5u(9X zJ@Z~_K0c75TRNiS+TuRhZZqjc3-!EwWk0f@2o!xcGtBYoMx?N^_C>+&m-cT}KgN5y zkMjjK{ut1)>R_u|q6iB5qSQ40bl`tv34eLuzYZA{u;GD5n`Wp<1G?JGLsEb@oub1- zLHFX+ZJrj!CA^JDK^iXmOU4_)`~zzfqM^h%STlE^AtO0 z-b$+8s|kxvhh(ce2PnYk{haBt@NDeq)R&T{!W0ITz zN=Jas&6w&PF_)jQ9^CTzQ0f(hJ#cO#YZ7nEkC=^9Pgz2cELK9p6?_CwWA=y#9WJFf zTm)iE4c!0dyMP_fJK+x43*Ic_!&IbqVl}c2H}rZzx z>TZtBn~`fZh|`4Aw!_o}uFfB7kHR9lcakM{-4O0RDWcf8dAz zxWM<_q3YVMaoc^v=fs? zM&^qnB=fqGO?IYjD7VyW_pdeq5)N!-; z%zIw1Fg;6({{;&e3zZnV4aJGcK9YRKfUw?OLn?jm9c#8SGTVF02dljCU6vK>!!7nd8`52GcwzhI#X0+Ml*NizPhYjlfnMM!o%*C z{CjmlU*fa0d?3|iNqDuQ^ql=jVTRl8a9y&P@R|`Aje>5HT#sk{k;MPqIsfT@S+vN2 zQ0O+`<^RQmpdEXwwxo6EtXuiHmYr>Nsf^!Tf8lir$)Lpuo7ltI0QQUxE~Z3n-JoT? z^j92tfzcts70PuE9X1Mb?Zuy*l1m!eu_N{dsi2Z(fvt*ynOM$`l{6RO@Gd@KQ_6O+ zs2`mcI>t%?6KG$rd8$R$cS`1yMqY!r5aVxvk6?G(4sLu>emUSq&7k38fzF*DQhlyu zJjgRw><#~*HEYn%(<+sVZ%jCaGBX};i-kc7=URUQ)>HX9Omi%D9&js5vCoSB2D|}s zerOPMzJ01Y*m$pDqTx3nRV&U)_Cw#n%TDlbKMr1=gV|lr_Ag;gIk$&9ZNPo9V$}g8JlSH+anb+HA41 zLSEcZ$o86f>R^P@X8IW6>$A?#;}a*J5RF%l06SksR1QtVNh=R;fy3{kO}pqafKI6A zE{E!J_miS>cW(7ry@{&3UqBiGb0uDlZNy_Kjc$8F=cddR4r>ChQMuzJ2(5yC52U89U{)0r!@F}~r)!@*aX<*$7VPsBg zSDmHKOX_ws}^L*~7Y~)H_ z*GvAcdnK{TZ83Ht!tRZI(}Ok7=4m;={}=?c4|s{sbwRNKtacss!X_wm&)}wGjH{ zGnHKJ6j$lp>Qez9Yg>Knp8nY+N5{G-Own;6Vs7ZQ>%SI;{~K#^ z#9&}JZnwl^)SBRh2emxT`bQ`Iu{Wbj`3#}kuFQDHMOd8B87~my<^d`>{x3QAzxN9N zewO8lcF!^=ImW2-(=sc1ao_{cqU_k!{_*qLcp=b&S~KQDwda))^t9c(W#*v77eeug zdqyjU;}6c2v~v_C@`k)3^h8mf&hyFrnd$zsFZ%a){m-+<4m|u^m;>K zOVLAH zwqMUPvObT@b~-;Q{6Bpz7C+9@8hgTdK96qMrWwP>?eOq4NKl zxczybKf?jjqkcZxxDCR`NTJZs*gEz}&eiO%6&U?uP(Hm8&9515nCeje+=@8zDZFZCzP{>*a=dy$?G%>Nq@B0eCa7sd#J7IkJq zm!0f+-PaeoT3G()iv|47jiZ>>>!|Tjs!_^hsvDcmGmXrLhma$air4Pwtw&y@*TW!J z92IMU3%Q#;{TZUvNV&xE6sGWTcOMk8Arr7!U5B}ML-s_`t!wa*0)so)8?@@0?e$U7 zoZzmaPpL}$QSg)45*y=dbm@xm3&Ey$DAO~{cpgiQ|I(xWcQ1fq6;BUin661Sykgs5 ullEp5c{-Ly7?1km;*=ymtZauJUEbUWocnWm|MR!>f11Mohu8jo`o92pXb&|2 literal 0 HcmV?d00001 diff --git a/VG Music Studio - WinForms/ObjectListView/Resources/filter-icons3.png b/VG Music Studio - WinForms/ObjectListView/Resources/filter-icons3.png new file mode 100644 index 0000000000000000000000000000000000000000..80178919d5b63ab83df2b7c669ab0779a49aa2aa GIT binary patch literal 1305 zcmeAS@N?(olHy`uVBq!ia0vp^+(695!3-or^+GlRDVB6cUq=Rpjs4tz5?L7-m>B|m zLR>-OY^*$7-1heN&W=uQZf+hP9-f|_-hqLkp`l@6VHp`2`FT0T#l=NMCB?<1B}K(e zO)YJ0ZTkrW0Q7{?;10DkVe@>jlz`)2*666>Be`EuO;P33J zzzE?i@Q5sCVBk9h!i=ICUJXD&$r9IylHmNblJdl&REB`W%)AmkKi3e2GGjecJqvT| zhFG8?MUW!rqSVBa%=|oskj&gv1|tJQLn{LlD?>vCBSR}gBP(MQ&19DgK*fQcE{-7* z;jU-hg&GtXm>qO(2x@=3_wWB9j=m(lnN#F*)B0@kMOI+Vn;qXQmn}9~_T~=D z+XX%QB%0i|8O%7kPm;e@o7oiiks3xCpOkgD9y%{jLaR6H_xy85}S Ib4q9e03rsV#sB~S literal 0 HcmV?d00001 diff --git a/VG Music Studio - WinForms/ObjectListView/Resources/sort-ascending.png b/VG Music Studio - WinForms/ObjectListView/Resources/sort-ascending.png new file mode 100644 index 0000000000000000000000000000000000000000..a21be93fa9f9415cfb652bd3f1fea9f14d09338c GIT binary patch literal 1364 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstPBjy3;{kN zu0U~1E30yA9S={>)b#X{vND5!2?h}ptRkldbB_{`{(WMnzDc9vUQs_&0BM1<bo1Nd^W+hLRw^;Qu2VFa&>RR|SSK zXMsm#F#`kNArNL1)$nQn3QCr^MwA5Sr=C^PE^0&oV$SnRjJ>Se)@el9KzgPb(xGQ0`HCE5kgy}D{ z`~Ouz>UtNs#CGW&tXB%1p1&yert|7m7J~gd`a(DPrKwMx!92N9ht=$^ar1VS;Aj5% zj}5qGI!+k)v>%we)P+^=ZeLFF8>M517RUD-ikl)^c|iKt?Ty_hV~;%rx{JZn)z4*} HQ$iB}j@iab literal 0 HcmV?d00001 diff --git a/VG Music Studio - WinForms/ObjectListView/Resources/sort-descending.png b/VG Music Studio - WinForms/ObjectListView/Resources/sort-descending.png new file mode 100644 index 0000000000000000000000000000000000000000..92dbe63f20afc6c51cf837b3aa6b0de64bfd126d GIT binary patch literal 1371 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstPBjy3;{kN zu0U~1D=Pzs0xPd_tHAPdYaI_y&(!qvlCm-bj|PW`1wjezAz8Cy3uk*KEb+`(5>mJ_ zzIa_`?dFp19SsHwZPq$dt*v|9EvGtJPjGOU;1Mu2FmhH_{erCc1!=j<%W^iBl`XC9 zUtQhMFk$`t#fzse-MhMN`o{iwySt|BpSf(^rcI02A6d8a^twH#_Z~dB@6@g1%a)zk zwD#DAn

dzjor>jk9NOoV#%O_LIwZpWS`@{PE+*Po6w|`R*Oi*`r`I1Sk&yjoeww z7#J8CN`m}?|Br0I5d5886&RwN1s;*b3=DjSK$uZf!>a)(C|TkfQ4*Y=R#Ki=l*$m0 zn3-3i=jR%tP-d)Ws%K$t-4F{@qzF>vT$Gwvl9`{U5R#dj%3x$*XlP|%Vr6KgU|?xw zWMXAtBrKh33{*VX)5S4FBRIB?o$rtVkE^f7?kRgUb8oi9nmze<{a1>@M6*p#p1k~N z-Eu`SL2~V)lb*#tCz*=Q4?JJNw&&=s+HH*Mod1ib@bvsXa8~LjPfF4c_V0OmAFLZw zPrO#0*_f`&8uipQN%z&d#SVpjdlp=i+dJcJioJkf_=1P$=Bx_(H07fFXSt;FNoxyO jXDxlJeOk+k^#@aL!J4$pI`)r1=P`J?`njxgN@xNAhOF7) literal 0 HcmV?d00001 diff --git a/VG Music Studio - WinForms/ObjectListView/SubControls/GlassPanelForm.cs b/VG Music Studio - WinForms/ObjectListView/SubControls/GlassPanelForm.cs new file mode 100644 index 0000000..300ca8a --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/SubControls/GlassPanelForm.cs @@ -0,0 +1,459 @@ +/* + * GlassPanelForm - A transparent form that is placed over an ObjectListView + * to allow flicker-free overlay images during scrolling. + * + * Author: Phillip Piper + * Date: 14/04/2009 4:36 PM + * + * Change log: + * 2010-08-18 JPP - Added WS_EX_TOOLWINDOW style so that the form won't appear in Alt-Tab list. + * v2.4 + * 2010-03-11 JPP - Work correctly in MDI applications -- more or less. Actually, less than more. + * They don't crash but they don't correctly handle overlapping MDI children. + * Overlays from one control are shown on top of the other windows. + * 2010-03-09 JPP - Correctly Unbind() when the related ObjectListView is disposed. + * 2009-10-28 JPP - Use FindForm() rather than TopMostControl, since the latter doesn't work + * as I expected when the OLV is part of an MDI child window. Thanks to + * wvd_vegt who tracked this down. + * v2.3 + * 2009-08-19 JPP - Only hide the glass pane on resize, not on move + * - Each glass panel now only draws one overlays + * v2.2 + * 2009-06-05 JPP - Handle when owning window is a topmost window + * 2009-04-14 JPP - Initial version + * + * To do: + * + * Copyright (C) 2009-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + ///

+ /// A GlassPanelForm sits transparently over an ObjectListView to show overlays. + /// + internal partial class GlassPanelForm : Form + { + public GlassPanelForm() { + this.Name = "GlassPanelForm"; + this.Text = "GlassPanelForm"; + + ClientSize = new System.Drawing.Size(0, 0); + ControlBox = false; + FormBorderStyle = FormBorderStyle.None; + SizeGripStyle = SizeGripStyle.Hide; + StartPosition = FormStartPosition.Manual; + MaximizeBox = false; + MinimizeBox = false; + ShowIcon = false; + ShowInTaskbar = false; + FormBorderStyle = FormBorderStyle.None; + + SetStyle(ControlStyles.Selectable, false); + + this.Opacity = 0.5f; + this.BackColor = Color.FromArgb(255, 254, 254, 254); + this.TransparencyKey = this.BackColor; + this.HideGlass(); + NativeMethods.ShowWithoutActivate(this); + } + + protected override void Dispose(bool disposing) { + if (disposing) + this.Unbind(); + + base.Dispose(disposing); + } + + #region Properties + + /// + /// Get the low-level windows flag that will be given to CreateWindow. + /// + protected override CreateParams CreateParams { + get { + CreateParams cp = base.CreateParams; + cp.ExStyle |= 0x20; // WS_EX_TRANSPARENT + cp.ExStyle |= 0x80; // WS_EX_TOOLWINDOW + return cp; + } + } + + #endregion + + #region Commands + + /// + /// Attach this form to the given ObjectListView + /// + public void Bind(ObjectListView olv, IOverlay overlay) { + if (this.objectListView != null) + this.Unbind(); + + this.objectListView = olv; + this.Overlay = overlay; + this.mdiClient = null; + this.mdiOwner = null; + + if (this.objectListView == null) + return; + + // NOTE: If you listen to any events here, you *must* stop listening in Unbind() + + this.objectListView.Disposed += new EventHandler(objectListView_Disposed); + this.objectListView.LocationChanged += new EventHandler(objectListView_LocationChanged); + this.objectListView.SizeChanged += new EventHandler(objectListView_SizeChanged); + this.objectListView.VisibleChanged += new EventHandler(objectListView_VisibleChanged); + this.objectListView.ParentChanged += new EventHandler(objectListView_ParentChanged); + + // Collect our ancestors in the widget hierarchy + if (this.ancestors == null) + this.ancestors = new List(); + Control parent = this.objectListView.Parent; + while (parent != null) { + this.ancestors.Add(parent); + parent = parent.Parent; + } + + // Listen for changes in the hierarchy + foreach (Control ancestor in this.ancestors) { + ancestor.ParentChanged += new EventHandler(objectListView_ParentChanged); + TabControl tabControl = ancestor as TabControl; + if (tabControl != null) { + tabControl.Selected += new TabControlEventHandler(tabControl_Selected); + } + } + + // Listen for changes in our owning form + this.Owner = this.objectListView.FindForm(); + this.myOwner = this.Owner; + if (this.Owner != null) { + this.Owner.LocationChanged += new EventHandler(Owner_LocationChanged); + this.Owner.SizeChanged += new EventHandler(Owner_SizeChanged); + this.Owner.ResizeBegin += new EventHandler(Owner_ResizeBegin); + this.Owner.ResizeEnd += new EventHandler(Owner_ResizeEnd); + if (this.Owner.TopMost) { + // We can't do this.TopMost = true; since that will activate the panel, + // taking focus away from the owner of the listview + NativeMethods.MakeTopMost(this); + } + + // We need special code to handle MDI + this.mdiOwner = this.Owner.MdiParent; + if (this.mdiOwner != null) { + this.mdiOwner.LocationChanged += new EventHandler(Owner_LocationChanged); + this.mdiOwner.SizeChanged += new EventHandler(Owner_SizeChanged); + this.mdiOwner.ResizeBegin += new EventHandler(Owner_ResizeBegin); + this.mdiOwner.ResizeEnd += new EventHandler(Owner_ResizeEnd); + + // Find the MDIClient control, which houses all MDI children + foreach (Control c in this.mdiOwner.Controls) { + this.mdiClient = c as MdiClient; + if (this.mdiClient != null) { + break; + } + } + if (this.mdiClient != null) { + this.mdiClient.ClientSizeChanged += new EventHandler(myMdiClient_ClientSizeChanged); + } + } + } + + this.UpdateTransparency(); + } + + void myMdiClient_ClientSizeChanged(object sender, EventArgs e) { + this.RecalculateBounds(); + this.Invalidate(); + } + + /// + /// Made the overlay panel invisible + /// + public void HideGlass() { + if (!this.isGlassShown) + return; + this.isGlassShown = false; + this.Bounds = new Rectangle(-10000, -10000, 1, 1); + } + + /// + /// Show the overlay panel in its correctly location + /// + /// + /// If the panel is always shown, this method does nothing. + /// If the panel is being resized, this method also does nothing. + /// + public void ShowGlass() { + if (this.isGlassShown || this.isDuringResizeSequence) + return; + + this.isGlassShown = true; + this.RecalculateBounds(); + } + + /// + /// Detach this glass panel from its previous ObjectListView + /// + /// + /// You should unbind the overlay panel before making any changes to the + /// widget hierarchy. + /// + public void Unbind() { + if (this.objectListView != null) { + this.objectListView.Disposed -= new EventHandler(objectListView_Disposed); + this.objectListView.LocationChanged -= new EventHandler(objectListView_LocationChanged); + this.objectListView.SizeChanged -= new EventHandler(objectListView_SizeChanged); + this.objectListView.VisibleChanged -= new EventHandler(objectListView_VisibleChanged); + this.objectListView.ParentChanged -= new EventHandler(objectListView_ParentChanged); + this.objectListView = null; + } + + if (this.ancestors != null) { + foreach (Control parent in this.ancestors) { + parent.ParentChanged -= new EventHandler(objectListView_ParentChanged); + TabControl tabControl = parent as TabControl; + if (tabControl != null) { + tabControl.Selected -= new TabControlEventHandler(tabControl_Selected); + } + } + this.ancestors = null; + } + + if (this.myOwner != null) { + this.myOwner.LocationChanged -= new EventHandler(Owner_LocationChanged); + this.myOwner.SizeChanged -= new EventHandler(Owner_SizeChanged); + this.myOwner.ResizeBegin -= new EventHandler(Owner_ResizeBegin); + this.myOwner.ResizeEnd -= new EventHandler(Owner_ResizeEnd); + this.myOwner = null; + } + + if (this.mdiOwner != null) { + this.mdiOwner.LocationChanged -= new EventHandler(Owner_LocationChanged); + this.mdiOwner.SizeChanged -= new EventHandler(Owner_SizeChanged); + this.mdiOwner.ResizeBegin -= new EventHandler(Owner_ResizeBegin); + this.mdiOwner.ResizeEnd -= new EventHandler(Owner_ResizeEnd); + this.mdiOwner = null; + } + + if (this.mdiClient != null) { + this.mdiClient.ClientSizeChanged -= new EventHandler(myMdiClient_ClientSizeChanged); + this.mdiClient = null; + } + } + + #endregion + + #region Event Handlers + + void objectListView_Disposed(object sender, EventArgs e) { + this.Unbind(); + } + + /// + /// Handle when the form that owns the ObjectListView begins to be resized + /// + /// + /// + void Owner_ResizeBegin(object sender, EventArgs e) { + // When the top level window is being resized, we just want to hide + // the overlay window. When the resizing finishes, we want to show + // the overlay window, if it was shown before the resize started. + this.isDuringResizeSequence = true; + this.wasGlassShownBeforeResize = this.isGlassShown; + } + + /// + /// Handle when the form that owns the ObjectListView finished to be resized + /// + /// + /// + void Owner_ResizeEnd(object sender, EventArgs e) { + this.isDuringResizeSequence = false; + if (this.wasGlassShownBeforeResize) + this.ShowGlass(); + } + + /// + /// The owning form has moved. Move the overlay panel too. + /// + /// + /// + void Owner_LocationChanged(object sender, EventArgs e) { + if (this.mdiOwner != null) + this.HideGlass(); + else + this.RecalculateBounds(); + } + + /// + /// The owning form is resizing. Hide our overlay panel until the resizing stops + /// + /// + /// + void Owner_SizeChanged(object sender, EventArgs e) { + this.HideGlass(); + } + + + /// + /// Handle when the bound OLV changes its location. The overlay panel must + /// be moved too, IFF it is currently visible. + /// + /// + /// + void objectListView_LocationChanged(object sender, EventArgs e) { + if (this.isGlassShown) { + this.RecalculateBounds(); + } + } + + /// + /// Handle when the bound OLV changes size. The overlay panel must + /// resize too, IFF it is currently visible. + /// + /// + /// + void objectListView_SizeChanged(object sender, EventArgs e) { + // This event is triggered in all sorts of places, and not always when the size changes. + //if (this.isGlassShown) { + // this.Size = this.objectListView.ClientSize; + //} + } + + /// + /// Handle when the bound OLV is part of a TabControl and that + /// TabControl changes tabs. The overlay panel is hidden. The + /// first time the bound OLV is redrawn, the overlay panel will + /// be shown again. + /// + /// + /// + void tabControl_Selected(object sender, TabControlEventArgs e) { + this.HideGlass(); + } + + /// + /// Somewhere the parent of the bound OLV has changed. Update + /// our events. + /// + /// + /// + void objectListView_ParentChanged(object sender, EventArgs e) { + ObjectListView olv = this.objectListView; + IOverlay overlay = this.Overlay; + this.Unbind(); + this.Bind(olv, overlay); + } + + /// + /// Handle when the bound OLV changes its visibility. + /// The overlay panel should match the OLV's visibility. + /// + /// + /// + void objectListView_VisibleChanged(object sender, EventArgs e) { + if (this.objectListView.Visible) + this.ShowGlass(); + else + this.HideGlass(); + } + + #endregion + + #region Implementation + + protected override void OnPaint(PaintEventArgs e) { + if (this.objectListView == null || this.Overlay == null) + return; + + Graphics g = e.Graphics; + g.TextRenderingHint = ObjectListView.TextRenderingHint; + g.SmoothingMode = ObjectListView.SmoothingMode; + //g.DrawRectangle(new Pen(Color.Green, 4.0f), this.ClientRectangle); + + // If we are part of an MDI app, make sure we don't draw outside the bounds + if (this.mdiClient != null) { + Rectangle r = mdiClient.RectangleToScreen(mdiClient.ClientRectangle); + Rectangle r2 = this.objectListView.RectangleToClient(r); + g.SetClip(r2, System.Drawing.Drawing2D.CombineMode.Intersect); + } + + this.Overlay.Draw(this.objectListView, g, this.objectListView.ClientRectangle); + } + + protected void RecalculateBounds() { + if (!this.isGlassShown) + return; + + Rectangle rect = this.objectListView.ClientRectangle; + rect.X = 0; + rect.Y = 0; + this.Bounds = this.objectListView.RectangleToScreen(rect); + } + + internal void UpdateTransparency() { + ITransparentOverlay transparentOverlay = this.Overlay as ITransparentOverlay; + if (transparentOverlay == null) + this.Opacity = this.objectListView.OverlayTransparency / 255.0f; + else + this.Opacity = transparentOverlay.Transparency / 255.0f; + } + + protected override void WndProc(ref Message m) { + const int WM_NCHITTEST = 132; + const int HTTRANSPARENT = -1; + switch (m.Msg) { + // Ignore all mouse interactions + case WM_NCHITTEST: + m.Result = (IntPtr)HTTRANSPARENT; + break; + } + base.WndProc(ref m); + } + + #endregion + + #region Implementation variables + + internal IOverlay Overlay; + + #endregion + + #region Private variables + + private ObjectListView objectListView; + private bool isDuringResizeSequence; + private bool isGlassShown; + private bool wasGlassShownBeforeResize; + + // Cache these so we can unsubscribe from events even when the OLV has been disposed. + private Form myOwner; + private Form mdiOwner; + private List ancestors; + MdiClient mdiClient; + + #endregion + + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/SubControls/HeaderControl.cs b/VG Music Studio - WinForms/ObjectListView/SubControls/HeaderControl.cs new file mode 100644 index 0000000..004deff --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/SubControls/HeaderControl.cs @@ -0,0 +1,1230 @@ +/* + * HeaderControl - A limited implementation of HeaderControl + * + * Author: Phillip Piper + * Date: 25/11/2008 17:15 + * + * Change log: + * 2015-06-12 JPP - Use HeaderTextAlignOrDefault instead of HeaderTextAlign + * 2014-09-07 JPP - Added ability to have checkboxes in headers + * + * 2011-05-11 JPP - Fixed bug that prevented columns from being resized in IDE Designer + * by dragging the column divider + * 2011-04-12 JPP - Added ability to draw filter indicator in a column's header + * v2.4.1 + * 2010-08-23 JPP - Added ability to draw header vertically (thanks to Mark Fenwick) + * - Uses OLVColumn.HeaderTextAlign to decide how to align the column's header + * 2010-08-08 JPP - Added ability to have image in header + * v2.4 + * 2010-03-22 JPP - Draw header using header styles + * 2009-10-30 JPP - Plugged GDI resource leak, where font handles were created during custom + * drawing, but never destroyed + * v2.3 + * 2009-10-03 JPP - Handle when ListView.HeaderFormatStyle is None + * 2009-08-24 JPP - Handle the header being destroyed + * v2.2.1 + * 2009-08-16 JPP - Correctly handle header themes + * 2009-08-15 JPP - Added formatting capabilities: font, color, word wrap + * v2.2 + * 2009-06-01 JPP - Use ToolTipControl + * 2009-05-10 JPP - Removed all unsafe code + * 2008-11-25 JPP - Initial version + * + * TO DO: + * - Put drawing code into header style object, so that it can be easily customized. + * + * Copyright (C) 2006-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Drawing; +using System.Runtime.Remoting; +using System.Windows.Forms; +using System.Runtime.InteropServices; +using System.Windows.Forms.VisualStyles; +using System.Drawing.Drawing2D; +using Kermalis.VGMusicStudio.WinForms.ObjectListView.Properties; +using System.Security.Permissions; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// Class used to capture window messages for the header of the list view + /// control. + /// + public class HeaderControl : NativeWindow { + /// + /// Create a header control for the given ObjectListView. + /// + /// + public HeaderControl(ObjectListView olv) { + this.ListView = olv; + this.AssignHandle(NativeMethods.GetHeaderControl(olv)); + } + + #region Properties + + /// + /// Return the index of the column under the current cursor position, + /// or -1 if the cursor is not over a column + /// + /// Index of the column under the cursor, or -1 + public int ColumnIndexUnderCursor { + get { + Point pt = this.ScrolledCursorPosition; + return NativeMethods.GetColumnUnderPoint(this.Handle, pt); + } + } + + /// + /// Return the Windows handle behind this control + /// + /// + /// When an ObjectListView is initialized as part of a UserControl, the + /// GetHeaderControl() method returns 0 until the UserControl is + /// completely initialized. So the AssignHandle() call in the constructor + /// doesn't work. So we override the Handle property so value is always + /// current. + /// + public new IntPtr Handle { + get { return NativeMethods.GetHeaderControl(this.ListView); } + } + + //TODO: The Handle property may no longer be necessary. CHECK! 2008/11/28 + + /// + /// Gets or sets a style that should be applied to the font of the + /// column's header text when the mouse is over that column + /// + /// THIS IS EXPERIMENTAL. USE AT OWN RISK. August 2009 + [Obsolete("Use HeaderStyle.Hot.FontStyle instead")] + public FontStyle HotFontStyle { + get { return FontStyle.Regular; } + set { } + } + + /// + /// Gets the index of the column under the cursor if the cursor is over it's checkbox + /// + protected int GetColumnCheckBoxUnderCursor() { + Point pt = this.ScrolledCursorPosition; + + int columnIndex = NativeMethods.GetColumnUnderPoint(this.Handle, pt); + return this.IsPointOverHeaderCheckBox(columnIndex, pt) ? columnIndex : -1; + } + + /// + /// Gets the client rectangle for the header + /// + public Rectangle ClientRectangle { + get { + Rectangle r = new Rectangle(); + NativeMethods.GetClientRect(this.Handle, ref r); + return r; + } + } + + /// + /// Return true if the given point is over the checkbox for the given column. + /// + /// + /// + /// + protected bool IsPointOverHeaderCheckBox(int columnIndex, Point pt) { + if (columnIndex < 0 || columnIndex >= this.ListView.Columns.Count) + return false; + + OLVColumn column = this.ListView.GetColumn(columnIndex); + if (!this.HasCheckBox(column)) + return false; + + Rectangle r = this.GetCheckBoxBounds(column); + r.Inflate(1, 1); // make the target slightly bigger + return r.Contains(pt); + } + + /// + /// Gets whether the cursor is over a "locked" divider, i.e. + /// one that cannot be dragged by the user. + /// + protected bool IsCursorOverLockedDivider { + get { + Point pt = this.ScrolledCursorPosition; + int dividerIndex = NativeMethods.GetDividerUnderPoint(this.Handle, pt); + if (dividerIndex >= 0 && dividerIndex < this.ListView.Columns.Count) { + OLVColumn column = this.ListView.GetColumn(dividerIndex); + return column.IsFixedWidth || column.FillsFreeSpace; + } else + return false; + } + } + + private Point ScrolledCursorPosition { + get { + Point pt = this.ListView.PointToClient(Cursor.Position); + pt.X += this.ListView.LowLevelScrollPosition.X; + return pt; + } + } + + /// + /// Gets or sets the listview that this header belongs to + /// + protected ObjectListView ListView { + get { return this.listView; } + set { this.listView = value; } + } + + private ObjectListView listView; + + /// + /// Gets the maximum height of the header. -1 means no maximum. + /// + public int MaximumHeight + { + get { return this.ListView.HeaderMaximumHeight; } + } + + /// + /// Gets the minimum height of the header. -1 means no minimum. + /// + public int MinimumHeight + { + get { return this.ListView.HeaderMinimumHeight; } + } + + /// + /// Get or set the ToolTip that shows tips for the header + /// + public ToolTipControl ToolTip { + get { + if (this.toolTip == null) { + this.CreateToolTip(); + } + return this.toolTip; + } + protected set { this.toolTip = value; } + } + + private ToolTipControl toolTip; + + /// + /// Gets or sets whether the text in column headers should be word + /// wrapped when it is too long to fit within the column + /// + public bool WordWrap { + get { return this.wordWrap; } + set { this.wordWrap = value; } + } + + private bool wordWrap; + + #endregion + + #region Commands + + /// + /// Calculate how height the header needs to be + /// + /// Height in pixels + protected int CalculateHeight(Graphics g) { + TextFormatFlags flags = this.TextFormatFlags; + int columnUnderCursor = this.ColumnIndexUnderCursor; + float height = this.MinimumHeight; + for (int i = 0; i < this.ListView.Columns.Count; i++) { + OLVColumn column = this.ListView.GetColumn(i); + height = Math.Max(height, CalculateColumnHeight(g, column, flags, columnUnderCursor == i, i)); + } + return this.MaximumHeight == -1 ? (int) height : Math.Min(this.MaximumHeight, (int) height); + } + + private float CalculateColumnHeight(Graphics g, OLVColumn column, TextFormatFlags flags, bool isHot, int i) { + Font f = this.CalculateFont(column, isHot, false); + if (column.IsHeaderVertical) + return TextRenderer.MeasureText(g, column.Text, f, new Size(10000, 10000), flags).Width; + + const int fudge = 9; // 9 is a magic constant that makes it perfectly match XP behavior + if (!this.WordWrap) + return f.Height + fudge; + + Rectangle r = this.GetHeaderDrawRect(i); + if (this.HasNonThemedSortIndicator(column)) + r.Width -= 16; + if (column.HasHeaderImage) + r.Width -= column.ImageList.ImageSize.Width + 3; + if (this.HasFilterIndicator(column)) + r.Width -= this.CalculateFilterIndicatorWidth(r); + if (this.HasCheckBox(column)) + r.Width -= this.CalculateCheckBoxBounds(g, r).Width; + SizeF size = TextRenderer.MeasureText(g, column.Text, f, new Size(r.Width, 100), flags); + return size.Height + fudge; + } + + /// + /// Get the bounds of the checkbox against the given column + /// + /// + /// + public Rectangle GetCheckBoxBounds(OLVColumn column) { + Rectangle r = this.GetHeaderDrawRect(column.Index); + + using (Graphics g = this.ListView.CreateGraphics()) + return this.CalculateCheckBoxBounds(g, r); + } + + /// + /// Should the given column be drawn with a checkbox against it? + /// + /// + /// + public bool HasCheckBox(OLVColumn column) { + return column.HeaderCheckBox || column.HeaderTriStateCheckBox; + } + + /// + /// Should the given column show a sort indicator? + /// + /// + /// + protected bool HasSortIndicator(OLVColumn column) { + if (!this.ListView.ShowSortIndicators) + return false; + return column == this.ListView.LastSortColumn && this.ListView.LastSortOrder != SortOrder.None; + } + + /// + /// Should the given column be drawn with a filter indicator against it? + /// + /// + /// + protected bool HasFilterIndicator(OLVColumn column) { + return (this.ListView.UseFiltering && this.ListView.UseFilterIndicator && column.HasFilterIndicator); + } + + /// + /// Should the given column show a non-themed sort indicator? + /// + /// + /// + protected bool HasNonThemedSortIndicator(OLVColumn column) { + if (!this.ListView.ShowSortIndicators) + return false; + if (VisualStyleRenderer.IsSupported) + return !VisualStyleRenderer.IsElementDefined(VisualStyleElement.Header.SortArrow.SortedUp) && + this.HasSortIndicator(column); + else + return this.HasSortIndicator(column); + } + + /// + /// Return the bounds of the item with the given index + /// + /// + /// + public Rectangle GetItemRect(int itemIndex) { + const int HDM_FIRST = 0x1200; + const int HDM_GETITEMRECT = HDM_FIRST + 7; + NativeMethods.RECT r = new NativeMethods.RECT(); + NativeMethods.SendMessageRECT(this.Handle, HDM_GETITEMRECT, itemIndex, ref r); + return Rectangle.FromLTRB(r.left, r.top, r.right, r.bottom); + } + + /// + /// Return the bounds within which the given column will be drawn + /// + /// + /// + public Rectangle GetHeaderDrawRect(int itemIndex) { + Rectangle r = this.GetItemRect(itemIndex); + + // Tweak the text rectangle a little to improve aesthetics + r.Inflate(-3, 0); + r.Y -= 2; + + return r; + } + + /// + /// Force the header to redraw by invalidating it + /// + public void Invalidate() { + NativeMethods.InvalidateRect(this.Handle, 0, true); + } + + /// + /// Force the header to redraw a single column by invalidating it + /// + public void Invalidate(OLVColumn column) { + NativeMethods.InvalidateRect(this.Handle, 0, true); // todo + } + + #endregion + + #region Tooltip + + /// + /// Create a native tool tip control for this listview + /// + protected virtual void CreateToolTip() { + this.ToolTip = new ToolTipControl(); + this.ToolTip.Create(this.Handle); + this.ToolTip.AddTool(this); + this.ToolTip.Showing += new EventHandler(this.ListView.HeaderToolTipShowingCallback); + } + + #endregion + + #region Windows messaging + + /// + /// Override the basic message pump + /// + /// + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] + protected override void WndProc(ref Message m) { + const int WM_DESTROY = 2; + const int WM_SETCURSOR = 0x20; + const int WM_NOTIFY = 0x4E; + const int WM_MOUSEMOVE = 0x200; + const int WM_LBUTTONDOWN = 0x201; + const int WM_LBUTTONUP = 0x202; + const int WM_MOUSELEAVE = 675; + const int HDM_FIRST = 0x1200; + const int HDM_LAYOUT = (HDM_FIRST + 5); + + // System.Diagnostics.Debug.WriteLine(String.Format("WndProc: {0}", m.Msg)); + + switch (m.Msg) { + case WM_SETCURSOR: + if (!this.HandleSetCursor(ref m)) + return; + break; + + case WM_NOTIFY: + if (!this.HandleNotify(ref m)) + return; + break; + + case WM_MOUSEMOVE: + if (!this.HandleMouseMove(ref m)) + return; + break; + + case WM_MOUSELEAVE: + if (!this.HandleMouseLeave(ref m)) + return; + break; + + case HDM_LAYOUT: + if (!this.HandleLayout(ref m)) + return; + break; + + case WM_DESTROY: + if (!this.HandleDestroy(ref m)) + return; + break; + + case WM_LBUTTONDOWN: + if (!this.HandleLButtonDown(ref m)) + return; + break; + + case WM_LBUTTONUP: + if (!this.HandleLButtonUp(ref m)) + return; + break; + } + + base.WndProc(ref m); + } + + private bool HandleReflectNotify(ref Message m) + { + NativeMethods.NMHDR nmhdr = (NativeMethods.NMHDR)m.GetLParam(typeof(NativeMethods.NMHDR)); + // System.Diagnostics.Debug.WriteLine(String.Format("rn: {0}", nmhdr.code)); + return true; + } + + /// + /// Handle the LButtonDown windows message + /// + /// + /// + protected bool HandleLButtonDown(ref Message m) + { + // Was there a header checkbox under the cursor? + this.columnIndexCheckBoxMouseDown = this.GetColumnCheckBoxUnderCursor(); + if (this.columnIndexCheckBoxMouseDown < 0) + return true; + + // Redraw the header so the checkbox redraws + this.Invalidate(); + + // Force the owning control to ignore this mouse click + // We don't want to sort the listview when they click the checkbox + m.Result = (IntPtr)1; + return false; + } + + private int columnIndexCheckBoxMouseDown = -1; + + /// + /// Handle the LButtonUp windows message + /// + /// + /// + protected bool HandleLButtonUp(ref Message m) { + //System.Diagnostics.Debug.WriteLine("WM_LBUTTONUP"); + + // Was the mouse released over a header checkbox? + if (this.columnIndexCheckBoxMouseDown < 0) + return true; + + // Was the mouse released over the same checkbox on which it was pressed? + if (this.columnIndexCheckBoxMouseDown != this.GetColumnCheckBoxUnderCursor()) + return true; + + // Toggle the header's checkbox + OLVColumn column = this.ListView.GetColumn(this.columnIndexCheckBoxMouseDown); + this.ListView.ToggleHeaderCheckBox(column); + + return true; + } + + /// + /// Handle the SetCursor windows message + /// + /// + /// + protected bool HandleSetCursor(ref Message m) { + if (this.IsCursorOverLockedDivider) { + m.Result = (IntPtr) 1; // Don't change the cursor + return false; + } + return true; + } + + /// + /// Handle the MouseMove windows message + /// + /// + /// + protected bool HandleMouseMove(ref Message m) { + + // Forward the mouse move event to the ListView itself + if (this.ListView.TriggerCellOverEventsWhenOverHeader) { + int x = m.LParam.ToInt32() & 0xFFFF; + int y = (m.LParam.ToInt32() >> 16) & 0xFFFF; + this.ListView.HandleMouseMove(new Point(x, y)); + } + + int columnIndex = this.ColumnIndexUnderCursor; + + // If the mouse has moved to a different header, pop the current tip (if any) + // For some reason, references this.ToolTip when in design mode, causes the + // columns to not be resizable by dragging the divider in the Designer. No idea why. + if (columnIndex != this.columnShowingTip && !this.ListView.IsDesignMode) { + this.ToolTip.PopToolTip(this); + this.columnShowingTip = columnIndex; + } + + // If the mouse has moved onto or away from a checkbox, we need to draw + int checkBoxUnderCursor = this.GetColumnCheckBoxUnderCursor(); + if (checkBoxUnderCursor != this.lastCheckBoxUnderCursor) { + this.Invalidate(); + this.lastCheckBoxUnderCursor = checkBoxUnderCursor; + } + + return true; + } + + private int columnShowingTip = -1; + private int lastCheckBoxUnderCursor = -1; + + /// + /// Handle the MouseLeave windows message + /// + /// + /// + protected bool HandleMouseLeave(ref Message m) { + // Forward the mouse leave event to the ListView itself + if (this.ListView.TriggerCellOverEventsWhenOverHeader) + this.ListView.HandleMouseMove(new Point(-1, -1)); + + return true; + } + + /// + /// Handle the Notify windows message + /// + /// + /// + protected bool HandleNotify(ref Message m) { + // Can this ever happen? JPP 2009-05-22 + if (m.LParam == IntPtr.Zero) + return false; + + NativeMethods.NMHDR nmhdr = (NativeMethods.NMHDR)m.GetLParam(typeof(NativeMethods.NMHDR)); + switch (nmhdr.code) + { + + case ToolTipControl.TTN_SHOW: + //System.Diagnostics.Debug.WriteLine("hdr TTN_SHOW"); + //System.Diagnostics.Trace.Assert(this.ToolTip.Handle == nmhdr.hwndFrom); + return this.ToolTip.HandleShow(ref m); + + case ToolTipControl.TTN_POP: + //System.Diagnostics.Debug.WriteLine("hdr TTN_POP"); + //System.Diagnostics.Trace.Assert(this.ToolTip.Handle == nmhdr.hwndFrom); + return this.ToolTip.HandlePop(ref m); + + case ToolTipControl.TTN_GETDISPINFO: + //System.Diagnostics.Debug.WriteLine("hdr TTN_GETDISPINFO"); + //System.Diagnostics.Trace.Assert(this.ToolTip.Handle == nmhdr.hwndFrom); + return this.ToolTip.HandleGetDispInfo(ref m); + } + + return false; + } + + /// + /// Handle the CustomDraw windows message + /// + /// + /// + internal virtual bool HandleHeaderCustomDraw(ref Message m) { + const int CDRF_NEWFONT = 2; + const int CDRF_SKIPDEFAULT = 4; + const int CDRF_NOTIFYPOSTPAINT = 0x10; + const int CDRF_NOTIFYITEMDRAW = 0x20; + + const int CDDS_PREPAINT = 1; + const int CDDS_POSTPAINT = 2; + const int CDDS_ITEM = 0x00010000; + const int CDDS_ITEMPREPAINT = (CDDS_ITEM | CDDS_PREPAINT); + const int CDDS_ITEMPOSTPAINT = (CDDS_ITEM | CDDS_POSTPAINT); + + NativeMethods.NMCUSTOMDRAW nmcustomdraw = (NativeMethods.NMCUSTOMDRAW) m.GetLParam(typeof (NativeMethods.NMCUSTOMDRAW)); + //System.Diagnostics.Debug.WriteLine(String.Format("header cd: {0:x}, {1}, {2:x}", nmcustomdraw.dwDrawStage, nmcustomdraw.dwItemSpec, nmcustomdraw.uItemState)); + switch (nmcustomdraw.dwDrawStage) { + case CDDS_PREPAINT: + this.cachedNeedsCustomDraw = this.NeedsCustomDraw(); + m.Result = (IntPtr) CDRF_NOTIFYITEMDRAW; + return true; + + case CDDS_ITEMPREPAINT: + int columnIndex = nmcustomdraw.dwItemSpec.ToInt32(); + OLVColumn column = this.ListView.GetColumn(columnIndex); + + // These don't work when visual styles are enabled + //NativeMethods.SetBkColor(nmcustomdraw.hdc, ColorTranslator.ToWin32(Color.Red)); + //NativeMethods.SetTextColor(nmcustomdraw.hdc, ColorTranslator.ToWin32(Color.Blue)); + //m.Result = IntPtr.Zero; + + if (this.cachedNeedsCustomDraw) { + using (Graphics g = Graphics.FromHdc(nmcustomdraw.hdc)) { + g.TextRenderingHint = ObjectListView.TextRenderingHint; + this.CustomDrawHeaderCell(g, columnIndex, nmcustomdraw.uItemState); + } + m.Result = (IntPtr) CDRF_SKIPDEFAULT; + } else { + const int CDIS_SELECTED = 1; + bool isPressed = ((nmcustomdraw.uItemState & CDIS_SELECTED) == CDIS_SELECTED); + + // We don't need to modify this based on checkboxes, since there can't be checkboxes if we are here + bool isHot = columnIndex == this.ColumnIndexUnderCursor; + + Font f = this.CalculateFont(column, isHot, isPressed); + + this.fontHandle = f.ToHfont(); + NativeMethods.SelectObject(nmcustomdraw.hdc, this.fontHandle); + + m.Result = (IntPtr) (CDRF_NEWFONT | CDRF_NOTIFYPOSTPAINT); + } + + return true; + + case CDDS_ITEMPOSTPAINT: + if (this.fontHandle != IntPtr.Zero) { + NativeMethods.DeleteObject(this.fontHandle); + this.fontHandle = IntPtr.Zero; + } + break; + } + + return false; + } + + private bool cachedNeedsCustomDraw; + private IntPtr fontHandle; + + /// + /// The message divides a ListView's space between the header and the rows of the listview. + /// The WINDOWPOS structure controls the headers bounds, the RECT controls the listview bounds. + /// + /// + /// + protected bool HandleLayout(ref Message m) { + if (this.ListView.HeaderStyle == ColumnHeaderStyle.None) + return true; + + NativeMethods.HDLAYOUT hdlayout = (NativeMethods.HDLAYOUT) m.GetLParam(typeof (NativeMethods.HDLAYOUT)); + NativeMethods.RECT rect = (NativeMethods.RECT) Marshal.PtrToStructure(hdlayout.prc, typeof (NativeMethods.RECT)); + NativeMethods.WINDOWPOS wpos = (NativeMethods.WINDOWPOS) Marshal.PtrToStructure(hdlayout.pwpos, typeof (NativeMethods.WINDOWPOS)); + + using (Graphics g = this.ListView.CreateGraphics()) { + g.TextRenderingHint = ObjectListView.TextRenderingHint; + int height = this.CalculateHeight(g); + wpos.hwnd = this.Handle; + wpos.hwndInsertAfter = IntPtr.Zero; + wpos.flags = NativeMethods.SWP_FRAMECHANGED; + wpos.x = rect.left; + wpos.y = rect.top; + wpos.cx = rect.right - rect.left; + wpos.cy = height; + + rect.top = height; + + Marshal.StructureToPtr(rect, hdlayout.prc, false); + Marshal.StructureToPtr(wpos, hdlayout.pwpos, false); + } + + this.ListView.BeginInvoke((MethodInvoker) delegate { + this.Invalidate(); + this.ListView.Invalidate(); + }); + return false; + } + + /// + /// Handle when the underlying header control is destroyed + /// + /// + /// + protected bool HandleDestroy(ref Message m) { + if (this.toolTip != null) { + this.toolTip.Showing -= new EventHandler(this.ListView.HeaderToolTipShowingCallback); + } + return false; + } + + #endregion + + #region Rendering + + /// + /// Does this header need to be custom drawn? + /// + /// Word wrapping and colored text require custom drawing. Funnily enough, we + /// can change the font natively. + protected bool NeedsCustomDraw() { + if (this.WordWrap) + return true; + + if (this.ListView.HeaderUsesThemes) + return false; + + if (this.NeedsCustomDraw(this.ListView.HeaderFormatStyle)) + return true; + + foreach (OLVColumn column in this.ListView.Columns) { + if (column.HasHeaderImage || + !column.ShowTextInHeader || + column.IsHeaderVertical || + this.HasFilterIndicator(column) || + this.HasCheckBox(column) || + column.TextAlign != column.HeaderTextAlignOrDefault || + (column.Index == 0 && column.HeaderTextAlignOrDefault != HorizontalAlignment.Left) || + this.NeedsCustomDraw(column.HeaderFormatStyle)) + return true; + } + + return false; + } + + private bool NeedsCustomDraw(HeaderFormatStyle style) { + if (style == null) + return false; + + return (this.NeedsCustomDraw(style.Normal) || + this.NeedsCustomDraw(style.Hot) || + this.NeedsCustomDraw(style.Pressed)); + } + + private bool NeedsCustomDraw(HeaderStateStyle style) { + if (style == null) + return false; + + // If we want fancy colors or frames, we have to custom draw. Oddly enough, we + // can handle font changes without custom drawing. + if (!style.BackColor.IsEmpty) + return true; + + if (style.FrameWidth > 0f && !style.FrameColor.IsEmpty) + return true; + + return (!style.ForeColor.IsEmpty && style.ForeColor != Color.Black); + } + + /// + /// Draw one cell of the header + /// + /// + /// + /// + protected void CustomDrawHeaderCell(Graphics g, int columnIndex, int itemState) { + OLVColumn column = this.ListView.GetColumn(columnIndex); + + bool hasCheckBox = this.HasCheckBox(column); + bool isMouseOverCheckBox = columnIndex == this.lastCheckBoxUnderCursor; + bool isMouseDownOnCheckBox = isMouseOverCheckBox && Control.MouseButtons == MouseButtons.Left; + bool isHot = (columnIndex == this.ColumnIndexUnderCursor) && (!(hasCheckBox && isMouseOverCheckBox)); + + const int CDIS_SELECTED = 1; + bool isPressed = ((itemState & CDIS_SELECTED) == CDIS_SELECTED); + + // System.Diagnostics.Debug.WriteLine(String.Format("{2:hh:mm:ss.ff} - HeaderCustomDraw: {0}, {1}", columnIndex, itemState, DateTime.Now)); + + // Calculate which style should be used for the header + HeaderStateStyle stateStyle = this.CalculateStateStyle(column, isHot, isPressed); + + // If there is an owner drawn delegate installed, give it a chance to draw the header + Rectangle fullCellBounds = this.GetItemRect(columnIndex); + if (column.HeaderDrawing != null) + { + if (!column.HeaderDrawing(g, fullCellBounds, columnIndex, column, isPressed, stateStyle)) + return; + } + + // Draw the background + if (this.ListView.HeaderUsesThemes && + VisualStyleRenderer.IsSupported && + VisualStyleRenderer.IsElementDefined(VisualStyleElement.Header.Item.Normal)) + this.DrawThemedBackground(g, fullCellBounds, columnIndex, isPressed, isHot); + else + this.DrawUnthemedBackground(g, fullCellBounds, columnIndex, isPressed, isHot, stateStyle); + + Rectangle r = this.GetHeaderDrawRect(columnIndex); + + // Draw the sort indicator if this column has one + if (this.HasSortIndicator(column)) { + if (this.ListView.HeaderUsesThemes && + VisualStyleRenderer.IsSupported && + VisualStyleRenderer.IsElementDefined(VisualStyleElement.Header.SortArrow.SortedUp)) + this.DrawThemedSortIndicator(g, r); + else + r = this.DrawUnthemedSortIndicator(g, r); + } + + if (this.HasFilterIndicator(column)) + r = this.DrawFilterIndicator(g, r); + + if (hasCheckBox) + r = this.DrawCheckBox(g, r, column.HeaderCheckState, column.HeaderCheckBoxDisabled, isMouseOverCheckBox, isMouseDownOnCheckBox); + + // Debugging - Where is the text going to be drawn + // g.DrawRectangle(Pens.Blue, r); + + // Finally draw the text + this.DrawHeaderImageAndText(g, r, column, stateStyle); + } + + private Rectangle DrawCheckBox(Graphics g, Rectangle r, CheckState checkState, bool isDisabled, bool isHot, + bool isPressed) { + CheckBoxState checkBoxState = this.GetCheckBoxState(checkState, isDisabled, isHot, isPressed); + Rectangle checkBoxBounds = this.CalculateCheckBoxBounds(g, r); + CheckBoxRenderer.DrawCheckBox(g, checkBoxBounds.Location, checkBoxState); + + // Move the left edge without changing the right edge + int newX = checkBoxBounds.Right + 3; + r.Width -= (newX - r.X); + r.X = newX; + + return r; + } + + private Rectangle CalculateCheckBoxBounds(Graphics g, Rectangle cellBounds) { + Size checkBoxSize = CheckBoxRenderer.GetGlyphSize(g, CheckBoxState.CheckedNormal); + + // Vertically center the checkbox + int topOffset = (cellBounds.Height - checkBoxSize.Height)/2; + return new Rectangle(cellBounds.X + 3, cellBounds.Y + topOffset, checkBoxSize.Width, checkBoxSize.Height); + } + + private CheckBoxState GetCheckBoxState(CheckState checkState, bool isDisabled, bool isHot, bool isPressed) { + // Should the checkbox be drawn as disabled? + if (isDisabled) { + switch (checkState) { + case CheckState.Checked: + return CheckBoxState.CheckedDisabled; + case CheckState.Unchecked: + return CheckBoxState.UncheckedDisabled; + default: + return CheckBoxState.MixedDisabled; + } + } + + // Is the mouse button currently down? + if (isPressed) { + switch (checkState) { + case CheckState.Checked: + return CheckBoxState.CheckedPressed; + case CheckState.Unchecked: + return CheckBoxState.UncheckedPressed; + default: + return CheckBoxState.MixedPressed; + } + } + + // Is the cursor currently over this checkbox? + if (isHot) { + switch (checkState) { + case CheckState.Checked: + return CheckBoxState.CheckedHot; + case CheckState.Unchecked: + return CheckBoxState.UncheckedHot; + default: + return CheckBoxState.MixedHot; + } + } + + // Not hot and not disabled -- just draw it normally + switch (checkState) { + case CheckState.Checked: + return CheckBoxState.CheckedNormal; + case CheckState.Unchecked: + return CheckBoxState.UncheckedNormal; + default: + return CheckBoxState.MixedNormal; + } + } + + /// + /// Draw a background for the header, without using Themes. + /// + /// + /// + /// + /// + /// + /// + protected void DrawUnthemedBackground(Graphics g, Rectangle r, int columnIndex, bool isPressed, bool isHot, HeaderStateStyle stateStyle) { + if (stateStyle.BackColor.IsEmpty) + // I know we're supposed to be drawing the unthemed background, but let's just see if we + // can draw something more interesting than the dull raised block + if (VisualStyleRenderer.IsSupported && + VisualStyleRenderer.IsElementDefined(VisualStyleElement.Header.Item.Normal)) + this.DrawThemedBackground(g, r, columnIndex, isPressed, isHot); + else + ControlPaint.DrawBorder3D(g, r, Border3DStyle.RaisedInner); + else { + using (Brush b = new SolidBrush(stateStyle.BackColor)) + g.FillRectangle(b, r); + } + + // Draw the frame if the style asks for one + if (!stateStyle.FrameColor.IsEmpty && stateStyle.FrameWidth > 0f) { + RectangleF r2 = r; + r2.Inflate(-stateStyle.FrameWidth, -stateStyle.FrameWidth); + using (Pen pen = new Pen(stateStyle.FrameColor, stateStyle.FrameWidth)) + g.DrawRectangle(pen, r2.X, r2.Y, r2.Width, r2.Height); + } + } + + /// + /// Draw a more-or-less pure themed header background. + /// + /// + /// + /// + /// + /// + protected void DrawThemedBackground(Graphics g, Rectangle r, int columnIndex, bool isPressed, bool isHot) { + int part = 1; // normal item + if (columnIndex == 0 && + VisualStyleRenderer.IsElementDefined(VisualStyleElement.Header.ItemLeft.Normal)) + part = 2; // left item + if (columnIndex == this.ListView.Columns.Count - 1 && + VisualStyleRenderer.IsElementDefined(VisualStyleElement.Header.ItemRight.Normal)) + part = 3; // right item + + int state = 1; // normal state + if (isPressed) + state = 3; // pressed + else if (isHot) + state = 2; // hot + + VisualStyleRenderer renderer = new VisualStyleRenderer("HEADER", part, state); + renderer.DrawBackground(g, r); + } + + /// + /// Draw a sort indicator using themes + /// + /// + /// + protected void DrawThemedSortIndicator(Graphics g, Rectangle r) { + VisualStyleRenderer renderer2 = null; + if (this.ListView.LastSortOrder == SortOrder.Ascending) + renderer2 = new VisualStyleRenderer(VisualStyleElement.Header.SortArrow.SortedUp); + if (this.ListView.LastSortOrder == SortOrder.Descending) + renderer2 = new VisualStyleRenderer(VisualStyleElement.Header.SortArrow.SortedDown); + if (renderer2 != null) { + Size sz = renderer2.GetPartSize(g, ThemeSizeType.True); + Point pt = renderer2.GetPoint(PointProperty.Offset); + // GetPoint() should work, but if it doesn't, put the arrow in the top middle + if (pt.X == 0 && pt.Y == 0) + pt = new Point(r.X + (r.Width/2) - (sz.Width/2), r.Y); + renderer2.DrawBackground(g, new Rectangle(pt, sz)); + } + } + + /// + /// Draw a sort indicator without using themes + /// + /// + /// + /// + protected Rectangle DrawUnthemedSortIndicator(Graphics g, Rectangle r) { + // No theme support for sort indicators. So, we draw a triangle at the right edge + // of the column header. + const int triangleHeight = 16; + const int triangleWidth = 16; + const int midX = triangleWidth/2; + const int midY = (triangleHeight/2) - 1; + const int deltaX = midX - 2; + const int deltaY = deltaX/2; + + Point triangleLocation = new Point(r.Right - triangleWidth - 2, r.Top + (r.Height - triangleHeight)/2); + Point[] pts = new Point[] {triangleLocation, triangleLocation, triangleLocation}; + + if (this.ListView.LastSortOrder == SortOrder.Ascending) { + pts[0].Offset(midX - deltaX, midY + deltaY); + pts[1].Offset(midX, midY - deltaY - 1); + pts[2].Offset(midX + deltaX, midY + deltaY); + } else { + pts[0].Offset(midX - deltaX, midY - deltaY); + pts[1].Offset(midX, midY + deltaY); + pts[2].Offset(midX + deltaX, midY - deltaY); + } + + g.FillPolygon(Brushes.SlateGray, pts); + r.Width = r.Width - triangleWidth; + return r; + } + + /// + /// Draw an indication that this column has a filter applied to it + /// + /// + /// + /// + protected Rectangle DrawFilterIndicator(Graphics g, Rectangle r) { + int width = this.CalculateFilterIndicatorWidth(r); + if (width <= 0) + return r; + + Image indicator = Resources.ColumnFilterIndicator; + int x = r.Right - width; + int y = r.Top + (r.Height - indicator.Height)/2; + g.DrawImageUnscaled(indicator, x, y); + + r.Width -= width; + return r; + } + + private int CalculateFilterIndicatorWidth(Rectangle r) { + if (Resources.ColumnFilterIndicator == null || r.Width < 48) + return 0; + return Resources.ColumnFilterIndicator.Width + 1; + } + + /// + /// Draw the header's image and text + /// + /// + /// + /// + /// + protected void DrawHeaderImageAndText(Graphics g, Rectangle r, OLVColumn column, HeaderStateStyle stateStyle) { + + TextFormatFlags flags = this.TextFormatFlags; + flags |= TextFormatFlags.VerticalCenter; + if (column.HeaderTextAlignOrDefault == HorizontalAlignment.Center) + flags |= TextFormatFlags.HorizontalCenter; + if (column.HeaderTextAlignOrDefault == HorizontalAlignment.Right) + flags |= TextFormatFlags.Right; + + Font f = this.ListView.HeaderUsesThemes ? this.ListView.Font : stateStyle.Font ?? this.ListView.Font; + Color color = this.ListView.HeaderUsesThemes ? Color.Black : stateStyle.ForeColor; + if (color.IsEmpty) + color = Color.Black; + + const int imageTextGap = 3; + + if (column.IsHeaderVertical) { + DrawVerticalText(g, r, column, f, color); + } else { + // Does the column have a header image and is there space for it? + if (column.HasHeaderImage && r.Width > column.ImageList.ImageSize.Width*2) + DrawImageAndText(g, r, column, flags, f, color, imageTextGap); + else + DrawText(g, r, column, flags, f, color); + } + } + + private void DrawText(Graphics g, Rectangle r, OLVColumn column, TextFormatFlags flags, Font f, Color color) { + if (column.ShowTextInHeader) + TextRenderer.DrawText(g, column.Text, f, r, color, Color.Transparent, flags); + } + + private void DrawImageAndText(Graphics g, Rectangle r, OLVColumn column, TextFormatFlags flags, Font f, + Color color, int imageTextGap) { + Rectangle textRect = r; + textRect.X += (column.ImageList.ImageSize.Width + imageTextGap); + textRect.Width -= (column.ImageList.ImageSize.Width + imageTextGap); + + Size textSize = Size.Empty; + if (column.ShowTextInHeader) + textSize = TextRenderer.MeasureText(g, column.Text, f, textRect.Size, flags); + + int imageY = r.Top + ((r.Height - column.ImageList.ImageSize.Height)/2); + int imageX = textRect.Left; + if (column.HeaderTextAlignOrDefault == HorizontalAlignment.Center) + imageX = textRect.Left + ((textRect.Width - textSize.Width)/2); + if (column.HeaderTextAlignOrDefault == HorizontalAlignment.Right) + imageX = textRect.Right - textSize.Width; + imageX -= (column.ImageList.ImageSize.Width + imageTextGap); + + column.ImageList.Draw(g, imageX, imageY, column.ImageList.Images.IndexOfKey(column.HeaderImageKey)); + + this.DrawText(g, textRect, column, flags, f, color); + } + + private static void DrawVerticalText(Graphics g, Rectangle r, OLVColumn column, Font f, Color color) { + try { + // Create a matrix transformation that will rotate the text 90 degrees vertically + // AND place the text in the middle of where it was previously. [Think of tipping + // a box over by its bottom left edge -- you have to move it back a bit so it's + // in the same place as it started] + Matrix m = new Matrix(); + m.RotateAt(-90, new Point(r.X, r.Bottom)); + m.Translate(0, r.Height); + g.Transform = m; + StringFormat fmt = new StringFormat(StringFormatFlags.NoWrap); + fmt.Alignment = StringAlignment.Near; + fmt.LineAlignment = column.HeaderTextAlignAsStringAlignment; + //fmt.Trimming = StringTrimming.EllipsisCharacter; + + // The drawing is rotated 90 degrees, so switch our text boundaries + Rectangle textRect = r; + textRect.Width = r.Height; + textRect.Height = r.Width; + using (Brush b = new SolidBrush(color)) + g.DrawString(column.Text, f, b, textRect, fmt); + } + finally { + g.ResetTransform(); + } + } + + /// + /// Return the header format that should be used for the given column + /// + /// + /// + protected HeaderFormatStyle CalculateHeaderStyle(OLVColumn column) { + return column.HeaderFormatStyle ?? this.ListView.HeaderFormatStyle ?? new HeaderFormatStyle(); + } + + /// + /// What style should be applied to the header? + /// + /// + /// + /// + /// + protected HeaderStateStyle CalculateStateStyle(OLVColumn column, bool isHot, bool isPressed) { + HeaderFormatStyle headerStyle = this.CalculateHeaderStyle(column); + if (this.ListView.IsDesignMode) + return headerStyle.Normal; + if (isPressed) + return headerStyle.Pressed; + if (isHot) + return headerStyle.Hot; + return headerStyle.Normal; + } + + /// + /// What font should be used to draw the header text? + /// + /// + /// + /// + /// + protected Font CalculateFont(OLVColumn column, bool isHot, bool isPressed) { + HeaderStateStyle stateStyle = this.CalculateStateStyle(column, isHot, isPressed); + return stateStyle.Font ?? this.ListView.Font; + } + + /// + /// What flags will be used when drawing text + /// + protected TextFormatFlags TextFormatFlags { + get { + TextFormatFlags flags = TextFormatFlags.EndEllipsis | + TextFormatFlags.NoPrefix | + TextFormatFlags.WordEllipsis | + TextFormatFlags.PreserveGraphicsTranslateTransform; + if (this.WordWrap) + flags |= TextFormatFlags.WordBreak; + else + flags |= TextFormatFlags.SingleLine; + if (this.ListView.RightToLeft == RightToLeft.Yes) + flags |= TextFormatFlags.RightToLeft; + + return flags; + } + } + + /// + /// Perform a HitTest for the header control + /// + /// + /// + /// Null if the given point isn't over the header + internal OlvListViewHitTestInfo.HeaderHitTestInfo HitTest(int x, int y) + { + Rectangle r = this.ClientRectangle; + if (!r.Contains(x, y)) + return null; + + Point pt = new Point(x + this.ListView.LowLevelScrollPosition.X, y); + + OlvListViewHitTestInfo.HeaderHitTestInfo hti = new OlvListViewHitTestInfo.HeaderHitTestInfo(); + hti.ColumnIndex = NativeMethods.GetColumnUnderPoint(this.Handle, pt); + hti.IsOverCheckBox = this.IsPointOverHeaderCheckBox(hti.ColumnIndex, pt); + hti.OverDividerIndex = NativeMethods.GetDividerUnderPoint(this.Handle, pt); + + return hti; + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/SubControls/ToolStripCheckedListBox.cs b/VG Music Studio - WinForms/ObjectListView/SubControls/ToolStripCheckedListBox.cs new file mode 100644 index 0000000..7cbf9f9 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/SubControls/ToolStripCheckedListBox.cs @@ -0,0 +1,189 @@ +/* + * ToolStripCheckedListBox - Puts a CheckedListBox into a tool strip menu item + * + * Author: Phillip Piper + * Date: 4-March-2011 11:59 pm + * + * Change log: + * 2011-03-04 JPP - First version + * + * Copyright (C) 2011-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using System.Drawing; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + + /// + /// Instances of this class put a CheckedListBox into a tool strip menu item. + /// + public class ToolStripCheckedListBox : ToolStripControlHost { + + /// + /// Create a ToolStripCheckedListBox + /// + public ToolStripCheckedListBox() + : base(new CheckedListBox()) { + this.CheckedListBoxControl.MaximumSize = new Size(400, 700); + this.CheckedListBoxControl.ThreeDCheckBoxes = true; + this.CheckedListBoxControl.CheckOnClick = true; + this.CheckedListBoxControl.SelectionMode = SelectionMode.One; + } + + /// + /// Gets the control embedded in the menu + /// + public CheckedListBox CheckedListBoxControl { + get { + return Control as CheckedListBox; + } + } + + /// + /// Gets the items shown in the checkedlistbox + /// + public CheckedListBox.ObjectCollection Items { + get { + return this.CheckedListBoxControl.Items; + } + } + + /// + /// Gets or sets whether an item should be checked when it is clicked + /// + public bool CheckedOnClick { + get { + return this.CheckedListBoxControl.CheckOnClick; + } + set { + this.CheckedListBoxControl.CheckOnClick = value; + } + } + + /// + /// Gets a collection of the checked items + /// + public CheckedListBox.CheckedItemCollection CheckedItems { + get { + return this.CheckedListBoxControl.CheckedItems; + } + } + + /// + /// Add a possibly checked item to the control + /// + /// + /// + public void AddItem(object item, bool isChecked) { + this.Items.Add(item); + if (isChecked) + this.CheckedListBoxControl.SetItemChecked(this.Items.Count - 1, true); + } + + /// + /// Add an item with the given state to the control + /// + /// + /// + public void AddItem(object item, CheckState state) { + this.Items.Add(item); + this.CheckedListBoxControl.SetItemCheckState(this.Items.Count - 1, state); + } + + /// + /// Gets the checkedness of the i'th item + /// + /// + /// + public CheckState GetItemCheckState(int i) { + return this.CheckedListBoxControl.GetItemCheckState(i); + } + + /// + /// Set the checkedness of the i'th item + /// + /// + /// + public void SetItemState(int i, CheckState checkState) { + if (i >= 0 && i < this.Items.Count) + this.CheckedListBoxControl.SetItemCheckState(i, checkState); + } + + /// + /// Check all the items in the control + /// + public void CheckAll() { + for (int i = 0; i < this.Items.Count; i++) + this.CheckedListBoxControl.SetItemChecked(i, true); + } + + /// + /// Unchecked all the items in the control + /// + public void UncheckAll() { + for (int i = 0; i < this.Items.Count; i++) + this.CheckedListBoxControl.SetItemChecked(i, false); + } + + #region Events + + /// + /// Listen for events on the underlying control + /// + /// + protected override void OnSubscribeControlEvents(Control c) { + base.OnSubscribeControlEvents(c); + + CheckedListBox control = (CheckedListBox)c; + control.ItemCheck += new ItemCheckEventHandler(OnItemCheck); + } + + /// + /// Stop listening for events on the underlying control + /// + /// + protected override void OnUnsubscribeControlEvents(Control c) { + base.OnUnsubscribeControlEvents(c); + + CheckedListBox control = (CheckedListBox)c; + control.ItemCheck -= new ItemCheckEventHandler(OnItemCheck); + } + + /// + /// Tell the world that an item was checked + /// + public event ItemCheckEventHandler ItemCheck; + + /// + /// Trigger the ItemCheck event + /// + /// + /// + private void OnItemCheck(object sender, ItemCheckEventArgs e) { + if (ItemCheck != null) { + ItemCheck(this, e); + } + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/SubControls/ToolTipControl.cs b/VG Music Studio - WinForms/ObjectListView/SubControls/ToolTipControl.cs new file mode 100644 index 0000000..8de65d2 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/SubControls/ToolTipControl.cs @@ -0,0 +1,699 @@ +/* + * ToolTipControl - A limited wrapper around a Windows tooltip control + * + * For some reason, the ToolTip class in the .NET framework is implemented in a significantly + * different manner to other controls. For our purposes, the worst of these problems + * is that we cannot get the Handle, so we cannot send Windows level messages to the control. + * + * Author: Phillip Piper + * Date: 2009-05-17 7:22PM + * + * Change log: + * v2.3 + * 2009-06-13 JPP - Moved ToolTipShowingEventArgs to Events.cs + * v2.2 + * 2009-06-06 JPP - Fixed some Vista specific problems + * 2009-05-17 JPP - Initial version + * + * TO DO: + * + * Copyright (C) 2006-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using System.Security.Permissions; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// A limited wrapper around a Windows tooltip window. + /// + public class ToolTipControl : NativeWindow + { + #region Constants + + /// + /// These are the standard icons that a tooltip can display. + /// + public enum StandardIcons + { + /// + /// No icon + /// + None = 0, + + /// + /// Info + /// + Info = 1, + + /// + /// Warning + /// + Warning = 2, + + /// + /// Error + /// + Error = 3, + + /// + /// Large info (Vista and later only) + /// + InfoLarge = 4, + + /// + /// Large warning (Vista and later only) + /// + WarningLarge = 5, + + /// + /// Large error (Vista and later only) + /// + ErrorLarge = 6 + } + + const int GWL_STYLE = -16; + const int WM_GETFONT = 0x31; + const int WM_SETFONT = 0x30; + const int WS_BORDER = 0x800000; + const int WS_EX_TOPMOST = 8; + + const int TTM_ADDTOOL = 0x432; + const int TTM_ADJUSTRECT = 0x400 + 31; + const int TTM_DELTOOL = 0x433; + const int TTM_GETBUBBLESIZE = 0x400 + 30; + const int TTM_GETCURRENTTOOL = 0x400 + 59; + const int TTM_GETTIPBKCOLOR = 0x400 + 22; + const int TTM_GETTIPTEXTCOLOR = 0x400 + 23; + const int TTM_GETDELAYTIME = 0x400 + 21; + const int TTM_NEWTOOLRECT = 0x400 + 52; + const int TTM_POP = 0x41c; + const int TTM_SETDELAYTIME = 0x400 + 3; + const int TTM_SETMAXTIPWIDTH = 0x400 + 24; + const int TTM_SETTIPBKCOLOR = 0x400 + 19; + const int TTM_SETTIPTEXTCOLOR = 0x400 + 20; + const int TTM_SETTITLE = 0x400 + 33; + const int TTM_SETTOOLINFO = 0x400 + 54; + + const int TTF_IDISHWND = 1; + //const int TTF_ABSOLUTE = 0x80; + const int TTF_CENTERTIP = 2; + const int TTF_RTLREADING = 4; + const int TTF_SUBCLASS = 0x10; + //const int TTF_TRACK = 0x20; + //const int TTF_TRANSPARENT = 0x100; + const int TTF_PARSELINKS = 0x1000; + + const int TTS_NOPREFIX = 2; + const int TTS_BALLOON = 0x40; + const int TTS_USEVISUALSTYLE = 0x100; + + const int TTN_FIRST = -520; + + /// + /// + /// + public const int TTN_SHOW = (TTN_FIRST - 1); + + /// + /// + /// + public const int TTN_POP = (TTN_FIRST - 2); + + /// + /// + /// + public const int TTN_LINKCLICK = (TTN_FIRST - 3); + + /// + /// + /// + public const int TTN_GETDISPINFO = (TTN_FIRST - 10); + + const int TTDT_AUTOMATIC = 0; + const int TTDT_RESHOW = 1; + const int TTDT_AUTOPOP = 2; + const int TTDT_INITIAL = 3; + + #endregion + + #region Properties + + /// + /// Get or set if the style of the tooltip control + /// + internal int WindowStyle { + get { + return (int)NativeMethods.GetWindowLong(this.Handle, GWL_STYLE); + } + set { + NativeMethods.SetWindowLong(this.Handle, GWL_STYLE, value); + } + } + + /// + /// Get or set if the tooltip should be shown as a balloon + /// + public bool IsBalloon { + get { + return (this.WindowStyle & TTS_BALLOON) == TTS_BALLOON; + } + set { + if (this.IsBalloon == value) + return; + + int windowStyle = this.WindowStyle; + if (value) { + windowStyle |= (TTS_BALLOON | TTS_USEVISUALSTYLE); + // On XP, a border makes the balloon look wrong + if (!ObjectListView.IsVistaOrLater) + windowStyle &= ~WS_BORDER; + } else { + windowStyle &= ~(TTS_BALLOON | TTS_USEVISUALSTYLE); + if (!ObjectListView.IsVistaOrLater) { + if (this.hasBorder) + windowStyle |= WS_BORDER; + else + windowStyle &= ~WS_BORDER; + } + } + this.WindowStyle = windowStyle; + } + } + + /// + /// Get or set if the tooltip should be shown as a balloon + /// + public bool HasBorder { + get { + return this.hasBorder; + } + set { + if (this.hasBorder == value) + return; + + if (value) { + this.WindowStyle |= WS_BORDER; + } else { + this.WindowStyle &= ~WS_BORDER; + } + } + } + private bool hasBorder = true; + + /// + /// Get or set the background color of the tooltip + /// + public Color BackColor { + get { + int color = (int)NativeMethods.SendMessage(this.Handle, TTM_GETTIPBKCOLOR, 0, 0); + return ColorTranslator.FromWin32(color); + } + set { + // For some reason, setting the color fails on Vista and messes up later ops. + // So we don't even try to set it. + if (!ObjectListView.IsVistaOrLater) { + int color = ColorTranslator.ToWin32(value); + NativeMethods.SendMessage(this.Handle, TTM_SETTIPBKCOLOR, color, 0); + //int x2 = Marshal.GetLastWin32Error(); + } + } + } + + /// + /// Get or set the color of the text and border on the tooltip. + /// + public Color ForeColor { + get { + int color = (int)NativeMethods.SendMessage(this.Handle, TTM_GETTIPTEXTCOLOR, 0, 0); + return ColorTranslator.FromWin32(color); + } + set { + // For some reason, setting the color fails on Vista and messes up later ops. + // So we don't even try to set it. + if (!ObjectListView.IsVistaOrLater) { + int color = ColorTranslator.ToWin32(value); + NativeMethods.SendMessage(this.Handle, TTM_SETTIPTEXTCOLOR, color, 0); + } + } + } + + /// + /// Get or set the title that will be shown on the tooltip. + /// + public string Title { + get { + return this.title; + } + set { + if (String.IsNullOrEmpty(value)) + this.title = String.Empty; + else + if (value.Length >= 100) + this.title = value.Substring(0, 99); + else + this.title = value; + NativeMethods.SendMessageString(this.Handle, TTM_SETTITLE, (int)this.standardIcon, this.title); + } + } + private string title; + + /// + /// Get or set the icon that will be shown on the tooltip. + /// + public StandardIcons StandardIcon { + get { + return this.standardIcon; + } + set { + this.standardIcon = value; + NativeMethods.SendMessageString(this.Handle, TTM_SETTITLE, (int)this.standardIcon, this.title); + } + } + private StandardIcons standardIcon; + + /// + /// Gets or sets the font that will be used to draw this control. + /// is still. + /// + /// Setting this to null reverts to the default font. + public Font Font { + get { + IntPtr hfont = NativeMethods.SendMessage(this.Handle, WM_GETFONT, 0, 0); + if (hfont == IntPtr.Zero) + return Control.DefaultFont; + else + return Font.FromHfont(hfont); + } + set { + Font newFont = value ?? Control.DefaultFont; + if (newFont == this.font) + return; + + this.font = newFont; + IntPtr hfont = this.font.ToHfont(); // THINK: When should we delete this hfont? + NativeMethods.SendMessage(this.Handle, WM_SETFONT, hfont, 0); + } + } + private Font font; + + /// + /// Gets or sets how many milliseconds the tooltip will remain visible while the mouse + /// is still. + /// + public int AutoPopDelay { + get { return this.GetDelayTime(TTDT_AUTOPOP); } + set { this.SetDelayTime(TTDT_AUTOPOP, value); } + } + + /// + /// Gets or sets how many milliseconds the mouse must be still before the tooltip is shown. + /// + public int InitialDelay { + get { return this.GetDelayTime(TTDT_INITIAL); } + set { this.SetDelayTime(TTDT_INITIAL, value); } + } + + /// + /// Gets or sets how many milliseconds the mouse must be still before the tooltip is shown again. + /// + public int ReshowDelay { + get { return this.GetDelayTime(TTDT_RESHOW); } + set { this.SetDelayTime(TTDT_RESHOW, value); } + } + + private int GetDelayTime(int which) { + return (int)NativeMethods.SendMessage(this.Handle, TTM_GETDELAYTIME, which, 0); + } + + private void SetDelayTime(int which, int value) { + NativeMethods.SendMessage(this.Handle, TTM_SETDELAYTIME, which, value); + } + + #endregion + + #region Commands + + /// + /// Create the underlying control. + /// + /// The parent of the tooltip + /// This does nothing if the control has already been created + public void Create(IntPtr parentHandle) { + if (this.Handle != IntPtr.Zero) + return; + + CreateParams cp = new CreateParams(); + cp.ClassName = "tooltips_class32"; + cp.Style = TTS_NOPREFIX; + cp.ExStyle = WS_EX_TOPMOST; + cp.Parent = parentHandle; + this.CreateHandle(cp); + + // Ensure that multiline tooltips work correctly + this.SetMaxWidth(); + } + + /// + /// Take a copy of the current settings and restore them when the + /// tooltip is popped. + /// + /// + /// This call cannot be nested. Subsequent calls to this method will be ignored + /// until PopSettings() is called. + /// + public void PushSettings() { + // Ignore any nested calls + if (this.settings != null) + return; + this.settings = new Hashtable(); + this.settings["IsBalloon"] = this.IsBalloon; + this.settings["HasBorder"] = this.HasBorder; + this.settings["BackColor"] = this.BackColor; + this.settings["ForeColor"] = this.ForeColor; + this.settings["Title"] = this.Title; + this.settings["StandardIcon"] = this.StandardIcon; + this.settings["AutoPopDelay"] = this.AutoPopDelay; + this.settings["InitialDelay"] = this.InitialDelay; + this.settings["ReshowDelay"] = this.ReshowDelay; + this.settings["Font"] = this.Font; + } + private Hashtable settings; + + /// + /// Restore the settings of the tooltip as they were when PushSettings() + /// was last called. + /// + public void PopSettings() { + if (this.settings == null) + return; + + this.IsBalloon = (bool)this.settings["IsBalloon"]; + this.HasBorder = (bool)this.settings["HasBorder"]; + this.BackColor = (Color)this.settings["BackColor"]; + this.ForeColor = (Color)this.settings["ForeColor"]; + this.Title = (string)this.settings["Title"]; + this.StandardIcon = (StandardIcons)this.settings["StandardIcon"]; + this.AutoPopDelay = (int)this.settings["AutoPopDelay"]; + this.InitialDelay = (int)this.settings["InitialDelay"]; + this.ReshowDelay = (int)this.settings["ReshowDelay"]; + this.Font = (Font)this.settings["Font"]; + + this.settings = null; + } + + /// + /// Add the given window to those for whom this tooltip will show tips + /// + /// The window + public void AddTool(IWin32Window window) { + NativeMethods.TOOLINFO lParam = this.MakeToolInfoStruct(window); + NativeMethods.SendMessageTOOLINFO(this.Handle, TTM_ADDTOOL, 0, lParam); + } + + /// + /// Hide any currently visible tooltip + /// + /// + public void PopToolTip(IWin32Window window) { + NativeMethods.SendMessage(this.Handle, TTM_POP, 0, 0); + } + + //public void Munge() { + // NativeMethods.TOOLINFO tool = new NativeMethods.TOOLINFO(); + // IntPtr result = NativeMethods.SendMessageTOOLINFO(this.Handle, TTM_GETCURRENTTOOL, 0, tool); + // System.Diagnostics.Trace.WriteLine("-"); + // System.Diagnostics.Trace.WriteLine(result); + // result = NativeMethods.SendMessageTOOLINFO(this.Handle, TTM_GETBUBBLESIZE, 0, tool); + // System.Diagnostics.Trace.WriteLine(String.Format("{0} {1}", result.ToInt32() >> 16, result.ToInt32() & 0xFFFF)); + // NativeMethods.ChangeSize(this, result.ToInt32() & 0xFFFF, result.ToInt32() >> 16); + // //NativeMethods.RECT r = new NativeMethods.RECT(); + // //r.right + // //IntPtr x = NativeMethods.SendMessageRECT(this.Handle, TTM_ADJUSTRECT, true, ref r); + + // //System.Diagnostics.Trace.WriteLine(String.Format("{0} {1} {2} {3}", r.left, r.top, r.right, r.bottom)); + //} + + /// + /// Remove the given window from those managed by this tooltip + /// + /// + public void RemoveToolTip(IWin32Window window) { + NativeMethods.TOOLINFO lParam = this.MakeToolInfoStruct(window); + NativeMethods.SendMessageTOOLINFO(this.Handle, TTM_DELTOOL, 0, lParam); + } + + /// + /// Set the maximum width of a tooltip string. + /// + public void SetMaxWidth() { + this.SetMaxWidth(SystemInformation.MaxWindowTrackSize.Width); + } + + /// + /// Set the maximum width of a tooltip string. + /// + /// Setting this ensures that line breaks in the tooltip are honoured. + public void SetMaxWidth(int maxWidth) { + NativeMethods.SendMessage(this.Handle, TTM_SETMAXTIPWIDTH, 0, maxWidth); + } + + #endregion + + #region Implementation + + /// + /// Make a TOOLINFO structure for the given window + /// + /// + /// A filled in TOOLINFO + private NativeMethods.TOOLINFO MakeToolInfoStruct(IWin32Window window) { + + NativeMethods.TOOLINFO toolinfo_tooltip = new NativeMethods.TOOLINFO(); + toolinfo_tooltip.hwnd = window.Handle; + toolinfo_tooltip.uFlags = TTF_IDISHWND | TTF_SUBCLASS; + toolinfo_tooltip.uId = window.Handle; + toolinfo_tooltip.lpszText = (IntPtr)(-1); // LPSTR_TEXTCALLBACK + + return toolinfo_tooltip; + } + + /// + /// Handle a WmNotify message + /// + /// The msg + /// True if the message has been handled + protected virtual bool HandleNotify(ref Message msg) { + + //THINK: What do we have to do here? Nothing it seems :) + + //NativeMethods.NMHEADER nmheader = (NativeMethods.NMHEADER)msg.GetLParam(typeof(NativeMethods.NMHEADER)); + //System.Diagnostics.Trace.WriteLine("HandleNotify"); + //System.Diagnostics.Trace.WriteLine(nmheader.nhdr.code); + + //switch (nmheader.nhdr.code) { + //} + + return false; + } + + /// + /// Handle a get display info message + /// + /// The msg + /// True if the message has been handled + public virtual bool HandleGetDispInfo(ref Message msg) { + //System.Diagnostics.Trace.WriteLine("HandleGetDispInfo"); + this.SetMaxWidth(); + ToolTipShowingEventArgs args = new ToolTipShowingEventArgs(); + args.ToolTipControl = this; + this.OnShowing(args); + if (String.IsNullOrEmpty(args.Text)) + return false; + + this.ApplyEventFormatting(args); + + NativeMethods.NMTTDISPINFO dispInfo = (NativeMethods.NMTTDISPINFO)msg.GetLParam(typeof(NativeMethods.NMTTDISPINFO)); + dispInfo.lpszText = args.Text; + dispInfo.hinst = IntPtr.Zero; + if (args.RightToLeft == RightToLeft.Yes) + dispInfo.uFlags |= TTF_RTLREADING; + Marshal.StructureToPtr(dispInfo, msg.LParam, false); + + return true; + } + + private void ApplyEventFormatting(ToolTipShowingEventArgs args) { + if (!args.IsBalloon.HasValue && + !args.BackColor.HasValue && + !args.ForeColor.HasValue && + args.Title == null && + !args.StandardIcon.HasValue && + !args.AutoPopDelay.HasValue && + args.Font == null) + return; + + this.PushSettings(); + if (args.IsBalloon.HasValue) + this.IsBalloon = args.IsBalloon.Value; + if (args.BackColor.HasValue) + this.BackColor = args.BackColor.Value; + if (args.ForeColor.HasValue) + this.ForeColor = args.ForeColor.Value; + if (args.StandardIcon.HasValue) + this.StandardIcon = args.StandardIcon.Value; + if (args.AutoPopDelay.HasValue) + this.AutoPopDelay = args.AutoPopDelay.Value; + if (args.Font != null) + this.Font = args.Font; + if (args.Title != null) + this.Title = args.Title; + } + + /// + /// Handle a TTN_LINKCLICK message + /// + /// The msg + /// True if the message has been handled + /// This cannot call base.WndProc() since the msg may have come from another control. + public virtual bool HandleLinkClick(ref Message msg) { + //System.Diagnostics.Trace.WriteLine("HandleLinkClick"); + return false; + } + + /// + /// Handle a TTN_POP message + /// + /// The msg + /// True if the message has been handled + /// This cannot call base.WndProc() since the msg may have come from another control. + public virtual bool HandlePop(ref Message msg) { + //System.Diagnostics.Trace.WriteLine("HandlePop"); + this.PopSettings(); + return true; + } + + /// + /// Handle a TTN_SHOW message + /// + /// The msg + /// True if the message has been handled + /// This cannot call base.WndProc() since the msg may have come from another control. + public virtual bool HandleShow(ref Message msg) { + //System.Diagnostics.Trace.WriteLine("HandleShow"); + return false; + } + + /// + /// Handle a reflected notify message + /// + /// The msg + /// True if the message has been handled + protected virtual bool HandleReflectNotify(ref Message msg) { + + NativeMethods.NMHEADER nmheader = (NativeMethods.NMHEADER)msg.GetLParam(typeof(NativeMethods.NMHEADER)); + switch (nmheader.nhdr.code) { + case TTN_SHOW: + //System.Diagnostics.Trace.WriteLine("reflect TTN_SHOW"); + if (this.HandleShow(ref msg)) + return true; + break; + case TTN_POP: + //System.Diagnostics.Trace.WriteLine("reflect TTN_POP"); + if (this.HandlePop(ref msg)) + return true; + break; + case TTN_LINKCLICK: + //System.Diagnostics.Trace.WriteLine("reflect TTN_LINKCLICK"); + if (this.HandleLinkClick(ref msg)) + return true; + break; + case TTN_GETDISPINFO: + //System.Diagnostics.Trace.WriteLine("reflect TTN_GETDISPINFO"); + if (this.HandleGetDispInfo(ref msg)) + return true; + break; + } + + return false; + } + + /// + /// Mess with the basic message pump of the tooltip + /// + /// + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] + override protected void WndProc(ref Message msg) { + //System.Diagnostics.Trace.WriteLine(String.Format("xx {0:x}", msg.Msg)); + switch (msg.Msg) { + case 0x4E: // WM_NOTIFY + if (!this.HandleNotify(ref msg)) + return; + break; + + case 0x204E: // WM_REFLECT_NOTIFY + if (!this.HandleReflectNotify(ref msg)) + return; + break; + } + + base.WndProc(ref msg); + } + + #endregion + + #region Events + + /// + /// Tell the world that a tooltip is about to show + /// + public event EventHandler Showing; + + /// + /// Tell the world that a tooltip is about to disappear + /// + public event EventHandler Pop; + + /// + /// + /// + /// + protected virtual void OnShowing(ToolTipShowingEventArgs e) { + if (this.Showing != null) + this.Showing(this, e); + } + + /// + /// + /// + /// + protected virtual void OnPop(EventArgs e) { + if (this.Pop != null) + this.Pop(this, e); + } + + #endregion + } + +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/TreeListView.cs b/VG Music Studio - WinForms/ObjectListView/TreeListView.cs new file mode 100644 index 0000000..6956e56 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/TreeListView.cs @@ -0,0 +1,2269 @@ +/* + * TreeListView - A listview that can show a tree of objects in a column + * + * Author: Phillip Piper + * Date: 23/09/2008 11:15 AM + * + * Change log: + * 2018-05-03 JPP - Added ITreeModel to allow models to provide the required information to TreeListView. + * 2018-04-30 JPP - Fix small visual glitch where connecting lines were not correctly drawn when filters changed + * v2.9.2 + * 2016-06-02 JPP - Added bounds check to GetNthObject(). + * v2.9 + * 2015-08-02 JPP - Fixed buy with hierarchical checkboxes where setting the checkedness of a deeply + * nested object would sometimes not correctly calculate the changes in the hierarchy. SF #150. + * 2015-06-27 JPP - Corrected small UI glitch when focus was lost and HideSelection was false. SF #135. + * v2.8.1 + * 2014-11-28 JPP - Fixed issue in RefreshObject() where a model with less children than previous that could not + * longer be expanded would cause an exception. + * 2014-11-23 JPP - Fixed an issue where collapsing a branch could leave the internal object->index map out of date. + * v2.8 + * 2014-10-08 JPP - Fixed an issue where pre-expanded branches would not initially expand properly + * 2014-09-29 JPP - Fixed issue where RefreshObject() on a root object could cause exceptions + * - Fixed issue where CollapseAll() while filtering could cause exception + * 2014-03-09 JPP - Fixed issue where removing a branches only child and then calling RefreshObject() + * could throw an exception. + * v2.7 + * 2014-02-23 JPP - Added Reveal() method to show a deeply nested models. + * 2014-02-05 JPP - Fix issue where refreshing a non-root item would collapse all expanded children of that item + * 2014-02-01 JPP - ClearObjects() now actually, you know, clears objects :) + * - Corrected issue where Expanded event was being raised twice. + * - RebuildChildren() no longer checks if CanExpand is true before rebuilding. + * 2014-01-16 JPP - Corrected an off-by-1 error in hit detection, which meant that clicking in the last 16 pixels + * of an items label was being ignored. + * 2013-11-20 JPP - Moved event triggers into Collapse() and Expand() so that the events are always triggered. + * - CheckedObjects now includes objects that are in a branch that is currently collapsed + * - CollapseAll() and ExpandAll() now trigger cancellable events + * 2013-09-29 JPP - Added TreeFactory to allow the underlying Tree to be replaced by another implementation. + * 2013-09-23 JPP - Fixed long standing issue where RefreshObject() would not work on root objects + * which overrode Equals()/GetHashCode(). + * 2013-02-23 JPP - Added HierarchicalCheckboxes. When this is true, the checkedness of a parent + * is an synopsis of the checkedness of its children. When all children are checked, + * the parent is checked. When all children are unchecked, the parent is unchecked. + * If some children are checked and some are not, the parent is indeterminate. + * v2.6 + * 2012-10-25 JPP - Circumvent annoying issue in ListView control where changing + * selection would leave artefacts on the control. + * 2012-08-10 JPP - Don't trigger selection changed events during expands + * + * v2.5.1 + * 2012-04-30 JPP - Fixed issue where CheckedObjects would return model objects that had been filtered out. + * - Allow any column to render the tree, not just column 0 (still not sure about this one) + * v2.5.0 + * 2011-04-20 JPP - Added ExpandedObjects property and RebuildAll() method. + * 2011-04-09 JPP - Added Expanding, Collapsing, Expanded and Collapsed events. + * The ..ing events are cancellable. These are only fired in response + * to user actions. + * v2.4.1 + * 2010-06-15 JPP - Fixed issue in Tree.RemoveObjects() which resulted in removed objects + * being reported as still existing. + * v2.3 + * 2009-09-01 JPP - Fixed off-by-one error that was messing up hit detection + * 2009-08-27 JPP - Fixed issue when dragging a node from one place to another in the tree + * v2.2.1 + * 2009-07-14 JPP - Clicks to the left of the expander in tree cells are now ignored. + * v2.2 + * 2009-05-12 JPP - Added tree traverse operations: GetParent and GetChildren. + * - Added DiscardAllState() to completely reset the TreeListView. + * 2009-05-10 JPP - Removed all unsafe code + * 2009-05-09 JPP - Fixed issue where any command (Expand/Collapse/Refresh) on a model + * object that was once visible but that is currently in a collapsed branch + * would cause the control to crash. + * 2009-05-07 JPP - Fixed issue where RefreshObjects() would fail when none of the given + * objects were present/visible. + * 2009-04-20 JPP - Fixed issue where calling Expand() on an already expanded branch confused + * the display of the children (SF#2499313) + * 2009-03-06 JPP - Calculate edit rectangle on column 0 more accurately + * v2.1 + * 2009-02-24 JPP - All commands now work when the list is empty (SF #2631054) + * - TreeListViews can now be printed with ListViewPrinter + * 2009-01-27 JPP - Changed to use new Renderer and HitTest scheme + * 2009-01-22 JPP - Added RevealAfterExpand property. If this is true (the default), + * after expanding a branch, the control scrolls to reveal as much of the + * expanded branch as possible. + * 2009-01-13 JPP - Changed TreeRenderer to work with visual styles are disabled + * v2.0.1 + * 2009-01-07 JPP - Made all public and protected methods virtual + * - Changed some classes from 'internal' to 'protected' so that they + * can be accessed by subclasses of TreeListView. + * 2008-12-22 JPP - Added UseWaitCursorWhenExpanding property + * - Made TreeRenderer public so that it can be subclassed + * - Added LinePen property to TreeRenderer to allow the connection drawing + * pen to be changed + * - Fixed some rendering issues where the text highlight rect was miscalculated + * - Fixed connection line problem when there is only a single root + * v2.0 + * 2008-12-10 JPP - Expand/collapse with mouse now works when there is no SmallImageList. + * 2008-12-01 JPP - Search-by-typing now works. + * 2008-11-26 JPP - Corrected calculation of expand/collapse icon (SF#2338819) + * - Fixed ugliness with dotted lines in renderer (SF#2332889) + * - Fixed problem with custom selection colors (SF#2338805) + * 2008-11-19 JPP - Expand/collapse now preserve the selection -- more or less :) + * - Overrode RefreshObjects() to rebuild the given objects and their children + * 2008-11-05 JPP - Added ExpandAll() and CollapseAll() commands + * - CanExpand is no longer cached + * - Renamed InitialBranches to RootModels since it deals with model objects + * 2008-09-23 JPP Initial version + * + * TO DO: + * + * Copyright (C) 2006-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// A TreeListView combines an expandable tree structure with list view columns. + /// + /// + /// To support tree operations, two delegates must be provided: + /// + /// + /// + /// CanExpandGetter + /// + /// + /// This delegate must accept a model object and return a boolean indicating + /// if that model should be expandable. + /// + /// + /// + /// + /// ChildrenGetter + /// + /// + /// This delegate must accept a model object and return an IEnumerable of model + /// objects that will be displayed as children of the parent model. This delegate will only be called + /// for a model object if the CanExpandGetter has already returned true for that model. + /// + /// + /// + /// + /// ParentGetter + /// + /// + /// This delegate must accept a model object and return the parent model. + /// This delegate will only be called when HierarchicalCheckboxes is true OR when Reveal() is called. + /// + /// + /// + /// + /// The top level branches of the tree are set via the Roots property. SetObjects(), AddObjects() + /// and RemoveObjects() are interpreted as operations on this collection of roots. + /// + /// + /// To add new children to an existing branch, make changes to your model objects and then + /// call RefreshObject() on the parent. + /// + /// The tree must be a directed acyclic graph -- no cycles are allowed. Put more mundanely, + /// each model object must appear only once in the tree. If the same model object appears in two + /// places in the tree, the control will become confused. + /// + public partial class TreeListView : VirtualObjectListView + { + /// + /// Make a default TreeListView + /// + public TreeListView() { + this.OwnerDraw = true; + this.View = View.Details; + this.CheckedObjectsMustStillExistInList = false; + +// ReSharper disable DoNotCallOverridableMethodsInConstructor + this.RegenerateTree(); + this.TreeColumnRenderer = new TreeRenderer(); +// ReSharper restore DoNotCallOverridableMethodsInConstructor + + // This improves hit detection even if we don't have any state image + this.SmallImageList = new ImageList(); + // this.StateImageList.ImageSize = new Size(6, 6); + } + + //------------------------------------------------------------------------------------------ + // Properties + + /// + /// This is the delegate that will be used to decide if a model object can be expanded. + /// + /// + /// + /// This is called *often* -- on every mouse move when required. It must be fast. + /// Don't do database lookups, linear searches, or pi calculations. Just return the + /// value of a property. + /// + /// + /// When this delegate is called, the TreeListView is not in a stable state. Don't make + /// calls back into the control. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual CanExpandGetterDelegate CanExpandGetter { + get { return this.TreeModel.CanExpandGetter; } + set { this.TreeModel.CanExpandGetter = value; } + } + + /// + /// Gets whether or not this listview is capable of showing groups + /// + [Browsable(false)] + public override bool CanShowGroups { + get { + return false; + } + } + + /// + /// This is the delegate that will be used to fetch the children of a model object + /// + /// + /// + /// This delegate will only be called if the CanExpand delegate has + /// returned true for the model object. + /// + /// + /// When this delegate is called, the TreeListView is not in a stable state. Don't do anything + /// that will result in calls being made back into the control. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual ChildrenGetterDelegate ChildrenGetter { + get { return this.TreeModel.ChildrenGetter; } + set { this.TreeModel.ChildrenGetter = value; } + } + + /// + /// This is the delegate that will be used to fetch the parent of a model object + /// + /// The parent of the given model, or null if the model doesn't exist or + /// if the model is a root + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ParentGetterDelegate ParentGetter { + get { return parentGetter ?? Tree.DefaultParentGetter; } + set { parentGetter = value; } + } + private ParentGetterDelegate parentGetter; + + /// + /// Get or set the collection of model objects that are checked. + /// When setting this property, any row whose model object isn't + /// in the given collection will be unchecked. Setting to null is + /// equivalent to unchecking all. + /// + /// + /// + /// This property returns a simple collection. Changes made to the returned + /// collection do NOT affect the list. This is different to the behaviour of + /// CheckedIndicies collection. + /// + /// + /// When getting CheckedObjects, the performance of this method is O(n) where n is the number of checked objects. + /// When setting CheckedObjects, the performance of this method is O(n) where n is the number of checked objects plus + /// the number of objects to be checked. + /// + /// + /// If the ListView is not currently showing CheckBoxes, this property does nothing. It does + /// not remember any check box settings made. + /// + /// + public override IList CheckedObjects { + get { + return base.CheckedObjects; + } + set { + ArrayList objectsToRecalculate = new ArrayList(this.CheckedObjects); + if (value != null) + objectsToRecalculate.AddRange(value); + + base.CheckedObjects = value; + + if (this.HierarchicalCheckboxes) + RecalculateHierarchicalCheckBoxGraph(objectsToRecalculate); + } + } + + /// + /// Gets or sets the model objects that are expanded. + /// + /// + /// This can be used to expand model objects before they are seen. + /// + /// Setting this does *not* force the control to rebuild + /// its display. You need to call RebuildAll(true). + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IEnumerable ExpandedObjects { + get { + return this.TreeModel.mapObjectToExpanded.Keys; + } + set { + this.TreeModel.mapObjectToExpanded.Clear(); + if (value != null) { + foreach (object x in value) + this.TreeModel.SetModelExpanded(x, true); + } + } + } + + /// + /// Gets or sets the filter that is applied to our whole list of objects. + /// TreeListViews do not currently support whole list filters. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override IListFilter ListFilter { + get { return null; } + set { + System.Diagnostics.Debug.Assert(value == null, "TreeListView do not support ListFilters"); + } + } + + /// + /// Gets or sets whether this tree list view will display hierarchical checkboxes. + /// Hierarchical checkboxes is when a parent's "checkedness" is calculated from + /// the "checkedness" of its children. If all children are checked, the parent + /// will be checked. If all children are unchecked, the parent will also be unchecked. + /// If some children are checked and others are not, the parent will be indeterminate. + /// + /// + /// Hierarchical checkboxes don't work with either CheckStateGetters or CheckedAspectName + /// (which is basically the same thing). This is because it is too expensive to build the + /// initial state of the control if these are installed, since the control would have to walk + /// *every* branch recursively since a single bottom level leaf could change the checkedness + /// of the top root. + /// + [Category("ObjectListView"), + Description("Show hierarchical checkboxes be enabled?"), + DefaultValue(false)] + public virtual bool HierarchicalCheckboxes { + get { return this.hierarchicalCheckboxes; } + set { + if (this.hierarchicalCheckboxes == value) + return; + + this.hierarchicalCheckboxes = value; + this.CheckBoxes = value; + if (value) + this.TriStateCheckBoxes = false; + } + } + private bool hierarchicalCheckboxes; + + /// + /// Gets or sets the collection of root objects of the tree + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override IEnumerable Objects { + get { return this.Roots; } + set { this.Roots = value; } + } + + /// + /// Gets the collection of objects that will be considered when creating clusters + /// (which are used to generate Excel-like column filters) + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override IEnumerable ObjectsForClustering { + get { + for (int i = 0; i < this.TreeModel.GetObjectCount(); i++) + yield return this.TreeModel.GetNthObject(i); + } + } + + /// + /// After expanding a branch, should the TreeListView attempts to show as much of the + /// revealed descendants as possible. + /// + [Category("ObjectListView"), + Description("Should the parent of an expand subtree be scrolled to the top revealing the children?"), + DefaultValue(true)] + public bool RevealAfterExpand { + get { return revealAfterExpand; } + set { revealAfterExpand = value; } + } + private bool revealAfterExpand = true; + + /// + /// The model objects that form the top level branches of the tree. + /// + /// Setting this does NOT reset the state of the control. + /// In particular, it does not collapse branches. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IEnumerable Roots { + get { return this.TreeModel.RootObjects; } + set { + this.TreeColumnRenderer = this.TreeColumnRenderer; + this.TreeModel.RootObjects = value ?? new ArrayList(); + this.UpdateVirtualListSize(); + } + } + + /// + /// Make sure that at least one column is displaying a tree. + /// If no columns is showing the tree, make column 0 do it. + /// + protected virtual void EnsureTreeRendererPresent(TreeRenderer renderer) { + if (this.Columns.Count == 0) + return; + + foreach (OLVColumn col in this.Columns) { + if (col.Renderer is TreeRenderer) { + col.Renderer = renderer; + return; + } + } + + // No column held a tree renderer, so give column 0 one + OLVColumn columnZero = this.GetColumn(0); + columnZero.Renderer = renderer; + columnZero.WordWrap = columnZero.WordWrap; + } + + /// + /// Gets or sets the renderer that will be used to draw the tree structure. + /// Setting this to null resets the renderer to default. + /// + /// If a column is currently rendering the tree, the renderer + /// for that column will be replaced. If no column is rendering the tree, + /// column 0 will be given this renderer. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual TreeRenderer TreeColumnRenderer { + get { return treeRenderer ?? (treeRenderer = new TreeRenderer()); } + set { + treeRenderer = value ?? new TreeRenderer(); + EnsureTreeRendererPresent(treeRenderer); + } + } + private TreeRenderer treeRenderer; + + /// + /// This is the delegate that will be used to create the underlying Tree structure + /// that the TreeListView uses to manage the information about the tree. + /// + /// + /// The factory must not return null. + /// + /// Most users of TreeListView will never have to use this delegate. + /// + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public TreeFactoryDelegate TreeFactory { + get { return treeFactory; } + set { treeFactory = value; } + } + private TreeFactoryDelegate treeFactory; + + /// + /// Should a wait cursor be shown when a branch is being expanded? + /// + /// When this is true, the wait cursor will be shown whilst the children of the + /// branch are being fetched. If the children of the branch have already been cached, + /// the cursor will not change. + [Category("ObjectListView"), + Description("Should a wait cursor be shown when a branch is being expanded?"), + DefaultValue(true)] + public virtual bool UseWaitCursorWhenExpanding { + get { return useWaitCursorWhenExpanding; } + set { useWaitCursorWhenExpanding = value; } + } + private bool useWaitCursorWhenExpanding = true; + + /// + /// Gets the model that is used to manage the tree structure + /// + /// + /// Don't mess with this property unless you really know what you are doing. + /// If you don't already know what it's for, you don't need it. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Tree TreeModel { + get { return this.treeModel; } + protected set { this.treeModel = value; } + } + private Tree treeModel; + + //------------------------------------------------------------------------------------------ + // Accessing + + /// + /// Return true if the branch at the given model is expanded + /// + /// + /// + public virtual bool IsExpanded(Object model) { + Branch br = this.TreeModel.GetBranch(model); + return (br != null && br.IsExpanded); + } + + //------------------------------------------------------------------------------------------ + // Commands + + /// + /// Collapse the subtree underneath the given model + /// + /// + public virtual void Collapse(Object model) { + if (this.GetItemCount() == 0) + return; + + OLVListItem item = this.ModelToItem(model); + TreeBranchCollapsingEventArgs args = new TreeBranchCollapsingEventArgs(model, item); + this.OnCollapsing(args); + if (args.Canceled) + return; + + IList selection = this.SelectedObjects; + int index = this.TreeModel.Collapse(model); + if (index >= 0) { + this.UpdateVirtualListSize(); + this.SelectedObjects = selection; + if (index < this.GetItemCount()) + this.RedrawItems(index, this.GetItemCount() - 1, true); + this.OnCollapsed(new TreeBranchCollapsedEventArgs(model, item)); + } + } + + /// + /// Collapse all subtrees within this control + /// + public virtual void CollapseAll() { + if (this.GetItemCount() == 0) + return; + + TreeBranchCollapsingEventArgs args = new TreeBranchCollapsingEventArgs(null, null); + this.OnCollapsing(args); + if (args.Canceled) + return; + + IList selection = this.SelectedObjects; + int index = this.TreeModel.CollapseAll(); + if (index >= 0) { + this.UpdateVirtualListSize(); + this.SelectedObjects = selection; + if (index < this.GetItemCount()) + this.RedrawItems(index, this.GetItemCount() - 1, true); + this.OnCollapsed(new TreeBranchCollapsedEventArgs(null, null)); + } + } + + /// + /// Remove all items from this list + /// + /// This method can safely be called from background threads. + public override void ClearObjects() { + if (this.InvokeRequired) + this.Invoke(new MethodInvoker(this.ClearObjects)); + else { + this.Roots = null; + this.DiscardAllState(); + } + } + + /// + /// Collapse all roots and forget everything we know about all models + /// + public virtual void DiscardAllState() { + this.CheckStateMap.Clear(); + this.RebuildAll(false); + } + + /// + /// Expand the subtree underneath the given model object + /// + /// + public virtual void Expand(Object model) { + if (this.GetItemCount() == 0) + return; + + // Give the world a chance to cancel the expansion + OLVListItem item = this.ModelToItem(model); + TreeBranchExpandingEventArgs args = new TreeBranchExpandingEventArgs(model, item); + this.OnExpanding(args); + if (args.Canceled) + return; + + // Remember the selection so we can put it back later + IList selection = this.SelectedObjects; + + // Expand the model first + int index = this.TreeModel.Expand(model); + if (index < 0) + return; + + // Update the size of the list and restore the selection + this.UpdateVirtualListSize(); + using (this.SuspendSelectionEventsDuring()) + this.SelectedObjects = selection; + + // Redraw the items that were changed by the expand operation + this.RedrawItems(index, this.GetItemCount() - 1, true); + + this.OnExpanded(new TreeBranchExpandedEventArgs(model, item)); + + if (this.RevealAfterExpand && index > 0) { + // TODO: This should be a separate method + this.BeginUpdate(); + try { + int countPerPage = NativeMethods.GetCountPerPage(this); + int descedentCount = this.TreeModel.GetVisibleDescendentCount(model); + // If all of the descendants can be shown in the window, make sure that last one is visible. + // If all the descendants can't fit into the window, move the model to the top of the window + // (which will show as many of the descendants as possible) + if (descedentCount < countPerPage) { + this.EnsureVisible(index + descedentCount); + } else { + this.TopItemIndex = index; + } + } + finally { + this.EndUpdate(); + } + } + } + + /// + /// Expand all the branches within this tree recursively. + /// + /// Be careful: this method could take a long time for large trees. + public virtual void ExpandAll() { + if (this.GetItemCount() == 0) + return; + + // Give the world a chance to cancel the expansion + TreeBranchExpandingEventArgs args = new TreeBranchExpandingEventArgs(null, null); + this.OnExpanding(args); + if (args.Canceled) + return; + + IList selection = this.SelectedObjects; + int index = this.TreeModel.ExpandAll(); + if (index < 0) + return; + + this.UpdateVirtualListSize(); + using (this.SuspendSelectionEventsDuring()) + this.SelectedObjects = selection; + this.RedrawItems(index, this.GetItemCount() - 1, true); + this.OnExpanded(new TreeBranchExpandedEventArgs(null, null)); + } + + /// + /// Completely rebuild the tree structure + /// + /// If true, the control will try to preserve selection and expansion + public virtual void RebuildAll(bool preserveState) { + int previousTopItemIndex = preserveState ? this.TopItemIndex : -1; + + this.RebuildAll( + preserveState ? this.SelectedObjects : null, + preserveState ? this.ExpandedObjects : null, + preserveState ? this.CheckedObjects : null); + + if (preserveState) + this.TopItemIndex = previousTopItemIndex; + } + + /// + /// Completely rebuild the tree structure + /// + /// If not null, this list of objects will be selected after the tree is rebuilt + /// If not null, this collection of objects will be expanded after the tree is rebuilt + /// If not null, this collection of objects will be checked after the tree is rebuilt + protected virtual void RebuildAll(IList selected, IEnumerable expanded, IList checkedObjects) { + // Remember the bits of info we don't want to forget (anyone ever see Memento?) + IEnumerable roots = this.Roots; + CanExpandGetterDelegate canExpand = this.CanExpandGetter; + ChildrenGetterDelegate childrenGetter = this.ChildrenGetter; + + try { + this.BeginUpdate(); + + // Give ourselves a new data structure + this.RegenerateTree(); + + // Put back the bits we didn't want to forget + this.CanExpandGetter = canExpand; + this.ChildrenGetter = childrenGetter; + if (expanded != null) + this.ExpandedObjects = expanded; + this.Roots = roots; + if (selected != null) + this.SelectedObjects = selected; + if (checkedObjects != null) + this.CheckedObjects = checkedObjects; + } + finally { + this.EndUpdate(); + } + } + + /// + /// Unroll all the ancestors of the given model and make sure it is then visible. + /// + /// This works best when a ParentGetter is installed. + /// The object to be revealed + /// If true, the model will be selected and focused after being revealed + /// True if the object was found and revealed. False if it was not found. + public virtual void Reveal(object modelToReveal, bool selectAfterReveal) { + // Collect all the ancestors of the model + ArrayList ancestors = new ArrayList(); + foreach (object ancestor in this.GetAncestors(modelToReveal)) + ancestors.Add(ancestor); + + // Arrange them from root down to the model's immediate parent + ancestors.Reverse(); + try { + this.BeginUpdate(); + foreach (object ancestor in ancestors) + this.Expand(ancestor); + this.EnsureModelVisible(modelToReveal); + if (selectAfterReveal) + this.SelectObject(modelToReveal, true); + } + finally { + this.EndUpdate(); + } + } + + /// + /// Update the rows that are showing the given objects + /// + public override void RefreshObjects(IList modelObjects) { + if (this.InvokeRequired) { + this.Invoke((MethodInvoker) delegate { this.RefreshObjects(modelObjects); }); + return; + } + // There is no point in refreshing anything if the list is empty + if (this.GetItemCount() == 0) + return; + + // Remember the selection so we can put it back later + IList selection = this.SelectedObjects; + + // We actually need to refresh the parents. + // Refreshes on root objects have to be handled differently + ArrayList updatedRoots = new ArrayList(); + Hashtable modelsAndParents = new Hashtable(); + foreach (Object model in modelObjects) { + if (model == null) + continue; + modelsAndParents[model] = true; + object parent = GetParent(model); + if (parent == null) { + updatedRoots.Add(model); + } else { + modelsAndParents[parent] = true; + } + } + + // Update any changed roots + if (updatedRoots.Count > 0) { + ArrayList newRoots = ObjectListView.EnumerableToArray(this.Roots, false); + bool changed = false; + foreach (Object model in updatedRoots) { + int index = newRoots.IndexOf(model); + if (index >= 0 && !ReferenceEquals(newRoots[index], model)) { + newRoots[index] = model; + changed = true; + } + } + if (changed) + this.Roots = newRoots; + } + + // Refresh each object, remembering where the first update occurred + int firstChange = Int32.MaxValue; + foreach (Object model in modelsAndParents.Keys) { + if (model != null) { + int index = this.TreeModel.RebuildChildren(model); + if (index >= 0) + firstChange = Math.Min(firstChange, index); + } + } + + // If we didn't refresh any objects, don't do anything else + if (firstChange >= this.GetItemCount()) + return; + + this.ClearCachedInfo(); + this.UpdateVirtualListSize(); + this.SelectedObjects = selection; + + // Redraw everything from the first update to the end of the list + this.RedrawItems(firstChange, this.GetItemCount() - 1, true); + } + + /// + /// Change the check state of the given object to be the given state. + /// + /// + /// If the given model object isn't in the list, we still try to remember + /// its state, in case it is referenced in the future. + /// + /// + /// True if the checkedness of the model changed + protected override bool SetObjectCheckedness(object modelObject, CheckState state) { + // If the checkedness of the given model changes AND this tree has + // hierarchical checkboxes, then we need to update the checkedness of + // its children, and recalculate the checkedness of the parent (recursively) + if (!base.SetObjectCheckedness(modelObject, state)) + return false; + + if (!this.HierarchicalCheckboxes) + return true; + + // Give each child the same checkedness as the model + + CheckState? checkedness = this.GetCheckState(modelObject); + if (!checkedness.HasValue || checkedness.Value == CheckState.Indeterminate) + return true; + + foreach (object child in this.GetChildrenWithoutExpanding(modelObject)) { + this.SetObjectCheckedness(child, checkedness.Value); + } + + ArrayList args = new ArrayList(); + args.Add(modelObject); + this.RecalculateHierarchicalCheckBoxGraph(args); + + return true; + } + + + private IEnumerable GetChildrenWithoutExpanding(Object model) { + Branch br = this.TreeModel.GetBranch(model); + if (br == null || !br.CanExpand) + return new ArrayList(); + + return br.Children; + } + + /// + /// Toggle the expanded state of the branch at the given model object + /// + /// + public virtual void ToggleExpansion(Object model) { + if (this.IsExpanded(model)) + this.Collapse(model); + else + this.Expand(model); + } + + //------------------------------------------------------------------------------------------ + // Commands - Tree traversal + + /// + /// Return whether or not the given model can expand. + /// + /// + /// The given model must have already been seen in the tree + public virtual bool CanExpand(Object model) { + Branch br = this.TreeModel.GetBranch(model); + return (br != null && br.CanExpand); + } + + /// + /// Return the model object that is the parent of the given model object. + /// + /// + /// + /// The given model must have already been seen in the tree. + public virtual Object GetParent(Object model) { + Branch br = this.TreeModel.GetBranch(model); + return br == null || br.ParentBranch == null ? null : br.ParentBranch.Model; + } + + /// + /// Return the collection of model objects that are the children of the + /// given model as they exist in the tree at the moment. + /// + /// + /// + /// + /// This method returns the collection of children as the tree knows them. If the given + /// model has never been presented to the user (e.g. it belongs to a parent that has + /// never been expanded), then this method will return an empty collection. + /// + /// Because of this, if you want to traverse the whole tree, this is not the method to use. + /// It's better to traverse the your data model directly. + /// + /// + /// If the given model has not already been seen in the tree or + /// if it is not expandable, an empty collection will be returned. + /// + /// + public virtual IEnumerable GetChildren(Object model) { + Branch br = this.TreeModel.GetBranch(model); + if (br == null || !br.CanExpand) + return new ArrayList(); + + br.FetchChildren(); + + return br.Children; + } + + //------------------------------------------------------------------------------------------ + // Delegates + + /// + /// Delegates of this type are use to decide if the given model object can be expanded + /// + /// The model under consideration + /// Can the given model be expanded? + public delegate bool CanExpandGetterDelegate(Object model); + + /// + /// Delegates of this type are used to fetch the children of the given model object + /// + /// The parent whose children should be fetched + /// An enumerable over the children + public delegate IEnumerable ChildrenGetterDelegate(Object model); + + /// + /// Delegates of this type are used to fetch the parent of the given model object. + /// + /// The child whose parent should be fetched + /// The parent of the child or null if the child is a root + public delegate Object ParentGetterDelegate(Object model); + + /// + /// Delegates of this type are used to create a new underlying Tree structure. + /// + /// The view for which the Tree is being created + /// A subclass of Tree + public delegate Tree TreeFactoryDelegate(TreeListView view); + + //------------------------------------------------------------------------------------------ + #region Implementation + + /// + /// Handle a left button down event + /// + /// + /// + protected override bool ProcessLButtonDown(OlvListViewHitTestInfo hti) { + // Did they click in the expander? + if (hti.HitTestLocation == HitTestLocation.ExpandButton) { + this.PossibleFinishCellEditing(); + this.ToggleExpansion(hti.RowObject); + return true; + } + + return base.ProcessLButtonDown(hti); + } + + /// + /// Create a OLVListItem for given row index + /// + /// The index of the row that is needed + /// An OLVListItem + /// This differs from the base method by also setting up the IndentCount property. + public override OLVListItem MakeListViewItem(int itemIndex) { + OLVListItem olvItem = base.MakeListViewItem(itemIndex); + Branch br = this.TreeModel.GetBranch(olvItem.RowObject); + if (br != null) + olvItem.IndentCount = br.Level; + return olvItem; + } + + /// + /// Reinitialise the Tree structure + /// + protected virtual void RegenerateTree() { + this.TreeModel = this.TreeFactory == null ? new Tree(this) : this.TreeFactory(this); + Trace.Assert(this.TreeModel != null); + this.VirtualListDataSource = this.TreeModel; + } + + /// + /// Recalculate the state of the checkboxes of all the items in the given list + /// and their ancestors. + /// + /// This only makes sense when HierarchicalCheckboxes is true. + /// + protected virtual void RecalculateHierarchicalCheckBoxGraph(IList toCheck) { + if (toCheck == null || toCheck.Count == 0) + return; + + // Avoid recursive calculations + if (isRecalculatingHierarchicalCheckBox) + return; + + try { + isRecalculatingHierarchicalCheckBox = true; + foreach (object ancestor in CalculateDistinctAncestors(toCheck)) + this.RecalculateSingleHierarchicalCheckBox(ancestor); + } + finally { + isRecalculatingHierarchicalCheckBox = false; + } + + } + private bool isRecalculatingHierarchicalCheckBox; + + /// + /// Recalculate the hierarchy state of the given item and its ancestors + /// + /// This only makes sense when HierarchicalCheckboxes is true. + /// + protected virtual void RecalculateSingleHierarchicalCheckBox(object modelObject) { + + if (modelObject == null) + return; + + // Only branches have calculated check states. Leaf node checkedness is not calculated + if (!this.CanExpandUncached(modelObject)) + return; + + // Set the checkedness of the given model based on the state of its children. + CheckState? aggregate = null; + foreach (object child in this.GetChildrenUncached(modelObject)) { + CheckState? checkedness = this.GetCheckState(child); + if (!checkedness.HasValue) + continue; + + if (aggregate.HasValue) { + if (aggregate.Value != checkedness.Value) { + aggregate = CheckState.Indeterminate; + break; + } + } else + aggregate = checkedness; + } + + base.SetObjectCheckedness(modelObject, aggregate ?? CheckState.Indeterminate); + } + + private bool CanExpandUncached(object model) { + return this.CanExpandGetter != null && model != null && this.CanExpandGetter(model); + } + + private IEnumerable GetChildrenUncached(object model) { + return this.ChildrenGetter != null && model != null ? this.ChildrenGetter(model) : new ArrayList(); + } + + /// + /// Yield the unique ancestors of the given collection of objects. + /// The order of the ancestors is guaranteed to be deeper objects first. + /// Roots will always be last. + /// + /// + /// Unique ancestors of the given objects + protected virtual IEnumerable CalculateDistinctAncestors(IList toCheck) { + + if (toCheck.Count == 1) { + foreach (object ancestor in this.GetAncestors(toCheck[0])) { + yield return ancestor; + } + } else { + // WARNING - Clever code + + // Example: Root --> GP +--> P +--> A + // | +--> B + // | + // +--> Q +--> X + // +--> Y + // + // Calculate ancestors of A, B, X and Y + + // Build a list of all ancestors of all objects we need to check + ArrayList allAncestors = new ArrayList(); + foreach (object child in toCheck) { + foreach (object ancestor in this.GetAncestors(child)) { + allAncestors.Add(ancestor); + } + } + + // allAncestors = { P, GP, Root, P, GP, Root, Q, GP, Root, Q, GP, Root } + + // Reverse them so "higher" ancestors come first + allAncestors.Reverse(); + + // allAncestors = { Root, GP, Q, Root, GP, Q, Root, GP, P, Root, GP, P } + + ArrayList uniqueAncestors = new ArrayList(); + Dictionary alreadySeen = new Dictionary(); + foreach (object ancestor in allAncestors) { + if (!alreadySeen.ContainsKey(ancestor)) { + alreadySeen[ancestor] = true; + uniqueAncestors.Add(ancestor); + } + } + + // uniqueAncestors = { Root, GP, Q, P } + + uniqueAncestors.Reverse(); + foreach (object x in uniqueAncestors) + yield return x; + } + } + + /// + /// Return all the ancestors of the given model + /// + /// + /// + /// This uses ParentGetter if possible. + /// + /// If the given model is a root OR if the model doesn't exist, the collection will be empty + /// + /// The model whose ancestors should be calculated + /// Return a collection of ancestors of the given model. + protected virtual IEnumerable GetAncestors(object model) { + ParentGetterDelegate parentGetterDelegate = this.ParentGetter ?? this.GetParent; + + object parent = parentGetterDelegate(model); + while (parent != null) { + yield return parent; + parent = parentGetterDelegate(parent); + } + } + + #endregion + + //------------------------------------------------------------------------------------------ + #region Event handlers + + /// + /// The application is idle and a SelectionChanged event has been scheduled + /// + /// + /// + protected override void HandleApplicationIdle(object sender, EventArgs e) { + base.HandleApplicationIdle(sender, e); + + // There is an annoying redraw issue on ListViews that use indentation and + // that have full row select enabled. When the selection reduces to a subset + // of previously selected rows, or when the selection is extended using + // shift-pageup/down, then the space occupied by the indentation is not + // invalidated, and hence remains highlighted. + // Ideally we'd want to know exactly which rows were selected or deselected + // and then invalidate just the indentation region of those rows, + // but that's too much work. So just redraw the control. + // Actually... the selection issues show just slightly for non-full row select + // controls as well. So, always redraw the control after the selection + // changes. + this.Invalidate(); + } + + /// + /// Decide if the given key event should be handled as a normal key input to the control? + /// + /// + /// + protected override bool IsInputKey(Keys keyData) { + // We want to handle Left and Right keys within the control + Keys key = keyData & Keys.KeyCode; + if (key == Keys.Left || key == Keys.Right) + return true; + + return base.IsInputKey(keyData); + } + + /// + /// Handle focus being lost, including making sure that the whole control is redrawn. + /// + /// + protected override void OnLostFocus(EventArgs e) + { + // When this focus is lost, the normal invalidation logic doesn't invalid the region + // of the control created by the IndentLevel on each row. This makes the control + // look wrong when HideSelection is false, since part of the selected row's background + // correctly changes colour to the "inactive" colour, but the left part of the row + // created by IndentLevel doesn't change colour. + // SF #135. + + this.Invalidate(); + } + + /// + /// Handle the keyboard input to mimic a TreeView. + /// + /// + /// Was the key press handled? + protected override void OnKeyDown(KeyEventArgs e) { + OLVListItem focused = this.FocusedItem as OLVListItem; + if (focused == null) { + base.OnKeyDown(e); + return; + } + + Object modelObject = focused.RowObject; + Branch br = this.TreeModel.GetBranch(modelObject); + + switch (e.KeyCode) { + case Keys.Left: + // If the branch is expanded, collapse it. If it's collapsed, + // select the parent of the branch. + if (br.IsExpanded) + this.Collapse(modelObject); + else { + if (br.ParentBranch != null && br.ParentBranch.Model != null) + this.SelectObject(br.ParentBranch.Model, true); + } + e.Handled = true; + break; + + case Keys.Right: + // If the branch is expanded, select the first child. + // If it isn't expanded and can be, expand it. + if (br.IsExpanded) { + List filtered = br.FilteredChildBranches; + if (filtered.Count > 0) + this.SelectObject(filtered[0].Model, true); + } else { + if (br.CanExpand) + this.Expand(modelObject); + } + e.Handled = true; + break; + } + + base.OnKeyDown(e); + } + + #endregion + + //------------------------------------------------------------------------------------------ + // Support classes + + /// + /// A Tree object represents a tree structure data model that supports both + /// tree and flat list operations as well as fast access to branches. + /// + /// If you create a subclass of Tree, you must install it in the TreeListView + /// via the TreeFactory delegate. + public class Tree : IVirtualListDataSource, IFilterableDataSource + { + /// + /// Create a Tree + /// + /// + public Tree(TreeListView treeView) { + this.treeView = treeView; + this.trunk = new Branch(null, this, null); + this.trunk.IsExpanded = true; + } + + //------------------------------------------------------------------------------------------ + // Properties + + /// + /// This is the delegate that will be used to decide if a model object can be expanded. + /// + public CanExpandGetterDelegate CanExpandGetter { + get { return canExpandGetter ?? DefaultCanExpandGetter; } + set { canExpandGetter = value; } + } + private CanExpandGetterDelegate canExpandGetter; + + + /// + /// This is the delegate that will be used to fetch the children of a model object + /// + /// This delegate will only be called if the CanExpand delegate has + /// returned true for the model object. + public ChildrenGetterDelegate ChildrenGetter { + get { return childrenGetter ?? DefaultChildrenGetter; } + set { childrenGetter = value; } + } + private ChildrenGetterDelegate childrenGetter; + + /// + /// Get or return the top level model objects in the tree + /// + public IEnumerable RootObjects { + get { return this.trunk.Children; } + set { + this.trunk.Children = value; + foreach (Branch br in this.trunk.ChildBranches) + br.RefreshChildren(); + this.RebuildList(); + } + } + + /// + /// What tree view is this Tree the model for? + /// + public TreeListView TreeView { + get { return this.treeView; } + } + + //------------------------------------------------------------------------------------------ + // Commands + + /// + /// Collapse the subtree underneath the given model + /// + /// The model to be collapsed. If the model isn't in the tree, + /// or if it is already collapsed, the command does nothing. + /// The index of the model in flat list version of the tree + public virtual int Collapse(Object model) { + Branch br = this.GetBranch(model); + if (br == null || !br.IsExpanded) + return -1; + + // Remember that the branch is collapsed, even if it's currently not visible + if (!br.Visible) { + br.Collapse(); + return -1; + } + + int count = br.NumberVisibleDescendents; + br.Collapse(); + + // Remove the visible descendants from after the branch itself + int index = this.GetObjectIndex(model); + this.objectList.RemoveRange(index + 1, count); + this.RebuildObjectMap(0); + return index; + } + + /// + /// Collapse all branches in this tree + /// + /// Nothing useful + public virtual int CollapseAll() { + this.trunk.CollapseAll(); + this.RebuildList(); + return 0; + } + + /// + /// Expand the subtree underneath the given model object + /// + /// The model to be expanded. + /// The index of the model in flat list version of the tree + /// + /// If the model isn't in the tree, + /// if it cannot be expanded or if it is already expanded, the command does nothing. + /// + public virtual int Expand(Object model) { + Branch br = this.GetBranch(model); + if (br == null || !br.CanExpand || br.IsExpanded) + return -1; + + // Remember that the branch is expanded, even if it's currently not visible + br.Expand(); + if (!br.Visible) + { + return -1; + } + + int index = this.GetObjectIndex(model); + this.InsertChildren(br, index + 1); + return index; + } + + /// + /// Expand all branches in this tree + /// + /// Return the index of the first branch that was expanded + public virtual int ExpandAll() { + this.trunk.ExpandAll(); + this.Sort(this.lastSortColumn, this.lastSortOrder); + return 0; + } + + /// + /// Return the Branch object that represents the given model in the tree + /// + /// The model whose branches is to be returned + /// The branch that represents the given model, or null if the model + /// isn't in the tree. + public virtual Branch GetBranch(object model) { + if (model == null) + return null; + + Branch br; + this.mapObjectToBranch.TryGetValue(model, out br); + return br; + } + + /// + /// Return the number of visible descendants that are below the given model. + /// + /// The model whose descendent count is to be returned + /// The number of visible descendants. 0 if the model doesn't exist or is collapsed + public virtual int GetVisibleDescendentCount(object model) + { + Branch br = this.GetBranch(model); + return br == null || !br.IsExpanded ? 0 : br.NumberVisibleDescendents; + } + + /// + /// Rebuild the children of the given model, refreshing any cached information held about the given object + /// + /// + /// The index of the model in flat list version of the tree + public virtual int RebuildChildren(Object model) { + Branch br = this.GetBranch(model); + if (br == null || !br.Visible) + return -1; + + int count = br.NumberVisibleDescendents; + + // Remove the visible descendants from after the branch itself + int index = this.GetObjectIndex(model); + if (count > 0) + this.objectList.RemoveRange(index + 1, count); + + // Refresh our knowledge of our children (do this even if CanExpand is false, because + // the branch have already collected some children and that information could be stale) + br.RefreshChildren(); + + // Insert the refreshed children if the branch can expand and is expanded + if (br.CanExpand && br.IsExpanded) + this.InsertChildren(br, index + 1); + else + this.RebuildObjectMap(index); + + return index; + } + + //------------------------------------------------------------------------------------------ + // Implementation + + private static bool DefaultCanExpandGetter(object model) { + ITreeModelWithChildren treeModel = model as ITreeModelWithChildren; + return treeModel != null && treeModel.TreeCanExpand; + } + + private static IEnumerable DefaultChildrenGetter(object model) { + ITreeModelWithChildren treeModel = model as ITreeModelWithChildren; + return treeModel == null ? new ArrayList() : treeModel.TreeChildren; + } + + internal static object DefaultParentGetter(object model) { + ITreeModelWithParent treeModel = model as ITreeModelWithParent; + return treeModel == null ? null : treeModel.TreeParent; + } + + /// + /// Is the given model expanded? + /// + /// + /// + internal bool IsModelExpanded(object model) { + // Special case: model == null is the container for the roots. This is always expanded + if (model == null) + return true; + bool isExpanded; + this.mapObjectToExpanded.TryGetValue(model, out isExpanded); + return isExpanded; + } + + /// + /// Remember whether or not the given model was expanded + /// + /// + /// + internal void SetModelExpanded(object model, bool isExpanded) { + if (model == null) return; + + if (isExpanded) + this.mapObjectToExpanded[model] = true; + else + this.mapObjectToExpanded.Remove(model); + } + + /// + /// Insert the children of the given branch into the given position + /// + /// The branch whose children should be inserted + /// The index where the children should be inserted + protected virtual void InsertChildren(Branch br, int index) { + // Expand the branch + br.Expand(); + br.Sort(this.GetBranchComparer()); + + // Insert the branch's visible descendants after the branch itself + this.objectList.InsertRange(index, br.Flatten()); + this.RebuildObjectMap(index); + } + + /// + /// Rebuild our flat internal list of objects. + /// + protected virtual void RebuildList() { + this.objectList = ArrayList.Adapter(this.trunk.Flatten()); + List filtered = this.trunk.FilteredChildBranches; + if (filtered.Count > 0) { + filtered[0].IsFirstBranch = true; + filtered[0].IsOnlyBranch = (filtered.Count == 1); + } + this.RebuildObjectMap(0); + } + + /// + /// Rebuild our reverse index that maps an object to its location + /// in the filteredObjectList array. + /// + /// + protected virtual void RebuildObjectMap(int startIndex) { + if (startIndex == 0) + this.mapObjectToIndex.Clear(); + for (int i = startIndex; i < this.objectList.Count; i++) + this.mapObjectToIndex[this.objectList[i]] = i; + } + + /// + /// Create a new branch within this tree + /// + /// + /// + /// + internal Branch MakeBranch(Branch parent, object model) { + Branch br = new Branch(parent, this, model); + + // Remember that the given branch is part of this tree. + this.mapObjectToBranch[model] = br; + return br; + } + + //------------------------------------------------------------------------------------------ + + #region IVirtualListDataSource Members + + /// + /// + /// + /// + /// + public virtual object GetNthObject(int n) { + if (n >= 0 && n < this.objectList.Count) + return this.objectList[n]; + return null; + } + + /// + /// + /// + /// + public virtual int GetObjectCount() { + return this.trunk.NumberVisibleDescendents; + } + + /// + /// + /// + /// + /// + public virtual int GetObjectIndex(object model) + { + int index; + if (model != null && this.mapObjectToIndex.TryGetValue(model, out index)) + return index; + + return -1; + } + + /// + /// + /// + /// + /// + public virtual void PrepareCache(int first, int last) { + } + + /// + /// + /// + /// + /// + /// + /// + /// + public virtual int SearchText(string value, int first, int last, OLVColumn column) { + return AbstractVirtualListDataSource.DefaultSearchText(value, first, last, column, this); + } + + /// + /// Sort the tree on the given column and in the given order + /// + /// + /// + public virtual void Sort(OLVColumn column, SortOrder order) { + this.lastSortColumn = column; + this.lastSortOrder = order; + + // TODO: Need to raise an AboutToSortEvent here + + // Sorting is going to change the order of the branches so clear + // the "first branch" flag + foreach (Branch b in this.trunk.ChildBranches) + b.IsFirstBranch = false; + + this.trunk.Sort(this.GetBranchComparer()); + this.RebuildList(); + } + + /// + /// + /// + /// + protected virtual BranchComparer GetBranchComparer() { + if (this.lastSortColumn == null) + return null; + + return new BranchComparer(new ModelObjectComparer( + this.lastSortColumn, + this.lastSortOrder, + this.treeView.SecondarySortColumn ?? this.treeView.GetColumn(0), + this.treeView.SecondarySortColumn == null ? this.lastSortOrder : this.treeView.SecondarySortOrder)); + } + + /// + /// Add the given collection of objects to the roots of this tree + /// + /// + public virtual void AddObjects(ICollection modelObjects) { + ArrayList newRoots = ObjectListView.EnumerableToArray(this.treeView.Roots, true); + foreach (Object x in modelObjects) + newRoots.Add(x); + this.SetObjects(newRoots); + } + + /// + /// + /// + /// + /// + public void InsertObjects(int index, ICollection modelObjects) { + throw new NotImplementedException(); + } + + /// + /// Remove all of the given objects from the roots of the tree. + /// Any objects that is not already in the roots collection is ignored. + /// + /// + public virtual void RemoveObjects(ICollection modelObjects) { + ArrayList newRoots = new ArrayList(); + foreach (Object x in this.treeView.Roots) + newRoots.Add(x); + foreach (Object x in modelObjects) { + newRoots.Remove(x); + this.mapObjectToIndex.Remove(x); + } + this.SetObjects(newRoots); + } + + /// + /// Set the roots of this tree to be the given collection + /// + /// + public virtual void SetObjects(IEnumerable collection) { + // We interpret a SetObjects() call as setting the roots of the tree + this.treeView.Roots = collection; + } + + /// + /// Update/replace the nth object with the given object + /// + /// + /// + public void UpdateObject(int index, object modelObject) { + ArrayList newRoots = ObjectListView.EnumerableToArray(this.treeView.Roots, false); + if (index < newRoots.Count) + newRoots[index] = modelObject; + SetObjects(newRoots); + } + + #endregion + + #region IFilterableDataSource Members + + /// + /// + /// + /// + /// + public void ApplyFilters(IModelFilter mFilter, IListFilter lFilter) { + this.modelFilter = mFilter; + this.listFilter = lFilter; + this.RebuildList(); + } + + /// + /// Is this list currently being filtered? + /// + internal bool IsFiltering { + get { + return this.treeView.UseFiltering && (this.modelFilter != null || this.listFilter != null); + } + } + + /// + /// Should the given model be included in this control? + /// + /// The model to consider + /// True if it will be included + internal bool IncludeModel(object model) { + if (!this.treeView.UseFiltering) + return true; + + if (this.modelFilter == null) + return true; + + return this.modelFilter.Filter(model); + } + + #endregion + + //------------------------------------------------------------------------------------------ + // Private instance variables + + private OLVColumn lastSortColumn; + private SortOrder lastSortOrder; + private readonly Dictionary mapObjectToBranch = new Dictionary(); +// ReSharper disable once InconsistentNaming + internal Dictionary mapObjectToExpanded = new Dictionary(); + private readonly Dictionary mapObjectToIndex = new Dictionary(); + private ArrayList objectList = new ArrayList(); + private readonly TreeListView treeView; + private readonly Branch trunk; + + /// + /// + /// +// ReSharper disable once InconsistentNaming + protected IModelFilter modelFilter; + /// + /// + /// +// ReSharper disable once InconsistentNaming + protected IListFilter listFilter; + } + + /// + /// A Branch represents a sub-tree within a tree + /// + public class Branch + { + /// + /// Indicators for branches + /// + [Flags] + public enum BranchFlags + { + /// + /// FirstBranch of tree + /// + FirstBranch = 1, + + /// + /// LastChild of parent + /// + LastChild = 2, + + /// + /// OnlyBranch of tree + /// + OnlyBranch = 4 + } + + #region Life and death + + /// + /// Create a Branch + /// + /// + /// + /// + public Branch(Branch parent, Tree tree, Object model) { + this.ParentBranch = parent; + this.Tree = tree; + this.Model = model; + } + + #endregion + + #region Public properties + + //------------------------------------------------------------------------------------------ + // Properties + + /// + /// Get the ancestor branches of this branch, with the 'oldest' ancestor first. + /// + public virtual IList Ancestors { + get { + List ancestors = new List(); + if (this.ParentBranch != null) + this.ParentBranch.PushAncestors(ancestors); + return ancestors; + } + } + + private void PushAncestors(IList list) { + // This is designed to ignore the trunk (which has no parent) + if (this.ParentBranch != null) { + this.ParentBranch.PushAncestors(list); + list.Add(this); + } + } + + /// + /// Can this branch be expanded? + /// + public virtual bool CanExpand { + get { + if (this.Tree.CanExpandGetter == null || this.Model == null) + return false; + + return this.Tree.CanExpandGetter(this.Model); + } + } + + /// + /// Gets or sets our children + /// + public List ChildBranches { + get { return this.childBranches; } + set { this.childBranches = value; } + } + private List childBranches = new List(); + + /// + /// Get/set the model objects that are beneath this branch + /// + public virtual IEnumerable Children { + get { + ArrayList children = new ArrayList(); + foreach (Branch x in this.ChildBranches) + children.Add(x.Model); + return children; + } + set { + this.ChildBranches.Clear(); + + TreeListView treeListView = this.Tree.TreeView; + CheckState? checkedness = null; + if (treeListView != null && treeListView.HierarchicalCheckboxes) + checkedness = treeListView.GetCheckState(this.Model); + foreach (Object x in value) { + this.AddChild(x); + + // If the tree view is showing hierarchical checkboxes, then + // when a child object is first added, it has the same checkedness as this branch + if (checkedness.HasValue && checkedness.Value == CheckState.Checked) + treeListView.SetObjectCheckedness(x, checkedness.Value); + } + } + } + + private void AddChild(object childModel) { + Branch br = this.Tree.GetBranch(childModel); + if (br == null) + br = this.Tree.MakeBranch(this, childModel); + else { + br.ParentBranch = this; + br.Model = childModel; + br.ClearCachedInfo(); + } + this.ChildBranches.Add(br); + } + + /// + /// Gets a list of all the branches that survive filtering + /// + public List FilteredChildBranches { + get { + if (!this.IsExpanded) + return new List(); + + if (!this.Tree.IsFiltering) + return this.ChildBranches; + + List filtered = new List(); + foreach (Branch b in this.ChildBranches) { + if (this.Tree.IncludeModel(b.Model)) + filtered.Add(b); + else { + // Also include this branch if it has any filtered branches (yes, its recursive) + if (b.FilteredChildBranches.Count > 0) + filtered.Add(b); + } + } + return filtered; + } + } + + /// + /// Gets or set whether this branch is expanded + /// + public bool IsExpanded { + get { return this.Tree.IsModelExpanded(this.Model); } + set { this.Tree.SetModelExpanded(this.Model, value); } + } + + /// + /// Return true if this branch is the first branch of the entire tree + /// + public virtual bool IsFirstBranch { + get { + return ((this.flags & Branch.BranchFlags.FirstBranch) != 0); + } + set { + if (value) + this.flags |= Branch.BranchFlags.FirstBranch; + else + this.flags &= ~Branch.BranchFlags.FirstBranch; + } + } + + /// + /// Return true if this branch is the last child of its parent + /// + public virtual bool IsLastChild { + get { + return ((this.flags & Branch.BranchFlags.LastChild) != 0); + } + set { + if (value) + this.flags |= Branch.BranchFlags.LastChild; + else + this.flags &= ~Branch.BranchFlags.LastChild; + } + } + + /// + /// Return true if this branch is the only top level branch + /// + public virtual bool IsOnlyBranch { + get { + return ((this.flags & Branch.BranchFlags.OnlyBranch) != 0); + } + set { + if (value) + this.flags |= Branch.BranchFlags.OnlyBranch; + else + this.flags &= ~Branch.BranchFlags.OnlyBranch; + } + } + + /// + /// Gets the depth level of this branch + /// + public int Level { + get { + if (this.ParentBranch == null) + return 0; + + return this.ParentBranch.Level + 1; + } + } + + /// + /// Gets or sets which model is represented by this branch + /// + public Object Model { + get { return model; } + set { model = value; } + } + private Object model; + + /// + /// Return the number of descendants of this branch that are currently visible + /// + /// + public virtual int NumberVisibleDescendents { + get { + if (!this.IsExpanded) + return 0; + + List filtered = this.FilteredChildBranches; + int count = filtered.Count; + foreach (Branch br in filtered) + count += br.NumberVisibleDescendents; + return count; + } + } + + /// + /// Gets or sets our parent branch + /// + public Branch ParentBranch { + get { return parentBranch; } + set { parentBranch = value; } + } + private Branch parentBranch; + + /// + /// Gets or sets our overall tree + /// + public Tree Tree { + get { return tree; } + set { tree = value; } + } + private Tree tree; + + /// + /// Is this branch currently visible? A branch is visible + /// if it has no parent (i.e. it's a root), or its parent + /// is visible and expanded. + /// + public virtual bool Visible { + get { + if (this.ParentBranch == null) + return true; + + return this.ParentBranch.IsExpanded && this.ParentBranch.Visible; + } + } + + #endregion + + #region Commands + + //------------------------------------------------------------------------------------------ + // Commands + + /// + /// Clear any cached information that this branch is holding + /// + public virtual void ClearCachedInfo() { + this.Children = new ArrayList(); + this.alreadyHasChildren = false; + } + + /// + /// Collapse this branch + /// + public virtual void Collapse() { + this.IsExpanded = false; + } + + /// + /// Expand this branch + /// + public virtual void Expand() { + if (this.CanExpand) { + this.IsExpanded = true; + this.FetchChildren(); + } + } + + /// + /// Expand this branch recursively + /// + public virtual void ExpandAll() { + this.Expand(); + foreach (Branch br in this.ChildBranches) { + if (br.CanExpand) + br.ExpandAll(); + } + } + + /// + /// Collapse all branches in this tree + /// + /// Nothing useful + public virtual void CollapseAll() + { + this.Collapse(); + foreach (Branch br in this.ChildBranches) { + if (br.IsExpanded) + br.CollapseAll(); + } + } + + /// + /// Fetch the children of this branch. + /// + /// This should only be called when CanExpand is true. + public virtual void FetchChildren() { + if (this.alreadyHasChildren) + return; + + this.alreadyHasChildren = true; + + if (this.Tree.ChildrenGetter == null) + return; + + Cursor previous = Cursor.Current; + try { + if (this.Tree.TreeView.UseWaitCursorWhenExpanding) + Cursor.Current = Cursors.WaitCursor; + this.Children = this.Tree.ChildrenGetter(this.Model); + } + finally { + Cursor.Current = previous; + } + } + + /// + /// Collapse the visible descendants of this branch into list of model objects + /// + /// + public virtual IList Flatten() { + ArrayList flatList = new ArrayList(); + if (this.IsExpanded) + this.FlattenOnto(flatList); + return flatList; + } + + /// + /// Flatten this branch's visible descendants onto the given list. + /// + /// + /// The branch itself is not included in the list. + public virtual void FlattenOnto(IList flatList) { + Branch lastBranch = null; + foreach (Branch br in this.FilteredChildBranches) { + lastBranch = br; + br.IsFirstBranch = br.IsOnlyBranch = br.IsLastChild = false; + flatList.Add(br.Model); + if (br.IsExpanded) { + br.FetchChildren(); // make sure we have the branches children + br.FlattenOnto(flatList); + } + } + if (lastBranch != null) + lastBranch.IsLastChild = true; + } + + /// + /// Force a refresh of all children recursively + /// + public virtual void RefreshChildren() { + + // Forget any previous children. We always do this so that if + // IsExpanded or CanExpand have changed, we aren't left with stale information. + this.ClearCachedInfo(); + + if (!this.IsExpanded || !this.CanExpand) + return; + + this.FetchChildren(); + foreach (Branch br in this.ChildBranches) + br.RefreshChildren(); + } + + /// + /// Sort the sub-branches and their descendants so they are ordered according + /// to the given comparer. + /// + /// The comparer that orders the branches + public virtual void Sort(BranchComparer comparer) { + if (this.ChildBranches.Count == 0) + return; + + if (comparer != null) + this.ChildBranches.Sort(comparer); + + foreach (Branch br in this.ChildBranches) + br.Sort(comparer); + } + + #endregion + + + //------------------------------------------------------------------------------------------ + // Private instance variables + + private bool alreadyHasChildren; + private BranchFlags flags; + } + + /// + /// This class sorts branches according to how their respective model objects are sorted + /// + public class BranchComparer : IComparer + { + /// + /// Create a BranchComparer + /// + /// + public BranchComparer(IComparer actualComparer) { + this.actualComparer = actualComparer; + } + + /// + /// Order the two branches + /// + /// + /// + /// + public int Compare(Branch x, Branch y) { + return this.actualComparer.Compare(x.Model, y.Model); + } + + private readonly IComparer actualComparer; + } + + } + + /// + /// This interface should be implemented by model objects that can provide children, + /// but that don't have a parent. This is either because the model objects are always + /// root level, or because they are used in TreeListView that never uses parent + /// calculations. Parent calculations are only used when HierarchicalCheckBoxes is true. + /// + public interface ITreeModelWithChildren { + /// + /// Get whether this this model can be expanded? If true, an expand glyph will be drawn next to it. + /// + /// This is called often! It must be fast. Dont do a database lookup, calculate pi, or do linear searches just return a property value. + bool TreeCanExpand { get; } + + /// + /// Get the models that will be shown under this model when it is expanded. + /// + /// This is only called when CanExpand returns true. + IEnumerable TreeChildren { get; } + } + + /// + /// This interface should be implemented by model objects that can never have children, + /// but that are used in a TreeListView that uses parent calculations. + /// Parent calculations are only used when HierarchicalCheckBoxes is true. + /// + public interface ITreeModelWithParent { + + /// + /// Get the hierarchical parent of this model. + /// + object TreeParent { get; } + } + + /// + /// ITreeModel allows model objects to provide the required information to TreeListView + /// without using the normal Getter delegates. + /// + public interface ITreeModel: ITreeModelWithChildren, ITreeModelWithParent { + + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Utilities/ColumnSelectionForm.Designer.cs b/VG Music Studio - WinForms/ObjectListView/Utilities/ColumnSelectionForm.Designer.cs new file mode 100644 index 0000000..528e444 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Utilities/ColumnSelectionForm.Designer.cs @@ -0,0 +1,190 @@ +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + partial class ColumnSelectionForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.buttonMoveUp = new System.Windows.Forms.Button(); + this.buttonMoveDown = new System.Windows.Forms.Button(); + this.buttonShow = new System.Windows.Forms.Button(); + this.buttonHide = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.buttonOK = new System.Windows.Forms.Button(); + this.buttonCancel = new System.Windows.Forms.Button(); + this.objectListView1 = new Kermalis.VGMusicStudio.WinForms.ObjectListView.ObjectListView(); + this.olvColumn1 = new Kermalis.VGMusicStudio.WinForms.ObjectListView.OLVColumn(); + ((System.ComponentModel.ISupportInitialize)(this.objectListView1)).BeginInit(); + this.SuspendLayout(); + // + // buttonMoveUp + // + this.buttonMoveUp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.buttonMoveUp.Location = new System.Drawing.Point(295, 31); + this.buttonMoveUp.Name = "buttonMoveUp"; + this.buttonMoveUp.Size = new System.Drawing.Size(87, 23); + this.buttonMoveUp.TabIndex = 1; + this.buttonMoveUp.Text = "Move &Up"; + this.buttonMoveUp.UseVisualStyleBackColor = true; + this.buttonMoveUp.Click += new System.EventHandler(this.buttonMoveUp_Click); + // + // buttonMoveDown + // + this.buttonMoveDown.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.buttonMoveDown.Location = new System.Drawing.Point(295, 60); + this.buttonMoveDown.Name = "buttonMoveDown"; + this.buttonMoveDown.Size = new System.Drawing.Size(87, 23); + this.buttonMoveDown.TabIndex = 2; + this.buttonMoveDown.Text = "Move &Down"; + this.buttonMoveDown.UseVisualStyleBackColor = true; + this.buttonMoveDown.Click += new System.EventHandler(this.buttonMoveDown_Click); + // + // buttonShow + // + this.buttonShow.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.buttonShow.Location = new System.Drawing.Point(295, 89); + this.buttonShow.Name = "buttonShow"; + this.buttonShow.Size = new System.Drawing.Size(87, 23); + this.buttonShow.TabIndex = 3; + this.buttonShow.Text = "&Show"; + this.buttonShow.UseVisualStyleBackColor = true; + this.buttonShow.Click += new System.EventHandler(this.buttonShow_Click); + // + // buttonHide + // + this.buttonHide.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.buttonHide.Location = new System.Drawing.Point(295, 118); + this.buttonHide.Name = "buttonHide"; + this.buttonHide.Size = new System.Drawing.Size(87, 23); + this.buttonHide.TabIndex = 4; + this.buttonHide.Text = "&Hide"; + this.buttonHide.UseVisualStyleBackColor = true; + this.buttonHide.Click += new System.EventHandler(this.buttonHide_Click); + // + // label1 + // + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label1.BackColor = System.Drawing.SystemColors.Control; + this.label1.Location = new System.Drawing.Point(13, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(366, 19); + this.label1.TabIndex = 5; + this.label1.Text = "Choose the columns you want to see in this list. "; + // + // buttonOK + // + this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonOK.Location = new System.Drawing.Point(198, 304); + this.buttonOK.Name = "buttonOK"; + this.buttonOK.Size = new System.Drawing.Size(87, 23); + this.buttonOK.TabIndex = 6; + this.buttonOK.Text = "&OK"; + this.buttonOK.UseVisualStyleBackColor = true; + this.buttonOK.Click += new System.EventHandler(this.buttonOK_Click); + // + // buttonCancel + // + this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.buttonCancel.Location = new System.Drawing.Point(295, 304); + this.buttonCancel.Name = "buttonCancel"; + this.buttonCancel.Size = new System.Drawing.Size(87, 23); + this.buttonCancel.TabIndex = 7; + this.buttonCancel.Text = "&Cancel"; + this.buttonCancel.UseVisualStyleBackColor = true; + this.buttonCancel.Click += new System.EventHandler(this.buttonCancel_Click); + // + // objectListView1 + // + this.objectListView1.AllColumns.Add(this.olvColumn1); + this.objectListView1.AlternateRowBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(255)))), ((int)(((byte)(192))))); + this.objectListView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.objectListView1.CellEditActivation = Kermalis.VGMusicStudio.WinForms.ObjectListView.ObjectListView.CellEditActivateMode.SingleClick; + this.objectListView1.CheckBoxes = true; + this.objectListView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.olvColumn1}); + this.objectListView1.FullRowSelect = true; + this.objectListView1.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None; + this.objectListView1.HideSelection = false; + this.objectListView1.Location = new System.Drawing.Point(12, 31); + this.objectListView1.MultiSelect = false; + this.objectListView1.Name = "objectListView1"; + this.objectListView1.ShowGroups = false; + this.objectListView1.ShowSortIndicators = false; + this.objectListView1.Size = new System.Drawing.Size(273, 259); + this.objectListView1.TabIndex = 0; + this.objectListView1.UseCompatibleStateImageBehavior = false; + this.objectListView1.View = System.Windows.Forms.View.Details; + this.objectListView1.SelectionChanged += new System.EventHandler(this.objectListView1_SelectionChanged); + // + // olvColumn1 + // + this.olvColumn1.AspectName = "Text"; + this.olvColumn1.IsVisible = true; + this.olvColumn1.Text = "Column"; + this.olvColumn1.Width = 267; + // + // ColumnSelectionForm + // + this.AcceptButton = this.buttonOK; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.buttonCancel; + this.ClientSize = new System.Drawing.Size(391, 339); + this.Controls.Add(this.buttonCancel); + this.Controls.Add(this.buttonOK); + this.Controls.Add(this.label1); + this.Controls.Add(this.buttonHide); + this.Controls.Add(this.buttonShow); + this.Controls.Add(this.buttonMoveDown); + this.Controls.Add(this.buttonMoveUp); + this.Controls.Add(this.objectListView1); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ColumnSelectionForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.Text = "Column Selection"; + ((System.ComponentModel.ISupportInitialize)(this.objectListView1)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private Kermalis.VGMusicStudio.WinForms.ObjectListView.ObjectListView objectListView1; + private System.Windows.Forms.Button buttonMoveUp; + private System.Windows.Forms.Button buttonMoveDown; + private System.Windows.Forms.Button buttonShow; + private System.Windows.Forms.Button buttonHide; + private Kermalis.VGMusicStudio.WinForms.ObjectListView.OLVColumn olvColumn1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Button buttonOK; + private System.Windows.Forms.Button buttonCancel; + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/Utilities/ColumnSelectionForm.cs b/VG Music Studio - WinForms/ObjectListView/Utilities/ColumnSelectionForm.cs new file mode 100644 index 0000000..8aa516b --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Utilities/ColumnSelectionForm.cs @@ -0,0 +1,263 @@ +/* + * ColumnSelectionForm - A utility form that allows columns to be rearranged and/or hidden + * + * Author: Phillip Piper + * Date: 1/04/2011 11:15 AM + * + * Change log: + * 2013-04-21 JPP - Fixed obscure bug in column re-ordered. Thanks to Edwin Chen. + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// This form is an example of how an application could allows the user to select which columns + /// an ObjectListView will display, as well as select which order the columns are displayed in. + /// + /// + /// In Tile view, ColumnHeader.DisplayIndex does nothing. To reorder the columns you have + /// to change the order of objects in the Columns property. + /// Remember that the first column is special! + /// It has to remain the first column. + /// + public partial class ColumnSelectionForm : Form + { + /// + /// Make a new ColumnSelectionForm + /// + public ColumnSelectionForm() + { + InitializeComponent(); + } + + /// + /// Open this form so it will edit the columns that are available in the listview's current view + /// + /// The ObjectListView whose columns are to be altered + public void OpenOn(ObjectListView olv) + { + this.OpenOn(olv, olv.View); + } + + /// + /// Open this form so it will edit the columns that are available in the given listview + /// when the listview is showing the given type of view. + /// + /// The ObjectListView whose columns are to be altered + /// The view that is to be altered. Must be View.Details or View.Tile + public void OpenOn(ObjectListView olv, View view) + { + if (view != View.Details && view != View.Tile) + return; + + this.InitializeForm(olv, view); + if (this.ShowDialog() == DialogResult.OK) + this.Apply(olv, view); + } + + /// + /// Initialize the form to show the columns of the given view + /// + /// + /// + protected void InitializeForm(ObjectListView olv, View view) + { + this.AllColumns = olv.AllColumns; + this.RearrangableColumns = new List(this.AllColumns); + foreach (OLVColumn col in this.RearrangableColumns) { + if (view == View.Details) + this.MapColumnToVisible[col] = col.IsVisible; + else + this.MapColumnToVisible[col] = col.IsTileViewColumn; + } + this.RearrangableColumns.Sort(new SortByDisplayOrder(this)); + + this.objectListView1.BooleanCheckStateGetter = delegate(Object rowObject) { + return this.MapColumnToVisible[(OLVColumn)rowObject]; + }; + + this.objectListView1.BooleanCheckStatePutter = delegate(Object rowObject, bool newValue) { + // Some columns should always be shown, so ignore attempts to hide them + OLVColumn column = (OLVColumn)rowObject; + if (!column.CanBeHidden) + return true; + + this.MapColumnToVisible[column] = newValue; + EnableControls(); + return newValue; + }; + + this.objectListView1.SetObjects(this.RearrangableColumns); + this.EnableControls(); + } + private List AllColumns = null; + private List RearrangableColumns = new List(); + private Dictionary MapColumnToVisible = new Dictionary(); + + /// + /// The user has pressed OK. Do what's required. + /// + /// + /// + protected void Apply(ObjectListView olv, View view) + { + olv.Freeze(); + + // Update the column definitions to reflect whether they have been hidden + if (view == View.Details) { + foreach (OLVColumn col in olv.AllColumns) + col.IsVisible = this.MapColumnToVisible[col]; + } else { + foreach (OLVColumn col in olv.AllColumns) + col.IsTileViewColumn = this.MapColumnToVisible[col]; + } + + // Collect the columns are still visible + List visibleColumns = this.RearrangableColumns.FindAll( + delegate(OLVColumn x) { return this.MapColumnToVisible[x]; }); + + // Detail view and Tile view have to be handled in different ways. + if (view == View.Details) { + // Of the still visible columns, change DisplayIndex to reflect their position in the rearranged list + olv.ChangeToFilteredColumns(view); + foreach (OLVColumn col in visibleColumns) { + col.DisplayIndex = visibleColumns.IndexOf((OLVColumn)col); + col.LastDisplayIndex = col.DisplayIndex; + } + } else { + // In Tile view, DisplayOrder does nothing. So to change the display order, we have to change the + // order of the columns in the Columns property. + // Remember, the primary column is special and has to remain first! + OLVColumn primaryColumn = this.AllColumns[0]; + visibleColumns.Remove(primaryColumn); + + olv.Columns.Clear(); + olv.Columns.Add(primaryColumn); + olv.Columns.AddRange(visibleColumns.ToArray()); + olv.CalculateReasonableTileSize(); + } + + olv.Unfreeze(); + } + + #region Event handlers + + private void buttonMoveUp_Click(object sender, EventArgs e) + { + int selectedIndex = this.objectListView1.SelectedIndices[0]; + OLVColumn col = this.RearrangableColumns[selectedIndex]; + this.RearrangableColumns.RemoveAt(selectedIndex); + this.RearrangableColumns.Insert(selectedIndex-1, col); + + this.objectListView1.BuildList(); + + EnableControls(); + } + + private void buttonMoveDown_Click(object sender, EventArgs e) + { + int selectedIndex = this.objectListView1.SelectedIndices[0]; + OLVColumn col = this.RearrangableColumns[selectedIndex]; + this.RearrangableColumns.RemoveAt(selectedIndex); + this.RearrangableColumns.Insert(selectedIndex + 1, col); + + this.objectListView1.BuildList(); + + EnableControls(); + } + + private void buttonShow_Click(object sender, EventArgs e) + { + this.objectListView1.SelectedItem.Checked = true; + } + + private void buttonHide_Click(object sender, EventArgs e) + { + this.objectListView1.SelectedItem.Checked = false; + } + + private void buttonOK_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void buttonCancel_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.Cancel; + this.Close(); + } + + private void objectListView1_SelectionChanged(object sender, EventArgs e) + { + EnableControls(); + } + + #endregion + + #region Control enabling + + /// + /// Enable the controls on the dialog to match the current state + /// + protected void EnableControls() + { + if (this.objectListView1.SelectedIndices.Count == 0) { + this.buttonMoveUp.Enabled = false; + this.buttonMoveDown.Enabled = false; + this.buttonShow.Enabled = false; + this.buttonHide.Enabled = false; + } else { + // Can't move the first row up or the last row down + this.buttonMoveUp.Enabled = (this.objectListView1.SelectedIndices[0] != 0); + this.buttonMoveDown.Enabled = (this.objectListView1.SelectedIndices[0] < (this.objectListView1.GetItemCount() - 1)); + + OLVColumn selectedColumn = (OLVColumn)this.objectListView1.SelectedObject; + + // Some columns cannot be hidden (and hence cannot be Shown) + this.buttonShow.Enabled = !this.MapColumnToVisible[selectedColumn] && selectedColumn.CanBeHidden; + this.buttonHide.Enabled = this.MapColumnToVisible[selectedColumn] && selectedColumn.CanBeHidden; + } + } + #endregion + + /// + /// A Comparer that will sort a list of columns so that visible ones come before hidden ones, + /// and that are ordered by their display order. + /// + private class SortByDisplayOrder : IComparer + { + public SortByDisplayOrder(ColumnSelectionForm form) + { + this.Form = form; + } + private ColumnSelectionForm Form; + + #region IComparer Members + + int IComparer.Compare(OLVColumn x, OLVColumn y) + { + if (this.Form.MapColumnToVisible[x] && !this.Form.MapColumnToVisible[y]) + return -1; + + if (!this.Form.MapColumnToVisible[x] && this.Form.MapColumnToVisible[y]) + return 1; + + if (x.DisplayIndex == y.DisplayIndex) + return x.Text.CompareTo(y.Text); + else + return x.DisplayIndex - y.DisplayIndex; + } + + #endregion + } + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Utilities/ColumnSelectionForm.resx b/VG Music Studio - WinForms/ObjectListView/Utilities/ColumnSelectionForm.resx new file mode 100644 index 0000000..19dc0dd --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Utilities/ColumnSelectionForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/Utilities/Generator.cs b/VG Music Studio - WinForms/ObjectListView/Utilities/Generator.cs new file mode 100644 index 0000000..1c424b1 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Utilities/Generator.cs @@ -0,0 +1,563 @@ +/* + * Generator - Utility methods that generate columns or methods + * + * Author: Phillip Piper + * Date: 15/08/2009 22:37 + * + * Change log: + * 2015-06-17 JPP - Columns without [OLVColumn] now auto size + * 2012-08-16 JPP - Generator now considers [OLVChildren] and [OLVIgnore] attributes. + * 2012-06-14 JPP - Allow columns to be generated even if they are not marked with [OLVColumn] + * - Converted class from static to instance to allow it to be subclassed. + * Also, added IGenerator to allow it to be completely reimplemented. + * v2.5.1 + * 2010-11-01 JPP - DisplayIndex is now set correctly for columns that lack that attribute + * v2.4.1 + * 2010-08-25 JPP - Generator now also resets sort columns + * v2.4 + * 2010-04-14 JPP - Allow Name property to be set + * - Don't double set the Text property + * v2.3 + * 2009-08-15 JPP - Initial version + * + * To do: + * + * Copyright (C) 2009-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Reflection; +using System.Reflection.Emit; +using System.Text.RegularExpressions; +using System.Windows.Forms; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// An object that implements the IGenerator interface provides the ability + /// to dynamically create columns + /// for an ObjectListView based on the characteristics of a given collection + /// of model objects. + /// + public interface IGenerator { + /// + /// Generate columns into the given ObjectListView that come from the given + /// model object type. + /// + /// The ObjectListView to modify + /// The model type whose attributes will be considered. + /// Will columns be generated for properties that are not marked with [OLVColumn]. + void GenerateAndReplaceColumns(ObjectListView olv, Type type, bool allProperties); + + /// + /// Generate a list of OLVColumns based on the attributes of the given type + /// If allProperties to true, all public properties will have a matching column generated. + /// If allProperties is false, only properties that have a OLVColumn attribute will have a column generated. + /// + /// + /// Will columns be generated for properties that are not marked with [OLVColumn]. + /// A collection of OLVColumns matching the attributes of Type that have OLVColumnAttributes. + IList GenerateColumns(Type type, bool allProperties); + } + + /// + /// The Generator class provides methods to dynamically create columns + /// for an ObjectListView based on the characteristics of a given collection + /// of model objects. + /// + /// + /// For a given type, a Generator can create columns to match the public properties + /// of that type. The generator can consider all public properties or only those public properties marked with + /// [OLVColumn] attribute. + /// + public class Generator : IGenerator { + #region Static convenience methods + + /// + /// Gets or sets the actual generator used by the static convenience methods. + /// + /// If you subclass the standard generator or implement IGenerator yourself, + /// you should install an instance of your subclass/implementation here. + public static IGenerator Instance { + get { return Generator.instance ?? (Generator.instance = new Generator()); } + set { Generator.instance = value; } + } + private static IGenerator instance; + + /// + /// Replace all columns of the given ObjectListView with columns generated + /// from the first member of the given enumerable. If the enumerable is + /// empty or null, the ObjectListView will be cleared. + /// + /// The ObjectListView to modify + /// The collection whose first element will be used to generate columns. + static public void GenerateColumns(ObjectListView olv, IEnumerable enumerable) { + Generator.GenerateColumns(olv, enumerable, false); + } + + /// + /// Replace all columns of the given ObjectListView with columns generated + /// from the first member of the given enumerable. If the enumerable is + /// empty or null, the ObjectListView will be cleared. + /// + /// The ObjectListView to modify + /// The collection whose first element will be used to generate columns. + /// Will columns be generated for properties that are not marked with [OLVColumn]. + static public void GenerateColumns(ObjectListView olv, IEnumerable enumerable, bool allProperties) { + // Generate columns based on the type of the first model in the collection and then quit + if (enumerable != null) { + foreach (object model in enumerable) { + Generator.Instance.GenerateAndReplaceColumns(olv, model.GetType(), allProperties); + return; + } + } + + // If we reach here, the collection was empty, so we clear the list + Generator.Instance.GenerateAndReplaceColumns(olv, null, allProperties); + } + + /// + /// Generate columns into the given ObjectListView that come from the public properties of the given + /// model object type. + /// + /// The ObjectListView to modify + /// The model type whose attributes will be considered. + static public void GenerateColumns(ObjectListView olv, Type type) { + Generator.Instance.GenerateAndReplaceColumns(olv, type, false); + } + + /// + /// Generate columns into the given ObjectListView that come from the public properties of the given + /// model object type. + /// + /// The ObjectListView to modify + /// The model type whose attributes will be considered. + /// Will columns be generated for properties that are not marked with [OLVColumn]. + static public void GenerateColumns(ObjectListView olv, Type type, bool allProperties) { + Generator.Instance.GenerateAndReplaceColumns(olv, type, allProperties); + } + + /// + /// Generate a list of OLVColumns based on the public properties of the given type + /// that have a OLVColumn attribute. + /// + /// + /// A collection of OLVColumns matching the attributes of Type that have OLVColumnAttributes. + static public IList GenerateColumns(Type type) { + return Generator.Instance.GenerateColumns(type, false); + } + + #endregion + + #region Public interface + + /// + /// Generate columns into the given ObjectListView that come from the given + /// model object type. + /// + /// The ObjectListView to modify + /// The model type whose attributes will be considered. + /// Will columns be generated for properties that are not marked with [OLVColumn]. + public virtual void GenerateAndReplaceColumns(ObjectListView olv, Type type, bool allProperties) { + IList columns = this.GenerateColumns(type, allProperties); + TreeListView tlv = olv as TreeListView; + if (tlv != null) + this.TryGenerateChildrenDelegates(tlv, type); + this.ReplaceColumns(olv, columns); + } + + /// + /// Generate a list of OLVColumns based on the attributes of the given type + /// If allProperties to true, all public properties will have a matching column generated. + /// If allProperties is false, only properties that have a OLVColumn attribute will have a column generated. + /// + /// + /// Will columns be generated for properties that are not marked with [OLVColumn]. + /// A collection of OLVColumns matching the attributes of Type that have OLVColumnAttributes. + public virtual IList GenerateColumns(Type type, bool allProperties) { + List columns = new List(); + + // Sanity + if (type == null) + return columns; + + // Iterate all public properties in the class and build columns from those that have + // an OLVColumn attribute and that are not ignored. + foreach (PropertyInfo pinfo in type.GetProperties()) { + if (Attribute.GetCustomAttribute(pinfo, typeof(OLVIgnoreAttribute)) != null) + continue; + + OLVColumnAttribute attr = Attribute.GetCustomAttribute(pinfo, typeof(OLVColumnAttribute)) as OLVColumnAttribute; + if (attr == null) { + if (allProperties) + columns.Add(this.MakeColumnFromPropertyInfo(pinfo)); + } else { + columns.Add(this.MakeColumnFromAttribute(pinfo, attr)); + } + } + + // How many columns have DisplayIndex specifically set? + int countPositiveDisplayIndex = 0; + foreach (OLVColumn col in columns) { + if (col.DisplayIndex >= 0) + countPositiveDisplayIndex += 1; + } + + // Give columns that don't have a DisplayIndex an incremental index + int columnIndex = countPositiveDisplayIndex; + foreach (OLVColumn col in columns) + if (col.DisplayIndex < 0) + col.DisplayIndex = (columnIndex++); + + columns.Sort(delegate(OLVColumn x, OLVColumn y) { + return x.DisplayIndex.CompareTo(y.DisplayIndex); + }); + + return columns; + } + + #endregion + + #region Implementation + + /// + /// Replace all the columns in the given listview with the given list of columns. + /// + /// + /// + protected virtual void ReplaceColumns(ObjectListView olv, IList columns) { + olv.Reset(); + + // Are there new columns to add? + if (columns == null || columns.Count == 0) + return; + + // Setup the columns + olv.AllColumns.AddRange(columns); + this.PostCreateColumns(olv); + } + + /// + /// Post process columns after creating them and adding them to the AllColumns collection. + /// + /// + public virtual void PostCreateColumns(ObjectListView olv) { + if (olv.AllColumns.Exists(delegate(OLVColumn x) { return x.CheckBoxes; })) + olv.UseSubItemCheckBoxes = true; + if (olv.AllColumns.Exists(delegate(OLVColumn x) { return x.Index > 0 && (x.ImageGetter != null || !String.IsNullOrEmpty(x.ImageAspectName)); })) + olv.ShowImagesOnSubItems = true; + olv.RebuildColumns(); + olv.AutoSizeColumns(); + } + + /// + /// Create a column from the given PropertyInfo and OLVColumn attribute + /// + /// + /// + /// + protected virtual OLVColumn MakeColumnFromAttribute(PropertyInfo pinfo, OLVColumnAttribute attr) { + return MakeColumn(pinfo.Name, DisplayNameToColumnTitle(pinfo.Name), pinfo.CanWrite, pinfo.PropertyType, attr); + } + + /// + /// Make a column from the given PropertyInfo + /// + /// + /// + protected virtual OLVColumn MakeColumnFromPropertyInfo(PropertyInfo pinfo) { + return MakeColumn(pinfo.Name, DisplayNameToColumnTitle(pinfo.Name), pinfo.CanWrite, pinfo.PropertyType, null); + } + + /// + /// Make a column from the given PropertyDescriptor + /// + /// + /// + public virtual OLVColumn MakeColumnFromPropertyDescriptor(PropertyDescriptor pd) { + OLVColumnAttribute attr = pd.Attributes[typeof(OLVColumnAttribute)] as OLVColumnAttribute; + return MakeColumn(pd.Name, DisplayNameToColumnTitle(pd.DisplayName), !pd.IsReadOnly, pd.PropertyType, attr); + } + + /// + /// Create a column with all the given information + /// + /// + /// + /// + /// + /// + /// + protected virtual OLVColumn MakeColumn(string aspectName, string title, bool editable, Type propertyType, OLVColumnAttribute attr) { + + OLVColumn column = this.MakeColumn(aspectName, title, attr); + column.Name = (attr == null || String.IsNullOrEmpty(attr.Name)) ? aspectName : attr.Name; + this.ConfigurePossibleBooleanColumn(column, propertyType); + + if (attr == null) { + column.IsEditable = editable; + column.Width = -1; // Auto size + return column; + } + + column.AspectToStringFormat = attr.AspectToStringFormat; + if (attr.IsCheckBoxesSet) + column.CheckBoxes = attr.CheckBoxes; + column.DisplayIndex = attr.DisplayIndex; + column.FillsFreeSpace = attr.FillsFreeSpace; + if (attr.IsFreeSpaceProportionSet) + column.FreeSpaceProportion = attr.FreeSpaceProportion; + column.GroupWithItemCountFormat = attr.GroupWithItemCountFormat; + column.GroupWithItemCountSingularFormat = attr.GroupWithItemCountSingularFormat; + column.Hyperlink = attr.Hyperlink; + column.ImageAspectName = attr.ImageAspectName; + column.IsEditable = attr.IsEditableSet ? attr.IsEditable : editable; + column.IsTileViewColumn = attr.IsTileViewColumn; + column.IsVisible = attr.IsVisible; + column.MaximumWidth = attr.MaximumWidth; + column.MinimumWidth = attr.MinimumWidth; + column.Tag = attr.Tag; + if (attr.IsTextAlignSet) + column.TextAlign = attr.TextAlign; + column.ToolTipText = attr.ToolTipText; + if (attr.IsTriStateCheckBoxesSet) + column.TriStateCheckBoxes = attr.TriStateCheckBoxes; + column.UseInitialLetterForGroup = attr.UseInitialLetterForGroup; + column.Width = attr.Width; + if (attr.GroupCutoffs != null && attr.GroupDescriptions != null) + column.MakeGroupies(attr.GroupCutoffs, attr.GroupDescriptions); + return column; + } + + /// + /// Create a column. + /// + /// + /// + /// + /// + protected virtual OLVColumn MakeColumn(string aspectName, string title, OLVColumnAttribute attr) { + string columnTitle = (attr == null || String.IsNullOrEmpty(attr.Title)) ? title : attr.Title; + return new OLVColumn(columnTitle, aspectName); + } + + /// + /// Convert a property name to a displayable title. + /// + /// + /// + protected virtual string DisplayNameToColumnTitle(string displayName) { + string title = displayName.Replace("_", " "); + // Put a space between a lower-case letter that is followed immediately by an upper case letter + title = Regex.Replace(title, @"(\p{Ll})(\p{Lu})", @"$1 $2"); + return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(title); + } + + /// + /// Configure the given column to show a checkbox if appropriate + /// + /// + /// + protected virtual void ConfigurePossibleBooleanColumn(OLVColumn column, Type propertyType) { + if (propertyType != typeof(bool) && propertyType != typeof(bool?) && propertyType != typeof(CheckState)) + return; + + column.CheckBoxes = true; + column.TextAlign = HorizontalAlignment.Center; + column.Width = 32; + column.TriStateCheckBoxes = (propertyType == typeof(bool?) || propertyType == typeof(CheckState)); + } + + /// + /// If this given type has an property marked with [OLVChildren], make delegates that will + /// traverse that property as the children of an instance of the model + /// + /// + /// + protected virtual void TryGenerateChildrenDelegates(TreeListView tlv, Type type) { + foreach (PropertyInfo pinfo in type.GetProperties()) { + OLVChildrenAttribute attr = Attribute.GetCustomAttribute(pinfo, typeof(OLVChildrenAttribute)) as OLVChildrenAttribute; + if (attr != null) { + this.GenerateChildrenDelegates(tlv, pinfo); + return; + } + } + } + + /// + /// Generate CanExpand and ChildrenGetter delegates from the given property. + /// + /// + /// + protected virtual void GenerateChildrenDelegates(TreeListView tlv, PropertyInfo pinfo) { + Munger childrenGetter = new Munger(pinfo.Name); + tlv.CanExpandGetter = delegate(object x) { + try { + IEnumerable result = childrenGetter.GetValueEx(x) as IEnumerable; + return !ObjectListView.IsEnumerableEmpty(result); + } + catch (MungerException ex) { + System.Diagnostics.Debug.WriteLine(ex); + return false; + } + }; + tlv.ChildrenGetter = delegate(object x) { + try { + return childrenGetter.GetValueEx(x) as IEnumerable; + } + catch (MungerException ex) { + System.Diagnostics.Debug.WriteLine(ex); + return null; + } + }; + } + #endregion + + /* + #region Dynamic methods + + /// + /// Generate methods so that reflection is not needed. + /// + /// + /// + public static void GenerateMethods(ObjectListView olv, Type type) { + foreach (OLVColumn column in olv.Columns) { + GenerateColumnMethods(column, type); + } + } + + public static void GenerateColumnMethods(OLVColumn column, Type type) { + if (column.AspectGetter == null && !String.IsNullOrEmpty(column.AspectName)) + column.AspectGetter = Generator.GenerateAspectGetter(type, column.AspectName); + } + + /// + /// Generates an aspect getter method dynamically. The method will execute + /// the given dotted chain of selectors against a model object given at runtime. + /// + /// The type of model object to be passed to the generated method + /// A dotted chain of selectors. Each selector can be the name of a + /// field, property or parameter-less method. + /// A typed delegate + /// + /// + /// If you have an AspectName of "Owner.Address.Postcode", this will generate + /// the equivalent of: this.AspectGetter = delegate (object x) { + /// return x.Owner.Address.Postcode; + /// } + /// + /// + /// + private static AspectGetterDelegate GenerateAspectGetter(Type type, string path) { + DynamicMethod getter = new DynamicMethod(String.Empty, typeof(Object), new Type[] { type }, type, true); + Generator.GenerateIL(type, path, getter.GetILGenerator()); + return (AspectGetterDelegate)getter.CreateDelegate(typeof(AspectGetterDelegate)); + } + + /// + /// This method generates the actual IL for the method. + /// + /// + /// + /// + private static void GenerateIL(Type modelType, string path, ILGenerator il) { + // Push our model object onto the stack + il.Emit(OpCodes.Ldarg_0); + OpCodes.Castclass + // Generate the IL to access each part of the dotted chain + Type type = modelType; + string[] parts = path.Split('.'); + for (int i = 0; i < parts.Length; i++) { + type = Generator.GeneratePart(il, type, parts[i], (i == parts.Length - 1)); + if (type == null) + break; + } + + // If the object to be returned is a value type (e.g. int, bool), it + // must be boxed, since the delegate returns an Object + if (type != null && type.IsValueType && !modelType.IsValueType) + il.Emit(OpCodes.Box, type); + + il.Emit(OpCodes.Ret); + } + + private static Type GeneratePart(ILGenerator il, Type type, string pathPart, bool isLastPart) { + // TODO: Generate check for null + + // Find the first member with the given name that is a field, property, or parameter-less method + List infos = new List(type.GetMember(pathPart)); + MemberInfo info = infos.Find(delegate(MemberInfo x) { + if (x.MemberType == MemberTypes.Field || x.MemberType == MemberTypes.Property) + return true; + if (x.MemberType == MemberTypes.Method) + return ((MethodInfo)x).GetParameters().Length == 0; + else + return false; + }); + + // If we couldn't find anything with that name, pop the current result and return an error + if (info == null) { + il.Emit(OpCodes.Pop); + il.Emit(OpCodes.Ldstr, String.Format("'{0}' is not a parameter-less method, property or field of type '{1}'", pathPart, type.FullName)); + return null; + } + + // Generate the correct IL to access the member. We remember the type of object that is going to be returned + // so that we can do a method lookup on it at the next iteration + Type resultType = null; + switch (info.MemberType) { + case MemberTypes.Method: + MethodInfo mi = (MethodInfo)info; + if (mi.IsVirtual) + il.Emit(OpCodes.Callvirt, mi); + else + il.Emit(OpCodes.Call, mi); + resultType = mi.ReturnType; + break; + case MemberTypes.Property: + PropertyInfo pi = (PropertyInfo)info; + il.Emit(OpCodes.Call, pi.GetGetMethod()); + resultType = pi.PropertyType; + break; + case MemberTypes.Field: + FieldInfo fi = (FieldInfo)info; + il.Emit(OpCodes.Ldfld, fi); + resultType = fi.FieldType; + break; + } + + // If the method returned a value type, and something is going to call a method on that value, + // we need to load its address onto the stack, rather than the object itself. + if (resultType.IsValueType && !isLastPart) { + LocalBuilder lb = il.DeclareLocal(resultType); + il.Emit(OpCodes.Stloc, lb); + il.Emit(OpCodes.Ldloca, lb); + } + + return resultType; + } + + #endregion + */ + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/Utilities/OLVExporter.cs b/VG Music Studio - WinForms/ObjectListView/Utilities/OLVExporter.cs new file mode 100644 index 0000000..18b7314 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Utilities/OLVExporter.cs @@ -0,0 +1,277 @@ +/* + * OLVExporter - Export the contents of an ObjectListView into various text-based formats + * + * Author: Phillip Piper + * Date: 7 August 2012, 10:35pm + * + * Change log: + * 2012-08-07 JPP Initial code + * + * Copyright (C) 2012 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView { + /// + /// An OLVExporter converts a collection of rows from an ObjectListView + /// into a variety of textual formats. + /// + public class OLVExporter { + + /// + /// What format will be used for exporting + /// + public enum ExportFormat { + + /// + /// Tab separated values, according to http://www.iana.org/assignments/media-types/text/tab-separated-values + /// + TabSeparated = 1, + + /// + /// Alias for TabSeparated + /// + TSV = 1, + + /// + /// Comma separated values, according to http://www.ietf.org/rfc/rfc4180.txt + /// + CSV, + + /// + /// HTML table, according to me + /// + HTML + } + + #region Life and death + + /// + /// Create an empty exporter + /// + public OLVExporter() {} + + /// + /// Create an exporter that will export all the rows of the given ObjectListView + /// + /// + public OLVExporter(ObjectListView olv) : this(olv, olv.Objects) {} + + /// + /// Create an exporter that will export all the given rows from the given ObjectListView + /// + /// + /// + public OLVExporter(ObjectListView olv, IEnumerable objectsToExport) { + if (olv == null) throw new ArgumentNullException("olv"); + if (objectsToExport == null) throw new ArgumentNullException("objectsToExport"); + + this.ListView = olv; + this.ModelObjects = ObjectListView.EnumerableToArray(objectsToExport, true); + } + + #endregion + + #region Properties + + /// + /// Gets or sets whether hidden columns will also be included in the textual + /// representation. If this is false (the default), only visible columns will + /// be included. + /// + public bool IncludeHiddenColumns { + get { return includeHiddenColumns; } + set { includeHiddenColumns = value; } + } + private bool includeHiddenColumns; + + /// + /// Gets or sets whether column headers will also be included in the text + /// and HTML representation. Default is true. + /// + public bool IncludeColumnHeaders { + get { return includeColumnHeaders; } + set { includeColumnHeaders = value; } + } + private bool includeColumnHeaders = true; + + /// + /// Gets the ObjectListView that is being used as the source of the data + /// to be exported + /// + public ObjectListView ListView { + get { return objectListView; } + set { objectListView = value; } + } + private ObjectListView objectListView; + + /// + /// Gets the model objects that are to be placed in the data object + /// + public IList ModelObjects { + get { return modelObjects; } + set { modelObjects = value; } + } + private IList modelObjects = new ArrayList(); + + #endregion + + #region Commands + + /// + /// Export the nominated rows from the nominated ObjectListView. + /// Returns the result in the expected format. + /// + /// + /// + /// This will perform only one conversion, even if called multiple times with different formats. + public string ExportTo(ExportFormat format) { + if (results == null) + this.Convert(); + + return results[format]; + } + + /// + /// Convert + /// + public void Convert() { + + IList columns = this.IncludeHiddenColumns ? this.ListView.AllColumns : this.ListView.ColumnsInDisplayOrder; + + StringBuilder sbText = new StringBuilder(); + StringBuilder sbCsv = new StringBuilder(); + StringBuilder sbHtml = new StringBuilder(""); + + // Include column headers + if (this.IncludeColumnHeaders) { + List strings = new List(); + foreach (OLVColumn col in columns) + strings.Add(col.Text); + + WriteOneRow(sbText, strings, "", "\t", "", null); + WriteOneRow(sbHtml, strings, "", HtmlEncode); + WriteOneRow(sbCsv, strings, "", ",", "", CsvEncode); + } + + foreach (object modelObject in this.ModelObjects) { + List strings = new List(); + foreach (OLVColumn col in columns) + strings.Add(col.GetStringValue(modelObject)); + + WriteOneRow(sbText, strings, "", "\t", "", null); + WriteOneRow(sbHtml, strings, "", HtmlEncode); + WriteOneRow(sbCsv, strings, "", ",", "", CsvEncode); + } + sbHtml.AppendLine("
", "", "
", "", "
"); + + results = new Dictionary(); + results[ExportFormat.TabSeparated] = sbText.ToString(); + results[ExportFormat.CSV] = sbCsv.ToString(); + results[ExportFormat.HTML] = sbHtml.ToString(); + } + + private delegate string StringToString(string str); + + private void WriteOneRow(StringBuilder sb, IEnumerable strings, string startRow, string betweenCells, string endRow, StringToString encoder) { + sb.Append(startRow); + bool first = true; + foreach (string s in strings) { + if (!first) + sb.Append(betweenCells); + sb.Append(encoder == null ? s : encoder(s)); + first = false; + } + sb.AppendLine(endRow); + } + + private Dictionary results; + + #endregion + + #region Encoding + + /// + /// Encode a string such that it can be used as a value in a CSV file. + /// This basically means replacing any quote mark with two quote marks, + /// and enclosing the whole string in quotes. + /// + /// + /// + private static string CsvEncode(string text) { + if (text == null) + return null; + + const string DOUBLEQUOTE = @""""; // one double quote + const string TWODOUBEQUOTES = @""""""; // two double quotes + + StringBuilder sb = new StringBuilder(DOUBLEQUOTE); + sb.Append(text.Replace(DOUBLEQUOTE, TWODOUBEQUOTES)); + sb.Append(DOUBLEQUOTE); + + return sb.ToString(); + } + + /// + /// HTML-encodes a string and returns the encoded string. + /// + /// The text string to encode. + /// The HTML-encoded text. + /// Taken from http://www.west-wind.com/weblog/posts/2009/Feb/05/Html-and-Uri-String-Encoding-without-SystemWeb + private static string HtmlEncode(string text) { + if (text == null) + return null; + + StringBuilder sb = new StringBuilder(text.Length); + + int len = text.Length; + for (int i = 0; i < len; i++) { + switch (text[i]) { + case '<': + sb.Append("<"); + break; + case '>': + sb.Append(">"); + break; + case '"': + sb.Append("""); + break; + case '&': + sb.Append("&"); + break; + default: + if (text[i] > 159) { + // decimal numeric entity + sb.Append("&#"); + sb.Append(((int)text[i]).ToString(CultureInfo.InvariantCulture)); + sb.Append(";"); + } else + sb.Append(text[i]); + break; + } + } + return sb.ToString(); + } + #endregion + } +} \ No newline at end of file diff --git a/VG Music Studio - WinForms/ObjectListView/Utilities/TypedObjectListView.cs b/VG Music Studio - WinForms/ObjectListView/Utilities/TypedObjectListView.cs new file mode 100644 index 0000000..20d822e --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/Utilities/TypedObjectListView.cs @@ -0,0 +1,561 @@ +/* + * TypedObjectListView - A wrapper around an ObjectListView that provides type-safe delegates. + * + * Author: Phillip Piper + * Date: 27/09/2008 9:15 AM + * + * Change log: + * v2.6 + * 2012-10-26 JPP - Handle rare case where a null model object was passed into aspect getters. + * v2.3 + * 2009-03-31 JPP - Added Objects property + * 2008-11-26 JPP - Added tool tip getting methods + * 2008-11-05 JPP - Added CheckState handling methods + * 2008-10-24 JPP - Generate dynamic methods MkII. This one handles value types + * 2008-10-21 JPP - Generate dynamic methods + * 2008-09-27 JPP - Separated from ObjectListView.cs + * + * Copyright (C) 2006-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using System.Reflection; +using System.Reflection.Emit; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// A TypedObjectListView is a type-safe wrapper around an ObjectListView. + /// + /// + /// VCS does not support generics on controls. It can be faked to some degree, but it + /// cannot be completely overcome. In our case in particular, there is no way to create + /// the custom OLVColumn's that we need to truly be generic. So this wrapper is an + /// experiment in providing some type-safe access in a way that is useful and available today. + /// A TypedObjectListView is not more efficient than a normal ObjectListView. + /// Underneath, the same name of casts are performed. But it is easier to use since you + /// do not have to write the casts yourself. + /// + /// + /// The class of model object that the list will manage + /// + /// To use a TypedObjectListView, you write code like this: + /// + /// TypedObjectListView<Person> tlist = new TypedObjectListView<Person>(this.listView1); + /// tlist.CheckStateGetter = delegate(Person x) { return x.IsActive; }; + /// tlist.GetColumn(0).AspectGetter = delegate(Person x) { return x.Name; }; + /// ... + /// + /// To iterate over the selected objects, you can write something elegant like this: + /// + /// foreach (Person x in tlist.SelectedObjects) { + /// x.GrantSalaryIncrease(); + /// } + /// + /// + public class TypedObjectListView where T : class + { + /// + /// Create a typed wrapper around the given list. + /// + /// The listview to be wrapped + public TypedObjectListView(ObjectListView olv) { + this.olv = olv; + } + + //-------------------------------------------------------------------------------------- + // Properties + + /// + /// Return the model object that is checked, if only one row is checked. + /// If zero rows are checked, or more than one row, null is returned. + /// + public virtual T CheckedObject { + get { return (T)this.olv.CheckedObject; } + } + + /// + /// Return the list of all the checked model objects + /// + public virtual IList CheckedObjects { + get { + IList checkedObjects = this.olv.CheckedObjects; + List objects = new List(checkedObjects.Count); + foreach (object x in checkedObjects) + objects.Add((T)x); + + return objects; + } + set { this.olv.CheckedObjects = (IList)value; } + } + + /// + /// The ObjectListView that is being wrapped + /// + public virtual ObjectListView ListView { + get { return olv; } + set { olv = value; } + } + private ObjectListView olv; + + /// + /// Get or set the list of all model objects + /// + public virtual IList Objects { + get { + List objects = new List(this.olv.GetItemCount()); + for (int i = 0; i < this.olv.GetItemCount(); i++) + objects.Add(this.GetModelObject(i)); + + return objects; + } + set { this.olv.SetObjects(value); } + } + + /// + /// Return the model object that is selected, if only one row is selected. + /// If zero rows are selected, or more than one row, null is returned. + /// + public virtual T SelectedObject { + get { return (T)this.olv.SelectedObject; } + set { this.olv.SelectedObject = value; } + } + + /// + /// The list of model objects that are selected. + /// + public virtual IList SelectedObjects { + get { + List objects = new List(this.olv.SelectedIndices.Count); + foreach (int index in this.olv.SelectedIndices) + objects.Add((T)this.olv.GetModelObject(index)); + + return objects; + } + set { this.olv.SelectedObjects = (IList)value; } + } + + //-------------------------------------------------------------------------------------- + // Accessors + + /// + /// Return a typed wrapper around the column at the given index + /// + /// The index of the column + /// A typed column or null + public virtual TypedColumn GetColumn(int i) { + return new TypedColumn(this.olv.GetColumn(i)); + } + + /// + /// Return a typed wrapper around the column with the given name + /// + /// The name of the column + /// A typed column or null + public virtual TypedColumn GetColumn(string name) { + return new TypedColumn(this.olv.GetColumn(name)); + } + + /// + /// Return the model object at the given index + /// + /// The index of the model object + /// The model object or null + public virtual T GetModelObject(int index) { + return (T)this.olv.GetModelObject(index); + } + + //-------------------------------------------------------------------------------------- + // Delegates + + /// + /// CheckStateGetter + /// + /// + /// + public delegate CheckState TypedCheckStateGetterDelegate(T rowObject); + + /// + /// Gets or sets the check state getter + /// + public virtual TypedCheckStateGetterDelegate CheckStateGetter { + get { return checkStateGetter; } + set { + this.checkStateGetter = value; + if (value == null) + this.olv.CheckStateGetter = null; + else + this.olv.CheckStateGetter = delegate(object x) { + return this.checkStateGetter((T)x); + }; + } + } + private TypedCheckStateGetterDelegate checkStateGetter; + + /// + /// BooleanCheckStateGetter + /// + /// + /// + public delegate bool TypedBooleanCheckStateGetterDelegate(T rowObject); + + /// + /// Gets or sets the boolean check state getter + /// + public virtual TypedBooleanCheckStateGetterDelegate BooleanCheckStateGetter { + set { + if (value == null) + this.olv.BooleanCheckStateGetter = null; + else + this.olv.BooleanCheckStateGetter = delegate(object x) { + return value((T)x); + }; + } + } + + /// + /// CheckStatePutter + /// + /// + /// + /// + public delegate CheckState TypedCheckStatePutterDelegate(T rowObject, CheckState newValue); + + /// + /// Gets or sets the check state putter delegate + /// + public virtual TypedCheckStatePutterDelegate CheckStatePutter { + get { return checkStatePutter; } + set { + this.checkStatePutter = value; + if (value == null) + this.olv.CheckStatePutter = null; + else + this.olv.CheckStatePutter = delegate(object x, CheckState newValue) { + return this.checkStatePutter((T)x, newValue); + }; + } + } + private TypedCheckStatePutterDelegate checkStatePutter; + + /// + /// BooleanCheckStatePutter + /// + /// + /// + /// + public delegate bool TypedBooleanCheckStatePutterDelegate(T rowObject, bool newValue); + + /// + /// Gets or sets the boolean check state putter + /// + public virtual TypedBooleanCheckStatePutterDelegate BooleanCheckStatePutter { + set { + if (value == null) + this.olv.BooleanCheckStatePutter = null; + else + this.olv.BooleanCheckStatePutter = delegate(object x, bool newValue) { + return value((T)x, newValue); + }; + } + } + + /// + /// ToolTipGetter + /// + /// + /// + /// + public delegate String TypedCellToolTipGetterDelegate(OLVColumn column, T modelObject); + + /// + /// Gets or sets the cell tooltip getter + /// + public virtual TypedCellToolTipGetterDelegate CellToolTipGetter { + set { + if (value == null) + this.olv.CellToolTipGetter = null; + else + this.olv.CellToolTipGetter = delegate(OLVColumn col, Object x) { + return value(col, (T)x); + }; + } + } + + /// + /// Gets or sets the header tool tip getter + /// + public virtual HeaderToolTipGetterDelegate HeaderToolTipGetter { + get { return this.olv.HeaderToolTipGetter; } + set { this.olv.HeaderToolTipGetter = value; } + } + + //-------------------------------------------------------------------------------------- + // Commands + + /// + /// This method will generate AspectGetters for any column that has an AspectName. + /// + public virtual void GenerateAspectGetters() { + for (int i = 0; i < this.ListView.Columns.Count; i++) + this.GetColumn(i).GenerateAspectGetter(); + } + } + + /// + /// A type-safe wrapper around an OLVColumn + /// + /// + public class TypedColumn where T : class + { + /// + /// Creates a TypedColumn + /// + /// + public TypedColumn(OLVColumn column) { + this.column = column; + } + private OLVColumn column; + + /// + /// + /// + /// + /// + public delegate Object TypedAspectGetterDelegate(T rowObject); + + /// + /// + /// + /// + /// + public delegate void TypedAspectPutterDelegate(T rowObject, Object newValue); + + /// + /// + /// + /// + /// + public delegate Object TypedGroupKeyGetterDelegate(T rowObject); + + /// + /// + /// + /// + /// + public delegate Object TypedImageGetterDelegate(T rowObject); + + /// + /// + /// + public TypedAspectGetterDelegate AspectGetter { + get { return this.aspectGetter; } + set { + this.aspectGetter = value; + if (value == null) + this.column.AspectGetter = null; + else + this.column.AspectGetter = delegate(object x) { + return x == null ? null : this.aspectGetter((T)x); + }; + } + } + private TypedAspectGetterDelegate aspectGetter; + + /// + /// + /// + public TypedAspectPutterDelegate AspectPutter { + get { return aspectPutter; } + set { + this.aspectPutter = value; + if (value == null) + this.column.AspectPutter = null; + else + this.column.AspectPutter = delegate(object x, object newValue) { + this.aspectPutter((T)x, newValue); + }; + } + } + private TypedAspectPutterDelegate aspectPutter; + + /// + /// + /// + public TypedImageGetterDelegate ImageGetter { + get { return imageGetter; } + set { + this.imageGetter = value; + if (value == null) + this.column.ImageGetter = null; + else + this.column.ImageGetter = delegate(object x) { + return this.imageGetter((T)x); + }; + } + } + private TypedImageGetterDelegate imageGetter; + + /// + /// + /// + public TypedGroupKeyGetterDelegate GroupKeyGetter { + get { return groupKeyGetter; } + set { + this.groupKeyGetter = value; + if (value == null) + this.column.GroupKeyGetter = null; + else + this.column.GroupKeyGetter = delegate(object x) { + return this.groupKeyGetter((T)x); + }; + } + } + private TypedGroupKeyGetterDelegate groupKeyGetter; + + #region Dynamic methods + + /// + /// Generate an aspect getter that does the same thing as the AspectName, + /// except without using reflection. + /// + /// + /// + /// If you have an AspectName of "Owner.Address.Postcode", this will generate + /// the equivalent of: this.AspectGetter = delegate (object x) { + /// return x.Owner.Address.Postcode; + /// } + /// + /// + /// + /// If AspectName is empty, this method will do nothing, otherwise + /// this will replace any existing AspectGetter. + /// + /// + public void GenerateAspectGetter() { + if (!String.IsNullOrEmpty(this.column.AspectName)) + this.AspectGetter = this.GenerateAspectGetter(typeof(T), this.column.AspectName); + } + + /// + /// Generates an aspect getter method dynamically. The method will execute + /// the given dotted chain of selectors against a model object given at runtime. + /// + /// The type of model object to be passed to the generated method + /// A dotted chain of selectors. Each selector can be the name of a + /// field, property or parameter-less method. + /// A typed delegate + private TypedAspectGetterDelegate GenerateAspectGetter(Type type, string path) { + DynamicMethod getter = new DynamicMethod(String.Empty, + typeof(Object), new Type[] { type }, type, true); + this.GenerateIL(type, path, getter.GetILGenerator()); + return (TypedAspectGetterDelegate)getter.CreateDelegate(typeof(TypedAspectGetterDelegate)); + } + + /// + /// This method generates the actual IL for the method. + /// + /// + /// + /// + private void GenerateIL(Type type, string path, ILGenerator il) { + // Push our model object onto the stack + il.Emit(OpCodes.Ldarg_0); + + // Generate the IL to access each part of the dotted chain + string[] parts = path.Split('.'); + for (int i = 0; i < parts.Length; i++) { + type = this.GeneratePart(il, type, parts[i], (i == parts.Length - 1)); + if (type == null) + break; + } + + // If the object to be returned is a value type (e.g. int, bool), it + // must be boxed, since the delegate returns an Object + if (type != null && type.IsValueType && !typeof(T).IsValueType) + il.Emit(OpCodes.Box, type); + + il.Emit(OpCodes.Ret); + } + + private Type GeneratePart(ILGenerator il, Type type, string pathPart, bool isLastPart) { + // TODO: Generate check for null + + // Find the first member with the given name that is a field, property, or parameter-less method + List infos = new List(type.GetMember(pathPart)); + MemberInfo info = infos.Find(delegate(MemberInfo x) { + if (x.MemberType == MemberTypes.Field || x.MemberType == MemberTypes.Property) + return true; + if (x.MemberType == MemberTypes.Method) + return ((MethodInfo)x).GetParameters().Length == 0; + else + return false; + }); + + // If we couldn't find anything with that name, pop the current result and return an error + if (info == null) { + il.Emit(OpCodes.Pop); + if (Munger.IgnoreMissingAspects) + il.Emit(OpCodes.Ldnull); + else + il.Emit(OpCodes.Ldstr, String.Format("'{0}' is not a parameter-less method, property or field of type '{1}'", pathPart, type.FullName)); + return null; + } + + // Generate the correct IL to access the member. We remember the type of object that is going to be returned + // so that we can do a method lookup on it at the next iteration + Type resultType = null; + switch (info.MemberType) { + case MemberTypes.Method: + MethodInfo mi = (MethodInfo)info; + if (mi.IsVirtual) + il.Emit(OpCodes.Callvirt, mi); + else + il.Emit(OpCodes.Call, mi); + resultType = mi.ReturnType; + break; + case MemberTypes.Property: + PropertyInfo pi = (PropertyInfo)info; + il.Emit(OpCodes.Call, pi.GetGetMethod()); + resultType = pi.PropertyType; + break; + case MemberTypes.Field: + FieldInfo fi = (FieldInfo)info; + il.Emit(OpCodes.Ldfld, fi); + resultType = fi.FieldType; + break; + } + + // If the method returned a value type, and something is going to call a method on that value, + // we need to load its address onto the stack, rather than the object itself. + if (resultType.IsValueType && !isLastPart) { + LocalBuilder lb = il.DeclareLocal(resultType); + il.Emit(OpCodes.Stloc, lb); + il.Emit(OpCodes.Ldloca, lb); + } + + return resultType; + } + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/VirtualObjectListView.cs b/VG Music Studio - WinForms/ObjectListView/VirtualObjectListView.cs new file mode 100644 index 0000000..d1324a6 --- /dev/null +++ b/VG Music Studio - WinForms/ObjectListView/VirtualObjectListView.cs @@ -0,0 +1,1255 @@ +/* + * VirtualObjectListView - A virtual listview delays fetching model objects until they are actually displayed. + * + * Author: Phillip Piper + * Date: 27/09/2008 9:15 AM + * + * Change log: + * 2015-06-14 JPP - Moved handling of CheckBoxes on virtual lists into base class (ObjectListView). + * This allows the property to be set correctly, even when set via an upcast reference. + * 2015-03-25 JPP - Subscribe to change notifications when objects are added + * v2.8 + * 2014-09-26 JPP - Correct an incorrect use of checkStateMap when setting CheckedObjects + * and a CheckStateGetter is installed + * v2.6 + * 2012-06-13 JPP - Corrected several bugs related to groups on virtual lists. + * - Added EnsureNthGroupVisible() since EnsureGroupVisible() can't work on virtual lists. + * v2.5.1 + * 2012-05-04 JPP - Avoid bug/feature in ListView.VirtalListSize setter that causes flickering + * when the size of the list changes. + * 2012-04-24 JPP - Fixed bug that occurred when adding/removing item while the view was grouped. + * v2.5 + * 2011-05-31 JPP - Setting CheckedObjects is more efficient on large collections + * 2011-04-05 JPP - CheckedObjects now only returns objects that are currently in the list. + * ClearObjects() now resets all check state info. + * 2011-03-31 JPP - Filtering on grouped virtual lists no longer behaves strangely. + * 2011-03-17 JPP - Virtual lists can (finally) set CheckBoxes back to false if it has been set to true. + * (this is a little hacky and may not work reliably). + * - GetNextItem() and GetPreviousItem() now work on grouped virtual lists. + * 2011-03-08 JPP - BREAKING CHANGE: 'DataSource' was renamed to 'VirtualListDataSource'. This was necessary + * to allow FastDataListView which is both a DataListView AND a VirtualListView -- + * which both used a 'DataSource' property :( + * v2.4 + * 2010-04-01 JPP - Support filtering + * v2.3 + * 2009-08-28 JPP - BIG CHANGE. Virtual lists can now have groups! + * - Objects property now uses "yield return" -- much more efficient for big lists + * 2009-08-07 JPP - Use new scheme for formatting rows/cells + * v2.2.1 + * 2009-07-24 JPP - Added specialised version of RefreshSelectedObjects() which works efficiently with virtual lists + * (thanks to chriss85 for finding this bug) + * 2009-07-03 JPP - Standardized code format + * v2.2 + * 2009-04-06 JPP - ClearObjects() now works again + * v2.1 + * 2009-02-24 JPP - Removed redundant OnMouseDown() since checkbox + * handling is now handled in the base class + * 2009-01-07 JPP - Made all public and protected methods virtual + * 2008-12-07 JPP - Trigger Before/AfterSearching events + * 2008-11-15 JPP - Fixed some caching issues + * 2008-11-05 JPP - Rewrote handling of check boxes + * 2008-10-28 JPP - Handle SetSelectedObjects(null) + * 2008-10-02 JPP - MAJOR CHANGE: Use IVirtualListDataSource + * 2008-09-27 JPP - Separated from ObjectListView.cs + * + * Copyright (C) 2006-2014 Phillip Piper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Reflection; +using System.Windows.Forms; +using System.Runtime.InteropServices; + +namespace Kermalis.VGMusicStudio.WinForms.ObjectListView +{ + /// + /// A virtual object list view operates in virtual mode, that is, it only gets model objects for + /// a row when it is needed. This gives it the ability to handle very large numbers of rows with + /// minimal resources. + /// + /// A listview is not a great user interface for a large number of items. But if you've + /// ever wanted to have a list with 10 million items, go ahead, knock yourself out. + /// Virtual lists can never iterate their contents. That would defeat the whole purpose. + /// Animated GIFs should not be used in virtual lists. Animated GIFs require some state + /// information to be stored for each animation, but virtual lists specifically do not keep any state information. + /// In any case, you really do not want to keep state information for 10 million animations! + /// + /// Although it isn't documented, .NET virtual lists cannot have checkboxes. This class codes around this limitation, + /// but you must use the functions provided by ObjectListView: CheckedObjects, CheckObject(), UncheckObject() and their friends. + /// If you use the normal check box properties (CheckedItems or CheckedIndicies), they will throw an exception, since the + /// list is in virtual mode, and .NET "knows" it can't handle checkboxes in virtual mode. + /// + /// Due to the limits of the underlying Windows control, virtual lists do not trigger ItemCheck/ItemChecked events. + /// Use a CheckStatePutter instead. + /// To enable grouping, you must provide an implementation of IVirtualGroups interface, via the GroupingStrategy property. + /// Similarly, to enable filtering on the list, your VirtualListDataSource must also implement the IFilterableDataSource interface. + /// + public class VirtualObjectListView : ObjectListView + { + /// + /// Create a VirtualObjectListView + /// + public VirtualObjectListView() + : base() { + this.VirtualMode = true; // Virtual lists have to be virtual -- no prizes for guessing that :) + + this.CacheVirtualItems += new CacheVirtualItemsEventHandler(this.HandleCacheVirtualItems); + this.RetrieveVirtualItem += new RetrieveVirtualItemEventHandler(this.HandleRetrieveVirtualItem); + this.SearchForVirtualItem += new SearchForVirtualItemEventHandler(this.HandleSearchForVirtualItem); + + // At the moment, we don't need to handle this event. But we'll keep this comment to remind us about it. + this.VirtualItemsSelectionRangeChanged += new ListViewVirtualItemsSelectionRangeChangedEventHandler(this.HandleVirtualItemsSelectionRangeChanged); + + this.VirtualListDataSource = new VirtualListVersion1DataSource(this); + + // Virtual lists have to manage their own check state, since the normal ListView control + // doesn't even allow checkboxes on virtual lists + this.PersistentCheckBoxes = true; + } + + #region Public Properties + + /// + /// Gets whether or not this listview is capable of showing groups + /// + [Browsable(false)] + public override bool CanShowGroups { + get { + // Virtual lists need Vista and a grouping strategy to show groups + return (ObjectListView.IsVistaOrLater && this.GroupingStrategy != null); + } + } + + /// + /// Get or set the collection of model objects that are checked. + /// When setting this property, any row whose model object isn't + /// in the given collection will be unchecked. Setting to null is + /// equivalent to unchecking all. + /// + /// + /// + /// This property returns a simple collection. Changes made to the returned + /// collection do NOT affect the list. This is different to the behaviour of + /// CheckedIndicies collection. + /// + /// + /// When getting CheckedObjects, the performance of this method is O(n) where n is the number of checked objects. + /// When setting CheckedObjects, the performance of this method is O(n) where n is the number of checked objects plus + /// the number of objects to be checked. + /// + /// + /// If the ListView is not currently showing CheckBoxes, this property does nothing. It does + /// not remember any check box settings made. + /// + /// + /// This class optimizes the management of CheckStates so that it will work efficiently even on + /// large lists of item. However, those optimizations are impossible if you install a CheckStateGetter. + /// With a CheckStateGetter installed, the performance of this method is O(n) where n is the size + /// of the list. This could be painfully slow. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override IList CheckedObjects { + get { + // If we aren't should checkboxes, then no objects can be checked + if (!this.CheckBoxes) + return new ArrayList(); + + // If the data source has somehow vanished, we can't do anything + if (this.VirtualListDataSource == null) + return new ArrayList(); + + // If a custom check state getter is install, we can't use our check state management + // We have to use the (slower) base version. + if (this.CheckStateGetter != null) + return base.CheckedObjects; + + // Collect items that are checked AND that still exist in the list. + ArrayList objects = new ArrayList(); + foreach (KeyValuePair kvp in this.CheckStateMap) + { + if (kvp.Value == CheckState.Checked && + (!this.CheckedObjectsMustStillExistInList || + this.VirtualListDataSource.GetObjectIndex(kvp.Key) >= 0)) + objects.Add(kvp.Key); + } + return objects; + } + set { + if (!this.CheckBoxes) + return; + + // If a custom check state getter is install, we can't use our check state management + // We have to use the (slower) base version. + if (this.CheckStateGetter != null) { + base.CheckedObjects = value; + return; + } + + Stopwatch sw = Stopwatch.StartNew(); + + // Set up an efficient way of testing for the presence of a particular model + Hashtable table = new Hashtable(this.GetItemCount()); + if (value != null) { + foreach (object x in value) + table[x] = true; + } + + this.BeginUpdate(); + + // Uncheck anything that is no longer checked + Object[] keys = new Object[this.CheckStateMap.Count]; + this.CheckStateMap.Keys.CopyTo(keys, 0); + foreach (Object key in keys) { + if (!table.Contains(key)) + this.SetObjectCheckedness(key, CheckState.Unchecked); + } + + // Check all the new checked objects + foreach (Object x in table.Keys) + this.SetObjectCheckedness(x, CheckState.Checked); + + this.EndUpdate(); + + // Debug.WriteLine(String.Format("PERF - Setting virtual CheckedObjects on {2} objects took {0}ms / {1} ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks, this.GetItemCount())); + } + } + + /// + /// Gets or sets whether or not an object will be included in the CheckedObjects + /// collection, even if it is not present in the control at the moment + /// + /// + /// This property is an implementation detail and should not be altered. + /// + protected internal bool CheckedObjectsMustStillExistInList { + get { return checkedObjectsMustStillExistInList; } + set { checkedObjectsMustStillExistInList = value; } + } + private bool checkedObjectsMustStillExistInList = true; + + /// + /// Gets the collection of objects that survive any filtering that may be in place. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override IEnumerable FilteredObjects { + get { + for (int i = 0; i < this.GetItemCount(); i++) + yield return this.GetModelObject(i); + } + } + + /// + /// Gets or sets the strategy that will be used to create groups + /// + /// + /// This must be provided for a virtual list to show groups. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IVirtualGroups GroupingStrategy { + get { return this.groupingStrategy; } + set { this.groupingStrategy = value; } + } + private IVirtualGroups groupingStrategy; + + /// + /// Gets whether or not the current list is filtering its contents + /// + /// + /// This is only possible if our underlying data source supports filtering. + /// + public override bool IsFiltering { + get { + return base.IsFiltering && (this.VirtualListDataSource is IFilterableDataSource); + } + } + + /// + /// Get/set the collection of objects that this list will show + /// + /// + /// + /// The contents of the control will be updated immediately after setting this property. + /// + /// Setting this property preserves selection, if possible. Use SetObjects() if + /// you do not want to preserve the selection. Preserving selection is the slowest part of this + /// code -- performance is O(n) where n is the number of selected rows. + /// This method is not thread safe. + /// The property DOES work on virtual lists, but if you try to iterate through a list + /// of 10 million objects, it may take some time :) + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override IEnumerable Objects { + get { + IFilterableDataSource filterable = this.VirtualListDataSource as IFilterableDataSource; + try { + // If we are filtering, we have to temporarily disable filtering so we get + // the whole collection + if (filterable != null && this.UseFiltering) + filterable.ApplyFilters(null, null); + return this.FilteredObjects; + } finally { + if (filterable != null && this.UseFiltering) + filterable.ApplyFilters(this.ModelFilter, this.ListFilter); + } + } + set { base.Objects = value; } + } + + /// + /// This delegate is used to fetch a rowObject, given it's index within the list + /// + /// Only use this property if you are not using a VirtualListDataSource. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual RowGetterDelegate RowGetter { + get { return ((VirtualListVersion1DataSource)this.virtualListDataSource).RowGetter; } + set { ((VirtualListVersion1DataSource)this.virtualListDataSource).RowGetter = value; } + } + + /// + /// Should this list show its items in groups? + /// + [Category("Appearance"), + Description("Should the list view show items in groups?"), + DefaultValue(true)] + override public bool ShowGroups { + get { + // Pre-Vista, virtual lists cannot show groups + return ObjectListView.IsVistaOrLater && this.showGroups; + } + set { + this.showGroups = value; + if (this.Created && !value) + this.DisableVirtualGroups(); + } + } + private bool showGroups; + + + /// + /// Get/set the data source that is behind this virtual list + /// + /// Setting this will cause the list to redraw. + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual IVirtualListDataSource VirtualListDataSource { + get { + return this.virtualListDataSource; + } + set { + this.virtualListDataSource = value; + this.CustomSorter = delegate(OLVColumn column, SortOrder sortOrder) { + this.ClearCachedInfo(); + this.virtualListDataSource.Sort(column, sortOrder); + }; + this.BuildList(false); + } + } + private IVirtualListDataSource virtualListDataSource; + + /// + /// Gets or sets the number of rows in this virtual list. + /// + /// + /// There is an annoying feature/bug in the .NET ListView class. + /// When you change the VirtualListSize property, it always scrolls so + /// that the focused item is the top item. This is annoying since it makes + /// the virtual list seem to flicker as the control scrolls to show the focused + /// item and then scrolls back to where ObjectListView wants it to be. + /// + [Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + protected new virtual int VirtualListSize { + get { return base.VirtualListSize; } + set { + if (value == this.VirtualListSize || value < 0) + return; + + // Get around the 'private' marker on 'virtualListSize' field using reflection + if (virtualListSizeFieldInfo == null) { + virtualListSizeFieldInfo = typeof(ListView).GetField("virtualListSize", BindingFlags.NonPublic | BindingFlags.Instance); + System.Diagnostics.Debug.Assert(virtualListSizeFieldInfo != null); + } + + // Set the base class private field so that it keeps on working + virtualListSizeFieldInfo.SetValue(this, value); + + // Send a raw message to change the virtual list size *without* changing the scroll position + if (this.IsHandleCreated && !this.DesignMode) + NativeMethods.SetItemCount(this, value); + } + } + static private FieldInfo virtualListSizeFieldInfo; + + #endregion + + #region OLV accessing + + /// + /// Return the number of items in the list + /// + /// the number of items in the list + public override int GetItemCount() { + return this.VirtualListSize; + } + + /// + /// Return the model object at the given index + /// + /// Index of the model object to be returned + /// A model object + public override object GetModelObject(int index) { + if (this.VirtualListDataSource != null && index >= 0 && index < this.GetItemCount()) + return this.VirtualListDataSource.GetNthObject(index); + else + return null; + } + + /// + /// Find the given model object within the listview and return its index + /// + /// The model object to be found + /// The index of the object. -1 means the object was not present + public override int IndexOf(Object modelObject) { + if (this.VirtualListDataSource == null || modelObject == null) + return -1; + + return this.VirtualListDataSource.GetObjectIndex(modelObject); + } + + /// + /// Return the OLVListItem that displays the given model object + /// + /// The modelObject whose item is to be found + /// The OLVListItem that displays the model, or null + /// This method has O(n) performance. + public override OLVListItem ModelToItem(object modelObject) { + if (this.VirtualListDataSource == null || modelObject == null) + return null; + + int index = this.VirtualListDataSource.GetObjectIndex(modelObject); + return index >= 0 ? this.GetItem(index) : null; + } + + #endregion + + #region Object manipulation + + /// + /// Add the given collection of model objects to this control. + /// + /// A collection of model objects + /// + /// The added objects will appear in their correct sort position, if sorting + /// is active. Otherwise, they will appear at the end of the list. + /// No check is performed to see if any of the objects are already in the ListView. + /// Null objects are silently ignored. + /// + public override void AddObjects(ICollection modelObjects) { + if (this.VirtualListDataSource == null) + return; + + // Give the world a chance to cancel or change the added objects + ItemsAddingEventArgs args = new ItemsAddingEventArgs(modelObjects); + this.OnItemsAdding(args); + if (args.Canceled) + return; + + try + { + this.BeginUpdate(); + this.VirtualListDataSource.AddObjects(args.ObjectsToAdd); + this.BuildList(); + this.SubscribeNotifications(args.ObjectsToAdd); + } + finally + { + this.EndUpdate(); + } + } + + /// + /// Remove all items from this list + /// + /// This method can safely be called from background threads. + public override void ClearObjects() { + if (this.InvokeRequired) + this.Invoke(new MethodInvoker(this.ClearObjects)); + else { + this.CheckStateMap.Clear(); + this.SetObjects(new ArrayList()); + } + } + + /// + /// Scroll the listview so that the given group is at the top. + /// + /// The index of the group to be revealed + /// + /// If the group is already visible, the list will still be scrolled to move + /// the group to the top, if that is possible. + /// + /// This only works when the list is showing groups (obviously). + /// + public virtual void EnsureNthGroupVisible(int groupIndex) { + if (!this.ShowGroups) + return; + + if (groupIndex <= 0 || groupIndex >= this.OLVGroups.Count) { + // There is no easy way to scroll back to the beginning of the list + int delta = 0 - NativeMethods.GetScrollPosition(this, false); + NativeMethods.Scroll(this, 0, delta); + } else { + // Find the display rectangle of the last item in the previous group + OLVGroup previousGroup = this.OLVGroups[groupIndex - 1]; + int lastItemInGroup = this.GroupingStrategy.GetGroupMember(previousGroup, previousGroup.VirtualItemCount - 1); + Rectangle r = this.GetItemRect(lastItemInGroup); + + // Scroll so that the last item of the previous group is just out of sight, + // which will make the desired group header visible. + int delta = r.Y + r.Height / 2; + NativeMethods.Scroll(this, 0, delta); + } + } + + /// + /// Inserts the given collection of model objects to this control at hte given location + /// + /// The index where the new objects will be inserted + /// A collection of model objects + /// + /// The added objects will appear in their correct sort position, if sorting + /// is active. Otherwise, they will appear at the given position of the list. + /// No check is performed to see if any of the objects are already in the ListView. + /// Null objects are silently ignored. + /// + public override void InsertObjects(int index, ICollection modelObjects) + { + if (this.VirtualListDataSource == null) + return; + + // Give the world a chance to cancel or change the added objects + ItemsAddingEventArgs args = new ItemsAddingEventArgs(index, modelObjects); + this.OnItemsAdding(args); + if (args.Canceled) + return; + + try + { + this.BeginUpdate(); + this.VirtualListDataSource.InsertObjects(index, args.ObjectsToAdd); + this.BuildList(); + this.SubscribeNotifications(args.ObjectsToAdd); + } + finally + { + this.EndUpdate(); + } + } + + /// + /// Update the rows that are showing the given objects + /// + /// This method does not resort the items. + public override void RefreshObjects(IList modelObjects) { + if (this.InvokeRequired) { + this.Invoke((MethodInvoker)delegate { this.RefreshObjects(modelObjects); }); + return; + } + + // Without a data source, we can't do this. + if (this.VirtualListDataSource == null) + return; + + try { + this.BeginUpdate(); + this.ClearCachedInfo(); + foreach (object modelObject in modelObjects) { + int index = this.VirtualListDataSource.GetObjectIndex(modelObject); + if (index >= 0) { + this.VirtualListDataSource.UpdateObject(index, modelObject); + this.RedrawItems(index, index, true); + } + } + } + finally { + this.EndUpdate(); + } + } + + /// + /// Update the rows that are selected + /// + /// This method does not resort or regroup the view. + public override void RefreshSelectedObjects() { + foreach (int index in this.SelectedIndices) + this.RedrawItems(index, index, true); + } + + /// + /// Remove all of the given objects from the control + /// + /// Collection of objects to be removed + /// + /// Nulls and model objects that are not in the ListView are silently ignored. + /// Due to problems in the underlying ListView, if you remove all the objects from + /// the control using this method and the list scroll vertically when you do so, + /// then when you subsequently add more objects to the control, + /// the vertical scroll bar will become confused and the control will draw one or more + /// blank lines at the top of the list. + /// + public override void RemoveObjects(ICollection modelObjects) { + if (this.VirtualListDataSource == null) + return; + + // Give the world a chance to cancel or change the removed objects + ItemsRemovingEventArgs args = new ItemsRemovingEventArgs(modelObjects); + this.OnItemsRemoving(args); + if (args.Canceled) + return; + + try { + this.BeginUpdate(); + this.VirtualListDataSource.RemoveObjects(args.ObjectsToRemove); + this.BuildList(); + this.UnsubscribeNotifications(args.ObjectsToRemove); + } + finally { + this.EndUpdate(); + } + } + + /// + /// Select the row that is displaying the given model object. All other rows are deselected. + /// + /// Model object to select + /// Should the object be focused as well? + public override void SelectObject(object modelObject, bool setFocus) { + // Without a data source, we can't do this. + if (this.VirtualListDataSource == null) + return; + + // Check that the object is in the list (plus not all data sources can locate objects) + int index = this.VirtualListDataSource.GetObjectIndex(modelObject); + if (index < 0 || index >= this.VirtualListSize) + return; + + // If the given model is already selected, don't do anything else (prevents an flicker) + if (this.SelectedIndices.Count == 1 && this.SelectedIndices[0] == index) + return; + + // Finally, select the row + this.SelectedIndices.Clear(); + this.SelectedIndices.Add(index); + if (setFocus && this.SelectedItem != null) + this.SelectedItem.Focused = true; + } + + /// + /// Select the rows that is displaying any of the given model object. All other rows are deselected. + /// + /// A collection of model objects + /// This method has O(n) performance where n is the number of model objects passed. + /// Do not use this to select all the rows in the list -- use SelectAll() for that. + public override void SelectObjects(IList modelObjects) { + // Without a data source, we can't do this. + if (this.VirtualListDataSource == null) + return; + + this.SelectedIndices.Clear(); + + if (modelObjects == null) + return; + + foreach (object modelObject in modelObjects) { + int index = this.VirtualListDataSource.GetObjectIndex(modelObject); + if (index >= 0 && index < this.VirtualListSize) + this.SelectedIndices.Add(index); + } + } + + /// + /// Set the collection of objects that this control will show. + /// + /// + /// Should the state of the list be preserved as far as is possible. + public override void SetObjects(IEnumerable collection, bool preserveState) { + if (this.InvokeRequired) { + this.Invoke((MethodInvoker)delegate { this.SetObjects(collection, preserveState); }); + return; + } + + if (this.VirtualListDataSource == null) + return; + + // Give the world a chance to cancel or change the assigned collection + ItemsChangingEventArgs args = new ItemsChangingEventArgs(null, collection); + this.OnItemsChanging(args); + if (args.Canceled) + return; + + this.BeginUpdate(); + try { + this.VirtualListDataSource.SetObjects(args.NewObjects); + this.BuildList(); + this.UpdateNotificationSubscriptions(args.NewObjects); + } + finally { + this.EndUpdate(); + } + } + + #endregion + + #region Check boxes +// +// /// +// /// Check all rows +// /// +// /// The performance of this method is O(n) where n is the number of rows in the control. +// public override void CheckAll() +// { +// if (!this.CheckBoxes) +// return; +// +// Stopwatch sw = Stopwatch.StartNew(); +// +// this.BeginUpdate(); +// +// foreach (Object x in this.Objects) +// this.SetObjectCheckedness(x, CheckState.Checked); +// +// this.EndUpdate(); +// +// Debug.WriteLine(String.Format("PERF - CheckAll() on {2} objects took {0}ms / {1} ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks, this.GetItemCount())); +// +// } +// +// /// +// /// Uncheck all rows +// /// +// /// The performance of this method is O(n) where n is the number of rows in the control. +// public override void UncheckAll() +// { +// if (!this.CheckBoxes) +// return; +// +// Stopwatch sw = Stopwatch.StartNew(); +// +// this.BeginUpdate(); +// +// foreach (Object x in this.Objects) +// this.SetObjectCheckedness(x, CheckState.Unchecked); +// +// this.EndUpdate(); +// +// Debug.WriteLine(String.Format("PERF - UncheckAll() on {2} objects took {0}ms / {1} ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks, this.GetItemCount())); +// } + + /// + /// Get the checkedness of an object from the model. Returning null means the + /// model does know and the value from the control will be used. + /// + /// + /// + protected override CheckState? GetCheckState(object modelObject) + { + if (this.CheckStateGetter != null) + return base.GetCheckState(modelObject); + + CheckState state; + if (modelObject != null && this.CheckStateMap.TryGetValue(modelObject, out state)) + return state; + return CheckState.Unchecked; + } + + #endregion + + #region Implementation + + /// + /// Rebuild the list with its current contents. + /// + /// + /// Invalidate any cached information when we rebuild the list. + /// + public override void BuildList(bool shouldPreserveSelection) { + this.UpdateVirtualListSize(); + this.ClearCachedInfo(); + if (this.ShowGroups) + this.BuildGroups(); + else + this.Sort(); + this.Invalidate(); + } + + /// + /// Clear any cached info this list may have been using + /// + public override void ClearCachedInfo() { + this.lastRetrieveVirtualItemIndex = -1; + } + + /// + /// Do the work of creating groups for this control + /// + /// + protected override void CreateGroups(IEnumerable groups) { + + // In a virtual list, we cannot touch the Groups property. + // It was obviously not written for virtual list and often throws exceptions. + + NativeMethods.ClearGroups(this); + + this.EnableVirtualGroups(); + + foreach (OLVGroup group in groups) { + System.Diagnostics.Debug.Assert(group.Items.Count == 0, "Groups in virtual lists cannot set Items. Use VirtualItemCount instead."); + System.Diagnostics.Debug.Assert(group.VirtualItemCount > 0, "VirtualItemCount must be greater than 0."); + + group.InsertGroupNewStyle(this); + } + } + + /// + /// Do the plumbing to disable groups on a virtual list + /// + protected void DisableVirtualGroups() { + NativeMethods.ClearGroups(this); + //System.Diagnostics.Debug.WriteLine(err); + + const int LVM_ENABLEGROUPVIEW = 0x1000 + 157; + IntPtr x = NativeMethods.SendMessage(this.Handle, LVM_ENABLEGROUPVIEW, 0, 0); + //System.Diagnostics.Debug.WriteLine(x); + + const int LVM_SETOWNERDATACALLBACK = 0x10BB; + IntPtr x2 = NativeMethods.SendMessage(this.Handle, LVM_SETOWNERDATACALLBACK, 0, 0); + //System.Diagnostics.Debug.WriteLine(x2); + } + + /// + /// Do the plumbing to enable groups on a virtual list + /// + protected void EnableVirtualGroups() { + + // We need to implement the IOwnerDataCallback interface + if (this.ownerDataCallbackImpl == null) + this.ownerDataCallbackImpl = new OwnerDataCallbackImpl(this); + + const int LVM_SETOWNERDATACALLBACK = 0x10BB; + IntPtr ptr = Marshal.GetComInterfaceForObject(ownerDataCallbackImpl, typeof(IOwnerDataCallback)); + IntPtr x = NativeMethods.SendMessage(this.Handle, LVM_SETOWNERDATACALLBACK, ptr, 0); + //System.Diagnostics.Debug.WriteLine(x); + Marshal.Release(ptr); + + const int LVM_ENABLEGROUPVIEW = 0x1000 + 157; + x = NativeMethods.SendMessage(this.Handle, LVM_ENABLEGROUPVIEW, 1, 0); + //System.Diagnostics.Debug.WriteLine(x); + } + private OwnerDataCallbackImpl ownerDataCallbackImpl; + + /// + /// Return the position of the given itemIndex in the list as it currently shown to the user. + /// If the control is not grouped, the display order is the same as the + /// sorted list order. But if the list is grouped, the display order is different. + /// + /// + /// + public override int GetDisplayOrderOfItemIndex(int itemIndex) { + if (!this.ShowGroups) + return itemIndex; + + int groupIndex = this.GroupingStrategy.GetGroup(itemIndex); + int displayIndex = 0; + for (int i = 0; i < groupIndex - 1; i++) + displayIndex += this.OLVGroups[i].VirtualItemCount; + displayIndex += this.GroupingStrategy.GetIndexWithinGroup(this.OLVGroups[groupIndex], itemIndex); + + return displayIndex; + } + + /// + /// Return the last item in the order they are shown to the user. + /// If the control is not grouped, the display order is the same as the + /// sorted list order. But if the list is grouped, the display order is different. + /// + /// + public override OLVListItem GetLastItemInDisplayOrder() { + if (!this.ShowGroups) + return base.GetLastItemInDisplayOrder(); + + if (this.OLVGroups.Count > 0) { + OLVGroup lastGroup = this.OLVGroups[this.OLVGroups.Count - 1]; + if (lastGroup.VirtualItemCount > 0) + return this.GetItem(this.GroupingStrategy.GetGroupMember(lastGroup, lastGroup.VirtualItemCount - 1)); + } + + return null; + } + + /// + /// Return the n'th item (0-based) in the order they are shown to the user. + /// If the control is not grouped, the display order is the same as the + /// sorted list order. But if the list is grouped, the display order is different. + /// + /// + /// + public override OLVListItem GetNthItemInDisplayOrder(int n) { + if (!this.ShowGroups || this.OLVGroups == null || this.OLVGroups.Count == 0) + return this.GetItem(n); + + foreach (OLVGroup group in this.OLVGroups) { + if (n < group.VirtualItemCount) + return this.GetItem(this.GroupingStrategy.GetGroupMember(group, n)); + + n -= group.VirtualItemCount; + } + + return null; + } + + /// + /// Return the ListViewItem that appears immediately after the given item. + /// If the given item is null, the first item in the list will be returned. + /// Return null if the given item is the last item. + /// + /// The item that is before the item that is returned, or null + /// A OLVListItem + public override OLVListItem GetNextItem(OLVListItem itemToFind) { + if (!this.ShowGroups) + return base.GetNextItem(itemToFind); + + // Sanity + if (this.OLVGroups == null || this.OLVGroups.Count == 0) + return null; + + // If the given item is null, return the first member of the first group + if (itemToFind == null) { + return this.GetItem(this.GroupingStrategy.GetGroupMember(this.OLVGroups[0], 0)); + } + + // Find where this item occurs (which group and where in that group) + int groupIndex = this.GroupingStrategy.GetGroup(itemToFind.Index); + int indexWithinGroup = this.GroupingStrategy.GetIndexWithinGroup(this.OLVGroups[groupIndex], itemToFind.Index); + + // If it's not the last member, just return the next member + if (indexWithinGroup < this.OLVGroups[groupIndex].VirtualItemCount - 1) + return this.GetItem(this.GroupingStrategy.GetGroupMember(this.OLVGroups[groupIndex], indexWithinGroup + 1)); + + // The item is the last member of its group. Return the first member of the next group + // (unless there isn't a next group) + if (groupIndex < this.OLVGroups.Count - 1) + return this.GetItem(this.GroupingStrategy.GetGroupMember(this.OLVGroups[groupIndex + 1], 0)); + + return null; + } + + /// + /// Return the ListViewItem that appears immediately before the given item. + /// If the given item is null, the last item in the list will be returned. + /// Return null if the given item is the first item. + /// + /// The item that is before the item that is returned + /// A ListViewItem + public override OLVListItem GetPreviousItem(OLVListItem itemToFind) { + if (!this.ShowGroups) + return base.GetPreviousItem(itemToFind); + + // Sanity + if (this.OLVGroups == null || this.OLVGroups.Count == 0) + return null; + + // If the given items is null, return the last member of the last group + if (itemToFind == null) { + OLVGroup lastGroup = this.OLVGroups[this.OLVGroups.Count - 1]; + return this.GetItem(this.GroupingStrategy.GetGroupMember(lastGroup, lastGroup.VirtualItemCount - 1)); + } + + // Find where this item occurs (which group and where in that group) + int groupIndex = this.GroupingStrategy.GetGroup(itemToFind.Index); + int indexWithinGroup = this.GroupingStrategy.GetIndexWithinGroup(this.OLVGroups[groupIndex], itemToFind.Index); + + // If it's not the first member of the group, just return the previous member + if (indexWithinGroup > 0) + return this.GetItem(this.GroupingStrategy.GetGroupMember(this.OLVGroups[groupIndex], indexWithinGroup - 1)); + + // The item is the first member of its group. Return the last member of the previous group + // (if there is one) + if (groupIndex > 0) { + OLVGroup previousGroup = this.OLVGroups[groupIndex - 1]; + return this.GetItem(this.GroupingStrategy.GetGroupMember(previousGroup, previousGroup.VirtualItemCount - 1)); + } + + return null; + } + + /// + /// Make a list of groups that should be shown according to the given parameters + /// + /// + /// + protected override IList MakeGroups(GroupingParameters parms) { + if (this.GroupingStrategy == null) + return new List(); + else + return this.GroupingStrategy.GetGroups(parms); + } + + /// + /// Create a OLVListItem for given row index + /// + /// The index of the row that is needed + /// An OLVListItem + public virtual OLVListItem MakeListViewItem(int itemIndex) { + OLVListItem olvi = new OLVListItem(this.GetModelObject(itemIndex)); + this.FillInValues(olvi, olvi.RowObject); + + this.PostProcessOneRow(itemIndex, this.GetDisplayOrderOfItemIndex(itemIndex), olvi); + + if (this.HotRowIndex == itemIndex) + this.UpdateHotRow(olvi); + + return olvi; + } + + /// + /// On virtual lists, this cannot work. + /// + protected override void PostProcessRows() { + } + + /// + /// Record the change of checkstate for the given object in the model. + /// This does not update the UI -- only the model + /// + /// + /// + /// The check state that was recorded and that should be used to update + /// the control. + protected override CheckState PutCheckState(object modelObject, CheckState state) { + state = base.PutCheckState(modelObject, state); + this.CheckStateMap[modelObject] = state; + return state; + } + + /// + /// Refresh the given item in the list + /// + /// The item to refresh + public override void RefreshItem(OLVListItem olvi) { + this.ClearCachedInfo(); + this.RedrawItems(olvi.Index, olvi.Index, true); + } + + /// + /// Change the size of the list + /// + /// + protected virtual void SetVirtualListSize(int newSize) { + if (newSize < 0 || this.VirtualListSize == newSize) + return; + + int oldSize = this.VirtualListSize; + + this.ClearCachedInfo(); + + // There is a bug in .NET when a virtual ListView is cleared + // (i.e. VirtuaListSize set to 0) AND it is scrolled vertically: the scroll position + // is wrong when the list is next populated. To avoid this, before + // clearing a virtual list, we make sure the list is scrolled to the top. + // [6 weeks later] Damn this is a pain! There are cases where this can also throw exceptions! + try { + if (newSize == 0 && this.TopItemIndex > 0) + this.TopItemIndex = 0; + } + catch (Exception) { + // Ignore any failures + } + + // In strange cases, this can throw the exceptions too. The best we can do is ignore them :( + try { + this.VirtualListSize = newSize; + } + catch (ArgumentOutOfRangeException) { + // pass + } + catch (NullReferenceException) { + // pass + } + + // Tell the world that the size of the list has changed + this.OnItemsChanged(new ItemsChangedEventArgs(oldSize, this.VirtualListSize)); + } + + /// + /// Take ownership of the 'objects' collection. This separates our collection from the source. + /// + /// + /// + /// This method + /// separates the 'objects' instance variable from its source, so that any AddObject/RemoveObject + /// calls will modify our collection and not the original collection. + /// + /// + /// VirtualObjectListViews always own their collections, so this is a no-op. + /// + /// + protected override void TakeOwnershipOfObjects() { + } + + /// + /// Change the state of the control to reflect changes in filtering + /// + protected override void UpdateFiltering() { + IFilterableDataSource filterable = this.VirtualListDataSource as IFilterableDataSource; + if (filterable == null) + return; + + this.BeginUpdate(); + try { + int originalSize = this.VirtualListSize; + filterable.ApplyFilters(this.ModelFilter, this.ListFilter); + this.BuildList(); + + //// If the filtering actually did something, rebuild the groups if they are being shown + //if (originalSize != this.VirtualListSize && this.ShowGroups) + // this.BuildGroups(); + } + finally { + this.EndUpdate(); + } + } + + /// + /// Change the size of the virtual list so that it matches its data source + /// + public virtual void UpdateVirtualListSize() { + if (this.VirtualListDataSource != null) + this.SetVirtualListSize(this.VirtualListDataSource.GetObjectCount()); + } + + #endregion + + #region Event handlers + + /// + /// Handle the CacheVirtualItems event + /// + /// + /// + protected virtual void HandleCacheVirtualItems(object sender, CacheVirtualItemsEventArgs e) { + if (this.VirtualListDataSource != null) + this.VirtualListDataSource.PrepareCache(e.StartIndex, e.EndIndex); + } + + /// + /// Handle a RetrieveVirtualItem + /// + /// + /// + protected virtual void HandleRetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e) { + // .NET 2.0 seems to generate a lot of these events. Before drawing *each* sub-item, + // this event is triggered 4-8 times for the same index. So we save lots of CPU time + // by caching the last result. + //System.Diagnostics.Debug.WriteLine(String.Format("HandleRetrieveVirtualItem({0})", e.ItemIndex)); + + if (this.lastRetrieveVirtualItemIndex != e.ItemIndex) { + this.lastRetrieveVirtualItemIndex = e.ItemIndex; + this.lastRetrieveVirtualItem = this.MakeListViewItem(e.ItemIndex); + } + e.Item = this.lastRetrieveVirtualItem; + } + + /// + /// Handle the SearchForVirtualList event, which is called when the user types into a virtual list + /// + /// + /// + protected virtual void HandleSearchForVirtualItem(object sender, SearchForVirtualItemEventArgs e) { + // The event has e.IsPrefixSearch, but as far as I can tell, this is always false (maybe that's different under Vista) + // So we ignore IsPrefixSearch and IsTextSearch and always to a case insensitive prefix match. + + // We can't do anything if we don't have a data source + if (this.VirtualListDataSource == null) + return; + + // Where should we start searching? If the last row is focused, the SearchForVirtualItemEvent starts searching + // from the next row, which is actually an invalidate index -- so we make sure we never go past the last object. + int start = Math.Min(e.StartIndex, this.VirtualListDataSource.GetObjectCount() - 1); + + // Give the world a chance to fiddle with or completely avoid the searching process + BeforeSearchingEventArgs args = new BeforeSearchingEventArgs(e.Text, start); + this.OnBeforeSearching(args); + if (args.Canceled) + return; + + // Do the search + int i = this.FindMatchingRow(args.StringToFind, args.StartSearchFrom, e.Direction); + + // Tell the world that a search has occurred + AfterSearchingEventArgs args2 = new AfterSearchingEventArgs(args.StringToFind, i); + this.OnAfterSearching(args2); + + // If we found a match, tell the event + if (i != -1) + e.Index = i; + } + + /// + /// Handle the VirtualItemsSelectionRangeChanged event, which is called "when the selection state of a range of items has changed" + /// + /// + /// + /// This method is not called whenever the selection changes on a virtual list. It only seems to be triggered when + /// the user uses Shift-Ctrl-Click to change the selection + protected virtual void HandleVirtualItemsSelectionRangeChanged(object sender, ListViewVirtualItemsSelectionRangeChangedEventArgs e) { + // System.Diagnostics.Debug.WriteLine(string.Format("HandleVirtualItemsSelectionRangeChanged: {0}->{1}, selected: {2}", e.StartIndex, e.EndIndex, e.IsSelected)); + this.TriggerDeferredSelectionChangedEvent(); + } + + /// + /// Find the first row in the given range of rows that prefix matches the string value of the given column. + /// + /// + /// + /// + /// + /// The index of the matched row, or -1 + protected override int FindMatchInRange(string text, int first, int last, OLVColumn column) { + return this.VirtualListDataSource.SearchText(text, first, last, column); + } + + #endregion + + #region Variable declarations + + private OLVListItem lastRetrieveVirtualItem; + private int lastRetrieveVirtualItemIndex = -1; + + #endregion + } +} diff --git a/VG Music Studio - WinForms/ObjectListView/olv-keyfile.snk b/VG Music Studio - WinForms/ObjectListView/olv-keyfile.snk new file mode 100644 index 0000000000000000000000000000000000000000..2658a0adcf122aaa2a591535b53464d516af3378 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa500986W$cIN!~W%bg8iKqt=v+03$~e~mJs!s zjxU8c(azlkAM^?lBdSg#@Xo86g5b(px(_u*|NRInNPMa(oZ7FOGX3y+S9*R?1vg*78kM6LjIK||X zWAU4blpG9GP}*;3w5@>NY`@4N-#_U$uE!7hH-#IbZu}s16Lpq{mQ;i43=-|@nVZDs zG|+jdK4Cm@V#c*g=H%5QNh=MIIeg0)t4qN7H3-6RuS70^Bde3q0o`&}OfWbuURm=Y zn5*%bskV-f%^ClA~ zN7~lW00)-QecLE7reqRZ{s)OjgHOfCAWI2#-kPm(z2TWw^;P~&q24T=-IOLtj~kL+ zUJ}~BbjP&wIAX literal 0 HcmV?d00001 diff --git a/VG Music Studio - WinForms/TaskbarPlayerButtons.cs b/VG Music Studio - WinForms/TaskbarPlayerButtons.cs index d6185e4..9db4380 100644 --- a/VG Music Studio - WinForms/TaskbarPlayerButtons.cs +++ b/VG Music Studio - WinForms/TaskbarPlayerButtons.cs @@ -2,7 +2,7 @@ using Kermalis.VGMusicStudio.Core.Properties; using Kermalis.VGMusicStudio.Core.Util; using Kermalis.VGMusicStudio.WinForms.Properties; -using Microsoft.WindowsAPICodePack.Taskbar; +using Kermalis.VGMusicStudio.WinForms.API.Taskbar; using System; namespace Kermalis.VGMusicStudio.WinForms; diff --git a/VG Music Studio - WinForms/TrackViewer.cs b/VG Music Studio - WinForms/TrackViewer.cs index 81520cc..b7aebdc 100644 --- a/VG Music Studio - WinForms/TrackViewer.cs +++ b/VG Music Studio - WinForms/TrackViewer.cs @@ -1,4 +1,4 @@ -using BrightIdeasSoftware; +using Kermalis.VGMusicStudio.WinForms.ObjectListView; using Kermalis.VGMusicStudio.Core; using Kermalis.VGMusicStudio.Core.Properties; using Kermalis.VGMusicStudio.Core.Util; @@ -14,7 +14,7 @@ namespace Kermalis.VGMusicStudio.WinForms; [DesignerCategory("")] internal sealed class TrackViewer : ThemedForm { - private readonly ObjectListView _listView; + private readonly ObjectListView.ObjectListView _listView; private readonly ComboBox _tracksBox; public TrackViewer() @@ -22,8 +22,8 @@ public TrackViewer() const int W = (600 / 2) - 12 - 6; const int H = 400 - 12 - 11; - _listView = new ObjectListView - { + _listView = new ObjectListView.ObjectListView + { FullRowSelect = true, HeaderStyle = ColumnHeaderStyle.Nonclickable, HideSelection = false, diff --git a/VG Music Studio - WinForms/VG Music Studio - WinForms.csproj b/VG Music Studio - WinForms/VG Music Studio - WinForms.csproj index 1e811e9..12d69d2 100644 --- a/VG Music Studio - WinForms/VG Music Studio - WinForms.csproj +++ b/VG Music Studio - WinForms/VG Music Studio - WinForms.csproj @@ -20,11 +20,8 @@ False - - - - - + + diff --git a/VG Music Studio.sln b/VG Music Studio.sln index 0f1fbff..bd1ce54 100644 --- a/VG Music Studio.sln +++ b/VG Music Studio.sln @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VG Music Studio - Core", "V EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VG Music Studio - MIDI", "VG Music Studio - MIDI\VG Music Studio - MIDI.csproj", "{6756ED81-71F6-457D-AD23-9C03B6C934E4}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EndianBinaryIO", "..\EndianBinaryIO\Source\EndianBinaryIO.csproj", "{D376BB1C-0DBC-4FED-9C8F-9E837CC913A6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {6756ED81-71F6-457D-AD23-9C03B6C934E4}.Debug|Any CPU.Build.0 = Debug|Any CPU {6756ED81-71F6-457D-AD23-9C03B6C934E4}.Release|Any CPU.ActiveCfg = Release|Any CPU {6756ED81-71F6-457D-AD23-9C03B6C934E4}.Release|Any CPU.Build.0 = Release|Any CPU + {D376BB1C-0DBC-4FED-9C8F-9E837CC913A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D376BB1C-0DBC-4FED-9C8F-9E837CC913A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D376BB1C-0DBC-4FED-9C8F-9E837CC913A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D376BB1C-0DBC-4FED-9C8F-9E837CC913A6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 9a74e1893d640b1cddc074bac55bc0202019530c Mon Sep 17 00:00:00 2001 From: Davin Date: Sun, 12 Nov 2023 00:51:49 +1100 Subject: [PATCH 2/9] Fixed an issue where local SWD wasn't being read, and where it tries to read ProgramInfos when null. So DS DSE should work again now. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 80921d3..ad2eb7d 100644 --- a/.gitignore +++ b/.gitignore @@ -259,4 +259,5 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc +/VG Music Studio - GTK4/share/glib-2.0 From 3db6e144e3de9fad91220c6cffed3ec2a2dcb086 Mon Sep 17 00:00:00 2001 From: Davin Date: Sun, 12 Nov 2023 01:16:11 +1100 Subject: [PATCH 3/9] Amending previous commit --- VG Music Studio - Core/Codec/ADPCMDecoder.cs | 11 ++++++----- VG Music Studio - Core/NDS/DSE/DSEChannel.cs | 10 ++++++++-- VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs | 6 ++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/VG Music Studio - Core/Codec/ADPCMDecoder.cs b/VG Music Studio - Core/Codec/ADPCMDecoder.cs index 3595433..8272fbc 100644 --- a/VG Music Studio - Core/Codec/ADPCMDecoder.cs +++ b/VG Music Studio - Core/Codec/ADPCMDecoder.cs @@ -31,11 +31,12 @@ internal sealed class ADPCMDecoder public ADPCMDecoder(byte[] data) { - LastSample = (short)(data[0] | (data[1] << 8)); - StepIndex = (short)((data[2] | (data[3] << 8)) & 0x7F); - DataOffset = 4; - _data = data; - } + _data = data; + LastSample = (short)(data[0] | (data[1] << 8)); + StepIndex = (short)((data[2] | (data[3] << 8)) & 0x7F); + DataOffset = 4; + OnSecondNibble = false; + } // TODO: Span? public static short[] ADPCMToPCM16(byte[] data) diff --git a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs index b9a2b44..2e1345f 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs @@ -64,8 +64,14 @@ public bool StartPCM(SWD localswd, SWD masterswd, byte voice, int key, uint note if (localswd == null) { SWDType = masterswd.Type; } else { SWDType = localswd.Type; } - SWD.IProgramInfo programInfo; - if (localswd == null) { programInfo = masterswd.Programs!.ProgramInfos![voice]; } + SWD.IProgramInfo? programInfo = null; // Declaring Program Info Interface here, to ensure VGMS compiles + if (localswd == null) + { + // Failsafe to check if SWD.ProgramBank contains an instance, if it doesn't, it will be skipped + // This is especially important for initializing a main SWD before the local SWDs + // accompaning the SMDs with the same names are loaded in. + if (masterswd.Programs != null) { programInfo = masterswd.Programs!.ProgramInfos![voice]; } + } else { programInfo = localswd.Programs!.ProgramInfos![voice]; } if (programInfo is null) diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs index 9023159..f0fb176 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs @@ -26,6 +26,12 @@ public DSELoadedSong(DSEPlayer player, string bgm) SWDFileName = bgm; SMDFileName = bgm; StringComparison comparison = StringComparison.CurrentCultureIgnoreCase; + + // Check if a local SWD is accompaning a SMD + if (new FileInfo(Path.ChangeExtension(bgm, "swd")).Exists) + { + LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); // If it exists, this will be loaded as the local SWD + } //if (SWDFileName.StartsWith("bgm", comparison) == SMDFileName.StartsWith("bgm", comparison)) //{ // LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); From fbc132297af5a8fef3dce467c6128bed9f0b8b2e Mon Sep 17 00:00:00 2001 From: Davin Date: Tue, 14 Nov 2023 02:28:35 +1100 Subject: [PATCH 4/9] More fixes, more failsafes implemented, and improvement to Wii DSE support --- VG Music Studio - Core/Codec/ADPCMDecoder.cs | 12 +- VG Music Studio - Core/NDS/DSE/DSEChannel.cs | 382 ++--- .../NDS/DSE/DSELoadedSong.cs | 28 +- VG Music Studio - Core/NDS/DSE/DSEPlayer.cs | 2 +- VG Music Studio - Core/NDS/DSE/SWD.cs | 1304 +++++++++-------- .../Properties/Strings.Designer.cs | 4 +- .../Properties/Strings.resx | 4 +- 7 files changed, 866 insertions(+), 870 deletions(-) diff --git a/VG Music Studio - Core/Codec/ADPCMDecoder.cs b/VG Music Studio - Core/Codec/ADPCMDecoder.cs index 8272fbc..8600ce5 100644 --- a/VG Music Studio - Core/Codec/ADPCMDecoder.cs +++ b/VG Music Studio - Core/Codec/ADPCMDecoder.cs @@ -31,12 +31,12 @@ internal sealed class ADPCMDecoder public ADPCMDecoder(byte[] data) { - _data = data; - LastSample = (short)(data[0] | (data[1] << 8)); - StepIndex = (short)((data[2] | (data[3] << 8)) & 0x7F); - DataOffset = 4; - OnSecondNibble = false; - } + _data = data; + LastSample = (short)(data[0] | (data[1] << 8)); + StepIndex = (short)((data[2] | (data[3] << 8)) & 0x7F); + DataOffset = 4; + OnSecondNibble = false; + } // TODO: Span? public static short[] ADPCMToPCM16(byte[] data) diff --git a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs index 2e1345f..58e2a2c 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs @@ -17,8 +17,8 @@ internal sealed class DSEChannel public sbyte Panpot; // Not necessary public ushort BaseTimer; public ushort Timer; - public uint NoteLength; - public byte Volume; + public uint NoteLength; + public byte Volume; private int _pos; private short _prevLeft; @@ -50,11 +50,11 @@ internal sealed class DSEChannel //private short[] _loopContext; //private DSPADPCM _outputData; //private DSPADPCM? _dspADPCM; - // PSG - private byte _psgDuty; - private int _psgCounter; + // PSG + private byte _psgDuty; + private int _psgCounter; - public DSEChannel(byte i) + public DSEChannel(byte i) { Index = i; } @@ -64,97 +64,100 @@ public bool StartPCM(SWD localswd, SWD masterswd, byte voice, int key, uint note if (localswd == null) { SWDType = masterswd.Type; } else { SWDType = localswd.Type; } - SWD.IProgramInfo? programInfo = null; // Declaring Program Info Interface here, to ensure VGMS compiles - if (localswd == null) + SWD.IProgramInfo? programInfo = null; // Declaring Program Info Interface here, to ensure VGMS compiles + if (localswd == null) { // Failsafe to check if SWD.ProgramBank contains an instance, if it doesn't, it will be skipped // This is especially important for initializing a main SWD before the local SWDs // accompaning the SMDs with the same names are loaded in. if (masterswd.Programs != null) { programInfo = masterswd.Programs!.ProgramInfos![voice]; } - } - else { programInfo = localswd.Programs!.ProgramInfos![voice]; } + } + else { programInfo = localswd.Programs!.ProgramInfos![voice]; } - if (programInfo is null) - { - return false; - } + if (programInfo is null) + { + return false; + } - for (int i = 0; i < programInfo.SplitEntries.Length; i++) - { - SWD.ISplitEntry split = programInfo.SplitEntries[i]; - if (key < split.LowKey || key > split.HighKey) - { - continue; - } + for (int i = 0; i < programInfo.SplitEntries.Length; i++) + { + SWD.ISplitEntry split = programInfo.SplitEntries[i]; + if (key < split.LowKey || key > split.HighKey) + { + continue; + } - //if (_sample == null) { throw new NullReferenceException("Null Reference Exception:\n\nThere's no data associated with this Sample Block in this SWD. Please check to make sure the samples are being read correctly.\n\nCall Stack:"); } - _sample = masterswd.Samples![split.SampleId]; - Key = (byte)key; - RootKey = split.SampleRootKey; - switch (SWDType) // Configures the base timer based on the specific console's CPU and sample rate - { - case "wds ": throw new NotImplementedException("The base timer for the WDS type is not yet implemented."); // PlayStation - case "swdm": throw new NotImplementedException("The base timer for the SWDM type is not yet implemented."); // PlayStation 2 - case "swdl": BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo!.SampleRate); break; // Nintendo DS - case "swdb": BaseTimer = (ushort)(WiiUtils.PPC_Broadway_Clock + _sample.WavInfo!.SampleRate / 33); break; // Wii - } - if (_sample.WavInfo!.SampleFormat == SampleFormat.ADPCM) - { - _adpcmDecoder = new ADPCMDecoder(_sample.Data!); - } - //if (masterswd.Type == "swdb") - //{ - // _dspADPCM = _sample.DSPADPCM; - //} - //attackVolume = sample.WavInfo.AttackVolume == 0 ? split.AttackVolume : sample.WavInfo.AttackVolume; - //attack = sample.WavInfo.Attack == 0 ? split.Attack : sample.WavInfo.Attack; - //decay = sample.WavInfo.Decay == 0 ? split.Decay : sample.WavInfo.Decay; - //sustain = sample.WavInfo.Sustain == 0 ? split.Sustain : sample.WavInfo.Sustain; - //hold = sample.WavInfo.Hold == 0 ? split.Hold : sample.WavInfo.Hold; - //decay2 = sample.WavInfo.Decay2 == 0 ? split.Decay2 : sample.WavInfo.Decay2; - //release = sample.WavInfo.Release == 0 ? split.Release : sample.WavInfo.Release; - //attackVolume = split.AttackVolume == 0 ? sample.WavInfo.AttackVolume : split.AttackVolume; - //attack = split.Attack == 0 ? sample.WavInfo.Attack : split.Attack; - //decay = split.Decay == 0 ? sample.WavInfo.Decay : split.Decay; - //sustain = split.Sustain == 0 ? sample.WavInfo.Sustain : split.Sustain; - //hold = split.Hold == 0 ? sample.WavInfo.Hold : split.Hold; - //decay2 = split.Decay2 == 0 ? sample.WavInfo.Decay2 : split.Decay2; - //release = split.Release == 0 ? sample.WavInfo.Release : split.Release; - _attackVolume = split.AttackVolume == 0 ? _sample.WavInfo.AttackVolume == 0 ? (byte)0x7F : _sample.WavInfo.AttackVolume : split.AttackVolume; - _attack = split.Attack == 0 ? _sample.WavInfo.Attack == 0 ? (byte)0x7F : _sample.WavInfo.Attack : split.Attack; - _decay = split.Decay1 == 0 ? _sample.WavInfo.Decay1 == 0 ? (byte)0x7F : _sample.WavInfo.Decay1 : split.Decay1; - _sustain = split.Sustain == 0 ? _sample.WavInfo.Sustain == 0 ? (byte)0x7F : _sample.WavInfo.Sustain : split.Sustain; - _hold = split.Hold == 0 ? _sample.WavInfo.Hold == 0 ? (byte)0x7F : _sample.WavInfo.Hold : split.Hold; - _decay2 = split.Decay2 == 0 ? _sample.WavInfo.Decay2 == 0 ? (byte)0x7F : _sample.WavInfo.Decay2 : split.Decay2; - _release = split.Release == 0 ? _sample.WavInfo.Release == 0 ? (byte)0x7F : _sample.WavInfo.Release : split.Release; - DetermineEnvelopeStartingPoint(); - _pos = 0; - _prevLeft = _prevRight = 0; - NoteLength = noteLength; - return true; - } - return false; + //if (_sample == null) { throw new NullReferenceException("Null Reference Exception:\n\nThere's no data associated with this Sample Block in this SWD. Please check to make sure the samples are being read correctly.\n\nCall Stack:"); } + _sample = masterswd.Samples![split.SampleId]; + Key = (byte)key; + RootKey = split.SampleRootKey; + if (_sample != null) + { + switch (SWDType) // Configures the base timer based on the specific console's CPU and sample rate + { + case "wds ": throw new NotImplementedException("The base timer for the WDS type is not yet implemented."); // PlayStation + case "swdm": throw new NotImplementedException("The base timer for the SWDM type is not yet implemented."); // PlayStation 2 + case "swdl": BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo!.SampleRate); break; // Nintendo DS + case "swdb": BaseTimer = (ushort)(WiiUtils.PPC_Broadway_Clock + _sample.WavInfo!.SampleRate / 33); break; // Wii + } + if (_sample.WavInfo!.SampleFormat == SampleFormat.ADPCM) + { + _adpcmDecoder = new ADPCMDecoder(_sample.Data!); + } + //if (masterswd.Type == "swdb") + //{ + // _dspADPCM = _sample.DSPADPCM; + //} + //attackVolume = sample.WavInfo.AttackVolume == 0 ? split.AttackVolume : sample.WavInfo.AttackVolume; + //attack = sample.WavInfo.Attack == 0 ? split.Attack : sample.WavInfo.Attack; + //decay = sample.WavInfo.Decay == 0 ? split.Decay : sample.WavInfo.Decay; + //sustain = sample.WavInfo.Sustain == 0 ? split.Sustain : sample.WavInfo.Sustain; + //hold = sample.WavInfo.Hold == 0 ? split.Hold : sample.WavInfo.Hold; + //decay2 = sample.WavInfo.Decay2 == 0 ? split.Decay2 : sample.WavInfo.Decay2; + //release = sample.WavInfo.Release == 0 ? split.Release : sample.WavInfo.Release; + //attackVolume = split.AttackVolume == 0 ? sample.WavInfo.AttackVolume : split.AttackVolume; + //attack = split.Attack == 0 ? sample.WavInfo.Attack : split.Attack; + //decay = split.Decay == 0 ? sample.WavInfo.Decay : split.Decay; + //sustain = split.Sustain == 0 ? sample.WavInfo.Sustain : split.Sustain; + //hold = split.Hold == 0 ? sample.WavInfo.Hold : split.Hold; + //decay2 = split.Decay2 == 0 ? sample.WavInfo.Decay2 : split.Decay2; + //release = split.Release == 0 ? sample.WavInfo.Release : split.Release; + _attackVolume = split.AttackVolume == 0 ? _sample.WavInfo.AttackVolume == 0 ? (byte)0x7F : _sample.WavInfo.AttackVolume : split.AttackVolume; + _attack = split.Attack == 0 ? _sample.WavInfo.Attack == 0 ? (byte)0x7F : _sample.WavInfo.Attack : split.Attack; + _decay = split.Decay1 == 0 ? _sample.WavInfo.Decay1 == 0 ? (byte)0x7F : _sample.WavInfo.Decay1 : split.Decay1; + _sustain = split.Sustain == 0 ? _sample.WavInfo.Sustain == 0 ? (byte)0x7F : _sample.WavInfo.Sustain : split.Sustain; + _hold = split.Hold == 0 ? _sample.WavInfo.Hold == 0 ? (byte)0x7F : _sample.WavInfo.Hold : split.Hold; + _decay2 = split.Decay2 == 0 ? _sample.WavInfo.Decay2 == 0 ? (byte)0x7F : _sample.WavInfo.Decay2 : split.Decay2; + _release = split.Release == 0 ? _sample.WavInfo.Release == 0 ? (byte)0x7F : _sample.WavInfo.Release : split.Release; + DetermineEnvelopeStartingPoint(); + _pos = 0; + _prevLeft = _prevRight = 0; + NoteLength = noteLength; + return true; + } + } + return false; } - public void StartPSG(byte duty, uint noteDuration) - { - _sample!.WavInfo!.SampleFormat = SampleFormat.PSG; - _psgCounter = 0; - _psgDuty = duty; - BaseTimer = 8006; // NDSUtils.ARM7_CLOCK / 2093 - Start(noteDuration); - } + public void StartPSG(byte duty, uint noteDuration) + { + _sample!.WavInfo!.SampleFormat = SampleFormat.PSG; + _psgCounter = 0; + _psgDuty = duty; + BaseTimer = 8006; // NDSUtils.ARM7_CLOCK / 2093 + Start(noteDuration); + } - private void Start(uint noteDuration) - { - State = EnvelopeState.One; - _velocity = -92544; - _pos = 0; - _prevLeft = _prevRight = 0; - NoteLength = noteDuration; - } + private void Start(uint noteDuration) + { + State = EnvelopeState.One; + _velocity = -92544; + _pos = 0; + _prevLeft = _prevRight = 0; + NoteLength = noteDuration; + } - public void Stop() + public void Stop() { if (Owner is not null) { @@ -353,114 +356,115 @@ public void Process(out short left, out short right) case "wds ": case "swdm": case "swdl": - { - short samp; - switch (_sample!.WavInfo!.SampleFormat) - { - case SampleFormat.PCM8: - { - // If hit end - if (_dataOffset >= _sample.Data!.Length) - { - if (_sample.WavInfo.Loop) - { - _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); - } - else - { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; - } - } - samp = (short)((sbyte)_sample.Data[_dataOffset++] << 8); - break; - } - case SampleFormat.PCM16: - { - // If hit end - if (_dataOffset >= _sample.Data!.Length) - { - if (_sample.WavInfo.Loop) - { - _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); - } - else - { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; - } - } - samp = (short)(_sample.Data[_dataOffset++] | (_sample.Data[_dataOffset++] << 8)); - break; - } - case SampleFormat.ADPCM: - { - // If just looped - if (_adpcmDecoder!.DataOffset == _sample.WavInfo.LoopStart * 4 && !_adpcmDecoder.OnSecondNibble) - { - _adpcmLoopLastSample = _adpcmDecoder.LastSample; - _adpcmLoopStepIndex = _adpcmDecoder.StepIndex; - } - // If hit end - if (_adpcmDecoder.DataOffset >= _sample.Data!.Length && !_adpcmDecoder.OnSecondNibble) - { - if (_sample.WavInfo.Loop) - { - _adpcmDecoder.DataOffset = (int)(_sample.WavInfo.LoopStart * 4); - _adpcmDecoder.StepIndex = _adpcmLoopStepIndex; - _adpcmDecoder.LastSample = _adpcmLoopLastSample; - _adpcmDecoder.OnSecondNibble = false; - } - else - { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; - } - } - samp = _adpcmDecoder.GetSample(); - break; - } + { + short samp; + switch (_sample!.WavInfo!.SampleFormat) + { + case SampleFormat.PCM8: + { + // If hit end + if (_dataOffset >= _sample.Data!.Length) + { + if (_sample.WavInfo.Loop) + { + _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + samp = (short)((sbyte)_sample.Data[_dataOffset++] << 8); + break; + } + case SampleFormat.PCM16: + { + // If hit end + if (_dataOffset >= _sample.Data!.Length) + { + if (_sample.WavInfo.Loop) + { + _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + samp = (short)(_sample.Data[_dataOffset++] | (_sample.Data[_dataOffset++] << 8)); + break; + } + case SampleFormat.ADPCM: + { + // If just looped + if (_adpcmDecoder!.DataOffset == _sample.WavInfo.LoopStart * 4 && !_adpcmDecoder.OnSecondNibble) + { + _adpcmLoopLastSample = _adpcmDecoder.LastSample; + _adpcmLoopStepIndex = _adpcmDecoder.StepIndex; + } + // If hit end + if (_adpcmDecoder.DataOffset >= _sample.Data!.Length && !_adpcmDecoder.OnSecondNibble) + { + if (_sample.WavInfo.Loop) + { + _adpcmDecoder.DataOffset = (int)(_sample.WavInfo.LoopStart * 4); + _adpcmDecoder.StepIndex = _adpcmLoopStepIndex; + _adpcmDecoder.LastSample = _adpcmLoopLastSample; + _adpcmDecoder.OnSecondNibble = false; + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + samp = _adpcmDecoder.GetSample(); + break; + } case SampleFormat.PSG: { - samp = _psgCounter <= _psgDuty ? short.MinValue : short.MaxValue; - _psgCounter++; - if (_psgCounter >= 8) - { - _psgCounter = 0; - } - break; - } - default: samp = 0; break; - } - samp = (short)(samp * Volume / 0x7F); - _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); - _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); - break; + samp = _psgCounter <= _psgDuty ? short.MinValue : short.MaxValue; + _psgCounter++; + if (_psgCounter >= 8) + { + _psgCounter = 0; + } + break; + } + default: samp = 0; break; + } + samp = (short)(samp * Volume / 0x7F); + _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); + _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); + break; } case "swdb": - { - // If hit end - if (_dataOffset >= _sample!.Data!.Length) - { - if (_sample.WavInfo!.Loop) - { - _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); - } - else - { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; - } - } - short samp = (short)(_sample.Data[_dataOffset++] | (_sample.Data[_dataOffset++] << 8)); - samp = (short)(samp * Volume / 0x7F); - _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); - _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); - break; + { + // If hit end + if (_dataOffset >= _sample!.Data!.Length) + { + if (_sample.WavInfo!.Loop) + { + _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + if (_dataOffset + 10 > _sample.Data.Length) { break; } + short samp = (short)(_sample.Data[_dataOffset++] | (_sample.Data[_dataOffset++] << 8)); + samp = (short)(samp * Volume / 0x7F); + _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); + _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); + break; } } } diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs index f0fb176..4e420fb 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs @@ -14,9 +14,9 @@ internal sealed partial class DSELoadedSong : ILoadedSong public int LongestTrack; private readonly DSEPlayer _player; - private readonly string SWDFileName; - private readonly string SMDFileName; - private readonly SWD LocalSWD; + private readonly string SWDFileName; + private readonly string SMDFileName; + private readonly SWD? LocalSWD; private readonly byte[] SMDFile; public readonly DSETrack[] Tracks; @@ -25,33 +25,21 @@ public DSELoadedSong(DSEPlayer player, string bgm) _player = player; SWDFileName = bgm; SMDFileName = bgm; - StringComparison comparison = StringComparison.CurrentCultureIgnoreCase; + //StringComparison comparison = StringComparison.CurrentCultureIgnoreCase; // Check if a local SWD is accompaning a SMD if (new FileInfo(Path.ChangeExtension(bgm, "swd")).Exists) { - LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); // If it exists, this will be loaded as the local SWD - } - //if (SWDFileName.StartsWith("bgm", comparison) == SMDFileName.StartsWith("bgm", comparison)) - //{ - // LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); - // } - // else if (SWDFileName.StartsWith("me") == SMDFileName.StartsWith("me")) - // { - // LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); - // } - // else if (SWDFileName.StartsWith("se") == SMDFileName.StartsWith("se")) - // { - // LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); - // } - //else { } + LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); // If it exists, this will be loaded as the local SWD + } + SMDFile = File.ReadAllBytes(bgm); using (var stream = new MemoryStream(SMDFile)) { var r = new EndianBinaryReader(stream, ascii: true); Header header = new Header(r); if (header.Version != 0x415) { throw new DSEInvalidHeaderVersionException(header.Version); } - SongChunk songChunk = new SongChunk(r); + SongChunk songChunk = new SongChunk(r); Tracks = new DSETrack[songChunk.NumTracks]; Events = new List[songChunk.NumTracks]; diff --git a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs index 54c563d..350f9d4 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs @@ -26,7 +26,7 @@ public DSEPlayer(string mainSWDFile, DSEConfig config, DSEMixer mixer) _config = config; //string swdPath = Directory.GetFiles(mainSWDFile)[0]; - MainSWD = new SWD(mainSWDFile); + MainSWD = new SWD(mainSWDFile); } public override void LoadSong(int index) diff --git a/VG Music Studio - Core/NDS/DSE/SWD.cs b/VG Music Studio - Core/NDS/DSE/SWD.cs index 95b5c53..1001a67 100644 --- a/VG Music Studio - Core/NDS/DSE/SWD.cs +++ b/VG Music Studio - Core/NDS/DSE/SWD.cs @@ -9,151 +9,155 @@ namespace Kermalis.VGMusicStudio.Core.NDS.DSE; internal sealed class SWD { - #region Header - public interface IHeader + #region Header + public interface IHeader { // } public class Header : IHeader // Size 0x40 { - public string Type { get; set; } - public byte[]? Unknown1 { get; set; } - public uint Length { get; set; } - public ushort Version { get; set; } - public byte[]? Unknown2 { get; set; } - public byte[]? Padding1 { get; set; } - public ushort Year { get; set; } - public byte Month { get; set; } - public byte Day { get; set; } - public byte Hour { get; set; } - public byte Minute { get; set; } - public byte Second { get; set; } - public byte Centisecond { get; set; } + public string Type { get; set; } + public byte[]? Unknown1 { get; set; } + public uint Length { get; set; } + public ushort Version { get; set; } + public byte[]? Unknown2 { get; set; } + public byte[]? Padding1 { get; set; } + public ushort Year { get; set; } + public byte Month { get; set; } + public byte Day { get; set; } + public byte Hour { get; set; } + public byte Minute { get; set; } + public byte Second { get; set; } + public byte Centisecond { get; set; } public string Label { get; set; } public byte[]? Unknown3 { get; set; } public uint PCMDLength { get; set; } public byte[]? Unknown4 { get; set; } public ushort NumWAVISlots { get; set; } public ushort NumPRGISlots { get; set; } - public byte NumKeyGroups { get; set; } - public byte[]? Unknown5 { get; set; } + public byte NumKeyGroups { get; set; } + public byte[]? Unknown5 { get; set; } public uint WAVILength { get; set; } - public byte[]? Padding2 { get; set; } - - public Header(EndianBinaryReader r) - { - // File type metadata - The file type, version, and size of the file - Type = r.ReadString_Count(4); - if (Type == "swdb") - { - r.Endianness = Endianness.BigEndian; - } - Unknown1 = new byte[4]; - r.ReadBytes(Unknown1); - Length = r.ReadUInt32(); - Version = r.ReadUInt16(); - Unknown2 = new byte[2]; - r.ReadBytes(Unknown2); - - // Timestamp metadata - The time the SWD was published - r.Endianness = Endianness.LittleEndian; // Timestamp is always Little Endian, regardless of version or type, so it must be set to Little Endian to be read - - Padding1 = new byte[8]; // Padding - r.ReadBytes(Padding1); - Year = r.ReadUInt16(); // Year - Month = r.ReadByte(); // Month - Day = r.ReadByte(); // Day - Hour = r.ReadByte(); // Hour - Minute = r.ReadByte(); // Minute - Second = r.ReadByte(); // Second - Centisecond = r.ReadByte(); // Centisecond - if (Type == "swdb") { r.Endianness = Endianness.BigEndian; } // If type is swdb, restore back to Big Endian - - - // Info table - Label = r.ReadString_Count(16); - - switch (Version) // To ensure the version differences apply beyond this point - { - case 1026: - { - Unknown3 = new byte[22]; - r.ReadBytes(Unknown3); - - NumWAVISlots = r.ReadByte(); - - NumPRGISlots = r.ReadByte(); - - NumKeyGroups = r.ReadByte(); - - Padding2 = new byte[7]; - r.ReadBytes(Padding2); - - break; - } - case 1045: - { - Unknown3 = new byte[16]; - r.ReadBytes(Unknown3); - - PCMDLength = r.ReadUInt32(); - - Unknown4 = new byte[2]; - r.ReadBytes(Unknown4); - - NumWAVISlots = r.ReadUInt16(); - - NumPRGISlots = r.ReadUInt16(); - - Unknown5 = new byte[2]; - r.ReadBytes(Unknown5); - - WAVILength = r.ReadUInt32(); - - break; - } - } - } + public byte[]? Padding2 { get; set; } + + public Header(EndianBinaryReader r) + { + // File type metadata - The file type, version, and size of the file + Type = r.ReadString_Count(4); + if (Type.StartsWith("swd") == false) // Failsafe, to check if the file is a valid SWD file + { + throw new InvalidDataException("Invalid Data Exception:\nThis file is not a Wave Data (.SWD) file, please make sure the file extension is correct before opening.\nCall Stack:"); + } + if (Type == "swdb") + { + r.Endianness = Endianness.BigEndian; + } + Unknown1 = new byte[4]; + r.ReadBytes(Unknown1); + Length = r.ReadUInt32(); + Version = r.ReadUInt16(); + Unknown2 = new byte[2]; + r.ReadBytes(Unknown2); + + // Timestamp metadata - The time the SWD was published + r.Endianness = Endianness.LittleEndian; // Timestamp is always Little Endian, regardless of version or type, so it must be set to Little Endian to be read + + Padding1 = new byte[8]; // Padding + r.ReadBytes(Padding1); + Year = r.ReadUInt16(); // Year + Month = r.ReadByte(); // Month + Day = r.ReadByte(); // Day + Hour = r.ReadByte(); // Hour + Minute = r.ReadByte(); // Minute + Second = r.ReadByte(); // Second + Centisecond = r.ReadByte(); // Centisecond + if (Type == "swdb") { r.Endianness = Endianness.BigEndian; } // If type is swdb, restore back to Big Endian + + + // Info table + Label = r.ReadString_Count(16); + + switch (Version) // To ensure the version differences apply beyond this point + { + case 1026: + { + Unknown3 = new byte[22]; + r.ReadBytes(Unknown3); + + NumWAVISlots = r.ReadByte(); + + NumPRGISlots = r.ReadByte(); + + NumKeyGroups = r.ReadByte(); + + Padding2 = new byte[7]; + r.ReadBytes(Padding2); + + break; + } + case 1045: + { + Unknown3 = new byte[16]; + r.ReadBytes(Unknown3); + + PCMDLength = r.ReadUInt32(); + + Unknown4 = new byte[2]; + r.ReadBytes(Unknown4); + + NumWAVISlots = r.ReadUInt16(); + + NumPRGISlots = r.ReadUInt16(); + + Unknown5 = new byte[2]; + r.ReadBytes(Unknown5); + + WAVILength = r.ReadUInt32(); + + break; + } + } + } } public class ChunkHeader : IHeader // Size 0x10 { - public string Name { get; set; } - public byte[] Padding { get; set; } - public ushort Version { get; set; } - public uint ChunkBegin { get; set; } - public uint ChunkEnd { get; set; } - - public ChunkHeader(EndianBinaryReader r, long chunkOffset, SWD swd) - { - long oldOffset = r.Stream.Position; - r.Stream.Position = chunkOffset; - - // Chunk Name - Name = r.ReadString_Count(4); - - // Padding - Padding = new byte[2]; - r.ReadBytes(Padding); - - // Version - Version = r.ReadUInt16(); - - // Chunk Begin - r.Endianness = Endianness.LittleEndian; // To ensure this is read in Little Endian in all versions and types - ChunkBegin = r.ReadUInt32(); - if (swd.Type == "swdb") { r.Endianness = Endianness.BigEndian; } // To revert back to Big Endian when the type is "swdb" - - // Chunk End - ChunkEnd = r.ReadUInt32(); - - r.Stream.Position = oldOffset; - } - } - #endregion - - #region SplitEntry - public interface ISplitEntry + public string Name { get; set; } + public byte[] Padding { get; set; } + public ushort Version { get; set; } + public uint ChunkBegin { get; set; } + public uint ChunkEnd { get; set; } + + public ChunkHeader(EndianBinaryReader r, long chunkOffset, SWD swd) + { + long oldOffset = r.Stream.Position; + r.Stream.Position = chunkOffset; + + // Chunk Name + Name = r.ReadString_Count(4); + + // Padding + Padding = new byte[2]; + r.ReadBytes(Padding); + + // Version + Version = r.ReadUInt16(); + + // Chunk Begin + r.Endianness = Endianness.LittleEndian; // To ensure this is read in Little Endian in all versions and types + ChunkBegin = r.ReadUInt32(); + if (swd.Type == "swdb") { r.Endianness = Endianness.BigEndian; } // To revert back to Big Endian when the type is "swdb" + + // Chunk End + ChunkEnd = r.ReadUInt32(); + + r.Stream.Position = oldOffset; + } + } + #endregion + + #region SplitEntry + public interface ISplitEntry { byte LowKey { get; } byte HighKey { get; } @@ -170,7 +174,7 @@ public interface ISplitEntry } public class SplitEntry : ISplitEntry // 0x30 { - public byte Unknown1 { get; set; } + public byte Unknown1 { get; set; } public byte Id { get; set; } public byte[] Unknown2 { get; set; } public byte LowKey { get; set; } @@ -201,125 +205,125 @@ public class SplitEntry : ISplitEntry // 0x30 ushort ISplitEntry.SampleId => SampleId; - public SplitEntry(EndianBinaryReader r, SWD swd) - { - Unknown1 = r.ReadByte(); + public SplitEntry(EndianBinaryReader r, SWD swd) + { + Unknown1 = r.ReadByte(); - Id = r.ReadByte(); + Id = r.ReadByte(); - Unknown2 = new byte[2]; - r.ReadBytes(Unknown2); + Unknown2 = new byte[2]; + r.ReadBytes(Unknown2); - LowKey = r.ReadByte(); + LowKey = r.ReadByte(); - HighKey = r.ReadByte(); + HighKey = r.ReadByte(); - LowKey2 = r.ReadByte(); + LowKey2 = r.ReadByte(); - HighKey2 = r.ReadByte(); + HighKey2 = r.ReadByte(); - LowVelocity = r.ReadByte(); + LowVelocity = r.ReadByte(); - HighVelocity = r.ReadByte(); + HighVelocity = r.ReadByte(); - LowVelocity2 = r.ReadByte(); + LowVelocity2 = r.ReadByte(); - HighVelocity2 = r.ReadByte(); + HighVelocity2 = r.ReadByte(); - switch (swd.Version) - { - case 1026: - { - Unknown3 = new byte[5]; - r.ReadBytes(Unknown3); + switch (swd.Version) + { + case 1026: + { + Unknown3 = new byte[5]; + r.ReadBytes(Unknown3); - SampleId = r.ReadByte(); + SampleId = r.ReadByte(); - Unknown4 = new byte[2]; - r.ReadBytes(Unknown4); + Unknown4 = new byte[2]; + r.ReadBytes(Unknown4); - SampleRootKey = r.ReadByte(); + SampleRootKey = r.ReadByte(); - SampleTranspose = r.ReadSByte(); + SampleTranspose = r.ReadSByte(); - SampleVolume = r.ReadByte(); + SampleVolume = r.ReadByte(); - SamplePanpot = r.ReadSByte(); + SamplePanpot = r.ReadSByte(); - KeyGroupId = r.ReadByte(); + KeyGroupId = r.ReadByte(); - Unknown5 = new byte[15]; - r.ReadBytes(Unknown5); + Unknown5 = new byte[15]; + r.ReadBytes(Unknown5); - AttackVolume = r.ReadByte(); + AttackVolume = r.ReadByte(); - Attack = r.ReadByte(); + Attack = r.ReadByte(); - Decay1 = r.ReadByte(); + Decay1 = r.ReadByte(); - Sustain = r.ReadByte(); + Sustain = r.ReadByte(); - Hold = r.ReadByte(); + Hold = r.ReadByte(); - Decay2 = r.ReadByte(); + Decay2 = r.ReadByte(); - Release = r.ReadByte(); + Release = r.ReadByte(); - Break = r.ReadByte(); + Break = r.ReadByte(); - break; - } - case 1045: - { - Unknown2 = new byte[6]; - r.ReadBytes(Unknown2); + break; + } + case 1045: + { + Unknown2 = new byte[6]; + r.ReadBytes(Unknown2); - SampleId = r.ReadUInt16(); + SampleId = r.ReadUInt16(); - Unknown3 = new byte[2]; - r.ReadBytes(Unknown3); + Unknown3 = new byte[2]; + r.ReadBytes(Unknown3); - SampleRootKey = r.ReadByte(); + SampleRootKey = r.ReadByte(); - SampleTranspose = r.ReadSByte(); + SampleTranspose = r.ReadSByte(); - SampleVolume = r.ReadByte(); + SampleVolume = r.ReadByte(); - SamplePanpot = r.ReadSByte(); + SamplePanpot = r.ReadSByte(); - KeyGroupId = r.ReadByte(); + KeyGroupId = r.ReadByte(); - Unknown4 = new byte[13]; - r.ReadBytes(Unknown4); + Unknown4 = new byte[13]; + r.ReadBytes(Unknown4); - AttackVolume = r.ReadByte(); + AttackVolume = r.ReadByte(); - Attack = r.ReadByte(); + Attack = r.ReadByte(); - Decay1 = r.ReadByte(); + Decay1 = r.ReadByte(); - Sustain = r.ReadByte(); + Sustain = r.ReadByte(); - Hold = r.ReadByte(); + Hold = r.ReadByte(); - Decay2 = r.ReadByte(); + Decay2 = r.ReadByte(); - Release = r.ReadByte(); + Release = r.ReadByte(); - Break = r.ReadByte(); + Break = r.ReadByte(); - break; - } + break; + } - // In the event that there's a SWD version that hasn't been discovered yet - default: throw new NotImplementedException("This version of the SWD specification has not been implemented into VG Music Studio."); - } - } + // In the event that there's a SWD version that hasn't been discovered yet + default: throw new NotImplementedException("This version of the SWD specification has not been implemented into VG Music Studio."); + } + } } #endregion - #region ProgramInfo - public interface IProgramInfo + #region ProgramInfo + public interface IProgramInfo { ISplitEntry[] SplitEntries { get; } } @@ -327,7 +331,7 @@ public class ProgramInfo : IProgramInfo { public ushort Id { get; set; } public byte NumSplits { get; set; } - public byte[] Unknown1 { get; set; } + public byte[] Unknown1 { get; set; } public byte Volume { get; set; } public byte Panpot { get; set; } public byte[] Unknown2 { get; set; } @@ -335,99 +339,99 @@ public class ProgramInfo : IProgramInfo public byte[] Unknown3 { get; set; } public LFOInfo[] LFOInfos { get; set; } public byte[]? Unknown4 { get; set; } - public KeyGroup[]? KeyGroups { get; set; } + public KeyGroup[]? KeyGroups { get; set; } public SplitEntry[] SplitEntries { get; set; } ISplitEntry[] IProgramInfo.SplitEntries => SplitEntries; - public ProgramInfo(EndianBinaryReader r, SWD swd) - { - switch(swd.Version) - { - case 1026: - { - Id = r.ReadByte(); + public ProgramInfo(EndianBinaryReader r, SWD swd) + { + switch(swd.Version) + { + case 1026: + { + Id = r.ReadByte(); - NumSplits = r.ReadByte(); + NumSplits = r.ReadByte(); - Unknown1 = new byte[2]; - r.ReadBytes(Unknown1); + Unknown1 = new byte[2]; + r.ReadBytes(Unknown1); - Volume = r.ReadByte(); + Volume = r.ReadByte(); - Panpot = r.ReadByte(); + Panpot = r.ReadByte(); - Unknown2 = new byte[5]; - r.ReadBytes(Unknown2); + Unknown2 = new byte[5]; + r.ReadBytes(Unknown2); - NumLFOs = r.ReadByte(); + NumLFOs = r.ReadByte(); - Unknown3 = new byte[4]; - r.ReadBytes(Unknown3); + Unknown3 = new byte[4]; + r.ReadBytes(Unknown3); - KeyGroups = new KeyGroup[16]; + KeyGroups = new KeyGroup[16]; - LFOInfos = new LFOInfo[NumLFOs]; - for (int i = 0; i < NumLFOs; i++) - { - LFOInfos[i] = new LFOInfo(r); - }; + LFOInfos = new LFOInfo[NumLFOs]; + for (int i = 0; i < NumLFOs; i++) + { + LFOInfos[i] = new LFOInfo(r); + }; - SplitEntries = new SplitEntry[NumSplits]; + SplitEntries = new SplitEntry[NumSplits]; - break; - } + break; + } - case 1045: - { - Id = r.ReadUInt16(); + case 1045: + { + Id = r.ReadUInt16(); - NumSplits = r.ReadByte(); + NumSplits = r.ReadByte(); - Unknown1 = new byte[1]; - r.ReadBytes(Unknown1); + Unknown1 = new byte[1]; + r.ReadBytes(Unknown1); - Volume = r.ReadByte(); + Volume = r.ReadByte(); - Panpot = r.ReadByte(); + Panpot = r.ReadByte(); - Unknown2 = new byte[5]; - r.ReadBytes(Unknown2); + Unknown2 = new byte[5]; + r.ReadBytes(Unknown2); - NumLFOs = r.ReadByte(); + NumLFOs = r.ReadByte(); - Unknown3 = new byte[4]; - r.ReadBytes(Unknown3); + Unknown3 = new byte[4]; + r.ReadBytes(Unknown3); - LFOInfos = new LFOInfo[NumLFOs]; - for (int i = 0; i < NumLFOs; i++) - { - LFOInfos[i] = new LFOInfo(r); - }; + LFOInfos = new LFOInfo[NumLFOs]; + for (int i = 0; i < NumLFOs; i++) + { + LFOInfos[i] = new LFOInfo(r); + }; - Unknown4 = new byte[16]; - r.ReadBytes(Unknown4); + Unknown4 = new byte[16]; + r.ReadBytes(Unknown4); - SplitEntries = new SplitEntry[NumSplits]; - for (int i = 0; i < NumSplits; i++) - { - SplitEntries[i] = new SplitEntry(r, swd); - } + SplitEntries = new SplitEntry[NumSplits]; + for (int i = 0; i < NumSplits; i++) + { + SplitEntries[i] = new SplitEntry(r, swd); + } - break; - } + break; + } - // In the event that there's a version that hasn't been discovered yet - default: throw new NotImplementedException("This Digital Sound Elements version has not been implemented into VG Music Studio."); - } + // In the event that there's a version that hasn't been discovered yet + default: throw new NotImplementedException("This Digital Sound Elements version has not been implemented into VG Music Studio."); + } - } + } - } - #endregion + } + #endregion - #region WavInfo - public interface IWavInfo + #region WavInfo + public interface IWavInfo { byte RootNote { get; } sbyte Transpose { get; } @@ -447,284 +451,284 @@ public interface IWavInfo byte Release { get; } } - public class WavInfo : IWavInfo // Size 0x40 + public class WavInfo : IWavInfo // Size 0x40 { - public byte[] Entry { get; set; } - public ushort Id { get; set; } - public byte[] Unknown2 { get; set; } - public byte RootNote { get; set; } - public sbyte Transpose { get; set; } - public byte Volume { get; set; } - public sbyte Panpot { get; set; } - public byte[] Unknown3 { get; set; } - public ushort Version { get; set; } - public SampleFormat SampleFormat { get; set; } - public byte Unknown4 { get; set; } - public bool Loop { get; set; } - public byte Unknown5 { get; set; } - public byte SamplesPer32Bits { get; set; } - public byte Unknown6 { get; set; } - public byte BitDepth { get; set; } - public byte[] Unknown7 { get; set; } - public uint SampleRate { get; set; } - public uint SampleOffset { get; set; } - public uint LoopStart { get; set; } - public uint LoopEnd { get; set; } - public byte EnvOn { get; set; } - public byte EnvMulti { get; set; } - public byte[] Unknown8 { get; set; } - public byte AttackVolume { get; set; } - public byte Attack { get; set; } - public byte Decay1 { get; set; } - public byte Sustain { get; set; } - public byte Hold { get; set; } - public byte Decay2 { get; set; } - public byte Release { get; set; } - public byte Break { get; set; } - - public WavInfo(EndianBinaryReader r, SWD swd) - { - // SWD version format check + public byte[] Entry { get; set; } + public ushort Id { get; set; } + public byte[] Unknown2 { get; set; } + public byte RootNote { get; set; } + public sbyte Transpose { get; set; } + public byte Volume { get; set; } + public sbyte Panpot { get; set; } + public byte[] Unknown3 { get; set; } + public ushort Version { get; set; } + public SampleFormat SampleFormat { get; set; } + public byte Unknown4 { get; set; } + public bool Loop { get; set; } + public byte Unknown5 { get; set; } + public byte SamplesPer32Bits { get; set; } + public byte Unknown6 { get; set; } + public byte BitDepth { get; set; } + public byte[] Unknown7 { get; set; } + public uint SampleRate { get; set; } + public uint SampleOffset { get; set; } + public uint LoopStart { get; set; } + public uint LoopEnd { get; set; } + public byte EnvOn { get; set; } + public byte EnvMulti { get; set; } + public byte[] Unknown8 { get; set; } + public byte AttackVolume { get; set; } + public byte Attack { get; set; } + public byte Decay1 { get; set; } + public byte Sustain { get; set; } + public byte Hold { get; set; } + public byte Decay2 { get; set; } + public byte Release { get; set; } + public byte Break { get; set; } + + public WavInfo(EndianBinaryReader r, SWD swd) + { + // SWD version format check switch(swd.Version) { - case 1026: - { - // The wave table Entry Variable - Entry = new byte[1]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() - r.ReadBytes(Entry); // Reads the byte + case 1026: + { + // The wave table Entry Variable + Entry = new byte[1]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() + r.ReadBytes(Entry); // Reads the byte - // Wave ID - Id = r.ReadByte(); // Reads the ID of the wave sample + // Wave ID + Id = r.ReadByte(); // Reads the ID of the wave sample - // Currently undocumented variable(s) - Unknown2 = new byte[2]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() - r.ReadBytes(Unknown2); // Reads the bytes + // Currently undocumented variable(s) + Unknown2 = new byte[2]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() + r.ReadBytes(Unknown2); // Reads the bytes - // Root Note - RootNote = r.ReadByte(); + // Root Note + RootNote = r.ReadByte(); - // Transpose - Transpose = r.ReadSByte(); + // Transpose + Transpose = r.ReadSByte(); - // Volume - Volume = r.ReadByte(); + // Volume + Volume = r.ReadByte(); - // Panpot - Panpot = r.ReadSByte(); + // Panpot + Panpot = r.ReadSByte(); - // Sample Format - if (swd.Type == "swdb") - { - r.Endianness = Endianness.LittleEndian; - SampleFormat = (SampleFormat)r.ReadUInt16(); - r.Endianness = Endianness.BigEndian; - } - else - { - r.Endianness = Endianness.BigEndian; - SampleFormat = (SampleFormat)r.ReadUInt16(); - r.Endianness = Endianness.LittleEndian; - } + // Sample Format + if (swd.Type == "swdb") + { + r.Endianness = Endianness.LittleEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.BigEndian; + } + else + { + r.Endianness = Endianness.BigEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.LittleEndian; + } - // Undocumented variable(s) - Unknown3 = new byte[7]; - r.ReadBytes(Unknown3); + // Undocumented variable(s) + Unknown3 = new byte[7]; + r.ReadBytes(Unknown3); - // Version - Version = r.ReadUInt16(); + // Version + Version = r.ReadUInt16(); - // Loop enable and disable - Loop = r.ReadBoolean(); + // Loop enable and disable + Loop = r.ReadBoolean(); - // Sample Rate - SampleRate = r.ReadUInt32(); + // Sample Rate + SampleRate = r.ReadUInt32(); - // Sample Offset - SampleOffset = r.ReadUInt32(); + // Sample Offset + SampleOffset = r.ReadUInt32(); - // Loop Start - LoopStart = r.ReadUInt32(); + // Loop Start + LoopStart = r.ReadUInt32(); - // Loop End - LoopEnd = r.ReadUInt32(); + // Loop End + LoopEnd = r.ReadUInt32(); - // Undocumented variable(s) - Unknown7 = new byte[16]; - r.ReadBytes(Unknown7); + // Undocumented variable(s) + Unknown7 = new byte[16]; + r.ReadBytes(Unknown7); - // Volume Envelop On - EnvOn = r.ReadByte(); + // Volume Envelop On + EnvOn = r.ReadByte(); - // Volume Envelop Multiple - EnvMulti = r.ReadByte(); + // Volume Envelop Multiple + EnvMulti = r.ReadByte(); - // Undocumented variable(s) - Unknown8 = new byte[6]; - r.ReadBytes(Unknown8); + // Undocumented variable(s) + Unknown8 = new byte[6]; + r.ReadBytes(Unknown8); - // Attack Volume - AttackVolume = r.ReadByte(); + // Attack Volume + AttackVolume = r.ReadByte(); - // Attack - Attack = r.ReadByte(); + // Attack + Attack = r.ReadByte(); - // Decay 1 - Decay1 = r.ReadByte(); + // Decay 1 + Decay1 = r.ReadByte(); - // Sustain - Sustain = r.ReadByte(); + // Sustain + Sustain = r.ReadByte(); - // Hold - Hold = r.ReadByte(); + // Hold + Hold = r.ReadByte(); - // Decay 2 - Decay2 = r.ReadByte(); + // Decay 2 + Decay2 = r.ReadByte(); - // Release - Release = r.ReadByte(); + // Release + Release = r.ReadByte(); - // The wave table Break Variable - Break = r.ReadByte(); + // The wave table Break Variable + Break = r.ReadByte(); - break; - } + break; + } - case 1045: // Digital Sound Elements - SWD Specification 4.21 - { - // The wave table Entry Variable - Entry = new byte[2]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() - r.ReadBytes(Entry); // Reads the bytes + case 1045: // Digital Sound Elements - SWD Specification 4.21 + { + // The wave table Entry Variable + Entry = new byte[2]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() + r.ReadBytes(Entry); // Reads the bytes - // Wave ID - r.Endianness = Endianness.LittleEndian; // Changes the reader to Little Endian - Id = r.ReadUInt16(); // Reads the ID of the wave sample as Little Endian - if (swd.Type == "swdb") // Checks if the str string value matches "swdb" - { - r.Endianness = Endianness.BigEndian; // Restores the reader back to Big Endian - } + // Wave ID + r.Endianness = Endianness.LittleEndian; // Changes the reader to Little Endian + Id = r.ReadUInt16(); // Reads the ID of the wave sample as Little Endian + if (swd.Type == "swdb") // Checks if the str string value matches "swdb" + { + r.Endianness = Endianness.BigEndian; // Restores the reader back to Big Endian + } - // Currently undocumented variable - Unknown2 = new byte[2]; // Same as the one before - r.ReadBytes(Unknown2); + // Currently undocumented variable + Unknown2 = new byte[2]; // Same as the one before + r.ReadBytes(Unknown2); - // Root Note - RootNote = r.ReadByte(); + // Root Note + RootNote = r.ReadByte(); - // Transpose - Transpose = r.ReadSByte(); + // Transpose + Transpose = r.ReadSByte(); - // Volume - Volume = r.ReadByte(); + // Volume + Volume = r.ReadByte(); - // Panpot - Panpot = r.ReadSByte(); + // Panpot + Panpot = r.ReadSByte(); - // Undocumented variable - Unknown3 = new byte[6]; // Same as before, except we need to read 6 bytes instead of 2 - r.ReadBytes(Unknown3); + // Undocumented variable + Unknown3 = new byte[6]; // Same as before, except we need to read 6 bytes instead of 2 + r.ReadBytes(Unknown3); - // Version - Version = r.ReadUInt16(); + // Version + Version = r.ReadUInt16(); - // Sample Format - if (swd.Type == "swdb") - { - r.Endianness = Endianness.LittleEndian; - SampleFormat = (SampleFormat)r.ReadUInt16(); - r.Endianness = Endianness.BigEndian; - } - else - { - r.Endianness = Endianness.BigEndian; - SampleFormat = (SampleFormat)r.ReadUInt16(); - r.Endianness = Endianness.LittleEndian; - } + // Sample Format + if (swd.Type == "swdb") + { + r.Endianness = Endianness.LittleEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.BigEndian; + } + else + { + r.Endianness = Endianness.BigEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.LittleEndian; + } - // Undocumented variable(s) - Unknown4 = r.ReadByte(); + // Undocumented variable(s) + Unknown4 = r.ReadByte(); - // Loop enable or disable - Loop = r.ReadBoolean(); + // Loop enable or disable + Loop = r.ReadBoolean(); - // Undocumented variable(s) - Unknown5 = r.ReadByte(); + // Undocumented variable(s) + Unknown5 = r.ReadByte(); - // Samples per 32 bits - SamplesPer32Bits = r.ReadByte(); + // Samples per 32 bits + SamplesPer32Bits = r.ReadByte(); - // Undocumented variable(s) - Unknown6 = r.ReadByte(); + // Undocumented variable(s) + Unknown6 = r.ReadByte(); - // Bit Depth - BitDepth = r.ReadByte(); + // Bit Depth + BitDepth = r.ReadByte(); - // Undocumented variable(s) - Unknown7 = new byte[6]; // Once again, create a variable to specify 6 bytes and to read using it - r.ReadBytes(Unknown7); + // Undocumented variable(s) + Unknown7 = new byte[6]; // Once again, create a variable to specify 6 bytes and to read using it + r.ReadBytes(Unknown7); - // Sample Rate - SampleRate = r.ReadUInt32(); + // Sample Rate + SampleRate = r.ReadUInt32(); - // Sample Offset - SampleOffset = r.ReadUInt32(); + // Sample Offset + SampleOffset = r.ReadUInt32(); - // Loop Start - LoopStart = r.ReadUInt32(); + // Loop Start + LoopStart = r.ReadUInt32(); - // Loop End - LoopEnd = r.ReadUInt32(); + // Loop End + LoopEnd = r.ReadUInt32(); - // Volume Envelop On - EnvOn = r.ReadByte(); + // Volume Envelop On + EnvOn = r.ReadByte(); - // Volume Envelop Multiple - EnvMulti = r.ReadByte(); + // Volume Envelop Multiple + EnvMulti = r.ReadByte(); - // Undocumented variable(s) - Unknown8 = new byte[6]; // Same as before - r.ReadBytes(Unknown8); + // Undocumented variable(s) + Unknown8 = new byte[6]; // Same as before + r.ReadBytes(Unknown8); - // Attack Volume - AttackVolume = r.ReadByte(); + // Attack Volume + AttackVolume = r.ReadByte(); - // Attack - Attack = r.ReadByte(); + // Attack + Attack = r.ReadByte(); - // Decay 1 - Decay1 = r.ReadByte(); + // Decay 1 + Decay1 = r.ReadByte(); - // Sustain - Sustain = r.ReadByte(); + // Sustain + Sustain = r.ReadByte(); - // Hold - Hold = r.ReadByte(); + // Hold + Hold = r.ReadByte(); - // Decay 2 - Decay2 = r.ReadByte(); + // Decay 2 + Decay2 = r.ReadByte(); - // Release - Release = r.ReadByte(); + // Release + Release = r.ReadByte(); - // The wave table Break Variable - Break = r.ReadByte(); + // The wave table Break Variable + Break = r.ReadByte(); - break; + break; } // In the event that there's a version that hasn't been discovered yet default: throw new NotImplementedException("This version of the SWD specification has not yet been implemented into VG Music Studio."); } - } - } - #endregion + } + } + #endregion - public class SampleBlock + public class SampleBlock { public WavInfo? WavInfo; - public DSPADPCM? DSPADPCM; + public DSPADPCM? DSPADPCM; public byte[]? Data; - //public short[]? Data16Bit; + //public short[]? Data16Bit; } public class ProgramBank { @@ -740,22 +744,22 @@ public class KeyGroup // Size 0x8 public byte HighNote { get; set; } public ushort Unknown { get; set; } - public KeyGroup(EndianBinaryReader r, SWD swd) - { - r.Endianness = Endianness.LittleEndian; - Id = r.ReadUInt16(); - if (swd.Type == "swdb") { r.Endianness = Endianness.BigEndian; } + public KeyGroup(EndianBinaryReader r, SWD swd) + { + r.Endianness = Endianness.LittleEndian; + Id = r.ReadUInt16(); + if (swd.Type == "swdb") { r.Endianness = Endianness.BigEndian; } - Poly = r.ReadByte(); + Poly = r.ReadByte(); - Priority = r.ReadByte(); + Priority = r.ReadByte(); - LowNote = r.ReadByte(); + LowNote = r.ReadByte(); - HighNote = r.ReadByte(); + HighNote = r.ReadByte(); - Unknown = r.ReadUInt16(); - } + Unknown = r.ReadUInt16(); + } } public class LFOInfo { @@ -770,41 +774,41 @@ public class LFOInfo public byte UnknownE { get; set; } public byte UnknownF { get; set; } - public LFOInfo(EndianBinaryReader r) - { - Unknown1 = r.ReadByte(); + public LFOInfo(EndianBinaryReader r) + { + Unknown1 = r.ReadByte(); - HasData = r.ReadByte(); + HasData = r.ReadByte(); - Type = r.ReadByte(); + Type = r.ReadByte(); - CallbackType = r.ReadByte(); + CallbackType = r.ReadByte(); - Unknown4 = r.ReadUInt32(); + Unknown4 = r.ReadUInt32(); - Unknown8 = r.ReadUInt16(); + Unknown8 = r.ReadUInt16(); - UnknownA = r.ReadUInt16(); + UnknownA = r.ReadUInt16(); - UnknownC = r.ReadUInt16(); + UnknownC = r.ReadUInt16(); - UnknownE = r.ReadByte(); + UnknownE = r.ReadByte(); - UnknownF = r.ReadByte(); - } + UnknownF = r.ReadByte(); + } } - public Header? Info; - public string Type; // "swdb" or "swdl" + public Header? Info; + public string Type; // "swdb" or "swdl" public uint Length; public ushort Version; - public long WaviChunkOffset, WaviDataOffset, - PrgiChunkOffset, PrgiDataOffset, - KgrpChunkOffset, KgrpDataOffset, - PcmdChunkOffset, PcmdDataOffset, - EodChunkOffset; - public ChunkHeader? WaviInfo, PrgiInfo, KgrpInfo, PcmdInfo, EodInfo; + public long WaviChunkOffset, WaviDataOffset, + PrgiChunkOffset, PrgiDataOffset, + KgrpChunkOffset, KgrpDataOffset, + PcmdChunkOffset, PcmdDataOffset, + EodChunkOffset; + public ChunkHeader? WaviInfo, PrgiInfo, KgrpInfo, PcmdInfo, EodInfo; public ProgramBank? Programs; public SampleBlock[]? Samples; @@ -814,202 +818,202 @@ public SWD(string path) using (var stream = new MemoryStream(File.ReadAllBytes(path))) { var r = new EndianBinaryReader(stream, ascii: true); - Info = new Header(r); - Type = Info.Type; - Length = Info.Length; - Version = Info.Version; - Programs = ReadPrograms(r, Info.NumPRGISlots, this); - - switch (Version) - { - case 0x402: - { - Samples = ReadSamples(r, Info.NumWAVISlots, this); - break; - } - case 0x415: - { - if (Info.PCMDLength != 0 && (Info.PCMDLength & 0xFFFF0000) != 0xAAAA0000) - { - Samples = ReadSamples(r, Info.NumWAVISlots, this); - } - break; - } - default: throw new InvalidDataException(); - } - } + Info = new Header(r); + Type = Info.Type; + Length = Info.Length; + Version = Info.Version; + Programs = ReadPrograms(r, Info.NumPRGISlots, this); + + switch (Version) + { + case 0x402: + { + Samples = ReadSamples(r, Info.NumWAVISlots, this); + break; + } + case 0x415: + { + if (Info.PCMDLength != 0 && (Info.PCMDLength & 0xFFFF0000) != 0xAAAA0000) + { + Samples = ReadSamples(r, Info.NumWAVISlots, this); + } + break; + } + default: throw new InvalidDataException(); + } + } return; } - #region FindChunk - private static long FindChunk(EndianBinaryReader r, string chunk) + #region FindChunk + private static long FindChunk(EndianBinaryReader r, string chunk) { long pos = -1; long oldPosition = r.Stream.Position; - r.Stream.Position = 0; - while (r.Stream.Position < r.Stream.Length) + r.Stream.Position = 0; + while (r.Stream.Position < r.Stream.Length) { string str = r.ReadString_Count(4); - if (str == chunk) + if (str == chunk) { pos = r.Stream.Position - 4; - break; + break; } - switch (str) + switch (str) { case "swdb": - { - r.Stream.Position += 0x4C; - break; - } - case "swdl": + { + r.Stream.Position += 0x4C; + break; + } + case "swdl": { r.Stream.Position += 0x4C; break; } default: { - Debug.WriteLine($"Ignoring {str} chunk"); + Debug.WriteLine($"Ignoring {str} chunk"); r.Stream.Position += 0x8; uint length = r.ReadUInt32(); r.Stream.Position += length; r.Stream.Align(16); - break; + break; } - } - } + } + } r.Stream.Position = oldPosition; - return pos; + return pos; } - #endregion + #endregion - #region SampleBlock - private SampleBlock[] ReadSamples(EndianBinaryReader r, int numWAVISlots, SWD swd) + #region SampleBlock + private SampleBlock[] ReadSamples(EndianBinaryReader r, int numWAVISlots, SWD swd) { - // These apply the chunk offsets that are found to both local and the field functions, chunk header constructors are available here incase they're needed - long waviChunkOffset = swd.WaviChunkOffset = FindChunk(r, "wavi"); - long pcmdChunkOffset = swd.PcmdChunkOffset = FindChunk(r, "pcmd"); - long eodChunkOffset = swd.EodChunkOffset = FindChunk(r, "eod "); - if (waviChunkOffset == -1 || pcmdChunkOffset == -1) + // These apply the chunk offsets that are found to both local and the field functions, chunk header constructors are available here incase they're needed + long waviChunkOffset = swd.WaviChunkOffset = FindChunk(r, "wavi"); + long pcmdChunkOffset = swd.PcmdChunkOffset = FindChunk(r, "pcmd"); + long eodChunkOffset = swd.EodChunkOffset = FindChunk(r, "eod "); + if (waviChunkOffset == -1 || pcmdChunkOffset == -1) { throw new InvalidDataException(); } else { - WaviInfo = new ChunkHeader(r, waviChunkOffset, swd); - long waviDataOffset = WaviDataOffset = waviChunkOffset + 0x10; - PcmdInfo = new ChunkHeader(r, pcmdChunkOffset, swd); - long pcmdDataOffset = PcmdDataOffset = pcmdChunkOffset + 0x10; - EodInfo = new ChunkHeader(r, eodChunkOffset, swd); - var samples = new SampleBlock[numWAVISlots]; - for (int i = 0; i < numWAVISlots; i++) + WaviInfo = new ChunkHeader(r, waviChunkOffset, swd); + long waviDataOffset = WaviDataOffset = waviChunkOffset + 0x10; + PcmdInfo = new ChunkHeader(r, pcmdChunkOffset, swd); + long pcmdDataOffset = PcmdDataOffset = pcmdChunkOffset + 0x10; + EodInfo = new ChunkHeader(r, eodChunkOffset, swd); + var samples = new SampleBlock[numWAVISlots]; + for (int i = 0; i < numWAVISlots; i++) { r.Stream.Position = waviDataOffset + (2 * i); ushort offset = r.ReadUInt16(); - if (offset != 0) + if (offset != 0) { r.Stream.Position = offset + waviDataOffset; - WavInfo wavInfo = new WavInfo(r, swd); - switch (Type) - { - case "swdm": - { - throw new NotImplementedException("This Digital Sound Elements type has not yet been implemented."); - } - - case "swdl": - { - samples[i] = new SampleBlock - { - WavInfo = wavInfo, - Data = new byte[(int)((wavInfo.LoopStart + wavInfo.LoopEnd) * 4)], - }; - r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; - r.ReadBytes(samples[i].Data); - - break; - } - - case "swdb": - { - samples[i] = new SampleBlock - { - WavInfo = wavInfo, - //Data = new byte[samples[i].DSPADPCM!.Info.num_samples] - }; - r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; - samples[i].DSPADPCM = new DSPADPCM(r); - Span data = new short[samples[i].DSPADPCM!.Info.num_samples / 2]; - data = DSPADPCM.DSPADPCMToPCM16(samples[i].DSPADPCM!.Data, samples[i].DSPADPCM!.Info.num_samples, samples[i].DSPADPCM!.Info); - samples[i].Data = new byte[samples[i].DSPADPCM!.Info.ea]; - //wavInfo.LoopStart = wavInfo.LoopStart / 4; - //wavInfo.LoopEnd = wavInfo.LoopEnd / 4; - //samples[i].Data16Bit = new short[(int)((wavInfo.LoopStart + wavInfo.LoopEnd) / 4) / 4 + 85]; - //DSPADPCM.Decode(samples[i].DSPADPCM!.Data, samples[i].Data16Bit, ref samples[i].DSPADPCM!.Info, samples[i].DSPADPCM!.Info.num_samples); - int e = 0; - for (int d = 0; d < samples[i].DSPADPCM!.Info.num_adpcm_nibbles; d++) - { - samples[i].Data![e] = (byte)(data[d] >> 8); - if (e < samples[i].Data!.Length) { e += 1; } - if (e >= samples[i].Data!.Length) { break; } - samples[i].Data![e] = (byte)(data[d]); - if (e < samples[i].Data!.Length) { e += 1; } - if (e >= samples[i].Data!.Length) { break; } - } - - // Trying to implement an error message that informs anyone of a EndOfStreamException caused by a different encoding type. - //if (swd.PcmdDataOffset + samples[i].WavInfo!.SampleOffset + (samples[i].DSPADPCM!.NumADPCMNibbles / 2) + 9 > swd.EodChunkOffset) - //{ - // throw new EndOfStreamException("End of Stream Exception:\n" + - // "The number of ADPCM nibbles, divided by 2, plus 9 bytes, reads the sample data beyond this SWD.\n" + - // "\n" + - // "This is because VG Music Studio is incorrectly reading the actual size of the DSP-ADPCM sample data.\n" + - // "\n" + - // "If you are a developer of VG Music Studio, please check the code in DSPADPCM.cs\n" + - // "and verify the SWD file in a hex editor to make sure it's being read correctly.\n" + - // "\n" + - // "Call Stack:"); - //} - - //samples[i].DSPADPCM.GetSamples(samples[i].DSPADPCM.Info, samples[i].DSPADPCM.Info.ea); - //samples[i].Data = samples[i].DSPADPCM!.Data; - //Array.Resize(ref samples[i].Data, (int)((wavInfo.LoopStart + wavInfo.LoopEnd) / 6)); - //samples[i].Data = samples[i].DSPADPCM.Data; - break; - } - default: - { - throw new NotImplementedException("This Digital Sound Elements type has not yet been implemented."); - } - } - } + WavInfo wavInfo = new WavInfo(r, swd); + switch (Type) + { + case "swdm": + { + throw new NotImplementedException("This Digital Sound Elements type has not yet been implemented."); + } + + case "swdl": + { + samples[i] = new SampleBlock + { + WavInfo = wavInfo, + Data = new byte[(int)((wavInfo.LoopStart + wavInfo.LoopEnd) * 4)], + }; + r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; + r.ReadBytes(samples[i].Data); + + break; + } + + case "swdb": + { + samples[i] = new SampleBlock + { + WavInfo = wavInfo, + //Data = new byte[samples[i].DSPADPCM!.Info.num_samples] + }; + r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; + samples[i].DSPADPCM = new DSPADPCM(r); + Span data = new short[samples[i].DSPADPCM!.Info.num_samples / 2]; + data = DSPADPCM.DSPADPCMToPCM16(samples[i].DSPADPCM!.Data, samples[i].DSPADPCM!.Info.num_samples, samples[i].DSPADPCM!.Info); + samples[i].Data = new byte[samples[i].DSPADPCM!.Info.ea]; + //wavInfo.LoopStart = wavInfo.LoopStart / 4; + //wavInfo.LoopEnd = wavInfo.LoopEnd / 4; + //samples[i].Data16Bit = new short[(int)((wavInfo.LoopStart + wavInfo.LoopEnd) / 4) / 4 + 85]; + //DSPADPCM.Decode(samples[i].DSPADPCM!.Data, samples[i].Data16Bit, ref samples[i].DSPADPCM!.Info, samples[i].DSPADPCM!.Info.num_samples); + int e = 0; + for (int d = 0; d < samples[i].DSPADPCM!.Info.num_adpcm_nibbles; d++) + { + samples[i].Data![e] = (byte)(data[d] >> 8); + if (e < samples[i].Data!.Length) { e += 1; } + if (e >= samples[i].Data!.Length) { break; } + samples[i].Data![e] = (byte)(data[d]); + if (e < samples[i].Data!.Length) { e += 1; } + if (e >= samples[i].Data!.Length) { break; } + } + + // Trying to implement an error message that informs anyone of a EndOfStreamException caused by a different encoding type. + //if (swd.PcmdDataOffset + samples[i].WavInfo!.SampleOffset + (samples[i].DSPADPCM!.NumADPCMNibbles / 2) + 9 > swd.EodChunkOffset) + //{ + // throw new EndOfStreamException("End of Stream Exception:\n" + + // "The number of ADPCM nibbles, divided by 2, plus 9 bytes, reads the sample data beyond this SWD.\n" + + // "\n" + + // "This is because VG Music Studio is incorrectly reading the actual size of the DSP-ADPCM sample data.\n" + + // "\n" + + // "If you are a developer of VG Music Studio, please check the code in DSPADPCM.cs\n" + + // "and verify the SWD file in a hex editor to make sure it's being read correctly.\n" + + // "\n" + + // "Call Stack:"); + //} + + //samples[i].DSPADPCM.GetSamples(samples[i].DSPADPCM.Info, samples[i].DSPADPCM.Info.ea); + //samples[i].Data = samples[i].DSPADPCM!.Data; + //Array.Resize(ref samples[i].Data, (int)((wavInfo.LoopStart + wavInfo.LoopEnd) / 6)); + //samples[i].Data = samples[i].DSPADPCM.Data; + break; + } + default: + { + throw new NotImplementedException("This Digital Sound Elements type has not yet been implemented."); + } + } + } } return samples; } } - #endregion + #endregion - #region ProgramBank and KeyGroup - private static ProgramBank? ReadPrograms(EndianBinaryReader r, int numPRGISlots, SWD swd) + #region ProgramBank and KeyGroup + private static ProgramBank? ReadPrograms(EndianBinaryReader r, int numPRGISlots, SWD swd) { long chunkOffset = swd.PrgiChunkOffset = FindChunk(r, "prgi"); - if (chunkOffset == -1) + if (chunkOffset == -1) { return null; } - swd.PrgiInfo = new ChunkHeader(r, chunkOffset, swd); - long dataOffset = swd.PrgiDataOffset = chunkOffset + 0x10; - var programInfos = new ProgramInfo[numPRGISlots]; + swd.PrgiInfo = new ChunkHeader(r, chunkOffset, swd); + long dataOffset = swd.PrgiDataOffset = chunkOffset + 0x10; + var programInfos = new ProgramInfo[numPRGISlots]; for (int i = 0; i < programInfos.Length; i++) { r.Stream.Position = dataOffset + (2 * i); - ushort offset = r.ReadUInt16(); - if (offset != 0) + ushort offset = r.ReadUInt16(); + if (offset != 0) { r.Stream.Position = offset + dataOffset; - programInfos[i] = new ProgramInfo(r, swd); - } + programInfos[i] = new ProgramInfo(r, swd); + } } return new ProgramBank { @@ -1020,20 +1024,20 @@ private SampleBlock[] ReadSamples(EndianBinaryReader r, int numWAVISlots, SWD sw private static KeyGroup[] ReadKeyGroups(EndianBinaryReader r, SWD swd) { long chunkOffset = swd.KgrpChunkOffset = FindChunk(r, "kgrp"); - if (chunkOffset == -1) + if (chunkOffset == -1) { return Array.Empty(); } - ChunkHeader info = swd.KgrpInfo = new ChunkHeader(r, chunkOffset, swd); - swd.KgrpDataOffset = chunkOffset + 0x10; - r.Stream.Position = swd.KgrpDataOffset; - var keyGroups = new KeyGroup[info.ChunkEnd / 8]; // 8 is the size of a KeyGroup - for (int i = 0; i < keyGroups.Length; i++) + ChunkHeader info = swd.KgrpInfo = new ChunkHeader(r, chunkOffset, swd); + swd.KgrpDataOffset = chunkOffset + 0x10; + r.Stream.Position = swd.KgrpDataOffset; + var keyGroups = new KeyGroup[info.ChunkEnd / 8]; // 8 is the size of a KeyGroup + for (int i = 0; i < keyGroups.Length; i++) { keyGroups[i] = new KeyGroup(r, swd); - } + } return keyGroups; } - #endregion + #endregion } diff --git a/VG Music Studio - Core/Properties/Strings.Designer.cs b/VG Music Studio - Core/Properties/Strings.Designer.cs index 0b12484..b3841cc 100644 --- a/VG Music Studio - Core/Properties/Strings.Designer.cs +++ b/VG Music Studio - Core/Properties/Strings.Designer.cs @@ -484,7 +484,7 @@ public static string MenuOpenSDAT { } /// - /// Looks up a localized string similar to Open SMD Folder. + /// Looks up a localized string similar to Open Folder Containing SMD Files. /// public static string MenuOpenSMD { get { @@ -493,7 +493,7 @@ public static string MenuOpenSMD { } /// - /// Looks up a localized string similar to Open SWD File. + /// Looks up a localized string similar to Open Main SWD File. /// public static string MenuOpenSWD { get { diff --git a/VG Music Studio - Core/Properties/Strings.resx b/VG Music Studio - Core/Properties/Strings.resx index c48ae06..ab98afd 100644 --- a/VG Music Studio - Core/Properties/Strings.resx +++ b/VG Music Studio - Core/Properties/Strings.resx @@ -205,7 +205,7 @@ End Current Playlist - Open SMD Folder + Open Folder Containing SMD Files Open GBA ROM (AlphaDream) @@ -376,6 +376,6 @@ Open DSE Files - Open SWD File + Open Main SWD File \ No newline at end of file From b1cf476995a70df044e21b14b8979cb407a57ed3 Mon Sep 17 00:00:00 2001 From: PlatinumLucario Date: Tue, 21 Nov 2023 03:05:05 +1100 Subject: [PATCH 5/9] Merging upstream with this branch --- .gitignore | 9 +- VG Music Studio - Core/ADPCMDecoder.cs | 94 -- VG Music Studio - Core/Codec/ADPCMDecoder.cs | 94 ++ VG Music Studio - Core/Codec/CodecEnums.cs | 180 ++ VG Music Studio - Core/Codec/DSPADPCM.cs | 1446 +++++++++++++++++ VG Music Studio - Core/Codec/LayoutEnums.cs | 59 + VG Music Studio - Core/Codec/MetaEnums.cs | 479 ++++++ VG Music Studio - Core/NDS/DSE/DSEChannel.cs | 308 ++-- VG Music Studio - Core/NDS/DSE/DSECommands.cs | 8 + VG Music Studio - Core/NDS/DSE/DSEConfig.cs | 42 +- VG Music Studio - Core/NDS/DSE/DSEEngine.cs | 8 +- VG Music Studio - Core/NDS/DSE/DSEEnums.cs | 7 +- .../NDS/DSE/DSEExceptions.cs | 4 +- .../NDS/DSE/DSELoadedSong.cs | 34 +- .../NDS/DSE/DSELoadedSong_Events.cs | 1070 +++++++++--- .../NDS/DSE/DSELoadedSong_Runtime.cs | 2 +- VG Music Studio - Core/NDS/DSE/DSEPlayer.cs | 9 +- VG Music Studio - Core/NDS/DSE/SMD.cs | 103 +- VG Music Studio - Core/NDS/DSE/SWD.cs | 1022 +++++++++--- .../Properties/Strings.Designer.cs | 29 +- .../Properties/Strings.resx | 13 +- .../EndianBinaryPrimitivesExtras.cs | 333 ++++ .../EndianBinaryReaderExtras.cs | 98 ++ .../EndianBinaryWriterExtras.cs | 100 ++ VG Music Studio - Core/Wii/WiiUtils.cs | 7 + VG Music Studio - WinForms/MainForm.cs | 11 +- VG Music Studio.sln | 12 + 27 files changed, 4877 insertions(+), 704 deletions(-) delete mode 100644 VG Music Studio - Core/ADPCMDecoder.cs create mode 100644 VG Music Studio - Core/Codec/ADPCMDecoder.cs create mode 100644 VG Music Studio - Core/Codec/CodecEnums.cs create mode 100644 VG Music Studio - Core/Codec/DSPADPCM.cs create mode 100644 VG Music Studio - Core/Codec/LayoutEnums.cs create mode 100644 VG Music Studio - Core/Codec/MetaEnums.cs create mode 100644 VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryPrimitivesExtras.cs create mode 100644 VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryReaderExtras.cs create mode 100644 VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryWriterExtras.cs create mode 100644 VG Music Studio - Core/Wii/WiiUtils.cs diff --git a/.gitignore b/.gitignore index 80921d3..b8718c9 100644 --- a/.gitignore +++ b/.gitignore @@ -259,4 +259,11 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc +/ObjectListView +/.vscode +/VG Music Studio - Core/GBA/AlphaDream/AlphaDreamChannel.cs +/VG Music Studio - Core/GBA/MP2K/MP2KChannel.cs +/VG Music Studio - WinForms/ObjectListView +/VG Music Studio - WinForms/API +/VG Music Studio - MIDI diff --git a/VG Music Studio - Core/ADPCMDecoder.cs b/VG Music Studio - Core/ADPCMDecoder.cs deleted file mode 100644 index 894ea76..0000000 --- a/VG Music Studio - Core/ADPCMDecoder.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; - -namespace Kermalis.VGMusicStudio.Core; - -internal struct ADPCMDecoder -{ - private static ReadOnlySpan IndexTable => new short[8] - { - -1, -1, -1, -1, 2, 4, 6, 8, - }; - private static ReadOnlySpan StepTable => new short[89] - { - 00007, 00008, 00009, 00010, 00011, 00012, 00013, 00014, - 00016, 00017, 00019, 00021, 00023, 00025, 00028, 00031, - 00034, 00037, 00041, 00045, 00050, 00055, 00060, 00066, - 00073, 00080, 00088, 00097, 00107, 00118, 00130, 00143, - 00157, 00173, 00190, 00209, 00230, 00253, 00279, 00307, - 00337, 00371, 00408, 00449, 00494, 00544, 00598, 00658, - 00724, 00796, 00876, 00963, 01060, 01166, 01282, 01411, - 01552, 01707, 01878, 02066, 02272, 02499, 02749, 03024, - 03327, 03660, 04026, 04428, 04871, 05358, 05894, 06484, - 07132, 07845, 08630, 09493, 10442, 11487, 12635, 13899, - 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, - 32767, - }; - - private byte[] _data; - public short LastSample; - public short StepIndex; - public int DataOffset; - public bool OnSecondNibble; - - public void Init(byte[] data) - { - _data = data; - LastSample = (short)(data[0] | (data[1] << 8)); - StepIndex = (short)((data[2] | (data[3] << 8)) & 0x7F); - DataOffset = 4; - OnSecondNibble = false; - } - - public static short[] ADPCMToPCM16(byte[] data) - { - var decoder = new ADPCMDecoder(); - decoder.Init(data); - - short[] buffer = new short[(data.Length - 4) * 2]; - for (int i = 0; i < buffer.Length; i++) - { - buffer[i] = decoder.GetSample(); - } - return buffer; - } - - public short GetSample() - { - int val = (_data[DataOffset] >> (OnSecondNibble ? 4 : 0)) & 0xF; - short step = StepTable[StepIndex]; - int diff = - (step / 8) + - (step / 4 * (val & 1)) + - (step / 2 * ((val >> 1) & 1)) + - (step * ((val >> 2) & 1)); - - int a = (diff * ((((val >> 3) & 1) == 1) ? -1 : 1)) + LastSample; - if (a < short.MinValue) - { - a = short.MinValue; - } - else if (a > short.MaxValue) - { - a = short.MaxValue; - } - LastSample = (short)a; - - a = StepIndex + IndexTable[val & 7]; - if (a < 0) - { - a = 0; - } - else if (a > 88) - { - a = 88; - } - StepIndex = (short)a; - - if (OnSecondNibble) - { - DataOffset++; - } - OnSecondNibble = !OnSecondNibble; - return LastSample; - } -} diff --git a/VG Music Studio - Core/Codec/ADPCMDecoder.cs b/VG Music Studio - Core/Codec/ADPCMDecoder.cs new file mode 100644 index 0000000..ad23027 --- /dev/null +++ b/VG Music Studio - Core/Codec/ADPCMDecoder.cs @@ -0,0 +1,94 @@ +using System; + +namespace Kermalis.VGMusicStudio.Core.Codec; + +internal struct ADPCMDecoder +{ + private static ReadOnlySpan IndexTable => new short[8] + { + -1, -1, -1, -1, 2, 4, 6, 8, + }; + private static ReadOnlySpan StepTable => new short[89] + { + 00007, 00008, 00009, 00010, 00011, 00012, 00013, 00014, + 00016, 00017, 00019, 00021, 00023, 00025, 00028, 00031, + 00034, 00037, 00041, 00045, 00050, 00055, 00060, 00066, + 00073, 00080, 00088, 00097, 00107, 00118, 00130, 00143, + 00157, 00173, 00190, 00209, 00230, 00253, 00279, 00307, + 00337, 00371, 00408, 00449, 00494, 00544, 00598, 00658, + 00724, 00796, 00876, 00963, 01060, 01166, 01282, 01411, + 01552, 01707, 01878, 02066, 02272, 02499, 02749, 03024, + 03327, 03660, 04026, 04428, 04871, 05358, 05894, 06484, + 07132, 07845, 08630, 09493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, + 32767, + }; + + private byte[] _data; + public short LastSample; + public short StepIndex; + public int DataOffset; + public bool OnSecondNibble; + + public void Init(byte[] data) + { + _data = data; + LastSample = (short)(data[0] | (data[1] << 8)); + StepIndex = (short)((data[2] | (data[3] << 8)) & 0x7F); + DataOffset = 4; + OnSecondNibble = false; + } + + public static short[] ADPCMToPCM16(byte[] data) + { + var decoder = new ADPCMDecoder(); + decoder.Init(data); + + short[] buffer = new short[(data.Length - 4) * 2]; + for (int i = 0; i < buffer.Length; i++) + { + buffer[i] = decoder.GetSample(); + } + return buffer; + } + + public short GetSample() + { + int val = (_data[DataOffset] >> (OnSecondNibble ? 4 : 0)) & 0xF; + short step = StepTable[StepIndex]; + int diff = + (step / 8) + + (step / 4 * (val & 1)) + + (step / 2 * ((val >> 1) & 1)) + + (step * ((val >> 2) & 1)); + + int a = (diff * ((((val >> 3) & 1) == 1) ? -1 : 1)) + LastSample; + if (a < short.MinValue) + { + a = short.MinValue; + } + else if (a > short.MaxValue) + { + a = short.MaxValue; + } + LastSample = (short)a; + + a = StepIndex + IndexTable[val & 7]; + if (a < 0) + { + a = 0; + } + else if (a > 88) + { + a = 88; + } + StepIndex = (short)a; + + if (OnSecondNibble) + { + DataOffset++; + } + OnSecondNibble = !OnSecondNibble; + return LastSample; + } +} diff --git a/VG Music Studio - Core/Codec/CodecEnums.cs b/VG Music Studio - Core/Codec/CodecEnums.cs new file mode 100644 index 0000000..1e916a8 --- /dev/null +++ b/VG Music Studio - Core/Codec/CodecEnums.cs @@ -0,0 +1,180 @@ +namespace Kermalis.VGMusicStudio.Core.Codec; + +/* This code has been copied directly from vgmstream.h in VGMStream's repository * + * and modified into C# code to work with VGMS. Link to its repository can be * + * found here: https://github.com/vgmstream/vgmstream */ +public enum CodecType +{ + codec_SILENCE, /* generates silence */ + + /* PCM */ + codec_PCM16LE, /* little endian 16-bit PCM */ + codec_PCM16BE, /* big endian 16-bit PCM */ + codec_PCM16_int, /* 16-bit PCM with sample-level interleave (for blocks) */ + + codec_PCM8, /* 8-bit PCM */ + codec_PCM8_int, /* 8-bit PCM with sample-level interleave (for blocks) */ + codec_PCM8_U, /* 8-bit PCM, unsigned (0x80 = 0) */ + codec_PCM8_U_int, /* 8-bit PCM, unsigned (0x80 = 0) with sample-level interleave (for blocks) */ + codec_PCM8_SB, /* 8-bit PCM, sign bit (others are 2's complement) */ + codec_PCM4, /* 4-bit PCM, signed */ + codec_PCM4_U, /* 4-bit PCM, unsigned */ + + codec_ULAW, /* 8-bit u-Law (non-linear PCM) */ + codec_ULAW_int, /* 8-bit u-Law (non-linear PCM) with sample-level interleave (for blocks) */ + codec_ALAW, /* 8-bit a-Law (non-linear PCM) */ + + codec_PCMFLOAT, /* 32-bit float PCM */ + codec_PCM24LE, /* little endian 24-bit PCM */ + codec_PCM24BE, /* big endian 24-bit PCM */ + + /* ADPCM */ + codec_CRI_ADX, /* CRI ADX */ + codec_CRI_ADX_fixed, /* CRI ADX, encoding type 2 with fixed coefficients */ + codec_CRI_ADX_exp, /* CRI ADX, encoding type 4 with exponential scale */ + codec_CRI_ADX_enc_8, /* CRI ADX, type 8 encryption (God Hand) */ + codec_CRI_ADX_enc_9, /* CRI ADX, type 9 encryption (PSO2) */ + + codec_NGC_DSP, /* Nintendo DSP ADPCM */ + codec_NGC_DSP_subint, /* Nintendo DSP ADPCM with frame subinterframe */ + codec_NGC_DTK, /* Nintendo DTK ADPCM (hardware disc), also called TRK or ADP */ + codec_NGC_AFC, /* Nintendo AFC ADPCM */ + codec_VADPCM, /* Silicon Graphics VADPCM */ + + codec_G721, /* CCITT G.721 */ + + codec_XA, /* CD-ROM XA 4-bit */ + codec_XA8, /* CD-ROM XA 8-bit */ + codec_XA_EA, /* EA's Saturn XA (not to be confused with EA-XA) */ + codec_PSX, /* Sony PS ADPCM (VAG) */ + codec_PSX_badflags, /* Sony PS ADPCM with custom flag byte */ + codec_PSX_cfg, /* Sony PS ADPCM with configurable frame size (int math) */ + codec_PSX_pivotal, /* Sony PS ADPCM with configurable frame size (float math) */ + codec_HEVAG, /* Sony PSVita ADPCM */ + + codec_EA_XA, /* Electronic Arts EA-XA ADPCM v1 (stereo) aka "EA ADPCM" */ + codec_EA_XA_int, /* Electronic Arts EA-XA ADPCM v1 (mono/interleave) */ + codec_EA_XA_V2, /* Electronic Arts EA-XA ADPCM v2 */ + codec_MAXIS_XA, /* Maxis EA-XA ADPCM */ + codec_EA_XAS_V0, /* Electronic Arts EA-XAS ADPCM v0 */ + codec_EA_XAS_V1, /* Electronic Arts EA-XAS ADPCM v1 */ + + codec_IMA, /* IMA ADPCM (stereo or mono, low nibble first) */ + codec_IMA_int, /* IMA ADPCM (mono/interleave, low nibble first) */ + codec_DVI_IMA, /* DVI IMA ADPCM (stereo or mono, high nibble first) */ + codec_DVI_IMA_int, /* DVI IMA ADPCM (mono/interleave, high nibble first) */ + codec_NW_IMA, + codec_SNDS_IMA, /* Heavy Iron Studios .snds IMA ADPCM */ + codec_QD_IMA, + codec_WV6_IMA, /* Gorilla Systems WV6 4-bit IMA ADPCM */ + codec_HV_IMA, /* High Voltage 4-bit IMA ADPCM */ + codec_FFTA2_IMA, /* Final Fantasy Tactics A2 4-bit IMA ADPCM */ + codec_BLITZ_IMA, /* Blitz Games 4-bit IMA ADPCM */ + + codec_MS_IMA, /* Microsoft IMA ADPCM */ + codec_MS_IMA_mono, /* Microsoft IMA ADPCM (mono/interleave) */ + codec_XBOX_IMA, /* XBOX IMA ADPCM */ + codec_XBOX_IMA_mch, /* XBOX IMA ADPCM (multichannel) */ + codec_XBOX_IMA_int, /* XBOX IMA ADPCM (mono/interleave) */ + codec_NDS_IMA, /* IMA ADPCM w/ NDS layout */ + codec_DAT4_IMA, /* Eurocom 'DAT4' IMA ADPCM */ + codec_RAD_IMA, /* Radical IMA ADPCM */ + codec_RAD_IMA_mono, /* Radical IMA ADPCM (mono/interleave) */ + codec_APPLE_IMA4, /* Apple Quicktime IMA4 */ + codec_FSB_IMA, /* FMOD's FSB multichannel IMA ADPCM */ + codec_WWISE_IMA, /* Audiokinetic Wwise IMA ADPCM */ + codec_REF_IMA, /* Reflections IMA ADPCM */ + codec_AWC_IMA, /* Rockstar AWC IMA ADPCM */ + codec_UBI_IMA, /* Ubisoft IMA ADPCM */ + codec_UBI_SCE_IMA, /* Ubisoft SCE IMA ADPCM */ + codec_H4M_IMA, /* H4M IMA ADPCM (stereo or mono, high nibble first) */ + codec_MTF_IMA, /* Capcom MT Framework IMA ADPCM */ + codec_CD_IMA, /* Crystal Dynamics IMA ADPCM */ + + codec_MSADPCM, /* Microsoft ADPCM (stereo/mono) */ + codec_MSADPCM_int, /* Microsoft ADPCM (mono) */ + codec_MSADPCM_ck, /* Microsoft ADPCM (Cricket Audio variation) */ + codec_WS, /* Westwood Studios VBR ADPCM */ + + codec_AICA, /* Yamaha AICA ADPCM (stereo) */ + codec_AICA_int, /* Yamaha AICA ADPCM (mono/interleave) */ + codec_CP_YM, /* Capcom's Yamaha ADPCM (stereo/mono) */ + codec_ASKA, /* Aska ADPCM */ + codec_NXAP, /* NXAP ADPCM */ + + codec_TGC, /* Tiger Game.com 4-bit ADPCM */ + + codec_PSX_DSE_SQUARESOFT, /* SquareSoft Digital Sound Elements 16-bit PCM (For PSX) */ + codec_PS2_DSE_PROCYON, /* Procyon Studio Digital Sound Elements ADPCM (PS2 Version, encoded with VAG-ADPCM) */ + codec_NDS_DSE_PROCYON, /* Procyon Studio Digital Sound Elements ADPCM (NDS Version, encoded with IMA-ADPCM) */ + codec_WII_DSE_PROCYON, /* Procyon Studio Digital Sound Elements ADPCM (Wii Version, encoded with DSP-ADPCM) */ + codec_L5_555, /* Level-5 0x555 ADPCM */ + codec_LSF, /* lsf ADPCM (Fastlane Street Racing iPhone)*/ + codec_MTAF, /* Konami MTAF ADPCM */ + codec_MTA2, /* Konami MTA2 ADPCM */ + codec_MC3, /* Paradigm MC3 3-bit ADPCM */ + codec_FADPCM, /* FMOD FADPCM 4-bit ADPCM */ + codec_ASF, /* Argonaut ASF 4-bit ADPCM */ + codec_DSA, /* Ocean DSA 4-bit ADPCM */ + codec_XMD, /* Konami XMD 4-bit ADPCM */ + codec_TANTALUS, /* Tantalus 4-bit ADPCM */ + codec_PCFX, /* PC-FX 4-bit ADPCM */ + codec_OKI16, /* OKI 4-bit ADPCM with 16-bit output and modified expand */ + codec_OKI4S, /* OKI 4-bit ADPCM with 16-bit output and cuadruple step */ + codec_PTADPCM, /* Platinum 4-bit ADPCM */ + codec_IMUSE, /* LucasArts iMUSE Variable ADPCM */ + codec_COMPRESSWAVE, /* CompressWave Huffman ADPCM */ + + /* others */ + codec_SDX2, /* SDX2 2:1 Squareroot-Delta-Exact compression DPCM */ + codec_SDX2_int, /* SDX2 2:1 Squareroot-Delta-Exact compression with sample-level interleave */ + codec_CBD2, /* CBD2 2:1 Cuberoot-Delta-Exact compression DPCM */ + codec_CBD2_int, /* CBD2 2:1 Cuberoot-Delta-Exact compression, with sample-level interleave */ + codec_SASSC, /* Activision EXAKT SASSC 8-bit DPCM */ + codec_DERF, /* DERF 8-bit DPCM */ + codec_WADY, /* WADY 8-bit DPCM */ + codec_NWA, /* VisualArt's NWA DPCM */ + codec_ACM, /* InterPlay ACM */ + codec_CIRCUS_ADPCM, /* Circus 8-bit ADPCM */ + codec_UBI_ADPCM, /* Ubisoft 4/6-bit ADPCM */ + + codec_EA_MT, /* Electronic Arts MicroTalk (linear-predictive speech codec) */ + codec_CIRCUS_VQ, /* Circus VQ */ + codec_RELIC, /* Relic Codec (DCT-based) */ + codec_CRI_HCA, /* CRI High Compression Audio (MDCT-based) */ + codec_TAC, /* tri-Ace Codec (MDCT-based) */ + codec_ICE_RANGE, /* Inti Creates "range" codec */ + codec_ICE_DCT, /* Inti Creates "DCT" codec */ + + + codec_OGG_VORBIS, /* Xiph Vorbis with Ogg layer (MDCT-based) */ + codec_VORBIS_custom, /* Xiph Vorbis with custom layer (MDCT-based) */ + + + codec_MPEG_custom, /* MPEG audio with custom features (MDCT-based) */ + codec_MPEG_ealayer3, /* EALayer3, custom MPEG frames */ + codec_MPEG_layer1, /* MP1 MPEG audio (MDCT-based) */ + codec_MPEG_layer2, /* MP2 MPEG audio (MDCT-based) */ + codec_MPEG_layer3, /* MP3 MPEG audio (MDCT-based) */ + + + codec_G7221C, /* ITU G.722.1 annex C (Polycom Siren 14) */ + + + codec_G719, /* ITU G.719 annex B (Polycom Siren 22) */ + + + codec_MP4_AAC, /* AAC (MDCT-based) */ + + + codec_ATRAC9, /* Sony ATRAC9 (MDCT-based) */ + + + codec_CELT_FSB, /* Custom Xiph CELT (MDCT-based) */ + + + codec_SPEEX, /* Custom Speex (CELP-based) */ + + + codec_FFmpeg, /* Formats handled by FFmpeg (ATRAC3, XMA, AC3, etc) */ +} \ No newline at end of file diff --git a/VG Music Studio - Core/Codec/DSPADPCM.cs b/VG Music Studio - Core/Codec/DSPADPCM.cs new file mode 100644 index 0000000..7afad52 --- /dev/null +++ b/VG Music Studio - Core/Codec/DSPADPCM.cs @@ -0,0 +1,1446 @@ +using Kermalis.EndianBinaryIO; +using Kermalis.VGMusicStudio.Core.Util; +using System; +using System.Diagnostics; + +namespace Kermalis.VGMusicStudio.Core.Codec +{ + internal sealed class DSPADPCM + { + public static short Min = short.MinValue; + public static short Max = short.MaxValue; + + public static double[] Tvec = new double[3]; + + public DSPADPCMInfo Info; + public byte[] Data; + public uint NumADPCMNibbles; + //public static short[]? DataOutput; + + public static class DSPADPCMConstants + { + public const int BytesPerFrame = 8; + public const int SamplesPerFrame = 14; + public const int NibblesPerFrame = 16; + } + + public DSPADPCM(EndianBinaryReader r) + { + Info = new DSPADPCMInfo(r); + NumADPCMNibbles = Info.num_adpcm_nibbles; + Data = new byte[(Info.num_adpcm_nibbles / 2) + 9]; + + r.ReadBytes(Data); + r.Stream.Align(16); + return; + } + + #region DSP-ADPCM Info + public interface IDSPADPCMInfo + { + public short[] coef { get; } + public ushort gain { get; } + public ushort pred_scale { get; } + public short yn1 { get; } + public short yn2 { get; } + + public ushort loop_pred_scale { get; } + public short loop_yn1 { get; } + public short loop_yn2 { get; } + } + + public class DSPADPCMInfo : IDSPADPCMInfo + { + public uint num_samples { get; set; } + public uint num_adpcm_nibbles { get; set; } + public uint sample_rate { get; set; } + public ushort loop_flag { get; set; } + public ushort format { get; set; } + public uint sa { get; set; } + public uint ea { get; set; } + public uint ca { get; set; } + public short[] coef { get; set; } + public ushort gain { get; set; } + public ushort pred_scale { get; set; } + public short yn1 { get; set; } + public short yn2 { get; set; } + + public ushort loop_pred_scale { get; set; } + public short loop_yn1 { get; set; } + public short loop_yn2 { get; set; } + public ushort[] padding { get; set; } + + public DSPADPCMInfo(EndianBinaryReader r) + { + num_samples = r.ReadUInt32(); + + num_adpcm_nibbles = r.ReadUInt32(); + + sample_rate = r.ReadUInt32(); + + loop_flag = r.ReadUInt16(); + + format = r.ReadUInt16(); + + sa = r.ReadUInt32(); + + ea = r.ReadUInt32(); + + ca = r.ReadUInt32(); + + coef = new short[16]; + r.ReadInt16s(coef); + + gain = r.ReadUInt16(); + + pred_scale = r.ReadUInt16(); + + yn1 = r.ReadInt16(); + + yn2 = r.ReadInt16(); + + loop_pred_scale = r.ReadUInt16(); + + loop_yn1 = r.ReadInt16(); + + loop_yn2 = r.ReadInt16(); + + padding = new ushort[11]; + r.ReadUInt16s(padding); + } + } + #endregion + + #region DSP-ADPCM Convert + + //public object GetSamples(DSPADPCMInfo cxt, uint loopEnd) + //{ + // return DSPADPCMToPCM16(Data, loopEnd, cxt); + //} + + public static Span DSPADPCMToPCM16(Span dspadpcm, uint numSamples, DSPADPCMInfo cxt) + { + Span dataOutput = new short[dspadpcm.Length]; // This is the new output data that's converted to PCM16 + Span dataIndex = new short[numSamples]; + for (int i = 0; i < dataOutput.Length; i++) + { + dataOutput[i] = dataIndex[i]; + Decode(dspadpcm, dataOutput, ref cxt, numSamples); + } + return dataOutput; + } + #endregion + + #region DSP-ADPCM Encode + public static void Encode(short[] src, byte[] dst, DSPADPCMInfo cxt, uint samples) + { + short[] coefs = cxt.coef; + CorrelateCoefs(src, samples, coefs); + + int frameCount = (int)((samples / DSPADPCMConstants.SamplesPerFrame) + (samples % DSPADPCMConstants.SamplesPerFrame)); + + short[] pcm = src; + byte[] adpcm = dst; + short[] pcmFrame = new short[DSPADPCMConstants.SamplesPerFrame + 2]; + byte[] adpcmFrame = new byte[DSPADPCMConstants.BytesPerFrame]; + + short srcIndex = 0; + short dstIndex = 0; + + for (int i = 0; i < frameCount; ++i, pcm[srcIndex] += DSPADPCMConstants.SamplesPerFrame, adpcm[srcIndex] += DSPADPCMConstants.BytesPerFrame) + { + coefs = new short[2 + 0]; + + DSPEncodeFrame(pcmFrame, DSPADPCMConstants.SamplesPerFrame, adpcmFrame, coefs); + + pcmFrame[0] = pcmFrame[14]; + pcmFrame[1] = pcmFrame[15]; + } + + cxt.gain = 0; + cxt.pred_scale = dst[dstIndex++]; + cxt.yn1 = 0; + cxt.yn2 = 0; + } + + public static void InnerProductMerge(double[] vecOut, short[] pcmBuf) + { + pcmBuf = new short[14]; + vecOut = Tvec; + + for (int i = 0; i <= 2; i++) + { + vecOut[i] = 0.0f; + for (int x = 0; x < 14; x++) + vecOut[i] -= pcmBuf[x - i] * pcmBuf[x]; + + } + } + + public static void OuterProductMerge(double[] mtxOut, short[] pcmBuf) + { + pcmBuf = new short[14]; + mtxOut[3] = Tvec[3]; + + for (int x = 1; x <= 2; x++) + for (int y = 1; y <= 2; y++) + { + mtxOut[x] = 0.0; + mtxOut[y] = 0.0; + for (int z = 0; z < 14; z++) + mtxOut[x + y] += pcmBuf[z - x] * pcmBuf[z - y]; + } + } + + public static bool AnalyzeRanges(double[] mtx, int[] vecIdxsOut) + { + mtx[3] = Tvec[3]; + double[] recips = new double[3]; + double val, tmp, min, max; + + /* Get greatest distance from zero */ + for (int x = 1; x <= 2; x++) + { + val = Math.Max(Math.Abs(mtx[x] + mtx[1]), Math.Abs(mtx[x] + mtx[2])); + if (val < double.Epsilon) + return true; + + recips[x] = 1.0 / val; + } + + int maxIndex = 0; + for (int i = 1; i <= 2; i++) + { + for (int x = 1; x < i; x++) + { + tmp = mtx[x] + mtx[i]; + for (int y = 1; y < x; y++) + tmp -= (mtx[x] + mtx[y]) * (mtx[y] + mtx[i]); + mtx[x + i] = tmp; + } + + val = 0.0; + for (int x = i; x <= 2; x++) + { + tmp = mtx[x] + mtx[i]; + for (int y = 1; y < i; y++) + tmp -= (mtx[x] + mtx[y]) * (mtx[y] + mtx[i]); + + mtx[x + i] = tmp; + tmp = Math.Abs(tmp) * recips[x]; + if (tmp >= val) + { + val = tmp; + maxIndex = x; + } + } + + if (maxIndex != i) + { + for (int y = 1; y <= 2; y++) + { + tmp = mtx[maxIndex] + mtx[y]; + mtx[maxIndex + y] = mtx[i] + mtx[y]; + mtx[i + y] = tmp; + } + recips[maxIndex] = recips[i]; + } + + vecIdxsOut[i] = maxIndex; + + if (mtx[i] + mtx[i] == 0.0) + return true; + + if (i != 2) + { + tmp = 1.0 / mtx[i] + mtx[i]; + for (int x = i + 1; x <= 2; x++) + mtx[x + i] *= tmp; + } + } + + /* Get range */ + min = 1.0e10; + max = 0.0; + for (int i = 1; i <= 2; i++) + { + tmp = Math.Abs(mtx[i] + mtx[i]); + if (tmp < min) + min = tmp; + if (tmp > max) + max = tmp; + } + + if (min / max < 1.0e-10) + return true; + + return false; + } + + public static void BidirectionalFilter(double[] mtx, int[] vecIdxs, double[] vecOut) + { + mtx[3] = Tvec[3]; + vecOut = Tvec; + double tmp; + + for (int i = 1, x = 0; i <= 2; i++) + { + int index = vecIdxs[i]; + tmp = vecOut[index]; + vecOut[index] = vecOut[i]; + if (x != 0) + for (int y = x; y <= i - 1; y++) + tmp -= vecOut[y] * mtx[i] + mtx[y]; + else if (tmp != 0.0) + x = i; + vecOut[i] = tmp; + } + + for (int i = 2; i > 0; i--) + { + tmp = vecOut[i]; + for (int y = i + 1; y <= 2; y++) + tmp -= vecOut[y] * mtx[i] + mtx[y]; + vecOut[i] = tmp / mtx[i] + mtx[i]; + } + + vecOut[0] = 1.0; + } + + public static bool QuadraticMerge(double[] inOutVec) + { + inOutVec = Tvec; + + double v0, v1, v2 = inOutVec[2]; + double tmp = 1.0 - (v2 * v2); + + if (tmp == 0.0) + return true; + + v0 = (inOutVec[0] - (v2 * v2)) / tmp; + v1 = (inOutVec[1] - (inOutVec[1] * v2)) / tmp; + + inOutVec[0] = v0; + inOutVec[1] = v1; + + return Math.Abs(v1) > 1.0; + } + + public static void FinishRecord(double[] vIn, double[] vOut) + { + vIn = Tvec; + vOut = Tvec; + for (int z = 1; z <= 2; z++) + { + if (vIn[z] >= 1.0) + vIn[z] = 0.9999999999; + + else if (vIn[z] <= -1.0) + vIn[z] = -0.9999999999; + } + vOut[0] = 1.0; + vOut[1] = (vIn[2] * vIn[1]) + vIn[1]; + vOut[2] = vIn[2]; + } + + public static void MatrixFilter(double[] src, double[] dst) + { + src = Tvec; + dst = Tvec; + double[] mtx = new double[3]; + Tvec = mtx; + + mtx[2 + 0] = 1.0; + for (int i = 1; i <= 2; i++) + mtx[2 + i] = -src[i]; + + for (int i = 2; i > 0; i--) + { + double val = 1.0 - ((mtx[i] + mtx[i]) * (mtx[i] + mtx[i])); + for (int y = 1; y <= i; y++) + mtx[i - 1 + y] = (((mtx[i] + mtx[i]) * (mtx[i] + mtx[y])) + mtx[i] + mtx[y]) / val; + } + + dst[0] = 1.0; + for (int i = 1; i <= 2; i++) + { + dst[i] = 0.0; + for (int y = 1; y <= i; y++) + dst[i] += (mtx[i] + mtx[y]) * dst[i - y]; + } + } + + public static void MergeFinishRecord(double[] src, double[] dst) + { + src = Tvec; + dst = Tvec; + int dstIndex = 0; + double[] tmp = new double[dstIndex]; + double val = src[0]; + + dst[0] = 1.0; + for (int i = 1; i <= 2; i++) + { + double v2 = 0.0; + for (int y = 1; y < i; y++) + v2 += dst[y] * src[i - y]; + + if (val > 0.0) + dst[i] = -(v2 + src[i]) / val; + else + dst[i] = 0.0; + + tmp[i] = dst[i]; + + for (int y = 1; y < i; y++) + dst[y] += dst[i] * dst[i - y]; + + val *= 1.0 - (dst[i] * dst[i]); + } + + FinishRecord(tmp, dst); + } + + public static double ContrastVectors(double[] source1, double[] source2) + { + source1 = Tvec; + source2 = Tvec; + double val = (source2[2] * source2[1] + -source2[1]) / (1.0 - source2[2] * source2[2]); + double val1 = (source1[0] * source1[0]) + (source1[1] * source1[1]) + (source1[2] * source1[2]); + double val2 = (source1[0] * source1[1]) + (source1[1] * source1[2]); + double val3 = source1[0] * source1[2]; + return val1 + (2.0 * val * val2) + (2.0 * (-source2[1] * val + -source2[2]) * val3); + } + + public static void FilterRecords(double[] vecBest, int exp, double[] records, int recordCount) + { + vecBest[8] = Tvec[8]; + records = Tvec; + double[] bufferList = new double[8]; + bufferList[8] = Tvec[8]; + + int[] buffer1 = new int[8]; + double[] buffer2 = Tvec; + + int index; + double value, tempVal = 0; + + for (int x = 0; x < 2; x++) + { + for (int y = 0; y < exp; y++) + { + buffer1[y] = 0; + for (int i = 0; i <= 2; i++) + bufferList[y + i] = 0.0; + } + for (int z = 0; z < recordCount; z++) + { + index = 0; + value = 1.0e30; + for (int i = 0; i < exp; i++) + { + vecBest = new double[i]; + records = new double[z]; + tempVal = ContrastVectors(vecBest, records); + if (tempVal < value) + { + value = tempVal; + index = i; + } + } + buffer1[index]++; + MatrixFilter(records, buffer2); + for (int i = 0; i <= 2; i++) + bufferList[index + i] += buffer2[i]; + } + + for (int i = 0; i < exp; i++) + if (buffer1[i] > 0) + for (int y = 0; y <= 2; y++) + bufferList[i + y] /= buffer1[i]; + + for (int i = 0; i < exp; i++) + bufferList = new double[i]; + MergeFinishRecord(bufferList, vecBest); + } + } + + public static void CorrelateCoefs(short[] source, uint samples, short[] coefsOut) + { + int numFrames = (int)((samples + 13) / 14); + int frameSamples; + + short[] blockBuffer = new short[0x3800]; + short[] pcmHistBuffer = new short[2 + 14]; + + double[] vec1 = Tvec; + double[] vec2 = Tvec; + + double[] mtx = Tvec; + mtx[3] = Tvec[3]; + int[] vecIdxs = new int[3]; + + double[] records = new double[numFrames * 2]; + records = Tvec; + int recordCount = 0; + + double[] vecBest = new double[8]; + vecBest[8] = Tvec[8]; + + int sourceIndex = 0; + + /* Iterate though 1024-block frames */ + for (int x = (int)samples; x > 0;) + { + if (x > 0x3800) /* Full 1024-block frame */ + { + frameSamples = 0x3800; + x -= 0x3800; + } + else /* Partial frame */ + { + /* Zero lingering block samples */ + frameSamples = x; + for (int z = 0; z < 14 && z + frameSamples < 0x3800; z++) + blockBuffer[frameSamples + z] = 0; + x = 0; + } + + /* Copy (potentially non-frame-aligned PCM samples into aligned buffer) */ + source[sourceIndex] += (short)frameSamples; + + + for (int i = 0; i < frameSamples;) + { + for (int z = 0; z < 14; z++) + pcmHistBuffer[0 + z] = pcmHistBuffer[1 + z]; + for (int z = 0; z < 14; z++) + pcmHistBuffer[1 + z] = blockBuffer[i++]; + + pcmHistBuffer = new short[1]; + + InnerProductMerge(vec1, pcmHistBuffer); + if (Math.Abs(vec1[0]) > 10.0) + { + OuterProductMerge(mtx, pcmHistBuffer); + if (!AnalyzeRanges(mtx, vecIdxs)) + { + BidirectionalFilter(mtx, vecIdxs, vec1); + if (!QuadraticMerge(vec1)) + { + records = new double[recordCount]; + FinishRecord(vec1, records); + recordCount++; + } + } + } + } + } + + vec1[0] = 1.0; + vec1[1] = 0.0; + vec1[2] = 0.0; + + for (int z = 0; z < recordCount; z++) + { + records = new double[z]; + vecBest = new double[0]; + MatrixFilter(records, vecBest); + for (int y = 1; y <= 2; y++) + vec1[y] += vecBest[0] + vecBest[y]; + } + for (int y = 1; y <= 2; y++) + vec1[y] /= recordCount; + + MergeFinishRecord(vec1, vecBest); + + + int exp = 1; + for (int w = 0; w < 3;) + { + vec2[0] = 0.0; + vec2[1] = -1.0; + vec2[2] = 0.0; + for (int i = 0; i < exp; i++) + for (int y = 0; y <= 2; y++) + vecBest[exp + i + y] = (0.01 * vec2[y]) + vecBest[i] + vecBest[y]; + ++w; + exp = 1 << w; + FilterRecords(vecBest, exp, records, recordCount); + } + + /* Write output */ + for (int z = 0; z < 8; z++) + { + double d; + d = -vecBest[z] + vecBest[1] * 2048.0; + if (d > 0.0) + coefsOut[z * 2] = (d > 32767.0) ? (short)32767 : (short)Math.Round(d); + else + coefsOut[z * 2] = (d < -32768.0) ? (short)-32768 : (short)Math.Round(d); + + d = -vecBest[z] + vecBest[2] * 2048.0; + if (d > 0.0) + coefsOut[z * 2 + 1] = (d > 32767.0) ? (short)32767 : (short)Math.Round(d); + else + coefsOut[z * 2 + 1] = (d < -32768.0) ? (short)-32768 : (short)Math.Round(d); + } + } + + /* Make sure source includes the yn values (16 samples total) */ + public static void DSPEncodeFrame(short[] pcmInOut, int sampleCount, byte[] adpcmOut, short[] coefsIn) + { + pcmInOut = new short[16]; + adpcmOut = new byte[8]; + coefsIn = new short[8]; + coefsIn = new short[2]; + + int[] inSamples = new int[8]; + inSamples = new int[16]; + int[] outSamples = new int[8]; + outSamples = new int[14]; + + int bestIndex = 0; + + int[] scale = new int[8]; + double[] distAccum = new double[8]; + + /* Iterate through each coef set, finding the set with the smallest error */ + for (int i = 0; i < 8; i++) + { + int v1, v2, v3; + int distance, index; + + /* Set yn values */ + inSamples[i + 0] = pcmInOut[0]; + inSamples[i + 1] = pcmInOut[1]; + + /* Round and clamp samples for this coef set */ + distance = 0; + for (int s = 0; s < sampleCount; s++) + { + /* Multiply previous samples by coefs */ + inSamples[i + (s + 2)] = v1 = ((pcmInOut[s] * (coefsIn[i] + coefsIn[1])) + (pcmInOut[s + 1] * (coefsIn[i] + coefsIn[0]))) / 2048; + /* Subtract from current sample */ + v2 = pcmInOut[s + 2] - v1; + /* Clamp */ + v3 = (v2 >= 32767) ? 32767 : (v2 <= -32768) ? -32768 : v2; + /* Compare distance */ + if (Math.Abs(v3) > Math.Abs(distance)) + distance = v3; + } + + /* Set initial scale */ + for (scale[i] = 0; (scale[i] <= 12) && ((distance > 7) || (distance < -8)); scale[i]++, distance /= 2) + { + } + scale[i] = (scale[i] <= 1) ? -1 : scale[i] - 2; + + do + { + scale[i]++; + distAccum[i] = 0; + index = 0; + + for (int s = 0; s < sampleCount; s++) + { + /* Multiply previous */ + v1 = (((inSamples[i] + inSamples[s]) * (coefsIn[i] + coefsIn[1])) + ((inSamples[i] + inSamples[s + 1]) * (coefsIn[i] + coefsIn[0]))); + /* Evaluate from real sample */ + v2 = (pcmInOut[s + 2] << 11) - v1; + /* Round to nearest sample */ + v3 = (v2 > 0) ? (int)((double)v2 / (1 << scale[i]) / 2048 + 0.4999999f) : (int)((double)v2 / (1 << scale[i]) / 2048 - 0.4999999f); + + /* Clamp sample and set index */ + if (v3 < -8) + { + if (index < (v3 = -8 - v3)) + index = v3; + v3 = -8; + } + else if (v3 > 7) + { + if (index < (v3 -= 7)) + index = v3; + v3 = 7; + } + + /* Store result */ + outSamples[i + s] = v3; + + /* Round and expand */ + v1 = (v1 + ((v3 * (1 << scale[i])) << 11) + 1024) >> 11; + /* Clamp and store */ + inSamples[i + (s + 2)] = v2 = (v1 >= 32767) ? 32767 : (v1 <= -32768) ? -32768 : v1; + /* Accumulate distance */ + v3 = pcmInOut[s + 2] - v2; + distAccum[i] += v3 * (double)v3; + } + + for (int x = index + 8; x > 256; x >>= 1) + if (++scale[i] >= 12) + scale[i] = 11; + } while ((scale[i] < 12) && (index > 1)); + } + + double min = double.MaxValue; + for (int i = 0; i < 8; i++) + { + if (distAccum[i] < min) + { + min = distAccum[i]; + bestIndex = i; + } + } + + /* Write converted samples */ + for (int s = 0; s < sampleCount; s++) + pcmInOut[s + 2] = (short)(inSamples[bestIndex] + inSamples[s + 2]); + + /* Write ps */ + adpcmOut[0] = (byte)((bestIndex << 4) | (scale[bestIndex] & 0xF)); + + /* Zero remaining samples */ + for (int s = sampleCount; s < 14; s++) + outSamples[bestIndex + s] = 0; + + /* Write output samples */ + for (int y = 0; y < 7; y++) + { + adpcmOut[y + 1] = (byte)((outSamples[bestIndex] + outSamples[y * 2] << 4) | (outSamples[bestIndex] + outSamples[y * 2 + 1] & 0xF)); + } + } + + public static void EncodeFrame(short[] src, byte[] dst, short[] coefs, byte one) + { + coefs = new short[0 + 2]; + DSPEncodeFrame(src, 14, dst, coefs); + } + #endregion + + #region DSP-ADPCM Decode + + #region Method 1 + public static int DivideByRoundUp(int dividend, int divisor) + { + return (dividend + divisor - 1) / divisor; + } + + public static sbyte GetHighNibble(byte value) + { + return (sbyte)((value >> 4) & 0xF); + } + + public static sbyte GetLowNibble(byte value) + { + return (sbyte)((value) & 0xF); + } + + public static short Clamp16(int value) + { + if (value > Max) + return Max; + if (value < Min) + return Min; + return (short)value; + } + + public static void Decode(Span src, Span dst, ref DSPADPCMInfo cxt, uint samples) + { + short hist1 = cxt.yn1; + short hist2 = cxt.yn2; + short[] coefs = cxt.coef; + int srcIndex = 0; + int dstIndex = 0; + + int frameCount = DivideByRoundUp((int)samples, DSPADPCMConstants.SamplesPerFrame); + int samplesRemaining = (int)samples; + + for (int i = 0; i < frameCount; i++) + { + int predictor = GetHighNibble(src[srcIndex]) & 0xF; + int scale = 1 << GetLowNibble(src[srcIndex++]); + short coef1 = coefs[predictor * 2]; + short coef2 = coefs[predictor * 2 + 1]; + + int samplesToRead = Math.Min(DSPADPCMConstants.SamplesPerFrame, samplesRemaining); + + + for (int s = 0; s < samplesToRead; s++) + { + // Get bits per byte + //byte bits = src[srcIndex++]; + byte bits = src[srcIndex + (s >> 1)]; + int sample = (s % 2) == 0 ? GetHighNibble(bits) : GetLowNibble(bits); + sample = sample >= 8 ? sample - 16 : sample; + sample = (((scale * sample) << 11) + 1024 + ((coef1 * hist1) + (coef2 * hist2))) >> 11; + short finalSample = Clamp16(sample); + + hist2 = hist1; + hist1 = finalSample; + + //if (samplesToRead <= 14) { samplesToRead = 0; } + //srcIndex += 1; + dst[dstIndex++] = finalSample; + if (dstIndex >= samplesToRead) break; + } + + //samplesRemaining -= samplesToRead; + srcIndex += samplesToRead / 2; + } + } + + public static void GetLoopContext(Span src, ref DSPADPCMInfo cxt, uint samples) + { + short hist1 = cxt.yn1; + short hist2 = cxt.yn2; + short[] coefs = cxt.coef; + int srcIndex = 0; + byte ps = 0; + + int frameCount = DivideByRoundUp((int)samples, DSPADPCMConstants.SamplesPerFrame); + int samplesRemaining = (int)samples; + + for (int i = 0; i < frameCount; i++) + { + ps = src[srcIndex]; + int predictor = GetHighNibble(src[srcIndex]) & 0x7; + int scale = 1 << GetLowNibble(src[srcIndex++]); + short coef1 = coefs[predictor * 2]; + short coef2 = coefs[predictor * 2 + 1]; + + int samplesToRead = Math.Min(DSPADPCMConstants.SamplesPerFrame, samplesRemaining); + + for (int s = 0; s < samplesToRead; s++) + { + int sample = s % 2 == 0 ? GetHighNibble(src[srcIndex]) : GetLowNibble(src[srcIndex++]); + sample = sample >= 8 ? sample - 16 : sample; + sample = (((scale * sample) << 11) + 1024 + (coef1 * hist1 + coef2 * hist2)) >> 11; + short finalSample = Clamp16(sample); + + hist2 = hist1; + hist1 = finalSample; + } + samplesRemaining -= samplesToRead; + } + + cxt.loop_pred_scale = ps; + cxt.loop_yn1 = hist1; + cxt.loop_yn2 = hist2; + } + #endregion + + #region Method 2 + + public class PlayConfigType + { + int config_set; /* some of the mods below are set */ + + /* modifiers */ + int play_forever; + int ignore_loop; + int force_loop; + int really_force_loop; + int ignore_fade; + + /* processing */ + double loop_count; + int pad_begin; + int trim_begin; + int body_time; + int trim_end; + double fade_delay; /* not in samples for backwards compatibility */ + double fade_time; + int pad_end; + + double pad_begin_s; + double trim_begin_s; + double body_time_s; + double trim_end_s; + //double fade_delay_s; + //double fade_time_s; + double pad_end_s; + + /* internal flags */ + int pad_begin_set; + int trim_begin_set; + int body_time_set; + int loop_count_set; + int trim_end_set; + int fade_delay_set; + int fade_time_set; + int pad_end_set; + + /* for lack of a better place... */ + int is_txtp; + int is_mini_txtp; + + } + + + public class PlayStateType + { + int input_channels; + int output_channels; + + int pad_begin_duration; + int pad_begin_left; + int trim_begin_duration; + int trim_begin_left; + int body_duration; + int fade_duration; + int fade_left; + int fade_start; + int pad_end_duration; + //int pad_end_left; + int pad_end_start; + + int play_duration; /* total samples that the stream lasts (after applying all config) */ + int play_position; /* absolute sample where stream is */ + + } + + public class Stream + { + /* basic config */ + int num_samples; /* the actual max number of samples */ + int sample_rate; /* sample rate in Hz */ + public int channels; /* number of channels */ + CodecType coding_type; /* type of encoding */ + LayoutType layout_type; /* type of layout */ + MetaType meta_type; /* type of metadata */ + + /* loopin config */ + int loop_flag; /* is this stream looped? */ + int loop_start_sample; /* first sample of the loop (included in the loop) */ + int loop_end_sample; /* last sample of the loop (not included in the loop) */ + + /* layouts/block config */ + int interleave_block_size; /* interleave, or block/frame size (depending on the codec) */ + int interleave_first_block_size; /* different interleave for first block */ + int interleave_first_skip; /* data skipped before interleave first (needed to skip other channels) */ + int interleave_last_block_size; /* smaller interleave for last block */ + int frame_size; /* for codecs with configurable size */ + + /* subsong config */ + int num_streams; /* for multi-stream formats (0=not set/one stream, 1=one stream) */ + int stream_index; /* selected subsong (also 1-based) */ + int stream_size; /* info to properly calculate bitrate in case of subsongs */ + char[] stream_name = new char[255]; /* name of the current stream (info), if the file stores it and it's filled */ + + /* mapping config (info for plugins) */ + uint channel_layout; /* order: FL FR FC LFE BL BR FLC FRC BC SL SR etc (WAVEFORMATEX flags where FL=lowest bit set) */ + + /* other config */ + int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */ + + + /* layout/block state */ + int full_block_size; /* actual data size of an entire block (ie. may be fixed, include padding/headers, etc) */ + int current_sample; /* sample point within the file (for loop detection) */ + int samples_into_block; /* number of samples into the current block/interleave/segment/etc */ + int current_block_offset; /* start of this block (offset of block header) */ + int current_block_size; /* size in usable bytes of the block we're in now (used to calculate num_samples per block) */ + int current_block_samples; /* size in samples of the block we're in now (used over current_block_size if possible) */ + int next_block_offset; /* offset of header of the next block */ + + /* loop state (saved when loop is hit to restore later) */ + int loop_current_sample; /* saved from current_sample (same as loop_start_sample, but more state-like) */ + int loop_samples_into_block;/* saved from samples_into_block */ + int loop_block_offset; /* saved from current_block_offset */ + int loop_block_size; /* saved from current_block_size */ + int loop_block_samples; /* saved from current_block_samples */ + int loop_next_block_offset; /* saved from next_block_offset */ + int hit_loop; /* save config when loop is hit, but first time only */ + + + /* decoder config/state */ + int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */ + int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to them */ + int ws_output_size; /* WS ADPCM: output bytes for this block */ + + + /* main state */ + public Channel[] ch; /* array of channels */ + Channel start_ch; /* shallow copy of channels as they were at the beginning of the stream (for resets) */ + Channel loop_ch; /* shallow copy of channels as they were at the loop point (for loops) */ + IntPtr start_vgmstream; /* shallow copy of the VGMSTREAM as it was at the beginning of the stream (for resets) */ + + IntPtr mixing_data; /* state for mixing effects */ + + /* Optional data the codec needs for the whole stream. This is for codecs too + * different from vgmstream's structure to be reasonably shoehorned. + * Note also that support must be added for resetting, looping and + * closing for every codec that uses this, as it will not be handled. */ + IntPtr codec_data; + /* Same, for special layouts. layout_data + codec_data may exist at the same time. */ + IntPtr layout_data; + + + /* play config/state */ + int config_enabled; /* config can be used */ + PlayConfigType config; /* player config (applied over decoding) */ + PlayStateType pstate; /* player state (applied over decoding) */ + int loop_count; /* counter of complete loops (1=looped once) */ + int loop_target; /* max loops before continuing with the stream end (loops forever if not set) */ + short[] tmpbuf; /* garbage buffer used for seeking/trimming */ + int tmpbuf_size; /* for all channels (samples = tmpbuf_size / channels) */ + + } + + /* read from a file, returns number of bytes read */ + public static int read_streamfile(Span dst, int offset, int length, StreamFile sf) + { + return read_streamfile(dst, offset, length, sf); + } + + /* return file size */ + public static int get_streamfile_size(StreamFile sf) + { + return get_streamfile_size(sf); + } + + public class StreamFile + { + + /* read 'length' data at 'offset' to 'dst' */ + static int read(Span dst, int offset, int length, StreamFile[] sf) + { + return read(dst, offset, length, sf); + } + + /* get max offset */ + static int get_size(StreamFile[] sf) + { + return get_size(sf); + } + + //todo: DO NOT USE, NOT RESET PROPERLY (remove?) + static int get_offset(StreamFile[] sf) + { + return get_offset(sf); + } + + /* copy current filename to name buf */ + static void get_name(string name, int name_size, StreamFile[] sf) + { + sf.SetValue(name, name_size); + } + + /* open another streamfile from filename */ + public StreamFile() + { + string filename; + int buf_size; + StreamFile[] sf; + } + + /* free current STREAMFILE */ + //void (* close) (struct _StreamFile sf); + + /* Substream selection for formats with subsongs. + * Not ideal here, but it was the simplest way to pass to all init_vgmstream_x functions. */ + int stream_index; /* 0=default/auto (first), 1=first, N=Nth */ + + } + + public class g72x_state + { + long yl; /* Locked or steady state step size multiplier. */ + short yu; /* Unlocked or non-steady state step size multiplier. */ + short dms; /* Short term energy estimate. */ + short dml; /* Long term energy estimate. */ + short ap; /* Linear weighting coefficient of 'yl' and 'yu'. */ + + short[] a = new short[2]; /* Coefficients of pole portion of prediction filter. */ + short[] b = new short[6]; /* Coefficients of zero portion of prediction filter. */ + short[] pk = new short[2]; /* + * Signs of previous two samples of a partially + * reconstructed signal. + */ + short[] dq = new short[6]; /* + * Previous 6 samples of the quantized difference + * signal represented in an internal floating point + * format. + */ + short[] sr = new short[2]; /* + * Previous 2 samples of the quantized difference + * signal represented in an internal floating point + * format. + */ + char td; /* delayed tone detect, new in 1988 version */ + }; + + public class Channel + { + public StreamFile streamfile = new StreamFile(); /* file used by this channel */ + public long channel_start_offset; /* where data for this channel begins */ + public long offset; /* current location in the file */ + + public int frame_header_offset; /* offset of the current frame header (for WS) */ + public int samples_left_in_frame; /* for WS */ + + /* format specific */ + + /* adpcm */ + public short[] adpcm_coef = new short[16]; /* formats with decode coefficients built in (DSP, some ADX) */ + public int[] adpcm_coef_3by32 = new int[0x60]; /* Level-5 0x555 */ + public short[] vadpcm_coefs = new short[8 * 2 * 8]; /* VADPCM: max 8 groups * max 2 order * fixed 8 subframe coefs */ + public short adpcm_history1_16; /* previous sample */ + public int adpcm_history1_32; + + public short adpcm_history2_16; /* previous previous sample */ + public int adpcm_history2_32; + + public short adpcm_history3_16; + public int adpcm_history3_32; + + public short adpcm_history4_16; + public int adpcm_history4_32; + + + //double adpcm_history1_double; + //double adpcm_history2_double; + + public int adpcm_step_index; /* for IMA */ + public int adpcm_scale; /* for MS ADPCM */ + + /* state for G.721 decoder, sort of big but we might as well keep it around */ + public g72x_state g72x_state = new g72x_state(); + + /* ADX encryption */ + public int adx_channels; + public short adx_xor; + public short adx_mult; + public short adx_add; + + }; + + public static int[] nibble_to_int = new int[16] {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1}; + + public static int get_nibble_signed(byte n, int upper) + { + /*return ((n&0x70)-(n&0x80))>>4;*/ + return nibble_to_int[(n >> (upper != 0 ? 4 : 0)) & 0x0f]; + } + + public static int get_high_nibble_signed(byte n) + { + /*return ((n&0x70)-(n&0x80))>>4;*/ + return nibble_to_int[n >> 4]; + } + + public static int get_low_nibble_signed(byte n) + { + /*return (n&7)-(n&8);*/ + return nibble_to_int[n & 0xf]; + } + + public static int clamp16(int val) + { + if (val > 32767) return 32767; + else if (val < -32768) return -32768; + else return val; + } + + public static void decode_ngc_dsp(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do) + { + byte[] frame = new byte[0x08] { 0,0,0,0,0,0,0,0 }; + int frame_offset; + int i, frames_in, sample_count = 0; + int bytes_per_frame, samples_per_frame; + int coef_index, scale, coef1, coef2; + int hist1 = stream.adpcm_history1_16; + int hist2 = stream.adpcm_history2_16; + + + /* external interleave (fixed size), mono */ + bytes_per_frame = 0x08; + samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 14 */ + frames_in = first_sample / samples_per_frame; + first_sample = first_sample % samples_per_frame; + + /* parse frame header */ + frame_offset = (int)((stream.offset + bytes_per_frame) * frames_in); + read_streamfile(frame, frame_offset, bytes_per_frame, stream.streamfile); /* ignore EOF errors */ + scale = 1 << ((frame[0] >> 0) & 0xf); + coef_index = (frame[0] >> 4) & 0xf; + + if (coef_index >= 8) { Debug.WriteLine($"DSP: incorrect coefs at %x\n", (uint)frame_offset); } + //if (coef_index > 8) //todo not correctly clamped in original decoder? + // coef_index = 8; + + coef1 = stream.adpcm_coef[coef_index * 2 + 0]; + coef2 = stream.adpcm_coef[coef_index * 2 + 1]; + + + /* decode nibbles */ + for (i = first_sample; i < first_sample + samples_to_do; i++) + { + int sample = 0; + byte nibbles = frame[0x01 + i / 2]; + + sample = (i & 1) != 0 ? /* high nibble first */ + get_low_nibble_signed(nibbles) : + get_high_nibble_signed(nibbles); + sample = ((sample * scale) << 11); + sample = (sample + 1024 + coef1 * hist1 + coef2 * hist2) >> 11; + sample = clamp16(sample); + + outbuf[sample_count] = sample; + sample_count += channelspacing; + + hist2 = hist1; + hist1 = sample; + } + + stream.adpcm_history1_16 = (short)hist1; + stream.adpcm_history2_16 = (short)hist2; + } + + + /* read from memory rather than a file */ + public static void decode_ngc_dsp_subint_internal(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do, Span frame) + { + int i, sample_count = 0; + int bytes_per_frame, samples_per_frame; + int coef_index, scale, coef1, coef2; + int hist1 = stream.adpcm_history1_16; + int hist2 = stream.adpcm_history2_16; + + + /* external interleave (fixed size), mono */ + bytes_per_frame = 0x08; + samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 14 */ + first_sample = first_sample % samples_per_frame; + if (samples_to_do > samples_per_frame) { Debug.WriteLine($"DSP: layout error, too many samples\n"); } + + /* parse frame header */ + scale = 1 << ((frame[0] >> 0) & 0xf); + coef_index = (frame[0] >> 4) & 0xf; + + if (coef_index >= 8) { Debug.WriteLine($"DSP: incorrect coefs\n"); } + //if (coef_index > 8) //todo not correctly clamped in original decoder? + // coef_index = 8; + + coef1 = stream.adpcm_coef[coef_index * 2 + 0]; + coef2 = stream.adpcm_coef[coef_index * 2 + 1]; + + for (i = first_sample; i < first_sample + samples_to_do; i++) + { + int sample = 0; + byte nibbles = frame[0x01 + i / 2]; + + sample = (i & 1) != 0 ? + get_low_nibble_signed(nibbles) : + get_high_nibble_signed(nibbles); + sample = ((sample * scale) << 11); + sample = (sample + 1024 + coef1 * hist1 + coef2 * hist2) >> 11; + sample = clamp16(sample); + + outbuf[sample_count] = sample; + sample_count += channelspacing; + + hist2 = hist1; + hist1 = sample; + } + + stream.adpcm_history1_16 = (short)hist1; + stream.adpcm_history2_16 = (short)hist2; + } + + private static sbyte read_8bit(int offset, StreamFile sf) + { + byte[] buf = new byte[1]; + + if (read_streamfile(buf, offset, 1, sf) != 1) return -1; + return (sbyte)buf[0]; + } + + /* decode DSP with byte-interleaved frames (ex. 0x08: 1122112211221122) */ + public static void decode_ngc_dsp_subint(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do, int channel, int interleave) + { + byte[] frame = new byte[0x08]; + int i; + int frames_in = first_sample / 14; + + for (i = 0; i < 0x08; i++) + { + /* base + current frame + subint section + subint byte + channel adjust */ + frame[i] = (byte)read_8bit( + (int)((stream.offset + + frames_in) * (0x08 * channelspacing) + + i / interleave * interleave * channelspacing + + i % interleave + + interleave * channel), stream.streamfile); + } + + decode_ngc_dsp_subint_internal(stream, outbuf, channelspacing, first_sample, samples_to_do, frame); + } + + + /* + * The original DSP spec uses nibble counts for loop points, and some + * variants don't have a proper sample count, so we (who are interested + * in sample counts) need to do this conversion occasionally. + */ + public static int dsp_nibbles_to_samples(int nibbles) + { + int whole_frames = nibbles / 16; + int remainder = nibbles % 16; + + if (remainder > 0) return whole_frames * 14 + remainder - 2; + else return whole_frames * 14; + } + + public static int dsp_bytes_to_samples(int bytes, int channels) + { + if (channels <= 0) return 0; + return bytes / channels / 8 * 14; + } + + /* host endian independent multi-byte integer reading */ + public static short get_16bitBE(Span p) + { + return (short)(((ushort)p[0] << 8) | ((ushort)p[1])); + } + + public static short get_16bitLE(Span p) + { + return (short)(((ushort)p[0]) | ((ushort)p[1] << 8)); + } + + public static short read_16bitLE(int offset, StreamFile sf) + { + byte[] buf = new byte[2]; + + if (read_streamfile(buf, offset, 2, sf) != 2) return -1; + return get_16bitLE(buf); + } + public static short read_16bitBE(int offset, StreamFile sf) + { + byte[] buf = new byte[2]; + + if (read_streamfile(buf, offset, 2, sf) != 2) return -1; + return get_16bitBE(buf); + } + + /* reads DSP coefs built in the streamfile */ + public static void dsp_read_coefs_be(Stream vgmstream, StreamFile streamFile, int offset, int spacing) + { + dsp_read_coefs(vgmstream, streamFile, offset, spacing, 1); + } + public static void dsp_read_coefs_le(Stream vgmstream, StreamFile streamFile, int offset, int spacing) + { + dsp_read_coefs(vgmstream, streamFile, offset, spacing, 0); + } + public static void dsp_read_coefs(Stream vgmstream, StreamFile streamFile, int offset, int spacing, int be) + { + int ch, i; + /* get ADPCM coefs */ + for (ch = 0; ch < vgmstream.channels; ch++) + { + for (i = 0; i < 16; i++) + { + vgmstream.ch[ch].adpcm_coef[i] = be != 0 ? + read_16bitBE(offset + ch * spacing + i * 2, streamFile) : + read_16bitLE(offset + ch * spacing + i * 2, streamFile); + } + } + } + + /* reads DSP initial hist built in the streamfile */ + public static void dsp_read_hist_be(Stream vgmstream, StreamFile streamFile, int offset, int spacing) + { + dsp_read_hist(vgmstream, streamFile, offset, spacing, 1); + } + public static void dsp_read_hist_le(Stream vgmstream, StreamFile streamFile, int offset, int spacing) + { + dsp_read_hist(vgmstream, streamFile, offset, spacing, 0); + } + public static void dsp_read_hist(Stream vgmstream, StreamFile streamFile, int offset, int spacing, int be) + { + int ch; + /* get ADPCM hist */ + for (ch = 0; ch < vgmstream.channels; ch++) + { + vgmstream.ch[ch].adpcm_history1_16 = be != 0 ? + read_16bitBE(offset + ch * spacing + 0 * 2, streamFile) : + read_16bitLE(offset + ch * spacing + 0 * 2, streamFile); ; + vgmstream.ch[ch].adpcm_history2_16 = be != 0 ? + read_16bitBE(offset + ch * spacing + 1 * 2, streamFile) : + read_16bitLE(offset + ch * spacing + 1 * 2, streamFile); ; + } + } + + + #endregion + + #endregion + + #region DSP-ADPCM Math + public static uint GetBytesForADPCMBuffer(uint samples) + { + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + if ((samples % DSPADPCMConstants.SamplesPerFrame) == frames) + frames++; + + return frames * DSPADPCMConstants.BytesPerFrame; + } + + public static uint GetBytesForADPCMSamples(uint samples) + { + uint extraBytes = 0; + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + uint extraSamples = (samples % DSPADPCMConstants.SamplesPerFrame); + + if (extraSamples == frames) + { + extraBytes = (extraSamples / 2) + (extraSamples % 2) + 1; + } + + return DSPADPCMConstants.BytesPerFrame * frames + extraBytes; + } + + public static uint GetBytesForPCMBuffer(uint samples) + { + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + if ((samples % DSPADPCMConstants.SamplesPerFrame) == frames) + frames++; + + return frames * DSPADPCMConstants.SamplesPerFrame * sizeof(int); + } + + public static uint GetBytesForPCMSamples(uint samples) + { + return samples * sizeof(int); + } + + public static uint GetNibbleAddress(uint samples) + { + int frames = (int)(samples / DSPADPCMConstants.SamplesPerFrame); + int extraSamples = (int)(samples % DSPADPCMConstants.SamplesPerFrame); + + return (uint)(DSPADPCMConstants.NibblesPerFrame * frames + extraSamples + 2); + } + + public static uint GetNibblesForNSamples(uint samples) + { + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + uint extraSamples = (samples % DSPADPCMConstants.SamplesPerFrame); + uint extraNibbles = extraSamples == 0 ? 0 : extraSamples + 2; + + return DSPADPCMConstants.NibblesPerFrame * frames + extraNibbles; + } + + public static uint GetSampleForADPCMNibble(uint nibble) + { + uint frames = nibble / DSPADPCMConstants.NibblesPerFrame; + uint extraNibbles = (nibble % DSPADPCMConstants.NibblesPerFrame); + uint samples = DSPADPCMConstants.SamplesPerFrame * frames; + + return samples + extraNibbles - 2; + } + #endregion + } +} diff --git a/VG Music Studio - Core/Codec/LayoutEnums.cs b/VG Music Studio - Core/Codec/LayoutEnums.cs new file mode 100644 index 0000000..f241948 --- /dev/null +++ b/VG Music Studio - Core/Codec/LayoutEnums.cs @@ -0,0 +1,59 @@ +namespace Kermalis.VGMusicStudio.Core.Codec; + +/* This code has been copied directly from vgmstream.h in VGMStream's repository * + * and modified into C# code to work with VGMS. Link to its repository can be * + * found here: https://github.com/vgmstream/vgmstream */ +public enum LayoutType +{ + /* generic */ + layout_none, /* straight data */ + + /* interleave */ + layout_interleave, /* equal interleave throughout the stream */ + + /* headered blocks */ + layout_blocked_ast, + layout_blocked_halpst, + layout_blocked_xa, + layout_blocked_ea_schl, + layout_blocked_ea_1snh, + layout_blocked_caf, + layout_blocked_wsi, + layout_blocked_str_snds, + layout_blocked_ws_aud, + layout_blocked_matx, + layout_blocked_dec, + layout_blocked_xvas, + layout_blocked_vs, + layout_blocked_mul, + layout_blocked_gsb, + layout_blocked_thp, + layout_blocked_filp, + layout_blocked_ea_swvr, + layout_blocked_adm, + layout_blocked_bdsp, + layout_blocked_mxch, + layout_blocked_ivaud, /* GTA IV .ivaud blocks */ + layout_blocked_ps2_iab, + layout_blocked_vs_str, + layout_blocked_rws, + layout_blocked_hwas, + layout_blocked_ea_sns, /* newest Electronic Arts blocks, found in SNS/SNU/SPS/etc formats */ + layout_blocked_awc, /* Rockstar AWC */ + layout_blocked_vgs, /* Guitar Hero II (PS2) */ + layout_blocked_xwav, + layout_blocked_xvag_subsong, /* XVAG subsongs [God of War III (PS4)] */ + layout_blocked_ea_wve_au00, /* EA WVE au00 blocks */ + layout_blocked_ea_wve_ad10, /* EA WVE Ad10 blocks */ + layout_blocked_sthd, /* Dream Factory STHD */ + layout_blocked_h4m, /* H4M video */ + layout_blocked_xa_aiff, /* XA in AIFF files [Crusader: No Remorse (SAT), Road Rash (3DO)] */ + layout_blocked_vs_square, + layout_blocked_vid1, + layout_blocked_ubi_sce, + layout_blocked_tt_ad, + + /* otherwise odd */ + layout_segmented, /* song divided in segments (song sections) */ + layout_layered, /* song divided in layers (song channels) */ +} \ No newline at end of file diff --git a/VG Music Studio - Core/Codec/MetaEnums.cs b/VG Music Studio - Core/Codec/MetaEnums.cs new file mode 100644 index 0000000..e4cb65a --- /dev/null +++ b/VG Music Studio - Core/Codec/MetaEnums.cs @@ -0,0 +1,479 @@ +namespace Kermalis.VGMusicStudio.Core.Codec; + +/* This code has been copied directly from vgmstream.h in VGMStream's repository * + * and modified into C# code to work with VGMS. Link to its repository can be * + * found here: https://github.com/vgmstream/vgmstream */ +public enum MetaType +{ + meta_SILENCE, + + meta_DSP_STD, /* Nintendo standard GC ADPCM (DSP) header */ + meta_DSP_CSTR, /* Star Fox Assault "Cstr" */ + meta_DSP_RS03, /* Retro: Metroid Prime 2 "RS03" */ + meta_DSP_STM, /* Paper Mario 2 STM */ + meta_AGSC, /* Retro: Metroid Prime 2 title */ + meta_CSMP, /* Retro: Metroid Prime 3 (Wii), Donkey Kong Country Returns (Wii) */ + meta_RFRM, /* Retro: Donkey Kong Country Tropical Freeze (Wii U) */ + meta_DSP_MPDSP, /* Monopoly Party single header stereo */ + meta_DSP_JETTERS, /* Bomberman Jetters .dsp */ + meta_DSP_MSS, /* Free Radical GC games */ + meta_DSP_GCM, /* some of Traveller's Tales games */ + meta_DSP_STR, /* Conan .str files */ + meta_DSP_SADB, /* Procyon Studio Digtial Sound Elements DSP-ADPCM (Wii) .sad */ + meta_DSP_WSI, /* .wsi */ + meta_IDSP_TT, /* Traveller's Tales games */ + meta_DSP_WII_MUS, /* .mus */ + meta_DSP_WII_WSD, /* Phantom Brave (Wii) */ + meta_WII_NDP, /* Vertigo (Wii) */ + meta_DSP_YGO, /* Konami: Yu-Gi-Oh! The Falsebound Kingdom (NGC), Hikaru no Go 3 (NGC) */ + + meta_STRM, /* Nintendo/HAL Labs Nitro Soundmaker STRM */ + meta_RSTM, /* Nintendo/HAL Labs NW4R Soundmaker RSTM (Revolution Stream, similar to STRM) */ + meta_AFC, /* AFC */ + meta_AST, /* AST */ + meta_RWSD, /* Nintendo/HAL Labs NW4R Soundmaker single-stream RWSD */ + meta_RWAR, /* Nintendo/HAL Labs NW4R Soundmaker single-stream RWAR */ + meta_RWAV, /* Nintendo/HAL Labs NW4R Soundmaker contents of RWAR */ + meta_CWAV, /* Nintendo/HAL Labs NW4C Soundmaker contents of CWAR */ + meta_FWAV, /* Nintendo/HAL Labs NW4F Soundmaker contents of FWAR */ + meta_THP, /* THP movie files */ + meta_SWAV, + meta_NDS_RRDS, /* Ridge Racer DS */ + meta_WII_BNS, /* Wii BNS Banner Sound (similar to RSTM) */ + meta_WIIU_BTSND, /* Wii U Boot Sound */ + + meta_ADX_03, /* CRI ADX "type 03" */ + meta_ADX_04, /* CRI ADX "type 04" */ + meta_ADX_05, /* CRI ADX "type 05" */ + meta_AIX, /* CRI AIX */ + meta_AAX, /* CRI AAX */ + meta_UTF_DSP, /* CRI ADPCM_WII, like AAX with DSP */ + + meta_DTK, + meta_RSF, + meta_HALPST, /* HAL Labs HALPST */ + meta_GCSW, /* GCSW (PCM) */ + meta_CAF, /* tri-Crescendo CAF */ + meta_MYSPD, /* U-Sing .myspd */ + meta_HIS, /* Her Ineractive .his */ + meta_BNSF, /* Bandai Namco Sound Format */ + + meta_XA, /* CD-ROM XA */ + meta_ADS, + meta_NPS, + meta_RXWS, + meta_RAW_INT, + meta_EXST, + meta_SVAG_KCET, + meta_PS_HEADERLESS, /* headerless PS-ADPCM */ + meta_MIB_MIH, + meta_PS2_MIC, /* KOEI MIC File */ + meta_PS2_VAGi, /* VAGi Interleaved File */ + meta_PS2_VAGp, /* VAGp Mono File */ + meta_PS2_pGAV, /* VAGp with Little Endian Header */ + meta_PS2_VAGp_AAAP, /* Acclaim Austin Audio VAG header */ + meta_SEB, + meta_STR_WAV, /* Blitz Games STR+WAV files */ + meta_ILD, + meta_PS2_PNB, /* PsychoNauts Bgm File */ + meta_VPK, /* VPK Audio File */ + meta_PS2_BMDX, /* Beatmania thing */ + meta_PS2_IVB, /* Langrisser 3 IVB */ + meta_PS2_SND, /* some Might & Magics SSND header */ + meta_SVS, /* Square SVS */ + meta_XSS, /* Dino Crisis 3 */ + meta_SL3, /* Test Drive Unlimited */ + meta_HGC1, /* Knights of the Temple 2 */ + meta_AUS, /* Various Capcom games */ + meta_RWS, /* RenderWare games (only when using RW Audio middleware) */ + meta_FSB1, /* FMOD Sample Bank, version 1 */ + meta_FSB2, /* FMOD Sample Bank, version 2 */ + meta_FSB3, /* FMOD Sample Bank, version 3.0/3.1 */ + meta_FSB4, /* FMOD Sample Bank, version 4 */ + meta_FSB5, /* FMOD Sample Bank, version 5 */ + meta_RWX, /* Air Force Delta Storm (XBOX) */ + meta_XWB, /* Microsoft XACT framework (Xbox, X360, Windows) */ + meta_PS2_XA30, /* Driver - Parallel Lines (PS2) */ + meta_MUSC, /* Krome PS2 games */ + meta_MUSX, + meta_LEG, /* Legaia 2 [no header_id] */ + meta_FILP, /* Resident Evil - Dead Aim */ + meta_IKM, + meta_STER, + meta_BG00, /* Ibara, Mushihimesama */ + meta_PS2_RSTM, /* Midnight Club 3 */ + meta_PS2_KCES, /* Dance Dance Revolution */ + meta_HXD, + meta_VSV, + meta_SCD_PCM, /* Lunar - Eternal Blue */ + meta_PS2_PCM, /* Konami KCEJ East: Ephemeral Fantasia, Yu-Gi-Oh! The Duelists of the Roses, 7 Blades */ + meta_PS2_RKV, /* Legacy of Kain - Blood Omen 2 (PS2) */ + meta_PS2_VAS, /* Pro Baseball Spirits 5 */ + meta_PS2_ENTH, /* Enthusia */ + meta_SDT, /* Baldur's Gate - Dark Alliance */ + meta_NGC_TYDSP, /* Ty - The Tasmanian Tiger */ + meta_DC_STR, /* SEGA Stream Asset Builder */ + meta_DC_STR_V2, /* variant of SEGA Stream Asset Builder */ + meta_NGC_BH2PCM, /* Bio Hazard 2 */ + meta_SAP, + meta_DC_IDVI, /* Eldorado Gate */ + meta_KRAW, /* Geometry Wars - Galaxies */ + meta_PS2_OMU, /* PS2 Int file with Header */ + meta_PS2_XA2, /* XG3 Extreme-G Racing */ + meta_NUB, + meta_IDSP_NL, /* Mario Strikers Charged (Wii) */ + meta_IDSP_IE, /* Defencer (GC) */ + meta_SPT_SPD, /* Various (SPT+SPT DSP) */ + meta_ISH_ISD, /* Various (ISH+ISD DSP) */ + meta_GSP_GSB, /* Tecmo games (Super Swing Golf 1 & 2, Quamtum Theory) */ + meta_YDSP, /* WWE Day of Reckoning */ + meta_FFCC_STR, /* Final Fantasy: Crystal Chronicles */ + meta_UBI_JADE, /* Beyond Good & Evil, Rayman Raving Rabbids */ + meta_GCA, /* Metal Slug Anthology */ + meta_NGC_SSM, /* Golden Gashbell Full Power */ + meta_PS2_JOE, /* Wall-E / Pixar games */ + meta_NGC_YMF, /* WWE WrestleMania X8 */ + meta_SADL, + meta_PS2_CCC, /* Tokyo Xtreme Racer DRIFT 2 */ + meta_FAG, /* Jackie Chan - Stuntmaster */ + meta_PS2_MIHB, /* Merged MIH+MIB */ + meta_NGC_PDT, /* Mario Party 6 */ + meta_DC_ASD, /* Miss Moonligh */ + meta_NAOMI_SPSD, /* Guilty Gear X */ + meta_RSD, + meta_PS2_ASS, /* ASS */ + meta_SEG, /* Eragon */ + meta_NDS_STRM_FFTA2, /* Final Fantasy Tactics A2 */ + meta_KNON, + meta_ZWDSP, /* Zack and Wiki */ + meta_VGS, /* Guitar Hero Encore - Rocks the 80s */ + meta_DCS_WAV, + meta_SMP, + meta_WII_SNG, /* Excite Trucks */ + meta_MUL, + meta_SAT_BAKA, /* Crypt Killer */ + meta_VSF, + meta_PS2_VSF_TTA, /* Tiny Toon Adventures: Defenders of the Universe */ + meta_ADS_MIDWAY, + meta_PS2_SPS, /* Ape Escape 2 */ + meta_PS2_XA2_RRP, /* RC Revenge Pro */ + meta_NGC_DSP_KONAMI, /* Konami DSP header, found in various games */ + meta_UBI_CKD, /* Ubisoft CKD RIFF header (Rayman Origins Wii) */ + meta_RAW_WAVM, + meta_WVS, + meta_XBOX_MATX, /* XBOX MATX */ + meta_XMU, + meta_XVAS, + meta_EA_SCHL, /* Electronic Arts SCHl with variable header */ + meta_EA_SCHL_fixed, /* Electronic Arts SCHl with fixed header */ + meta_EA_BNK, /* Electronic Arts BNK */ + meta_EA_1SNH, /* Electronic Arts 1SNh/EACS */ + meta_EA_EACS, + meta_RAW_PCM, + meta_GENH, /* generic header */ + meta_AIFC, /* Audio Interchange File Format AIFF-C */ + meta_AIFF, /* Audio Interchange File Format */ + meta_STR_SNDS, /* .str with SNDS blocks and SHDR header */ + meta_WS_AUD, /* Westwood Studios .aud */ + meta_WS_AUD_old, /* Westwood Studios .aud, old style */ + meta_RIFF_WAVE, /* RIFF, for WAVs */ + meta_RIFF_WAVE_POS, /* .wav + .pos for looping (Ys Complete PC) */ + meta_RIFF_WAVE_labl, /* RIFF w/ loop Markers in LIST-adtl-labl */ + meta_RIFF_WAVE_smpl, /* RIFF w/ loop data in smpl chunk */ + meta_RIFF_WAVE_wsmp, /* RIFF w/ loop data in wsmp chunk */ + meta_RIFF_WAVE_MWV, /* .mwv RIFF w/ loop data in ctrl chunk pflt */ + meta_RIFX_WAVE, /* RIFX, for big-endian WAVs */ + meta_RIFX_WAVE_smpl, /* RIFX w/ loop data in smpl chunk */ + meta_XNB, /* XNA Game Studio 4.0 */ + meta_PC_MXST, /* Lego Island MxSt */ + meta_SAB, /* Worms 4 Mayhem SAB+SOB file */ + meta_NWA, /* Visual Art's NWA */ + meta_NWA_NWAINFOINI, /* Visual Art's NWA w/ NWAINFO.INI for looping */ + meta_NWA_GAMEEXEINI, /* Visual Art's NWA w/ Gameexe.ini for looping */ + meta_SAT_DVI, /* Konami KCE Nagoya DVI (SAT games) */ + meta_DC_KCEY, /* Konami KCE Yokohama KCEYCOMP (DC games) */ + meta_ACM, /* InterPlay ACM header */ + meta_MUS_ACM, /* MUS playlist of InterPlay ACM files */ + meta_DEC, /* Falcom PC games (Xanadu Next, Gurumin) */ + meta_VS, /* Men in Black .vs */ + meta_FFXI_BGW, /* FFXI (PC) BGW */ + meta_FFXI_SPW, /* FFXI (PC) SPW */ + meta_STS, + meta_PS2_P2BT, /* Pop'n'Music 7 Audio File */ + meta_PS2_GBTS, /* Pop'n'Music 9 Audio File */ + meta_NGC_DSP_IADP, /* Gamecube Interleave DSP */ + meta_PS2_TK5, /* Tekken 5 Stream Files */ + meta_PS2_MCG, /* Gunvari MCG Files (was name .GCM on disk) */ + meta_ZSD, /* Dragon Booster ZSD */ + meta_REDSPARK, /* "RedSpark" RSD (MadWorld) */ + meta_IVAUD, /* .ivaud GTA IV */ + meta_NDS_HWAS, /* Spider-Man 3, Tony Hawk's Downhill Jam, possibly more... */ + meta_NGC_LPS, /* Rave Master (Groove Adventure Rave)(GC) */ + meta_NAOMI_ADPCM, /* NAOMI/NAOMI2 ARcade games */ + meta_SD9, /* beatmaniaIIDX16 - EMPRESS (Arcade) */ + meta_2DX9, /* beatmaniaIIDX16 - EMPRESS (Arcade) */ + meta_PS2_VGV, /* Rune: Viking Warlord */ + meta_GCUB, + meta_MAXIS_XA, /* Sim City 3000 (PC) */ + meta_NGC_SCK_DSP, /* Scorpion King (NGC) */ + meta_CAFF, /* iPhone .caf */ + meta_EXAKT_SC, /* Activision EXAKT .SC (PS2) */ + meta_WII_WAS, /* DiRT 2 (WII) */ + meta_PONA_3DO, /* Policenauts (3DO) */ + meta_PONA_PSX, /* Policenauts (PSX) */ + meta_XBOX_HLWAV, /* Half Life 2 (XBOX) */ + meta_AST_MV, + meta_AST_MMV, + meta_DMSG, /* Nightcaster II - Equinox (XBOX) */ + meta_NGC_DSP_AAAP, /* Turok: Evolution (NGC), Vexx (NGC) */ + meta_PS2_WB, /* Shooting Love. ~TRIZEAL~ */ + meta_S14, /* raw Siren 14, 24kbit mono */ + meta_SSS, /* raw Siren 14, 48kbit stereo */ + meta_PS2_GCM, /* NamCollection */ + meta_PS2_SMPL, /* Homura */ + meta_PS2_MSA, /* Psyvariar -Complete Edition- */ + meta_PS2_VOI, /* RAW Danger (Zettaizetsumei Toshi 2 - Itetsuita Kiokutachi) [PS2] */ + meta_P3D, /* Prototype P3D */ + meta_PS2_TK1, /* Tekken (NamCollection) */ + meta_NGC_RKV, /* Legacy of Kain - Blood Omen 2 (GC) */ + meta_DSP_DDSP, /* Various (2 dsp files stuck together */ + meta_NGC_DSP_MPDS, /* Big Air Freestyle, Terminator 3 */ + meta_DSP_STR_IG, /* Micro Machines, Superman Superman: Shadow of Apokolis */ + meta_EA_SWVR, /* Future Cop L.A.P.D., Freekstyle */ + meta_PS2_B1S, /* 7 Wonders of the ancient world */ + meta_PS2_WAD, /* The golden Compass */ + meta_DSP_XIII, /* XIII, possibly more (Ubisoft header???) */ + meta_DSP_CABELAS, /* Cabelas games */ + meta_PS2_ADM, /* Dragon Quest V (PS2) */ + meta_LPCM_SHADE, + meta_DSP_BDSP, /* Ah! My Goddess */ + meta_PS2_VMS, /* Autobahn Raser - Police Madness */ + meta_XAU, /* XPEC Entertainment (Beat Down (PS2 Xbox), Spectral Force Chronicle (PS2)) */ + meta_GH3_BAR, /* Guitar Hero III Mobile .bar */ + meta_FFW, /* Freedom Fighters [NGC] */ + meta_DSP_DSPW, /* Sengoku Basara 3 [WII] */ + meta_PS2_JSTM, /* Tantei Jinguji Saburo - Kind of Blue (PS2) */ + meta_SQEX_SCD, /* Square-Enix SCD */ + meta_NGC_NST_DSP, /* Animaniacs [NGC] */ + meta_BAF, /* Bizarre Creations (Blur, James Bond) */ + meta_XVAG, /* Ratchet & Clank Future: Quest for Booty (PS3) */ + meta_CPS, + meta_MSF, + meta_PS3_PAST, /* Bakugan Battle Brawlers (PS3) */ + meta_SGXD, /* Sony: Folklore, Genji, Tokyo Jungle (PS3), Brave Story, Kurohyo (PSP) */ + meta_WII_RAS, /* Donkey Kong Country Returns (Wii) */ + meta_SPM, + meta_VGS_PS, + meta_PS2_IAB, /* Ueki no Housoku - Taosu ze Robert Juudan!! (PS2) */ + meta_VS_STR, /* The Bouncer */ + meta_LSF_N1NJ4N, /* .lsf n1nj4n Fastlane Street Racing (iPhone) */ + meta_XWAV, + meta_RAW_SNDS, + meta_PS2_WMUS, /* The Warriors (PS2) */ + meta_HYPERSCAN_KVAG, /* Hyperscan KVAG/BVG */ + meta_IOS_PSND, /* Crash Bandicoot Nitro Kart 2 (iOS) */ + meta_BOS_ADP, + meta_QD_ADP, + meta_EB_SFX, /* Excitebots .sfx */ + meta_EB_SF0, /* Excitebots .sf0 */ + meta_MTAF, + meta_PS2_VAG1, /* Metal Gear Solid 3 VAG1 */ + meta_PS2_VAG2, /* Metal Gear Solid 3 VAG2 */ + meta_ALP, + meta_WPD, /* Shuffle! (PC) */ + meta_MN_STR, /* Mini Ninjas (PC/PS3/WII) */ + meta_MSS, /* Guerilla: ShellShock Nam '67 (PS2/Xbox), Killzone (PS2) */ + meta_PS2_HSF, /* Lowrider (PS2) */ + meta_IVAG, + meta_PS2_2PFS, /* Konami: Mahoromatic: Moetto - KiraKira Maid-San, GANTZ (PS2) */ + meta_PS2_VBK, /* Disney's Stitch - Experiment 626 */ + meta_OTM, /* Otomedius (Arcade) */ + meta_CSTM, /* Nintendo/HAL Labs NW4C Soundmaker CSTM (Century Stream) */ + meta_FSTM, /* Nintendo/HAL Labs NW4F Soundmaker FSTM (caFe? Stream) */ + meta_IDSP_NAMCO, + meta_KT_WIIBGM, /* Koei Tecmo WiiBGM */ + meta_KTSS, /* Koei Tecmo Nintendo Stream (KNS) */ + meta_MCA, /* Capcom MCA "MADP" */ + meta_XB3D_ADX, /* Xenoblade Chronicles 3D ADX */ + meta_HCA, /* CRI HCA */ + meta_SVAG_SNK, + meta_PS2_VDS_VDM, /* Graffiti Kingdom */ + meta_FFMPEG, + meta_FFMPEG_faulty, + meta_CXS, + meta_AKB, + meta_PASX, + meta_XMA_RIFF, + meta_ASTB, + meta_WWISE_RIFF, /* Audiokinetic Wwise RIFF/RIFX */ + meta_UBI_RAKI, /* Ubisoft RAKI header (Rayman Legends, Just Dance 2017) */ + meta_SXD, /* Sony SXD (Gravity Rush, Freedom Wars PSV) */ + meta_OGL, /* Shin'en Wii/WiiU (Jett Rocket (Wii), FAST Racing NEO (WiiU)) */ + meta_MC3, /* Paradigm games (T3 PS2, MX Rider PS2, MI: Operation Surma PS2) */ + meta_GHS, + meta_AAC_TRIACE, + meta_MTA2, + meta_NGC_ULW, /* Burnout 1 (GC only) */ + meta_XA_XA30, + meta_XA_04SW, + meta_TXTH, /* generic text header */ + meta_SK_AUD, /* Silicon Knights .AUD (Eternal Darkness GC) */ + meta_AHX, + meta_STM, /* Angel Studios/Rockstar San Diego Games */ + meta_BINK, /* RAD Game Tools BINK audio/video */ + meta_EA_SNU, /* Electronic Arts SNU (Dead Space) */ + meta_AWC, /* Rockstar AWC (GTA5, RDR) */ + meta_OPUS, /* Nintendo Opus [Lego City Undercover (Switch)] */ + meta_RAW_AL, + meta_PC_AST, /* Dead Rising (PC) */ + meta_NAAC, /* Namco AAC (3DS) */ + meta_UBI_SB, /* Ubisoft banks */ + meta_EZW, /* EZ2DJ (Arcade) EZWAV */ + meta_VXN, /* Gameloft mobile games */ + meta_EA_SNR_SNS, /* Electronic Arts SNR+SNS (Burnout Paradise) */ + meta_EA_SPS, /* Electronic Arts SPS (Burnout Crash) */ + meta_VID1, + meta_PC_FLX, /* Ultima IX PC */ + meta_MOGG, /* Harmonix Music Systems MOGG Vorbis */ + meta_OGG_VORBIS, /* Ogg Vorbis */ + meta_OGG_SLI, /* Ogg Vorbis file w/ companion .sli for looping */ + meta_OPUS_SLI, /* Ogg Opus file w/ companion .sli for looping */ + meta_OGG_SFL, /* Ogg Vorbis file w/ .sfl (RIFF SFPL) for looping */ + meta_OGG_KOVS, /* Ogg Vorbis with header and encryption (Koei Tecmo Games) */ + meta_OGG_encrypted, /* Ogg Vorbis with encryption */ + meta_KMA9, /* Koei Tecmo [Nobunaga no Yabou - Souzou (Vita)] */ + meta_XWC, /* Starbreeze games */ + meta_SQEX_SAB, /* Square-Enix newest middleware (sound) */ + meta_SQEX_MAB, /* Square-Enix newest middleware (music) */ + meta_WAF, /* KID WAF [Ever 17 (PC)] */ + meta_WAVE, /* EngineBlack games [Mighty Switch Force! (3DS)] */ + meta_WAVE_segmented, /* EngineBlack games, segmented [Shantae and the Pirate's Curse (PC)] */ + meta_SMV, /* Cho Aniki Zero (PSP) */ + meta_NXAP, /* Nex Entertainment games [Time Crisis 4 (PS3), Time Crisis Razing Storm (PS3)] */ + meta_EA_WVE_AU00, /* Electronic Arts PS movies [Future Cop - L.A.P.D. (PS), Supercross 2000 (PS)] */ + meta_EA_WVE_AD10, /* Electronic Arts PS movies [Wing Commander 3/4 (PS)] */ + meta_STHD, /* STHD .stx [Kakuto Chojin (Xbox)] */ + meta_MP4, /* MP4/AAC */ + meta_PCM_SRE, /* .PCM+SRE [Viewtiful Joe (PS2)] */ + meta_DSP_MCADPCM, /* Skyrim (Switch) */ + meta_UBI_LYN, /* Ubisoft LyN engine [The Adventures of Tintin (multi)] */ + meta_MSB_MSH, /* sfx companion of MIH+MIB */ + meta_TXTP, /* generic text playlist */ + meta_SMC_SMH, /* Wangan Midnight (System 246) */ + meta_PPST, /* PPST [Parappa the Rapper (PSP)] */ + meta_SPS_N1, + meta_UBI_BAO, /* Ubisoft BAO */ + meta_DSP_SWITCH_AUDIO, /* Gal Gun 2 (Switch) */ + meta_H4M, /* Hudson HVQM4 video [Resident Evil 0 (GC), Tales of Symphonia (GC)] */ + meta_ASF, /* Argonaut ASF [Croc 2 (PC)] */ + meta_XMD, /* Konami XMD [Silent Hill 4 (Xbox), Castlevania: Curse of Darkness (Xbox)] */ + meta_CKS, /* Cricket Audio stream [Part Time UFO (Android), Mega Man 1-6 (Android)] */ + meta_CKB, /* Cricket Audio bank [Fire Emblem Heroes (Android), Mega Man 1-6 (Android)] */ + meta_WV6, /* Gorilla Systems PC games */ + meta_WAVEBATCH, /* Firebrand Games */ + meta_HD3_BD3, /* Sony PS3 bank */ + meta_BNK_SONY, /* Sony Scream Tool bank */ + meta_SSCF, + meta_DSP_VAG, /* Penny-Punching Princess (Switch) sfx */ + meta_DSP_ITL, /* Charinko Hero (GC) */ + meta_A2M, /* Scooby-Doo! Unmasked (PS2) */ + meta_AHV, /* Headhunter (PS2) */ + meta_MSV, + meta_SDF, + meta_SVG, /* Hunter - The Reckoning - Wayward (PS2) */ + meta_VIS, /* AirForce Delta Strike (PS2) */ + meta_VAI, /* Ratatouille (GC) */ + meta_AIF_ASOBO, /* Ratatouille (PC) */ + meta_AO, /* Cloudphobia (PC) */ + meta_APC, /* MegaRace 3 (PC) */ + meta_WV2, /* Slave Zero (PC) */ + meta_XAU_KONAMI, /* Yu-Gi-Oh - The Dawn of Destiny (Xbox) */ + meta_DERF, /* Stupid Invaders (PC) */ + meta_SADF, + meta_UTK, + meta_NXA, + meta_ADPCM_CAPCOM, + meta_UE4OPUS, + meta_XWMA, + meta_VA3, /* DDR Supernova 2 AC */ + meta_XOPUS, + meta_VS_SQUARE, + meta_NWAV, + meta_XPCM, + meta_MSF_TAMASOFT, + meta_XPS_DAT, + meta_ZSND, + meta_DSP_ADPY, + meta_DSP_ADPX, + meta_OGG_OPUS, + meta_IMC, + meta_GIN, + meta_DSF, + meta_208, + meta_DSP_DS2, + meta_MUS_VC, + meta_STRM_ABYLIGHT, + meta_MSF_KONAMI, + meta_XWMA_KONAMI, + meta_9TAV, + meta_BWAV, + meta_RAD, + meta_SMACKER, + meta_MZRT, + meta_XAVS, + meta_PSF, + meta_DSP_ITL_i, + meta_IMA, + meta_XWV_VALVE, + meta_UBI_HX, + meta_BMP_KONAMI, + meta_ISB, + meta_XSSB, + meta_XMA_UE3, + meta_FWSE, + meta_FDA, + meta_TGC, + meta_KWB, + meta_LRMD, + meta_WWISE_FX, + meta_DIVA, + meta_IMUSE, + meta_KTSR, + meta_KAT, + meta_PCM_SUCCESS, + meta_ADP_KONAMI, + meta_SDRH, + meta_WADY, + meta_DSP_SQEX, + meta_DSP_WIIVOICE, + meta_SBK, + meta_DSP_WIIADPCM, + meta_DSP_CWAC, + meta_COMPRESSWAVE, + meta_KTAC, + meta_MJB_MJH, + meta_BSNF, + meta_TAC, + meta_IDSP_TOSE, + meta_DSP_KWA, + meta_OGV_3RDEYE, + meta_PIFF_TPCM, + meta_WXD_WXH, + meta_BNK_RELIC, + meta_XSH_XSD_XSS, + meta_PSB, + meta_LOPU_FB, + meta_LPCM_FB, + meta_WBK, + meta_WBK_NSLB, + meta_DSP_APEX, + meta_MPEG, + meta_SSPF, + meta_S3V, + meta_ESF, + meta_ADM3, + meta_TT_AD, + meta_SNDZ, + meta_VAB, + meta_BIGRP, +} \ No newline at end of file diff --git a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs index 11670ce..684534e 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs @@ -1,10 +1,15 @@ -namespace Kermalis.VGMusicStudio.Core.NDS.DSE; +using Kermalis.VGMusicStudio.Core.Codec; +using Kermalis.VGMusicStudio.Core.Wii; +using System; + +namespace Kermalis.VGMusicStudio.Core.NDS.DSE; internal sealed class DSEChannel { public readonly byte Index; public DSETrack? Owner; + public string? SWDType; public EnvelopeState State; public byte RootKey; public byte Key; @@ -32,14 +37,22 @@ internal sealed class DSEChannel private byte _decay2; private byte _release; - // PCM8, PCM16, ADPCM - private SWD.SampleBlock _sample; + // PCM8, PCM16, ADPCM, DSP-ADPCM + private SWD.SampleBlock? _sample; // PCM8, PCM16 private int _dataOffset; // ADPCM - private ADPCMDecoder _adpcmDecoder; + private ADPCMDecoder? _adpcmDecoder; private short _adpcmLoopLastSample; private short _adpcmLoopStepIndex; + // DSP-ADPCM + //private DSPADPCM dspADPCM = new DSPADPCM(); + //private short[] _loopContext; + //private DSPADPCM _outputData; + //private DSPADPCM? _dspADPCM; + // PSG + private byte _psgDuty; + private int _psgCounter; public DSEChannel(byte i) { @@ -49,7 +62,19 @@ public DSEChannel(byte i) public bool StartPCM(SWD localswd, SWD masterswd, byte voice, int key, uint noteLength) { - SWD.IProgramInfo? programInfo = localswd.Programs?.ProgramInfos[voice]; + if (localswd == null) { SWDType = masterswd.Type; } + else { SWDType = localswd.Type; } + + SWD.IProgramInfo? programInfo = null; // Declaring Program Info Interface here, to ensure VGMS compiles + if (localswd == null) + { + // Failsafe to check if SWD.ProgramBank contains an instance, if it doesn't, it will be skipped + // This is especially important for initializing a main SWD before the local SWDs + // accompaning the SMDs with the same names are loaded in. + if (masterswd.Programs != null) { programInfo = masterswd.Programs!.ProgramInfos![voice]; } + } + else { programInfo = localswd.Programs!.ProgramInfos![voice]; } + if (programInfo is null) { return false; @@ -63,47 +88,82 @@ public bool StartPCM(SWD localswd, SWD masterswd, byte voice, int key, uint note continue; } + //if (_sample == null) { throw new NullReferenceException("Null Reference Exception:\n\nThere's no data associated with this Sample Block in this SWD. Please check to make sure the samples are being read correctly.\n\nCall Stack:"); } _sample = masterswd.Samples![split.SampleId]; Key = (byte)key; RootKey = split.SampleRootKey; - BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo.SampleRate); - if (_sample.WavInfo.SampleFormat == SampleFormat.ADPCM) + if (_sample != null) { + switch (SWDType) // Configures the base timer based on the specific console's CPU and sample rate + { + case "wds ": throw new NotImplementedException("The base timer for the WDS type is not yet implemented."); // PlayStation + case "swdm": throw new NotImplementedException("The base timer for the SWDM type is not yet implemented."); // PlayStation 2 + case "swdl": BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo!.SampleRate); break; // Nintendo DS + case "swdb": BaseTimer = (ushort)(WiiUtils.PPC_Broadway_Clock + _sample.WavInfo!.SampleRate / 33); break; // Wii + } + if (_sample.WavInfo!.SampleFormat == SampleFormat.ADPCM) + { _adpcmDecoder.Init(_sample.Data); + } + //if (masterswd.Type == "swdb") + //{ + // _dspADPCM = _sample.DSPADPCM; + //} + //attackVolume = sample.WavInfo.AttackVolume == 0 ? split.AttackVolume : sample.WavInfo.AttackVolume; + //attack = sample.WavInfo.Attack == 0 ? split.Attack : sample.WavInfo.Attack; + //decay = sample.WavInfo.Decay == 0 ? split.Decay : sample.WavInfo.Decay; + //sustain = sample.WavInfo.Sustain == 0 ? split.Sustain : sample.WavInfo.Sustain; + //hold = sample.WavInfo.Hold == 0 ? split.Hold : sample.WavInfo.Hold; + //decay2 = sample.WavInfo.Decay2 == 0 ? split.Decay2 : sample.WavInfo.Decay2; + //release = sample.WavInfo.Release == 0 ? split.Release : sample.WavInfo.Release; + //attackVolume = split.AttackVolume == 0 ? sample.WavInfo.AttackVolume : split.AttackVolume; + //attack = split.Attack == 0 ? sample.WavInfo.Attack : split.Attack; + //decay = split.Decay == 0 ? sample.WavInfo.Decay : split.Decay; + //sustain = split.Sustain == 0 ? sample.WavInfo.Sustain : split.Sustain; + //hold = split.Hold == 0 ? sample.WavInfo.Hold : split.Hold; + //decay2 = split.Decay2 == 0 ? sample.WavInfo.Decay2 : split.Decay2; + //release = split.Release == 0 ? sample.WavInfo.Release : split.Release; + _attackVolume = split.AttackVolume == 0 ? _sample.WavInfo.AttackVolume == 0 ? (byte)0x7F : _sample.WavInfo.AttackVolume : split.AttackVolume; + _attack = split.Attack == 0 ? _sample.WavInfo.Attack == 0 ? (byte)0x7F : _sample.WavInfo.Attack : split.Attack; + _decay = split.Decay1 == 0 ? _sample.WavInfo.Decay1 == 0 ? (byte)0x7F : _sample.WavInfo.Decay1 : split.Decay1; + _sustain = split.Sustain == 0 ? _sample.WavInfo.Sustain == 0 ? (byte)0x7F : _sample.WavInfo.Sustain : split.Sustain; + _hold = split.Hold == 0 ? _sample.WavInfo.Hold == 0 ? (byte)0x7F : _sample.WavInfo.Hold : split.Hold; + _decay2 = split.Decay2 == 0 ? _sample.WavInfo.Decay2 == 0 ? (byte)0x7F : _sample.WavInfo.Decay2 : split.Decay2; + _release = split.Release == 0 ? _sample.WavInfo.Release == 0 ? (byte)0x7F : _sample.WavInfo.Release : split.Release; + DetermineEnvelopeStartingPoint(); + _pos = 0; + _prevLeft = _prevRight = 0; + NoteLength = noteLength; + return true; } - //attackVolume = sample.WavInfo.AttackVolume == 0 ? split.AttackVolume : sample.WavInfo.AttackVolume; - //attack = sample.WavInfo.Attack == 0 ? split.Attack : sample.WavInfo.Attack; - //decay = sample.WavInfo.Decay == 0 ? split.Decay : sample.WavInfo.Decay; - //sustain = sample.WavInfo.Sustain == 0 ? split.Sustain : sample.WavInfo.Sustain; - //hold = sample.WavInfo.Hold == 0 ? split.Hold : sample.WavInfo.Hold; - //decay2 = sample.WavInfo.Decay2 == 0 ? split.Decay2 : sample.WavInfo.Decay2; - //release = sample.WavInfo.Release == 0 ? split.Release : sample.WavInfo.Release; - //attackVolume = split.AttackVolume == 0 ? sample.WavInfo.AttackVolume : split.AttackVolume; - //attack = split.Attack == 0 ? sample.WavInfo.Attack : split.Attack; - //decay = split.Decay == 0 ? sample.WavInfo.Decay : split.Decay; - //sustain = split.Sustain == 0 ? sample.WavInfo.Sustain : split.Sustain; - //hold = split.Hold == 0 ? sample.WavInfo.Hold : split.Hold; - //decay2 = split.Decay2 == 0 ? sample.WavInfo.Decay2 : split.Decay2; - //release = split.Release == 0 ? sample.WavInfo.Release : split.Release; - _attackVolume = split.AttackVolume == 0 ? _sample.WavInfo.AttackVolume == 0 ? (byte)0x7F : _sample.WavInfo.AttackVolume : split.AttackVolume; - _attack = split.Attack == 0 ? _sample.WavInfo.Attack == 0 ? (byte)0x7F : _sample.WavInfo.Attack : split.Attack; - _decay = split.Decay == 0 ? _sample.WavInfo.Decay == 0 ? (byte)0x7F : _sample.WavInfo.Decay : split.Decay; - _sustain = split.Sustain == 0 ? _sample.WavInfo.Sustain == 0 ? (byte)0x7F : _sample.WavInfo.Sustain : split.Sustain; - _hold = split.Hold == 0 ? _sample.WavInfo.Hold == 0 ? (byte)0x7F : _sample.WavInfo.Hold : split.Hold; - _decay2 = split.Decay2 == 0 ? _sample.WavInfo.Decay2 == 0 ? (byte)0x7F : _sample.WavInfo.Decay2 : split.Decay2; - _release = split.Release == 0 ? _sample.WavInfo.Release == 0 ? (byte)0x7F : _sample.WavInfo.Release : split.Release; - DetermineEnvelopeStartingPoint(); - _pos = 0; - _prevLeft = _prevRight = 0; - NoteLength = noteLength; - return true; } return false; } + public void StartPSG(byte duty, uint noteDuration) + { + _sample!.WavInfo!.SampleFormat = SampleFormat.PSG; + _psgCounter = 0; + _psgDuty = duty; + BaseTimer = 8006; // NDSUtils.ARM7_CLOCK / 2093 + Start(noteDuration); + } + + private void Start(uint noteDuration) + { + State = EnvelopeState.One; + _velocity = -92544; + _pos = 0; + _prevLeft = _prevRight = 0; + NoteLength = noteDuration; + } + public void Stop() { - Owner?.Channels.Remove(this); + if (Owner is not null) + { + Owner.Channels.Remove(this); + } Owner = null; Volume = 0; } @@ -111,9 +171,9 @@ public void Stop() private bool CMDB1___sub_2074CA0() { bool b = true; - bool ge = _sample.WavInfo.EnvMult >= 0x7F; - bool ee = _sample.WavInfo.EnvMult == 0x7F; - if (_sample.WavInfo.EnvMult > 0x7F) + bool ge = _sample!.WavInfo!.EnvMulti >= 0x7F; + bool ee = _sample.WavInfo.EnvMulti == 0x7F; + if (_sample.WavInfo.EnvMulti > 0x7F) { ge = _attackVolume >= 0x7F; ee = _attackVolume == 0x7F; @@ -271,9 +331,9 @@ private void UpdateEnvelopePlan(byte targetVolume, int envelopeParam) else { _targetVolume = targetVolume; - _envelopeTimeLeft = _sample.WavInfo.EnvMult == 0 + _envelopeTimeLeft = _sample!.WavInfo!.EnvMulti == 0 ? DSEUtils.Duration32[envelopeParam] * 1_000 / 10_000 - : DSEUtils.Duration16[envelopeParam] * _sample.WavInfo.EnvMult * 1_000 / 10_000; + : DSEUtils.Duration16[envelopeParam] * _sample.WavInfo.EnvMulti * 1_000 / 10_000; _volumeIncrement = _envelopeTimeLeft == 0 ? 0 : ((targetVolume << 23) - _velocity) / _envelopeTimeLeft; } } @@ -292,80 +352,122 @@ public void Process(out short left, out short right) // prevLeft and prevRight are stored because numSamples can be 0. for (int i = 0; i < numSamples; i++) { - short samp; - switch (_sample.WavInfo.SampleFormat) + switch (SWDType) { - case SampleFormat.PCM8: - { - // If hit end - if (_dataOffset >= _sample.Data.Length) - { - if (_sample.WavInfo.Loop) - { - _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); - } - else - { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; - } - } - samp = (short)((sbyte)_sample.Data[_dataOffset++] << 8); - break; - } - case SampleFormat.PCM16: - { - // If hit end - if (_dataOffset >= _sample.Data.Length) + case "wds ": + case "swdm": + case "swdl": { - if (_sample.WavInfo.Loop) + short samp; + switch (_sample!.WavInfo!.SampleFormat) { - _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + case SampleFormat.PCM8: + { + // If hit end + if (_dataOffset >= _sample.Data!.Length) + { + if (_sample.WavInfo.Loop) + { + _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + samp = (short)((sbyte)_sample.Data[_dataOffset++] << 8); + break; + } + case SampleFormat.PCM16: + { + // If hit end + if (_dataOffset >= _sample.Data!.Length) + { + if (_sample.WavInfo.Loop) + { + _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + samp = (short)(_sample.Data[_dataOffset++] | (_sample.Data[_dataOffset++] << 8)); + break; + } + case SampleFormat.ADPCM: + { + // If just looped + if (_adpcmDecoder!.DataOffset == _sample.WavInfo.LoopStart * 4 && !_adpcmDecoder.OnSecondNibble) + { + _adpcmLoopLastSample = _adpcmDecoder.LastSample; + _adpcmLoopStepIndex = _adpcmDecoder.StepIndex; + } + // If hit end + if (_adpcmDecoder.DataOffset >= _sample.Data!.Length && !_adpcmDecoder.OnSecondNibble) + { + if (_sample.WavInfo.Loop) + { + _adpcmDecoder.DataOffset = (int)(_sample.WavInfo.LoopStart * 4); + _adpcmDecoder.StepIndex = _adpcmLoopStepIndex; + _adpcmDecoder.LastSample = _adpcmLoopLastSample; + _adpcmDecoder.OnSecondNibble = false; + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } + } + samp = _adpcmDecoder.GetSample(); + break; + } + case SampleFormat.PSG: + { + samp = _psgCounter <= _psgDuty ? short.MinValue : short.MaxValue; + _psgCounter++; + if (_psgCounter >= 8) + { + _psgCounter = 0; + } + break; + } + default: samp = 0; break; } - else - { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; - } - } - samp = (short)(_sample.Data[_dataOffset++] | (_sample.Data[_dataOffset++] << 8)); - break; - } - case SampleFormat.ADPCM: - { - // If just looped - if (_adpcmDecoder.DataOffset == _sample.WavInfo.LoopStart * 4 && !_adpcmDecoder.OnSecondNibble) - { - _adpcmLoopLastSample = _adpcmDecoder.LastSample; - _adpcmLoopStepIndex = _adpcmDecoder.StepIndex; + samp = (short)(samp * Volume / 0x7F); + _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); + _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); + break; } - // If hit end - if (_adpcmDecoder.DataOffset >= _sample.Data.Length && !_adpcmDecoder.OnSecondNibble) + case "swdb": { - if (_sample.WavInfo.Loop) - { - _adpcmDecoder.DataOffset = (int)(_sample.WavInfo.LoopStart * 4); - _adpcmDecoder.StepIndex = _adpcmLoopStepIndex; - _adpcmDecoder.LastSample = _adpcmLoopLastSample; - _adpcmDecoder.OnSecondNibble = false; - } - else + // If hit end + if (_dataOffset >= _sample!.Data!.Length) { - left = right = _prevLeft = _prevRight = 0; - Stop(); - return; + if (_sample.WavInfo!.Loop) + { + _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + } + else + { + left = right = _prevLeft = _prevRight = 0; + Stop(); + return; + } } + if (_dataOffset + 10 > _sample.Data.Length) { break; } + short samp = (short)(_sample.Data[_dataOffset++] | (_sample.Data[_dataOffset++] << 8)); + samp = (short)(samp * Volume / 0x7F); + _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); + _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); + break; } - samp = _adpcmDecoder.GetSample(); - break; - } - default: samp = 0; break; } - samp = (short)(samp * Volume / 0x7F); - _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); - _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); } left = _prevLeft; right = _prevRight; diff --git a/VG Music Studio - Core/NDS/DSE/DSECommands.cs b/VG Music Studio - Core/NDS/DSE/DSECommands.cs index 76e8e5b..f173227 100644 --- a/VG Music Studio - Core/NDS/DSE/DSECommands.cs +++ b/VG Music Studio - Core/NDS/DSE/DSECommands.cs @@ -85,6 +85,14 @@ internal sealed class RestCommand : ICommand public uint Rest { get; set; } } +internal sealed class CheckIntervalCommand : ICommand +{ + public Color Color => Color.DarkViolet; + public string Label => "Check Interval"; + public string Arguments => Interval.ToString(); + + public uint Interval { get; set; } +} internal sealed class SkipBytesCommand : ICommand { public Color Color => Color.MediumVioletRed; diff --git a/VG Music Studio - Core/NDS/DSE/DSEConfig.cs b/VG Music Studio - Core/NDS/DSE/DSEConfig.cs index 6a68eed..d0cb474 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEConfig.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEConfig.cs @@ -7,29 +7,39 @@ namespace Kermalis.VGMusicStudio.Core.NDS.DSE; public sealed class DSEConfig : Config { - public readonly string BGMPath; - public readonly string[] BGMFiles; + public readonly string SMDPath; + public readonly string[] SMDFiles; - internal DSEConfig(string bgmPath) + internal DSEConfig(string smdPath) { - BGMPath = bgmPath; - BGMFiles = Directory.GetFiles(bgmPath, "bgm*.smd", SearchOption.TopDirectoryOnly); - if (BGMFiles.Length == 0) + SMDPath = smdPath; + SMDFiles = Directory.GetFiles(smdPath, "*.smd", SearchOption.TopDirectoryOnly); + if (SMDFiles.Length == 0) { - throw new DSENoSequencesException(bgmPath); + throw new DSENoSequencesException(smdPath); } // TODO: Big endian files - var songs = new List(BGMFiles.Length); - for (int i = 0; i < BGMFiles.Length; i++) + var songs = new List(SMDFiles.Length); + for (int i = 0; i < SMDFiles.Length; i++) { - using (FileStream stream = File.OpenRead(BGMFiles[i])) + using (FileStream stream = File.OpenRead(SMDFiles[i])) { var r = new EndianBinaryReader(stream, ascii: true); - SMD.Header header = r.ReadObject(); - char[] chars = header.Label.ToCharArray(); - EndianBinaryPrimitives.TrimNullTerminators(ref chars); - songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(BGMFiles[i])} - {new string(chars)}")); + SMD.Header header = new SMD.Header(r); + if(header.Type == "smdl") + { + char[] chars = header.Label.ToCharArray(); + EndianBinaryPrimitives.TrimNullTerminators(ref chars); + songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(SMDFiles[i])} - {new string(chars)}")); + } + else if(header.Type == "smdb") + { + r.Endianness = Endianness.BigEndian; + char[] chars = header.Label.ToCharArray(); + EndianBinaryPrimitives.TrimNullTerminators(ref chars); + songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(SMDFiles[i])} - {new string(chars)}")); + } } } Playlists.Add(new Playlist(Strings.PlaylistMusic, songs)); @@ -41,8 +51,8 @@ public override string GetGameName() } public override string GetSongName(int index) { - return index < 0 || index >= BGMFiles.Length + return index < 0 || index >= SMDFiles.Length ? index.ToString() - : '\"' + BGMFiles[index] + '\"'; + : '\"' + SMDFiles[index] + '\"'; } } diff --git a/VG Music Studio - Core/NDS/DSE/DSEEngine.cs b/VG Music Studio - Core/NDS/DSE/DSEEngine.cs index a7a933e..0b064ba 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEEngine.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEEngine.cs @@ -1,4 +1,6 @@ -namespace Kermalis.VGMusicStudio.Core.NDS.DSE; +using System; + +namespace Kermalis.VGMusicStudio.Core.NDS.DSE; public sealed class DSEEngine : Engine { @@ -8,11 +10,11 @@ public sealed class DSEEngine : Engine public override DSEMixer Mixer { get; } public override DSEPlayer Player { get; } - public DSEEngine(string bgmPath) + public DSEEngine(string mainSWDFile, string bgmPath) { Config = new DSEConfig(bgmPath); Mixer = new DSEMixer(); - Player = new DSEPlayer(Config, Mixer); + Player = new DSEPlayer(mainSWDFile, Config, Mixer); DSEInstance = this; Instance = this; diff --git a/VG Music Studio - Core/NDS/DSE/DSEEnums.cs b/VG Music Studio - Core/NDS/DSE/DSEEnums.cs index 911c5f0..e10ca02 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEEnums.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEEnums.cs @@ -15,7 +15,8 @@ internal enum EnvelopeState : byte internal enum SampleFormat : ushort { - PCM8 = 0x000, - PCM16 = 0x100, - ADPCM = 0x200, + PCM8 = 0, + PCM16 = 1, + ADPCM = 2, + PSG = 3 } diff --git a/VG Music Studio - Core/NDS/DSE/DSEExceptions.cs b/VG Music Studio - Core/NDS/DSE/DSEExceptions.cs index 82c22e9..149ee6d 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEExceptions.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEExceptions.cs @@ -4,11 +4,11 @@ namespace Kermalis.VGMusicStudio.Core.NDS.DSE; public sealed class DSENoSequencesException : Exception { - public string BGMPath { get; } + public string SMDPath { get; } internal DSENoSequencesException(string bgmPath) { - BGMPath = bgmPath; + SMDPath = bgmPath; } } diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs index 2cee106..dd53e10 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs @@ -2,6 +2,7 @@ using Kermalis.VGMusicStudio.Core.Util; using System.Collections.Generic; using System.IO; +using static Kermalis.VGMusicStudio.Core.NDS.DSE.SMD; namespace Kermalis.VGMusicStudio.Core.NDS.DSE; @@ -12,35 +13,32 @@ internal sealed partial class DSELoadedSong : ILoadedSong public int LongestTrack; private readonly DSEPlayer _player; - private readonly SWD LocalSWD; + private readonly string SWDFileName; + private readonly string SMDFileName; + private readonly SWD? LocalSWD; private readonly byte[] SMDFile; public readonly DSETrack[] Tracks; public DSELoadedSong(DSEPlayer player, string bgm) { _player = player; + SWDFileName = bgm; + SMDFileName = bgm; + //StringComparison comparison = StringComparison.CurrentCultureIgnoreCase; + + // Check if a local SWD is accompaning a SMD + if (new FileInfo(Path.ChangeExtension(bgm, "swd")).Exists) + { + LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); // If it exists, this will be loaded as the local SWD + } - LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); SMDFile = File.ReadAllBytes(bgm); using (var stream = new MemoryStream(SMDFile)) { var r = new EndianBinaryReader(stream, ascii: true); - SMD.Header header = r.ReadObject(); - SMD.ISongChunk songChunk; - switch (header.Version) - { - case 0x402: - { - songChunk = r.ReadObject(); - break; - } - case 0x415: - { - songChunk = r.ReadObject(); - break; - } - default: throw new DSEInvalidHeaderVersionException(header.Version); - } + Header header = new Header(r); + if (header.Version != 0x415) { throw new DSEInvalidHeaderVersionException(header.Version); } + SongChunk songChunk = new SongChunk(r); Tracks = new DSETrack[songChunk.NumTracks]; Events = new List[songChunk.NumTracks]; diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs index b37dde9..f9c853e 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs @@ -1,4 +1,5 @@ -using Kermalis.EndianBinaryIO; +using Kermalis.VGMusicStudio.Core.Util; +using Kermalis.VGMusicStudio.Core.Util.EndianBinaryExtras; using System; using System.Collections.Generic; using System.Linq; @@ -108,274 +109,973 @@ private void AddTrackEvents(byte trackIndex, EndianBinaryReader r) } case 0x94: { - lastRest = (uint)(r.ReadByte() | (r.ReadByte() << 8) | (r.ReadByte() << 16)); + lastRest = r.ReadUInt24(); if (!EventExists(trackIndex, cmdOffset)) { AddEvent(trackIndex, cmdOffset, new RestCommand { Rest = lastRest }); } break; } - case 0x96: - case 0x97: - case 0x9A: - case 0x9B: - case 0x9F: - case 0xA2: - case 0xA3: - case 0xA6: - case 0xA7: - case 0xAD: - case 0xAE: - case 0xB7: - case 0xB8: - case 0xB9: - case 0xBA: - case 0xBB: - case 0xBD: - case 0xC1: - case 0xC2: - case 0xC4: - case 0xC5: - case 0xC6: - case 0xC7: - case 0xC8: - case 0xC9: - case 0xCA: - case 0xCC: - case 0xCD: - case 0xCE: - case 0xCF: - case 0xD9: - case 0xDA: - case 0xDE: - case 0xE6: - case 0xEB: - case 0xEE: - case 0xF4: - case 0xF5: - case 0xF7: - case 0xF9: - case 0xFA: - case 0xFB: - case 0xFC: - case 0xFD: - case 0xFE: - case 0xFF: + case 0x95: { + uint intervals = r.ReadByte(); if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + AddEvent(trackIndex, cmdOffset, new CheckIntervalCommand { Interval = intervals }); } break; } - case 0x98: + case 0x96: { if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new FinishCommand()); + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); } - cont = false; break; } - case 0x99: + case 0x97: { if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new LoopStartCommand { Offset = r.Stream.Position }); + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); } break; } - case 0xA0: + case 0x98: { - byte octave = r.ReadByte(); if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new OctaveSetCommand { Octave = octave }); + AddEvent(trackIndex, cmdOffset, new FinishCommand()); + r.Stream.Align(4); } + cont = false; break; } + case 0x99: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new LoopStartCommand { Offset = r.Stream.Position }); + } + break; + } + case 0x9A: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0x9B: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0x9C: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0x9D: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd }); + } + break; + } + case 0x9E: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd }); + } + break; + } + case 0x9F: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xA0: + { + byte octave = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new OctaveSetCommand { Octave = octave }); + } + break; + } case 0xA1: - { - sbyte change = r.ReadSByte(); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new OctaveAddCommand { OctaveChange = change }); + sbyte change = r.ReadSByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new OctaveAddCommand { OctaveChange = change }); + } + break; + } + case 0xA2: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xA3: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } case 0xA4: + { + byte tempoArg = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new TempoCommand { Command = cmd, Tempo = tempoArg }); + } + break; + } case 0xA5: // The code for these two is identical - { - byte tempoArg = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new TempoCommand { Command = cmd, Tempo = tempoArg }); + byte tempoArg = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new TempoCommand { Command = cmd, Tempo = tempoArg }); + } + break; } - break; - } - case 0xAB: - { - byte[] bytes = new byte[1]; - r.ReadBytes(bytes); - if (!EventExists(trackIndex, cmdOffset)) + case 0xA6: { - AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } - case 0xAC: - { - byte voice = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xA7: { - AddEvent(trackIndex, cmdOffset, new VoiceCommand { Voice = voice }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } - case 0xCB: - case 0xF8: - { - byte[] bytes = new byte[2]; - r.ReadBytes(bytes); - if (!EventExists(trackIndex, cmdOffset)) + case 0xA8: { - AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xD7: - { - ushort bend = r.ReadUInt16(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xA9: { - AddEvent(trackIndex, cmdOffset, new PitchBendCommand { Bend = bend }); + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xE0: - { - byte volume = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xAA: { - AddEvent(trackIndex, cmdOffset, new VolumeCommand { Volume = volume }); + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xE3: - { - byte expression = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xAB: { - AddEvent(trackIndex, cmdOffset, new ExpressionCommand { Expression = expression }); + byte[] bytes = new byte[1]; + r.ReadBytes(bytes); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + } + break; } - break; - } - case 0xE8: - { - byte panArg = r.ReadByte(); - if (!EventExists(trackIndex, cmdOffset)) + case 0xAC: { - AddEvent(trackIndex, cmdOffset, new PanpotCommand { Panpot = (sbyte)(panArg - 0x40) }); + byte voice = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new VoiceCommand { Voice = voice }); + } + break; + } + case 0xAD: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xAE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xAF: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0x9D: case 0xB0: - case 0xC0: - { - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = Array.Empty() }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd }); + } + break; } - break; - } - case 0x9C: - case 0xA9: - case 0xAA: case 0xB1: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xB2: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xB3: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xB4: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xB5: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xB6: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xB7: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xB8: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xB9: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xBA: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xBB: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xBC: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xBD: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xBE: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xBF: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xC0: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xC1: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC2: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xC3: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xC4: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC5: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC6: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC7: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC8: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xC9: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCA: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCB: + { + byte[] bytes = new byte[2]; + r.ReadBytes(bytes); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + } + break; + } + case 0xCC: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCD: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xCF: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xD0: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xD1: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xD2: - case 0xDB: - case 0xDF: - case 0xE1: - case 0xE7: - case 0xE9: - case 0xEF: - case 0xF6: - { - byte[] args = new byte[1]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xA8: - case 0xB4: case 0xD3: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xD4: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xD5: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xD6: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xD7: + { + ushort bend = r.ReadUInt16(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new PitchBendCommand { Bend = bend }); + } + break; + } case 0xD8: - case 0xF2: - { - byte[] args = new byte[2]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } - case 0xAF: - case 0xD4: - case 0xE2: - case 0xEA: - case 0xF3: - { - byte[] args = new byte[3]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) + case 0xD9: { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } - case 0xDD: - case 0xE5: - case 0xED: - case 0xF1: - { - byte[] args = new byte[4]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) + case 0xDA: { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xDB: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; } - break; - } case 0xDC: + { + byte[] args = new byte[5]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xDD: + { + byte[] args = new byte[4]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xDE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xDF: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE0: + { + byte volume = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new VolumeCommand { Volume = volume }); + } + break; + } + case 0xE1: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE2: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE3: + { + byte expression = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new ExpressionCommand { Expression = expression }); + } + break; + } case 0xE4: + { + byte[] args = new byte[5]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE5: + { + byte[] args = new byte[4]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE6: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xE7: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xE8: + { + byte panArg = r.ReadByte(); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new PanpotCommand { Panpot = (sbyte)(panArg - 0x40) }); + } + break; + } + case 0xE9: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xEA: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xEB: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } case 0xEC: + { + byte[] args = new byte[5]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xED: + { + byte[] args = new byte[4]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xEE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xEF: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } case 0xF0: - { - byte[] args = new byte[5]; - r.ReadBytes(args); - if (!EventExists(trackIndex, cmdOffset)) { - AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + byte[] args = new byte[5]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF1: + { + byte[] args = new byte[4]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF2: + { + byte[] args = new byte[2]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF3: + { + byte[] args = new byte[3]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF4: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xF5: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xF6: + { + byte[] args = new byte[1]; + r.ReadBytes(args); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new UnknownCommand { Command = cmd, Args = args }); + } + break; + } + case 0xF7: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xF8: + { + byte[] bytes = new byte[2]; + r.ReadBytes(bytes); + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new SkipBytesCommand { Command = cmd, SkippedBytes = bytes }); + } + break; + } + case 0xF9: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFA: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFB: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFC: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFD: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFE: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; + } + case 0xFF: + { + if (!EventExists(trackIndex, cmdOffset)) + { + AddEvent(trackIndex, cmdOffset, new InvalidCommand { Command = cmd }); + } + break; } - break; - } default: throw new DSEInvalidCMDException(trackIndex, (int)cmdOffset, cmd); } } diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs index 90e30e4..ac12c27 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs @@ -45,7 +45,7 @@ public void ExecuteNext(DSETrack track) channel.Stop(); track.Octave = (byte)(track.Octave + oct); - if (channel.StartPCM(LocalSWD, _player.MasterSWD, track.Voice, n + (12 * track.Octave), duration)) + if (channel.StartPCM(LocalSWD, _player.MainSWD, track.Voice, n + (12 * track.Octave), duration)) { channel.NoteVelocity = cmd; channel.Owner = track; diff --git a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs index bfdcda2..e04a997 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs @@ -8,7 +8,7 @@ public sealed class DSEPlayer : Player private readonly DSEConfig _config; internal readonly DSEMixer DMixer; - internal readonly SWD MasterSWD; + internal readonly SWD MainSWD; private DSELoadedSong? _loadedSong; internal byte Tempo; @@ -18,13 +18,14 @@ public sealed class DSEPlayer : Player public override ILoadedSong? LoadedSong => _loadedSong; protected override Mixer Mixer => DMixer; - public DSEPlayer(DSEConfig config, DSEMixer mixer) + public DSEPlayer(string mainSWDFile, DSEConfig config, DSEMixer mixer) : base(192) { DMixer = mixer; _config = config; + //string swdPath = Directory.GetFiles(mainSWDFile)[0]; - MasterSWD = new SWD(Path.Combine(config.BGMPath, "bgm.swd")); + MainSWD = new SWD(mainSWDFile); } public override void LoadSong(int index) @@ -35,7 +36,7 @@ public override void LoadSong(int index) } // If there's an exception, this will remain null - _loadedSong = new DSELoadedSong(this, _config.BGMFiles[index]); + _loadedSong = new DSELoadedSong(this, _config.SMDFiles[index]); _loadedSong.SetTicks(); } public override void UpdateSongState(SongState info) diff --git a/VG Music Studio - Core/NDS/DSE/SMD.cs b/VG Music Studio - Core/NDS/DSE/SMD.cs index e9a9083..36d4d13 100644 --- a/VG Music Studio - Core/NDS/DSE/SMD.cs +++ b/VG Music Studio - Core/NDS/DSE/SMD.cs @@ -6,14 +6,11 @@ internal sealed class SMD { public sealed class Header // Size 0x40 { - [BinaryStringFixedLength(4)] - public string Type { get; set; } = null!; // "smdb" or "smdl" - [BinaryArrayFixedLength(4)] - public byte[] Unknown1 { get; set; } = null!; + public string Type { get; set; } // "smdb" or "smdl" + public byte[] Unknown1 { get; set; } public uint Length { get; set; } public ushort Version { get; set; } - [BinaryArrayFixedLength(10)] - public byte[] Unknown2 { get; set; } = null!; + public byte[] Unknown2 { get; set; } public ushort Year { get; set; } public byte Month { get; set; } public byte Day { get; set; } @@ -21,40 +18,82 @@ public sealed class Header // Size 0x40 public byte Minute { get; set; } public byte Second { get; set; } public byte Centisecond { get; set; } - [BinaryStringFixedLength(16)] - public string Label { get; set; } = null!; - [BinaryArrayFixedLength(16)] - public byte[] Unknown3 { get; set; } = null!; + public string Label { get; set; } + public byte[] Unknown3 { get; set; } + + public Header(EndianBinaryReader r) + { + Type = r.ReadString_Count(4); + + if (Type == "smdb") { r.Endianness = Endianness.BigEndian; } + + Unknown1 = new byte[4]; + r.ReadBytes(Unknown1); + + Length = r.ReadUInt32(); + + Version = r.ReadUInt16(); + + Unknown2 = new byte[10]; + r.ReadBytes(Unknown2); + + r.Endianness = Endianness.LittleEndian; + + Year = r.ReadUInt16(); + + Month = r.ReadByte(); + + Day = r.ReadByte(); + + Hour = r.ReadByte(); + + Minute = r.ReadByte(); + + Second = r.ReadByte(); + + Centisecond = r.ReadByte(); + + Label = r.ReadString_Count(16); + + Unknown3 = new byte[16]; + r.ReadBytes(Unknown3); + + if (Type == "smdb") { r.Endianness = Endianness.BigEndian; } + } } public interface ISongChunk { byte NumTracks { get; } } - public sealed class SongChunk_V402 : ISongChunk // Size 0x20 - { - [BinaryStringFixedLength(4)] - public string Type { get; set; } = null!; - [BinaryArrayFixedLength(16)] - public byte[] Unknown1 { get; set; } = null!; - public byte NumTracks { get; set; } - public byte NumChannels { get; set; } - [BinaryArrayFixedLength(4)] - public byte[] Unknown2 { get; set; } = null!; - public sbyte MasterVolume { get; set; } - public sbyte MasterPanpot { get; set; } - [BinaryArrayFixedLength(4)] - public byte[] Unknown3 { get; set; } = null!; - } - public sealed class SongChunk_V415 : ISongChunk // Size 0x40 + public sealed class SongChunk : ISongChunk // Size 0x40 { - [BinaryStringFixedLength(4)] - public string Type { get; set; } = null!; - [BinaryArrayFixedLength(18)] - public byte[] Unknown1 { get; set; } = null!; + public string Type { get; set; } + public byte[] Unknown1 { get; set; } + public ushort TicksPerQuarter { get; set; } + public byte[] Unknown2 { get; set; } public byte NumTracks { get; set; } public byte NumChannels { get; set; } - [BinaryArrayFixedLength(40)] - public byte[] Unknown2 { get; set; } = null!; + public byte[] Unknown3 { get; set; } + + public SongChunk(EndianBinaryReader r) + { + Type = r.ReadString_Count(4); + + Unknown1 = new byte[14]; + r.ReadBytes(Unknown1); + + TicksPerQuarter = r.ReadUInt16(); + + Unknown2 = new byte[2]; + r.ReadBytes(Unknown2); + + NumTracks = r.ReadByte(); + + NumChannels = r.ReadByte(); + + Unknown3 = new byte[40]; + r.ReadBytes(Unknown3); + } } } diff --git a/VG Music Studio - Core/NDS/DSE/SWD.cs b/VG Music Studio - Core/NDS/DSE/SWD.cs index 90c28ad..1001a67 100644 --- a/VG Music Studio - Core/NDS/DSE/SWD.cs +++ b/VG Music Studio - Core/NDS/DSE/SWD.cs @@ -1,4 +1,5 @@ using Kermalis.EndianBinaryIO; +using Kermalis.VGMusicStudio.Core.Codec; using Kermalis.VGMusicStudio.Core.Util; using System; using System.Diagnostics; @@ -8,35 +9,19 @@ namespace Kermalis.VGMusicStudio.Core.NDS.DSE; internal sealed class SWD { + #region Header public interface IHeader { // } - private sealed class Header_V402 : IHeader // Size 0x40 + public class Header : IHeader // Size 0x40 { - [BinaryArrayFixedLength(8)] - public byte[] Unknown1 { get; set; } = null!; - public ushort Year { get; set; } - public byte Month { get; set; } - public byte Day { get; set; } - public byte Hour { get; set; } - public byte Minute { get; set; } - public byte Second { get; set; } - public byte Centisecond { get; set; } - [BinaryStringFixedLength(16)] - public string Label { get; set; } = null!; - [BinaryArrayFixedLength(22)] - public byte[] Unknown2 { get; set; } = null!; - public byte NumWAVISlots { get; set; } - public byte NumPRGISlots { get; set; } - public byte NumKeyGroups { get; set; } - [BinaryArrayFixedLength(7)] - public byte[] Padding { get; set; } = null!; - } - private sealed class Header_V415 : IHeader // Size 0x40 - { - [BinaryArrayFixedLength(8)] - public byte[] Unknown1 { get; set; } = null!; + public string Type { get; set; } + public byte[]? Unknown1 { get; set; } + public uint Length { get; set; } + public ushort Version { get; set; } + public byte[]? Unknown2 { get; set; } + public byte[]? Padding1 { get; set; } public ushort Year { get; set; } public byte Month { get; set; } public byte Day { get; set; } @@ -44,77 +29,154 @@ private sealed class Header_V415 : IHeader // Size 0x40 public byte Minute { get; set; } public byte Second { get; set; } public byte Centisecond { get; set; } - [BinaryStringFixedLength(16)] - public string Label { get; set; } = null!; - [BinaryArrayFixedLength(16)] - public byte[] Unknown2 { get; set; } = null!; + public string Label { get; set; } + public byte[]? Unknown3 { get; set; } public uint PCMDLength { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown3 { get; set; } = null!; + public byte[]? Unknown4 { get; set; } public ushort NumWAVISlots { get; set; } public ushort NumPRGISlots { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown4 { get; set; } = null!; + public byte NumKeyGroups { get; set; } + public byte[]? Unknown5 { get; set; } public uint WAVILength { get; set; } + public byte[]? Padding2 { get; set; } + + public Header(EndianBinaryReader r) + { + // File type metadata - The file type, version, and size of the file + Type = r.ReadString_Count(4); + if (Type.StartsWith("swd") == false) // Failsafe, to check if the file is a valid SWD file + { + throw new InvalidDataException("Invalid Data Exception:\nThis file is not a Wave Data (.SWD) file, please make sure the file extension is correct before opening.\nCall Stack:"); + } + if (Type == "swdb") + { + r.Endianness = Endianness.BigEndian; + } + Unknown1 = new byte[4]; + r.ReadBytes(Unknown1); + Length = r.ReadUInt32(); + Version = r.ReadUInt16(); + Unknown2 = new byte[2]; + r.ReadBytes(Unknown2); + + // Timestamp metadata - The time the SWD was published + r.Endianness = Endianness.LittleEndian; // Timestamp is always Little Endian, regardless of version or type, so it must be set to Little Endian to be read + + Padding1 = new byte[8]; // Padding + r.ReadBytes(Padding1); + Year = r.ReadUInt16(); // Year + Month = r.ReadByte(); // Month + Day = r.ReadByte(); // Day + Hour = r.ReadByte(); // Hour + Minute = r.ReadByte(); // Minute + Second = r.ReadByte(); // Second + Centisecond = r.ReadByte(); // Centisecond + if (Type == "swdb") { r.Endianness = Endianness.BigEndian; } // If type is swdb, restore back to Big Endian + + + // Info table + Label = r.ReadString_Count(16); + + switch (Version) // To ensure the version differences apply beyond this point + { + case 1026: + { + Unknown3 = new byte[22]; + r.ReadBytes(Unknown3); + + NumWAVISlots = r.ReadByte(); + + NumPRGISlots = r.ReadByte(); + + NumKeyGroups = r.ReadByte(); + + Padding2 = new byte[7]; + r.ReadBytes(Padding2); + + break; + } + case 1045: + { + Unknown3 = new byte[16]; + r.ReadBytes(Unknown3); + + PCMDLength = r.ReadUInt32(); + + Unknown4 = new byte[2]; + r.ReadBytes(Unknown4); + + NumWAVISlots = r.ReadUInt16(); + + NumPRGISlots = r.ReadUInt16(); + + Unknown5 = new byte[2]; + r.ReadBytes(Unknown5); + + WAVILength = r.ReadUInt32(); + + break; + } + } + } } + public class ChunkHeader : IHeader // Size 0x10 + { + public string Name { get; set; } + public byte[] Padding { get; set; } + public ushort Version { get; set; } + public uint ChunkBegin { get; set; } + public uint ChunkEnd { get; set; } + + public ChunkHeader(EndianBinaryReader r, long chunkOffset, SWD swd) + { + long oldOffset = r.Stream.Position; + r.Stream.Position = chunkOffset; + + // Chunk Name + Name = r.ReadString_Count(4); + + // Padding + Padding = new byte[2]; + r.ReadBytes(Padding); + + // Version + Version = r.ReadUInt16(); + + // Chunk Begin + r.Endianness = Endianness.LittleEndian; // To ensure this is read in Little Endian in all versions and types + ChunkBegin = r.ReadUInt32(); + if (swd.Type == "swdb") { r.Endianness = Endianness.BigEndian; } // To revert back to Big Endian when the type is "swdb" + + // Chunk End + ChunkEnd = r.ReadUInt32(); + + r.Stream.Position = oldOffset; + } + } + #endregion + + #region SplitEntry public interface ISplitEntry { byte LowKey { get; } byte HighKey { get; } - int SampleId { get; } + ushort SampleId { get; } byte SampleRootKey { get; } sbyte SampleTranspose { get; } byte AttackVolume { get; set; } byte Attack { get; set; } - byte Decay { get; set; } + byte Decay1 { get; set; } byte Sustain { get; set; } byte Hold { get; set; } byte Decay2 { get; set; } byte Release { get; set; } } - public sealed class SplitEntry_V402 : ISplitEntry // Size 0x30 + public class SplitEntry : ISplitEntry // 0x30 { - public ushort Id { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown1 { get; set; } = null!; - public byte LowKey { get; set; } - public byte HighKey { get; set; } - public byte LowKey2 { get; set; } - public byte HighKey2 { get; set; } - public byte LowVelocity { get; set; } - public byte HighVelocity { get; set; } - public byte LowVelocity2 { get; set; } - public byte HighVelocity2 { get; set; } - [BinaryArrayFixedLength(5)] - public byte[] Unknown2 { get; set; } = null!; - public byte SampleId { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown3 { get; set; } = null!; - public byte SampleRootKey { get; set; } - public sbyte SampleTranspose { get; set; } - public byte SampleVolume { get; set; } - public sbyte SamplePanpot { get; set; } - public byte KeyGroupId { get; set; } - [BinaryArrayFixedLength(15)] - public byte[] Unknown4 { get; set; } = null!; - public byte AttackVolume { get; set; } - public byte Attack { get; set; } - public byte Decay { get; set; } - public byte Sustain { get; set; } - public byte Hold { get; set; } - public byte Decay2 { get; set; } - public byte Release { get; set; } - public byte Unknown5 { get; set; } - - [BinaryIgnore] - int ISplitEntry.SampleId => SampleId; - } - public sealed class SplitEntry_V415 : ISplitEntry // 0x30 - { - public ushort Id { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown1 { get; set; } = null!; + public byte Unknown1 { get; set; } + public byte Id { get; set; } + public byte[] Unknown2 { get; set; } public byte LowKey { get; set; } public byte HighKey { get; set; } public byte LowKey2 { get; set; } @@ -123,80 +185,252 @@ public sealed class SplitEntry_V415 : ISplitEntry // 0x30 public byte HighVelocity { get; set; } public byte LowVelocity2 { get; set; } public byte HighVelocity2 { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown2 { get; set; } = null!; + public byte[] Unknown3 { get; set; } public ushort SampleId { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown3 { get; set; } = null!; + public byte[] Unknown4 { get; set; } public byte SampleRootKey { get; set; } public sbyte SampleTranspose { get; set; } public byte SampleVolume { get; set; } public sbyte SamplePanpot { get; set; } public byte KeyGroupId { get; set; } - [BinaryArrayFixedLength(13)] - public byte[] Unknown4 { get; set; } = null!; + public byte[]? Unknown5 { get; set; } public byte AttackVolume { get; set; } public byte Attack { get; set; } - public byte Decay { get; set; } + public byte Decay1 { get; set; } public byte Sustain { get; set; } public byte Hold { get; set; } public byte Decay2 { get; set; } public byte Release { get; set; } - public byte Unknown5 { get; set; } + public byte Break { get; set; } + + ushort ISplitEntry.SampleId => SampleId; + + public SplitEntry(EndianBinaryReader r, SWD swd) + { + Unknown1 = r.ReadByte(); + + Id = r.ReadByte(); + + Unknown2 = new byte[2]; + r.ReadBytes(Unknown2); + + LowKey = r.ReadByte(); + + HighKey = r.ReadByte(); + + LowKey2 = r.ReadByte(); + + HighKey2 = r.ReadByte(); + + LowVelocity = r.ReadByte(); + + HighVelocity = r.ReadByte(); + + LowVelocity2 = r.ReadByte(); + + HighVelocity2 = r.ReadByte(); + + switch (swd.Version) + { + case 1026: + { + Unknown3 = new byte[5]; + r.ReadBytes(Unknown3); + + SampleId = r.ReadByte(); + + Unknown4 = new byte[2]; + r.ReadBytes(Unknown4); + + SampleRootKey = r.ReadByte(); + + SampleTranspose = r.ReadSByte(); + + SampleVolume = r.ReadByte(); + + SamplePanpot = r.ReadSByte(); + + KeyGroupId = r.ReadByte(); + + Unknown5 = new byte[15]; + r.ReadBytes(Unknown5); + + AttackVolume = r.ReadByte(); + + Attack = r.ReadByte(); + + Decay1 = r.ReadByte(); + + Sustain = r.ReadByte(); + + Hold = r.ReadByte(); + + Decay2 = r.ReadByte(); + + Release = r.ReadByte(); + + Break = r.ReadByte(); + + break; + } + case 1045: + { + Unknown2 = new byte[6]; + r.ReadBytes(Unknown2); + + SampleId = r.ReadUInt16(); + + Unknown3 = new byte[2]; + r.ReadBytes(Unknown3); + + SampleRootKey = r.ReadByte(); + + SampleTranspose = r.ReadSByte(); + + SampleVolume = r.ReadByte(); + + SamplePanpot = r.ReadSByte(); + + KeyGroupId = r.ReadByte(); + + Unknown4 = new byte[13]; + r.ReadBytes(Unknown4); + + AttackVolume = r.ReadByte(); + + Attack = r.ReadByte(); - [BinaryIgnore] - int ISplitEntry.SampleId => SampleId; + Decay1 = r.ReadByte(); + + Sustain = r.ReadByte(); + + Hold = r.ReadByte(); + + Decay2 = r.ReadByte(); + + Release = r.ReadByte(); + + Break = r.ReadByte(); + + break; + } + + // In the event that there's a SWD version that hasn't been discovered yet + default: throw new NotImplementedException("This version of the SWD specification has not been implemented into VG Music Studio."); + } + } } +#endregion + #region ProgramInfo public interface IProgramInfo { ISplitEntry[] SplitEntries { get; } } - public sealed class ProgramInfo_V402 : IProgramInfo - { - public byte Id { get; set; } - public byte NumSplits { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown1 { get; set; } = null!; - public byte Volume { get; set; } - public byte Panpot { get; set; } - [BinaryArrayFixedLength(5)] - public byte[] Unknown2 { get; set; } = null!; - public byte NumLFOs { get; set; } - [BinaryArrayFixedLength(4)] - public byte[] Unknown3 { get; set; } = null!; - [BinaryArrayFixedLength(16)] - public KeyGroup[] KeyGroups { get; set; } = null!; - [BinaryArrayVariableLength(nameof(NumLFOs))] - public LFOInfo LFOInfos { get; set; } = null!; - [BinaryArrayVariableLength(nameof(NumSplits))] - public SplitEntry_V402[] SplitEntries { get; set; } = null!; - - [BinaryIgnore] - ISplitEntry[] IProgramInfo.SplitEntries => SplitEntries; - } - public sealed class ProgramInfo_V415 : IProgramInfo + public class ProgramInfo : IProgramInfo { public ushort Id { get; set; } - public ushort NumSplits { get; set; } + public byte NumSplits { get; set; } + public byte[] Unknown1 { get; set; } public byte Volume { get; set; } public byte Panpot { get; set; } - [BinaryArrayFixedLength(5)] - public byte[] Unknown1 { get; set; } = null!; + public byte[] Unknown2 { get; set; } public byte NumLFOs { get; set; } - [BinaryArrayFixedLength(4)] - public byte[] Unknown2 { get; set; } = null!; - [BinaryArrayVariableLength(nameof(NumLFOs))] - public LFOInfo[] LFOInfos { get; set; } = null!; - [BinaryArrayFixedLength(16)] - public byte[] Unknown3 { get; set; } = null!; - [BinaryArrayVariableLength(nameof(NumSplits))] - public SplitEntry_V415[] SplitEntries { get; set; } = null!; - - [BinaryIgnore] + public byte[] Unknown3 { get; set; } + public LFOInfo[] LFOInfos { get; set; } + public byte[]? Unknown4 { get; set; } + public KeyGroup[]? KeyGroups { get; set; } + public SplitEntry[] SplitEntries { get; set; } + ISplitEntry[] IProgramInfo.SplitEntries => SplitEntries; + + public ProgramInfo(EndianBinaryReader r, SWD swd) + { + switch(swd.Version) + { + case 1026: + { + Id = r.ReadByte(); + + NumSplits = r.ReadByte(); + + Unknown1 = new byte[2]; + r.ReadBytes(Unknown1); + + Volume = r.ReadByte(); + + Panpot = r.ReadByte(); + + Unknown2 = new byte[5]; + r.ReadBytes(Unknown2); + + NumLFOs = r.ReadByte(); + + Unknown3 = new byte[4]; + r.ReadBytes(Unknown3); + + KeyGroups = new KeyGroup[16]; + + LFOInfos = new LFOInfo[NumLFOs]; + for (int i = 0; i < NumLFOs; i++) + { + LFOInfos[i] = new LFOInfo(r); + }; + + SplitEntries = new SplitEntry[NumSplits]; + + break; + } + + case 1045: + { + Id = r.ReadUInt16(); + + NumSplits = r.ReadByte(); + + Unknown1 = new byte[1]; + r.ReadBytes(Unknown1); + + Volume = r.ReadByte(); + + Panpot = r.ReadByte(); + + Unknown2 = new byte[5]; + r.ReadBytes(Unknown2); + + NumLFOs = r.ReadByte(); + + Unknown3 = new byte[4]; + r.ReadBytes(Unknown3); + + LFOInfos = new LFOInfo[NumLFOs]; + for (int i = 0; i < NumLFOs; i++) + { + LFOInfos[i] = new LFOInfo(r); + }; + + Unknown4 = new byte[16]; + r.ReadBytes(Unknown4); + + SplitEntries = new SplitEntry[NumSplits]; + for (int i = 0; i < NumSplits; i++) + { + SplitEntries[i] = new SplitEntry(r, swd); + } + + break; + } + + // In the event that there's a version that hasn't been discovered yet + default: throw new NotImplementedException("This Digital Sound Elements version has not been implemented into VG Music Studio."); + } + + } + } + #endregion + #region WavInfo public interface IWavInfo { byte RootNote { get; } @@ -207,61 +441,26 @@ public interface IWavInfo uint SampleOffset { get; } uint LoopStart { get; } uint LoopEnd { get; } - byte EnvMult { get; } + byte EnvMulti { get; } byte AttackVolume { get; } byte Attack { get; } - byte Decay { get; } + byte Decay1 { get; } byte Sustain { get; } byte Hold { get; } byte Decay2 { get; } byte Release { get; } } - public sealed class WavInfo_V402 : IWavInfo // Size 0x40 - { - public byte Unknown1 { get; set; } - public byte Id { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown2 { get; set; } = null!; - public byte RootNote { get; set; } - public sbyte Transpose { get; set; } - public byte Volume { get; set; } - public sbyte Panpot { get; set; } - public SampleFormat SampleFormat { get; set; } - [BinaryArrayFixedLength(7)] - public byte[] Unknown3 { get; set; } = null!; - public bool Loop { get; set; } - public uint SampleRate { get; set; } - public uint SampleOffset { get; set; } - public uint LoopStart { get; set; } - public uint LoopEnd { get; set; } - [BinaryArrayFixedLength(16)] - public byte[] Unknown4 { get; set; } = null!; - public byte EnvOn { get; set; } - public byte EnvMult { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown5 { get; set; } = null!; - public byte AttackVolume { get; set; } - public byte Attack { get; set; } - public byte Decay { get; set; } - public byte Sustain { get; set; } - public byte Hold { get; set; } - public byte Decay2 { get; set; } - public byte Release { get; set; } - public byte Unknown6 { get; set; } - } - public sealed class WavInfo_V415 : IWavInfo // 0x40 + + public class WavInfo : IWavInfo // Size 0x40 { - [BinaryArrayFixedLength(2)] - public byte[] Unknown1 { get; set; } = null!; + public byte[] Entry { get; set; } public ushort Id { get; set; } - [BinaryArrayFixedLength(2)] - public byte[] Unknown2 { get; set; } = null!; + public byte[] Unknown2 { get; set; } public byte RootNote { get; set; } public sbyte Transpose { get; set; } public byte Volume { get; set; } public sbyte Panpot { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown3 { get; set; } = null!; + public byte[] Unknown3 { get; set; } public ushort Version { get; set; } public SampleFormat SampleFormat { get; set; } public byte Unknown4 { get; set; } @@ -270,35 +469,271 @@ public sealed class WavInfo_V415 : IWavInfo // 0x40 public byte SamplesPer32Bits { get; set; } public byte Unknown6 { get; set; } public byte BitDepth { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown7 { get; set; } = null!; + public byte[] Unknown7 { get; set; } public uint SampleRate { get; set; } public uint SampleOffset { get; set; } public uint LoopStart { get; set; } public uint LoopEnd { get; set; } public byte EnvOn { get; set; } - public byte EnvMult { get; set; } - [BinaryArrayFixedLength(6)] - public byte[] Unknown8 { get; set; } = null!; + public byte EnvMulti { get; set; } + public byte[] Unknown8 { get; set; } public byte AttackVolume { get; set; } public byte Attack { get; set; } - public byte Decay { get; set; } + public byte Decay1 { get; set; } public byte Sustain { get; set; } public byte Hold { get; set; } public byte Decay2 { get; set; } public byte Release { get; set; } - public byte Unknown9 { get; set; } + public byte Break { get; set; } + + public WavInfo(EndianBinaryReader r, SWD swd) + { + // SWD version format check + switch(swd.Version) + { + + case 1026: + { + // The wave table Entry Variable + Entry = new byte[1]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() + r.ReadBytes(Entry); // Reads the byte + + // Wave ID + Id = r.ReadByte(); // Reads the ID of the wave sample + + // Currently undocumented variable(s) + Unknown2 = new byte[2]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() + r.ReadBytes(Unknown2); // Reads the bytes + + // Root Note + RootNote = r.ReadByte(); + + // Transpose + Transpose = r.ReadSByte(); + + // Volume + Volume = r.ReadByte(); + + // Panpot + Panpot = r.ReadSByte(); + + // Sample Format + if (swd.Type == "swdb") + { + r.Endianness = Endianness.LittleEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.BigEndian; + } + else + { + r.Endianness = Endianness.BigEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.LittleEndian; + } + + // Undocumented variable(s) + Unknown3 = new byte[7]; + r.ReadBytes(Unknown3); + + // Version + Version = r.ReadUInt16(); + + // Loop enable and disable + Loop = r.ReadBoolean(); + + // Sample Rate + SampleRate = r.ReadUInt32(); + + // Sample Offset + SampleOffset = r.ReadUInt32(); + + // Loop Start + LoopStart = r.ReadUInt32(); + + // Loop End + LoopEnd = r.ReadUInt32(); + + // Undocumented variable(s) + Unknown7 = new byte[16]; + r.ReadBytes(Unknown7); + + // Volume Envelop On + EnvOn = r.ReadByte(); + + // Volume Envelop Multiple + EnvMulti = r.ReadByte(); + + // Undocumented variable(s) + Unknown8 = new byte[6]; + r.ReadBytes(Unknown8); + + // Attack Volume + AttackVolume = r.ReadByte(); + + // Attack + Attack = r.ReadByte(); + + // Decay 1 + Decay1 = r.ReadByte(); + + // Sustain + Sustain = r.ReadByte(); + + // Hold + Hold = r.ReadByte(); + + // Decay 2 + Decay2 = r.ReadByte(); + + // Release + Release = r.ReadByte(); + + // The wave table Break Variable + Break = r.ReadByte(); + + break; + } + + case 1045: // Digital Sound Elements - SWD Specification 4.21 + { + // The wave table Entry Variable + Entry = new byte[2]; // Specify a variable with a byte array before doing EndianBinaryReader.ReadBytes() + r.ReadBytes(Entry); // Reads the bytes + + // Wave ID + r.Endianness = Endianness.LittleEndian; // Changes the reader to Little Endian + Id = r.ReadUInt16(); // Reads the ID of the wave sample as Little Endian + if (swd.Type == "swdb") // Checks if the str string value matches "swdb" + { + r.Endianness = Endianness.BigEndian; // Restores the reader back to Big Endian + } + + // Currently undocumented variable + Unknown2 = new byte[2]; // Same as the one before + r.ReadBytes(Unknown2); + + // Root Note + RootNote = r.ReadByte(); + + // Transpose + Transpose = r.ReadSByte(); + + // Volume + Volume = r.ReadByte(); + + // Panpot + Panpot = r.ReadSByte(); + + // Undocumented variable + Unknown3 = new byte[6]; // Same as before, except we need to read 6 bytes instead of 2 + r.ReadBytes(Unknown3); + + // Version + Version = r.ReadUInt16(); + + // Sample Format + if (swd.Type == "swdb") + { + r.Endianness = Endianness.LittleEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.BigEndian; + } + else + { + r.Endianness = Endianness.BigEndian; + SampleFormat = (SampleFormat)r.ReadUInt16(); + r.Endianness = Endianness.LittleEndian; + } + + // Undocumented variable(s) + Unknown4 = r.ReadByte(); + + // Loop enable or disable + Loop = r.ReadBoolean(); + + // Undocumented variable(s) + Unknown5 = r.ReadByte(); + + // Samples per 32 bits + SamplesPer32Bits = r.ReadByte(); + + // Undocumented variable(s) + Unknown6 = r.ReadByte(); + + // Bit Depth + BitDepth = r.ReadByte(); + + // Undocumented variable(s) + Unknown7 = new byte[6]; // Once again, create a variable to specify 6 bytes and to read using it + r.ReadBytes(Unknown7); + + // Sample Rate + SampleRate = r.ReadUInt32(); + + // Sample Offset + SampleOffset = r.ReadUInt32(); + + // Loop Start + LoopStart = r.ReadUInt32(); + + // Loop End + LoopEnd = r.ReadUInt32(); + + // Volume Envelop On + EnvOn = r.ReadByte(); + + // Volume Envelop Multiple + EnvMulti = r.ReadByte(); + + // Undocumented variable(s) + Unknown8 = new byte[6]; // Same as before + r.ReadBytes(Unknown8); + + // Attack Volume + AttackVolume = r.ReadByte(); + + // Attack + Attack = r.ReadByte(); + + // Decay 1 + Decay1 = r.ReadByte(); + + // Sustain + Sustain = r.ReadByte(); + + // Hold + Hold = r.ReadByte(); + + // Decay 2 + Decay2 = r.ReadByte(); + + // Release + Release = r.ReadByte(); + + // The wave table Break Variable + Break = r.ReadByte(); + + break; + } + + // In the event that there's a version that hasn't been discovered yet + default: throw new NotImplementedException("This version of the SWD specification has not yet been implemented into VG Music Studio."); + } + } } + #endregion public class SampleBlock { - public IWavInfo WavInfo = null!; - public byte[] Data = null!; + public WavInfo? WavInfo; + public DSPADPCM? DSPADPCM; + public byte[]? Data; + //public short[]? Data16Bit; } public class ProgramBank { - public IProgramInfo?[] ProgramInfos = null!; - public KeyGroup[] KeyGroups = null!; + public ProgramInfo[]? ProgramInfos; + public KeyGroup[]? KeyGroups; } public class KeyGroup // Size 0x8 { @@ -308,8 +743,25 @@ public class KeyGroup // Size 0x8 public byte LowNote { get; set; } public byte HighNote { get; set; } public ushort Unknown { get; set; } + + public KeyGroup(EndianBinaryReader r, SWD swd) + { + r.Endianness = Endianness.LittleEndian; + Id = r.ReadUInt16(); + if (swd.Type == "swdb") { r.Endianness = Endianness.BigEndian; } + + Poly = r.ReadByte(); + + Priority = r.ReadByte(); + + LowNote = r.ReadByte(); + + HighNote = r.ReadByte(); + + Unknown = r.ReadUInt16(); + } } - public sealed class LFOInfo + public class LFOInfo { public byte Unknown1 { get; set; } public byte HasData { get; set; } @@ -321,64 +773,84 @@ public sealed class LFOInfo public ushort UnknownC { get; set; } public byte UnknownE { get; set; } public byte UnknownF { get; set; } + + public LFOInfo(EndianBinaryReader r) + { + Unknown1 = r.ReadByte(); + + HasData = r.ReadByte(); + + Type = r.ReadByte(); + + CallbackType = r.ReadByte(); + + Unknown4 = r.ReadUInt32(); + + Unknown8 = r.ReadUInt16(); + + UnknownA = r.ReadUInt16(); + + UnknownC = r.ReadUInt16(); + + UnknownE = r.ReadByte(); + + UnknownF = r.ReadByte(); + } } + public Header? Info; public string Type; // "swdb" or "swdl" - public byte[] Unknown1; public uint Length; public ushort Version; - public IHeader Header; - public byte[] Unknown2; + + public long WaviChunkOffset, WaviDataOffset, + PrgiChunkOffset, PrgiDataOffset, + KgrpChunkOffset, KgrpDataOffset, + PcmdChunkOffset, PcmdDataOffset, + EodChunkOffset; + public ChunkHeader? WaviInfo, PrgiInfo, KgrpInfo, PcmdInfo, EodInfo; public ProgramBank? Programs; public SampleBlock[]? Samples; public SWD(string path) { - using (FileStream stream = File.OpenRead(path)) + using (var stream = new MemoryStream(File.ReadAllBytes(path))) { var r = new EndianBinaryReader(stream, ascii: true); - - Type = r.ReadString_Count(4); - Unknown1 = new byte[4]; - r.ReadBytes(Unknown1); - Length = r.ReadUInt32(); - Version = r.ReadUInt16(); - Unknown2 = new byte[2]; - r.ReadBytes(Unknown2); + Info = new Header(r); + Type = Info.Type; + Length = Info.Length; + Version = Info.Version; + Programs = ReadPrograms(r, Info.NumPRGISlots, this); switch (Version) { case 0x402: - { - Header_V402 header = r.ReadObject(); - Header = header; - Programs = ReadPrograms(r, header.NumPRGISlots); - Samples = ReadSamples(r, header.NumWAVISlots); - break; - } + { + Samples = ReadSamples(r, Info.NumWAVISlots, this); + break; + } case 0x415: - { - Header_V415 header = r.ReadObject(); - Header = header; - Programs = ReadPrograms(r, header.NumPRGISlots); - if (header.PCMDLength != 0 && (header.PCMDLength & 0xFFFF0000) != 0xAAAA0000) { - Samples = ReadSamples(r, header.NumWAVISlots); + if (Info.PCMDLength != 0 && (Info.PCMDLength & 0xFFFF0000) != 0xAAAA0000) + { + Samples = ReadSamples(r, Info.NumWAVISlots, this); + } + break; } - break; - } default: throw new InvalidDataException(); } } + return; } + #region FindChunk private static long FindChunk(EndianBinaryReader r, string chunk) { long pos = -1; long oldPosition = r.Stream.Position; r.Stream.Position = 0; - while (r.Stream.Position < r.Stream.Length) { string str = r.ReadString_Count(4); @@ -387,10 +859,13 @@ private static long FindChunk(EndianBinaryReader r, string chunk) pos = r.Stream.Position - 4; break; } - switch (str) { case "swdb": + { + r.Stream.Position += 0x4C; + break; + } case "swdl": { r.Stream.Position += 0x4C; @@ -407,87 +882,162 @@ private static long FindChunk(EndianBinaryReader r, string chunk) } } } - r.Stream.Position = oldPosition; return pos; } + #endregion - private static SampleBlock[] ReadSamples(EndianBinaryReader r, int numWAVISlots) - where T : IWavInfo, new() + #region SampleBlock + private SampleBlock[] ReadSamples(EndianBinaryReader r, int numWAVISlots, SWD swd) { - long waviChunkOffset = FindChunk(r, "wavi"); - long pcmdChunkOffset = FindChunk(r, "pcmd"); + // These apply the chunk offsets that are found to both local and the field functions, chunk header constructors are available here incase they're needed + long waviChunkOffset = swd.WaviChunkOffset = FindChunk(r, "wavi"); + long pcmdChunkOffset = swd.PcmdChunkOffset = FindChunk(r, "pcmd"); + long eodChunkOffset = swd.EodChunkOffset = FindChunk(r, "eod "); if (waviChunkOffset == -1 || pcmdChunkOffset == -1) { throw new InvalidDataException(); } else { - waviChunkOffset += 0x10; - pcmdChunkOffset += 0x10; + WaviInfo = new ChunkHeader(r, waviChunkOffset, swd); + long waviDataOffset = WaviDataOffset = waviChunkOffset + 0x10; + PcmdInfo = new ChunkHeader(r, pcmdChunkOffset, swd); + long pcmdDataOffset = PcmdDataOffset = pcmdChunkOffset + 0x10; + EodInfo = new ChunkHeader(r, eodChunkOffset, swd); var samples = new SampleBlock[numWAVISlots]; for (int i = 0; i < numWAVISlots; i++) { - r.Stream.Position = waviChunkOffset + (2 * i); + r.Stream.Position = waviDataOffset + (2 * i); ushort offset = r.ReadUInt16(); if (offset != 0) { - r.Stream.Position = offset + waviChunkOffset; - T wavInfo = r.ReadObject(); - samples[i] = new SampleBlock + r.Stream.Position = offset + waviDataOffset; + WavInfo wavInfo = new WavInfo(r, swd); + switch (Type) { - WavInfo = wavInfo, - Data = new byte[(int)((wavInfo.LoopStart + wavInfo.LoopEnd) * 4)], - }; - r.Stream.Position = pcmdChunkOffset + wavInfo.SampleOffset; - r.ReadBytes(samples[i].Data); + case "swdm": + { + throw new NotImplementedException("This Digital Sound Elements type has not yet been implemented."); + } + + case "swdl": + { + samples[i] = new SampleBlock + { + WavInfo = wavInfo, + Data = new byte[(int)((wavInfo.LoopStart + wavInfo.LoopEnd) * 4)], + }; + r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; + r.ReadBytes(samples[i].Data); + + break; + } + + case "swdb": + { + samples[i] = new SampleBlock + { + WavInfo = wavInfo, + //Data = new byte[samples[i].DSPADPCM!.Info.num_samples] + }; + r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; + samples[i].DSPADPCM = new DSPADPCM(r); + Span data = new short[samples[i].DSPADPCM!.Info.num_samples / 2]; + data = DSPADPCM.DSPADPCMToPCM16(samples[i].DSPADPCM!.Data, samples[i].DSPADPCM!.Info.num_samples, samples[i].DSPADPCM!.Info); + samples[i].Data = new byte[samples[i].DSPADPCM!.Info.ea]; + //wavInfo.LoopStart = wavInfo.LoopStart / 4; + //wavInfo.LoopEnd = wavInfo.LoopEnd / 4; + //samples[i].Data16Bit = new short[(int)((wavInfo.LoopStart + wavInfo.LoopEnd) / 4) / 4 + 85]; + //DSPADPCM.Decode(samples[i].DSPADPCM!.Data, samples[i].Data16Bit, ref samples[i].DSPADPCM!.Info, samples[i].DSPADPCM!.Info.num_samples); + int e = 0; + for (int d = 0; d < samples[i].DSPADPCM!.Info.num_adpcm_nibbles; d++) + { + samples[i].Data![e] = (byte)(data[d] >> 8); + if (e < samples[i].Data!.Length) { e += 1; } + if (e >= samples[i].Data!.Length) { break; } + samples[i].Data![e] = (byte)(data[d]); + if (e < samples[i].Data!.Length) { e += 1; } + if (e >= samples[i].Data!.Length) { break; } + } + + // Trying to implement an error message that informs anyone of a EndOfStreamException caused by a different encoding type. + //if (swd.PcmdDataOffset + samples[i].WavInfo!.SampleOffset + (samples[i].DSPADPCM!.NumADPCMNibbles / 2) + 9 > swd.EodChunkOffset) + //{ + // throw new EndOfStreamException("End of Stream Exception:\n" + + // "The number of ADPCM nibbles, divided by 2, plus 9 bytes, reads the sample data beyond this SWD.\n" + + // "\n" + + // "This is because VG Music Studio is incorrectly reading the actual size of the DSP-ADPCM sample data.\n" + + // "\n" + + // "If you are a developer of VG Music Studio, please check the code in DSPADPCM.cs\n" + + // "and verify the SWD file in a hex editor to make sure it's being read correctly.\n" + + // "\n" + + // "Call Stack:"); + //} + + //samples[i].DSPADPCM.GetSamples(samples[i].DSPADPCM.Info, samples[i].DSPADPCM.Info.ea); + //samples[i].Data = samples[i].DSPADPCM!.Data; + //Array.Resize(ref samples[i].Data, (int)((wavInfo.LoopStart + wavInfo.LoopEnd) / 6)); + //samples[i].Data = samples[i].DSPADPCM.Data; + break; + } + default: + { + throw new NotImplementedException("This Digital Sound Elements type has not yet been implemented."); + } + } } } return samples; } } - private static ProgramBank? ReadPrograms(EndianBinaryReader r, int numPRGISlots) - where T : IProgramInfo, new() + #endregion + + #region ProgramBank and KeyGroup + private static ProgramBank? ReadPrograms(EndianBinaryReader r, int numPRGISlots, SWD swd) { - long chunkOffset = FindChunk(r, "prgi"); + long chunkOffset = swd.PrgiChunkOffset = FindChunk(r, "prgi"); if (chunkOffset == -1) { return null; } - chunkOffset += 0x10; - var programInfos = new IProgramInfo?[numPRGISlots]; + swd.PrgiInfo = new ChunkHeader(r, chunkOffset, swd); + long dataOffset = swd.PrgiDataOffset = chunkOffset + 0x10; + var programInfos = new ProgramInfo[numPRGISlots]; for (int i = 0; i < programInfos.Length; i++) { - r.Stream.Position = chunkOffset + (2 * i); + r.Stream.Position = dataOffset + (2 * i); ushort offset = r.ReadUInt16(); if (offset != 0) { - r.Stream.Position = offset + chunkOffset; - programInfos[i] = r.ReadObject(); + r.Stream.Position = offset + dataOffset; + programInfos[i] = new ProgramInfo(r, swd); } } return new ProgramBank { ProgramInfos = programInfos, - KeyGroups = ReadKeyGroups(r), + KeyGroups = ReadKeyGroups(r, swd), }; } - private static KeyGroup[] ReadKeyGroups(EndianBinaryReader r) + private static KeyGroup[] ReadKeyGroups(EndianBinaryReader r, SWD swd) { - long chunkOffset = FindChunk(r, "kgrp"); + long chunkOffset = swd.KgrpChunkOffset = FindChunk(r, "kgrp"); if (chunkOffset == -1) { return Array.Empty(); } - r.Stream.Position = chunkOffset + 0xC; - uint chunkLength = r.ReadUInt32(); - var keyGroups = new KeyGroup[chunkLength / 8]; // 8 is the size of a KeyGroup + ChunkHeader info = swd.KgrpInfo = new ChunkHeader(r, chunkOffset, swd); + swd.KgrpDataOffset = chunkOffset + 0x10; + r.Stream.Position = swd.KgrpDataOffset; + var keyGroups = new KeyGroup[info.ChunkEnd / 8]; // 8 is the size of a KeyGroup for (int i = 0; i < keyGroups.Length; i++) { - keyGroups[i] = r.ReadObject(); + keyGroups[i] = new KeyGroup(r, swd); } return keyGroups; } + #endregion } diff --git a/VG Music Studio - Core/Properties/Strings.Designer.cs b/VG Music Studio - Core/Properties/Strings.Designer.cs index eea96fb..b3841cc 100644 --- a/VG Music Studio - Core/Properties/Strings.Designer.cs +++ b/VG Music Studio - Core/Properties/Strings.Designer.cs @@ -375,6 +375,15 @@ public static string FilterOpenSDAT { } } + /// + /// Looks up a localized string similar to Wave Data. + /// + public static string FilterOpenSWD { + get { + return ResourceManager.GetString("FilterOpenSWD", resourceCulture); + } + } + /// /// Looks up a localized string similar to DLS Files. /// @@ -448,7 +457,7 @@ public static string MenuOpenAlphaDream { } /// - /// Looks up a localized string similar to Open DSE Folder. + /// Looks up a localized string similar to Open DSE Files. /// public static string MenuOpenDSE { get { @@ -474,6 +483,24 @@ public static string MenuOpenSDAT { } } + /// + /// Looks up a localized string similar to Open Folder Containing SMD Files. + /// + public static string MenuOpenSMD { + get { + return ResourceManager.GetString("MenuOpenSMD", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open Main SWD File. + /// + public static string MenuOpenSWD { + get { + return ResourceManager.GetString("MenuOpenSWD", resourceCulture); + } + } + /// /// Looks up a localized string similar to Playlist. /// diff --git a/VG Music Studio - Core/Properties/Strings.resx b/VG Music Studio - Core/Properties/Strings.resx index 916279d..ab98afd 100644 --- a/VG Music Studio - Core/Properties/Strings.resx +++ b/VG Music Studio - Core/Properties/Strings.resx @@ -204,8 +204,8 @@ End Current Playlist - - Open DSE Folder + + Open Folder Containing SMD Files Open GBA ROM (AlphaDream) @@ -369,4 +369,13 @@ songs|0_0|song|1_1|songs|2_*| + + Wave Data + + + Open DSE Files + + + Open Main SWD File + \ No newline at end of file diff --git a/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryPrimitivesExtras.cs b/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryPrimitivesExtras.cs new file mode 100644 index 0000000..238528d --- /dev/null +++ b/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryPrimitivesExtras.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Kermalis.EndianBinaryIO; + +namespace Kermalis.VGMusicStudio.Core.Util.EndianBinaryExtras; + +public static class EndianBinaryPrimitivesExtras +{ + public static readonly Endianness SystemEndianness = BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; + + #region Read + + // 24-bits (3 bytes) + public static int ReadInt24(ReadOnlySpan src, Endianness endianness) + { + uint val; // Do a "sign extend" to maintain the sign from 24->32 bits + if (endianness == Endianness.LittleEndian) + { + val = ((uint)src[0] << 8) | ((uint)src[1] << 16) | ((uint)src[2] << 24); + } + else + { + val = ((uint)src[2] << 8) | ((uint)src[1] << 16) | ((uint)src[0] << 24); + } + return (int)val >> 8; + } + public static void ReadInt24s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadInt24(src.Slice(i * 3, 3), endianness); + } + } + public static uint ReadUInt24(ReadOnlySpan src, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + return (uint)((src[0]) | (src[1] << 8) | (src[2] << 16)); + } + else + { + return (uint)((src[2]) | (src[1] << 8) | (src[0] << 16)); + } + } + public static void ReadUInt24s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadUInt24(src.Slice(i * 3, 3), endianness); + } + } + + // 40-bits (5 bytes) + public static long ReadInt40(ReadOnlySpan src, Endianness endianness) + { + ulong val; // Do a "sign extend" to maintain the sign from 40->64 bits + if (endianness == Endianness.LittleEndian) + { + val = ((uint)src[0] << 8) | ((uint)src[1] << 16) | ((uint)src[2] << 24) | ((uint)src[3] << 32) | ((uint)src[4] << 40); + } + else + { + val = ((uint)src[4] << 8) | ((uint)src[3] << 16) | ((uint)src[2] << 24) | ((uint)src[1] << 32) | ((uint)src[0] << 40); + } + return (long)val >> 24; + } + public static void ReadInt40s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadInt40(src.Slice(i * 5, 5), endianness); + } + } + public static ulong ReadUInt40(ReadOnlySpan src, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + return (uint)((src[0]) | (src[1] << 8) | (src[2] << 16) | (src[3] << 24) | (src[4] << 32)); + } + else + { + return (uint)((src[4]) | (src[3] << 8) | (src[2] << 16) | (src[1] << 24) | (src[0] << 32)); + } + } + public static void ReadUInt40s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadUInt40(src.Slice(i * 5, 5), endianness); + } + } + + // 48-bits (6 bytes) + public static long ReadInt48(ReadOnlySpan src, Endianness endianness) + { + ulong val; // Do a "sign extend" to maintain the sign from 48->64 bits + if (endianness == Endianness.LittleEndian) + { + val = ((uint)src[0] << 8) | ((uint)src[1] << 16) | ((uint)src[2] << 24) | ((uint)src[3] << 32) | ((uint)src[4] << 40) | ((uint)src[5] << 48); + } + else + { + val = ((uint)src[5] << 8) | ((uint)src[4] << 16) | ((uint)src[3] << 24) | ((uint)src[2] << 32) | ((uint)src[1] << 40) | ((uint)src[0] << 48); + } + return (long)val >> 16; + } + public static void ReadInt48s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadInt48(src.Slice(i * 6, 6), endianness); + } + } + public static ulong ReadUInt48(ReadOnlySpan src, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + return (uint)((src[0]) | (src[1] << 8) | (src[2] << 16) | (src[3] << 24) | (src[4] << 32) | (src[5] << 40)); + } + else + { + return (uint)((src[5]) | (src[4] << 8) | (src[3] << 16) | (src[2] << 24) | (src[1] << 32) | (src[0] << 40)); + } + } + public static void ReadUInt48s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadUInt48(src.Slice(i * 6, 6), endianness); + } + } + + // 56-bits (7 bytes) + public static long ReadInt56(ReadOnlySpan src, Endianness endianness) + { + ulong val; // Do a "sign extend" to maintain the sign from 56->64 bits + if (endianness == Endianness.LittleEndian) + { + val = ((uint)src[0] << 8) | ((uint)src[1] << 16) | ((uint)src[2] << 24) | ((uint)src[3] << 32) | ((uint)src[4] << 40) | ((uint)src[5] << 48) | ((uint)src[6] << 56); + } + else + { + val = ((uint)src[6] << 8) | ((uint)src[5] << 16) | ((uint)src[4] << 24) | ((uint)src[3] << 32) | ((uint)src[2] << 40) | ((uint)src[1] << 48) | ((uint)src[0] << 56); + } + return (long)val >> 8; + } + public static void ReadInt56s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadInt56(src.Slice(i * 7, 7), endianness); + } + } + public static ulong ReadUInt56(ReadOnlySpan src, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + return (uint)((src[0]) | (src[1] << 8) | (src[2] << 16) | (src[3] << 24) | (src[4] << 32) | (src[5] << 40) | (src[6] << 48)); + } + else + { + return (uint)((src[6]) | (src[5] << 8) | (src[4] << 16) | (src[3] << 24) | (src[2] << 32) | (src[1] << 40) | (src[0] << 48)); + } + } + public static void ReadUInt56s(ReadOnlySpan src, Span dest, Endianness endianness) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = ReadUInt56(src.Slice(i * 7, 7), endianness); + } + } + + #endregion + + #region Write + + public static void WriteInt24(Span dest, int value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); + } + else + { + dest[2] = (byte)value; dest[1] = (byte)(value >> 8); dest[0] = (byte)(value >> 16); + } + } + public static void WriteInt24s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteInt24(dest.Slice(i * 3, 3), src[i], endianness); + } + } + public static void WriteUInt24(Span dest, uint value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); + } + else + { + dest[2] = (byte)value; dest[1] = (byte)(value >> 8); dest[0] = (byte)(value >> 16); + } + } + public static void WriteUInt24s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteUInt24(dest.Slice(i * 3, 3), src[i], endianness); + } + } + + public static void WriteInt40(Span dest, long value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[4] = (byte)(value >> 32); + } + else + { + dest[4] = (byte)value; dest[3] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[1] = (byte)(value >> 24); dest[0] = (byte)(value >> 32); + } + } + public static void WriteInt40s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteInt40(dest.Slice(i * 5, 5), src[i], endianness); + } + } + + public static void WriteUInt40(Span dest, ulong value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[4] = (byte)(value >> 32); + } + else + { + dest[4] = (byte)value; dest[3] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[1] = (byte)(value >> 24); dest[0] = (byte)(value >> 32); + } + } + public static void WriteUInt40s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteUInt40(dest.Slice(i * 5, 5), src[i], endianness); + } + } + + public static void WriteInt48(Span dest, long value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[4] = (byte)(value >> 32); dest[5] = (byte)(value >> 40); + } + else + { + dest[5] = (byte)value; dest[4] = (byte)(value >> 8); dest[3] = (byte)(value >> 16); dest[2] = (byte)(value >> 24); dest[1] = (byte)(value >> 32); dest[0] = (byte)(value >> 40); + } + } + public static void WriteInt48s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteInt48(dest.Slice(i * 6, 6), src[i], endianness); + } + } + + public static void WriteUInt48(Span dest, ulong value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[4] = (byte)(value >> 32); dest[5] = (byte)(value >> 40); + } + else + { + dest[5] = (byte)value; dest[4] = (byte)(value >> 8); dest[3] = (byte)(value >> 16); dest[2] = (byte)(value >> 24); dest[1] = (byte)(value >> 32); dest[0] = (byte)(value >> 40); + } + } + public static void WriteUInt48s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteUInt48(dest.Slice(i * 6, 6), src[i], endianness); + } + } + + public static void WriteInt56(Span dest, long value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[4] = (byte)(value >> 32); dest[5] = (byte)(value >> 40); dest[6] = (byte)(value >> 48); + } + else + { + dest[6] = (byte)value; dest[5] = (byte)(value >> 8); dest[4] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[2] = (byte)(value >> 32); dest[1] = (byte)(value >> 40); dest[0] = (byte)(value >> 48); + } + } + public static void WriteInt56s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteInt56(dest.Slice(i * 7, 7), src[i], endianness); + } + } + + public static void WriteUInt56(Span dest, ulong value, Endianness endianness) + { + if (endianness == Endianness.LittleEndian) + { + dest[0] = (byte)value; dest[1] = (byte)(value >> 8); dest[2] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[4] = (byte)(value >> 32); dest[5] = (byte)(value >> 40); dest[6] = (byte)(value >> 48); + } + else + { + dest[6] = (byte)value; dest[5] = (byte)(value >> 8); dest[4] = (byte)(value >> 16); dest[3] = (byte)(value >> 24); dest[2] = (byte)(value >> 32); dest[1] = (byte)(value >> 40); dest[0] = (byte)(value >> 48); + } + } + public static void WriteUInt56s(Span dest, ReadOnlySpan src, Endianness endianness) + { + for (int i = 0; i < src.Length; i++) + { + WriteUInt56(dest.Slice(i * 7, 7), src[i], endianness); + } + } + + #endregion +} + diff --git a/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryReaderExtras.cs b/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryReaderExtras.cs new file mode 100644 index 0000000..ffd999f --- /dev/null +++ b/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryReaderExtras.cs @@ -0,0 +1,98 @@ +using System; +using System.IO; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Kermalis.EndianBinaryIO; + +namespace Kermalis.VGMusicStudio.Core.Util.EndianBinaryExtras; + +public partial class EndianBinaryReader : EndianBinaryIO.EndianBinaryReader +{ + public EndianBinaryReader(Stream stream, Endianness endianness = Endianness.LittleEndian, BooleanSize booleanSize = BooleanSize.U8, bool ascii = false) + : base(stream, endianness, booleanSize, ascii) + { + } + + public int ReadInt24() + { + Span buffer = _buffer.AsSpan(0, 3); + ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadInt24(buffer, Endianness); + } + public void ReadInt24s(Span dest) + { + ReadArray(dest, 3, EndianBinaryPrimitivesExtras.ReadInt24s); + } + public uint ReadUInt24() + { + Span buffer = _buffer.AsSpan(0, 3); + ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadUInt24(buffer, Endianness); + } + public void ReadUInt24s(Span dest) + { + ReadArray(dest, 3, EndianBinaryPrimitivesExtras.ReadUInt24s); + } + public long ReadInt40() + { + Span buffer = _buffer.AsSpan(0, 5); + ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadInt40(buffer, Endianness); + } + public void ReadInt40s(Span dest) + { + ReadArray(dest, 5, EndianBinaryPrimitivesExtras.ReadInt40s); + } + public ulong ReadUInt40() + { + Span buffer = _buffer.AsSpan(0, 5); + ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadUInt40(buffer, Endianness); + } + public void ReadUInt40s(Span dest) + { + ReadArray(dest, 5, EndianBinaryPrimitivesExtras.ReadUInt40s); + } + public long ReadInt48() + { + Span buffer = _buffer.AsSpan(0, 6); + ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadInt48(buffer, Endianness); + } + public void ReadInt48s(Span dest) + { + ReadArray(dest, 6, EndianBinaryPrimitivesExtras.ReadInt48s); + } + public ulong ReadUInt48() + { + Span buffer = _buffer.AsSpan(0, 6); + ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadUInt48(buffer, Endianness); + } + public void ReadUInt48s(Span dest) + { + ReadArray(dest, 6, EndianBinaryPrimitivesExtras.ReadUInt48s); + } + public long ReadInt56() + { + Span buffer = _buffer.AsSpan(0, 7); + ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadInt56(buffer, Endianness); + } + public void ReadInt56s(Span dest) + { + ReadArray(dest, 7, EndianBinaryPrimitivesExtras.ReadInt56s); + } + public ulong ReadUInt56() + { + Span buffer = _buffer.AsSpan(0, 7); + ReadBytes(buffer); + return EndianBinaryPrimitivesExtras.ReadUInt56(buffer, Endianness); + } + public void ReadUInt56s(Span dest) + { + ReadArray(dest, 7, EndianBinaryPrimitivesExtras.ReadUInt56s); + } +} + diff --git a/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryWriterExtras.cs b/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryWriterExtras.cs new file mode 100644 index 0000000..3bdcab1 --- /dev/null +++ b/VG Music Studio - Core/Util/EndianBinaryExtras/EndianBinaryWriterExtras.cs @@ -0,0 +1,100 @@ +using System; +using System.IO; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Kermalis.EndianBinaryIO; + +namespace Kermalis.VGMusicStudio.Core.Util.EndianBinaryExtras; + +public partial class EndianBinaryWriter : EndianBinaryIO.EndianBinaryWriter +{ + public EndianBinaryWriter(Stream stream, Endianness endianness = Endianness.LittleEndian, BooleanSize booleanSize = BooleanSize.U8, bool ascii = false) + : base(stream, endianness) + { + } + + public void WriteInt24(int value) + { + Span buffer = _buffer.AsSpan(0, 3); + EndianBinaryPrimitivesExtras.WriteInt24(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteInt24s(ReadOnlySpan values) + { + WriteArray(values, 3, EndianBinaryPrimitivesExtras.WriteInt24s); + } + public void WriteUInt24(uint value) + { + Span buffer = _buffer.AsSpan(0, 3); + EndianBinaryPrimitivesExtras.WriteUInt24(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteUInt24s(ReadOnlySpan values) + { + WriteArray(values, 3, EndianBinaryPrimitivesExtras.WriteUInt24s); + } + + public void WriteInt40(long value) + { + Span buffer = _buffer.AsSpan(0, 5); + EndianBinaryPrimitivesExtras.WriteInt40(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteInt40s(ReadOnlySpan values) + { + WriteArray(values, 5, EndianBinaryPrimitivesExtras.WriteInt40s); + } + public void WriteUInt40(ulong value) + { + Span buffer = _buffer.AsSpan(0, 5); + EndianBinaryPrimitivesExtras.WriteUInt40(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteUInt40s(ReadOnlySpan values) + { + WriteArray(values, 5, EndianBinaryPrimitivesExtras.WriteUInt40s); + } + + public void WriteInt48(long value) + { + Span buffer = _buffer.AsSpan(0, 6); + EndianBinaryPrimitivesExtras.WriteInt48(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteInt48s(ReadOnlySpan values) + { + WriteArray(values, 6, EndianBinaryPrimitivesExtras.WriteInt48s); + } + public void WriteUInt48(ulong value) + { + Span buffer = _buffer.AsSpan(0, 6); + EndianBinaryPrimitivesExtras.WriteUInt48(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteUInt48s(ReadOnlySpan values) + { + WriteArray(values, 6, EndianBinaryPrimitivesExtras.WriteUInt48s); + } + public void WriteInt56(long value) + { + Span buffer = _buffer.AsSpan(0, 7); + EndianBinaryPrimitivesExtras.WriteInt56(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteInt56s(ReadOnlySpan values) + { + WriteArray(values, 7, EndianBinaryPrimitivesExtras.WriteInt56s); + } + public void WriteUInt56(ulong value) + { + Span buffer = _buffer.AsSpan(0, 7); + EndianBinaryPrimitivesExtras.WriteUInt56(buffer, value, Endianness); + Stream.Write(buffer); + } + public void WriteUInt56s(ReadOnlySpan values) + { + WriteArray(values, 7, EndianBinaryPrimitivesExtras.WriteUInt56s); + } +} + diff --git a/VG Music Studio - Core/Wii/WiiUtils.cs b/VG Music Studio - Core/Wii/WiiUtils.cs new file mode 100644 index 0000000..797775f --- /dev/null +++ b/VG Music Studio - Core/Wii/WiiUtils.cs @@ -0,0 +1,7 @@ +namespace Kermalis.VGMusicStudio.Core.Wii +{ + internal static class WiiUtils + { + public const int PPC_Broadway_Clock = 382_205_952; // (764.411904 / 2) = 382.205952 = 382,205,952 + } +} diff --git a/VG Music Studio - WinForms/MainForm.cs b/VG Music Studio - WinForms/MainForm.cs index 8820c08..9485404 100644 --- a/VG Music Studio - WinForms/MainForm.cs +++ b/VG Music Studio - WinForms/MainForm.cs @@ -264,9 +264,14 @@ private void EndCurrentPlaylist(object? sender, EventArgs e) private void OpenDSE(object? sender, EventArgs e) { + var f = WinFormsUtils.CreateLoadDialog(".swd", Strings.MenuOpenSWD, Strings.FilterOpenSWD + " (*.swd)|*.swd"); + if (f is null) + { + return; + } var d = new FolderBrowserDialog { - Description = Strings.MenuOpenDSE, + Description = Strings.MenuOpenSMD, UseDescriptionForTitle = true, }; if (d.ShowDialog() != DialogResult.OK) @@ -277,7 +282,7 @@ private void OpenDSE(object? sender, EventArgs e) DisposeEngine(); try { - _ = new DSEEngine(d.SelectedPath); + _ = new DSEEngine(f.ToString(), d.SelectedPath); } catch (Exception ex) { @@ -286,7 +291,7 @@ private void OpenDSE(object? sender, EventArgs e) } DSEConfig config = DSEEngine.DSEInstance!.Config; - FinishLoading(config.BGMFiles.Length); + FinishLoading(config.SMDFiles.Length); _songNumerical.Visible = false; _exportDLSItem.Visible = false; _exportMIDIItem.Visible = false; diff --git a/VG Music Studio.sln b/VG Music Studio.sln index 31bb2a2..bd1ce54 100644 --- a/VG Music Studio.sln +++ b/VG Music Studio.sln @@ -7,6 +7,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VG Music Studio - WinForms" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VG Music Studio - Core", "VG Music Studio - Core\VG Music Studio - Core.csproj", "{5DC1E437-AEA1-4C0E-A57F-09D3DC9F4E7D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VG Music Studio - MIDI", "VG Music Studio - MIDI\VG Music Studio - MIDI.csproj", "{6756ED81-71F6-457D-AD23-9C03B6C934E4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EndianBinaryIO", "..\EndianBinaryIO\Source\EndianBinaryIO.csproj", "{D376BB1C-0DBC-4FED-9C8F-9E837CC913A6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +25,14 @@ Global {5DC1E437-AEA1-4C0E-A57F-09D3DC9F4E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU {5DC1E437-AEA1-4C0E-A57F-09D3DC9F4E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU {5DC1E437-AEA1-4C0E-A57F-09D3DC9F4E7D}.Release|Any CPU.Build.0 = Release|Any CPU + {6756ED81-71F6-457D-AD23-9C03B6C934E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6756ED81-71F6-457D-AD23-9C03B6C934E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6756ED81-71F6-457D-AD23-9C03B6C934E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6756ED81-71F6-457D-AD23-9C03B6C934E4}.Release|Any CPU.Build.0 = Release|Any CPU + {D376BB1C-0DBC-4FED-9C8F-9E837CC913A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D376BB1C-0DBC-4FED-9C8F-9E837CC913A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D376BB1C-0DBC-4FED-9C8F-9E837CC913A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D376BB1C-0DBC-4FED-9C8F-9E837CC913A6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 326094da937c7a8ff9998d4279cdbede6c509498 Mon Sep 17 00:00:00 2001 From: PlatinumLucario Date: Mon, 18 Dec 2023 05:09:48 +1100 Subject: [PATCH 6/9] SWDB and SMDB playback is now fully functional --- VG Music Studio - Core/Codec/DSPADPCM.cs | 1980 +++++++++-------- VG Music Studio - Core/Codec/IMAADPCM.cs | 5 +- VG Music Studio - Core/NDS/DSE/DSEChannel.cs | 29 +- VG Music Studio - Core/NDS/DSE/DSEConfig.cs | 14 +- VG Music Studio - Core/NDS/DSE/DSEMixer.cs | 2 +- VG Music Studio - Core/NDS/DSE/DSEPlayer.cs | 10 +- VG Music Studio - Core/NDS/DSE/DSETrack.cs | 1 + VG Music Studio - Core/NDS/DSE/DSEUtils.cs | 253 +++ VG Music Studio - Core/NDS/DSE/SWD.cs | 65 +- .../NDS/SDAT/SDATChannel.cs | 2 +- VG Music Studio - Core/Util/DataUtils.cs | 19 +- 11 files changed, 1416 insertions(+), 964 deletions(-) diff --git a/VG Music Studio - Core/Codec/DSPADPCM.cs b/VG Music Studio - Core/Codec/DSPADPCM.cs index 360769e..5965bcd 100644 --- a/VG Music Studio - Core/Codec/DSPADPCM.cs +++ b/VG Music Studio - Core/Codec/DSPADPCM.cs @@ -1,345 +1,479 @@ using Kermalis.EndianBinaryIO; using Kermalis.VGMusicStudio.Core.Util; using System; +using System.Buffers.Binary; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; -namespace Kermalis.VGMusicStudio.Core.Codec +namespace Kermalis.VGMusicStudio.Core.Codec; + +internal struct DSPADPCM { - internal sealed class DSPADPCM + public static short Min = short.MinValue; + public static short Max = short.MaxValue; + + public static double[] Tvec = new double[3]; + + public DSPADPCMInfo Info; + private readonly ushort NumChannels; + private readonly bool IsInterleaved; + public byte[] Data; + public short[]? DataOutput; + + private int DataOffset; + private int SamplePos; + public int Scale; + public byte ByteValue; + public int FrameOffset; + public int Coef1; + public int Coef2; + public short Hist1; + public short Hist2; + private int Nibble; + + public static class DSPADPCMConstants { - public static short Min = short.MinValue; - public static short Max = short.MaxValue; - - public static double[] Tvec = new double[3]; - - public DSPADPCMInfo Info; - public byte[] Data; - public short[] DataOutput; - //public static short[]? DataOutput; + public const int BytesPerFrame = 8; + public const int SamplesPerFrame = 14; + public const int NibblesPerFrame = 16; + } - public static class DSPADPCMConstants - { - public const int BytesPerFrame = 8; - public const int SamplesPerFrame = 14; - public const int NibblesPerFrame = 16; - } + public DSPADPCM(EndianBinaryReader r, ushort numChannels, bool hasInterleavedFrames) + { + Info = new DSPADPCMInfo(r); // First, the 96 byte-long DSP-ADPCM header table is read and each variable is assigned with a value + NumChannels = numChannels; // The number of waveform channels + IsInterleaved = hasInterleavedFrames; // If the frames are byte-interleaved + Data = new byte[DataUtils.RoundUp((int)Info.NumAdpcmNibbles / 2, 16)]; // Next, this allocates the full size of the data, based on NumAdpcmNibbles divided by 2 and rounded up to the 16th byte + DataOutput = new short[Info.NumSamples]; // This will allocate the size of the DataOutput array based on the value in NumSamples + + r.ReadBytes(Data); // This reads the compressed sample data based on the size allocated + r.Stream.Align(16); // This will align the EndianBinaryReader stream offset to the 16th byte, since all sample data ends at every 16th byte + return; + } - public DSPADPCM(EndianBinaryReader r, int outputSize) - { - Info = new DSPADPCMInfo(r); // First, the DSP-ADPCM table is read and each variable is assigned with a value - Data = new byte[(Info.NumAdpcmNibbles / 2) + 9]; // Next, the allocated size of the compressed sample data is determined based on NumAdpcmNibbles divided by 2, plus 9 - DataOutput = new short[outputSize]; // The data output needs to be the actual size of the sample data when uncompressed + #region DSP-ADPCM Info + public interface IDSPADPCMInfo + { + public short[] Coef { get; } + public ushort Gain { get; } + public ushort PredScale { get; } + public short Yn1 { get; } + public short Yn2 { get; } + + public ushort LoopPredScale { get; } + public short LoopYn1 { get; } + public short LoopYn2 { get; } + } - r.ReadBytes(Data); // This reads the compressed sample data based on the size allocated - r.Stream.Align(16); // This will align the EndianBinaryReader stream offset to the 16th byte, since all sample data ends at every 16th byte - return; - } + public class DSPADPCMInfo : IDSPADPCMInfo + { + public uint NumSamples { get; set; } + public uint NumAdpcmNibbles { get; set; } + public uint SampleRate { get; set; } + public ushort LoopFlag { get; set; } + public ushort Format { get; set; } + public uint Sa { get; set; } + public uint Ea { get; set; } + public uint Ca { get; set; } + public short[] Coef { get; set; } + public ushort Gain { get; set; } + public ushort PredScale { get; set; } + public short Yn1 { get; set; } + public short Yn2 { get; set; } - #region DSP-ADPCM Info - public interface IDSPADPCMInfo - { - public short[] Coef { get; } - public ushort Gain { get; } - public ushort PredScale { get; } - public short Yn1 { get; } - public short Yn2 { get; } - - public ushort LoopPredScale { get; } - public short LoopYn1 { get; } - public short LoopYn2 { get; } - } + public ushort LoopPredScale { get; set; } + public short LoopYn1 { get; set; } + public short LoopYn2 { get; set; } + public ushort[] Padding { get; set; } - public class DSPADPCMInfo : IDSPADPCMInfo + public DSPADPCMInfo(EndianBinaryReader r) { - public uint NumSamples { get; set; } - public uint NumAdpcmNibbles { get; set; } - public uint SampleRate { get; set; } - public ushort LoopFlag { get; set; } - public ushort Format { get; set; } - public uint Sa { get; set; } - public uint Ea { get; set; } - public uint Ca { get; set; } - public short[] Coef { get; set; } - public ushort Gain { get; set; } - public ushort PredScale { get; set; } - public short Yn1 { get; set; } - public short Yn2 { get; set; } - - public ushort LoopPredScale { get; set; } - public short LoopYn1 { get; set; } - public short LoopYn2 { get; set; } - public ushort[] Padding { get; set; } - - public DSPADPCMInfo(EndianBinaryReader r) - { - NumSamples = r.ReadUInt32(); + NumSamples = r.ReadUInt32(); - NumAdpcmNibbles = r.ReadUInt32(); + NumAdpcmNibbles = r.ReadUInt32(); - SampleRate = r.ReadUInt32(); + SampleRate = r.ReadUInt32(); - LoopFlag = r.ReadUInt16(); + LoopFlag = r.ReadUInt16(); - Format = r.ReadUInt16(); + Format = r.ReadUInt16(); - Sa = r.ReadUInt32(); + Sa = r.ReadUInt32(); - Ea = r.ReadUInt32(); + Ea = r.ReadUInt32(); - Ca = r.ReadUInt32(); + Ca = r.ReadUInt32(); - Coef = new short[16]; - r.ReadInt16s(Coef); + Coef = new short[16]; + r.ReadInt16s(Coef); - Gain = r.ReadUInt16(); + Gain = r.ReadUInt16(); - PredScale = r.ReadUInt16(); + PredScale = r.ReadUInt16(); - Yn1 = r.ReadInt16(); + Yn1 = r.ReadInt16(); - Yn2 = r.ReadInt16(); + Yn2 = r.ReadInt16(); - LoopPredScale = r.ReadUInt16(); + LoopPredScale = r.ReadUInt16(); - LoopYn1 = r.ReadInt16(); + LoopYn1 = r.ReadInt16(); - LoopYn2 = r.ReadInt16(); + LoopYn2 = r.ReadInt16(); - Padding = new ushort[11]; - r.ReadUInt16s(Padding); - } + Padding = new ushort[11]; + r.ReadUInt16s(Padding); } - #endregion - - #region DSP-ADPCM Convert - - //public object GetSamples(DSPADPCMInfo cxt, uint loopEnd) - //{ - // return DSPADPCMToPCM16(Data, loopEnd, cxt); - //} - public static Span DSPADPCMToPCM16(DSPADPCM dspadpcm, DSPADPCMInfo cxt, int numChannels, bool useSubInterframes) - { - //Span dataOutput = new short[outputSize]; // This is the new output data that's converted to PCM16 - for (int i = 0; i < dspadpcm.Data.Length; i++) + public byte[] ToBytes() + { + _ = new byte[96]; + var numSamples = new byte[4]; + var numAdpcmNibbles = new byte[4]; + var sampleRate = new byte[4]; + var loopFlag = new byte[2]; + var format = new byte[2]; + var sa = new byte[4]; + var ea = new byte[4]; + var ca = new byte[4]; + var coef = new byte[32]; + var gain = new byte[2]; + var predScale = new byte[2]; + var yn1 = new byte[2]; + var yn2 = new byte[2]; + var loopPredScale = new byte[2]; + var loopYn1 = new byte[2]; + var loopYn2 = new byte[2]; + var padding = new byte[22]; + + BinaryPrimitives.WriteUInt32BigEndian(numSamples, NumSamples); + BinaryPrimitives.WriteUInt32BigEndian(numAdpcmNibbles, NumAdpcmNibbles); + BinaryPrimitives.WriteUInt32BigEndian(sampleRate, SampleRate); + BinaryPrimitives.WriteUInt16BigEndian(loopFlag, LoopFlag); + BinaryPrimitives.WriteUInt16BigEndian(format, Format); + BinaryPrimitives.WriteUInt32BigEndian(sa, Sa); + BinaryPrimitives.WriteUInt32BigEndian(ea, Ea); + BinaryPrimitives.WriteUInt32BigEndian(ca, Ca); + int index = 0; + for (int i = 0; i < 16; i++) { - Decode(dspadpcm, ref cxt, numChannels, useSubInterframes); + coef[index++] = (byte)(Coef[i] >> 8); + coef[index++] = (byte)(Coef[i] & 0xff); } - return dspadpcm.DataOutput; + BinaryPrimitives.WriteUInt16BigEndian(gain, Gain); + BinaryPrimitives.WriteUInt16BigEndian(predScale, PredScale); + BinaryPrimitives.WriteInt16BigEndian(yn1, Yn1); + BinaryPrimitives.WriteInt16BigEndian(yn2, Yn2); + BinaryPrimitives.WriteUInt16BigEndian(loopPredScale, LoopPredScale); + BinaryPrimitives.WriteInt16BigEndian(loopYn1, LoopYn1); + BinaryPrimitives.WriteInt16BigEndian(loopYn2, LoopYn2); + + // A collection expression is used for combining all byte arrays into one, instead of using the Concat() function + byte[]? bytes = [.. numSamples, .. numAdpcmNibbles, .. sampleRate, + .. loopFlag, .. format, .. sa, .. ea, .. ca, .. coef, .. gain, + .. predScale, .. yn1, .. yn2, .. loopPredScale, .. loopYn1, .. loopYn2, .. padding]; + + return bytes; } - #endregion + } + #endregion + + #region DSP-ADPCM Convert - #region DSP-ADPCM Encode - public static void Encode(short[] src, byte[] dst, DSPADPCMInfo cxt, uint samples) + public readonly byte[] DataOutputToBytes() + { + int index = 0; + var data = new byte[DataOutput!.Length * 2]; + for (int i = 0; i < DataOutput.Length; i++) { - short[] coefs = cxt.Coef; - CorrelateCoefs(src, samples, coefs); + data[index++] = (byte)(DataOutput[i] >> 8); + data[index++] = (byte)DataOutput[i]; + } + return data; + } - int frameCount = (int)((samples / DSPADPCMConstants.SamplesPerFrame) + (samples % DSPADPCMConstants.SamplesPerFrame)); + public readonly byte[] ConvertToWav() + { + // Creating the RIFF Wave header + string fileID = "RIFF"; + uint fileSize = (uint)((DataOutput!.Length * 2) + 44); // File size must match the size of the samples and header size + string waveID = "WAVE"; + string formatID = "fmt "; + uint formatLength = 16; // Always a length 16 + ushort formatType = 1; // Always PCM16 + // Number of channels is already manually defined + uint sampleRate = Info.SampleRate; // Sample Rate is read directly from the Info context + ushort bitsPerSample = 16; // bitsPerSample must be written to AFTER numNibbles + uint numNibbles = sampleRate * bitsPerSample * NumChannels / 8; // numNibbles must be written BEFORE bitsPerSample is written + ushort bitRate = (ushort)((bitsPerSample * NumChannels) / 8); + string dataID = "data"; + uint dataSize = (uint)(DataOutput!.Length * 2); + + var convertedData = new byte[dataSize]; + int index = 0; + for (int i = 0; i < DataOutput!.Length; i++) + { + convertedData[index++] = (byte)(DataOutput![i] & 0xff); + convertedData[index++] = (byte)(DataOutput![i] >> 8); + } - short[] pcm = src; - byte[] adpcm = dst; - short[] pcmFrame = new short[DSPADPCMConstants.SamplesPerFrame + 2]; - byte[] adpcmFrame = new byte[DSPADPCMConstants.BytesPerFrame]; + _ = new byte[4]; + var header2 = new byte[4]; + _ = new byte[4]; + _ = new byte[4]; + var header5 = new byte[4]; + var header6 = new byte[2]; + var header7 = new byte[2]; + var header8 = new byte[4]; + var header9 = new byte[4]; + var header10 = new byte[2]; + var header11 = new byte[2]; + _ = new byte[4]; + var header13 = new byte[4]; + + byte[]? header1 = Encoding.ASCII.GetBytes(fileID); + BinaryPrimitives.WriteUInt32LittleEndian(header2, fileSize); + byte[]? header3 = Encoding.ASCII.GetBytes(waveID); + byte[]? header4 = Encoding.ASCII.GetBytes(formatID); + BinaryPrimitives.WriteUInt32LittleEndian(header5, formatLength); + BinaryPrimitives.WriteUInt16LittleEndian(header6, formatType); + BinaryPrimitives.WriteUInt16LittleEndian(header7, NumChannels); + BinaryPrimitives.WriteUInt32LittleEndian(header8, sampleRate); + BinaryPrimitives.WriteUInt32LittleEndian(header9, numNibbles); + BinaryPrimitives.WriteUInt16LittleEndian(header10, bitRate); + BinaryPrimitives.WriteUInt16LittleEndian(header11, bitsPerSample); + byte[]? header12 = Encoding.ASCII.GetBytes(dataID); + BinaryPrimitives.WriteUInt32LittleEndian(header13, dataSize); + + _ = new byte[44]; + byte[]? header = [ // Using this instead of the Concat() function, which does the exact same task of adding data into the array + .. header1, .. header2, .. header3, + .. header4, .. header5, .. header6, + .. header7, .. header8, .. header9, + .. header10, .. header11, .. header12, .. header13]; + _ = new byte[fileSize]; + byte[]? waveData = [.. header, .. convertedData]; + + + return waveData; + } - short srcIndex = 0; - short dstIndex = 0; + #endregion - for (int i = 0; i < frameCount; ++i, pcm[srcIndex] += DSPADPCMConstants.SamplesPerFrame, adpcm[srcIndex] += DSPADPCMConstants.BytesPerFrame) - { - coefs = new short[2 + 0]; + #region DSP-ADPCM Encode + public static void Encode(short[] src, byte[] dst, DSPADPCMInfo cxt, uint samples) + { + short[] coefs = cxt.Coef; + CorrelateCoefs(src, samples, coefs); - DSPEncodeFrame(pcmFrame, DSPADPCMConstants.SamplesPerFrame, adpcmFrame, coefs); + int frameCount = (int)((samples / DSPADPCMConstants.SamplesPerFrame) + (samples % DSPADPCMConstants.SamplesPerFrame)); - pcmFrame[0] = pcmFrame[14]; - pcmFrame[1] = pcmFrame[15]; - } + short[] pcm = src; + byte[] adpcm = dst; + short[] pcmFrame = new short[DSPADPCMConstants.SamplesPerFrame + 2]; + byte[] adpcmFrame = new byte[DSPADPCMConstants.BytesPerFrame]; - cxt.Gain = 0; - cxt.PredScale = dst[dstIndex++]; - cxt.Yn1 = 0; - cxt.Yn2 = 0; - } + short srcIndex = 0; + short dstIndex = 0; - public static void InnerProductMerge(double[] vecOut, short[] pcmBuf) + for (int i = 0; i < frameCount; ++i, pcm[srcIndex] += DSPADPCMConstants.SamplesPerFrame, adpcm[srcIndex] += DSPADPCMConstants.BytesPerFrame) { - pcmBuf = new short[14]; - vecOut = Tvec; + coefs = new short[2 + 0]; - for (int i = 0; i <= 2; i++) - { - vecOut[i] = 0.0f; - for (int x = 0; x < 14; x++) - vecOut[i] -= pcmBuf[x - i] * pcmBuf[x]; + DSPEncodeFrame(pcmFrame, DSPADPCMConstants.SamplesPerFrame, adpcmFrame, coefs); - } + pcmFrame[0] = pcmFrame[14]; + pcmFrame[1] = pcmFrame[15]; } - public static void OuterProductMerge(double[] mtxOut, short[] pcmBuf) - { - pcmBuf = new short[14]; - mtxOut[3] = Tvec[3]; + cxt.Gain = 0; + cxt.PredScale = dst[dstIndex++]; + cxt.Yn1 = 0; + cxt.Yn2 = 0; + } - for (int x = 1; x <= 2; x++) - for (int y = 1; y <= 2; y++) - { - mtxOut[x] = 0.0; - mtxOut[y] = 0.0; - for (int z = 0; z < 14; z++) - mtxOut[x + y] += pcmBuf[z - x] * pcmBuf[z - y]; - } - } + public static void InnerProductMerge(double[] vecOut, short[] pcmBuf) + { + pcmBuf = new short[14]; + vecOut = Tvec; - public static bool AnalyzeRanges(double[] mtx, int[] vecIdxsOut) + for (int i = 0; i <= 2; i++) { - mtx[3] = Tvec[3]; - double[] recips = new double[3]; - double val, tmp, min, max; + vecOut[i] = 0.0f; + for (int x = 0; x < 14; x++) + vecOut[i] -= pcmBuf[x - i] * pcmBuf[x]; - /* Get greatest distance from zero */ - for (int x = 1; x <= 2; x++) - { - val = Math.Max(Math.Abs(mtx[x] + mtx[1]), Math.Abs(mtx[x] + mtx[2])); - if (val < double.Epsilon) - return true; + } + } - recips[x] = 1.0 / val; - } + public static void OuterProductMerge(double[] mtxOut, short[] pcmBuf) + { + pcmBuf = new short[14]; + mtxOut[3] = Tvec[3]; - int maxIndex = 0; - for (int i = 1; i <= 2; i++) + for (int x = 1; x <= 2; x++) + for (int y = 1; y <= 2; y++) { - for (int x = 1; x < i; x++) - { - tmp = mtx[x] + mtx[i]; - for (int y = 1; y < x; y++) - tmp -= (mtx[x] + mtx[y]) * (mtx[y] + mtx[i]); - mtx[x + i] = tmp; - } + mtxOut[x] = 0.0; + mtxOut[y] = 0.0; + for (int z = 0; z < 14; z++) + mtxOut[x + y] += pcmBuf[z - x] * pcmBuf[z - y]; + } + } - val = 0.0; - for (int x = i; x <= 2; x++) - { - tmp = mtx[x] + mtx[i]; - for (int y = 1; y < i; y++) - tmp -= (mtx[x] + mtx[y]) * (mtx[y] + mtx[i]); + public static bool AnalyzeRanges(double[] mtx, int[] vecIdxsOut) + { + mtx[3] = Tvec[3]; + double[] recips = new double[3]; + double val, tmp, min, max; - mtx[x + i] = tmp; - tmp = Math.Abs(tmp) * recips[x]; - if (tmp >= val) - { - val = tmp; - maxIndex = x; - } - } + /* Get greatest distance from zero */ + for (int x = 1; x <= 2; x++) + { + val = Math.Max(Math.Abs(mtx[x] + mtx[1]), Math.Abs(mtx[x] + mtx[2])); + if (val < double.Epsilon) + return true; - if (maxIndex != i) - { - for (int y = 1; y <= 2; y++) - { - tmp = mtx[maxIndex] + mtx[y]; - mtx[maxIndex + y] = mtx[i] + mtx[y]; - mtx[i + y] = tmp; - } - recips[maxIndex] = recips[i]; - } + recips[x] = 1.0 / val; + } - vecIdxsOut[i] = maxIndex; + int maxIndex = 0; + for (int i = 1; i <= 2; i++) + { + for (int x = 1; x < i; x++) + { + tmp = mtx[x] + mtx[i]; + for (int y = 1; y < x; y++) + tmp -= (mtx[x] + mtx[y]) * (mtx[y] + mtx[i]); + mtx[x + i] = tmp; + } - if (mtx[i] + mtx[i] == 0.0) - return true; + val = 0.0; + for (int x = i; x <= 2; x++) + { + tmp = mtx[x] + mtx[i]; + for (int y = 1; y < i; y++) + tmp -= (mtx[x] + mtx[y]) * (mtx[y] + mtx[i]); - if (i != 2) + mtx[x + i] = tmp; + tmp = Math.Abs(tmp) * recips[x]; + if (tmp >= val) { - tmp = 1.0 / mtx[i] + mtx[i]; - for (int x = i + 1; x <= 2; x++) - mtx[x + i] *= tmp; + val = tmp; + maxIndex = x; } } - /* Get range */ - min = 1.0e10; - max = 0.0; - for (int i = 1; i <= 2; i++) + if (maxIndex != i) { - tmp = Math.Abs(mtx[i] + mtx[i]); - if (tmp < min) - min = tmp; - if (tmp > max) - max = tmp; + for (int y = 1; y <= 2; y++) + { + tmp = mtx[maxIndex] + mtx[y]; + mtx[maxIndex + y] = mtx[i] + mtx[y]; + mtx[i + y] = tmp; + } + recips[maxIndex] = recips[i]; } - if (min / max < 1.0e-10) + vecIdxsOut[i] = maxIndex; + + if (mtx[i] + mtx[i] == 0.0) return true; - return false; + if (i != 2) + { + tmp = 1.0 / mtx[i] + mtx[i]; + for (int x = i + 1; x <= 2; x++) + mtx[x + i] *= tmp; + } } - public static void BidirectionalFilter(double[] mtx, int[] vecIdxs, double[] vecOut) + /* Get range */ + min = 1.0e10; + max = 0.0; + for (int i = 1; i <= 2; i++) { - mtx[3] = Tvec[3]; - vecOut = Tvec; - double tmp; + tmp = Math.Abs(mtx[i] + mtx[i]); + if (tmp < min) + min = tmp; + if (tmp > max) + max = tmp; + } - for (int i = 1, x = 0; i <= 2; i++) - { - int index = vecIdxs[i]; - tmp = vecOut[index]; - vecOut[index] = vecOut[i]; - if (x != 0) - for (int y = x; y <= i - 1; y++) - tmp -= vecOut[y] * mtx[i] + mtx[y]; - else if (tmp != 0.0) - x = i; - vecOut[i] = tmp; - } + if (min / max < 1.0e-10) + return true; - for (int i = 2; i > 0; i--) - { - tmp = vecOut[i]; - for (int y = i + 1; y <= 2; y++) - tmp -= vecOut[y] * mtx[i] + mtx[y]; - vecOut[i] = tmp / mtx[i] + mtx[i]; - } + return false; + } - vecOut[0] = 1.0; + public static void BidirectionalFilter(double[] mtx, int[] vecIdxs, double[] vecOut) + { + mtx[3] = Tvec[3]; + vecOut = Tvec; + double tmp; + + for (int i = 1, x = 0; i <= 2; i++) + { + int index = vecIdxs[i]; + tmp = vecOut[index]; + vecOut[index] = vecOut[i]; + if (x != 0) + for (int y = x; y <= i - 1; y++) + tmp -= vecOut[y] * mtx[i] + mtx[y]; + else if (tmp != 0.0) + x = i; + vecOut[i] = tmp; } - public static bool QuadraticMerge(double[] inOutVec) + for (int i = 2; i > 0; i--) { - inOutVec = Tvec; + tmp = vecOut[i]; + for (int y = i + 1; y <= 2; y++) + tmp -= vecOut[y] * mtx[i] + mtx[y]; + vecOut[i] = tmp / mtx[i] + mtx[i]; + } - double v0, v1, v2 = inOutVec[2]; - double tmp = 1.0 - (v2 * v2); + vecOut[0] = 1.0; + } - if (tmp == 0.0) - return true; + public static bool QuadraticMerge(double[] inOutVec) + { + inOutVec = Tvec; - v0 = (inOutVec[0] - (v2 * v2)) / tmp; - v1 = (inOutVec[1] - (inOutVec[1] * v2)) / tmp; + double v0, v1, v2 = inOutVec[2]; + double tmp = 1.0 - (v2 * v2); - inOutVec[0] = v0; - inOutVec[1] = v1; + if (tmp == 0.0) + return true; - return Math.Abs(v1) > 1.0; - } + v0 = (inOutVec[0] - (v2 * v2)) / tmp; + v1 = (inOutVec[1] - (inOutVec[1] * v2)) / tmp; - public static void FinishRecord(double[] vIn, double[] vOut) + inOutVec[0] = v0; + inOutVec[1] = v1; + + return Math.Abs(v1) > 1.0; + } + + public static void FinishRecord(double[] vIn, double[] vOut) + { + vIn = Tvec; + vOut = Tvec; + for (int z = 1; z <= 2; z++) { - vIn = Tvec; - vOut = Tvec; - for (int z = 1; z <= 2; z++) - { - if (vIn[z] >= 1.0) - vIn[z] = 0.9999999999; + if (vIn[z] >= 1.0) + vIn[z] = 0.9999999999; - else if (vIn[z] <= -1.0) - vIn[z] = -0.9999999999; - } - vOut[0] = 1.0; - vOut[1] = (vIn[2] * vIn[1]) + vIn[1]; - vOut[2] = vIn[2]; + else if (vIn[z] <= -1.0) + vIn[z] = -0.9999999999; } + vOut[0] = 1.0; + vOut[1] = (vIn[2] * vIn[1]) + vIn[1]; + vOut[2] = vIn[2]; + } public static void MatrixFilter(double[] src, double[] dst) { @@ -459,7 +593,7 @@ public static void FilterRecords(double[] vecBest, int exp, double[] records, in for (int i = 0; i < exp; i++) bufferList = new double[i]; - MergeFinishRecord(bufferList, vecBest); + MergeFinishRecord(bufferList, vecBest); } } @@ -504,8 +638,8 @@ public static void CorrelateCoefs(short[] source, uint samples, short[] coefsOut x = 0; } - /* Copy (potentially non-frame-aligned PCM samples into aligned buffer) */ - source[sourceIndex] += (short)frameSamples; + /* Copy (potentially non-frame-aligned PCM samples into aligned buffer) */ + source[sourceIndex] += (short)frameSamples; for (int i = 0; i < frameSamples;) @@ -714,782 +848,818 @@ public static void EncodeFrame(short[] src, byte[] dst, short[] coefs, byte one) coefs = new short[0 + 2]; DSPEncodeFrame(src, 14, dst, coefs); } - #endregion + #endregion - #region DSP-ADPCM Decode + #region DSP-ADPCM Decode - #region Method 1 - public static int DivideByRoundUp(int dividend, int divisor) - { - return (dividend + divisor - 1) / divisor; - } + #region Method 1 - public static sbyte GetHighNibble(byte value) - { - return (sbyte)((value >> 4) & 0xF); - } + private static readonly sbyte[] NibbleToSbyte = [0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1]; - public static sbyte GetLowNibble(byte value) - { - return (sbyte)((value) & 0xF); - } + public static int DivideByRoundUp(int dividend, int divisor) + { + return (dividend + divisor - 1) / divisor; + } + + private static sbyte GetHighNibble(byte value) + { + return NibbleToSbyte[(value >> 4) & 0xF]; + } + + private static sbyte GetLowNibble(byte value) + { + return NibbleToSbyte[value & 0xF]; + } + + public static short Clamp16(int value) + { + if (value > Max) + return Max; + else if (value < Min) + return Min; + else return (short)value; + } + + #region Current code + public void Init(byte[] data, DSPADPCMInfo info) + { + Info = info; + Data = data; + DataOffset = 0; + SamplePos = 0; + FrameOffset = 0; + } + + public void Decode() + { + Init(Data, Info); // Sets up the field variables - public static short Clamp16(int value) + // Each DSP-ADPCM frame is 8 bytes long: 1 byte for header, 7 bytes for sample data + // This loop reads every 8 bytes and decodes them until the samplePos reaches NumSamples + while (SamplePos < Info.NumSamples) { - if (value > Max) - return Max; - if (value < Min) - return Min; - return (short)value; + // This function will decode one frame at a time + DecodeFrame(); + + // The dataOffset is incremented by 8, and samplePos is incremented by 14 to prepare for the next frame + DataOffset += DSPADPCMConstants.BytesPerFrame; + SamplePos += DSPADPCMConstants.SamplesPerFrame; } - public static void Decode(DSPADPCM samples, ref DSPADPCMInfo ctx, int numChannels, bool useSubInterframes) - { - int data_offset = 0; - int sample_pos = 0; - while (sample_pos < samples.Info.NumSamples) - { - // Decodes 1 single DSP frame of size 0x08 (src) into a 14 samples in a PCM buffer (dst) - int hist1 = ctx.Yn1; - int hist2 = ctx.Yn2; + } - /* parse frame header */ - int scale = 1 << ((samples.Data[0x00] >> 0) & 0xf); - int index = (samples.Data[0x00] >> 4) & 0xf; + public void DecodeFrame() + { + // It will decode 1 single DSP frame of size 0x08 (src) into a 14 samples in a PCM buffer (dst) + Hist1 = Info.Yn1; + Hist2 = Info.Yn2; - int coef1 = ctx.Coef[index * 2 + 0]; - int coef2 = ctx.Coef[index * 2 + 1]; + // Parsing the frame's header byte + Scale = 1 << ((Data[DataOffset]) & 0xf); + int coefIndex = ((Data[DataOffset] >> 4) & 0xf) * 2; - /* decode nibbles */ - for (int i = 0; i < DSPADPCMConstants.SamplesPerFrame; i++) - { - byte nibbles = samples.Data[0x01 + i / 2]; + // Parsing the coefficient pairs, based on the nibble's value + Coef1 = Info.Coef[coefIndex + 0]; + Coef2 = Info.Coef[coefIndex + 1]; - int sample = (i & 1) != 0 ? /* high nibble first */ - get_low_nibble_signed(nibbles) : - get_high_nibble_signed(nibbles); - sample = ((sample * scale) << 11); - sample = ((sample + 1024) + (coef1 * hist1) + (coef2 * hist2)) >> 11; - sample = clamp16(sample); + // This loop decodes the frame's nibbles, each of which are 4-bits long (half a byte in length) + for (FrameOffset = 0; FrameOffset < DSPADPCMConstants.SamplesPerFrame; FrameOffset++) + { + // Stores the value of the entire byte based on the frame's offset + ByteValue = Data[DataOffset + 0x01 + FrameOffset / 2]; - samples.DataOutput[(sample_pos + i) * numChannels] = (byte)sample; + // This function decodes one nibble within a frame into a sample + short sample = GetSample(); - hist2 = hist1; - hist1 = sample; - } + // The DSP-ADPCM frame may have bytes that go beyond the DataOutput length, if this happens, this will safely finish the DecodeFrame function's task as is + if ((SamplePos + FrameOffset) * NumChannels >= DataOutput!.Length) { return; } - ctx.Yn1 = (short)hist1; - ctx.Yn2 = (short)hist2; + // The PCM16 sample is stored into the array entry, based on the sample offset and frame offset, multiplied by the number of wave channels + DataOutput[(SamplePos + FrameOffset) * NumChannels] = sample; - data_offset += DSPADPCMConstants.BytesPerFrame; - sample_pos += DSPADPCMConstants.SamplesPerFrame; - } - + // History values are stored, hist1 is copied into hist2 and the PCM16 sample is copied into hist1, before moving onto the next byte in the frame + Hist2 = Hist1; + Hist1 = sample; } - public void DecodeSample(DSPADPCM sample, ref DSPADPCMInfo ctx, bool useSubInterframes) - { - - } + // After the frame is decoded, the values in hist1 and hist2 are copied into Yn1 and Yn2 to prepare for the next frame + Info.Yn1 = Hist1; + Info.Yn2 = Hist2; + } - #region Old Code - //public static void Decode(Span src, Span dst, ref DSPADPCMInfo cxt, uint samples) - //{ - // short hist1 = cxt.Yn1; - // short hist2 = cxt.Yn2; - // short[] coefs = cxt.Coef; - // int srcIndex = 0; - // int dstIndex = 0; - - // int frameCount = DivideByRoundUp((int)samples, DSPADPCMConstants.SamplesPerFrame); - // int samplesRemaining = (int)samples; - - // for (int i = 0; i < frameCount; i++) - // { - // int predictor = GetHighNibble(src[srcIndex]) & 0xF; - // int scale = 1 << GetLowNibble(src[srcIndex++]); - // short coef1 = coefs[predictor * 2]; - // short coef2 = coefs[predictor * 2 + 1]; - - // int samplesToRead = Math.Min(DSPADPCMConstants.SamplesPerFrame, samplesRemaining); - - - // for (int s = 0; s < samplesToRead; s++) - // { - // // Get bits per byte - // //byte bits = src[srcIndex++]; - // byte bits = src[srcIndex + (s >> 1)]; - // int sample = (s % 2) == 0 ? GetHighNibble(bits) : GetLowNibble(bits); - // sample = sample >= 8 ? sample - 16 : sample; - // sample = (((scale * sample) << 11) + 1024 + ((coef1 * hist1) + (coef2 * hist2))) >> 11; - // short finalSample = Clamp16(sample); - - // hist2 = hist1; - // hist1 = finalSample; - - // //if (samplesToRead <= 14) { samplesToRead = 0; } - // //srcIndex += 1; - // dst[dstIndex++] = finalSample; - // if (dstIndex >= samplesToRead) break; - // } - - // //samplesRemaining -= samplesToRead; - // srcIndex += samplesToRead / 2; - // } - //} - #endregion - - public static void GetLoopContext(Span src, ref DSPADPCMInfo cxt, uint samples) - { - short hist1 = cxt.Yn1; - short hist2 = cxt.Yn2; - short[] coefs = cxt.Coef; - int srcIndex = 0; - byte ps = 0; + public short GetSample() + { + Nibble = (FrameOffset & 1) != 0 ? // This conditional operator will store the value of the nibble + GetLowNibble(ByteValue) : // If the byte is not 0, it will obtain the least significant nibble (4-bits) + GetHighNibble(ByteValue); // Otherwise, if the byte is 0, it will obtain the most significant nibble (4-bits) + int largerVal = (Nibble * Scale) << 11; // The nibble's value is multiplied by scale's value, then 11 bits are shifted left, making the value larger + int newVal = (largerVal + 1024 + (Coef1 * Hist1) + (Coef2 * Hist2)) >> 11; // Coefficients are multiplied by the value stored in hist1 and hist2 respectively, then the values are added together to make a new value + short sample = Clamp16(newVal); // The new value is then clamped into a 16-bit value, which makes a PCM16 sample + + return sample; + } - int frameCount = DivideByRoundUp((int)samples, DSPADPCMConstants.SamplesPerFrame); - int samplesRemaining = (int)samples; + #endregion + + #region Old Code 1 + //public static void Decode(Span src, Span dst, ref DSPADPCMInfo cxt, uint samples) + //{ + // short hist1 = cxt.Yn1; + // short hist2 = cxt.Yn2; + // short[] coefs = cxt.Coef; + // int srcIndex = 0; + // int dstIndex = 0; + + // int frameCount = DivideByRoundUp((int)samples, DSPADPCMConstants.SamplesPerFrame); + // int samplesRemaining = (int)samples; + + // for (int i = 0; i < frameCount; i++) + // { + // int predictor = GetHighNibble(src[srcIndex]) & 0xF; + // int scale = 1 << GetLowNibble(src[srcIndex++]); + // short coef1 = coefs[predictor * 2]; + // short coef2 = coefs[predictor * 2 + 1]; + + // int samplesToRead = Math.Min(DSPADPCMConstants.SamplesPerFrame, samplesRemaining); + + + // for (int s = 0; s < samplesToRead; s++) + // { + // // Get bits per byte + // //byte bits = src[srcIndex++]; + // byte bits = src[srcIndex + (s >> 1)]; + // int sample = (s % 2) == 0 ? GetHighNibble(bits) : GetLowNibble(bits); + // sample = sample >= 8 ? sample - 16 : sample; + // sample = (((scale * sample) << 11) + 1024 + ((coef1 * hist1) + (coef2 * hist2))) >> 11; + // short finalSample = Clamp16(sample); + + // hist2 = hist1; + // hist1 = finalSample; + + // //if (samplesToRead <= 14) { samplesToRead = 0; } + // //srcIndex += 1; + // dst[dstIndex++] = finalSample; + // if (dstIndex >= samplesToRead) break; + // } + + // //samplesRemaining -= samplesToRead; + // srcIndex += samplesToRead / 2; + // } + //} + + + //public static void GetLoopContext(Span src, ref DSPADPCMInfo cxt, uint samples) + //{ + // short hist1 = cxt.Yn1; + // short hist2 = cxt.Yn2; + // short[] coefs = cxt.Coef; + // int srcIndex = 0; + // byte ps = 0; + + // int frameCount = DivideByRoundUp((int)samples, DSPADPCMConstants.SamplesPerFrame); + // int samplesRemaining = (int)samples; + + // for (int i = 0; i < frameCount; i++) + // { + // ps = src[srcIndex]; + // int predictor = GetHighNibble(src[srcIndex]) & 0x7; + // int scale = 1 << GetLowNibble(src[srcIndex++]); + // short coef1 = coefs[predictor * 2]; + // short coef2 = coefs[predictor * 2 + 1]; + + // int samplesToRead = Math.Min(DSPADPCMConstants.SamplesPerFrame, samplesRemaining); + + // for (int s = 0; s < samplesToRead; s++) + // { + // int sample = s % 2 == 0 ? GetHighNibble(src[srcIndex]) : GetLowNibble(src[srcIndex++]); + // sample = sample >= 8 ? sample - 16 : sample; + // sample = (((scale * sample) << 11) + 1024 + (coef1 * hist1 + coef2 * hist2)) >> 11; + // short finalSample = Clamp16(sample); + + // hist2 = hist1; + // hist1 = finalSample; + // } + // samplesRemaining -= samplesToRead; + // } + + // cxt.LoopPredScale = ps; + // cxt.LoopYn1 = hist1; + // cxt.LoopYn2 = hist2; + //} + #endregion + + #endregion + + #region Method 2 + + public class PlayConfigType + { + int config_set; /* some of the mods below are set */ + + /* modifiers */ + int play_forever; + int ignore_loop; + int force_loop; + int really_force_loop; + int ignore_fade; + + /* processing */ + double loop_count; + int pad_begin; + int trim_begin; + int body_time; + int trim_end; + double fade_delay; /* not in samples for backwards compatibility */ + double fade_time; + int pad_end; + + double pad_begin_s; + double trim_begin_s; + double body_time_s; + double trim_end_s; + //double fade_delay_s; + //double fade_time_s; + double pad_end_s; + + /* internal flags */ + int pad_begin_set; + int trim_begin_set; + int body_time_set; + int loop_count_set; + int trim_end_set; + int fade_delay_set; + int fade_time_set; + int pad_end_set; + + /* for lack of a better place... */ + int is_txtp; + int is_mini_txtp; - for (int i = 0; i < frameCount; i++) - { - ps = src[srcIndex]; - int predictor = GetHighNibble(src[srcIndex]) & 0x7; - int scale = 1 << GetLowNibble(src[srcIndex++]); - short coef1 = coefs[predictor * 2]; - short coef2 = coefs[predictor * 2 + 1]; + } - int samplesToRead = Math.Min(DSPADPCMConstants.SamplesPerFrame, samplesRemaining); - for (int s = 0; s < samplesToRead; s++) - { - int sample = s % 2 == 0 ? GetHighNibble(src[srcIndex]) : GetLowNibble(src[srcIndex++]); - sample = sample >= 8 ? sample - 16 : sample; - sample = (((scale * sample) << 11) + 1024 + (coef1 * hist1 + coef2 * hist2)) >> 11; - short finalSample = Clamp16(sample); + public class PlayStateType + { + int input_channels; + int output_channels; + + int pad_begin_duration; + int pad_begin_left; + int trim_begin_duration; + int trim_begin_left; + int body_duration; + int fade_duration; + int fade_left; + int fade_start; + int pad_end_duration; + //int pad_end_left; + int pad_end_start; + + int play_duration; /* total samples that the stream lasts (after applying all config) */ + int play_position; /* absolute sample where stream is */ - hist2 = hist1; - hist1 = finalSample; - } - samplesRemaining -= samplesToRead; - } + } - cxt.LoopPredScale = ps; - cxt.LoopYn1 = hist1; - cxt.LoopYn2 = hist2; - } - #endregion + public class Stream + { + /* basic config */ + int num_samples; /* the actual max number of samples */ + int sample_rate; /* sample rate in Hz */ + public int channels; /* number of channels */ + CodecType coding_type; /* type of encoding */ + LayoutType layout_type; /* type of layout */ + MetaType meta_type; /* type of metadata */ + + /* loopin config */ + int loop_flag; /* is this stream looped? */ + int loop_start_sample; /* first sample of the loop (included in the loop) */ + int loop_end_sample; /* last sample of the loop (not included in the loop) */ + + /* layouts/block config */ + int interleave_block_size; /* interleave, or block/frame size (depending on the codec) */ + int interleave_first_block_size; /* different interleave for first block */ + int interleave_first_skip; /* data skipped before interleave first (needed to skip other channels) */ + int interleave_last_block_size; /* smaller interleave for last block */ + int frame_size; /* for codecs with configurable size */ + + /* subsong config */ + int num_streams; /* for multi-stream formats (0=not set/one stream, 1=one stream) */ + int stream_index; /* selected subsong (also 1-based) */ + int stream_size; /* info to properly calculate bitrate in case of subsongs */ + char[] stream_name = new char[255]; /* name of the current stream (info), if the file stores it and it's filled */ + + /* mapping config (info for plugins) */ + uint channel_layout; /* order: FL FR FC LFE BL BR FLC FRC BC SL SR etc (WAVEFORMATEX flags where FL=lowest bit set) */ + + /* other config */ + int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */ + + + /* layout/block state */ + int full_block_size; /* actual data size of an entire block (ie. may be fixed, include padding/headers, etc) */ + int current_sample; /* sample point within the file (for loop detection) */ + int samples_into_block; /* number of samples into the current block/interleave/segment/etc */ + int current_block_offset; /* start of this block (offset of block header) */ + int current_block_size; /* size in usable bytes of the block we're in now (used to calculate num_samples per block) */ + int current_block_samples; /* size in samples of the block we're in now (used over current_block_size if possible) */ + int next_block_offset; /* offset of header of the next block */ + + /* loop state (saved when loop is hit to restore later) */ + int loop_current_sample; /* saved from current_sample (same as loop_start_sample, but more state-like) */ + int loop_samples_into_block;/* saved from samples_into_block */ + int loop_block_offset; /* saved from current_block_offset */ + int loop_block_size; /* saved from current_block_size */ + int loop_block_samples; /* saved from current_block_samples */ + int loop_next_block_offset; /* saved from next_block_offset */ + int hit_loop; /* save config when loop is hit, but first time only */ + + + /* decoder config/state */ + int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */ + int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to them */ + int ws_output_size; /* WS ADPCM: output bytes for this block */ + + + /* main state */ + public Channel[] ch; /* array of channels */ + Channel start_ch; /* shallow copy of channels as they were at the beginning of the stream (for resets) */ + Channel loop_ch; /* shallow copy of channels as they were at the loop point (for loops) */ + IntPtr start_vgmstream; /* shallow copy of the VGMSTREAM as it was at the beginning of the stream (for resets) */ + + IntPtr mixing_data; /* state for mixing effects */ + + /* Optional data the codec needs for the whole stream. This is for codecs too + * different from vgmstream's structure to be reasonably shoehorned. + * Note also that support must be added for resetting, looping and + * closing for every codec that uses this, as it will not be handled. */ + IntPtr codec_data; + /* Same, for special layouts. layout_data + codec_data may exist at the same time. */ + IntPtr layout_data; + + + /* play config/state */ + int config_enabled; /* config can be used */ + PlayConfigType config; /* player config (applied over decoding) */ + PlayStateType pstate; /* player state (applied over decoding) */ + int loop_count; /* counter of complete loops (1=looped once) */ + int loop_target; /* max loops before continuing with the stream end (loops forever if not set) */ + short[] tmpbuf; /* garbage buffer used for seeking/trimming */ + int tmpbuf_size; /* for all channels (samples = tmpbuf_size / channels) */ - #region Method 2 + } - public class PlayConfigType - { - int config_set; /* some of the mods below are set */ - - /* modifiers */ - int play_forever; - int ignore_loop; - int force_loop; - int really_force_loop; - int ignore_fade; - - /* processing */ - double loop_count; - int pad_begin; - int trim_begin; - int body_time; - int trim_end; - double fade_delay; /* not in samples for backwards compatibility */ - double fade_time; - int pad_end; - - double pad_begin_s; - double trim_begin_s; - double body_time_s; - double trim_end_s; - //double fade_delay_s; - //double fade_time_s; - double pad_end_s; - - /* internal flags */ - int pad_begin_set; - int trim_begin_set; - int body_time_set; - int loop_count_set; - int trim_end_set; - int fade_delay_set; - int fade_time_set; - int pad_end_set; - - /* for lack of a better place... */ - int is_txtp; - int is_mini_txtp; + /* read from a file, returns number of bytes read */ + public static int read_streamfile(Span dst, int offset, int length, StreamFile sf) + { + return read_streamfile(dst, offset, length, sf); + } - } + /* return file size */ + public static int get_streamfile_size(StreamFile sf) + { + return get_streamfile_size(sf); + } + public class StreamFile + { - public class PlayStateType + /* read 'length' data at 'offset' to 'dst' */ + static int read(Span dst, int offset, int length, StreamFile[] sf) { - int input_channels; - int output_channels; - - int pad_begin_duration; - int pad_begin_left; - int trim_begin_duration; - int trim_begin_left; - int body_duration; - int fade_duration; - int fade_left; - int fade_start; - int pad_end_duration; - //int pad_end_left; - int pad_end_start; - - int play_duration; /* total samples that the stream lasts (after applying all config) */ - int play_position; /* absolute sample where stream is */ - + return read(dst, offset, length, sf); } - public class Stream + /* get max offset */ + static int get_size(StreamFile[] sf) { - /* basic config */ - int num_samples; /* the actual max number of samples */ - int sample_rate; /* sample rate in Hz */ - public int channels; /* number of channels */ - CodecType coding_type; /* type of encoding */ - LayoutType layout_type; /* type of layout */ - MetaType meta_type; /* type of metadata */ - - /* loopin config */ - int loop_flag; /* is this stream looped? */ - int loop_start_sample; /* first sample of the loop (included in the loop) */ - int loop_end_sample; /* last sample of the loop (not included in the loop) */ - - /* layouts/block config */ - int interleave_block_size; /* interleave, or block/frame size (depending on the codec) */ - int interleave_first_block_size; /* different interleave for first block */ - int interleave_first_skip; /* data skipped before interleave first (needed to skip other channels) */ - int interleave_last_block_size; /* smaller interleave for last block */ - int frame_size; /* for codecs with configurable size */ - - /* subsong config */ - int num_streams; /* for multi-stream formats (0=not set/one stream, 1=one stream) */ - int stream_index; /* selected subsong (also 1-based) */ - int stream_size; /* info to properly calculate bitrate in case of subsongs */ - char[] stream_name = new char[255]; /* name of the current stream (info), if the file stores it and it's filled */ - - /* mapping config (info for plugins) */ - uint channel_layout; /* order: FL FR FC LFE BL BR FLC FRC BC SL SR etc (WAVEFORMATEX flags where FL=lowest bit set) */ - - /* other config */ - int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */ - - - /* layout/block state */ - int full_block_size; /* actual data size of an entire block (ie. may be fixed, include padding/headers, etc) */ - int current_sample; /* sample point within the file (for loop detection) */ - int samples_into_block; /* number of samples into the current block/interleave/segment/etc */ - int current_block_offset; /* start of this block (offset of block header) */ - int current_block_size; /* size in usable bytes of the block we're in now (used to calculate num_samples per block) */ - int current_block_samples; /* size in samples of the block we're in now (used over current_block_size if possible) */ - int next_block_offset; /* offset of header of the next block */ - - /* loop state (saved when loop is hit to restore later) */ - int loop_current_sample; /* saved from current_sample (same as loop_start_sample, but more state-like) */ - int loop_samples_into_block;/* saved from samples_into_block */ - int loop_block_offset; /* saved from current_block_offset */ - int loop_block_size; /* saved from current_block_size */ - int loop_block_samples; /* saved from current_block_samples */ - int loop_next_block_offset; /* saved from next_block_offset */ - int hit_loop; /* save config when loop is hit, but first time only */ - - - /* decoder config/state */ - int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */ - int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to them */ - int ws_output_size; /* WS ADPCM: output bytes for this block */ - - - /* main state */ - public Channel[] ch; /* array of channels */ - Channel start_ch; /* shallow copy of channels as they were at the beginning of the stream (for resets) */ - Channel loop_ch; /* shallow copy of channels as they were at the loop point (for loops) */ - IntPtr start_vgmstream; /* shallow copy of the VGMSTREAM as it was at the beginning of the stream (for resets) */ - - IntPtr mixing_data; /* state for mixing effects */ - - /* Optional data the codec needs for the whole stream. This is for codecs too - * different from vgmstream's structure to be reasonably shoehorned. - * Note also that support must be added for resetting, looping and - * closing for every codec that uses this, as it will not be handled. */ - IntPtr codec_data; - /* Same, for special layouts. layout_data + codec_data may exist at the same time. */ - IntPtr layout_data; - - - /* play config/state */ - int config_enabled; /* config can be used */ - PlayConfigType config; /* player config (applied over decoding) */ - PlayStateType pstate; /* player state (applied over decoding) */ - int loop_count; /* counter of complete loops (1=looped once) */ - int loop_target; /* max loops before continuing with the stream end (loops forever if not set) */ - short[] tmpbuf; /* garbage buffer used for seeking/trimming */ - int tmpbuf_size; /* for all channels (samples = tmpbuf_size / channels) */ - + return get_size(sf); } - /* read from a file, returns number of bytes read */ - public static int read_streamfile(Span dst, int offset, int length, StreamFile sf) + //todo: DO NOT USE, NOT RESET PROPERLY (remove?) + static int get_offset(StreamFile[] sf) { - return read_streamfile(dst, offset, length, sf); + return get_offset(sf); } - /* return file size */ - public static int get_streamfile_size(StreamFile sf) + /* copy current filename to name buf */ + static void get_name(string name, int name_size, StreamFile[] sf) { - return get_streamfile_size(sf); + sf.SetValue(name, name_size); } - public class StreamFile + /* open another streamfile from filename */ + public StreamFile() { + string filename; + int buf_size; + StreamFile[] sf; + } - /* read 'length' data at 'offset' to 'dst' */ - static int read(Span dst, int offset, int length, StreamFile[] sf) - { - return read(dst, offset, length, sf); - } - - /* get max offset */ - static int get_size(StreamFile[] sf) - { - return get_size(sf); - } - - //todo: DO NOT USE, NOT RESET PROPERLY (remove?) - static int get_offset(StreamFile[] sf) - { - return get_offset(sf); - } - - /* copy current filename to name buf */ - static void get_name(string name, int name_size, StreamFile[] sf) - { - sf.SetValue(name, name_size); - } - - /* open another streamfile from filename */ - public StreamFile() - { - string filename; - int buf_size; - StreamFile[] sf; - } - - /* free current STREAMFILE */ - //void (* close) (struct _StreamFile sf); + /* free current STREAMFILE */ + //void (* close) (struct _StreamFile sf); - /* Substream selection for formats with subsongs. - * Not ideal here, but it was the simplest way to pass to all init_vgmstream_x functions. */ - int stream_index; /* 0=default/auto (first), 1=first, N=Nth */ + /* Substream selection for formats with subsongs. + * Not ideal here, but it was the simplest way to pass to all init_vgmstream_x functions. */ + int stream_index; /* 0=default/auto (first), 1=first, N=Nth */ - } + } - public class g72x_state - { - long yl; /* Locked or steady state step size multiplier. */ - short yu; /* Unlocked or non-steady state step size multiplier. */ - short dms; /* Short term energy estimate. */ - short dml; /* Long term energy estimate. */ - short ap; /* Linear weighting coefficient of 'yl' and 'yu'. */ - - short[] a = new short[2]; /* Coefficients of pole portion of prediction filter. */ - short[] b = new short[6]; /* Coefficients of zero portion of prediction filter. */ - short[] pk = new short[2]; /* - * Signs of previous two samples of a partially - * reconstructed signal. - */ - short[] dq = new short[6]; /* - * Previous 6 samples of the quantized difference - * signal represented in an internal floating point - * format. - */ - short[] sr = new short[2]; /* - * Previous 2 samples of the quantized difference - * signal represented in an internal floating point - * format. - */ - char td; /* delayed tone detect, new in 1988 version */ - }; - - public class Channel - { - public StreamFile streamfile = new StreamFile(); /* file used by this channel */ - public long channel_start_offset; /* where data for this channel begins */ - public long offset; /* current location in the file */ + public class g72x_state + { + long yl; /* Locked or steady state step size multiplier. */ + short yu; /* Unlocked or non-steady state step size multiplier. */ + short dms; /* Short term energy estimate. */ + short dml; /* Long term energy estimate. */ + short ap; /* Linear weighting coefficient of 'yl' and 'yu'. */ + + short[] a = new short[2]; /* Coefficients of pole portion of prediction filter. */ + short[] b = new short[6]; /* Coefficients of zero portion of prediction filter. */ + short[] pk = new short[2]; /* + * Signs of previous two samples of a partially + * reconstructed signal. + */ + short[] dq = new short[6]; /* + * Previous 6 samples of the quantized difference + * signal represented in an internal floating point + * format. + */ + short[] sr = new short[2]; /* + * Previous 2 samples of the quantized difference + * signal represented in an internal floating point + * format. + */ + char td; /* delayed tone detect, new in 1988 version */ + }; - public int frame_header_offset; /* offset of the current frame header (for WS) */ - public int samples_left_in_frame; /* for WS */ + public class Channel + { + public StreamFile streamfile = new StreamFile(); /* file used by this channel */ + public long channel_start_offset; /* where data for this channel begins */ + public long offset; /* current location in the file */ - /* format specific */ + public int frame_header_offset; /* offset of the current frame header (for WS) */ + public int samples_left_in_frame; /* for WS */ - /* adpcm */ - public short[] adpcm_coef = new short[16]; /* formats with decode coefficients built in (DSP, some ADX) */ - public int[] adpcm_coef_3by32 = new int[0x60]; /* Level-5 0x555 */ - public short[] vadpcm_coefs = new short[8 * 2 * 8]; /* VADPCM: max 8 groups * max 2 order * fixed 8 subframe coefs */ - public short adpcm_history1_16; /* previous sample */ - public int adpcm_history1_32; + /* format specific */ - public short adpcm_history2_16; /* previous previous sample */ - public int adpcm_history2_32; + /* adpcm */ + public short[] adpcm_coef = new short[16]; /* formats with decode coefficients built in (DSP, some ADX) */ + public int[] adpcm_coef_3by32 = new int[0x60]; /* Level-5 0x555 */ + public short[] vadpcm_coefs = new short[8 * 2 * 8]; /* VADPCM: max 8 groups * max 2 order * fixed 8 subframe coefs */ + public short adpcm_history1_16; /* previous sample */ + public int adpcm_history1_32; - public short adpcm_history3_16; - public int adpcm_history3_32; + public short adpcm_history2_16; /* previous previous sample */ + public int adpcm_history2_32; - public short adpcm_history4_16; - public int adpcm_history4_32; + public short adpcm_history3_16; + public int adpcm_history3_32; + public short adpcm_history4_16; + public int adpcm_history4_32; - //double adpcm_history1_double; - //double adpcm_history2_double; - public int adpcm_step_index; /* for IMA */ - public int adpcm_scale; /* for MS ADPCM */ + //double adpcm_history1_double; + //double adpcm_history2_double; - /* state for G.721 decoder, sort of big but we might as well keep it around */ - public g72x_state g72x_state = new g72x_state(); + public int adpcm_step_index; /* for IMA */ + public int adpcm_scale; /* for MS ADPCM */ - /* ADX encryption */ - public int adx_channels; - public short adx_xor; - public short adx_mult; - public short adx_add; + /* state for G.721 decoder, sort of big but we might as well keep it around */ + public g72x_state g72x_state = new g72x_state(); - }; + /* ADX encryption */ + public int adx_channels; + public short adx_xor; + public short adx_mult; + public short adx_add; - public static int[] nibble_to_int = new int[16] {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1}; + }; - public static int get_nibble_signed(byte n, int upper) - { - /*return ((n&0x70)-(n&0x80))>>4;*/ - return nibble_to_int[(n >> (upper != 0 ? 4 : 0)) & 0x0f]; - } + public static int[] nibble_to_int = new int[16] {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1}; - public static int get_high_nibble_signed(byte n) - { - /*return ((n&0x70)-(n&0x80))>>4;*/ - return nibble_to_int[n >> 4]; - } + public static int get_nibble_signed(byte n, int upper) + { + /*return ((n&0x70)-(n&0x80))>>4;*/ + return nibble_to_int[(n >> (upper != 0 ? 4 : 0)) & 0x0f]; + } - public static int get_low_nibble_signed(byte n) - { - /*return (n&7)-(n&8);*/ - return nibble_to_int[n & 0xf]; - } + public static int get_high_nibble_signed(byte n) + { + /*return ((n&0x70)-(n&0x80))>>4;*/ + return nibble_to_int[n >> 4]; + } - public static int clamp16(int val) - { - if (val > 32767) return 32767; - else if (val < -32768) return -32768; - else return val; - } + public static int get_low_nibble_signed(byte n) + { + /*return (n&7)-(n&8);*/ + return nibble_to_int[n & 0xf]; + } - public static void decode_ngc_dsp(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do) - { - byte[] frame = new byte[0x08] { 0,0,0,0,0,0,0,0 }; - int frame_offset; - int i, frames_in, sample_count = 0; - int bytes_per_frame, samples_per_frame; - int coef_index, scale, coef1, coef2; - int hist1 = stream.adpcm_history1_16; - int hist2 = stream.adpcm_history2_16; + public static int clamp16(int val) + { + if (val > 32767) return 32767; + else if (val < -32768) return -32768; + else return val; + } + public static void decode_ngc_dsp(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do) + { + byte[] frame = new byte[0x08] { 0,0,0,0,0,0,0,0 }; + int frame_offset; + int i, frames_in, sample_count = 0; + int bytes_per_frame, samples_per_frame; + int coef_index, scale, coef1, coef2; + int hist1 = stream.adpcm_history1_16; + int hist2 = stream.adpcm_history2_16; - /* external interleave (fixed size), mono */ - bytes_per_frame = 0x08; - samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 14 */ - frames_in = first_sample / samples_per_frame; - first_sample = first_sample % samples_per_frame; - /* parse frame header */ - frame_offset = (int)((stream.offset + bytes_per_frame) * frames_in); - read_streamfile(frame, frame_offset, bytes_per_frame, stream.streamfile); /* ignore EOF errors */ - scale = 1 << ((frame[0] >> 0) & 0xf); - coef_index = (frame[0] >> 4) & 0xf; + /* external interleave (fixed size), mono */ + bytes_per_frame = 0x08; + samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 14 */ + frames_in = first_sample / samples_per_frame; + first_sample = first_sample % samples_per_frame; - if (coef_index >= 8) { Debug.WriteLine($"DSP: incorrect coefs at %x\n", (uint)frame_offset); } - //if (coef_index > 8) //todo not correctly clamped in original decoder? - // coef_index = 8; + /* parse frame header */ + frame_offset = (int)((stream.offset + bytes_per_frame) * frames_in); + read_streamfile(frame, frame_offset, bytes_per_frame, stream.streamfile); /* ignore EOF errors */ + scale = 1 << ((frame[0] >> 0) & 0xf); + coef_index = (frame[0] >> 4) & 0xf; - coef1 = stream.adpcm_coef[coef_index * 2 + 0]; - coef2 = stream.adpcm_coef[coef_index * 2 + 1]; + if (coef_index >= 8) { Debug.WriteLine($"DSP: incorrect coefs at %x\n", (uint)frame_offset); } + //if (coef_index > 8) //todo not correctly clamped in original decoder? + // coef_index = 8; + coef1 = stream.adpcm_coef[coef_index * 2 + 0]; + coef2 = stream.adpcm_coef[coef_index * 2 + 1]; - /* decode nibbles */ - for (i = first_sample; i < first_sample + samples_to_do; i++) - { - int sample = 0; - byte nibbles = frame[0x01 + i / 2]; - sample = (i & 1) != 0 ? /* high nibble first */ - get_low_nibble_signed(nibbles) : - get_high_nibble_signed(nibbles); - sample = ((sample * scale) << 11); - sample = (sample + 1024 + coef1 * hist1 + coef2 * hist2) >> 11; - sample = clamp16(sample); + /* decode nibbles */ + for (i = first_sample; i < first_sample + samples_to_do; i++) + { + int sample = 0; + byte nibbles = frame[0x01 + i / 2]; - outbuf[sample_count] = sample; - sample_count += channelspacing; + sample = (i & 1) != 0 ? /* high nibble first */ + get_low_nibble_signed(nibbles) : + get_high_nibble_signed(nibbles); + sample = ((sample * scale) << 11); + sample = (sample + 1024 + coef1 * hist1 + coef2 * hist2) >> 11; + sample = clamp16(sample); - hist2 = hist1; - hist1 = sample; - } + outbuf[sample_count] = sample; + sample_count += channelspacing; - stream.adpcm_history1_16 = (short)hist1; - stream.adpcm_history2_16 = (short)hist2; + hist2 = hist1; + hist1 = sample; } + stream.adpcm_history1_16 = (short)hist1; + stream.adpcm_history2_16 = (short)hist2; + } - /* read from memory rather than a file */ - public static void decode_ngc_dsp_subint_internal(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do, Span frame) - { - int i, sample_count = 0; - int bytes_per_frame, samples_per_frame; - int coef_index, scale, coef1, coef2; - int hist1 = stream.adpcm_history1_16; - int hist2 = stream.adpcm_history2_16; + /* read from memory rather than a file */ + public static void decode_ngc_dsp_subint_internal(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do, Span frame) + { + int i, sample_count = 0; + int bytes_per_frame, samples_per_frame; + int coef_index, scale, coef1, coef2; + int hist1 = stream.adpcm_history1_16; + int hist2 = stream.adpcm_history2_16; - /* external interleave (fixed size), mono */ - bytes_per_frame = 0x08; - samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 14 */ - first_sample = first_sample % samples_per_frame; - if (samples_to_do > samples_per_frame) { Debug.WriteLine($"DSP: layout error, too many samples\n"); } - /* parse frame header */ - scale = 1 << ((frame[0] >> 0) & 0xf); - coef_index = (frame[0] >> 4) & 0xf; + /* external interleave (fixed size), mono */ + bytes_per_frame = 0x08; + samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 14 */ + first_sample = first_sample % samples_per_frame; + if (samples_to_do > samples_per_frame) { Debug.WriteLine($"DSP: layout error, too many samples\n"); } - if (coef_index >= 8) { Debug.WriteLine($"DSP: incorrect coefs\n"); } - //if (coef_index > 8) //todo not correctly clamped in original decoder? - // coef_index = 8; + /* parse frame header */ + scale = 1 << ((frame[0] >> 0) & 0xf); + coef_index = (frame[0] >> 4) & 0xf; - coef1 = stream.adpcm_coef[coef_index * 2 + 0]; - coef2 = stream.adpcm_coef[coef_index * 2 + 1]; + if (coef_index >= 8) { Debug.WriteLine($"DSP: incorrect coefs\n"); } + //if (coef_index > 8) //todo not correctly clamped in original decoder? + // coef_index = 8; - for (i = first_sample; i < first_sample + samples_to_do; i++) - { - int sample = 0; - byte nibbles = frame[0x01 + i / 2]; + coef1 = stream.adpcm_coef[coef_index * 2 + 0]; + coef2 = stream.adpcm_coef[coef_index * 2 + 1]; - sample = (i & 1) != 0 ? - get_low_nibble_signed(nibbles) : - get_high_nibble_signed(nibbles); - sample = ((sample * scale) << 11); - sample = (sample + 1024 + coef1 * hist1 + coef2 * hist2) >> 11; - sample = clamp16(sample); + for (i = first_sample; i < first_sample + samples_to_do; i++) + { + int sample = 0; + byte nibbles = frame[0x01 + i / 2]; - outbuf[sample_count] = sample; - sample_count += channelspacing; + sample = (i & 1) != 0 ? + get_low_nibble_signed(nibbles) : + get_high_nibble_signed(nibbles); + sample = ((sample * scale) << 11); + sample = (sample + 1024 + coef1 * hist1 + coef2 * hist2) >> 11; + sample = clamp16(sample); - hist2 = hist1; - hist1 = sample; - } + outbuf[sample_count] = sample; + sample_count += channelspacing; - stream.adpcm_history1_16 = (short)hist1; - stream.adpcm_history2_16 = (short)hist2; + hist2 = hist1; + hist1 = sample; } - private static sbyte read_8bit(int offset, StreamFile sf) - { - byte[] buf = new byte[1]; - - if (read_streamfile(buf, offset, 1, sf) != 1) return -1; - return (sbyte)buf[0]; - } + stream.adpcm_history1_16 = (short)hist1; + stream.adpcm_history2_16 = (short)hist2; + } - /* decode DSP with byte-interleaved frames (ex. 0x08: 1122112211221122) */ - public static void decode_ngc_dsp_subint(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do, int channel, int interleave) - { - byte[] frame = new byte[0x08]; - int i; - int frames_in = first_sample / 14; + private static sbyte read_8bit(int offset, StreamFile sf) + { + byte[] buf = new byte[1]; - for (i = 0; i < 0x08; i++) - { - /* base + current frame + subint section + subint byte + channel adjust */ - frame[i] = (byte)read_8bit( - (int)((stream.offset - + frames_in) * (0x08 * channelspacing) - + i / interleave * interleave * channelspacing - + i % interleave - + interleave * channel), stream.streamfile); - } + if (read_streamfile(buf, offset, 1, sf) != 1) return -1; + return (sbyte)buf[0]; + } - decode_ngc_dsp_subint_internal(stream, outbuf, channelspacing, first_sample, samples_to_do, frame); + /* decode DSP with byte-interleaved frames (ex. 0x08: 1122112211221122) */ + public static void decode_ngc_dsp_subint(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do, int channel, int interleave) + { + byte[] frame = new byte[0x08]; + int i; + int frames_in = first_sample / 14; + + for (i = 0; i < 0x08; i++) + { + /* base + current frame + subint section + subint byte + channel adjust */ + frame[i] = (byte)read_8bit( + (int)((stream.offset + + frames_in) * (0x08 * channelspacing) + + i / interleave * interleave * channelspacing + + i % interleave + + interleave * channel), stream.streamfile); } + decode_ngc_dsp_subint_internal(stream, outbuf, channelspacing, first_sample, samples_to_do, frame); + } - /* - * The original DSP spec uses nibble counts for loop points, and some - * variants don't have a proper sample count, so we (who are interested - * in sample counts) need to do this conversion occasionally. - */ - public static int dsp_nibbles_to_samples(int nibbles) - { - int whole_frames = nibbles / 16; - int remainder = nibbles % 16; - if (remainder > 0) return whole_frames * 14 + remainder - 2; - else return whole_frames * 14; - } + /* + * The original DSP spec uses nibble counts for loop points, and some + * variants don't have a proper sample count, so we (who are interested + * in sample counts) need to do this conversion occasionally. + */ + public static int dsp_nibbles_to_samples(int nibbles) + { + int whole_frames = nibbles / 16; + int remainder = nibbles % 16; - public static int dsp_bytes_to_samples(int bytes, int channels) - { - if (channels <= 0) return 0; - return bytes / channels / 8 * 14; - } + if (remainder > 0) return whole_frames * 14 + remainder - 2; + else return whole_frames * 14; + } - /* host endian independent multi-byte integer reading */ - public static short get_16bitBE(Span p) - { - return (short)(((ushort)p[0] << 8) | ((ushort)p[1])); - } + public static int dsp_bytes_to_samples(int bytes, int channels) + { + if (channels <= 0) return 0; + return bytes / channels / 8 * 14; + } - public static short get_16bitLE(Span p) - { - return (short)(((ushort)p[0]) | ((ushort)p[1] << 8)); - } + /* host endian independent multi-byte integer reading */ + public static short get_16bitBE(Span p) + { + return (short)(((ushort)p[0] << 8) | ((ushort)p[1])); + } - public static short read_16bitLE(int offset, StreamFile sf) - { - byte[] buf = new byte[2]; + public static short get_16bitLE(Span p) + { + return (short)(((ushort)p[0]) | ((ushort)p[1] << 8)); + } - if (read_streamfile(buf, offset, 2, sf) != 2) return -1; - return get_16bitLE(buf); - } - public static short read_16bitBE(int offset, StreamFile sf) - { - byte[] buf = new byte[2]; + public static short read_16bitLE(int offset, StreamFile sf) + { + byte[] buf = new byte[2]; - if (read_streamfile(buf, offset, 2, sf) != 2) return -1; - return get_16bitBE(buf); - } + if (read_streamfile(buf, offset, 2, sf) != 2) return -1; + return get_16bitLE(buf); + } + public static short read_16bitBE(int offset, StreamFile sf) + { + byte[] buf = new byte[2]; - /* reads DSP coefs built in the streamfile */ - public static void dsp_read_coefs_be(Stream vgmstream, StreamFile streamFile, int offset, int spacing) - { - dsp_read_coefs(vgmstream, streamFile, offset, spacing, 1); - } - public static void dsp_read_coefs_le(Stream vgmstream, StreamFile streamFile, int offset, int spacing) - { - dsp_read_coefs(vgmstream, streamFile, offset, spacing, 0); - } - public static void dsp_read_coefs(Stream vgmstream, StreamFile streamFile, int offset, int spacing, int be) - { - int ch, i; - /* get ADPCM coefs */ - for (ch = 0; ch < vgmstream.channels; ch++) - { - for (i = 0; i < 16; i++) - { - vgmstream.ch[ch].adpcm_coef[i] = be != 0 ? - read_16bitBE(offset + ch * spacing + i * 2, streamFile) : - read_16bitLE(offset + ch * spacing + i * 2, streamFile); - } - } - } + if (read_streamfile(buf, offset, 2, sf) != 2) return -1; + return get_16bitBE(buf); + } - /* reads DSP initial hist built in the streamfile */ - public static void dsp_read_hist_be(Stream vgmstream, StreamFile streamFile, int offset, int spacing) - { - dsp_read_hist(vgmstream, streamFile, offset, spacing, 1); - } - public static void dsp_read_hist_le(Stream vgmstream, StreamFile streamFile, int offset, int spacing) - { - dsp_read_hist(vgmstream, streamFile, offset, spacing, 0); - } - public static void dsp_read_hist(Stream vgmstream, StreamFile streamFile, int offset, int spacing, int be) + /* reads DSP coefs built in the streamfile */ + public static void dsp_read_coefs_be(Stream vgmstream, StreamFile streamFile, int offset, int spacing) + { + dsp_read_coefs(vgmstream, streamFile, offset, spacing, 1); + } + public static void dsp_read_coefs_le(Stream vgmstream, StreamFile streamFile, int offset, int spacing) + { + dsp_read_coefs(vgmstream, streamFile, offset, spacing, 0); + } + public static void dsp_read_coefs(Stream vgmstream, StreamFile streamFile, int offset, int spacing, int be) + { + int ch, i; + /* get ADPCM coefs */ + for (ch = 0; ch < vgmstream.channels; ch++) { - int ch; - /* get ADPCM hist */ - for (ch = 0; ch < vgmstream.channels; ch++) + for (i = 0; i < 16; i++) { - vgmstream.ch[ch].adpcm_history1_16 = be != 0 ? - read_16bitBE(offset + ch * spacing + 0 * 2, streamFile) : - read_16bitLE(offset + ch * spacing + 0 * 2, streamFile); ; - vgmstream.ch[ch].adpcm_history2_16 = be != 0 ? - read_16bitBE(offset + ch * spacing + 1 * 2, streamFile) : - read_16bitLE(offset + ch * spacing + 1 * 2, streamFile); ; + vgmstream.ch[ch].adpcm_coef[i] = be != 0 ? + read_16bitBE(offset + ch * spacing + i * 2, streamFile) : + read_16bitLE(offset + ch * spacing + i * 2, streamFile); } } + } + /* reads DSP initial hist built in the streamfile */ + public static void dsp_read_hist_be(Stream vgmstream, StreamFile streamFile, int offset, int spacing) + { + dsp_read_hist(vgmstream, streamFile, offset, spacing, 1); + } + public static void dsp_read_hist_le(Stream vgmstream, StreamFile streamFile, int offset, int spacing) + { + dsp_read_hist(vgmstream, streamFile, offset, spacing, 0); + } + public static void dsp_read_hist(Stream vgmstream, StreamFile streamFile, int offset, int spacing, int be) + { + int ch; + /* get ADPCM hist */ + for (ch = 0; ch < vgmstream.channels; ch++) + { + vgmstream.ch[ch].adpcm_history1_16 = be != 0 ? + read_16bitBE(offset + ch * spacing + 0 * 2, streamFile) : + read_16bitLE(offset + ch * spacing + 0 * 2, streamFile); ; + vgmstream.ch[ch].adpcm_history2_16 = be != 0 ? + read_16bitBE(offset + ch * spacing + 1 * 2, streamFile) : + read_16bitLE(offset + ch * spacing + 1 * 2, streamFile); ; + } + } - #endregion - #endregion + #endregion - #region DSP-ADPCM Math - public static uint GetBytesForADPCMBuffer(uint samples) - { - uint frames = samples / DSPADPCMConstants.SamplesPerFrame; - if ((samples % DSPADPCMConstants.SamplesPerFrame) == frames) - frames++; + #endregion - return frames * DSPADPCMConstants.BytesPerFrame; - } + #region DSP-ADPCM Math + public static uint GetBytesForADPCMBuffer(uint samples) + { + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + if ((samples % DSPADPCMConstants.SamplesPerFrame) == frames) + frames++; - public static uint GetBytesForADPCMSamples(uint samples) - { - uint extraBytes = 0; - uint frames = samples / DSPADPCMConstants.SamplesPerFrame; - uint extraSamples = (samples % DSPADPCMConstants.SamplesPerFrame); + return frames * DSPADPCMConstants.BytesPerFrame; + } - if (extraSamples == frames) - { - extraBytes = (extraSamples / 2) + (extraSamples % 2) + 1; - } + public static uint GetBytesForADPCMSamples(uint samples) + { + uint extraBytes = 0; + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + uint extraSamples = (samples % DSPADPCMConstants.SamplesPerFrame); - return DSPADPCMConstants.BytesPerFrame * frames + extraBytes; + if (extraSamples == frames) + { + extraBytes = (extraSamples / 2) + (extraSamples % 2) + 1; } - public static uint GetBytesForPCMBuffer(uint samples) - { - uint frames = samples / DSPADPCMConstants.SamplesPerFrame; - if ((samples % DSPADPCMConstants.SamplesPerFrame) == frames) - frames++; + return DSPADPCMConstants.BytesPerFrame * frames + extraBytes; + } - return frames * DSPADPCMConstants.SamplesPerFrame * sizeof(int); - } + public static uint GetBytesForPCMBuffer(uint samples) + { + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + if ((samples % DSPADPCMConstants.SamplesPerFrame) == frames) + frames++; - public static uint GetBytesForPCMSamples(uint samples) - { - return samples * sizeof(int); - } + return frames * DSPADPCMConstants.SamplesPerFrame * sizeof(int); + } - public static uint GetNibbleAddress(uint samples) - { - int frames = (int)(samples / DSPADPCMConstants.SamplesPerFrame); - int extraSamples = (int)(samples % DSPADPCMConstants.SamplesPerFrame); + public static uint GetBytesForPCMSamples(uint samples) + { + return samples * sizeof(int); + } - return (uint)(DSPADPCMConstants.NibblesPerFrame * frames + extraSamples + 2); - } + public static uint GetNibbleAddress(uint samples) + { + int frames = (int)(samples / DSPADPCMConstants.SamplesPerFrame); + int extraSamples = (int)(samples % DSPADPCMConstants.SamplesPerFrame); - public static uint GetNibblesForNSamples(uint samples) - { - uint frames = samples / DSPADPCMConstants.SamplesPerFrame; - uint extraSamples = (samples % DSPADPCMConstants.SamplesPerFrame); - uint extraNibbles = extraSamples == 0 ? 0 : extraSamples + 2; + return (uint)(DSPADPCMConstants.NibblesPerFrame * frames + extraSamples + 2); + } - return DSPADPCMConstants.NibblesPerFrame * frames + extraNibbles; - } + public static uint GetNibblesForNSamples(uint samples) + { + uint frames = samples / DSPADPCMConstants.SamplesPerFrame; + uint extraSamples = (samples % DSPADPCMConstants.SamplesPerFrame); + uint extraNibbles = extraSamples == 0 ? 0 : extraSamples + 2; - public static uint GetSampleForADPCMNibble(uint nibble) - { - uint frames = nibble / DSPADPCMConstants.NibblesPerFrame; - uint extraNibbles = (nibble % DSPADPCMConstants.NibblesPerFrame); - uint samples = DSPADPCMConstants.SamplesPerFrame * frames; + return DSPADPCMConstants.NibblesPerFrame * frames + extraNibbles; + } - return samples + extraNibbles - 2; - } - #endregion + public static uint GetSampleForADPCMNibble(uint nibble) + { + uint frames = nibble / DSPADPCMConstants.NibblesPerFrame; + uint extraNibbles = (nibble % DSPADPCMConstants.NibblesPerFrame); + uint samples = DSPADPCMConstants.SamplesPerFrame * frames; + + return samples + extraNibbles - 2; } + #endregion } diff --git a/VG Music Studio - Core/Codec/IMAADPCM.cs b/VG Music Studio - Core/Codec/IMAADPCM.cs index 89c8f95..df6840d 100644 --- a/VG Music Studio - Core/Codec/IMAADPCM.cs +++ b/VG Music Studio - Core/Codec/IMAADPCM.cs @@ -4,6 +4,7 @@ namespace Kermalis.VGMusicStudio.Core.Codec; internal struct IMAADPCM { + // TODO: Add encoding functionality and PCM16 to IMA-ADPCM conversion private static ReadOnlySpan IndexTable => new short[8] { -1, -1, -1, -1, 2, 4, 6, 8, @@ -30,7 +31,7 @@ internal struct IMAADPCM public int DataOffset; public bool OnSecondNibble; - public void Decode(byte[] data) + public void Init(byte[] data) { _data = data; LastSample = (short)(data[0] | (data[1] << 8)); @@ -42,7 +43,7 @@ public void Decode(byte[] data) public static short[] IMAADPCMToPCM16(byte[] data) { var decoder = new IMAADPCM(); - decoder.Decode(data); + decoder.Init(data); short[] buffer = new short[(data.Length - 4) * 2]; for (int i = 0; i < buffer.Length; i++) diff --git a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs index 77b0b21..8dae6a0 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs @@ -46,10 +46,7 @@ internal sealed class DSEChannel private short _adpcmLoopLastSample; private short _adpcmLoopStepIndex; // DSP-ADPCM - //private DSPADPCM dspADPCM = new DSPADPCM(); - //private short[] _loopContext; - //private DSPADPCM _outputData; - //private DSPADPCM? _dspADPCM; + private DSPADPCM _dspADPCM; // PSG private byte _psgDuty; private int _psgCounter; @@ -99,16 +96,16 @@ public bool StartPCM(SWD localswd, SWD masterswd, byte voice, int key, uint note case "wds ": throw new NotImplementedException("The base timer for the WDS type is not yet implemented."); // PlayStation case "swdm": throw new NotImplementedException("The base timer for the SWDM type is not yet implemented."); // PlayStation 2 case "swdl": BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo!.SampleRate); break; // Nintendo DS - case "swdb": BaseTimer = (ushort)(WiiUtils.PPC_Broadway_Clock + _sample.WavInfo!.SampleRate / 34); break; // Wii + case "swdb": BaseTimer = 380; break; // Wii } if (_sample.WavInfo!.SampleFormat == SampleFormat.ADPCM) { - _adpcmDecoder.Decode(_sample.Data); + _adpcmDecoder.Init(_sample.Data!); + } + if (masterswd.Type == "swdb") + { + _dspADPCM.Init(_sample.DSPADPCM.Data, _sample.DSPADPCM.Info); } - //if (masterswd.Type == "swdb") - //{ - // _dspADPCM = _sample.DSPADPCM; - //} //attackVolume = sample.WavInfo.AttackVolume == 0 ? split.AttackVolume : sample.WavInfo.AttackVolume; //attack = sample.WavInfo.Attack == 0 ? split.Attack : sample.WavInfo.Attack; //decay = sample.WavInfo.Decay == 0 ? split.Decay : sample.WavInfo.Decay; @@ -368,7 +365,7 @@ public void Process(out short left, out short right) { if (_sample.WavInfo.Loop) { - _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); + _dataOffset = (int)(_sample.WavInfo.LoopStart * 4); // DS counts LoopStart 32-bits (4 bytes) at a time, so LoopStart needs to be bigger } else { @@ -446,14 +443,12 @@ public void Process(out short left, out short right) } case "swdb": { - DSPADPCM.Decode(_sample!.DSPADPCM!, ref _sample!.DSPADPCM!.Info, 1, false); - // If hit end - if (_dataOffset >= _sample!.DSPADPCM!.DataOutput.Length) + if (_dataOffset >= _sample!.DSPADPCM!.DataOutput!.Length) { if (_sample.WavInfo!.Loop) { - _dataOffset = (int)(_sample.WavInfo.LoopStart / 4); + _dataOffset = (int)(_sample.WavInfo.LoopStart / 2); // Wii values for LoopStart are counted 8-bits at a time, but because DataOutput is using a 16-bit array, LoopStart needs to be divided by 2 } else { @@ -462,8 +457,8 @@ public void Process(out short left, out short right) return; } } - short samp = (short)(((byte)_sample.DSPADPCM!.DataOutput[_dataOffset] << 8) | (byte)_sample.DSPADPCM!.DataOutput[_dataOffset++]); - samp = (short)(samp * Volume / 0x7F); + short samp = _sample.DSPADPCM!.DataOutput![_dataOffset++]; // Since DataOutput is already a 16-bit array, only one array entry is needed per loop, no bitshifting needed either + samp = (short)(samp * Volume / 0x7F); _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); break; diff --git a/VG Music Studio - Core/NDS/DSE/DSEConfig.cs b/VG Music Studio - Core/NDS/DSE/DSEConfig.cs index d0cb474..77da198 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEConfig.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEConfig.cs @@ -9,6 +9,7 @@ public sealed class DSEConfig : Config { public readonly string SMDPath; public readonly string[] SMDFiles; + internal SMD.Header Header; internal DSEConfig(string smdPath) { @@ -19,24 +20,25 @@ internal DSEConfig(string smdPath) throw new DSENoSequencesException(smdPath); } - // TODO: Big endian files + // TODO: Big endian for SMDS (PlayStation 1), and mixed endian for SMDM (PlayStation 2) var songs = new List(SMDFiles.Length); for (int i = 0; i < SMDFiles.Length; i++) { using (FileStream stream = File.OpenRead(SMDFiles[i])) { + // This will read the SMD header to determine if the majority of the file is little endian or big endian var r = new EndianBinaryReader(stream, ascii: true); - SMD.Header header = new SMD.Header(r); - if(header.Type == "smdl") + Header = new SMD.Header(r); + if(Header.Type == "smdl") { - char[] chars = header.Label.ToCharArray(); + char[] chars = Header.Label.ToCharArray(); EndianBinaryPrimitives.TrimNullTerminators(ref chars); songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(SMDFiles[i])} - {new string(chars)}")); } - else if(header.Type == "smdb") + else if(Header.Type == "smdb") { r.Endianness = Endianness.BigEndian; - char[] chars = header.Label.ToCharArray(); + char[] chars = Header.Label.ToCharArray(); EndianBinaryPrimitives.TrimNullTerminators(ref chars); songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(SMDFiles[i])} - {new string(chars)}")); } diff --git a/VG Music Studio - Core/NDS/DSE/DSEMixer.cs b/VG Music Studio - Core/NDS/DSE/DSEMixer.cs index 89f6c52..caec7c2 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEMixer.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEMixer.cs @@ -98,7 +98,7 @@ internal void ChannelTick() { chan.Volume = SDATUtils.GetChannelVolume(vol); chan.Panpot = chan.Owner.Panpot; - chan.Timer = SDATUtils.GetChannelTimer(chan.BaseTimer, pitch); + chan.Timer = DSEUtils.GetChannelTimer(chan.BaseTimer, pitch); } } } diff --git a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs index 350f9d4..96f7c80 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs @@ -24,7 +24,6 @@ public DSEPlayer(string mainSWDFile, DSEConfig config, DSEMixer mixer) { DMixer = mixer; _config = config; - //string swdPath = Directory.GetFiles(mainSWDFile)[0]; MainSWD = new SWD(mainSWDFile); } @@ -78,7 +77,14 @@ protected override bool Tick(bool playing, bool recording) bool allDone = false; while (!allDone && TempoStack >= 240) { - TempoStack -= 240; + if (_config.Header.Type == "smdb") + { + TempoStack -= 130; + } + else + { + TempoStack -= 240; + } allDone = true; for (int i = 0; i < s.Tracks.Length; i++) { diff --git a/VG Music Studio - Core/NDS/DSE/DSETrack.cs b/VG Music Studio - Core/NDS/DSE/DSETrack.cs index a15e380..5bf092d 100644 --- a/VG Music Studio - Core/NDS/DSE/DSETrack.cs +++ b/VG Music Studio - Core/NDS/DSE/DSETrack.cs @@ -96,6 +96,7 @@ public void UpdateSongState(SongState.Track tin) for (int j = 0; j < channels.Length; j++) { DSEChannel c = channels[j]; + c ??= new DSEChannel((byte)j); // Failsafe in the rare event that the c variable becomes null if (!DSEUtils.IsStateRemovable(c.State)) { tin.Keys[numKeys++] = c.Key; diff --git a/VG Music Studio - Core/NDS/DSE/DSEUtils.cs b/VG Music Studio - Core/NDS/DSE/DSEUtils.cs index 6c3d60f..ceb5287 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEUtils.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEUtils.cs @@ -45,6 +45,199 @@ internal static class DSEUtils 0x000341B0, 0x000355F8, 0x00036A90, 0x00037F79, 0x000394B4, 0x0003AA41, 0x0003C021, 0x0003D654, 0x0003ECDA, 0x000403B5, 0x00041AE5, 0x0004326A, 0x00044A45, 0x00046277, 0x00047B00, 0x7FFFFFFF, }; + private static ReadOnlySpan PitchTable => new ushort[768] + { + 0, 59, 118, 178, 237, 296, 356, 415, + 475, 535, 594, 654, 714, 773, 833, 893, + 953, 1013, 1073, 1134, 1194, 1254, 1314, 1375, + 1435, 1496, 1556, 1617, 1677, 1738, 1799, 1859, + 1920, 1981, 2042, 2103, 2164, 2225, 2287, 2348, + 2409, 2471, 2532, 2593, 2655, 2716, 2778, 2840, + 2902, 2963, 3025, 3087, 3149, 3211, 3273, 3335, + 3397, 3460, 3522, 3584, 3647, 3709, 3772, 3834, + 3897, 3960, 4022, 4085, 4148, 4211, 4274, 4337, + 4400, 4463, 4526, 4590, 4653, 4716, 4780, 4843, + 4907, 4971, 5034, 5098, 5162, 5226, 5289, 5353, + 5417, 5481, 5546, 5610, 5674, 5738, 5803, 5867, + 5932, 5996, 6061, 6125, 6190, 6255, 6320, 6384, + 6449, 6514, 6579, 6645, 6710, 6775, 6840, 6906, + 6971, 7037, 7102, 7168, 7233, 7299, 7365, 7431, + 7496, 7562, 7628, 7694, 7761, 7827, 7893, 7959, + 8026, 8092, 8159, 8225, 8292, 8358, 8425, 8492, + 8559, 8626, 8693, 8760, 8827, 8894, 8961, 9028, + 9096, 9163, 9230, 9298, 9366, 9433, 9501, 9569, + 9636, 9704, 9772, 9840, 9908, 9976, 10045, 10113, + 10181, 10250, 10318, 10386, 10455, 10524, 10592, 10661, + 10730, 10799, 10868, 10937, 11006, 11075, 11144, 11213, + 11283, 11352, 11421, 11491, 11560, 11630, 11700, 11769, + 11839, 11909, 11979, 12049, 12119, 12189, 12259, 12330, + 12400, 12470, 12541, 12611, 12682, 12752, 12823, 12894, + 12965, 13036, 13106, 13177, 13249, 13320, 13391, 13462, + 13533, 13605, 13676, 13748, 13819, 13891, 13963, 14035, + 14106, 14178, 14250, 14322, 14394, 14467, 14539, 14611, + 14684, 14756, 14829, 14901, 14974, 15046, 15119, 15192, + 15265, 15338, 15411, 15484, 15557, 15630, 15704, 15777, + 15850, 15924, 15997, 16071, 16145, 16218, 16292, 16366, + 16440, 16514, 16588, 16662, 16737, 16811, 16885, 16960, + 17034, 17109, 17183, 17258, 17333, 17408, 17483, 17557, + 17633, 17708, 17783, 17858, 17933, 18009, 18084, 18160, + 18235, 18311, 18387, 18462, 18538, 18614, 18690, 18766, + 18842, 18918, 18995, 19071, 19147, 19224, 19300, 19377, + 19454, 19530, 19607, 19684, 19761, 19838, 19915, 19992, + 20070, 20147, 20224, 20302, 20379, 20457, 20534, 20612, + 20690, 20768, 20846, 20924, 21002, 21080, 21158, 21236, + 21315, 21393, 21472, 21550, 21629, 21708, 21786, 21865, + 21944, 22023, 22102, 22181, 22260, 22340, 22419, 22498, + 22578, 22658, 22737, 22817, 22897, 22977, 23056, 23136, + 23216, 23297, 23377, 23457, 23537, 23618, 23698, 23779, + 23860, 23940, 24021, 24102, 24183, 24264, 24345, 24426, + 24507, 24589, 24670, 24752, 24833, 24915, 24996, 25078, + 25160, 25242, 25324, 25406, 25488, 25570, 25652, 25735, + 25817, 25900, 25982, 26065, 26148, 26230, 26313, 26396, + 26479, 26562, 26645, 26729, 26812, 26895, 26979, 27062, + 27146, 27230, 27313, 27397, 27481, 27565, 27649, 27733, + 27818, 27902, 27986, 28071, 28155, 28240, 28324, 28409, + 28494, 28579, 28664, 28749, 28834, 28919, 29005, 29090, + 29175, 29261, 29346, 29432, 29518, 29604, 29690, 29776, + 29862, 29948, 30034, 30120, 30207, 30293, 30380, 30466, + 30553, 30640, 30727, 30814, 30900, 30988, 31075, 31162, + 31249, 31337, 31424, 31512, 31599, 31687, 31775, 31863, + 31951, 32039, 32127, 32215, 32303, 32392, 32480, 32568, + 32657, 32746, 32834, 32923, 33012, 33101, 33190, 33279, + 33369, 33458, 33547, 33637, 33726, 33816, 33906, 33995, + 34085, 34175, 34265, 34355, 34446, 34536, 34626, 34717, + 34807, 34898, 34988, 35079, 35170, 35261, 35352, 35443, + 35534, 35626, 35717, 35808, 35900, 35991, 36083, 36175, + 36267, 36359, 36451, 36543, 36635, 36727, 36820, 36912, + 37004, 37097, 37190, 37282, 37375, 37468, 37561, 37654, + 37747, 37841, 37934, 38028, 38121, 38215, 38308, 38402, + 38496, 38590, 38684, 38778, 38872, 38966, 39061, 39155, + 39250, 39344, 39439, 39534, 39629, 39724, 39819, 39914, + 40009, 40104, 40200, 40295, 40391, 40486, 40582, 40678, + 40774, 40870, 40966, 41062, 41158, 41255, 41351, 41448, + 41544, 41641, 41738, 41835, 41932, 42029, 42126, 42223, + 42320, 42418, 42515, 42613, 42710, 42808, 42906, 43004, + 43102, 43200, 43298, 43396, 43495, 43593, 43692, 43790, + 43889, 43988, 44087, 44186, 44285, 44384, 44483, 44583, + 44682, 44781, 44881, 44981, 45081, 45180, 45280, 45381, + 45481, 45581, 45681, 45782, 45882, 45983, 46083, 46184, + 46285, 46386, 46487, 46588, 46690, 46791, 46892, 46994, + 47095, 47197, 47299, 47401, 47503, 47605, 47707, 47809, + 47912, 48014, 48117, 48219, 48322, 48425, 48528, 48631, + 48734, 48837, 48940, 49044, 49147, 49251, 49354, 49458, + 49562, 49666, 49770, 49874, 49978, 50082, 50187, 50291, + 50396, 50500, 50605, 50710, 50815, 50920, 51025, 51131, + 51236, 51341, 51447, 51552, 51658, 51764, 51870, 51976, + 52082, 52188, 52295, 52401, 52507, 52614, 52721, 52827, + 52934, 53041, 53148, 53256, 53363, 53470, 53578, 53685, + 53793, 53901, 54008, 54116, 54224, 54333, 54441, 54549, + 54658, 54766, 54875, 54983, 55092, 55201, 55310, 55419, + 55529, 55638, 55747, 55857, 55966, 56076, 56186, 56296, + 56406, 56516, 56626, 56736, 56847, 56957, 57068, 57179, + 57289, 57400, 57511, 57622, 57734, 57845, 57956, 58068, + 58179, 58291, 58403, 58515, 58627, 58739, 58851, 58964, + 59076, 59189, 59301, 59414, 59527, 59640, 59753, 59866, + 59979, 60092, 60206, 60319, 60433, 60547, 60661, 60774, + 60889, 61003, 61117, 61231, 61346, 61460, 61575, 61690, + 61805, 61920, 62035, 62150, 62265, 62381, 62496, 62612, + 62727, 62843, 62959, 63075, 63191, 63308, 63424, 63540, + 63657, 63774, 63890, 64007, 64124, 64241, 64358, 64476, + 64593, 64711, 64828, 64946, 65064, 65182, 65300, 65418, + }; + private static ReadOnlySpan VolumeTable => new byte[724] + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 7, + 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 10, 10, 10, + 10, 10, 10, 10, 10, 11, 11, 11, + 11, 11, 11, 11, 11, 12, 12, 12, + 12, 12, 12, 12, 13, 13, 13, 13, + 13, 13, 13, 14, 14, 14, 14, 14, + 14, 15, 15, 15, 15, 15, 16, 16, + 16, 16, 16, 16, 17, 17, 17, 17, + 17, 18, 18, 18, 18, 19, 19, 19, + 19, 19, 20, 20, 20, 20, 21, 21, + 21, 21, 22, 22, 22, 22, 23, 23, + 23, 23, 24, 24, 24, 25, 25, 25, + 25, 26, 26, 26, 27, 27, 27, 28, + 28, 28, 29, 29, 29, 30, 30, 30, + 31, 31, 31, 32, 32, 33, 33, 33, + 34, 34, 35, 35, 35, 36, 36, 37, + 37, 38, 38, 38, 39, 39, 40, 40, + 41, 41, 42, 42, 43, 43, 44, 44, + 45, 45, 46, 46, 47, 47, 48, 48, + 49, 50, 50, 51, 51, 52, 52, 53, + 54, 54, 55, 56, 56, 57, 58, 58, + 59, 60, 60, 61, 62, 62, 63, 64, + 65, 66, 66, 67, 68, 69, 70, 70, + 71, 72, 73, 74, 75, 75, 76, 77, + 78, 79, 80, 81, 82, 83, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 93, + 94, 95, 96, 97, 98, 99, 101, 102, + 103, 104, 105, 106, 108, 109, 110, 111, + 113, 114, 115, 117, 118, 119, 121, 122, + 124, 125, 126, 127, + }; public static ReadOnlySpan FixedRests => new byte[0x10] { 96, 72, 64, 48, 36, 32, 24, 18, 16, 12, 9, 8, 6, 4, 3, 2, @@ -98,4 +291,64 @@ internal static long FindChunk(EndianBinaryReader r, string chunk) } #endregion + public static ushort GetChannelTimer(ushort baseTimer, int pitch) + { + int shift = 0; + pitch = -pitch; + + while (pitch < 0) + { + shift--; + pitch += 0x300; + } + + while (pitch >= 0x300) + { + shift++; + pitch -= 0x300; + } + + ulong timer = (PitchTable[pitch] + 0x10000uL) * baseTimer; + shift -= 16; + if (shift <= 0) + { + timer >>= -shift; + } + else if (shift < 32) + { + if ((timer & (ulong.MaxValue << (32 - shift))) != 0) + { + return ushort.MaxValue; + } + timer <<= shift; + } + else + { + return ushort.MaxValue; + } + + if (timer < 0x10) + { + return 0x10; + } + if (timer > ushort.MaxValue) + { + timer = ushort.MaxValue; + } + return (ushort)timer; + } + public static byte GetChannelVolume(int vol) + { + int a = vol / 0x80; + if (a < -723) + { + a = -723; + } + else if (a > 0) + { + a = 0; + } + return VolumeTable[a + 723]; + } + } diff --git a/VG Music Studio - Core/NDS/DSE/SWD.cs b/VG Music Studio - Core/NDS/DSE/SWD.cs index 6ce5257..4a495fc 100644 --- a/VG Music Studio - Core/NDS/DSE/SWD.cs +++ b/VG Music Studio - Core/NDS/DSE/SWD.cs @@ -2,8 +2,10 @@ using Kermalis.VGMusicStudio.Core.Codec; using Kermalis.VGMusicStudio.Core.Util; using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; namespace Kermalis.VGMusicStudio.Core.NDS.DSE; @@ -726,9 +728,8 @@ public WavInfo(EndianBinaryReader r, SWD swd) public class SampleBlock { public WavInfo? WavInfo; - public DSPADPCM? DSPADPCM; + public DSPADPCM DSPADPCM; public byte[]? Data; - public short[]? Data16Bit; } public class ProgramBank { @@ -798,6 +799,7 @@ public LFOInfo(EndianBinaryReader r) } } + public string FileName; public Header? Info; public string Type; // "swdb" or "swdl" public uint Length; @@ -815,32 +817,31 @@ public LFOInfo(EndianBinaryReader r) public SWD(string path) { - using (var stream = File.OpenRead(path)) + FileName = new FileInfo(path).Name; + var stream = File.OpenRead(path); + var r = new EndianBinaryReader(stream, ascii: true); + Info = new Header(r); + Type = Info.Type; + Length = Info.Length; + Version = Info.Version; + Programs = ReadPrograms(r, Info.NumPRGISlots, this); + + switch (Version) { - var r = new EndianBinaryReader(stream, ascii: true); - Info = new Header(r); - Type = Info.Type; - Length = Info.Length; - Version = Info.Version; - Programs = ReadPrograms(r, Info.NumPRGISlots, this); - - switch (Version) - { - case 0x402: + case 0x402: + { + Samples = ReadSamples(r, Info.NumWAVISlots, this); + break; + } + case 0x415: + { + if (Info.PCMDLength != 0 && (Info.PCMDLength & 0xFFFF0000) != 0xAAAA0000) { Samples = ReadSamples(r, Info.NumWAVISlots, this); - break; } - case 0x415: - { - if (Info.PCMDLength != 0 && (Info.PCMDLength & 0xFFFF0000) != 0xAAAA0000) - { - Samples = ReadSamples(r, Info.NumWAVISlots, this); - } - break; - } - default: throw new InvalidDataException(); - } + break; + } + default: throw new InvalidDataException(); } return; } @@ -871,7 +872,7 @@ private SampleBlock[] ReadSamples(EndianBinaryReader r, int numWAVISlots, SWD sw if (offset != 0) { r.Stream.Position = offset + waviDataOffset; - WavInfo wavInfo = new WavInfo(r, swd); + var wavInfo = new WavInfo(r, swd); switch (Type) { case "swdm": @@ -899,10 +900,16 @@ private SampleBlock[] ReadSamples(EndianBinaryReader r, int numWAVISlots, SWD sw WavInfo = wavInfo, // This is the only variable we can use for this initializer declarator, since the samples are DSP-ADPCM compressed }; r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; // This sets the EndianBinaryReader stream position offset to the encoded DSP-ADPCM data - var outputSize = (int)(wavInfo.LoopStart + wavInfo.LoopEnd * 4); // So we set a local variable with the size of Procyon DSE's LoopStart and LoopEnd offsets - - samples[i].DSPADPCM = new DSPADPCM(r, outputSize); // Then we read the entire DSP-ADPCM data + samples[i].DSPADPCM = new DSPADPCM(r, 1, false); // Reads the entire DSP-ADPCM header and encoded data + samples[i].DSPADPCM.Decode(); // Decodes all bytes into PCM16 data +#if DEBUG + // This is for dumping both the encoded and decoded samples, for ensuring that the decoder works correctly + new FileInfo("./ExtractedSamples/" + FileName + "/dsp/").Directory!.Create(); + File.WriteAllBytes("./ExtractedSamples/" + FileName + "/dsp/" + "sample" + i.ToString() + ".dsp", [.. samples[i].DSPADPCM.Info.ToBytes(), .. samples[i].DSPADPCM.Data]); + new FileInfo("./ExtractedSamples/" + FileName + "/wav/").Directory!.Create(); + File.WriteAllBytes("./ExtractedSamples/" + FileName + "/wav/" + "sample" + i.ToString() + ".wav", samples[i].DSPADPCM.ConvertToWav()); +#endif break; } default: @@ -950,7 +957,7 @@ private static KeyGroup[] ReadKeyGroups(EndianBinaryReader r, SWD swd) long chunkOffset = swd.KgrpChunkOffset = DSEUtils.FindChunk(r, "kgrp"); if (chunkOffset == -1) { - return Array.Empty(); + return []; } ChunkHeader info = swd.KgrpInfo = new ChunkHeader(r, chunkOffset, swd); diff --git a/VG Music Studio - Core/NDS/SDAT/SDATChannel.cs b/VG Music Studio - Core/NDS/SDAT/SDATChannel.cs index f323b24..b422874 100644 --- a/VG Music Studio - Core/NDS/SDAT/SDATChannel.cs +++ b/VG Music Studio - Core/NDS/SDAT/SDATChannel.cs @@ -71,7 +71,7 @@ public void StartPCM(SWAR.SWAV swav, int noteDuration) _swav = swav; if (swav.Format == SWAVFormat.ADPCM) { - _adpcmDecoder.Decode(swav.Samples); + _adpcmDecoder.Init(swav.Samples); } BaseTimer = swav.Timer; Start(noteDuration); diff --git a/VG Music Studio - Core/Util/DataUtils.cs b/VG Music Studio - Core/Util/DataUtils.cs index fe16180..14b0a69 100644 --- a/VG Music Studio - Core/Util/DataUtils.cs +++ b/VG Music Studio - Core/Util/DataUtils.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; namespace Kermalis.VGMusicStudio.Core.Util; @@ -11,4 +12,20 @@ public static void Align(this Stream s, int num) s.Position++; } } + + public static int RoundUp(int numToRound, int multiple) + { + if (multiple == 0) + { + return numToRound; + } + + int remainder = Math.Abs(numToRound) % multiple; + if (remainder == 0) + { + return numToRound; + } + + return (numToRound < 0) ? -(Math.Abs(numToRound) - remainder) : (numToRound + multiple - remainder); + } } From cf246122a2f1ccaa3ef5ca8ea05fb3c9d480921f Mon Sep 17 00:00:00 2001 From: PlatinumLucario Date: Fri, 22 Dec 2023 18:13:22 +1100 Subject: [PATCH 7/9] Proper support for sample looping --- VG Music Studio - Core/Codec/DSPADPCM.cs | 856 +++---------------- VG Music Studio - Core/NDS/DSE/DSEChannel.cs | 10 +- VG Music Studio - Core/NDS/DSE/DSEPlayer.cs | 53 +- VG Music Studio - Core/NDS/DSE/SMD.cs | 4 +- VG Music Studio - Core/NDS/DSE/SWD.cs | 6 +- 5 files changed, 181 insertions(+), 748 deletions(-) diff --git a/VG Music Studio - Core/Codec/DSPADPCM.cs b/VG Music Studio - Core/Codec/DSPADPCM.cs index 5965bcd..b18acb8 100644 --- a/VG Music Studio - Core/Codec/DSPADPCM.cs +++ b/VG Music Studio - Core/Codec/DSPADPCM.cs @@ -17,9 +17,9 @@ internal struct DSPADPCM public static double[] Tvec = new double[3]; - public DSPADPCMInfo Info; + public DSPADPCMInfo[] Info; private readonly ushort NumChannels; - private readonly bool IsInterleaved; + private ushort Channel; public byte[] Data; public short[]? DataOutput; @@ -28,10 +28,10 @@ internal struct DSPADPCM public int Scale; public byte ByteValue; public int FrameOffset; - public int Coef1; - public int Coef2; - public short Hist1; - public short Hist2; + public int[]? Coef1; + public int[]? Coef2; + public short[]? Hist1; + public short[]? Hist2; private int Nibble; public static class DSPADPCMConstants @@ -41,13 +41,16 @@ public static class DSPADPCMConstants public const int NibblesPerFrame = 16; } - public DSPADPCM(EndianBinaryReader r, ushort numChannels, bool hasInterleavedFrames) + public DSPADPCM(EndianBinaryReader r, ushort? numChannels) { - Info = new DSPADPCMInfo(r); // First, the 96 byte-long DSP-ADPCM header table is read and each variable is assigned with a value - NumChannels = numChannels; // The number of waveform channels - IsInterleaved = hasInterleavedFrames; // If the frames are byte-interleaved - Data = new byte[DataUtils.RoundUp((int)Info.NumAdpcmNibbles / 2, 16)]; // Next, this allocates the full size of the data, based on NumAdpcmNibbles divided by 2 and rounded up to the 16th byte - DataOutput = new short[Info.NumSamples]; // This will allocate the size of the DataOutput array based on the value in NumSamples + _ = numChannels == null ? NumChannels = 1 : NumChannels = (ushort)numChannels; // The number of waveform channels + Info = new DSPADPCMInfo[NumChannels]; + for (int i = 0; i < NumChannels; i++) + { + Info[i] = new DSPADPCMInfo(r); // First, the 96 byte-long DSP-ADPCM header table is read and each variable is assigned with a value + } + Data = new byte[DataUtils.RoundUp((int)Info[0].NumAdpcmNibbles / 2, 16) * NumChannels]; // Next, this allocates the full size of the data, based on NumAdpcmNibbles divided by 2 and rounded up to the 16th byte + DataOutput = new short[Info[0].NumSamples * NumChannels]; // This will allocate the size of the DataOutput array based on the value in NumSamples r.ReadBytes(Data); // This reads the compressed sample data based on the size allocated r.Stream.Align(16); // This will align the EndianBinaryReader stream offset to the 16th byte, since all sample data ends at every 16th byte @@ -183,6 +186,30 @@ public byte[] ToBytes() #region DSP-ADPCM Convert + public static int NibblesToSamples(int nibbles) + { + var fullFrames = nibbles / 16; + var remainder = nibbles % 16; + + return remainder > 0 ? (fullFrames * 14) + remainder - 2 : fullFrames * 14; + } + + public static int BytesToSamples(int bytes, int channels) + { + return channels <= 0 ? 0 : (bytes / channels) / (8 * 14); + } + + public readonly byte[] InfoToBytes() + { + var info = new DSPADPCMInfo[NumChannels]; + var infoData = new byte[96 * NumChannels]; + for (int i = 0; i < NumChannels; i++) + { + Array.Copy(info[i].ToBytes(), infoData, 96 * (i + 1)); + } + return infoData; + } + public readonly byte[] DataOutputToBytes() { int index = 0; @@ -205,10 +232,10 @@ public readonly byte[] ConvertToWav() uint formatLength = 16; // Always a length 16 ushort formatType = 1; // Always PCM16 // Number of channels is already manually defined - uint sampleRate = Info.SampleRate; // Sample Rate is read directly from the Info context + uint sampleRate = Info[0].SampleRate; // Sample Rate is read directly from the Info context ushort bitsPerSample = 16; // bitsPerSample must be written to AFTER numNibbles - uint numNibbles = sampleRate * bitsPerSample * NumChannels / 8; // numNibbles must be written BEFORE bitsPerSample is written - ushort bitRate = (ushort)((bitsPerSample * NumChannels) / 8); + uint numNibbles = sampleRate * bitsPerSample * Channel / 8; // numNibbles must be written BEFORE bitsPerSample is written + ushort bitRate = (ushort)((bitsPerSample * Channel) / 8); string dataID = "data"; uint dataSize = (uint)(DataOutput!.Length * 2); @@ -264,17 +291,18 @@ public readonly byte[] ConvertToWav() #endregion #region DSP-ADPCM Encode - public static void Encode(short[] src, byte[] dst, DSPADPCMInfo cxt, uint samples) + + public static void Encode(Span src, Span dst, DSPADPCMInfo cxt, uint samples) { - short[] coefs = cxt.Coef; + Span coefs = cxt.Coef; CorrelateCoefs(src, samples, coefs); int frameCount = (int)((samples / DSPADPCMConstants.SamplesPerFrame) + (samples % DSPADPCMConstants.SamplesPerFrame)); - short[] pcm = src; - byte[] adpcm = dst; - short[] pcmFrame = new short[DSPADPCMConstants.SamplesPerFrame + 2]; - byte[] adpcmFrame = new byte[DSPADPCMConstants.BytesPerFrame]; + Span pcm = src; + Span adpcm = dst; + Span pcmFrame = new short[DSPADPCMConstants.SamplesPerFrame + 2]; + Span adpcmFrame = new byte[DSPADPCMConstants.BytesPerFrame]; short srcIndex = 0; short dstIndex = 0; @@ -295,9 +323,9 @@ public static void Encode(short[] src, byte[] dst, DSPADPCMInfo cxt, uint sample cxt.Yn2 = 0; } - public static void InnerProductMerge(double[] vecOut, short[] pcmBuf) + public static void InnerProductMerge(Span vecOut, Span pcmBuf) { - pcmBuf = new short[14]; + pcmBuf = new short[14].AsSpan(); vecOut = Tvec; for (int i = 0; i <= 2; i++) @@ -309,9 +337,9 @@ public static void InnerProductMerge(double[] vecOut, short[] pcmBuf) } } - public static void OuterProductMerge(double[] mtxOut, short[] pcmBuf) + public static void OuterProductMerge(Span mtxOut, Span pcmBuf) { - pcmBuf = new short[14]; + pcmBuf = new short[14].AsSpan(); mtxOut[3] = Tvec[3]; for (int x = 1; x <= 2; x++) @@ -324,10 +352,10 @@ public static void OuterProductMerge(double[] mtxOut, short[] pcmBuf) } } - public static bool AnalyzeRanges(double[] mtx, int[] vecIdxsOut) + public static bool AnalyzeRanges(Span mtx, Span vecIdxsOut) { mtx[3] = Tvec[3]; - double[] recips = new double[3]; + Span recips = new double[3].AsSpan(); double val, tmp, min, max; /* Get greatest distance from zero */ @@ -409,7 +437,7 @@ public static bool AnalyzeRanges(double[] mtx, int[] vecIdxsOut) return false; } - public static void BidirectionalFilter(double[] mtx, int[] vecIdxs, double[] vecOut) + public static void BidirectionalFilter(Span mtx, Span vecIdxs, Span vecOut) { mtx[3] = Tvec[3]; vecOut = Tvec; @@ -439,7 +467,7 @@ public static void BidirectionalFilter(double[] mtx, int[] vecIdxs, double[] vec vecOut[0] = 1.0; } - public static bool QuadraticMerge(double[] inOutVec) + public static bool QuadraticMerge(Span inOutVec) { inOutVec = Tvec; @@ -458,7 +486,7 @@ public static bool QuadraticMerge(double[] inOutVec) return Math.Abs(v1) > 1.0; } - public static void FinishRecord(double[] vIn, double[] vOut) + public static void FinishRecord(Span vIn, Span vOut) { vIn = Tvec; vOut = Tvec; @@ -475,7 +503,7 @@ public static void FinishRecord(double[] vIn, double[] vOut) vOut[2] = vIn[2]; } - public static void MatrixFilter(double[] src, double[] dst) + public static void MatrixFilter(Span src, Span dst) { src = Tvec; dst = Tvec; @@ -502,12 +530,12 @@ public static void MatrixFilter(double[] src, double[] dst) } } - public static void MergeFinishRecord(double[] src, double[] dst) + public static void MergeFinishRecord(Span src, Span dst) { src = Tvec; dst = Tvec; int dstIndex = 0; - double[] tmp = new double[dstIndex]; + Span tmp = new double[dstIndex].AsSpan(); double val = src[0]; dst[0] = 1.0; @@ -533,7 +561,7 @@ public static void MergeFinishRecord(double[] src, double[] dst) FinishRecord(tmp, dst); } - public static double ContrastVectors(double[] source1, double[] source2) + public static double ContrastVectors(Span source1, Span source2) { source1 = Tvec; source2 = Tvec; @@ -544,15 +572,15 @@ public static double ContrastVectors(double[] source1, double[] source2) return val1 + (2.0 * val * val2) + (2.0 * (-source2[1] * val + -source2[2]) * val3); } - public static void FilterRecords(double[] vecBest, int exp, double[] records, int recordCount) + public static void FilterRecords(Span vecBest, int exp, Span records, int recordCount) { vecBest[8] = Tvec[8]; records = Tvec; - double[] bufferList = new double[8]; + Span bufferList = new double[8].AsSpan(); bufferList[8] = Tvec[8]; - int[] buffer1 = new int[8]; - double[] buffer2 = Tvec; + Span buffer1 = new int[8].AsSpan(); + Span buffer2 = Tvec; int index; double value, tempVal = 0; @@ -571,8 +599,8 @@ public static void FilterRecords(double[] vecBest, int exp, double[] records, in value = 1.0e30; for (int i = 0; i < exp; i++) { - vecBest = new double[i]; - records = new double[z]; + vecBest = new double[i].AsSpan(); + records = new double[z].AsSpan(); tempVal = ContrastVectors(vecBest, records); if (tempVal < value) { @@ -597,26 +625,26 @@ public static void FilterRecords(double[] vecBest, int exp, double[] records, in } } - public static void CorrelateCoefs(short[] source, uint samples, short[] coefsOut) + public static void CorrelateCoefs(Span source, uint samples, Span coefsOut) { int numFrames = (int)((samples + 13) / 14); int frameSamples; - short[] blockBuffer = new short[0x3800]; - short[] pcmHistBuffer = new short[2 + 14]; + Span blockBuffer = new short[0x3800].AsSpan(); + Span pcmHistBuffer = new short[2 + 14].AsSpan(); - double[] vec1 = Tvec; - double[] vec2 = Tvec; + Span vec1 = Tvec; + Span vec2 = Tvec; - double[] mtx = Tvec; + Span mtx = Tvec; mtx[3] = Tvec[3]; - int[] vecIdxs = new int[3]; + Span vecIdxs = new int[3].AsSpan(); - double[] records = new double[numFrames * 2]; + Span records = new double[numFrames * 2].AsSpan(); records = Tvec; int recordCount = 0; - double[] vecBest = new double[8]; + Span vecBest = new double[8].AsSpan(); vecBest[8] = Tvec[8]; int sourceIndex = 0; @@ -649,7 +677,7 @@ public static void CorrelateCoefs(short[] source, uint samples, short[] coefsOut for (int z = 0; z < 14; z++) pcmHistBuffer[1 + z] = blockBuffer[i++]; - pcmHistBuffer = new short[1]; + pcmHistBuffer = new short[1].AsSpan(); InnerProductMerge(vec1, pcmHistBuffer); if (Math.Abs(vec1[0]) > 10.0) @@ -660,7 +688,7 @@ public static void CorrelateCoefs(short[] source, uint samples, short[] coefsOut BidirectionalFilter(mtx, vecIdxs, vec1); if (!QuadraticMerge(vec1)) { - records = new double[recordCount]; + records = new double[recordCount].AsSpan(); FinishRecord(vec1, records); recordCount++; } @@ -675,8 +703,8 @@ public static void CorrelateCoefs(short[] source, uint samples, short[] coefsOut for (int z = 0; z < recordCount; z++) { - records = new double[z]; - vecBest = new double[0]; + records = new double[z].AsSpan(); + vecBest = new double[0].AsSpan(); MatrixFilter(records, vecBest); for (int y = 1; y <= 2; y++) vec1[y] += vecBest[0] + vecBest[y]; @@ -720,22 +748,22 @@ public static void CorrelateCoefs(short[] source, uint samples, short[] coefsOut } /* Make sure source includes the yn values (16 samples total) */ - public static void DSPEncodeFrame(short[] pcmInOut, int sampleCount, byte[] adpcmOut, short[] coefsIn) + public static void DSPEncodeFrame(Span pcmInOut, int sampleCount, Span adpcmOut, Span coefsIn) { - pcmInOut = new short[16]; - adpcmOut = new byte[8]; - coefsIn = new short[8]; - coefsIn = new short[2]; + pcmInOut = new short[16].AsSpan(); + adpcmOut = new byte[8].AsSpan(); + coefsIn = new short[8].AsSpan(); + coefsIn = new short[2].AsSpan(); - int[] inSamples = new int[8]; - inSamples = new int[16]; - int[] outSamples = new int[8]; - outSamples = new int[14]; + Span inSamples = new int[8].AsSpan(); + inSamples = new int[16].AsSpan(); + Span outSamples = new int[8].AsSpan(); + outSamples = new int[14].AsSpan(); int bestIndex = 0; - int[] scale = new int[8]; - double[] distAccum = new double[8]; + Span scale = new int[8].AsSpan(); + Span distAccum = new double[8].AsSpan(); /* Iterate through each coef set, finding the set with the smallest error */ for (int i = 0; i < 8; i++) @@ -843,11 +871,12 @@ public static void DSPEncodeFrame(short[] pcmInOut, int sampleCount, byte[] adpc } } - public static void EncodeFrame(short[] src, byte[] dst, short[] coefs, byte one) + public static void EncodeFrame(Span src, Span dst, Span coefs, byte one) { coefs = new short[0 + 2]; DSPEncodeFrame(src, 14, dst, coefs); } + #endregion #region DSP-ADPCM Decode @@ -881,14 +910,19 @@ public static short Clamp16(int value) } #region Current code - public void Init(byte[] data, DSPADPCMInfo info) + public void Init(byte[] data, DSPADPCMInfo[] info) { Info = info; Data = data; DataOffset = 0; SamplePos = 0; FrameOffset = 0; - } + + Hist1 = new short[NumChannels]; + Hist2 = new short[NumChannels]; + Coef1 = new int[NumChannels]; + Coef2 = new int[NumChannels]; + } public void Decode() { @@ -896,7 +930,7 @@ public void Decode() // Each DSP-ADPCM frame is 8 bytes long: 1 byte for header, 7 bytes for sample data // This loop reads every 8 bytes and decodes them until the samplePos reaches NumSamples - while (SamplePos < Info.NumSamples) + while (SamplePos < Info[0].NumSamples) { // This function will decode one frame at a time DecodeFrame(); @@ -910,41 +944,54 @@ public void Decode() public void DecodeFrame() { - // It will decode 1 single DSP frame of size 0x08 (src) into a 14 samples in a PCM buffer (dst) - Hist1 = Info.Yn1; - Hist2 = Info.Yn2; + // It will decode 1 single DSP frame of size 0x08 (src) into 14 samples in a PCM buffer (dst) + for (int i = 0; i < NumChannels; i++) + { + Hist1![i] = Info[i].Yn1; + Hist2![i] = Info[i].Yn2; + } // Parsing the frame's header byte Scale = 1 << ((Data[DataOffset]) & 0xf); int coefIndex = ((Data[DataOffset] >> 4) & 0xf) * 2; - // Parsing the coefficient pairs, based on the nibble's value - Coef1 = Info.Coef[coefIndex + 0]; - Coef2 = Info.Coef[coefIndex + 1]; + // Parsing the coefficient pairs, based on the nibble's value + for (int i = 0; i < NumChannels; i++) + { + Coef1![i] = Info[i].Coef[coefIndex + 0]; + Coef2![i] = Info[i].Coef[coefIndex + 1]; + } // This loop decodes the frame's nibbles, each of which are 4-bits long (half a byte in length) - for (FrameOffset = 0; FrameOffset < DSPADPCMConstants.SamplesPerFrame; FrameOffset++) + for (FrameOffset = 0; FrameOffset < DSPADPCMConstants.SamplesPerFrame * NumChannels; FrameOffset += NumChannels) { - // Stores the value of the entire byte based on the frame's offset - ByteValue = Data[DataOffset + 0x01 + FrameOffset / 2]; + // This ensures multi-channel DSP-ADPCM data is decoded as well + for (Channel = 0; Channel < NumChannels; Channel++) + { + // Stores the value of the entire byte based on the frame's offset + ByteValue = Data[DataOffset + 0x01 + FrameOffset / 2]; - // This function decodes one nibble within a frame into a sample - short sample = GetSample(); + // This function decodes one nibble within a frame into a sample + short sample = GetSample(); - // The DSP-ADPCM frame may have bytes that go beyond the DataOutput length, if this happens, this will safely finish the DecodeFrame function's task as is - if ((SamplePos + FrameOffset) * NumChannels >= DataOutput!.Length) { return; } + // The DSP-ADPCM frame may have bytes that go beyond the DataOutput length, if this happens, this will safely finish the DecodeFrame function's task as is + if ((SamplePos + FrameOffset) * (Channel + 1) >= DataOutput!.Length) { return; } - // The PCM16 sample is stored into the array entry, based on the sample offset and frame offset, multiplied by the number of wave channels - DataOutput[(SamplePos + FrameOffset) * NumChannels] = sample; + // The PCM16 sample is stored into the array entry, based on the sample offset and frame offset, multiplied by which wave channel is being used + DataOutput[(SamplePos + FrameOffset) * (Channel + 1)] = sample; - // History values are stored, hist1 is copied into hist2 and the PCM16 sample is copied into hist1, before moving onto the next byte in the frame - Hist2 = Hist1; - Hist1 = sample; + // History values are stored, hist1 is copied into hist2 and the PCM16 sample is copied into hist1, before moving onto the next byte in the frame + Hist2![Channel] = Hist1![Channel]; + Hist1[Channel] = sample; + } } // After the frame is decoded, the values in hist1 and hist2 are copied into Yn1 and Yn2 to prepare for the next frame - Info.Yn1 = Hist1; - Info.Yn2 = Hist2; + for (int i = 0; i < NumChannels; i++) + { + Info[i].Yn1 = Hist1![i]; + Info[i].Yn2 = Hist2![i]; + } } public short GetSample() @@ -953,7 +1000,7 @@ public short GetSample() GetLowNibble(ByteValue) : // If the byte is not 0, it will obtain the least significant nibble (4-bits) GetHighNibble(ByteValue); // Otherwise, if the byte is 0, it will obtain the most significant nibble (4-bits) int largerVal = (Nibble * Scale) << 11; // The nibble's value is multiplied by scale's value, then 11 bits are shifted left, making the value larger - int newVal = (largerVal + 1024 + (Coef1 * Hist1) + (Coef2 * Hist2)) >> 11; // Coefficients are multiplied by the value stored in hist1 and hist2 respectively, then the values are added together to make a new value + int newVal = (largerVal + 1024 + (Coef1![Channel] * Hist1![Channel]) + (Coef2![Channel] * Hist2![Channel])) >> 11; // Coefficients are multiplied by the value stored in hist1 and hist2 respectively, then the values are added together to make a new value short sample = Clamp16(newVal); // The new value is then clamped into a 16-bit value, which makes a PCM16 sample return sample; @@ -961,640 +1008,9 @@ public short GetSample() #endregion - #region Old Code 1 - //public static void Decode(Span src, Span dst, ref DSPADPCMInfo cxt, uint samples) - //{ - // short hist1 = cxt.Yn1; - // short hist2 = cxt.Yn2; - // short[] coefs = cxt.Coef; - // int srcIndex = 0; - // int dstIndex = 0; - - // int frameCount = DivideByRoundUp((int)samples, DSPADPCMConstants.SamplesPerFrame); - // int samplesRemaining = (int)samples; - - // for (int i = 0; i < frameCount; i++) - // { - // int predictor = GetHighNibble(src[srcIndex]) & 0xF; - // int scale = 1 << GetLowNibble(src[srcIndex++]); - // short coef1 = coefs[predictor * 2]; - // short coef2 = coefs[predictor * 2 + 1]; - - // int samplesToRead = Math.Min(DSPADPCMConstants.SamplesPerFrame, samplesRemaining); - - - // for (int s = 0; s < samplesToRead; s++) - // { - // // Get bits per byte - // //byte bits = src[srcIndex++]; - // byte bits = src[srcIndex + (s >> 1)]; - // int sample = (s % 2) == 0 ? GetHighNibble(bits) : GetLowNibble(bits); - // sample = sample >= 8 ? sample - 16 : sample; - // sample = (((scale * sample) << 11) + 1024 + ((coef1 * hist1) + (coef2 * hist2))) >> 11; - // short finalSample = Clamp16(sample); - - // hist2 = hist1; - // hist1 = finalSample; - - // //if (samplesToRead <= 14) { samplesToRead = 0; } - // //srcIndex += 1; - // dst[dstIndex++] = finalSample; - // if (dstIndex >= samplesToRead) break; - // } - - // //samplesRemaining -= samplesToRead; - // srcIndex += samplesToRead / 2; - // } - //} - - - //public static void GetLoopContext(Span src, ref DSPADPCMInfo cxt, uint samples) - //{ - // short hist1 = cxt.Yn1; - // short hist2 = cxt.Yn2; - // short[] coefs = cxt.Coef; - // int srcIndex = 0; - // byte ps = 0; - - // int frameCount = DivideByRoundUp((int)samples, DSPADPCMConstants.SamplesPerFrame); - // int samplesRemaining = (int)samples; - - // for (int i = 0; i < frameCount; i++) - // { - // ps = src[srcIndex]; - // int predictor = GetHighNibble(src[srcIndex]) & 0x7; - // int scale = 1 << GetLowNibble(src[srcIndex++]); - // short coef1 = coefs[predictor * 2]; - // short coef2 = coefs[predictor * 2 + 1]; - - // int samplesToRead = Math.Min(DSPADPCMConstants.SamplesPerFrame, samplesRemaining); - - // for (int s = 0; s < samplesToRead; s++) - // { - // int sample = s % 2 == 0 ? GetHighNibble(src[srcIndex]) : GetLowNibble(src[srcIndex++]); - // sample = sample >= 8 ? sample - 16 : sample; - // sample = (((scale * sample) << 11) + 1024 + (coef1 * hist1 + coef2 * hist2)) >> 11; - // short finalSample = Clamp16(sample); - - // hist2 = hist1; - // hist1 = finalSample; - // } - // samplesRemaining -= samplesToRead; - // } - - // cxt.LoopPredScale = ps; - // cxt.LoopYn1 = hist1; - // cxt.LoopYn2 = hist2; - //} - #endregion #endregion - #region Method 2 - - public class PlayConfigType - { - int config_set; /* some of the mods below are set */ - - /* modifiers */ - int play_forever; - int ignore_loop; - int force_loop; - int really_force_loop; - int ignore_fade; - - /* processing */ - double loop_count; - int pad_begin; - int trim_begin; - int body_time; - int trim_end; - double fade_delay; /* not in samples for backwards compatibility */ - double fade_time; - int pad_end; - - double pad_begin_s; - double trim_begin_s; - double body_time_s; - double trim_end_s; - //double fade_delay_s; - //double fade_time_s; - double pad_end_s; - - /* internal flags */ - int pad_begin_set; - int trim_begin_set; - int body_time_set; - int loop_count_set; - int trim_end_set; - int fade_delay_set; - int fade_time_set; - int pad_end_set; - - /* for lack of a better place... */ - int is_txtp; - int is_mini_txtp; - - } - - - public class PlayStateType - { - int input_channels; - int output_channels; - - int pad_begin_duration; - int pad_begin_left; - int trim_begin_duration; - int trim_begin_left; - int body_duration; - int fade_duration; - int fade_left; - int fade_start; - int pad_end_duration; - //int pad_end_left; - int pad_end_start; - - int play_duration; /* total samples that the stream lasts (after applying all config) */ - int play_position; /* absolute sample where stream is */ - - } - - public class Stream - { - /* basic config */ - int num_samples; /* the actual max number of samples */ - int sample_rate; /* sample rate in Hz */ - public int channels; /* number of channels */ - CodecType coding_type; /* type of encoding */ - LayoutType layout_type; /* type of layout */ - MetaType meta_type; /* type of metadata */ - - /* loopin config */ - int loop_flag; /* is this stream looped? */ - int loop_start_sample; /* first sample of the loop (included in the loop) */ - int loop_end_sample; /* last sample of the loop (not included in the loop) */ - - /* layouts/block config */ - int interleave_block_size; /* interleave, or block/frame size (depending on the codec) */ - int interleave_first_block_size; /* different interleave for first block */ - int interleave_first_skip; /* data skipped before interleave first (needed to skip other channels) */ - int interleave_last_block_size; /* smaller interleave for last block */ - int frame_size; /* for codecs with configurable size */ - - /* subsong config */ - int num_streams; /* for multi-stream formats (0=not set/one stream, 1=one stream) */ - int stream_index; /* selected subsong (also 1-based) */ - int stream_size; /* info to properly calculate bitrate in case of subsongs */ - char[] stream_name = new char[255]; /* name of the current stream (info), if the file stores it and it's filled */ - - /* mapping config (info for plugins) */ - uint channel_layout; /* order: FL FR FC LFE BL BR FLC FRC BC SL SR etc (WAVEFORMATEX flags where FL=lowest bit set) */ - - /* other config */ - int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */ - - - /* layout/block state */ - int full_block_size; /* actual data size of an entire block (ie. may be fixed, include padding/headers, etc) */ - int current_sample; /* sample point within the file (for loop detection) */ - int samples_into_block; /* number of samples into the current block/interleave/segment/etc */ - int current_block_offset; /* start of this block (offset of block header) */ - int current_block_size; /* size in usable bytes of the block we're in now (used to calculate num_samples per block) */ - int current_block_samples; /* size in samples of the block we're in now (used over current_block_size if possible) */ - int next_block_offset; /* offset of header of the next block */ - - /* loop state (saved when loop is hit to restore later) */ - int loop_current_sample; /* saved from current_sample (same as loop_start_sample, but more state-like) */ - int loop_samples_into_block;/* saved from samples_into_block */ - int loop_block_offset; /* saved from current_block_offset */ - int loop_block_size; /* saved from current_block_size */ - int loop_block_samples; /* saved from current_block_samples */ - int loop_next_block_offset; /* saved from next_block_offset */ - int hit_loop; /* save config when loop is hit, but first time only */ - - - /* decoder config/state */ - int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */ - int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to them */ - int ws_output_size; /* WS ADPCM: output bytes for this block */ - - - /* main state */ - public Channel[] ch; /* array of channels */ - Channel start_ch; /* shallow copy of channels as they were at the beginning of the stream (for resets) */ - Channel loop_ch; /* shallow copy of channels as they were at the loop point (for loops) */ - IntPtr start_vgmstream; /* shallow copy of the VGMSTREAM as it was at the beginning of the stream (for resets) */ - - IntPtr mixing_data; /* state for mixing effects */ - - /* Optional data the codec needs for the whole stream. This is for codecs too - * different from vgmstream's structure to be reasonably shoehorned. - * Note also that support must be added for resetting, looping and - * closing for every codec that uses this, as it will not be handled. */ - IntPtr codec_data; - /* Same, for special layouts. layout_data + codec_data may exist at the same time. */ - IntPtr layout_data; - - - /* play config/state */ - int config_enabled; /* config can be used */ - PlayConfigType config; /* player config (applied over decoding) */ - PlayStateType pstate; /* player state (applied over decoding) */ - int loop_count; /* counter of complete loops (1=looped once) */ - int loop_target; /* max loops before continuing with the stream end (loops forever if not set) */ - short[] tmpbuf; /* garbage buffer used for seeking/trimming */ - int tmpbuf_size; /* for all channels (samples = tmpbuf_size / channels) */ - - } - - /* read from a file, returns number of bytes read */ - public static int read_streamfile(Span dst, int offset, int length, StreamFile sf) - { - return read_streamfile(dst, offset, length, sf); - } - - /* return file size */ - public static int get_streamfile_size(StreamFile sf) - { - return get_streamfile_size(sf); - } - - public class StreamFile - { - - /* read 'length' data at 'offset' to 'dst' */ - static int read(Span dst, int offset, int length, StreamFile[] sf) - { - return read(dst, offset, length, sf); - } - - /* get max offset */ - static int get_size(StreamFile[] sf) - { - return get_size(sf); - } - - //todo: DO NOT USE, NOT RESET PROPERLY (remove?) - static int get_offset(StreamFile[] sf) - { - return get_offset(sf); - } - - /* copy current filename to name buf */ - static void get_name(string name, int name_size, StreamFile[] sf) - { - sf.SetValue(name, name_size); - } - - /* open another streamfile from filename */ - public StreamFile() - { - string filename; - int buf_size; - StreamFile[] sf; - } - - /* free current STREAMFILE */ - //void (* close) (struct _StreamFile sf); - - /* Substream selection for formats with subsongs. - * Not ideal here, but it was the simplest way to pass to all init_vgmstream_x functions. */ - int stream_index; /* 0=default/auto (first), 1=first, N=Nth */ - - } - - public class g72x_state - { - long yl; /* Locked or steady state step size multiplier. */ - short yu; /* Unlocked or non-steady state step size multiplier. */ - short dms; /* Short term energy estimate. */ - short dml; /* Long term energy estimate. */ - short ap; /* Linear weighting coefficient of 'yl' and 'yu'. */ - - short[] a = new short[2]; /* Coefficients of pole portion of prediction filter. */ - short[] b = new short[6]; /* Coefficients of zero portion of prediction filter. */ - short[] pk = new short[2]; /* - * Signs of previous two samples of a partially - * reconstructed signal. - */ - short[] dq = new short[6]; /* - * Previous 6 samples of the quantized difference - * signal represented in an internal floating point - * format. - */ - short[] sr = new short[2]; /* - * Previous 2 samples of the quantized difference - * signal represented in an internal floating point - * format. - */ - char td; /* delayed tone detect, new in 1988 version */ - }; - - public class Channel - { - public StreamFile streamfile = new StreamFile(); /* file used by this channel */ - public long channel_start_offset; /* where data for this channel begins */ - public long offset; /* current location in the file */ - - public int frame_header_offset; /* offset of the current frame header (for WS) */ - public int samples_left_in_frame; /* for WS */ - - /* format specific */ - - /* adpcm */ - public short[] adpcm_coef = new short[16]; /* formats with decode coefficients built in (DSP, some ADX) */ - public int[] adpcm_coef_3by32 = new int[0x60]; /* Level-5 0x555 */ - public short[] vadpcm_coefs = new short[8 * 2 * 8]; /* VADPCM: max 8 groups * max 2 order * fixed 8 subframe coefs */ - public short adpcm_history1_16; /* previous sample */ - public int adpcm_history1_32; - - public short adpcm_history2_16; /* previous previous sample */ - public int adpcm_history2_32; - - public short adpcm_history3_16; - public int adpcm_history3_32; - - public short adpcm_history4_16; - public int adpcm_history4_32; - - - //double adpcm_history1_double; - //double adpcm_history2_double; - - public int adpcm_step_index; /* for IMA */ - public int adpcm_scale; /* for MS ADPCM */ - - /* state for G.721 decoder, sort of big but we might as well keep it around */ - public g72x_state g72x_state = new g72x_state(); - - /* ADX encryption */ - public int adx_channels; - public short adx_xor; - public short adx_mult; - public short adx_add; - - }; - - public static int[] nibble_to_int = new int[16] {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1}; - - public static int get_nibble_signed(byte n, int upper) - { - /*return ((n&0x70)-(n&0x80))>>4;*/ - return nibble_to_int[(n >> (upper != 0 ? 4 : 0)) & 0x0f]; - } - - public static int get_high_nibble_signed(byte n) - { - /*return ((n&0x70)-(n&0x80))>>4;*/ - return nibble_to_int[n >> 4]; - } - - public static int get_low_nibble_signed(byte n) - { - /*return (n&7)-(n&8);*/ - return nibble_to_int[n & 0xf]; - } - - public static int clamp16(int val) - { - if (val > 32767) return 32767; - else if (val < -32768) return -32768; - else return val; - } - - public static void decode_ngc_dsp(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do) - { - byte[] frame = new byte[0x08] { 0,0,0,0,0,0,0,0 }; - int frame_offset; - int i, frames_in, sample_count = 0; - int bytes_per_frame, samples_per_frame; - int coef_index, scale, coef1, coef2; - int hist1 = stream.adpcm_history1_16; - int hist2 = stream.adpcm_history2_16; - - - /* external interleave (fixed size), mono */ - bytes_per_frame = 0x08; - samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 14 */ - frames_in = first_sample / samples_per_frame; - first_sample = first_sample % samples_per_frame; - - /* parse frame header */ - frame_offset = (int)((stream.offset + bytes_per_frame) * frames_in); - read_streamfile(frame, frame_offset, bytes_per_frame, stream.streamfile); /* ignore EOF errors */ - scale = 1 << ((frame[0] >> 0) & 0xf); - coef_index = (frame[0] >> 4) & 0xf; - - if (coef_index >= 8) { Debug.WriteLine($"DSP: incorrect coefs at %x\n", (uint)frame_offset); } - //if (coef_index > 8) //todo not correctly clamped in original decoder? - // coef_index = 8; - - coef1 = stream.adpcm_coef[coef_index * 2 + 0]; - coef2 = stream.adpcm_coef[coef_index * 2 + 1]; - - - /* decode nibbles */ - for (i = first_sample; i < first_sample + samples_to_do; i++) - { - int sample = 0; - byte nibbles = frame[0x01 + i / 2]; - - sample = (i & 1) != 0 ? /* high nibble first */ - get_low_nibble_signed(nibbles) : - get_high_nibble_signed(nibbles); - sample = ((sample * scale) << 11); - sample = (sample + 1024 + coef1 * hist1 + coef2 * hist2) >> 11; - sample = clamp16(sample); - - outbuf[sample_count] = sample; - sample_count += channelspacing; - - hist2 = hist1; - hist1 = sample; - } - - stream.adpcm_history1_16 = (short)hist1; - stream.adpcm_history2_16 = (short)hist2; - } - - - /* read from memory rather than a file */ - public static void decode_ngc_dsp_subint_internal(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do, Span frame) - { - int i, sample_count = 0; - int bytes_per_frame, samples_per_frame; - int coef_index, scale, coef1, coef2; - int hist1 = stream.adpcm_history1_16; - int hist2 = stream.adpcm_history2_16; - - - /* external interleave (fixed size), mono */ - bytes_per_frame = 0x08; - samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 14 */ - first_sample = first_sample % samples_per_frame; - if (samples_to_do > samples_per_frame) { Debug.WriteLine($"DSP: layout error, too many samples\n"); } - - /* parse frame header */ - scale = 1 << ((frame[0] >> 0) & 0xf); - coef_index = (frame[0] >> 4) & 0xf; - - if (coef_index >= 8) { Debug.WriteLine($"DSP: incorrect coefs\n"); } - //if (coef_index > 8) //todo not correctly clamped in original decoder? - // coef_index = 8; - - coef1 = stream.adpcm_coef[coef_index * 2 + 0]; - coef2 = stream.adpcm_coef[coef_index * 2 + 1]; - - for (i = first_sample; i < first_sample + samples_to_do; i++) - { - int sample = 0; - byte nibbles = frame[0x01 + i / 2]; - - sample = (i & 1) != 0 ? - get_low_nibble_signed(nibbles) : - get_high_nibble_signed(nibbles); - sample = ((sample * scale) << 11); - sample = (sample + 1024 + coef1 * hist1 + coef2 * hist2) >> 11; - sample = clamp16(sample); - - outbuf[sample_count] = sample; - sample_count += channelspacing; - - hist2 = hist1; - hist1 = sample; - } - - stream.adpcm_history1_16 = (short)hist1; - stream.adpcm_history2_16 = (short)hist2; - } - - private static sbyte read_8bit(int offset, StreamFile sf) - { - byte[] buf = new byte[1]; - - if (read_streamfile(buf, offset, 1, sf) != 1) return -1; - return (sbyte)buf[0]; - } - - /* decode DSP with byte-interleaved frames (ex. 0x08: 1122112211221122) */ - public static void decode_ngc_dsp_subint(Channel stream, Span outbuf, int channelspacing, int first_sample, int samples_to_do, int channel, int interleave) - { - byte[] frame = new byte[0x08]; - int i; - int frames_in = first_sample / 14; - - for (i = 0; i < 0x08; i++) - { - /* base + current frame + subint section + subint byte + channel adjust */ - frame[i] = (byte)read_8bit( - (int)((stream.offset - + frames_in) * (0x08 * channelspacing) - + i / interleave * interleave * channelspacing - + i % interleave - + interleave * channel), stream.streamfile); - } - - decode_ngc_dsp_subint_internal(stream, outbuf, channelspacing, first_sample, samples_to_do, frame); - } - - - /* - * The original DSP spec uses nibble counts for loop points, and some - * variants don't have a proper sample count, so we (who are interested - * in sample counts) need to do this conversion occasionally. - */ - public static int dsp_nibbles_to_samples(int nibbles) - { - int whole_frames = nibbles / 16; - int remainder = nibbles % 16; - - if (remainder > 0) return whole_frames * 14 + remainder - 2; - else return whole_frames * 14; - } - - public static int dsp_bytes_to_samples(int bytes, int channels) - { - if (channels <= 0) return 0; - return bytes / channels / 8 * 14; - } - - /* host endian independent multi-byte integer reading */ - public static short get_16bitBE(Span p) - { - return (short)(((ushort)p[0] << 8) | ((ushort)p[1])); - } - - public static short get_16bitLE(Span p) - { - return (short)(((ushort)p[0]) | ((ushort)p[1] << 8)); - } - - public static short read_16bitLE(int offset, StreamFile sf) - { - byte[] buf = new byte[2]; - - if (read_streamfile(buf, offset, 2, sf) != 2) return -1; - return get_16bitLE(buf); - } - public static short read_16bitBE(int offset, StreamFile sf) - { - byte[] buf = new byte[2]; - - if (read_streamfile(buf, offset, 2, sf) != 2) return -1; - return get_16bitBE(buf); - } - - /* reads DSP coefs built in the streamfile */ - public static void dsp_read_coefs_be(Stream vgmstream, StreamFile streamFile, int offset, int spacing) - { - dsp_read_coefs(vgmstream, streamFile, offset, spacing, 1); - } - public static void dsp_read_coefs_le(Stream vgmstream, StreamFile streamFile, int offset, int spacing) - { - dsp_read_coefs(vgmstream, streamFile, offset, spacing, 0); - } - public static void dsp_read_coefs(Stream vgmstream, StreamFile streamFile, int offset, int spacing, int be) - { - int ch, i; - /* get ADPCM coefs */ - for (ch = 0; ch < vgmstream.channels; ch++) - { - for (i = 0; i < 16; i++) - { - vgmstream.ch[ch].adpcm_coef[i] = be != 0 ? - read_16bitBE(offset + ch * spacing + i * 2, streamFile) : - read_16bitLE(offset + ch * spacing + i * 2, streamFile); - } - } - } - - /* reads DSP initial hist built in the streamfile */ - public static void dsp_read_hist_be(Stream vgmstream, StreamFile streamFile, int offset, int spacing) - { - dsp_read_hist(vgmstream, streamFile, offset, spacing, 1); - } - public static void dsp_read_hist_le(Stream vgmstream, StreamFile streamFile, int offset, int spacing) - { - dsp_read_hist(vgmstream, streamFile, offset, spacing, 0); - } - public static void dsp_read_hist(Stream vgmstream, StreamFile streamFile, int offset, int spacing, int be) - { - int ch; - /* get ADPCM hist */ - for (ch = 0; ch < vgmstream.channels; ch++) - { - vgmstream.ch[ch].adpcm_history1_16 = be != 0 ? - read_16bitBE(offset + ch * spacing + 0 * 2, streamFile) : - read_16bitLE(offset + ch * spacing + 0 * 2, streamFile); ; - vgmstream.ch[ch].adpcm_history2_16 = be != 0 ? - read_16bitBE(offset + ch * spacing + 1 * 2, streamFile) : - read_16bitLE(offset + ch * spacing + 1 * 2, streamFile); ; - } - } - - - #endregion #endregion diff --git a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs index 8dae6a0..a3a319a 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs @@ -96,7 +96,7 @@ public bool StartPCM(SWD localswd, SWD masterswd, byte voice, int key, uint note case "wds ": throw new NotImplementedException("The base timer for the WDS type is not yet implemented."); // PlayStation case "swdm": throw new NotImplementedException("The base timer for the SWDM type is not yet implemented."); // PlayStation 2 case "swdl": BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo!.SampleRate); break; // Nintendo DS - case "swdb": BaseTimer = 380; break; // Wii + case "swdb": BaseTimer = (ushort)(_sample.WavInfo!.SampleRate / 116); break; // Wii // The best I can get is setting the BaseTimer to 380 } if (_sample.WavInfo!.SampleFormat == SampleFormat.ADPCM) { @@ -444,11 +444,11 @@ public void Process(out short left, out short right) case "swdb": { // If hit end - if (_dataOffset >= _sample!.DSPADPCM!.DataOutput!.Length) + if (_dataOffset >= DSPADPCM.NibblesToSamples((int)_sample!.WavInfo!.LoopEnd)) // Wii DSE always reads the LoopEnd address (in nibbles) when looping is enabled for a SWD entry, instead of reading until the end of the sample data { if (_sample.WavInfo!.Loop) { - _dataOffset = (int)(_sample.WavInfo.LoopStart / 2); // Wii values for LoopStart are counted 8-bits at a time, but because DataOutput is using a 16-bit array, LoopStart needs to be divided by 2 + _dataOffset = DSPADPCM.NibblesToSamples((int)_sample.WavInfo.LoopStart); // Wii values for LoopStart offset are counted in nibbles (4-bits or half a byte) at a time, but because DataOutput is using a 16-bit array, LoopStart value needs to be converted to a 16-bit PCM sample offset } else { @@ -457,8 +457,8 @@ public void Process(out short left, out short right) return; } } - short samp = _sample.DSPADPCM!.DataOutput![_dataOffset++]; // Since DataOutput is already a 16-bit array, only one array entry is needed per loop, no bitshifting needed either - samp = (short)(samp * Volume / 0x7F); + short samp = _sample.DSPADPCM!.DataOutput![_dataOffset++]; // Since DataOutput is already a 16-bit array, only one array entry is needed per loop, no bitshifting needed either + samp = (short)(samp * Volume / 0x7F); _prevLeft = (short)(samp * (-Panpot + 0x40) / 0x80); _prevRight = (short)(samp * (Panpot + 0x40) / 0x80); break; diff --git a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs index 96f7c80..5e1ed7c 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs @@ -75,25 +75,42 @@ protected override bool Tick(bool playing, bool recording) DSELoadedSong s = _loadedSong!; bool allDone = false; - while (!allDone && TempoStack >= 240) + switch (_config.Header.Type) { - if (_config.Header.Type == "smdb") - { - TempoStack -= 130; - } - else - { - TempoStack -= 240; - } - allDone = true; - for (int i = 0; i < s.Tracks.Length; i++) - { - TickTrack(s, s.Tracks[i], ref allDone); - } - if (DMixer.IsFadeDone()) - { - allDone = true; - } + case "smdl": + { + while (!allDone && TempoStack >= 240) + { + TempoStack -= 240; + allDone = true; + for (int i = 0; i < s.Tracks.Length; i++) + { + TickTrack(s, s.Tracks[i], ref allDone); + } + if (DMixer.IsFadeDone()) + { + allDone = true; + } + } + break; + } + case "smdb": + { + while (!allDone && TempoStack >= 120) + { + TempoStack -= 120; + allDone = true; + for (int i = 0; i < s.Tracks.Length; i++) + { + TickTrack(s, s.Tracks[i], ref allDone); + } + if (DMixer.IsFadeDone()) + { + allDone = true; + } + } + break; + } } if (!allDone) { diff --git a/VG Music Studio - Core/NDS/DSE/SMD.cs b/VG Music Studio - Core/NDS/DSE/SMD.cs index b939939..f3cc670 100644 --- a/VG Music Studio - Core/NDS/DSE/SMD.cs +++ b/VG Music Studio - Core/NDS/DSE/SMD.cs @@ -80,7 +80,7 @@ public sealed class SongChunk : ISongChunk // Size 0x40 public ushort TicksPerQuarter { get; set; } public byte[] Unknown2 { get; set; } public byte NumTracks { get; set; } - public byte NumChannels { get; set; } + public byte Channel { get; set; } public byte[] Unknown3 { get; set; } public SongChunk(EndianBinaryReader r) @@ -97,7 +97,7 @@ public SongChunk(EndianBinaryReader r) NumTracks = r.ReadByte(); - NumChannels = r.ReadByte(); + Channel = r.ReadByte(); Unknown3 = new byte[40]; r.ReadBytes(Unknown3); diff --git a/VG Music Studio - Core/NDS/DSE/SWD.cs b/VG Music Studio - Core/NDS/DSE/SWD.cs index 4a495fc..c428e58 100644 --- a/VG Music Studio - Core/NDS/DSE/SWD.cs +++ b/VG Music Studio - Core/NDS/DSE/SWD.cs @@ -899,14 +899,14 @@ private SampleBlock[] ReadSamples(EndianBinaryReader r, int numWAVISlots, SWD sw { WavInfo = wavInfo, // This is the only variable we can use for this initializer declarator, since the samples are DSP-ADPCM compressed }; - r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; // This sets the EndianBinaryReader stream position offset to the encoded DSP-ADPCM data + r.Stream.Position = pcmdDataOffset + wavInfo.SampleOffset; // This sets the EndianBinaryReader stream position offset to the DSP-ADPCM header - samples[i].DSPADPCM = new DSPADPCM(r, 1, false); // Reads the entire DSP-ADPCM header and encoded data + samples[i].DSPADPCM = new DSPADPCM(r, null); // Reads the entire DSP-ADPCM header and encoded data, also the SWD spec doesn't define number of channels samples[i].DSPADPCM.Decode(); // Decodes all bytes into PCM16 data #if DEBUG // This is for dumping both the encoded and decoded samples, for ensuring that the decoder works correctly new FileInfo("./ExtractedSamples/" + FileName + "/dsp/").Directory!.Create(); - File.WriteAllBytes("./ExtractedSamples/" + FileName + "/dsp/" + "sample" + i.ToString() + ".dsp", [.. samples[i].DSPADPCM.Info.ToBytes(), .. samples[i].DSPADPCM.Data]); + File.WriteAllBytes("./ExtractedSamples/" + FileName + "/dsp/" + "sample" + i.ToString() + ".dsp", [.. samples[i].DSPADPCM.Info[0].ToBytes(), .. samples[i].DSPADPCM.Data]); new FileInfo("./ExtractedSamples/" + FileName + "/wav/").Directory!.Create(); File.WriteAllBytes("./ExtractedSamples/" + FileName + "/wav/" + "sample" + i.ToString() + ".wav", samples[i].DSPADPCM.ConvertToWav()); #endif From 54e0b2229a10288509da79d91659d41132806a6d Mon Sep 17 00:00:00 2001 From: PlatinumLucario Date: Thu, 28 Dec 2023 04:08:52 +1100 Subject: [PATCH 8/9] Accurate BaseTimer pitch for Wii DSE --- VG Music Studio - Core/NDS/DSE/DSEChannel.cs | 13 ++-- VG Music Studio - Core/NDS/DSE/DSEConfig.cs | 34 +++++----- .../NDS/DSE/DSELoadedSong.cs | 43 +++++++------ .../NDS/DSE/DSELoadedSong_Events.cs | 62 ++++++++++++++----- .../NDS/DSE/DSELoadedSong_Runtime.cs | 2 +- VG Music Studio - Core/NDS/DSE/DSEPlayer.cs | 58 ++++++++--------- VG Music Studio - Core/SongState.cs | 2 +- 7 files changed, 122 insertions(+), 92 deletions(-) diff --git a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs index a3a319a..cbf92ba 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs @@ -19,6 +19,7 @@ internal sealed class DSEChannel public ushort Timer; public uint NoteLength; public byte Volume; + public static readonly float Root12Of2 = MathF.Pow(2, 1f / 12); private int _pos; private short _prevLeft; @@ -37,11 +38,11 @@ internal sealed class DSEChannel private byte _decay2; private byte _release; - // PCM8, PCM16, ADPCM, DSP-ADPCM + // PCM8, PCM16, IMA-ADPCM, DSP-ADPCM private SWD.SampleBlock? _sample; // PCM8, PCM16 private int _dataOffset; - // ADPCM + // IMA-ADPCM private IMAADPCM _adpcmDecoder; private short _adpcmLoopLastSample; private short _adpcmLoopStepIndex; @@ -57,7 +58,7 @@ public DSEChannel(byte i) Index = i; } - public bool StartPCM(SWD localswd, SWD masterswd, byte voice, int key, uint noteLength) + public bool StartPCM(SWD localswd, SWD masterswd, ushort ticksPerQuarter, byte voice, int key, int note, uint noteLength) { if (localswd == null) { SWDType = masterswd.Type; } else { SWDType = localswd.Type; } @@ -91,12 +92,12 @@ public bool StartPCM(SWD localswd, SWD masterswd, byte voice, int key, uint note RootKey = split.SampleRootKey; if (_sample != null) { - switch (SWDType) // Configures the base timer based on the specific console's CPU and sample rate + switch (SWDType) // Configures the base timer based on the specific console's sound framework and sample rate { case "wds ": throw new NotImplementedException("The base timer for the WDS type is not yet implemented."); // PlayStation case "swdm": throw new NotImplementedException("The base timer for the SWDM type is not yet implemented."); // PlayStation 2 - case "swdl": BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo!.SampleRate); break; // Nintendo DS - case "swdb": BaseTimer = (ushort)(_sample.WavInfo!.SampleRate / 116); break; // Wii // The best I can get is setting the BaseTimer to 380 + case "swdl": BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo!.SampleRate); break; // Nintendo DS // Time Base algorithm is the ARM7 CPU clock rate divided by SampleRate + case "swdb": BaseTimer = (ushort)(ticksPerQuarter * ticksPerQuarter * 7.1 / (_sample.WavInfo!.SampleRate >> 8)); break; // Wii // Time Base algorithm is TicksPerQuarterNote multiplied by TicksPerQuarterNote Length, multiplied by 7.1, divided by a SampleRate that's made smaller with Rsh by 8 } if (_sample.WavInfo!.SampleFormat == SampleFormat.ADPCM) { diff --git a/VG Music Studio - Core/NDS/DSE/DSEConfig.cs b/VG Music Studio - Core/NDS/DSE/DSEConfig.cs index 77da198..eb36d83 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEConfig.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEConfig.cs @@ -9,7 +9,7 @@ public sealed class DSEConfig : Config { public readonly string SMDPath; public readonly string[] SMDFiles; - internal SMD.Header Header; + internal SMD.Header? Header; internal DSEConfig(string smdPath) { @@ -24,24 +24,22 @@ internal DSEConfig(string smdPath) var songs = new List(SMDFiles.Length); for (int i = 0; i < SMDFiles.Length; i++) { - using (FileStream stream = File.OpenRead(SMDFiles[i])) + using FileStream stream = File.OpenRead(SMDFiles[i]); + // This will read the SMD header to determine if the majority of the file is little endian or big endian + var r = new EndianBinaryReader(stream, ascii: true); + Header = new SMD.Header(r); + if (Header.Type == "smdl") { - // This will read the SMD header to determine if the majority of the file is little endian or big endian - var r = new EndianBinaryReader(stream, ascii: true); - Header = new SMD.Header(r); - if(Header.Type == "smdl") - { - char[] chars = Header.Label.ToCharArray(); - EndianBinaryPrimitives.TrimNullTerminators(ref chars); - songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(SMDFiles[i])} - {new string(chars)}")); - } - else if(Header.Type == "smdb") - { - r.Endianness = Endianness.BigEndian; - char[] chars = Header.Label.ToCharArray(); - EndianBinaryPrimitives.TrimNullTerminators(ref chars); - songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(SMDFiles[i])} - {new string(chars)}")); - } + char[] chars = Header.Label.ToCharArray(); + EndianBinaryPrimitives.TrimNullTerminators(ref chars); + songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(SMDFiles[i])} - {new string(chars)}")); + } + else if (Header.Type == "smdb") + { + r.Endianness = Endianness.BigEndian; + char[] chars = Header.Label.ToCharArray(); + EndianBinaryPrimitives.TrimNullTerminators(ref chars); + songs.Add(new Song(i, $"{Path.GetFileNameWithoutExtension(SMDFiles[i])} - {new string(chars)}")); } } Playlists.Add(new Playlist(Strings.PlaylistMusic, songs)); diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs index 356f955..63b6a71 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.IO; -using static Kermalis.VGMusicStudio.Core.NDS.DSE.SMD; namespace Kermalis.VGMusicStudio.Core.NDS.DSE; @@ -16,6 +15,8 @@ internal sealed partial class DSELoadedSong : ILoadedSong private readonly DSEPlayer _player; private readonly SWD? LocalSWD; private readonly byte[] SMDFile; + public SMD.SongChunk SongChunk; + public SMD.Header Header; public readonly DSETrack[] Tracks; public DSELoadedSong(DSEPlayer player, string bgm) @@ -30,28 +31,26 @@ public DSELoadedSong(DSEPlayer player, string bgm) } SMDFile = File.ReadAllBytes(bgm); - using (var stream = new MemoryStream(SMDFile)) + using var stream = new MemoryStream(SMDFile); + var r = new EndianBinaryReader(stream, ascii: true); + Header = new SMD.Header(r); + if (Header.Version != 0x415) { throw new DSEInvalidHeaderVersionException(Header.Version); } + SongChunk = new SMD.SongChunk(r); + + Tracks = new DSETrack[SongChunk.NumTracks]; + Events = new List[SongChunk.NumTracks]; + for (byte trackIndex = 0; trackIndex < SongChunk.NumTracks; trackIndex++) { - var r = new EndianBinaryReader(stream, ascii: true); - Header header = new Header(r); - if (header.Version != 0x415) { throw new DSEInvalidHeaderVersionException(header.Version); } - SongChunk songChunk = new SongChunk(r); - - Tracks = new DSETrack[songChunk.NumTracks]; - Events = new List[songChunk.NumTracks]; - for (byte trackIndex = 0; trackIndex < songChunk.NumTracks; trackIndex++) - { - long chunkStart = r.Stream.Position; - r.Stream.Position += 0x14; // Skip header - Tracks[trackIndex] = new DSETrack(trackIndex, (int)r.Stream.Position); - - AddTrackEvents(trackIndex, r); - - r.Stream.Position = chunkStart + 0xC; - uint chunkLength = r.ReadUInt32(); - r.Stream.Position += chunkLength; - r.Stream.Align(4); - } + long chunkStart = r.Stream.Position; + r.Stream.Position += 0x14; // Skip header + Tracks[trackIndex] = new DSETrack(trackIndex, (int)r.Stream.Position); + + AddTrackEvents(trackIndex, r); + + r.Stream.Position = chunkStart + 0xC; + uint chunkLength = r.ReadUInt32(); + r.Stream.Position += chunkLength; + r.Stream.Align(4); } } } diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs index 3df7d80..1e61984 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Events.cs @@ -1130,26 +1130,58 @@ internal void SetCurTick(long ticks) goto finish; } - while (_player.TempoStack >= 240) + switch (Header.Type) { - _player.TempoStack -= 240; - for (int trackIndex = 0; trackIndex < Tracks.Length; trackIndex++) - { - DSETrack track = Tracks[trackIndex]; - if (!track.Stopped) + case "smdl": { - track.Tick(); - while (track.Rest == 0 && !track.Stopped) + while (_player.TempoStack >= 240) { - ExecuteNext(track); + _player.TempoStack -= 240; + for (int trackIndex = 0; trackIndex < Tracks.Length; trackIndex++) + { + DSETrack track = Tracks[trackIndex]; + if (!track.Stopped) + { + track.Tick(); + while (track.Rest == 0 && !track.Stopped) + { + ExecuteNext(track); + } + } + } + _player.ElapsedTicks++; + if (_player.ElapsedTicks == ticks) + { + goto finish; + } } + break; + } + case "smdb": + { + while (_player.TempoStack >= 120) + { + _player.TempoStack -= 120; + for (int trackIndex = 0; trackIndex < Tracks.Length; trackIndex++) + { + DSETrack track = Tracks[trackIndex]; + if (!track.Stopped) + { + track.Tick(); + while (track.Rest == 0 && !track.Stopped) + { + ExecuteNext(track); + } + } + } + _player.ElapsedTicks++; + if (_player.ElapsedTicks == ticks) + { + goto finish; + } + } + break; } - } - _player.ElapsedTicks++; - if (_player.ElapsedTicks == ticks) - { - goto finish; - } } _player.TempoStack += _player.Tempo; } diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs index ac12c27..f902fc6 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs @@ -45,7 +45,7 @@ public void ExecuteNext(DSETrack track) channel.Stop(); track.Octave = (byte)(track.Octave + oct); - if (channel.StartPCM(LocalSWD, _player.MainSWD, track.Voice, n + (12 * track.Octave), duration)) + if (channel.StartPCM(LocalSWD!, _player.MainSWD, SongChunk.TicksPerQuarter, track.Voice, n + (12 * track.Octave), n, duration)) { channel.NoteVelocity = cmd; channel.Owner = track; diff --git a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs index 5e1ed7c..7aa3489 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs @@ -75,42 +75,42 @@ protected override bool Tick(bool playing, bool recording) DSELoadedSong s = _loadedSong!; bool allDone = false; - switch (_config.Header.Type) + switch (_config.Header!.Type) { case "smdl": { - while (!allDone && TempoStack >= 240) - { - TempoStack -= 240; - allDone = true; - for (int i = 0; i < s.Tracks.Length; i++) - { - TickTrack(s, s.Tracks[i], ref allDone); - } - if (DMixer.IsFadeDone()) - { - allDone = true; - } - } + while (!allDone && TempoStack >= 240) + { + TempoStack -= 240; + allDone = true; + for (int i = 0; i < s.Tracks.Length; i++) + { + TickTrack(s, s.Tracks[i], ref allDone); + } + if (DMixer.IsFadeDone()) + { + allDone = true; + } + } break; - } + } case "smdb": { - while (!allDone && TempoStack >= 120) - { - TempoStack -= 120; - allDone = true; - for (int i = 0; i < s.Tracks.Length; i++) - { - TickTrack(s, s.Tracks[i], ref allDone); - } - if (DMixer.IsFadeDone()) - { - allDone = true; - } - } + while (!allDone && TempoStack >= 120) // Wii tempo is 120 by default + { + TempoStack -= 120; + allDone = true; + for (int i = 0; i < s.Tracks.Length; i++) + { + TickTrack(s, s.Tracks[i], ref allDone); + } + if (DMixer.IsFadeDone()) + { + allDone = true; + } + } break; - } + } } if (!allDone) { diff --git a/VG Music Studio - Core/SongState.cs b/VG Music Studio - Core/SongState.cs index 8535e7f..5b172f3 100644 --- a/VG Music Studio - Core/SongState.cs +++ b/VG Music Studio - Core/SongState.cs @@ -48,7 +48,7 @@ public void Reset() } public const int MAX_KEYS = 32 + 1; // DSE is currently set to use 32 channels - public const int MAX_TRACKS = 256; // PMD2 has a few songs with 18 tracks, PMD WiiWare has some that are 21+ tracks + public const int MAX_TRACKS = 255 + 1; // PMD2 has a few songs with 18 tracks, where as PMD WiiWare has some that are up to 27 tracks, but made it 255 to future proof it public ushort Tempo; public readonly Track[] Tracks; From 4ec3e3d3ca5af7f91d82e0f0c4b325ea5ca20ee2 Mon Sep 17 00:00:00 2001 From: PlatinumLucario Date: Thu, 28 Dec 2023 16:21:20 +1100 Subject: [PATCH 9/9] Small adjustments to Wii BaseTimer Made it more accurate thanks to CodaHighland's findings. And I also added multi-select for selecting a main SWD, in the event that SWD file names don't match the SMD files. --- VG Music Studio - Core/NDS/DSE/DSEChannel.cs | 8 ++++++-- VG Music Studio - Core/NDS/DSE/DSEEngine.cs | 4 ++-- .../NDS/DSE/DSELoadedSong.cs | 13 +++++++++--- .../NDS/DSE/DSELoadedSong_Runtime.cs | 2 +- VG Music Studio - Core/NDS/DSE/DSEPlayer.cs | 9 +++++++-- VG Music Studio - WinForms/MainForm.cs | 4 ++-- .../Util/WinFormsUtils.cs | 20 ++++++++++++++++++- 7 files changed, 47 insertions(+), 13 deletions(-) diff --git a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs index cbf92ba..d5483d2 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEChannel.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEChannel.cs @@ -58,7 +58,7 @@ public DSEChannel(byte i) Index = i; } - public bool StartPCM(SWD localswd, SWD masterswd, ushort ticksPerQuarter, byte voice, int key, int note, uint noteLength) + public bool StartPCM(SWD localswd, SWD masterswd, byte voice, int key, uint noteLength) { if (localswd == null) { SWDType = masterswd.Type; } else { SWDType = localswd.Type; } @@ -71,6 +71,10 @@ public bool StartPCM(SWD localswd, SWD masterswd, ushort ticksPerQuarter, byte v // accompaning the SMDs with the same names are loaded in. if (masterswd.Programs != null) { programInfo = masterswd.Programs!.ProgramInfos![voice]; } } + else if (voice > localswd.Programs!.ProgramInfos!.Length) + { + programInfo = masterswd.Programs!.ProgramInfos![voice]; + } else { programInfo = localswd.Programs!.ProgramInfos![voice]; } if (programInfo is null) @@ -97,7 +101,7 @@ public bool StartPCM(SWD localswd, SWD masterswd, ushort ticksPerQuarter, byte v case "wds ": throw new NotImplementedException("The base timer for the WDS type is not yet implemented."); // PlayStation case "swdm": throw new NotImplementedException("The base timer for the SWDM type is not yet implemented."); // PlayStation 2 case "swdl": BaseTimer = (ushort)(NDSUtils.ARM7_CLOCK / _sample.WavInfo!.SampleRate); break; // Nintendo DS // Time Base algorithm is the ARM7 CPU clock rate divided by SampleRate - case "swdb": BaseTimer = (ushort)(ticksPerQuarter * ticksPerQuarter * 7.1 / (_sample.WavInfo!.SampleRate >> 8)); break; // Wii // Time Base algorithm is TicksPerQuarterNote multiplied by TicksPerQuarterNote Length, multiplied by 7.1, divided by a SampleRate that's made smaller with Rsh by 8 + case "swdb": BaseTimer = (ushort)(256 * 65536 / _sample.WavInfo!.SampleRate); break; // Wii // The AX Time Base algorithm is 256 multiplied by 65536, divided by SampleRate } if (_sample.WavInfo!.SampleFormat == SampleFormat.ADPCM) { diff --git a/VG Music Studio - Core/NDS/DSE/DSEEngine.cs b/VG Music Studio - Core/NDS/DSE/DSEEngine.cs index 0b064ba..956be54 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEEngine.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEEngine.cs @@ -10,11 +10,11 @@ public sealed class DSEEngine : Engine public override DSEMixer Mixer { get; } public override DSEPlayer Player { get; } - public DSEEngine(string mainSWDFile, string bgmPath) + public DSEEngine(string[] SWDFiles, string bgmPath) { Config = new DSEConfig(bgmPath); Mixer = new DSEMixer(); - Player = new DSEPlayer(mainSWDFile, Config, Mixer); + Player = new DSEPlayer(SWDFiles, Config, Mixer); DSEInstance = this; Instance = this; diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs index 63b6a71..3ef7158 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong.cs @@ -24,10 +24,17 @@ public DSELoadedSong(DSEPlayer player, string bgm) _player = player; //StringComparison comparison = StringComparison.CurrentCultureIgnoreCase; - // Check if a local SWD is accompaning a SMD - if (new FileInfo(Path.ChangeExtension(bgm, "swd")).Exists) + if (_player.LocalSWD != null) { - LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); // If it exists, this will be loaded as the local SWD + LocalSWD = _player.LocalSWD; + } + else + { + // Check if a local SWD is accompaning a SMD + if (new FileInfo(Path.ChangeExtension(bgm, "swd")).Exists) + { + LocalSWD = new SWD(Path.ChangeExtension(bgm, "swd")); // If it exists, this will be loaded as the local SWD + } } SMDFile = File.ReadAllBytes(bgm); diff --git a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs index f902fc6..7a9b3bc 100644 --- a/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs +++ b/VG Music Studio - Core/NDS/DSE/DSELoadedSong_Runtime.cs @@ -45,7 +45,7 @@ public void ExecuteNext(DSETrack track) channel.Stop(); track.Octave = (byte)(track.Octave + oct); - if (channel.StartPCM(LocalSWD!, _player.MainSWD, SongChunk.TicksPerQuarter, track.Voice, n + (12 * track.Octave), n, duration)) + if (channel.StartPCM(LocalSWD!, _player.MainSWD, track.Voice, n + (12 * track.Octave), duration)) { channel.NoteVelocity = cmd; channel.Owner = track; diff --git a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs index 7aa3489..242b723 100644 --- a/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs +++ b/VG Music Studio - Core/NDS/DSE/DSEPlayer.cs @@ -10,6 +10,7 @@ public sealed class DSEPlayer : Player private readonly DSEConfig _config; internal readonly DSEMixer DMixer; internal readonly SWD MainSWD; + internal readonly SWD? LocalSWD; private DSELoadedSong? _loadedSong; internal byte Tempo; @@ -19,13 +20,17 @@ public sealed class DSEPlayer : Player public override ILoadedSong? LoadedSong => _loadedSong; protected override Mixer Mixer => DMixer; - public DSEPlayer(string mainSWDFile, DSEConfig config, DSEMixer mixer) + public DSEPlayer(string[] SWDFiles, DSEConfig config, DSEMixer mixer) : base(192) { DMixer = mixer; _config = config; - MainSWD = new SWD(mainSWDFile); + MainSWD = new SWD(SWDFiles[0]); + if (SWDFiles.Length > 1 ) + { + LocalSWD = new SWD(SWDFiles[1]); + } } public override void LoadSong(int index) diff --git a/VG Music Studio - WinForms/MainForm.cs b/VG Music Studio - WinForms/MainForm.cs index 9485404..efc1319 100644 --- a/VG Music Studio - WinForms/MainForm.cs +++ b/VG Music Studio - WinForms/MainForm.cs @@ -264,7 +264,7 @@ private void EndCurrentPlaylist(object? sender, EventArgs e) private void OpenDSE(object? sender, EventArgs e) { - var f = WinFormsUtils.CreateLoadDialog(".swd", Strings.MenuOpenSWD, Strings.FilterOpenSWD + " (*.swd)|*.swd"); + var f = WinFormsUtils.CreateLoadMultipleDialog(".swd", Strings.MenuOpenSWD, Strings.FilterOpenSWD + " (*.swd)|*.swd"); if (f is null) { return; @@ -282,7 +282,7 @@ private void OpenDSE(object? sender, EventArgs e) DisposeEngine(); try { - _ = new DSEEngine(f.ToString(), d.SelectedPath); + _ = new DSEEngine([.. f], d.SelectedPath); } catch (Exception ex) { diff --git a/VG Music Studio - WinForms/Util/WinFormsUtils.cs b/VG Music Studio - WinForms/Util/WinFormsUtils.cs index 33a0766..895accd 100644 --- a/VG Music Studio - WinForms/Util/WinFormsUtils.cs +++ b/VG Music Studio - WinForms/Util/WinFormsUtils.cs @@ -55,7 +55,25 @@ public static float Lerp(float value, float a1, float a2, float b1, float b2) } return null; } - public static string? CreateSaveDialog(string fileName, string extension, string title, string filter) + public static string[]? CreateLoadMultipleDialog(string extension, string title, string filter) + { + var d = new OpenFileDialog + { + DefaultExt = extension, + ValidateNames = true, + Multiselect = true, + CheckFileExists = true, + CheckPathExists = true, + Title = title, + Filter = $"{filter}|All files (*.*)|*.*", + }; + if (d.ShowDialog() == DialogResult.OK) + { + return d.FileNames; + } + return null; + } + public static string? CreateSaveDialog(string fileName, string extension, string title, string filter) { var d = new SaveFileDialog {