From a1239b00e1bc27839c60a0c1ed5bf293e4b3e7f5 Mon Sep 17 00:00:00 2001 From: jkuehner Date: Tue, 24 Jun 2014 11:40:08 +0200 Subject: [PATCH] Initial import --- .gitignore | 108 ++ .../Demos/AlignTableCellMiddleCenterHS.png | Bin 0 -> 432 bytes Odyssey/Demos/App.xaml | 8 + Odyssey/Demos/App.xaml.cs | 16 + Odyssey/Demos/BreadcrumbWithDataSource.xaml | 55 + .../Demos/BreadcrumbWithDataSource.xaml.cs | 28 + Odyssey/Demos/Demos.csproj | 188 +++ Odyssey/Demos/FolderItem.cs | 24 + Odyssey/Demos/Main.xaml | 21 + Odyssey/Demos/Main.xaml.cs | 51 + Odyssey/Demos/MainDemo.xaml | 92 ++ Odyssey/Demos/MainDemo.xaml.cs | 208 +++ Odyssey/Demos/Outlook.xaml | 81 + Odyssey/Demos/Outlook.xaml.cs | 66 + Odyssey/Demos/Properties/AssemblyInfo.cs | 55 + .../Demos/Properties/Resources.Designer.cs | 71 + Odyssey/Demos/Properties/Resources.resx | 117 ++ Odyssey/Demos/Properties/Settings.Designer.cs | 30 + Odyssey/Demos/Properties/Settings.settings | 7 + Odyssey/Demos/Ribbon/RibbonDemo.xaml | 343 ++++ Odyssey/Demos/Ribbon/RibbonDemo.xaml.cs | 90 ++ Odyssey/Demos/Ribbon/ThumbnailConverter.cs | 30 + Odyssey/Demos/Web.png | Bin 0 -> 3463 bytes Odyssey/Demos/img/cut16.png | Bin 0 -> 3597 bytes Odyssey/Demos/img/cut32.png | Bin 0 -> 4729 bytes Odyssey/Demos/img/delete16.png | Bin 0 -> 3456 bytes Odyssey/Demos/img/delete32.png | Bin 0 -> 4597 bytes Odyssey/Demos/img/favorites16.png | Bin 0 -> 3438 bytes Odyssey/Demos/img/favorites32.png | Bin 0 -> 4348 bytes Odyssey/Demos/img/folder16.png | Bin 0 -> 3157 bytes Odyssey/Demos/img/folder32.png | Bin 0 -> 3947 bytes Odyssey/Demos/img/history16.png | Bin 0 -> 3488 bytes Odyssey/Demos/img/history32.png | Bin 0 -> 5352 bytes Odyssey/Demos/img/home16.png | Bin 0 -> 3319 bytes Odyssey/Demos/img/home32.png | Bin 0 -> 4766 bytes Odyssey/Demos/img/mail16.png | Bin 0 -> 3582 bytes Odyssey/Demos/img/mail32.png | Bin 0 -> 5113 bytes Odyssey/Demos/img/paste16.png | Bin 0 -> 3277 bytes Odyssey/Demos/img/paste32.png | Bin 0 -> 3973 bytes Odyssey/Demos/img/props16.png | Bin 0 -> 3411 bytes Odyssey/Demos/img/props32.png | Bin 0 -> 4410 bytes Odyssey/Demos/img/save16.png | Bin 0 -> 3144 bytes Odyssey/Demos/img/save32.png | Bin 0 -> 3770 bytes Odyssey/Demos/img/search16.png | Bin 0 -> 3514 bytes Odyssey/Demos/img/search32.png | Bin 0 -> 4937 bytes Odyssey/Demos/img/undo16.png | Bin 0 -> 3577 bytes Odyssey/Demos/img/undo32.png | Bin 0 -> 4676 bytes Odyssey/Demos/openfolderHS.png | Bin 0 -> 743 bytes Odyssey/Odyssey.sln | 72 + .../BreadcrumbBar/ApplyPropertiesEventArgs.cs | 56 + .../Odyssey/BreadcrumbBar/BreadcrumbBar.cs | 1399 ++++++++++++++++ .../Odyssey/BreadcrumbBar/BreadcrumbButton.cs | 463 ++++++ .../Odyssey/BreadcrumbBar/BreadcrumbItem.cs | 724 +++++++++ .../BreadcrumbBar/BreadcrumbItemEventArgs.cs | 33 + Odyssey/Odyssey/BreadcrumbBar/ImageButton.cs | 60 + .../BreadcrumbBar/PathConversionEventArgs.cs | 80 + Odyssey/Odyssey/ChangeLog.txt | 47 + Odyssey/Odyssey/Common/AeroChrome.cs | 71 + Odyssey/Odyssey/Common/AnimationDecorator.cs | 327 ++++ .../Common/EmptyStringVisibilityConverter.cs | 27 + Odyssey/Odyssey/Common/IKeyTipControl.cs | 12 + Odyssey/Odyssey/Common/PopupHelper.cs | 27 + Odyssey/Odyssey/Common/SkinId.cs | 21 + Odyssey/Odyssey/Common/SkinManager.cs | 129 ++ Odyssey/Odyssey/Common/Skins.cs | 16 + Odyssey/Odyssey/Controls/ClickableTreeView.cs | 22 + .../Odyssey/Controls/ClickableTreeViewItem.cs | 240 +++ Odyssey/Odyssey/Controls/DropDownButton.cs | 57 + Odyssey/Odyssey/Controls/OdcButton.cs | 40 + Odyssey/Odyssey/Controls/OdcTextBox.cs | 78 + Odyssey/Odyssey/Effects/GrayscaleEffect.cs | 55 + Odyssey/Odyssey/Effects/GrayscaleEffect.fx | 16 + Odyssey/Odyssey/Effects/GrayscaleEffect.ps | Bin 0 -> 400 bytes Odyssey/Odyssey/ExplorerBar/ExplorerBar.cs | 35 + Odyssey/Odyssey/ExplorerBar/OdcExpander.cs | 348 ++++ .../Odyssey/ExplorerBar/OdcExpanderHeader.cs | 133 ++ Odyssey/Odyssey/Odyssey.csproj | 725 +++++++++ Odyssey/Odyssey/OutlookBar/ExpandPosition.cs | 13 + Odyssey/Odyssey/OutlookBar/OutlookBar.cs | 973 +++++++++++ Odyssey/Odyssey/OutlookBar/OutlookSection.cs | 128 ++ .../OverflowMenuCreatedEventArgs.cs | 19 + Odyssey/Odyssey/Properties/AssemblyInfo.cs | 58 + .../Odyssey/Properties/Resources.Designer.cs | 63 + Odyssey/Odyssey/Properties/Resources.resx | 122 ++ .../Odyssey/Properties/Settings.Designer.cs | 26 + Odyssey/Odyssey/Properties/Settings.settings | 5 + .../Odyssey/Ribbon/Classes/BoolConverter.cs | 32 + Odyssey/Odyssey/Ribbon/Classes/IRibbonSize.cs | 16 + .../Ribbon/Classes/ImageRenderOptions.cs | 94 ++ .../Odyssey/Ribbon/Classes/NativeMethods.cs | 289 ++++ .../Odyssey/Ribbon/Classes/QAItemPlacement.cs | 19 + Odyssey/Odyssey/Ribbon/Classes/QAPlacement.cs | 21 + .../Ribbon/Classes/RibbonBarAlignment.cs | 19 + .../Ribbon/Classes/RibbonGalleryColumns.cs | 19 + ...RibbonGalleryColumnsCollectionConverter.cs | 41 + .../RibbonGroupReductionOrderConverter.cs | 41 + .../Odyssey/Ribbon/Classes/RibbonOption.cs | 52 + .../RibbonReductionCollectionConverter.cs | 54 + Odyssey/Odyssey/Ribbon/Classes/RibbonSize.cs | 37 + .../Ribbon/Classes/RibbonSizeCollection.cs | 20 + .../Ribbon/Classes/RibbonWindowCornerMode.cs | 19 + .../Ribbon/Classes/RoundedCornerConverter.cs | 55 + .../Classes/RoundedCornerResizeConverter.cs | 41 + .../Ribbon/Classes/TwoLineConverter.cs | 82 + .../Ribbon/Classes/TwoLineTextConverter.cs | 81 + .../Ribbon/Controls/InternalGroupPanel.cs | 185 +++ Odyssey/Odyssey/Ribbon/Controls/KeyTip.cs | 406 +++++ .../Ribbon/Controls/RibbonApplicationMenu.cs | 387 +++++ .../Controls/RibbonApplicationMenuItem.cs | 63 + .../Ribbon/Controls/RibbonBar.Commands.cs | 78 + .../Ribbon/Controls/RibbonBar.Handlers.cs | 22 + Odyssey/Odyssey/Ribbon/Controls/RibbonBar.cs | 1436 +++++++++++++++++ .../Odyssey/Ribbon/Controls/RibbonButton.cs | 123 ++ .../Ribbon/Controls/RibbonButtonGroup.cs | 77 + .../Ribbon/Controls/RibbonButtonStyle.cs | 30 + .../Odyssey/Ribbon/Controls/RibbonChrome.cs | 192 +++ .../Odyssey/Ribbon/Controls/RibbonComboBox.cs | 192 +++ .../Ribbon/Controls/RibbonComboBoxItem.cs | 36 + .../Ribbon/Controls/RibbonContextualTabSet.cs | 168 ++ .../Ribbon/Controls/RibbonDropDownButton.cs | 887 ++++++++++ .../Ribbon/Controls/RibbonFlowGroup.cs | 33 + .../Ribbon/Controls/RibbonGallery.Commands.cs | 127 ++ .../Odyssey/Ribbon/Controls/RibbonGallery.cs | 708 ++++++++ .../Ribbon/Controls/RibbonGroup.Commands.cs | 38 + .../Ribbon/Controls/RibbonGroup.Handlers.cs | 44 + .../Odyssey/Ribbon/Controls/RibbonGroup.cs | 626 +++++++ .../Odyssey/Ribbon/Controls/RibbonMenuItem.cs | 118 ++ .../Ribbon/Controls/RibbonQAToolBar.cs | 364 +++++ .../Ribbon/Controls/RibbonQAToolbarPanel.cs | 30 + .../Ribbon/Controls/RibbonSeparator.cs | 24 + .../Ribbon/Controls/RibbonSplitButton.cs | 120 ++ .../Odyssey/Ribbon/Controls/RibbonTabItem.cs | 276 ++++ .../Ribbon/Controls/RibbonTabItemPanel.cs | 138 ++ .../Ribbon/Controls/RibbonTabScroller.cs | 215 +++ .../Odyssey/Ribbon/Controls/RibbonTextBox.cs | 105 ++ .../Ribbon/Controls/RibbonThumbnail.cs | 119 ++ .../Ribbon/Controls/RibbonToggleButton.cs | 90 ++ .../Odyssey/Ribbon/Controls/RibbonToolTip.cs | 159 ++ .../Ribbon/Controls/RibbonWindow.Commands.cs | 48 + .../Odyssey/Ribbon/Controls/RibbonWindow.cs | 462 ++++++ .../Ribbon/Controls/RibbonWrapPanel.cs | 147 ++ .../EventArgs/SelectedTabIndexChangedEvent.cs | 18 + .../Ribbon/Interfaces/IRibbonButton.cs | 26 + .../Ribbon/Interfaces/IRibbonControl.cs | 16 + .../Ribbon/Interfaces/IRibbonGallery.cs | 21 + .../Ribbon/Interfaces/IRibbonLargeControl.cs | 19 + .../Ribbon/Interfaces/IRibbonStretch.cs | 19 + Odyssey/Odyssey/Skins/BlackSkin.xaml | 8 + Odyssey/Odyssey/Skins/BlueSkin.xaml | 9 + .../Skins/OutlookBar/OutlookBlackSkin.xaml | 75 + .../Skins/OutlookBar/OutlookBlueSkin.xaml | 73 + .../Skins/OutlookBar/OutlookSilverSkin.xaml | 73 + .../Odyssey/Skins/OutlookBar/Win7Skin.xaml | 68 + .../Odyssey/Skins/Ribbon/OfficeBlackSkin.xaml | 203 +++ .../Odyssey/Skins/Ribbon/OfficeBlueSkin.xaml | 161 ++ .../Skins/Ribbon/OfficeSilverSkin.xaml | 178 ++ Odyssey/Odyssey/Skins/Ribbon/VistaSkin.xaml | 57 + Odyssey/Odyssey/Skins/Ribbon/Window7Skin.xaml | 343 ++++ Odyssey/Odyssey/Skins/SilverSkin.xaml | 8 + Odyssey/Odyssey/Skins/VistaSkin.xaml | 8 + Odyssey/Odyssey/Skins/Win7Skin.xaml | 8 + Odyssey/Odyssey/Themes/Aero.NormalColor.xaml | 6 + .../Themes/BreadcrumbBar/AeroChrome.xaml | 50 + .../BreadcrumbBar/BreadcrumbButton.xaml | 168 ++ .../Themes/BreadcrumbBar/BreadcrumbItem.xaml | 38 + .../Odyssey/Themes/BreadcrumbBar/Brushes.xaml | 27 + .../Themes/BreadcrumbBar/ButtonTemplates.xaml | 63 + .../Odyssey/Themes/BreadcrumbBar/Generic.xaml | 125 ++ .../Themes/BreadcrumbBar/OdcTextBox.xaml | 108 ++ .../Themes/BreadcrumbBar/ProgressBar.xaml | 107 ++ Odyssey/Odyssey/Themes/Classic.xaml | 6 + .../Themes/Controls/DropDownButton.xaml | 55 + .../Odyssey/Themes/Controls/OdcTextBox.xaml | 94 ++ .../Aero.NormalColor.ExpandHeader.xaml | 89 + .../Themes/Expander/Aero.NormalColor.xaml | 63 + .../Themes/Expander/Classic.ExpandHeader.xaml | 81 + Odyssey/Odyssey/Themes/Expander/Classic.xaml | 33 + .../Themes/Expander/Generic.ExpandHeader.xaml | 78 + .../Themes/Expander/Generic.Expander.xaml | 132 ++ Odyssey/Odyssey/Themes/Expander/Generic.xaml | 35 + .../Themes/Expander/Luna.Homestead.xaml | 47 + .../Themes/Expander/Luna.Metallic.xaml | 47 + .../Themes/Expander/Luna.NormalColor.xaml | 46 + Odyssey/Odyssey/Themes/Generic.xaml | 14 + Odyssey/Odyssey/Themes/Luna.Homestead.xaml | 6 + Odyssey/Odyssey/Themes/Luna.Metallic.xaml | 6 + Odyssey/Odyssey/Themes/Luna.NormalColor.xaml | 6 + .../Odyssey/Themes/OutlookBar/Generic.xaml | 9 + .../Odyssey/Themes/OutlookBar/OutlookBar.xaml | 364 +++++ .../Themes/OutlookBar/OutlookSection.xaml | 44 + .../Themes/OutlookBar/ToggleButton.xaml | 37 + .../Ribbon/DefaultRibbonButtonBrushes.xaml | 34 + .../Ribbon/DefaultWindowButtonStyles.xaml | 88 + Odyssey/Odyssey/Themes/Ribbon/Generic.xaml | 33 + .../Themes/Ribbon/HighlightedBackgrounds.xaml | 57 + .../Themes/Ribbon/InternalRibbonButton.xaml | 19 + .../Odyssey/Themes/Ribbon/QuickAccessKey.xaml | 24 + .../Themes/Ribbon/RibbonAppMenuItem.xaml | 146 ++ .../Ribbon/RibbonApplicationButton.xaml | 95 ++ .../Themes/Ribbon/RibbonApplicationMenu.xaml | 126 ++ Odyssey/Odyssey/Themes/Ribbon/RibbonBar.xaml | 185 +++ .../Odyssey/Themes/Ribbon/RibbonButton.xaml | 131 ++ .../Themes/Ribbon/RibbonButtonGroup.xaml | 86 + .../Odyssey/Themes/Ribbon/RibbonChrome.xaml | 116 ++ .../Odyssey/Themes/Ribbon/RibbonComboBox.xaml | 143 ++ .../Themes/Ribbon/RibbonComboBoxItem.xaml | 30 + .../Themes/Ribbon/RibbonContextualTabSet.xaml | 55 + .../Themes/Ribbon/RibbonDropDownButton.xaml | 253 +++ .../Themes/Ribbon/RibbonFlowGroup.xaml | 37 + .../Odyssey/Themes/Ribbon/RibbonGallery.xaml | 107 ++ .../Odyssey/Themes/Ribbon/RibbonGroup.xaml | 145 ++ .../Themes/Ribbon/RibbonGroupBrushes.xaml | 185 +++ .../Ribbon/RibbonGroupDropDownButton.xaml | 36 + .../Themes/Ribbon/RibbonHLChromeStyle.xaml | 14 + .../Odyssey/Themes/Ribbon/RibbonImages.xaml | 207 +++ .../Odyssey/Themes/Ribbon/RibbonMenuItem.xaml | 130 ++ .../Themes/Ribbon/RibbonQAToolBar.xaml | 232 +++ .../Themes/Ribbon/RibbonSeparator.xaml | 42 + .../Themes/Ribbon/RibbonSplitButton.xaml | 274 ++++ .../Odyssey/Themes/Ribbon/RibbonTabItem.xaml | 106 ++ .../Themes/Ribbon/RibbonTabScroller.xaml | 83 + .../Odyssey/Themes/Ribbon/RibbonTextBox.xaml | 103 ++ .../Themes/Ribbon/RibbonThumbnail.xaml | 30 + .../Themes/Ribbon/RibbonToggleButton.xaml | 121 ++ .../Odyssey/Themes/Ribbon/RibbonToolTip.xaml | 59 + .../Odyssey/Themes/Ribbon/RibbonWindow.xaml | 153 ++ Odyssey/Odyssey/Themes/TreeViewStyle.xaml | 190 +++ Odyssey/Odyssey/app.config | 5 + .../PasswordSafe.Data/Biz/BaseObject.cs | 148 ++ .../PasswordSafe.Data/Biz/BizContext.cs | 548 +++++++ .../PasswordSafe.Data/Biz/Category.cs | 379 +++++ .../PasswordSafe.Data/Biz/CustomNode.cs | 17 + PasswordSafe/PasswordSafe.Data/Biz/Field.cs | 431 +++++ .../PasswordSafe.Data/Biz/FieldType.cs | 23 + PasswordSafe/PasswordSafe.Data/Biz/Folder.cs | 287 ++++ .../PasswordSafe.Data/Biz/NodeBase.cs | 83 + .../PasswordSafe.Data/Biz/NotifyEventArgs.cs | 42 + .../PasswordSafe.Data/Biz/NotifyList.cs | 102 ++ .../PasswordSafe.Data/Biz/Password.cs | 304 ++++ .../PasswordSafe.Data/Biz/PasswordFolder.cs | 132 ++ .../PasswordSafe.Data/Biz/TemplateField.cs | 177 ++ PasswordSafe/PasswordSafe.Data/DAL/DAL.cs | 667 ++++++++ .../DAL/DBEntityNotUpdatedException.cs | 11 + .../PasswordSafe.Data.csproj | 157 ++ .../Properties/AssemblyInfo.cs | 36 + .../Properties/Resources.Designer.cs | 63 + .../Properties/Resources.resx | 117 ++ .../Properties/Settings.Designer.cs | 26 + .../Properties/Settings.settings | 7 + PasswordSafe/PasswordSafe/App.xaml | 5 + PasswordSafe/PasswordSafe/App.xaml.cs | 16 + .../PasswordSafe/Classes/UIContext.cs | 154 ++ PasswordSafe/PasswordSafe/Commands.cs | 793 +++++++++ .../PasswordSafe/Controls/EditLabel.cs | 179 ++ .../Converter/CopyFieldConverter.cs | 29 + .../PasswordSafe/Converter/CountConverter.cs | 26 + .../PasswordSafe/Converter/DateConverter.cs | 31 + .../DisplayTypeToVisibleConverter.cs | 31 + .../Converter/FavoriteImageConverter.cs | 41 + .../Converter/FieldTypeConverter.cs | 25 + .../Converter/FieldValidatonRule.cs | 43 + .../PasswordSafe/Converter/ImageConverter.cs | 42 + .../PasswordSafe/Converter/IntConverter.cs | 37 + .../IsModifiedFromUIPasswordConverter.cs | 32 + .../Converter/NullToBoolConverter.cs | 29 + .../Converter/NullToVisibleConverter.cs | 27 + .../PasswordSafe/Converter/TimeConverter.cs | 32 + .../Converter/VisibilityConverter.cs | 30 + .../BreadcrumbItemSelector.cs | 35 + .../CopyPasswordSelector.cs | 29 + .../DataTemplateSelectors/FieldSelector.cs | 47 + .../TemplateFieldSelector.cs | 44 + PasswordSafe/PasswordSafe/DataTemplates.xaml | 220 +++ .../PasswordSafe/Enums/DisplayMode.cs | 13 + .../PasswordSafe/Enums/DisplayType.cs | 25 + .../PasswordSafe/Export/XmlExporter.cs | 58 + PasswordSafe/PasswordSafe/Export/XmlField.cs | 21 + .../PasswordSafe/Export/XmlPassword.cs | 22 + PasswordSafe/PasswordSafe/FieldTemplates.xaml | 153 ++ PasswordSafe/PasswordSafe/Main.xaml | 552 +++++++ PasswordSafe/PasswordSafe/Main.xaml.cs | 939 +++++++++++ PasswordSafe/PasswordSafe/PasswordSafe.csproj | 432 +++++ .../PasswordSafe/Properties/AssemblyInfo.cs | 55 + .../Properties/Resources.Designer.cs | 63 + .../PasswordSafe/Properties/Resources.resx | 117 ++ .../Properties/Settings.Designer.cs | 180 +++ .../PasswordSafe/Properties/Settings.settings | 50 + PasswordSafe/PasswordSafe/Settings.cs | 28 + .../PasswordSafe/TemplateFieldTemplates.xaml | 117 ++ PasswordSafe/PasswordSafe/Themes/Generic.xaml | 35 + PasswordSafe/PasswordSafe/Tools/RibbonSrc.cs | 100 ++ .../PasswordSafe/Tools/TreeViewExtender.cs | 185 +++ .../UserControls/ChangePassword.xaml | 70 + .../UserControls/ChangePassword.xaml.cs | 119 ++ .../PasswordSafe/UserControls/LockScreen.xaml | 50 + .../UserControls/LockScreen.xaml.cs | 110 ++ .../UserControls/PasswordGrid.xaml | 149 ++ .../UserControls/PasswordGrid.xaml.cs | 325 ++++ .../UserControls/TemplateGrid.xaml | 49 + .../UserControls/TemplateGrid.xaml.cs | 126 ++ PasswordSafe/PasswordSafe/app.config | 53 + PasswordSafe/PasswordSafe/img/Accept_16.png | Bin 0 -> 795 bytes PasswordSafe/PasswordSafe/img/Accept_32.png | Bin 0 -> 1966 bytes PasswordSafe/PasswordSafe/img/Add_16.png | Bin 0 -> 572 bytes PasswordSafe/PasswordSafe/img/Add_32.png | Bin 0 -> 1189 bytes PasswordSafe/PasswordSafe/img/Black48.png | Bin 0 -> 4265 bytes PasswordSafe/PasswordSafe/img/Blue48.png | Bin 0 -> 4145 bytes PasswordSafe/PasswordSafe/img/Bottom_16.png | Bin 0 -> 565 bytes PasswordSafe/PasswordSafe/img/Bottom_32.png | Bin 0 -> 1119 bytes PasswordSafe/PasswordSafe/img/Comment_16.png | Bin 0 -> 731 bytes PasswordSafe/PasswordSafe/img/DateTime_16.png | Bin 0 -> 1424 bytes .../PasswordSafe/img/DefineName_16.png | Bin 0 -> 457 bytes PasswordSafe/PasswordSafe/img/DeleteTb_32.png | Bin 0 -> 1707 bytes PasswordSafe/PasswordSafe/img/Delete_32.png | Bin 0 -> 4080 bytes PasswordSafe/PasswordSafe/img/Down_16.png | Bin 0 -> 454 bytes PasswordSafe/PasswordSafe/img/Down_32.png | Bin 0 -> 966 bytes PasswordSafe/PasswordSafe/img/Export_32.png | Bin 0 -> 1809 bytes PasswordSafe/PasswordSafe/img/Glass_32.png | Bin 0 -> 2306 bytes PasswordSafe/PasswordSafe/img/InsertB_32.png | Bin 0 -> 2204 bytes PasswordSafe/PasswordSafe/img/Layout_16.png | Bin 0 -> 1409 bytes PasswordSafe/PasswordSafe/img/Layout_32.png | Bin 0 -> 1592 bytes PasswordSafe/PasswordSafe/img/Left_16.png | Bin 0 -> 436 bytes PasswordSafe/PasswordSafe/img/Left_32.png | Bin 0 -> 962 bytes PasswordSafe/PasswordSafe/img/Lock_16.png | Bin 0 -> 653 bytes PasswordSafe/PasswordSafe/img/Lock_32.png | Bin 0 -> 1498 bytes PasswordSafe/PasswordSafe/img/Max_16.png | Bin 0 -> 1285 bytes PasswordSafe/PasswordSafe/img/Min_16.png | Bin 0 -> 1284 bytes .../PasswordSafe/img/NewPassword_32.png | Bin 0 -> 3954 bytes PasswordSafe/PasswordSafe/img/NoFaves32.png | Bin 0 -> 4031 bytes PasswordSafe/PasswordSafe/img/Numeric_16.png | Bin 0 -> 1348 bytes PasswordSafe/PasswordSafe/img/Pushpin_16.png | Bin 0 -> 660 bytes .../PasswordSafe/img/QuickParts_16.png | Bin 0 -> 1325 bytes .../PasswordSafe/img/QuickParts_32.png | Bin 0 -> 1537 bytes PasswordSafe/PasswordSafe/img/Reject_16.png | Bin 0 -> 1404 bytes PasswordSafe/PasswordSafe/img/Reject_32.png | Bin 0 -> 2006 bytes PasswordSafe/PasswordSafe/img/Remove_16.png | Bin 0 -> 762 bytes PasswordSafe/PasswordSafe/img/Remove_32.png | Bin 0 -> 1764 bytes .../PasswordSafe/img/RightArrowHS_16.png | Bin 0 -> 481 bytes PasswordSafe/PasswordSafe/img/Right_16.png | Bin 0 -> 440 bytes PasswordSafe/PasswordSafe/img/Right_32.png | Bin 0 -> 990 bytes PasswordSafe/PasswordSafe/img/Search16.png | Bin 0 -> 1352 bytes .../PasswordSafe/img/Separator_16.png | Bin 0 -> 178 bytes PasswordSafe/PasswordSafe/img/Silver48.png | Bin 0 -> 4081 bytes PasswordSafe/PasswordSafe/img/Task_16.png | Bin 0 -> 819 bytes PasswordSafe/PasswordSafe/img/TextBox_32.png | Bin 0 -> 1409 bytes PasswordSafe/PasswordSafe/img/Text_16.png | Bin 0 -> 1276 bytes PasswordSafe/PasswordSafe/img/Textbox_16.png | Bin 0 -> 1325 bytes PasswordSafe/PasswordSafe/img/Top_16.png | Bin 0 -> 551 bytes PasswordSafe/PasswordSafe/img/Top_32.png | Bin 0 -> 1194 bytes PasswordSafe/PasswordSafe/img/Up_16.png | Bin 0 -> 436 bytes PasswordSafe/PasswordSafe/img/Up_32.png | Bin 0 -> 931 bytes PasswordSafe/PasswordSafe/img/Vista48.png | Bin 0 -> 4198 bytes PasswordSafe/PasswordSafe/img/Win7.48.png | Bin 0 -> 4206 bytes PasswordSafe/PasswordSafe/img/Zoom_16.png | Bin 0 -> 511 bytes PasswordSafe/PasswordSafe/img/Zoom_32.png | Bin 0 -> 1186 bytes .../PasswordSafe/img/arrow_left_16.png | Bin 0 -> 3563 bytes .../PasswordSafe/img/arrow_left_32.png | Bin 0 -> 4880 bytes .../PasswordSafe/img/arrow_right_16.png | Bin 0 -> 3558 bytes .../PasswordSafe/img/arrow_right_24.png | Bin 0 -> 4127 bytes .../PasswordSafe/img/arrow_right_24_d.png | Bin 0 -> 3761 bytes .../PasswordSafe/img/arrow_right_24_h.png | Bin 0 -> 4189 bytes .../PasswordSafe/img/arrow_right_24_p.png | Bin 0 -> 4137 bytes .../PasswordSafe/img/arrow_right_32.png | Bin 0 -> 4632 bytes PasswordSafe/PasswordSafe/img/category_16.png | Bin 0 -> 1326 bytes PasswordSafe/PasswordSafe/img/category_32.png | Bin 0 -> 2047 bytes PasswordSafe/PasswordSafe/img/checkbox_16.png | Bin 0 -> 1332 bytes PasswordSafe/PasswordSafe/img/copy_16.png | Bin 0 -> 3036 bytes PasswordSafe/PasswordSafe/img/copy_32.png | Bin 0 -> 3474 bytes .../PasswordSafe/img/cut_clipboard_16.png | Bin 0 -> 3589 bytes PasswordSafe/PasswordSafe/img/del16.png | Bin 0 -> 286 bytes PasswordSafe/PasswordSafe/img/delete_16.png | Bin 0 -> 3487 bytes PasswordSafe/PasswordSafe/img/faves16.png | Bin 0 -> 3532 bytes .../PasswordSafe/img/favorites_16.png | Bin 0 -> 3438 bytes .../PasswordSafe/img/favorites_24.png | Bin 0 -> 3632 bytes .../PasswordSafe/img/favorites_32.png | Bin 0 -> 4333 bytes PasswordSafe/PasswordSafe/img/glass_16.png | Bin 0 -> 865 bytes .../PasswordSafe/img/halfselected_16.png | Bin 0 -> 3534 bytes PasswordSafe/PasswordSafe/img/home_16.png | Bin 0 -> 3317 bytes PasswordSafe/PasswordSafe/img/home_24.png | Bin 0 -> 3840 bytes PasswordSafe/PasswordSafe/img/home_32.png | Bin 0 -> 4775 bytes .../PasswordSafe/img/new_document_16.png | Bin 0 -> 2989 bytes .../PasswordSafe/img/new_document_32.png | Bin 0 -> 3357 bytes PasswordSafe/PasswordSafe/img/nofaves16.png | Bin 0 -> 3493 bytes .../PasswordSafe/img/open_document_16.png | Bin 0 -> 3219 bytes .../PasswordSafe/img/open_document_32.png | Bin 0 -> 4274 bytes PasswordSafe/PasswordSafe/img/print_16.png | Bin 0 -> 3312 bytes PasswordSafe/PasswordSafe/img/print_32.png | Bin 0 -> 1174 bytes .../img/properties_document_16.png | Bin 0 -> 3175 bytes .../img/properties_document_32.png | Bin 0 -> 3940 bytes PasswordSafe/PasswordSafe/img/pwField_16.png | Bin 0 -> 1329 bytes PasswordSafe/PasswordSafe/img/save_16.png | Bin 0 -> 3141 bytes PasswordSafe/PasswordSafe/img/save_32.png | Bin 0 -> 3774 bytes PasswordSafe/PasswordSafe/img/search_16.png | Bin 0 -> 3494 bytes PasswordSafe/PasswordSafe/img/search_32.png | Bin 0 -> 4843 bytes PasswordSafe/PasswordSafe/img/stop_16.png | Bin 0 -> 3620 bytes PasswordSafe/PasswordSafe/img/stop_24.png | Bin 0 -> 4414 bytes PasswordSafe/PasswordSafe/img/undo_16.png | Bin 0 -> 3404 bytes PasswordSafe/PasswordSafe/img/undo_32.png | Bin 0 -> 4111 bytes PasswordSafe/PasswordSafe/img/view_24.png | Bin 0 -> 3538 bytes 399 files changed, 34899 insertions(+) create mode 100644 .gitignore create mode 100644 Odyssey/Demos/AlignTableCellMiddleCenterHS.png create mode 100644 Odyssey/Demos/App.xaml create mode 100644 Odyssey/Demos/App.xaml.cs create mode 100644 Odyssey/Demos/BreadcrumbWithDataSource.xaml create mode 100644 Odyssey/Demos/BreadcrumbWithDataSource.xaml.cs create mode 100644 Odyssey/Demos/Demos.csproj create mode 100644 Odyssey/Demos/FolderItem.cs create mode 100644 Odyssey/Demos/Main.xaml create mode 100644 Odyssey/Demos/Main.xaml.cs create mode 100644 Odyssey/Demos/MainDemo.xaml create mode 100644 Odyssey/Demos/MainDemo.xaml.cs create mode 100644 Odyssey/Demos/Outlook.xaml create mode 100644 Odyssey/Demos/Outlook.xaml.cs create mode 100644 Odyssey/Demos/Properties/AssemblyInfo.cs create mode 100644 Odyssey/Demos/Properties/Resources.Designer.cs create mode 100644 Odyssey/Demos/Properties/Resources.resx create mode 100644 Odyssey/Demos/Properties/Settings.Designer.cs create mode 100644 Odyssey/Demos/Properties/Settings.settings create mode 100644 Odyssey/Demos/Ribbon/RibbonDemo.xaml create mode 100644 Odyssey/Demos/Ribbon/RibbonDemo.xaml.cs create mode 100644 Odyssey/Demos/Ribbon/ThumbnailConverter.cs create mode 100644 Odyssey/Demos/Web.png create mode 100644 Odyssey/Demos/img/cut16.png create mode 100644 Odyssey/Demos/img/cut32.png create mode 100644 Odyssey/Demos/img/delete16.png create mode 100644 Odyssey/Demos/img/delete32.png create mode 100644 Odyssey/Demos/img/favorites16.png create mode 100644 Odyssey/Demos/img/favorites32.png create mode 100644 Odyssey/Demos/img/folder16.png create mode 100644 Odyssey/Demos/img/folder32.png create mode 100644 Odyssey/Demos/img/history16.png create mode 100644 Odyssey/Demos/img/history32.png create mode 100644 Odyssey/Demos/img/home16.png create mode 100644 Odyssey/Demos/img/home32.png create mode 100644 Odyssey/Demos/img/mail16.png create mode 100644 Odyssey/Demos/img/mail32.png create mode 100644 Odyssey/Demos/img/paste16.png create mode 100644 Odyssey/Demos/img/paste32.png create mode 100644 Odyssey/Demos/img/props16.png create mode 100644 Odyssey/Demos/img/props32.png create mode 100644 Odyssey/Demos/img/save16.png create mode 100644 Odyssey/Demos/img/save32.png create mode 100644 Odyssey/Demos/img/search16.png create mode 100644 Odyssey/Demos/img/search32.png create mode 100644 Odyssey/Demos/img/undo16.png create mode 100644 Odyssey/Demos/img/undo32.png create mode 100644 Odyssey/Demos/openfolderHS.png create mode 100644 Odyssey/Odyssey.sln create mode 100644 Odyssey/Odyssey/BreadcrumbBar/ApplyPropertiesEventArgs.cs create mode 100644 Odyssey/Odyssey/BreadcrumbBar/BreadcrumbBar.cs create mode 100644 Odyssey/Odyssey/BreadcrumbBar/BreadcrumbButton.cs create mode 100644 Odyssey/Odyssey/BreadcrumbBar/BreadcrumbItem.cs create mode 100644 Odyssey/Odyssey/BreadcrumbBar/BreadcrumbItemEventArgs.cs create mode 100644 Odyssey/Odyssey/BreadcrumbBar/ImageButton.cs create mode 100644 Odyssey/Odyssey/BreadcrumbBar/PathConversionEventArgs.cs create mode 100644 Odyssey/Odyssey/ChangeLog.txt create mode 100644 Odyssey/Odyssey/Common/AeroChrome.cs create mode 100644 Odyssey/Odyssey/Common/AnimationDecorator.cs create mode 100644 Odyssey/Odyssey/Common/EmptyStringVisibilityConverter.cs create mode 100644 Odyssey/Odyssey/Common/IKeyTipControl.cs create mode 100644 Odyssey/Odyssey/Common/PopupHelper.cs create mode 100644 Odyssey/Odyssey/Common/SkinId.cs create mode 100644 Odyssey/Odyssey/Common/SkinManager.cs create mode 100644 Odyssey/Odyssey/Common/Skins.cs create mode 100644 Odyssey/Odyssey/Controls/ClickableTreeView.cs create mode 100644 Odyssey/Odyssey/Controls/ClickableTreeViewItem.cs create mode 100644 Odyssey/Odyssey/Controls/DropDownButton.cs create mode 100644 Odyssey/Odyssey/Controls/OdcButton.cs create mode 100644 Odyssey/Odyssey/Controls/OdcTextBox.cs create mode 100644 Odyssey/Odyssey/Effects/GrayscaleEffect.cs create mode 100644 Odyssey/Odyssey/Effects/GrayscaleEffect.fx create mode 100644 Odyssey/Odyssey/Effects/GrayscaleEffect.ps create mode 100644 Odyssey/Odyssey/ExplorerBar/ExplorerBar.cs create mode 100644 Odyssey/Odyssey/ExplorerBar/OdcExpander.cs create mode 100644 Odyssey/Odyssey/ExplorerBar/OdcExpanderHeader.cs create mode 100644 Odyssey/Odyssey/Odyssey.csproj create mode 100644 Odyssey/Odyssey/OutlookBar/ExpandPosition.cs create mode 100644 Odyssey/Odyssey/OutlookBar/OutlookBar.cs create mode 100644 Odyssey/Odyssey/OutlookBar/OutlookSection.cs create mode 100644 Odyssey/Odyssey/OutlookBar/OverflowMenuCreatedEventArgs.cs create mode 100644 Odyssey/Odyssey/Properties/AssemblyInfo.cs create mode 100644 Odyssey/Odyssey/Properties/Resources.Designer.cs create mode 100644 Odyssey/Odyssey/Properties/Resources.resx create mode 100644 Odyssey/Odyssey/Properties/Settings.Designer.cs create mode 100644 Odyssey/Odyssey/Properties/Settings.settings create mode 100644 Odyssey/Odyssey/Ribbon/Classes/BoolConverter.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/IRibbonSize.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/ImageRenderOptions.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/NativeMethods.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/QAItemPlacement.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/QAPlacement.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/RibbonBarAlignment.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/RibbonGalleryColumns.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/RibbonGalleryColumnsCollectionConverter.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/RibbonGroupReductionOrderConverter.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/RibbonOption.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/RibbonReductionCollectionConverter.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/RibbonSize.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/RibbonSizeCollection.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/RibbonWindowCornerMode.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/RoundedCornerConverter.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/RoundedCornerResizeConverter.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/TwoLineConverter.cs create mode 100644 Odyssey/Odyssey/Ribbon/Classes/TwoLineTextConverter.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/InternalGroupPanel.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/KeyTip.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonApplicationMenu.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonApplicationMenuItem.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonBar.Commands.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonBar.Handlers.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonBar.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonButton.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonButtonGroup.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonButtonStyle.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonChrome.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonComboBox.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonComboBoxItem.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonContextualTabSet.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonDropDownButton.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonFlowGroup.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonGallery.Commands.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonGallery.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonGroup.Commands.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonGroup.Handlers.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonGroup.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonMenuItem.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonQAToolBar.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonQAToolbarPanel.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonSeparator.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonSplitButton.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonTabItem.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonTabItemPanel.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonTabScroller.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonTextBox.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonThumbnail.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonToggleButton.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonToolTip.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonWindow.Commands.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonWindow.cs create mode 100644 Odyssey/Odyssey/Ribbon/Controls/RibbonWrapPanel.cs create mode 100644 Odyssey/Odyssey/Ribbon/EventArgs/SelectedTabIndexChangedEvent.cs create mode 100644 Odyssey/Odyssey/Ribbon/Interfaces/IRibbonButton.cs create mode 100644 Odyssey/Odyssey/Ribbon/Interfaces/IRibbonControl.cs create mode 100644 Odyssey/Odyssey/Ribbon/Interfaces/IRibbonGallery.cs create mode 100644 Odyssey/Odyssey/Ribbon/Interfaces/IRibbonLargeControl.cs create mode 100644 Odyssey/Odyssey/Ribbon/Interfaces/IRibbonStretch.cs create mode 100644 Odyssey/Odyssey/Skins/BlackSkin.xaml create mode 100644 Odyssey/Odyssey/Skins/BlueSkin.xaml create mode 100644 Odyssey/Odyssey/Skins/OutlookBar/OutlookBlackSkin.xaml create mode 100644 Odyssey/Odyssey/Skins/OutlookBar/OutlookBlueSkin.xaml create mode 100644 Odyssey/Odyssey/Skins/OutlookBar/OutlookSilverSkin.xaml create mode 100644 Odyssey/Odyssey/Skins/OutlookBar/Win7Skin.xaml create mode 100644 Odyssey/Odyssey/Skins/Ribbon/OfficeBlackSkin.xaml create mode 100644 Odyssey/Odyssey/Skins/Ribbon/OfficeBlueSkin.xaml create mode 100644 Odyssey/Odyssey/Skins/Ribbon/OfficeSilverSkin.xaml create mode 100644 Odyssey/Odyssey/Skins/Ribbon/VistaSkin.xaml create mode 100644 Odyssey/Odyssey/Skins/Ribbon/Window7Skin.xaml create mode 100644 Odyssey/Odyssey/Skins/SilverSkin.xaml create mode 100644 Odyssey/Odyssey/Skins/VistaSkin.xaml create mode 100644 Odyssey/Odyssey/Skins/Win7Skin.xaml create mode 100644 Odyssey/Odyssey/Themes/Aero.NormalColor.xaml create mode 100644 Odyssey/Odyssey/Themes/BreadcrumbBar/AeroChrome.xaml create mode 100644 Odyssey/Odyssey/Themes/BreadcrumbBar/BreadcrumbButton.xaml create mode 100644 Odyssey/Odyssey/Themes/BreadcrumbBar/BreadcrumbItem.xaml create mode 100644 Odyssey/Odyssey/Themes/BreadcrumbBar/Brushes.xaml create mode 100644 Odyssey/Odyssey/Themes/BreadcrumbBar/ButtonTemplates.xaml create mode 100644 Odyssey/Odyssey/Themes/BreadcrumbBar/Generic.xaml create mode 100644 Odyssey/Odyssey/Themes/BreadcrumbBar/OdcTextBox.xaml create mode 100644 Odyssey/Odyssey/Themes/BreadcrumbBar/ProgressBar.xaml create mode 100644 Odyssey/Odyssey/Themes/Classic.xaml create mode 100644 Odyssey/Odyssey/Themes/Controls/DropDownButton.xaml create mode 100644 Odyssey/Odyssey/Themes/Controls/OdcTextBox.xaml create mode 100644 Odyssey/Odyssey/Themes/Expander/Aero.NormalColor.ExpandHeader.xaml create mode 100644 Odyssey/Odyssey/Themes/Expander/Aero.NormalColor.xaml create mode 100644 Odyssey/Odyssey/Themes/Expander/Classic.ExpandHeader.xaml create mode 100644 Odyssey/Odyssey/Themes/Expander/Classic.xaml create mode 100644 Odyssey/Odyssey/Themes/Expander/Generic.ExpandHeader.xaml create mode 100644 Odyssey/Odyssey/Themes/Expander/Generic.Expander.xaml create mode 100644 Odyssey/Odyssey/Themes/Expander/Generic.xaml create mode 100644 Odyssey/Odyssey/Themes/Expander/Luna.Homestead.xaml create mode 100644 Odyssey/Odyssey/Themes/Expander/Luna.Metallic.xaml create mode 100644 Odyssey/Odyssey/Themes/Expander/Luna.NormalColor.xaml create mode 100644 Odyssey/Odyssey/Themes/Generic.xaml create mode 100644 Odyssey/Odyssey/Themes/Luna.Homestead.xaml create mode 100644 Odyssey/Odyssey/Themes/Luna.Metallic.xaml create mode 100644 Odyssey/Odyssey/Themes/Luna.NormalColor.xaml create mode 100644 Odyssey/Odyssey/Themes/OutlookBar/Generic.xaml create mode 100644 Odyssey/Odyssey/Themes/OutlookBar/OutlookBar.xaml create mode 100644 Odyssey/Odyssey/Themes/OutlookBar/OutlookSection.xaml create mode 100644 Odyssey/Odyssey/Themes/OutlookBar/ToggleButton.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/DefaultRibbonButtonBrushes.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/DefaultWindowButtonStyles.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/Generic.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/HighlightedBackgrounds.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/InternalRibbonButton.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/QuickAccessKey.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonAppMenuItem.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonApplicationButton.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonApplicationMenu.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonBar.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonButton.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonButtonGroup.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonChrome.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonComboBox.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonComboBoxItem.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonContextualTabSet.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonDropDownButton.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonFlowGroup.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonGallery.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonGroup.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonGroupBrushes.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonGroupDropDownButton.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonHLChromeStyle.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonImages.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonMenuItem.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonQAToolBar.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonSeparator.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonSplitButton.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonTabItem.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonTabScroller.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonTextBox.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonThumbnail.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonToggleButton.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonToolTip.xaml create mode 100644 Odyssey/Odyssey/Themes/Ribbon/RibbonWindow.xaml create mode 100644 Odyssey/Odyssey/Themes/TreeViewStyle.xaml create mode 100644 Odyssey/Odyssey/app.config create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/BaseObject.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/BizContext.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/Category.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/CustomNode.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/Field.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/FieldType.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/Folder.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/NodeBase.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/NotifyEventArgs.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/NotifyList.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/Password.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/PasswordFolder.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Biz/TemplateField.cs create mode 100644 PasswordSafe/PasswordSafe.Data/DAL/DAL.cs create mode 100644 PasswordSafe/PasswordSafe.Data/DAL/DBEntityNotUpdatedException.cs create mode 100644 PasswordSafe/PasswordSafe.Data/PasswordSafe.Data.csproj create mode 100644 PasswordSafe/PasswordSafe.Data/Properties/AssemblyInfo.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Properties/Resources.Designer.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Properties/Resources.resx create mode 100644 PasswordSafe/PasswordSafe.Data/Properties/Settings.Designer.cs create mode 100644 PasswordSafe/PasswordSafe.Data/Properties/Settings.settings create mode 100644 PasswordSafe/PasswordSafe/App.xaml create mode 100644 PasswordSafe/PasswordSafe/App.xaml.cs create mode 100644 PasswordSafe/PasswordSafe/Classes/UIContext.cs create mode 100644 PasswordSafe/PasswordSafe/Commands.cs create mode 100644 PasswordSafe/PasswordSafe/Controls/EditLabel.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/CopyFieldConverter.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/CountConverter.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/DateConverter.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/DisplayTypeToVisibleConverter.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/FavoriteImageConverter.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/FieldTypeConverter.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/FieldValidatonRule.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/ImageConverter.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/IntConverter.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/IsModifiedFromUIPasswordConverter.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/NullToBoolConverter.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/NullToVisibleConverter.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/TimeConverter.cs create mode 100644 PasswordSafe/PasswordSafe/Converter/VisibilityConverter.cs create mode 100644 PasswordSafe/PasswordSafe/DataTemplateSelectors/BreadcrumbItemSelector.cs create mode 100644 PasswordSafe/PasswordSafe/DataTemplateSelectors/CopyPasswordSelector.cs create mode 100644 PasswordSafe/PasswordSafe/DataTemplateSelectors/FieldSelector.cs create mode 100644 PasswordSafe/PasswordSafe/DataTemplateSelectors/TemplateFieldSelector.cs create mode 100644 PasswordSafe/PasswordSafe/DataTemplates.xaml create mode 100644 PasswordSafe/PasswordSafe/Enums/DisplayMode.cs create mode 100644 PasswordSafe/PasswordSafe/Enums/DisplayType.cs create mode 100644 PasswordSafe/PasswordSafe/Export/XmlExporter.cs create mode 100644 PasswordSafe/PasswordSafe/Export/XmlField.cs create mode 100644 PasswordSafe/PasswordSafe/Export/XmlPassword.cs create mode 100644 PasswordSafe/PasswordSafe/FieldTemplates.xaml create mode 100644 PasswordSafe/PasswordSafe/Main.xaml create mode 100644 PasswordSafe/PasswordSafe/Main.xaml.cs create mode 100644 PasswordSafe/PasswordSafe/PasswordSafe.csproj create mode 100644 PasswordSafe/PasswordSafe/Properties/AssemblyInfo.cs create mode 100644 PasswordSafe/PasswordSafe/Properties/Resources.Designer.cs create mode 100644 PasswordSafe/PasswordSafe/Properties/Resources.resx create mode 100644 PasswordSafe/PasswordSafe/Properties/Settings.Designer.cs create mode 100644 PasswordSafe/PasswordSafe/Properties/Settings.settings create mode 100644 PasswordSafe/PasswordSafe/Settings.cs create mode 100644 PasswordSafe/PasswordSafe/TemplateFieldTemplates.xaml create mode 100644 PasswordSafe/PasswordSafe/Themes/Generic.xaml create mode 100644 PasswordSafe/PasswordSafe/Tools/RibbonSrc.cs create mode 100644 PasswordSafe/PasswordSafe/Tools/TreeViewExtender.cs create mode 100644 PasswordSafe/PasswordSafe/UserControls/ChangePassword.xaml create mode 100644 PasswordSafe/PasswordSafe/UserControls/ChangePassword.xaml.cs create mode 100644 PasswordSafe/PasswordSafe/UserControls/LockScreen.xaml create mode 100644 PasswordSafe/PasswordSafe/UserControls/LockScreen.xaml.cs create mode 100644 PasswordSafe/PasswordSafe/UserControls/PasswordGrid.xaml create mode 100644 PasswordSafe/PasswordSafe/UserControls/PasswordGrid.xaml.cs create mode 100644 PasswordSafe/PasswordSafe/UserControls/TemplateGrid.xaml create mode 100644 PasswordSafe/PasswordSafe/UserControls/TemplateGrid.xaml.cs create mode 100644 PasswordSafe/PasswordSafe/app.config create mode 100644 PasswordSafe/PasswordSafe/img/Accept_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Accept_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Add_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Add_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Black48.png create mode 100644 PasswordSafe/PasswordSafe/img/Blue48.png create mode 100644 PasswordSafe/PasswordSafe/img/Bottom_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Bottom_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Comment_16.png create mode 100644 PasswordSafe/PasswordSafe/img/DateTime_16.png create mode 100644 PasswordSafe/PasswordSafe/img/DefineName_16.png create mode 100644 PasswordSafe/PasswordSafe/img/DeleteTb_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Delete_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Down_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Down_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Export_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Glass_32.png create mode 100644 PasswordSafe/PasswordSafe/img/InsertB_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Layout_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Layout_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Left_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Left_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Lock_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Lock_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Max_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Min_16.png create mode 100644 PasswordSafe/PasswordSafe/img/NewPassword_32.png create mode 100644 PasswordSafe/PasswordSafe/img/NoFaves32.png create mode 100644 PasswordSafe/PasswordSafe/img/Numeric_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Pushpin_16.png create mode 100644 PasswordSafe/PasswordSafe/img/QuickParts_16.png create mode 100644 PasswordSafe/PasswordSafe/img/QuickParts_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Reject_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Reject_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Remove_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Remove_32.png create mode 100644 PasswordSafe/PasswordSafe/img/RightArrowHS_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Right_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Right_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Search16.png create mode 100644 PasswordSafe/PasswordSafe/img/Separator_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Silver48.png create mode 100644 PasswordSafe/PasswordSafe/img/Task_16.png create mode 100644 PasswordSafe/PasswordSafe/img/TextBox_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Text_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Textbox_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Top_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Top_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Up_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Up_32.png create mode 100644 PasswordSafe/PasswordSafe/img/Vista48.png create mode 100644 PasswordSafe/PasswordSafe/img/Win7.48.png create mode 100644 PasswordSafe/PasswordSafe/img/Zoom_16.png create mode 100644 PasswordSafe/PasswordSafe/img/Zoom_32.png create mode 100644 PasswordSafe/PasswordSafe/img/arrow_left_16.png create mode 100644 PasswordSafe/PasswordSafe/img/arrow_left_32.png create mode 100644 PasswordSafe/PasswordSafe/img/arrow_right_16.png create mode 100644 PasswordSafe/PasswordSafe/img/arrow_right_24.png create mode 100644 PasswordSafe/PasswordSafe/img/arrow_right_24_d.png create mode 100644 PasswordSafe/PasswordSafe/img/arrow_right_24_h.png create mode 100644 PasswordSafe/PasswordSafe/img/arrow_right_24_p.png create mode 100644 PasswordSafe/PasswordSafe/img/arrow_right_32.png create mode 100644 PasswordSafe/PasswordSafe/img/category_16.png create mode 100644 PasswordSafe/PasswordSafe/img/category_32.png create mode 100644 PasswordSafe/PasswordSafe/img/checkbox_16.png create mode 100644 PasswordSafe/PasswordSafe/img/copy_16.png create mode 100644 PasswordSafe/PasswordSafe/img/copy_32.png create mode 100644 PasswordSafe/PasswordSafe/img/cut_clipboard_16.png create mode 100644 PasswordSafe/PasswordSafe/img/del16.png create mode 100644 PasswordSafe/PasswordSafe/img/delete_16.png create mode 100644 PasswordSafe/PasswordSafe/img/faves16.png create mode 100644 PasswordSafe/PasswordSafe/img/favorites_16.png create mode 100644 PasswordSafe/PasswordSafe/img/favorites_24.png create mode 100644 PasswordSafe/PasswordSafe/img/favorites_32.png create mode 100644 PasswordSafe/PasswordSafe/img/glass_16.png create mode 100644 PasswordSafe/PasswordSafe/img/halfselected_16.png create mode 100644 PasswordSafe/PasswordSafe/img/home_16.png create mode 100644 PasswordSafe/PasswordSafe/img/home_24.png create mode 100644 PasswordSafe/PasswordSafe/img/home_32.png create mode 100644 PasswordSafe/PasswordSafe/img/new_document_16.png create mode 100644 PasswordSafe/PasswordSafe/img/new_document_32.png create mode 100644 PasswordSafe/PasswordSafe/img/nofaves16.png create mode 100644 PasswordSafe/PasswordSafe/img/open_document_16.png create mode 100644 PasswordSafe/PasswordSafe/img/open_document_32.png create mode 100644 PasswordSafe/PasswordSafe/img/print_16.png create mode 100644 PasswordSafe/PasswordSafe/img/print_32.png create mode 100644 PasswordSafe/PasswordSafe/img/properties_document_16.png create mode 100644 PasswordSafe/PasswordSafe/img/properties_document_32.png create mode 100644 PasswordSafe/PasswordSafe/img/pwField_16.png create mode 100644 PasswordSafe/PasswordSafe/img/save_16.png create mode 100644 PasswordSafe/PasswordSafe/img/save_32.png create mode 100644 PasswordSafe/PasswordSafe/img/search_16.png create mode 100644 PasswordSafe/PasswordSafe/img/search_32.png create mode 100644 PasswordSafe/PasswordSafe/img/stop_16.png create mode 100644 PasswordSafe/PasswordSafe/img/stop_24.png create mode 100644 PasswordSafe/PasswordSafe/img/undo_16.png create mode 100644 PasswordSafe/PasswordSafe/img/undo_32.png create mode 100644 PasswordSafe/PasswordSafe/img/view_24.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bdc3535 --- /dev/null +++ b/.gitignore @@ -0,0 +1,108 @@ +# Build Folders (you can keep bin if you'd like, to store dlls and pdbs) +[Bb]in/ +[Oo]bj/ + +# mstest test results +TestResults + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.log +*.vspscc +*.vssscc +.builds + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper* + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory +packages + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +[Bb]in +[Oo]bj +sql +TestResults +[Tt]est[Rr]esult* +*.Cache +ClientBin +[Ss]tyle[Cc]op.* +~$* +*.dbmdl +Generated_Code #added for RIA/Silverlight projects + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML diff --git a/Odyssey/Demos/AlignTableCellMiddleCenterHS.png b/Odyssey/Demos/AlignTableCellMiddleCenterHS.png new file mode 100644 index 0000000000000000000000000000000000000000..34014a1824ed1a2766f0575621c5b78de91fd8f4 GIT binary patch literal 432 zcmV;h0Z;ykP)z@;j(q!3lK=n!AY({UO#lFTB>(_`g8%^e{{R4h=>PzA zFaQARU;qF*m;eA5Z<1fdMgRZ-CP_p=RCwB?Q@u(9VGx`z=Jbht0vqjZt*iuJN_+va z@BswH(#lG)Nf41BYI5GUKeHCOKgKV%f_2n`fRa#kWDR7Q zDpz92GM#4l`q4JAF@+556x;tQU*8WQ>KK>Js{Pr+J)lmEK1*cvxpI? a@iPGXdPnD5e7rLN0000 + + + + diff --git a/Odyssey/Demos/App.xaml.cs b/Odyssey/Demos/App.xaml.cs new file mode 100644 index 0000000..4d1351a --- /dev/null +++ b/Odyssey/Demos/App.xaml.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Windows; + +namespace Demos +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/Odyssey/Demos/BreadcrumbWithDataSource.xaml b/Odyssey/Demos/BreadcrumbWithDataSource.xaml new file mode 100644 index 0000000..24199bd --- /dev/null +++ b/Odyssey/Demos/BreadcrumbWithDataSource.xaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Odyssey/Demos/BreadcrumbWithDataSource.xaml.cs b/Odyssey/Demos/BreadcrumbWithDataSource.xaml.cs new file mode 100644 index 0000000..ecdc693 --- /dev/null +++ b/Odyssey/Demos/BreadcrumbWithDataSource.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Demos +{ + /// + /// Interaction logic for Window1.xaml + /// + public partial class BreadcrumbDS : Window + { + public BreadcrumbDS() + { + + InitializeComponent(); + } + } +} diff --git a/Odyssey/Demos/Demos.csproj b/Odyssey/Demos/Demos.csproj new file mode 100644 index 0000000..eb19a5e --- /dev/null +++ b/Odyssey/Demos/Demos.csproj @@ -0,0 +1,188 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {4F736E6C-D570-4882-B521-93819C059E82} + WinExe + Properties + Demos + Demos + v3.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + SAK + SAK + SAK + SAK + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + 3.5 + + + 3.5 + + + + + 3.0 + + + 3.0 + + + 3.0 + + + 3.0 + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + App.xaml + Code + + + MainDemo.xaml + Code + + + + + BreadcrumbWithDataSource.xaml + + + + Main.xaml + + + Outlook.xaml + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + RibbonDemo.xaml + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + + + {333FDC55-6B47-4A64-A2DF-A4C5823FAC74} + Odyssey + + + + + \ No newline at end of file diff --git a/Odyssey/Demos/FolderItem.cs b/Odyssey/Demos/FolderItem.cs new file mode 100644 index 0000000..e15e633 --- /dev/null +++ b/Odyssey/Demos/FolderItem.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows.Media.Imaging; +using System.Windows.Media; +using System.IO; + +namespace Demos +{ + public class FolderItem + { + public string Folder { get; set; } + public ImageSource Image { get; set; } + + public FolderItem() + : base() + { + ImageSourceConverter isc = new ImageSourceConverter(); + Image = isc.ConvertFrom("openfolderHS.png") as ImageSource; + } + } +} diff --git a/Odyssey/Demos/Main.xaml b/Odyssey/Demos/Main.xaml new file mode 100644 index 0000000..fa87a52 --- /dev/null +++ b/Odyssey/Demos/Main.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + diff --git a/Odyssey/Demos/Main.xaml.cs b/Odyssey/Demos/Main.xaml.cs new file mode 100644 index 0000000..4cdf858 --- /dev/null +++ b/Odyssey/Demos/Main.xaml.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using Demos.Ribbon; + +namespace Demos +{ + /// + /// Interaction logic for Main.xaml + /// + public partial class Main : Window + { + public Main() + { + InitializeComponent(); + } + + private void OutlookClick(object sender, RoutedEventArgs e) + { + Outlook outlook = new Outlook(); + outlook.ShowDialog(); + } + + private void ExplorerBarClick(object sender, RoutedEventArgs e) + { + ExplorerBar explorerBar = new ExplorerBar(); + explorerBar.ShowDialog(); + } + + private void BreadcrumbBarClick(object sender, RoutedEventArgs e) + { + BreadcrumbDS bds = new BreadcrumbDS(); + bds.ShowDialog(); + } + + private void RibbonBarClick(object sender, RoutedEventArgs e) + { + RibbonDemo ribbonDemo = new RibbonDemo(); + ribbonDemo.Show(); + } + } +} diff --git a/Odyssey/Demos/MainDemo.xaml b/Odyssey/Demos/MainDemo.xaml new file mode 100644 index 0000000..5bd41f0 --- /dev/null +++ b/Odyssey/Demos/MainDemo.xaml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Odyssey/Demos/MainDemo.xaml.cs b/Odyssey/Demos/MainDemo.xaml.cs new file mode 100644 index 0000000..33f6a6c --- /dev/null +++ b/Odyssey/Demos/MainDemo.xaml.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Odyssey.Controls; +using System.Windows.Media.Animation; + +namespace Demos +{ + //BUGFIX: Corrected a glitch when the SubItems of a Breadrumb where reopened. This caused the SelectedItem to be set to null and therefore alls trails after the breadcrumb where removed. + + /// + /// Interaction logic for Window1.xaml + /// + public partial class ExplorerBar : Window + { + public ExplorerBar() + { + InitializeComponent(); + } + + #region ExplorerBar + private void Button_Click(object sender, RoutedEventArgs e) + { + Background = Brushes.LightSteelBlue; + } + + private void Button_Click_1(object sender, RoutedEventArgs e) + { + text3.Visibility = text3.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible; + text1.Visibility = text1.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible; + } + + + private void Button_Click_3(object sender, RoutedEventArgs e) + { + expander1.IsMinimized ^= true; + } + + private void Button_Click_4(object sender, RoutedEventArgs e) + { + expander2.IsMinimized ^= true; + } + + private void Animate1Click(object sender, RoutedEventArgs e) + { + expander2.IsExpanded ^= true; + expander1.IsMinimized ^= true; + + } + private void Animate2Click(object sender, RoutedEventArgs e) + { + //text2.Visibility = text2.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible; + expander1.IsExpanded ^= true; + expander2.IsExpanded ^= true; + } + #endregion + + #region BreadcrumbBar + /// + /// A BreadcrumbItem needs to populate it's Items. This can be due to the fact that a new BreadcrumbItem is selected, and thus + /// it's Items must be populated to determine whether this BreadcrumbItem show a dropdown button, + /// or when the Path property of the BreadcrumbBar is changed and therefore the Breadcrumbs must be populated from the new path. + /// + private void BreadcrumbBar_PopulateItems(object sender, Odyssey.Controls.BreadcrumbItemEventArgs e) + { + BreadcrumbItem item = e.Item; + if (item.Items.Count == 0) + { + PopulateFolders(item); + e.Handled = true; + } + } + + /// + /// Gets a collection of all drives on the computer. + /// + private static IEnumerable GetDrives(string separatorString) + { + int separatorLength = separatorString.Length; + var folders = from drive in System.IO.Directory.GetLogicalDrives() select drive.EndsWith(separatorString) ? drive.Remove(drive.Length - separatorLength) : drive; + return folders.AsEnumerable(); + } + + /// + /// Populate the Items of the specified BreadcrumbItem with the sub folders if necassary. + /// + /// + private static void PopulateFolders(BreadcrumbItem item) + { + List folderItems = GetFolderItemsFromBreadcrumb(item); + item.ItemsSource = folderItems; + } + + /// + /// Gets a list of FolderItems that are the subfolders of the specified BreadcrumbItem. + /// + private static List GetFolderItemsFromBreadcrumb(BreadcrumbItem item) + { + BreadcrumbBar bar = item.BreadcrumbBar; + string path = bar.PathFromBreadcrumbItem(item); + string trace = item.TraceValue; + string[] subFolders; + if (trace.Equals("Computer")) + { + subFolders = GetDrives(bar.SeparatorString).ToArray(); + } + else + { + try + { + subFolders = (from dir in System.IO.Directory.GetDirectories(path + "\\") select System.IO.Path.GetFileName(dir)).ToArray(); + } + catch + { + //maybe we don't have access! + subFolders = new string[] { }; + } + } + List folderItems = (from folder in subFolders orderby folder select new FolderItem { Folder = folder }).ToList(); + return folderItems; + } + + /// + /// Convert the path from visual to logical or vice versa: + /// + private void BreadcrumbBar_PathConversion(object sender, PathConversionEventArgs e) + { + if (e.Mode == PathConversionEventArgs.ConversionMode.DisplayToEdit) + { + if (e.DisplayPath.StartsWith(@"Computer\", StringComparison.OrdinalIgnoreCase)) + { + e.EditPath = e.DisplayPath.Remove(0, 9); + } + else if (e.DisplayPath.StartsWith(@"Network\", StringComparison.OrdinalIgnoreCase)) + { + string editPath = e.DisplayPath.Remove(0, 8); + editPath = @"\\" + editPath.Replace('\\', '/'); + e.EditPath = editPath; + } + } + else + { + if (e.EditPath.StartsWith("c:", StringComparison.OrdinalIgnoreCase)) + { + e.DisplayPath = @"Desktop\Computer\" + e.EditPath; + } + else if (e.EditPath.StartsWith(@"\\")) + { + e.DisplayPath = @"Desktop\Network\" + e.EditPath.Remove(0, 2).Replace('/', '\\'); + } + } + } + + /// + /// Show a progress bar animation for demonstation purpose. + /// + private void RefreshClick(object sender, RoutedEventArgs e) + { + DoubleAnimation da = new DoubleAnimation(100, new Duration(new TimeSpan(0, 0, 2))); + da.FillBehavior = FillBehavior.Stop; + bar.BeginAnimation(BreadcrumbBar.ProgressValueProperty, da); + } + + /// + /// The dropdown menu of a BreadcrumbItem was pressed, so delete the current folders, and repopulate the folders + /// to ensure actual data. + /// + private void bar_BreadcrumbItemDropDownOpened(object sender, BreadcrumbItemEventArgs e) + { + BreadcrumbItem item = e.Item; + + // only repopulate, if the BreadcrumbItem is dynamically generated which means, item.Data is a pointer to itself: + if (!(item.Data is BreadcrumbItem)) + { + UpdateFolderItems(item); + } + } + + /// + /// Update the list of Subfolders from a BreadcrumbItem. + /// + private void UpdateFolderItems(BreadcrumbItem item) + { + List actualFolders = GetFolderItemsFromBreadcrumb(item); + List currentFolders = item.ItemsSource as List; + currentFolders.Clear(); + currentFolders.AddRange(actualFolders); + + } + + public void ShowStaticBreadcrumbBar(object sender, RoutedEventArgs e) + { + BreadcrumbDS ds = new BreadcrumbDS(); + ds.ShowDialog(); + } + #endregion + } +} diff --git a/Odyssey/Demos/Outlook.xaml b/Odyssey/Demos/Outlook.xaml new file mode 100644 index 0000000..0acd681 --- /dev/null +++ b/Odyssey/Demos/Outlook.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Toggle1 + Toggle2 + + + + + diff --git a/Odyssey/Demos/Outlook.xaml.cs b/Odyssey/Demos/Outlook.xaml.cs new file mode 100644 index 0000000..5142c58 --- /dev/null +++ b/Odyssey/Demos/Outlook.xaml.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using Odyssey.Controls; +using Odyssey.Controls.Classes; + +namespace Demos +{ + /// + /// Interaction logic for Outlook.xaml + /// + public partial class Outlook : Window + { + public Outlook() + { + InitializeComponent(); + } + + + private void OutlookBarIncClick(object sender, RoutedEventArgs e) + { + bar.MaxNumberOfButtons++; + } + + private void OutlookBarDecClick(object sender, RoutedEventArgs e) + { + bar.MaxNumberOfButtons--; + } + + private void bar_SelectedSectionChanged(object sender, RoutedPropertyChangedEventArgs e) + { + if (IsInitialized) + { + label.Text = e.NewValue.Header.ToString(); + } + } + + + private void BlueSkinClick(object sender, RoutedEventArgs e) + { + SkinManager.SkinId = SkinId.OfficeBlue; + + } + + private void SilverSkinClick(object sender, RoutedEventArgs e) + { + SkinManager.SkinId = SkinId.OfficeSilver; + } + + private void BlackSkinClick(object sender, RoutedEventArgs e) + { + SkinManager.SkinId = SkinId.OfficeBlack; + } + + + } +} diff --git a/Odyssey/Demos/Properties/AssemblyInfo.cs b/Odyssey/Demos/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4edad3a --- /dev/null +++ b/Odyssey/Demos/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Odyssey Demos")] +[assembly: AssemblyDescription("Demonstation of the Odyssey controls.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("tomssoftware.net")] +[assembly: AssemblyProduct("Odyssey Demos")] +[assembly: AssemblyCopyright("Copyright © Thomas Gerber 2008-2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.*")] +[assembly: AssemblyFileVersion("1.0.1.100")] diff --git a/Odyssey/Demos/Properties/Resources.Designer.cs b/Odyssey/Demos/Properties/Resources.Designer.cs new file mode 100644 index 0000000..4181dff --- /dev/null +++ b/Odyssey/Demos/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3053 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Demos.Properties +{ + + + /// + /// 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", "2.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 ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Demos.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; + } + } + } +} diff --git a/Odyssey/Demos/Properties/Resources.resx b/Odyssey/Demos/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/Odyssey/Demos/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/Odyssey/Demos/Properties/Settings.Designer.cs b/Odyssey/Demos/Properties/Settings.Designer.cs new file mode 100644 index 0000000..e161cd0 --- /dev/null +++ b/Odyssey/Demos/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3053 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Demos.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Odyssey/Demos/Properties/Settings.settings b/Odyssey/Demos/Properties/Settings.settings new file mode 100644 index 0000000..8f2fd95 --- /dev/null +++ b/Odyssey/Demos/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Odyssey/Demos/Ribbon/RibbonDemo.xaml b/Odyssey/Demos/Ribbon/RibbonDemo.xaml new file mode 100644 index 0000000..8880cec --- /dev/null +++ b/Odyssey/Demos/Ribbon/RibbonDemo.xaml @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Close Demo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Check me + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Radio 1 + Radio 2 + Radio 3 + Radio 4 + Radio 5 + Radio 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Odyssey/Demos/Ribbon/RibbonDemo.xaml.cs b/Odyssey/Demos/Ribbon/RibbonDemo.xaml.cs new file mode 100644 index 0000000..0143536 --- /dev/null +++ b/Odyssey/Demos/Ribbon/RibbonDemo.xaml.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using Odyssey.Controls.Classes; +using Odyssey.Controls; + +namespace Demos.Ribbon +{ + /// + /// Interaction logic for RibbonDemo.xaml + /// + public partial class RibbonDemo : RibbonWindow + { + public RibbonDemo() + { + InitializeComponent(); + } + + private void Win7Click(object sender, RoutedEventArgs e) + { + SkinManager.SkinId = SkinId.Windows7; + } + + private void VistaClick(object sender, RoutedEventArgs e) + { + SkinManager.SkinId = SkinId.Vista; + } + + private void OfficeBlueClick(object sender, RoutedEventArgs e) + { + SkinManager.SkinId = SkinId.OfficeBlue; + } + + private void OfficeSilverClick(object sender, RoutedEventArgs e) + { + SkinManager.SkinId = SkinId.OfficeSilver; + } + + private void OfficeBlackClick(object sender, RoutedEventArgs e) + { + SkinManager.SkinId = SkinId.OfficeBlack; + } + + private void Context1Click(object sender, RoutedEventArgs e) + { + ribbonBar.ContextualTabSet = ribbonBar.ContextualTabSets[0]; + } + + private void Context2Click(object sender, RoutedEventArgs e) + { + ribbonBar.ContextualTabSet = ribbonBar.ContextualTabSets[1]; + } + + private void ContextOffClick(object sender, RoutedEventArgs e) + { + ribbonBar.ContextualTabSet = null; + } + + + private void ShowBelowClick(object sender, RoutedEventArgs e) + { + ribbonBar.ToolbarPlacement = QAPlacement.Bottom; + } + + private void ShowAboveClick(object sender, RoutedEventArgs e) + { + ribbonBar.ToolbarPlacement = QAPlacement.Top; + } + + private void CloseDemoClick(object sender, RoutedEventArgs e) + { + Close(); + } + + private void RibbonGroup_LaunchDialog(object sender, RoutedEventArgs e) + { + MessageBox.Show("Launcher"); + } + + } +} diff --git a/Odyssey/Demos/Ribbon/ThumbnailConverter.cs b/Odyssey/Demos/Ribbon/ThumbnailConverter.cs new file mode 100644 index 0000000..0f96f72 --- /dev/null +++ b/Odyssey/Demos/Ribbon/ThumbnailConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using Odyssey.Controls; + +namespace Demos +{ + public class ThumbnailConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + RibbonThumbnail tn = value as RibbonThumbnail; + if (tn == null) return value; + + string s = System.IO.Path.GetFileName(tn.ImageSource.ToString()); + return s; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/Odyssey/Demos/Web.png b/Odyssey/Demos/Web.png new file mode 100644 index 0000000000000000000000000000000000000000..4782ae8ae190496969c510918ebbebabe6498996 GIT binary patch literal 3463 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 Hu0mjfLoDu( literal 0 HcmV?d00001 diff --git a/Odyssey/Demos/img/delete16.png b/Odyssey/Demos/img/delete16.png new file mode 100644 index 0000000000000000000000000000000000000000..9d692071127b26aa32af0a9ebf27a8158a44a497 GIT binary patch literal 3456 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 literal 0 HcmV?d00001 diff --git a/Odyssey/Demos/img/favorites16.png b/Odyssey/Demos/img/favorites16.png new file mode 100644 index 0000000000000000000000000000000000000000..d50109d0655924da7556b22e1d4308d4ea4dae14 GIT binary patch literal 3438 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 literal 0 HcmV?d00001 diff --git a/Odyssey/Demos/img/folder32.png b/Odyssey/Demos/img/folder32.png new file mode 100644 index 0000000000000000000000000000000000000000..5a0b338f56025b1b37c97b5d723a3521d560b590 GIT binary patch literal 3947 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 BD;)p; literal 0 HcmV?d00001 diff --git a/Odyssey/Demos/img/home32.png b/Odyssey/Demos/img/home32.png new file mode 100644 index 0000000000000000000000000000000000000000..2c1eda18914d5e3c354957e2a260af380200154c GIT binary patch literal 4766 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 literal 0 HcmV?d00001 diff --git a/Odyssey/Demos/img/mail16.png b/Odyssey/Demos/img/mail16.png new file mode 100644 index 0000000000000000000000000000000000000000..62ce00a6a7394dad895cfd4460a28b40f2fdf038 GIT binary patch literal 3582 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 b;6DQZ%L5K5h5VO100000NkvXXu0mjfDa5y& literal 0 HcmV?d00001 diff --git a/Odyssey/Demos/img/paste16.png b/Odyssey/Demos/img/paste16.png new file mode 100644 index 0000000000000000000000000000000000000000..f618e48641017949954bf195f22566d99fc6d04b GIT binary patch literal 3277 zcmV;;3^MbHP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=00004XF*Lt006O$eEU(80000WV@Og>004R=004l4008;_004mL004C` z008P>0026e000+nl3&F}0005#NklzL`s_m6h$<~rGkTsNfa~|6QrPnUgp3nE(dn{uN{}WMw`s!4u@*}Z;GW%&XHvl-6I!r8P5$`M_7>t3F=PK5a z0KmS)39$F_@X2djca&7h#c>=OjgPcib>{P90KBW#{w4rWVqrUnaNSYT>C-sQ9suI; zg8)2!GsfoTcVL`T0|J&YhWhH1i5)mj^~?F4{YC}P^Jq4kWHK2GAOfJppp`IjbOq_$ zA{rHzmTq$Elgmw$&0Yi`nM~q&9>rqOgcd{#N@*a1Q3Ak~t2e-3=gzawA%O$b zYTL{}`36e_r3I1RgUn2h@c!vKGm|3&*Dr;qQmM2h;AKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=00004XF*Lt006O$eEU(80000WV@Og>004R=004l4008;_004mL004C` z008P>0026e000+nl3&F}000D_Nkl?3l**|UPJ;#gr zvoqV-*=jV-&CJfu`@YY4&U2pkykchjpVwIOAR_x3aOKLCH`?u;OJ+2h%?BS|zWnj` zz&40d?&OQt zK)^*yp}NJ`(T|=7o&dcpfRur-`+)A#I{*X#0^?(g++V-Pf$BKjZDnh#OQTW8dr!4G zfr#+v(PO&ZHq%oFc-HRl!rV)QyG@)31|SZ?O@NcYZJ=400yB^8MyPxcftZ+_U~O#; zfJS4MW5?#wfd>yBpxy3p=unOO_t#ljxkJ5PXa1E13`5PpIUGuy+*zPHR0EiS^abe= zsuLAjtrkm5ODr$nq*gna8=U<;|MEO zbN829EG)dv^774i!P;v8q!H=I6wit6MPla+BE`uBm)MgZ`Jg%N--es}%)xBs}lS5{W2 z)oLs(yv?7jCabHfUmibx{8X%iURwT1IXwRDE8E(*7J(7NiQ~o(>-_jri`UM*$Mn=x zpKUjqm7Sd(!Z4&>uQNS8O{de@nVFfXoH%jfRp6eP^#&MFyv7;;)EQ?^&7rECT|C1~ zVVVDgn{gHk-a8&?|nLU7~nnw>hVbt1V$hb zn#J|ppCF+=3uF*Ll8)@+&N+%UzCHu$QT4Gh!HXjhL)8lNT&97n4iX@$TF64ih{Dtd zM8HsdjK(q(CbSn#mW?pGxb4RY&3j0EK#VbQr~?fR$WxgWV-bMCYDl$#m1!WvCkz;< zXu3t1j|4Rsc{UA$^be*X(m)vlp-;;YF}ym)G!`J5n>2|$0W~ZYgBqYrgx-8AIOjkx zLW$Lk?a6OZ2US!BxzsRQpH z4;U&$`-8!=XB`ZUXtZpE8ke`F5n3kP=oW>cxZwbvwwhSy_q?J_Xm4)u^{4Md?qJco z7(=piq!wogk$dN(_iS_Ni(fy;v|@XkQW2Srk59Az(LwkLa4!b%Bxy>s0II;@qT|!Q fboGi3r+*Cq<2s1^5P3HS00000NkvXXu0mjfV&`)s literal 0 HcmV?d00001 diff --git a/Odyssey/Demos/img/props16.png b/Odyssey/Demos/img/props16.png new file mode 100644 index 0000000000000000000000000000000000000000..8ea41731339e7663292f98e78cd97e6711b7b55b GIT binary patch literal 3411 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 literal 0 HcmV?d00001 diff --git a/Odyssey/Demos/img/search16.png b/Odyssey/Demos/img/search16.png new file mode 100644 index 0000000000000000000000000000000000000000..50a239003bccb9a82e0d0a7fd66437b5521b814e GIT binary patch literal 3514 zcmV;r4Mp;aP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=00004XF*Lt006O$eEU(80000WV@Og>004R=004l4008;_004mL004C` z008P>0026e000+nl3&F}0008jNkl$(+oCO{EkbbBjXR->LI-hWM2ph85*NinHwj5rEo~P@YC>x2kfvyp z$;6~@GIQsB+;^{wh|q>Y!2^f0IzP_;obxAZEuJOh`1m-O8OemRd*-)$pNAR;EGS9V zEbB;J-?_8$MZ4Wr{{^6li3vo3|Ka4!{VOJs<86K0+pgZ_lS30>MHM*)_E%@zW1K?ppd|a-_GE-mSIX01*54 zzj00RmePs$j!#RNL=eV)C)7<}$4y7_qn>%8pQbeI?CIOy zKW}3jdx2hLLY$JEOi65ZB%H29Qoh(1HR?UdG|>w1bQk zBp9$vuwYBBn>NfM8w3HQlZwFlN274%9akHv3x2SHx@tgO~s*V2K2;dac~TpR@5|z5cZjF50aa%MFEvhaOr@ z1K!bJuKKHscjspBzS8v80!PYFN?`ygS6ADG`h$BfbalDey?gh4cIM1!QfnF=6(I-= zi~*!EMi&c(Bena}U)iudxU#iJq|zCVd|%GY{QBt5?c3)zb#Hp}@Zm$J2FjJA!^4+8 zICx-iR6Nl_V`F2m*1{NrTCKLN(O7!R@Ay4l)0-mD$dSRp&dV=VaP{i7Z(n=;)pxgS z&g*LR{Fi;b<+J(x^Zx{Btq}wP!Z3vI`%p@uR4Sod?uYAUaO>80mkNdaCj$c&TsVJm z^v2EW$2R;X5rK#xgm`)?00@ErLqkKKDy2?OP2CAU`uP2$)#`;``2YLPB93F6Jb7w( oxv_F+Zm#w)U&!U?S?&CI01tdsHWM7dga7~l07*qoM6N<$g0O|Cq5uE@ literal 0 HcmV?d00001 diff --git a/Odyssey/Demos/img/search32.png b/Odyssey/Demos/img/search32.png new file mode 100644 index 0000000000000000000000000000000000000000..59b25e8aa6f3d26d3a958604bbfc642de3b10b9a GIT binary patch literal 4937 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 Hu0mjfJQz{h literal 0 HcmV?d00001 diff --git a/Odyssey/Demos/img/undo16.png b/Odyssey/Demos/img/undo16.png new file mode 100644 index 0000000000000000000000000000000000000000..ccf24caf8284c18e3bf282932fa7fa6362e2acbf GIT binary patch literal 3577 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 literal 0 HcmV?d00001 diff --git a/Odyssey/Demos/img/undo32.png b/Odyssey/Demos/img/undo32.png new file mode 100644 index 0000000000000000000000000000000000000000..fff1cd93aa1c66fe448caaad5a7763abd0d48a21 GIT binary patch literal 4676 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 + /// The breadcrumb for which to apply the properites. + /// + public BreadcrumbItem Breadcrumb { get; private set; } + + /// + /// The data item of the breadcrumb. + /// + public object Item { get; private set; } + + public ImageSource Image { get; set; } + + /// + /// The trace that is used to show the title of a breadcrumb. + /// + public object Trace { get; set; } + + /// + /// The trace that is used to build the path. + /// This can be used to remove the trace of the root item in the path, if necassary. + /// + public string TraceValue { get; set; } + } + + public delegate void ApplyPropertiesEventHandler(object sender, ApplyPropertiesEventArgs e); +} diff --git a/Odyssey/Odyssey/BreadcrumbBar/BreadcrumbBar.cs b/Odyssey/Odyssey/BreadcrumbBar/BreadcrumbBar.cs new file mode 100644 index 0000000..b91ec65 --- /dev/null +++ b/Odyssey/Odyssey/BreadcrumbBar/BreadcrumbBar.cs @@ -0,0 +1,1399 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.ComponentModel; +using System.Collections; +using System.Diagnostics; +using System.Collections.ObjectModel; +using System.Windows.Controls.Primitives; +using System.Windows.Markup; + +#region changelog +// Version 1.3.17: +// - preserved underscores for headers. +// reported by bigbigllama on http://www.codeproject.com/KB/tree/WPFBreadcrumbBar.aspx +// thanx for your help. +#endregion +#region Licence +// Copyright (c) 2008 Thomas Gerber +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#endregion +namespace Odyssey.Controls +{ + + ///TODO: Enable async popuplation on demand. + /// + /// A breadcrumb bar the contains breadcrumb items, a dropdown control, additional buttons and a progress bar. + /// + [ContentProperty("Root")] + [TemplatePart(Name = partComboBox)] + [TemplatePart(Name = partRoot)] + public class BreadcrumbBar : Control, IAddChild + { + private const string partComboBox = "PART_ComboBox"; + private const string partRoot = "PART_Root"; + + /// + /// Gets the number of the first breadcrumb to hide in the path if descending breadcrumbs are selected. + /// + int BreadcrumbsToHide + { + get { return HideRootNode ? 1 : 0; } + } + + + + /// + /// Gets or sets wether the root node is removed from the breadcrumb bar if any child node is selected. + /// This is a dependency property. + /// + public bool HideRootNode + { + get { return (bool)GetValue(HideRootNodeProperty); } + set { SetValue(HideRootNodeProperty, value); } + } + + // Using a DependencyProperty as the backing store for HideRootNode. This enables animation, styling, binding, etc... + public static readonly DependencyProperty HideRootNodeProperty = + DependencyProperty.Register("HideRootNode", typeof(bool), typeof(BreadcrumbBar), new UIPropertyMetadata(true)); + + + + #region Dependency Properties + + public static readonly DependencyProperty HasDropDownItemsProperty = + DependencyProperty.Register("HasDropDownItems", typeof(bool), typeof(BreadcrumbBar), new UIPropertyMetadata(false)); + + + public static readonly DependencyProperty DropDownItemsPanelProperty = + DependencyProperty.Register("DropDownItemsPanel", typeof(ItemsPanelTemplate), typeof(BreadcrumbBar), new UIPropertyMetadata(null)); + + private static readonly DependencyPropertyKey IsRootSelectedPropertyKey = + DependencyProperty.RegisterReadOnly("IsRootSelected", typeof(bool), typeof(BreadcrumbBar), new UIPropertyMetadata(true)); + + public static readonly DependencyProperty IsRootSelectedProperty = IsRootSelectedPropertyKey.DependencyProperty; + + public static readonly DependencyProperty DropDownItemTemplateProperty = + DependencyProperty.Register("DropDownItemTemplate", typeof(DataTemplate), typeof(BreadcrumbBar), new UIPropertyMetadata(null)); + + public static readonly DependencyProperty IsEditableProperty = + DependencyProperty.Register("IsEditable", typeof(bool), typeof(BreadcrumbBar), new UIPropertyMetadata(true)); + + public static readonly DependencyProperty DropDownItemTemplateSelectorProperty = + DependencyProperty.Register("DropDownItemTemplateSelector", typeof(DataTemplateSelector), typeof(BreadcrumbBar), new UIPropertyMetadata(null)); + + public static readonly DependencyProperty OverflowItemTemplateSelectorProperty = + DependencyProperty.Register("OverflowItemTemplateSelector", typeof(DataTemplateSelector), typeof(BreadcrumbBar), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.Inherits)); + + public static readonly DependencyProperty OverflowItemTemplateProperty = + DependencyProperty.Register("OverflowItemTemplate", typeof(DataTemplate), typeof(BreadcrumbBar), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.Inherits)); + + private static readonly DependencyPropertyKey CollapsedTracesPropertyKey = + DependencyProperty.RegisterReadOnly("CollapsedTraces", typeof(IEnumerable), typeof(BreadcrumbBar), new UIPropertyMetadata(null)); + public static readonly DependencyProperty CollapsedTracesProperty = CollapsedTracesPropertyKey.DependencyProperty; + + public static readonly DependencyProperty RootProperty = + DependencyProperty.Register("Root", typeof(object), typeof(BreadcrumbBar), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.AffectsMeasure | + FrameworkPropertyMetadataOptions.AffectsRender | + FrameworkPropertyMetadataOptions.AffectsArrange, + OnRootPropertyChanged)); + + public static readonly DependencyProperty SelectedItemProperty = + DependencyProperty.Register("SelectedItem", typeof(object), typeof(BreadcrumbBar), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.AffectsArrange | + FrameworkPropertyMetadataOptions.AffectsMeasure | + FrameworkPropertyMetadataOptions.AffectsMeasure, + OnSelectedItemPropertyChanged)); + + private static readonly DependencyPropertyKey SelectedBreadcrumbPropertyKey = + DependencyProperty.RegisterReadOnly("SelectedBreadcrumb", typeof(BreadcrumbItem), typeof(BreadcrumbBar), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsArrange, + OnSelectedBreadcrumbPropertyChanged)); + public static readonly DependencyProperty SelectedBreadcrumbProperty = SelectedBreadcrumbPropertyKey.DependencyProperty; + + public static readonly DependencyProperty IsOverflowPressedProperty = + DependencyProperty.Register("IsOverflowPressed", typeof(bool), typeof(BreadcrumbBar), new UIPropertyMetadata(false, OverflowPressedChanged)); + + private static readonly DependencyPropertyKey RootItemPropertyKey = + DependencyProperty.RegisterReadOnly("RootItem", typeof(BreadcrumbItem), typeof(BreadcrumbBar), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsMeasure, + OnRootItemPropertyChanged + )); + private static readonly DependencyProperty RootItemProperty = RootItemPropertyKey.DependencyProperty; + + private static void OnRootItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((BreadcrumbBar)d).OnRootItemChanged((BreadcrumbItem)e.OldValue, (BreadcrumbItem)e.NewValue); + } + + protected virtual void OnRootItemChanged(BreadcrumbItem oldValue, BreadcrumbItem newValue) + { + } + + public static readonly DependencyProperty BreadcrumbItemTemplateSelectorProperty = + DependencyProperty.Register("BreadcrumbItemTemplateSelector", typeof(DataTemplateSelector), typeof(BreadcrumbBar), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); + + public static readonly DependencyProperty BreadcrumbItemTemplateProperty = + DependencyProperty.Register("BreadcrumbItemTemplate", typeof(DataTemplate), typeof(BreadcrumbBar), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); + + private static readonly DependencyPropertyKey OverflowModePropertyKey = + DependencyProperty.RegisterReadOnly("OverflowMode", typeof(BreadcrumbButton.ButtonMode), typeof(BreadcrumbBar), + new FrameworkPropertyMetadata(BreadcrumbButton.ButtonMode.Overflow, + FrameworkPropertyMetadataOptions.AffectsRender)); + + public static readonly DependencyProperty OverflowModeProperty = OverflowModePropertyKey.DependencyProperty; + + public static readonly DependencyProperty IsDropDownOpenProperty = + DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(BreadcrumbBar), new UIPropertyMetadata(false, IsDropDownOpenChanged)); + + public static readonly DependencyProperty SeparatorStringProperty = + DependencyProperty.Register("SeparatorString", typeof(string), typeof(BreadcrumbBar), new UIPropertyMetadata("\\")); + + public static readonly DependencyProperty PathProperty = + DependencyProperty.Register("Path", typeof(string), typeof(BreadcrumbBar), new FrameworkPropertyMetadata("",FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PathPropertyChanged)); + + public static readonly DependencyProperty DropDownItemsSourceProperty = + DependencyProperty.Register("DropDownItemsSource", typeof(IEnumerable), typeof(BreadcrumbBar), new UIPropertyMetadata(null, OnDropDownItemsSourcePropertyChanged)); + + + public static readonly DependencyProperty SelectedDropDownIndexProperty = + DependencyProperty.Register("SelectedDropDownIndex", typeof(int), typeof(BreadcrumbBar), new UIPropertyMetadata(-1)); + + public static readonly DependencyProperty ProgressValueProperty = + DependencyProperty.Register("ProgressValue", typeof(double), typeof(BreadcrumbBar), + new UIPropertyMetadata((double)0.0, ProgressValuePropertyChanged, CoerceProgressValue)); + + public static readonly DependencyProperty ProgressMaximumProperty = + DependencyProperty.Register("ProgressMaximum", typeof(double), typeof(BreadcrumbBar), new UIPropertyMetadata(100.0, null, CoerceProgressMaximum)); + + public static readonly DependencyProperty ProgressMinimumProperty = + DependencyProperty.Register("ProgressMinimum", typeof(double), typeof(BreadcrumbBar), new UIPropertyMetadata(0.0, null, CoerceProgressMinimum)); + + #endregion + + #region RoutedEvents + + public static readonly RoutedEvent BreadcrumbItemDropDownOpenedEvent = EventManager.RegisterRoutedEvent("BreadcrumbItemDropDownOpened", + RoutingStrategy.Bubble, typeof(BreadcrumbItemEventHandler), typeof(BreadcrumbBar)); + + public static readonly RoutedEvent BreadcrumbItemDropDownClosedEvent = EventManager.RegisterRoutedEvent("BreadcrumbItemDropDownClosed", + RoutingStrategy.Bubble, typeof(BreadcrumbItemEventHandler), typeof(BreadcrumbBar)); + + public static readonly RoutedEvent ProgressValueChangedEvent = EventManager.RegisterRoutedEvent("ProgressValueChanged", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(BreadcrumbBar)); + + public static readonly RoutedEvent ApplyPropertiesEvent = EventManager.RegisterRoutedEvent("ApplyProperties", + RoutingStrategy.Bubble, typeof(ApplyPropertiesEventHandler), typeof(BreadcrumbBar)); + + + public static readonly RoutedEvent SelectedBreadcrumbChangedEvent = EventManager.RegisterRoutedEvent("SelectedBreadcrumbChanged", + RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler), typeof(BreadcrumbBar)); + + public static readonly RoutedEvent PathChangedEvent = EventManager.RegisterRoutedEvent("PathChanged", + RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler), typeof(BreadcrumbBar)); + + /// + /// Occurs before acessing the Items property of a BreadcrumbItem. This event can be used to populate the Items on demand. + /// + public static readonly RoutedEvent PopulateItemsEvent = EventManager.RegisterRoutedEvent("PopulateItems", + RoutingStrategy.Bubble, typeof(BreadcrumbItemEventHandler), typeof(BreadcrumbBar)); + + /// + /// Occurs when a path needs to be converted between display path and edit path. + /// + public static readonly RoutedEvent PathConversionEvent = EventManager.RegisterRoutedEvent("PathConversion", + RoutingStrategy.Bubble, typeof(PathConversionEventHandler), typeof(BreadcrumbBar)); + + #endregion + + /// + /// This command shows the drop down part of the combobox. + /// + public static RoutedUICommand ShowDropDownCommand + { + get { return showDropDownCommand; } + } + + private static RoutedUICommand showDropDownCommand = new RoutedUICommand("Show DropDown", "ShowDropDownCommand", typeof(BreadcrumbBar)); + + /// + /// This command selects the BreadcrumbItem that is specified as Parameter. + /// + public static RoutedUICommand SelectTraceCommand + { + get { return selectTraceCommand; } + } + + /// + /// This command selects the root. + /// + public static RoutedUICommand SelectRootCommand + { + get { return selectRootCommand; } + } + + private static RoutedUICommand selectRootCommand = new RoutedUICommand("Select", "SelectRootCommand", typeof(BreadcrumbBar)); + private static RoutedUICommand selectTraceCommand = new RoutedUICommand("Select", "SelectTraceCommand", typeof(BreadcrumbBar)); + + + static BreadcrumbBar() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(BreadcrumbBar), new FrameworkPropertyMetadata(typeof(BreadcrumbBar))); + BorderThicknessProperty.OverrideMetadata(typeof(BreadcrumbBar), new FrameworkPropertyMetadata(new Thickness(1))); + BorderBrushProperty.OverrideMetadata(typeof(BreadcrumbBar), new FrameworkPropertyMetadata(Brushes.Black)); + Color c = new Color(); + c.R = 245; c.G = 245; c.B = 245; c.A = 255; + BackgroundProperty.OverrideMetadata(typeof(BreadcrumbBar), new FrameworkPropertyMetadata(new SolidColorBrush(c))); + + CommandManager.RegisterClassCommandBinding(typeof(FrameworkElement), new CommandBinding(selectRootCommand, SelectRootCommandExecuted)); + CommandManager.RegisterClassCommandBinding(typeof(FrameworkElement), new CommandBinding(selectTraceCommand, SelectTraceCommandExecuted)); + CommandManager.RegisterClassCommandBinding(typeof(FrameworkElement), new CommandBinding(showDropDownCommand, ShowDropDownExecuted)); + } + + // A helper class to store the DropDownItems since ItemCollection has no public creator: + private ItemsControl comboBoxControlItems; + + /// + /// Creates a new BreadcrumbBar. + /// + public BreadcrumbBar() + : base() + { + comboBoxControlItems = new ItemsControl(); + Binding b = new Binding("HasItems"); + b.Source = comboBoxControlItems; + this.SetBinding(BreadcrumbBar.HasDropDownItemsProperty, b); + + traces = new ObservableCollection(); + CollapsedTraces = traces; + AddHandler(BreadcrumbItem.SelectionChangedEvent, new RoutedEventHandler(breadcrumbItemSelectedItemChanged)); + AddHandler(BreadcrumbItem.TraceChangedEvent, new RoutedEventHandler(breadcrumbItemTraceValueChanged)); + AddHandler(BreadcrumbItem.SelectionChangedEvent, new RoutedEventHandler(breadcrumbItemSelectionChangedEvent)); + AddHandler(BreadcrumbItem.DropDownPressedChangedEvent, new RoutedEventHandler(breadcrumbItemDropDownChangedEvent)); + AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(buttonClickedEvent)); + traces.Add(null); + + InputBindings.Add(new KeyBinding(BreadcrumbBar.ShowDropDownCommand, new KeyGesture(Key.Down, ModifierKeys.Alt))); + } + + static void IsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BreadcrumbBar bar = d as BreadcrumbBar; + + bar.OnDropDownOpenChanged((bool)e.OldValue, (bool)e.NewValue); + } + + static void OnSelectedBreadcrumbPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BreadcrumbBar bar = d as BreadcrumbBar; + BreadcrumbItem selected = e.NewValue as BreadcrumbItem; + bar.IsRootSelected = selected == bar.RootItem; + if (bar.IsInitialized) + { + RoutedPropertyChangedEventArgs args = new RoutedPropertyChangedEventArgs(e.OldValue as BreadcrumbItem, e.NewValue as BreadcrumbItem, BreadcrumbBar.SelectedBreadcrumbChangedEvent); + bar.RaiseEvent(args); + } + } + + + /// + /// Occurs after a BreadcrumbItem is created for which to apply additional properties. + /// + public event ApplyPropertiesEventHandler ApplyProperties + { + add { AddHandler(BreadcrumbBar.ApplyPropertiesEvent, value); } + remove { RemoveHandler(BreadcrumbBar.ApplyPropertiesEvent, value); } + } + + /// + /// Occurs when the selected BreadcrumbItem is changed. + /// + public event RoutedPropertyChangedEventHandler SelectedBreadcrumbChanged + { + add { AddHandler(BreadcrumbBar.SelectedBreadcrumbChangedEvent, value); } + remove { RemoveHandler(BreadcrumbBar.SelectedBreadcrumbChangedEvent, value); } + } + + protected virtual void OnSelectedBreadcrumbChanged(DependencyPropertyChangedEventArgs e) + { + if (SelectedBreadcrumb != null) SelectedBreadcrumb.SelectedItem = null; + } + + /// + /// On initializing, it is possible that the Path property is set before the RootItem property, thus the declarative xaml Path would be overwritten by settings the + /// RootItem property later. To avoid this affect, setting the Path also sets initPath on initializing and after initializing, the Path is restored by this value: + /// + private string initPath; + + static void PathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BreadcrumbBar bar = d as BreadcrumbBar; + string newPath = e.NewValue as string; + // bar.PathBinding = newPath; + + if (!bar.IsInitialized) + { + bar.Path = bar.initPath = newPath; + } + else + { + bar.BuildBreadcrumbsFromPath(newPath); + bar.OnPathChanged(e.OldValue as string, newPath); + } + } + + /// + /// Occurs when the Path property is changed. + /// + public event RoutedPropertyChangedEventHandler PathChanged + { + add { AddHandler(PathChangedEvent, value); } + remove { RemoveHandler(PathChangedEvent, value); } + } + + /// + /// Occurs when the Path property is changed. + /// + protected virtual void OnPathChanged(string oldValue, string newValue) + { + BuildBreadcrumbsFromPath(newValue); + if (IsLoaded) + { + RoutedPropertyChangedEventArgs args = new RoutedPropertyChangedEventArgs(oldValue, newValue, PathChangedEvent); + RaiseEvent(args); + } + } + + + /// + /// Traces the specified path and builds the associated BreadcrumbItems. + /// + /// The traces separated by the SepearatorString property. + private bool BuildBreadcrumbsFromPath(string newPath) + { + PathConversionEventArgs e = new PathConversionEventArgs(PathConversionEventArgs.ConversionMode.EditToDisplay, newPath, Root, PathConversionEvent); + RaiseEvent(e); + newPath = e.DisplayPath; + + BreadcrumbItem item = RootItem; + if (item == null) + { + this.Path = null; + return false; + } + + + newPath = RemoveLastEmptySeparator(newPath); + string[] traces = newPath.Split(new string[] { SeparatorString }, StringSplitOptions.RemoveEmptyEntries); + if (traces.Length == 0) RootItem.SelectedItem = null; + int index = 0; + + List itemIndex = new List(); + + // if the root is specified as first trace, then skip: + int length = traces.Length; + int max = BreadcrumbsToHide; + if (traces.Length>index && max > 0 && traces[index] == (RootItem.TraceValue)) + { + length--; + index++; + max--; + } + + for (int i = index; i < traces.Length; i++) + { + if (item == null) break; + + string trace = traces[i]; + OnPopulateItems(item); + object next = item.GetTraceItem(trace); + if (next == null) break; + itemIndex.Add(item.Items.IndexOf(next)); + BreadcrumbItem container = item.ContainerFromItem(next); + + item = container; + } + if (length != itemIndex.Count) + { + //recover the last path: + Path = GetDisplayPath(); + return false; + } + + // temporarily remove the SelectionChangedEvent handler to minimize processing of events while building the breadcrumb items: + RemoveHandler(BreadcrumbItem.SelectionChangedEvent, new RoutedEventHandler(breadcrumbItemSelectedItemChanged)); + try + { + item = RootItem; + for (int i = 0; i < itemIndex.Count; i++) + { + if (item == null) break; + item.SelectedIndex = itemIndex[i]; + item = item.SelectedBreadcrumb; + } + if (item != null) item.SelectedItem = null; + SelectedBreadcrumb = item; + SelectedItem = item != null ? item.Data : null; + } + finally + { + AddHandler(BreadcrumbItem.SelectionChangedEvent, new RoutedEventHandler(breadcrumbItemSelectedItemChanged)); + } + + return true; + } + + + /// + /// Remove the last separator string from the path if there is no additional trace. + /// + /// The path from which to remove the last separator. + /// The path without an unecassary last separator. + private string RemoveLastEmptySeparator(string path) + { + path = path.Trim(); + int sepLength = SeparatorString.Length; + if (path.EndsWith(SeparatorString)) + { + path = path.Remove(path.Length - sepLength, sepLength); + } + return path; + } + + static void OnDropDownItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BreadcrumbBar bar = d as BreadcrumbBar; + + bar.comboBoxControlItems.ItemsSource = e.NewValue as IEnumerable; + } + + /// + /// Occurs when the IsDropDownOpen property is changed. + /// + /// + /// + protected virtual void OnDropDownOpenChanged(bool oldValue, bool newValue) + { + if (comboBox != null && newValue) + { + SetInputState(); + if (IsEditable) + { + comboBox.Visibility = Visibility.Visible; + comboBox.IsDropDownOpen = true; + } + } + } + + private static void SelectRootCommandExecuted(object sender, ExecutedRoutedEventArgs e) + { + BreadcrumbItem item = e.Parameter as BreadcrumbItem; + if (item != null) + { + item.SelectedItem = null; + } + } + + private static void SelectTraceCommandExecuted(object sender, ExecutedRoutedEventArgs e) + { + BreadcrumbItem item = e.Parameter as BreadcrumbItem; + if (item != null) + { + item.SelectedItem = null; + } + } + + private static void ShowDropDownExecuted(object sender, ExecutedRoutedEventArgs e) + { + BreadcrumbBar bar = sender as BreadcrumbBar; + if (bar.IsEditable && bar.DropDownItems.Count > 0) bar.IsDropDownOpen = true; + } + + private void breadcrumbItemSelectedItemChanged(object sender, RoutedEventArgs e) + { + BreadcrumbItem breadcrumb = e.OriginalSource as BreadcrumbItem; + if (breadcrumb != null && breadcrumb.SelectedBreadcrumb != null) breadcrumb = breadcrumb.SelectedBreadcrumb; + SelectedBreadcrumb = breadcrumb; + + if (SelectedBreadcrumb != null) + { + SelectedItem = SelectedBreadcrumb.Data; + } + Path = GetEditPath(); + } + + private void breadcrumbItemTraceValueChanged(object sender, RoutedEventArgs e) + { + if (e.OriginalSource == RootItem) + { + //TODO: This causes Path binding not to work (see PasswordSafe): + //Path = GetEditPath(); + } + } + + private void breadcrumbItemSelectionChangedEvent(object sender, RoutedEventArgs e) + { + BreadcrumbItem parent = e.Source as BreadcrumbItem; + if (parent != null && parent.SelectedBreadcrumb != null) + { + OnPopulateItems(parent.SelectedBreadcrumb); + } + } + + + private void breadcrumbItemDropDownChangedEvent(object sender, RoutedEventArgs e) + { + BreadcrumbItem breadcrumb = e.Source as BreadcrumbItem; + if (breadcrumb.IsDropDownPressed) + { + OnBreadcrumbItemDropDownOpened(e); + } + else + { + OnBreadcrumbItemDropDownClosed(e); + } + } + + /// + /// Remove the focus from a button when it was clicked. + /// + /// The sender. + /// The instance containing the event data. + private void buttonClickedEvent(object sender, RoutedEventArgs e) + { + if (!this.IsDropDownOpen) + { + // focus only if it has aleady focus and the button that was pressed is not the drop down button of the combobox: + if (this.IsKeyboardFocusWithin && (e.OriginalSource is BreadcrumbButton)) + { + this.Focus(); + } + } + } + + /// + /// Occurs before acessing the Items property of a BreadcrumbItem. This event can be used to populate the Items on demand. + /// + public event BreadcrumbItemEventHandler PopulateItems + { + add { AddHandler(BreadcrumbBar.PopulateItemsEvent, value); } + remove { RemoveHandler(BreadcrumbBar.PopulateItemsEvent, value); } + } + + /// + /// Occurs when a path needs to be converted between display path and edit path. + /// + public event PathConversionEventHandler PathConversion + { + add { AddHandler(BreadcrumbBar.PathConversionEvent, value); } + remove { RemoveHandler(BreadcrumbBar.PathConversionEvent, value); } + } + + /// + /// Occurs before acessing the Items property of a BreadcrumbItem. This event can be used to populate the Items on demand. + /// + protected virtual void OnPopulateItems(BreadcrumbItem item) + { + BreadcrumbItemEventArgs args = new BreadcrumbItemEventArgs(item, BreadcrumbBar.PopulateItemsEvent); + RaiseEvent(args); + } + + /// + /// Occurs when the dropdown of a BreadcrumbItem is opened. + /// + public event BreadcrumbItemEventHandler BreadcrumbItemDropDownOpened + { + add { AddHandler(BreadcrumbBar.BreadcrumbItemDropDownOpenedEvent, value); } + remove { RemoveHandler(BreadcrumbBar.BreadcrumbItemDropDownOpenedEvent, value); } + } + + /// + /// Occurs when the dropdown of a BreadcrumbItem is closed. + /// + public event BreadcrumbItemEventHandler BreadcrumbItemDropDownClosed + { + add { AddHandler(BreadcrumbBar.BreadcrumbItemDropDownClosedEvent, value); } + remove { RemoveHandler(BreadcrumbBar.BreadcrumbItemDropDownClosedEvent, value); } + } + + /// + /// Occurs when the dropdown of a BreadcrumbItem is opened. + /// + protected virtual void OnBreadcrumbItemDropDownOpened(RoutedEventArgs e) + { + BreadcrumbItemEventArgs args = new BreadcrumbItemEventArgs(e.Source as BreadcrumbItem, BreadcrumbItemDropDownOpenedEvent); + RaiseEvent(args); + } + + /// + /// Occurs when the dropdown of a BreadcrumbItem is closed. + /// + protected virtual void OnBreadcrumbItemDropDownClosed(RoutedEventArgs e) + { + BreadcrumbItemEventArgs args = new BreadcrumbItemEventArgs(e.Source as BreadcrumbItem, BreadcrumbItemDropDownClosedEvent); + RaiseEvent(args); + } + + private ObservableCollection traces; + + protected override Size ArrangeOverride(Size arrangeBounds) + { + Size size = base.ArrangeOverride(arrangeBounds); + CheckOverflowImage(); + + return size; + } + + /// + /// Gets whether the selected breadcrumb is the RootItem. + /// + public bool IsRootSelected + { + get { return (bool)GetValue(IsRootSelectedProperty); } + private set { SetValue(IsRootSelectedPropertyKey, value); } + } + + + /// + /// Check what image to display in the drop down button of the overflow button: + /// + private void CheckOverflowImage() + { + bool isOverflow = (RootItem != null && RootItem.SelectedBreadcrumb != null && RootItem.SelectedBreadcrumb.IsOverflow); + OverflowMode = isOverflow ? BreadcrumbButton.ButtonMode.Overflow : BreadcrumbButton.ButtonMode.Breadcrumb; + } + + /// + /// Build the list of traces for the overflow button. + /// + private void BuildTraces() + { + BreadcrumbItem item = RootItem; + + traces.Clear(); + if (item != null && item.IsOverflow) + { + foreach (object trace in item.Items) + { + MenuItem menuItem = new RibbonMenuItem(); + menuItem.Tag = trace; + BreadcrumbItem bcItem = item.ContainerFromItem(trace); + + menuItem.Click += new RoutedEventHandler(menuItem_Click); + menuItem.Icon = GetImage(bcItem != null ? bcItem.Image : null); + if (trace == RootItem.SelectedItem) menuItem.FontWeight = FontWeights.Bold; + traces.Add(menuItem); + if (bcItem != null) menuItem.Header = bcItem.TraceValue; + } + traces.Insert(0, new Separator()); + MenuItem rootMenuItem = new RibbonMenuItem(); + rootMenuItem.Header = item.TraceValue; + rootMenuItem.Command = BreadcrumbBar.SelectRootCommand; + rootMenuItem.CommandParameter = item; + rootMenuItem.Icon = GetImage(item.Image); + traces.Insert(0, rootMenuItem); + } + + item = item != null ? item.SelectedBreadcrumb : null; + + while (item != null) + { + if (!item.IsOverflow) break; + MenuItem traceMenuItem = new MenuItem(); + traceMenuItem.Header = item.TraceValue; + traceMenuItem.Command = BreadcrumbBar.SelectRootCommand; + traceMenuItem.CommandParameter = item; + traceMenuItem.Icon = GetImage(item.Image); + traces.Insert(0, traceMenuItem); + item = item.SelectedBreadcrumb; + } + } + + private object GetImage(ImageSource imageSource) + { + if (imageSource == null) return null; + Image image = new Image(); + image.Source = imageSource; + image.Stretch = Stretch.Fill; + image.SnapsToDevicePixels = true; + image.Width = image.Height = 16; + + return image; + } + + void menuItem_Click(object sender, RoutedEventArgs e) + { + MenuItem item = e.Source as MenuItem; + if (RootItem != null && item != null) + { + object dataItem = item.Tag; + if (dataItem != null && dataItem.Equals(RootItem.SelectedItem)) RootItem.SelectedItem = null; + RootItem.SelectedItem = dataItem; + } + } + + + /// + /// Gets or sets the DataTemplateSelector for the overflow items. + /// + public DataTemplateSelector OverflowItemTemplateSelector + { + get { return (DataTemplateSelector)GetValue(OverflowItemTemplateSelectorProperty); } + set { SetValue(OverflowItemTemplateSelectorProperty, value); } + } + + /// + /// Gets or set the DataTemplate for the OverflowItem. + /// + public DataTemplate OverflowItemTemplate + { + get { return (DataTemplate)GetValue(OverflowItemTemplateProperty); } + set { SetValue(OverflowItemTemplateProperty, value); } + } + + + /// + /// Gets the collapsed traces. + /// + public IEnumerable CollapsedTraces + { + get { return (IEnumerable)GetValue(CollapsedTracesProperty); } + private set { SetValue(CollapsedTracesPropertyKey, value); } + } + + /// + /// Gets or sets the root of the breadcrumb which can be a hierarchical data source or a BreadcrumbItem. + /// + public object Root + { + get { return (object)GetValue(RootProperty); } + set { SetValue(RootProperty, value); } + } + + private static void OnRootPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BreadcrumbBar bar = d as BreadcrumbBar; + bar.OnRootChanged(d, e.OldValue, e.NewValue); + } + + private static void OnSelectedItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BreadcrumbBar bar = d as BreadcrumbBar; + bar.OnSelectedItemChanged(d, e.OldValue, e.NewValue); + } + + /// + /// Occurs when the selected item of an embedded BreadcrumbItem is changed. + /// + /// + /// + protected virtual void OnSelectedItemChanged(object sender, object oldvalue, object newValue) + { + } + + /// + /// Occurs when the Root property is changed. + /// + /// + /// + protected virtual void OnRootChanged(object sender, object oldValue, object newValue) + { + newValue = GetFirstItem(newValue); + BreadcrumbItem oldRoot = oldValue as BreadcrumbItem; + if (oldRoot != null) + { + oldRoot.IsRoot = false; + } + + if (newValue == null) + { + RootItem = null; + Path = null; + } + else + { + BreadcrumbItem root = newValue as BreadcrumbItem; + if (root == null) + { + root = BreadcrumbItem.CreateItem(newValue); + } + if (root != null) + { + root.IsRoot = true; + } + this.RemoveLogicalChild(oldValue); + RootItem = root; + if (root != null) + { + if (LogicalTreeHelper.GetParent(root) == null) this.AddLogicalChild(root); + } + SelectedItem = root != null ? root.DataContext : null; + if (IsInitialized) SelectedBreadcrumb = root; else selectedBreadcrumb = root; + } + } + + /// + /// Gets the first item of the specified value if it is a collection, otherwise it returns the value itself. + /// + /// A collection, otherwise an object. + /// The first item of the collection, otherwise the entity. + private object GetFirstItem(object entity) + { + ICollection c = entity as ICollection; + if (c != null) + { + foreach (object item in c) + { + return item; + } + } + return entity; + } + + + /// + /// Gets or sets the selected item. + /// + public object SelectedItem + { + get { return (object)GetValue(SelectedItemProperty); } + private set { SetValue(SelectedItemProperty, value); } + } + + private BreadcrumbItem selectedBreadcrumb; + + /// + /// Gets the selected BreadcrumbItem + /// + public BreadcrumbItem SelectedBreadcrumb + { + get { return (BreadcrumbItem)GetValue(SelectedBreadcrumbProperty); } + private set + { + selectedBreadcrumb = value; + SetValue(SelectedBreadcrumbPropertyKey, value); + } + } + + + /// + /// Gets whether the Overflow button is pressed. + /// + public bool IsOverflowPressed + { + get { return (bool)GetValue(IsOverflowPressedProperty); } + private set { SetValue(IsOverflowPressedProperty, value); } + } + + private static void OverflowPressedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BreadcrumbBar bar = d as BreadcrumbBar; + bar.OnOverflowPressedChanged(); + } + + /// + /// Occurs when the IsOverflowPressed property is changed. + /// + protected virtual void OnOverflowPressedChanged() + { + // rebuild the list of tracess to show in the popup of the overflow button: + if (IsOverflowPressed) BuildTraces(); + } + + + + /// + /// Gets the Root BreadcrumbItem. + /// + public BreadcrumbItem RootItem + { + get { return (BreadcrumbItem)GetValue(RootItemProperty); } + protected set + { + SetValue(RootItemPropertyKey, value); + } + } + + /// + /// Gets or sets the TemplateSelector for an embedded BreadcrumbItem. + /// + public DataTemplateSelector BreadcrumbItemTemplateSelector + { + get { return (DataTemplateSelector)GetValue(BreadcrumbItemTemplateSelectorProperty); } + set { SetValue(BreadcrumbItemTemplateSelectorProperty, value); } + } + + + /// + /// Gets or sets the Template for an embedded BreadcrumbItem. + /// + public DataTemplate BreadcrumbItemTemplate + { + get { return (DataTemplate)GetValue(BreadcrumbItemTemplateProperty); } + set { SetValue(BreadcrumbItemTemplateProperty, value); } + } + + + /// + /// Gets the overflow mode for the Overflow BreadcrumbButton (PART_Root). + /// + public BreadcrumbButton.ButtonMode OverflowMode + { + get { return (BreadcrumbButton.ButtonMode)GetValue(OverflowModeProperty); } + private set { SetValue(OverflowModePropertyKey, value); } + } + + private ComboBox comboBox; + private BreadcrumbButton rootButton; + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + comboBox = GetTemplateChild(partComboBox) as ComboBox; + rootButton = GetTemplateChild(partRoot) as BreadcrumbButton; + if (comboBox != null) + { + comboBox.DropDownClosed += new EventHandler(comboBox_DropDownClosed); + comboBox.IsKeyboardFocusWithinChanged += new DependencyPropertyChangedEventHandler(comboBox_IsKeyboardFocusWithinChanged); + comboBox.KeyDown += new KeyEventHandler(comboBox_KeyDown); + } + if (rootButton != null) + { + rootButton.Click += new RoutedEventHandler(rootButton_Click); + } + } + + + protected override void OnInitialized(EventArgs e) + { + base.OnInitialized(e); + if (initPath != null) + { + initPath = null; + BuildBreadcrumbsFromPath(Path); + } + } + + void comboBox_KeyDown(object sender, KeyEventArgs e) + { + switch (e.Key) + { + case Key.Escape: Exit(false); break; + case Key.Enter: Exit(true); break; + default: return; + } + e.Handled = true; + } + + void comboBox_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e) + { + bool isKeyboardFocusWithin = (bool)e.NewValue; + if (!isKeyboardFocusWithin) Exit(true); + } + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + if (e.Handled) return; + if (e.ChangedButton == MouseButton.Left && e.LeftButton == MouseButtonState.Pressed) + { + e.Handled = true; + SetInputState(); + } + base.OnMouseDown(e); + + } + + void rootButton_Click(object sender, RoutedEventArgs e) + { + SetInputState(); + } + + private void SetInputState() + { + if (comboBox != null && IsEditable) + { + comboBox.Text = Path; + comboBox.Visibility = Visibility.Visible; + comboBox.Focus(); + } + } + + /// + /// Gets the edit path from the tracess of the BreacrumbItems. + /// + /// + public string GetEditPath() + { + string displayPath = GetDisplayPath(); + PathConversionEventArgs e = new PathConversionEventArgs(PathConversionEventArgs.ConversionMode.DisplayToEdit, displayPath, Root, PathConversionEvent); + RaiseEvent(e); + return e.EditPath; + } + + /// + /// Gets the path of the specified BreadcrumbItem. + /// + /// The BreadrumbItem for which to determine the path. + /// The path of the BreadcrumbItem which is the concenation of all Traces from all selected breadcrumbs. + public string PathFromBreadcrumbItem(BreadcrumbItem item) + { + StringBuilder sb = new StringBuilder(); + while (item != null) + { + if (item == RootItem && sb.Length > 0) break; + if (sb.Length > 0) sb.Insert(0, SeparatorString); + sb.Insert(0, item.TraceValue); + item = item.ParentBreadcrumbItem; + } + PathConversionEventArgs e = new PathConversionEventArgs(PathConversionEventArgs.ConversionMode.DisplayToEdit, sb.ToString(), Root, PathConversionEvent); + RaiseEvent(e); + return e.EditPath; + } + + /// + /// Gets the display path from the traces of the BreacrumbItems. + /// + /// + public string GetDisplayPath() + { + string separator = SeparatorString; + StringBuilder sb = new StringBuilder(); + BreadcrumbItem item = RootItem; + int index = 0; + while (item != null) + { + if (sb.Length > 0) sb.Append(separator); + if (index >= BreadcrumbsToHide || item.SelectedItem == null) + { + sb.Append(item.GetTracePathValue()); + } + index++; + item = item.SelectedBreadcrumb; + } + + return sb.ToString(); + } + + + /// + /// Do what's necassary to do when the BreadcrumbBar has lost focus. + /// + private void Exit(bool updatePath) + { + if (comboBox != null) + { + if (updatePath && comboBox.IsVisible) Path = comboBox.Text; + comboBox.Visibility = Visibility.Hidden; + } + } + + void comboBox_DropDownClosed(object sender, EventArgs e) + { + IsDropDownOpen = false; + Path = comboBox.Text; + } + + /// + /// Gets or sets the DataSource for the DropDownItems of the combobox. + /// + public IEnumerable DropDownItemsSource + { + get { return (IEnumerable)GetValue(DropDownItemsSourceProperty); } + set { SetValue(DropDownItemsSourceProperty, value); } + } + + + /// + /// Gets or sets whether the combobox dropdown is opened. + /// + public bool IsDropDownOpen + { + get { return (bool)GetValue(IsDropDownOpenProperty); } + set { SetValue(IsDropDownOpenProperty, value); } + } + + + /// + /// Gets or sets the string that is used to separate between traces. + /// + public string SeparatorString + { + get { return (string)GetValue(SeparatorStringProperty); } + set { SetValue(SeparatorStringProperty, value); } + } + + + + public string PathBinding + { + get { return (string)GetValue(PathBindingProperty); } + set { SetValue(PathBindingProperty, value); } + } + + + + + public static readonly DependencyProperty PathBindingProperty = + DependencyProperty.Register("PathBinding", typeof(string), typeof(BreadcrumbBar), new UIPropertyMetadata("", OnPathBindingPropertyChanged)); + + private static void OnPathBindingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + d.SetValue(PathProperty, e.NewValue); + } + + + /// + /// Gets or sets the selected path. + /// + public string Path + { + get { return (string)GetValue(PathProperty); } + set { SetValue(PathProperty, value); } + } + + private ObservableCollection buttons = new ObservableCollection(); + + /// + /// Gets the collection of buttons to appear on the right of the breadcrumb bar. + /// + public ObservableCollection Buttons + { + get { return buttons; } + } + + + /// + /// Gets or sets the DropDownItems for the combobox. + /// + public ItemCollection DropDownItems + { + get { return comboBoxControlItems.Items; } + } + + + /// + /// Gets whether the dropdown has items. + /// + public bool HasDropDownItems + { + get { return (bool)GetValue(HasDropDownItemsProperty); } + private set { SetValue(HasDropDownItemsProperty, value); } + } + + + /// + /// Gets or sets the ItemsPanelTemplate for the DropDownItems of the combobox. + /// + public ItemsPanelTemplate DropDownItemsPanel + { + get { return (ItemsPanelTemplate)GetValue(DropDownItemsPanelProperty); } + set { SetValue(DropDownItemsPanelProperty, value); } + } + + + /// + /// Gets or sets the ItemsPanelTemplateSelector for the DropDownItems of the combobox. + /// + public DataTemplateSelector DropDownItemTemplateSelector + { + get { return (DataTemplateSelector)GetValue(DropDownItemTemplateSelectorProperty); } + set { SetValue(DropDownItemTemplateSelectorProperty, value); } + } + + + /// + /// Gets or sets the DataTemplate for the DropDownItems of the combobox. + /// + public DataTemplate DropDownItemTemplate + { + get { return (DataTemplate)GetValue(DropDownItemTemplateProperty); } + set { SetValue(DropDownItemTemplateProperty, value); } + } + + + /// + /// Gets or sets whether the breadcrumb bar can change to edit mode where the path can be edited. + /// + public bool IsEditable + { + get { return (bool)GetValue(IsEditableProperty); } + set { SetValue(IsEditableProperty, value); } + } + + + /// + /// Gets or sets the SelectedIndex of the combobox. + /// + public int SelectedDropDownIndex + { + get { return (int)GetValue(SelectedDropDownIndexProperty); } + set { SetValue(SelectedDropDownIndexProperty, value); } + } + + + /// + /// Gets or sets the current progress indicator value. + /// + public double ProgressValue + { + get { return (double)GetValue(ProgressValueProperty); } + set { SetValue(ProgressValueProperty, value); } + } + + + /// + /// Check the desired value for ProgressValue and asure that it is between Minimum and Maximum: + /// + /// + /// + /// The value between mimimum and maximum. + static object CoerceProgressValue(DependencyObject d, object baseValue) + { + BreadcrumbBar bar = d as BreadcrumbBar; + double value = (double)baseValue; + if (value > bar.ProgressMaximum) value = bar.ProgressMaximum; + if (value < bar.ProgressMimimum) value = bar.ProgressMimimum; + + return value; + } + + /// + /// Occurs when the ProgressValue is changed. + /// + public event RoutedEventHandler ProgressValueChanged + { + add { AddHandler(BreadcrumbBar.ProgressValueChangedEvent, value); } + remove { RemoveHandler(BreadcrumbBar.ProgressValueChangedEvent, value); } + } + + static void ProgressValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + // RoutedPropertyChangedEventArgs args = new RoutedPropertyChangedEventArgs((double)e.OldValue, (double)e.NewValue,BreadcrumbBar.ProgessValueChangedEvent); + RoutedEventArgs args = new RoutedEventArgs(BreadcrumbBar.ProgressValueChangedEvent); + BreadcrumbBar bar = d as BreadcrumbBar; + bar.RaiseEvent(args); + } + + protected override void OnMouseLeave(MouseEventArgs e) + { + // if (this.IsKeyboardFocusWithin) this.Focus(); + base.OnMouseLeave(e); + } + + static object CoerceProgressMaximum(DependencyObject d, object baseValue) + { + BreadcrumbBar bar = d as BreadcrumbBar; + double value = (double)baseValue; + if (value < bar.ProgressMimimum) value = bar.ProgressMimimum; + if (value < bar.ProgressValue) bar.ProgressValue = value; + if (value < 0) value = 0; + + return value; + } + + + static object CoerceProgressMinimum(DependencyObject d, object baseValue) + { + BreadcrumbBar bar = d as BreadcrumbBar; + double value = (double)baseValue; + if (value > bar.ProgressMaximum) value = bar.ProgressMaximum; + if (value > bar.ProgressValue) bar.ProgressValue = value; + + return value; + } + + /// + /// Gets or sets the maximum progress value. + /// + public double ProgressMaximum + { + get { return (double)GetValue(ProgressMaximumProperty); } + set { SetValue(ProgressMaximumProperty, value); } + } + + + /// + /// Gets or sets the minimum progess value. + /// + public double ProgressMimimum + { + get { return (double)GetValue(ProgressMinimumProperty); } + set { SetValue(ProgressMinimumProperty, value); } + } + + + protected override IEnumerator LogicalChildren + { + get + { + object content = this.RootItem; ; + if (content == null) + { + return base.LogicalChildren; + } + if (base.TemplatedParent != null) + { + DependencyObject current = content as DependencyObject; + if (current != null) + { + DependencyObject parent = LogicalTreeHelper.GetParent(current); + if ((parent != null) && (parent != this)) + { + return base.LogicalChildren; + } + } + } + + object[] array = new object[] { RootItem }; + return array.GetEnumerator(); + } + } + + + + #region IAddChild Members + + public void AddChild(object value) + { + this.Root = value; + } + + public void AddText(string text) + { + AddChild(text); + } + + #endregion + + /// + /// Gets or sets the TraceBinding property that will be set to every child BreadcrumbItem. This is not a dependency property! + /// + public BindingBase TraceBinding { get; set; } + + /// + /// Gets or sets the ImageBinding property that will be set to every child BreadcrumbItem. This is not a dependency property! + /// + public BindingBase ImageBinding { get; set; } + + } +} diff --git a/Odyssey/Odyssey/BreadcrumbBar/BreadcrumbButton.cs b/Odyssey/Odyssey/BreadcrumbBar/BreadcrumbButton.cs new file mode 100644 index 0000000..1b3dc57 --- /dev/null +++ b/Odyssey/Odyssey/BreadcrumbBar/BreadcrumbButton.cs @@ -0,0 +1,463 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Controls.Primitives; +using System.Diagnostics; + + +#region Licence +// Copyright (c) 2008 Thomas Gerber +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#endregion +namespace Odyssey.Controls +{ + /// + /// A breadcrumb button is part of a BreadcrumbItem and contains a header and a dropdown button. + /// + [TemplatePart(Name = partMenu)] + [TemplatePart(Name = partToggle)] + [TemplatePart(Name = partButton)] + [TemplatePart(Name = partDropDown)] + public class BreadcrumbButton : HeaderedItemsControl + { + const string partMenu = "PART_Menu"; + const string partToggle = "PART_Toggle"; + const string partButton = "PART_button"; + const string partDropDown = "PART_DropDown"; + static BreadcrumbButton() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(BreadcrumbButton), new FrameworkPropertyMetadata(typeof(BreadcrumbButton))); + } + + #region Dependency Properties + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(object), typeof(BreadcrumbButton), new UIPropertyMetadata(null)); + + public static readonly DependencyProperty SelectedItemProperty = + DependencyProperty.Register("SelectedItem", typeof(object), typeof(BreadcrumbButton), new UIPropertyMetadata(null, SelectedItemChangedEvent)); + + + #endregion + + #region RoutedEvents + public static readonly RoutedEvent SelectedItemChanged = EventManager.RegisterRoutedEvent("SelectedItemChanged", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(BreadcrumbButton)); + + public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent("Click", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(BreadcrumbButton)); + #endregion + + private ContextMenu contextMenu; + private Control dropDownBtn; + + public BreadcrumbButton() + : base() + { + CommandBindings.Add(new CommandBinding(SelectCommand, SelectCommandExecuted)); + CommandBindings.Add(new CommandBinding(OpenOverflowCommand, OpenOverflowCommandExecuted, OpenOverflowCommandCanExecute)); + + InputBindings.Add(new KeyBinding(BreadcrumbButton.SelectCommand, new KeyGesture(Key.Enter))); + InputBindings.Add(new KeyBinding(BreadcrumbButton.SelectCommand, new KeyGesture(Key.Space))); + InputBindings.Add(new KeyBinding(BreadcrumbButton.OpenOverflowCommand, new KeyGesture(Key.Down))); + InputBindings.Add(new KeyBinding(BreadcrumbButton.OpenOverflowCommand, new KeyGesture(Key.Up))); + } + + + private bool isPressed = false; + + protected override void OnMouseLeave(MouseEventArgs e) + { + IsPressed = false; + } + + + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + e.Handled = true; + IsPressed = isPressed = true; + base.OnMouseLeftButtonDown(e); + } + + + + + protected override void OnMouseUp(MouseButtonEventArgs e) + { + e.Handled = true; + if (isPressed) + { + RoutedEventArgs args = new RoutedEventArgs(BreadcrumbButton.ClickEvent); + RaiseEvent(args); + selectCommand.Execute(null, this); + } + IsPressed = isPressed = false; + base.OnMouseUp(e); + } + + private void SelectCommandExecuted(object sender, ExecutedRoutedEventArgs e) + { + SelectedItem = null; + RoutedEventArgs args = new RoutedEventArgs(Button.ClickEvent); + RaiseEvent(args); + } + + private void OpenOverflowCommandExecuted(object sender, ExecutedRoutedEventArgs e) + { + IsDropDownPressed = true; + } + + private void OpenOverflowCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) + { + e.CanExecute = Items.Count > 0; + } + + public static RoutedUICommand OpenOverflowCommand + { + get { return openOverflowCommand; } + } + + public static RoutedUICommand SelectCommand + { + get { return selectCommand; } + } + + private static RoutedUICommand openOverflowCommand = new RoutedUICommand("Open Overflow", "OpenOverflowCommand", typeof(BreadcrumbButton)); + private static RoutedUICommand selectCommand = new RoutedUICommand("Select", "SelectCommand", typeof(BreadcrumbButton)); + + + public override void OnApplyTemplate() + { + dropDownBtn = this.GetTemplateChild(partDropDown) as Control; + contextMenu = this.GetTemplateChild(partMenu) as ContextMenu; + if (contextMenu != null) + { + contextMenu.Opened += new RoutedEventHandler(contextMenu_Opened); + } + if (dropDownBtn != null) + { + dropDownBtn.MouseDown += new MouseButtonEventHandler(dropDownBtn_MouseDown); + } + base.OnApplyTemplate(); + } + + void dropDownBtn_MouseDown(object sender, MouseButtonEventArgs e) + { + e.Handled = true; + IsDropDownPressed ^= true; + } + + + /// + /// Gets or sets the Image of the BreadcrumbButton. + /// + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + + //TODO: Menu needs too long to render if there are too many items (> 20000). + void contextMenu_Opened(object sender, RoutedEventArgs e) + { + contextMenu.Items.Clear(); + contextMenu.ItemTemplate = ItemTemplate; + contextMenu.ItemTemplateSelector = ItemTemplateSelector; + + // List menuItems = new List(); + foreach (object item in Items) + { + if (!(item is MenuItem) && !(item is Separator)) + { + + RibbonMenuItem menuItem = new RibbonMenuItem(); + menuItem.DataContext = item; + BreadcrumbItem bi = item as BreadcrumbItem; + if (bi == null) + { + BreadcrumbItem parent = TemplatedParent as BreadcrumbItem; + if (parent != null) bi = parent.ContainerFromItem(item); + } + if (bi != null) menuItem.Header = bi.TraceValue; + + Image image = new Image(); + image.Source = bi != null ? bi.Image : null; + image.SnapsToDevicePixels = true; + image.Stretch = Stretch.Fill; + image.VerticalAlignment = VerticalAlignment.Center; + image.HorizontalAlignment = HorizontalAlignment.Center; + image.Width = 16; + image.Height = 16; + + menuItem.Icon = image; + + menuItem.Click += new RoutedEventHandler(item_Click); + if (item != null && item.Equals(SelectedItem)) menuItem.FontWeight = FontWeights.Bold; + menuItem.ItemTemplate = ItemTemplate; + menuItem.ItemTemplateSelector = ItemTemplateSelector; + contextMenu.Items.Add(menuItem); + } + else + { + contextMenu.Items.Add(item); + } + } + contextMenu.Placement = PlacementMode.Relative; + contextMenu.PlacementTarget = dropDownBtn; + contextMenu.VerticalOffset = dropDownBtn.ActualHeight; + } + + void item_Click(object sender, RoutedEventArgs e) + { + MenuItem item = e.Source as MenuItem; + object dataItem = item.DataContext; + RemoveSelectedItem(dataItem); + SelectedItem = dataItem; + } + + /// + /// When a BreadcrumbItem is selected from a dropdown menu, the SelectedItem of the new selected item must be set to null. + /// Since no event is raised when a DependencyProperty is assigned to it's current value, this cannot be recognized at this place, + /// therefore the SelectedItem DependencyProperty must previously set to null before setting it to it's new value to raise event + /// when SelectedItem is changed: + /// + /// + private void RemoveSelectedItem(object dataItem) + { + if (dataItem != null && dataItem.Equals(SelectedItem)) SelectedItem = null; + } + + /// + /// Gets or sets the selectedItem. + /// + public object SelectedItem + { + get { return (object)GetValue(SelectedItemProperty); } + set { SetValue(SelectedItemProperty, value); } + } + + private static void SelectedItemChangedEvent(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BreadcrumbButton button = d as BreadcrumbButton; + if (button.IsInitialized) + { + RoutedEventArgs args = new RoutedEventArgs(SelectedItemChanged); + button.RaiseEvent(args); + } + } + + /// + /// Focus this BreadcrumbButton if the focus is currently within the BreadcrumbBar where this BreadcrumbButton is embedded: + /// + protected override void OnMouseEnter(MouseEventArgs e) + { + isPressed = e.LeftButton == MouseButtonState.Pressed; + FrameworkElement parent = TemplatedParent as FrameworkElement; + while (parent != null && !(parent is BreadcrumbBar)) parent = VisualTreeHelper.GetParent(parent) as FrameworkElement; + BreadcrumbBar bar = parent as BreadcrumbBar; + if (bar != null && bar.IsKeyboardFocusWithin) Focus(); + IsPressed = isPressed; + base.OnMouseEnter(e); + } + + + private static void OverflowPressedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BreadcrumbButton button = d as BreadcrumbButton; + button.OnOverflowPressedChanged(); + } + + protected virtual void OnOverflowPressedChanged() + { + } + + + /// + /// Specifies how to display the BreadcrumbButton. + /// + public enum ButtonMode + { + /// + /// Display as Breadcrumb. + /// + Breadcrumb, + + /// + /// Display as overflow. + /// + Overflow, + + /// + /// Display as drop down. + /// + DropDown + } + + + /// + /// Gets or sets the ButtonMode for the BreadcrumbButton. + /// + public ButtonMode Mode + { + get { return (ButtonMode)GetValue(ModeProperty); } + set { SetValue(ModeProperty, value); } + } + + // Using a DependencyProperty as the backing store for Mode. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ModeProperty = + DependencyProperty.Register("Mode", typeof(ButtonMode), typeof(BreadcrumbButton), new UIPropertyMetadata(ButtonMode.Breadcrumb)); + + + /// + /// Occurs when the Button is clicked. + /// + public event RoutedEventHandler Click + { + add { AddHandler(BreadcrumbButton.ClickEvent, value); } + remove { RemoveHandler(BreadcrumbButton.ClickEvent, value); } + } + + /// + /// Occurs when the SelectedItem is changed. + /// + public event RoutedEventHandler Select + { + add { AddHandler(BreadcrumbButton.SelectedItemChanged, value); } + remove { RemoveHandler(BreadcrumbButton.SelectedItemChanged, value); } + } + + + /// + /// Gets or sets whether the button is pressed. + /// + public bool IsPressed + { + get { return (bool)GetValue(IsPressedProperty); } + set { SetValue(IsPressedProperty, value); } + } + + /// + /// Gets or sets whether the button is pressed. + /// + public static readonly DependencyProperty IsPressedProperty = + DependencyProperty.Register("IsPressed", typeof(bool), typeof(BreadcrumbButton), new UIPropertyMetadata(false)); + + + /// + /// Gets or sets whether the drop down button is pressed. + /// + public bool IsDropDownPressed + { + get { return (bool)GetValue(IsDropDownPressedProperty); } + set { SetValue(IsDropDownPressedProperty, value); } + } + + /// + /// Gets or sets whether the drop down button is pressed. + /// + public static readonly DependencyProperty IsDropDownPressedProperty = + DependencyProperty.Register("IsDropDownPressed", typeof(bool), typeof(BreadcrumbButton), new UIPropertyMetadata(false, OverflowPressedChanged)); + + + /// + /// Gets or sets the DataTemplate for the drop down items. + /// + public DataTemplate DropDownContentTemplate + { + get { return (DataTemplate)GetValue(DropDownContentTemplateProperty); } + set { SetValue(DropDownContentTemplateProperty, value); } + } + + // Using a DependencyProperty as the backing store for DropDownContentTemplate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DropDownContentTemplateProperty = + DependencyProperty.Register("DropDownContentTemplate", typeof(DataTemplate), typeof(BreadcrumbButton), new UIPropertyMetadata(null)); + + + + /// + /// Gets or sets whether the drop down button is visible. + /// + public bool IsDropDownVisible + { + get { return (bool)GetValue(IsDropDownVisibleProperty); } + set { SetValue(IsDropDownVisibleProperty, value); } + } + + /// + /// Gets or sets whether the drop down button is visible. + /// + public static readonly DependencyProperty IsDropDownVisibleProperty = + DependencyProperty.Register("IsDropDownVisible", typeof(bool), typeof(BreadcrumbButton), new UIPropertyMetadata(true)); + + + + /// + /// Gets or sets whether the button is visible. + /// + public bool IsButtonVisible + { + get { return (bool)GetValue(IsButtonVisibleProperty); } + set { SetValue(IsButtonVisibleProperty, value); } + } + + /// + /// Gets or sets whether the button is visible. + /// + public static readonly DependencyProperty IsButtonVisibleProperty = + DependencyProperty.Register("IsButtonVisible", typeof(bool), typeof(BreadcrumbButton), new UIPropertyMetadata(true)); + + + /// + /// Gets or sets whether the Image is visible + /// + public bool IsImageVisible + { + get { return (bool)GetValue(IsImageVisibleProperty); } + set { SetValue(IsImageVisibleProperty, value); } + } + + /// + /// Gets or sets whether the Image is visible + /// + public static readonly DependencyProperty IsImageVisibleProperty = + DependencyProperty.Register("IsImageVisible", typeof(bool), typeof(BreadcrumbButton), new UIPropertyMetadata(true)); + + + + + /// + /// Gets or sets whether to use visual background style on MouseOver and/or MouseDown. + /// + public bool EnableVisualButtonStyle + { + get { return (bool)GetValue(EnableVisualButtonStyleProperty); } + set { SetValue(EnableVisualButtonStyleProperty, value); } + } + + /// + /// Gets or sets whether to use visual background style on MouseOver and/or MouseDown. + /// + public static readonly DependencyProperty EnableVisualButtonStyleProperty = + DependencyProperty.Register("EnableVisualButtonStyle", typeof(bool), typeof(BreadcrumbButton), new UIPropertyMetadata(true)); + + + } +} diff --git a/Odyssey/Odyssey/BreadcrumbBar/BreadcrumbItem.cs b/Odyssey/Odyssey/BreadcrumbBar/BreadcrumbItem.cs new file mode 100644 index 0000000..b3eb8d3 --- /dev/null +++ b/Odyssey/Odyssey/BreadcrumbBar/BreadcrumbItem.cs @@ -0,0 +1,724 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Diagnostics; +using System.Windows.Controls.Primitives; +using System.Reflection; +using System.Xml; +using System.Collections; + +#region Licence +// Copyright (c) 2008 Thomas Gerber +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#endregion +namespace Odyssey.Controls +{ + /// + /// A breadcrumb item that is part of a BreadcrumbBar and contains a BreadcrumbButton and nested child BreadcrumbItems. + /// + [TemplatePart(Name = partHeader)] + [TemplatePart(Name = partSelected)] + public class BreadcrumbItem : Selector + { + + #region DependencyProperties + + /// + /// Gets or sets whether the dropdown button is pressed. + /// + public static readonly DependencyProperty IsDropDownPressedProperty = + DependencyProperty.Register("IsDropDownPressed", typeof(bool), typeof(BreadcrumbItem), new UIPropertyMetadata(false, DropDownPressedPropertyChanged)); + + /// + /// Gets or sets whether the BreadcrumbItem is in overflow mode, which means that the header property is not visible. + /// + public static readonly DependencyProperty IsOverflowProperty = + DependencyProperty.Register("IsOverflow", typeof(bool), typeof(BreadcrumbItem), new FrameworkPropertyMetadata(false, + FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure, + OverflowPropertyChanged)); + + /// + /// Gets or sets whether this BreadcrumbItem is the Root of a BreadcrumbBar. + /// + public static readonly DependencyProperty IsRootProperty = + DependencyProperty.Register("IsRoot", typeof(bool), typeof(BreadcrumbItem), new UIPropertyMetadata(false)); + + private static readonly DependencyPropertyKey SelectedBreadcrumbPropertyKey = + DependencyProperty.RegisterReadOnly("SelectedBreadcrumb", typeof(BreadcrumbItem), typeof(BreadcrumbItem), + new UIPropertyMetadata(null, SelectedBreadcrumbPropertyChanged)); + + /// + /// Gets or sets the TemplateSelector of an Item. + /// + public static readonly DependencyProperty OverflowItemTemplateSelectorProperty; + + public static readonly DependencyProperty OverflowItemTemplateProperty; + + /// + /// Gets or sets the ImageSource. + /// + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(ImageSource), typeof(BreadcrumbItem), new UIPropertyMetadata(null)); + + /// + /// Gets or sets the Trace string to build the Path. + /// + public static readonly DependencyProperty TraceProperty = + DependencyProperty.Register("Trace", typeof(object), typeof(BreadcrumbItem), new UIPropertyMetadata(null, TracePropertyChanged)); + + /// + /// Gets or sets the Header. + /// + public static readonly DependencyProperty HeaderProperty = + DependencyProperty.Register("Header", typeof(object), typeof(BreadcrumbItem), new UIPropertyMetadata(null, HeaderPropertyChanged)); + + public static readonly DependencyProperty HeaderTemplateProperty; + + public static readonly DependencyProperty HeaderTemplateSelectorProperty; + + #endregion + + const string partHeader = "PART_Header"; + const string partSelected = "PART_Selected"; + + static BreadcrumbItem() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(BreadcrumbItem), new FrameworkPropertyMetadata(typeof(BreadcrumbItem))); + + OverflowItemTemplateProperty = BreadcrumbBar.OverflowItemTemplateProperty.AddOwner(typeof(BreadcrumbItem), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); + + OverflowItemTemplateSelectorProperty = BreadcrumbBar.OverflowItemTemplateSelectorProperty.AddOwner(typeof(BreadcrumbItem), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); + + HeaderTemplateSelectorProperty = BreadcrumbBar.BreadcrumbItemTemplateSelectorProperty.AddOwner(typeof(BreadcrumbItem), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); + + HeaderTemplateProperty = BreadcrumbBar.BreadcrumbItemTemplateProperty.AddOwner(typeof(BreadcrumbItem), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); + } + + protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + base.OnItemsChanged(e); + } + + private static void HeaderPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + BreadcrumbItem item = sender as BreadcrumbItem; + } + + private static void TracePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + BreadcrumbItem item = sender as BreadcrumbItem; + + RoutedPropertyChangedEventArgs args = new RoutedPropertyChangedEventArgs(e.OldValue, e.NewValue, TraceChangedEvent); + item.RaiseEvent(args); + } + + /// + /// Occurs when the IsDropDownPressed property is changed. + /// + public event RoutedPropertyChangedEventHandler DropDownPressedChanged + { + add { AddHandler(DropDownPressedChangedEvent, value); } + remove { RemoveHandler(DropDownPressedChangedEvent, value); } + } + + /// + /// Occurs when the IsDropDownPressed property is changed. + /// + public static readonly RoutedEvent DropDownPressedChangedEvent = EventManager.RegisterRoutedEvent("DropDownPressedChanged", + RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler), typeof(BreadcrumbItem)); + + /// + /// Occurs when the Trace property is changed. + /// + public event RoutedPropertyChangedEventHandler TraceChanged + { + add { AddHandler(TraceChangedEvent, value); } + remove { RemoveHandler(TraceChangedEvent, value); } + } + + /// + /// Occurs when the Trace property is changed. + /// + public static readonly RoutedEvent TraceChangedEvent = EventManager.RegisterRoutedEvent("TraceChanged", + RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler), typeof(BreadcrumbItem)); + + + /// + /// Creates a new instance of BreadcrumbItem. + /// + public BreadcrumbItem() + : base() + { + } + + + /// + /// Creates a new BreadcrumbItem out of the specified data. + /// + /// The DataContext for the BreadcrumbItem + /// DataContext if dataContext is a Breadcrumbitem, otherwhise a new BreadcrumbItem. + public static BreadcrumbItem CreateItem(object dataContext) + { + BreadcrumbItem item = dataContext as BreadcrumbItem; + if (item == null && dataContext != null) + { + item = new BreadcrumbItem(); + item.DataContext = dataContext; + } + return item; + } + + private static void SelectedBreadcrumbPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BreadcrumbItem item = d as BreadcrumbItem; + item.OnSelectedBreadcrumbChanged(e.OldValue, e.NewValue); + } + + /// + /// Occurs when the selected BreadcrumbItem is changed. + /// + /// + /// + protected virtual void OnSelectedBreadcrumbChanged(object oldItem, object newItem) + { + if (SelectedBreadcrumb != null) + { + SelectedBreadcrumb.SelectedItem = null; + } + } + + protected override bool IsItemItsOwnContainerOverride(object item) + { + return item is BreadcrumbItem; + } + + protected override DependencyObject GetContainerForItemOverride() + { + BreadcrumbItem item = new BreadcrumbItem(); + return item; + } + + private FrameworkElement headerControl; + private FrameworkElement selectedControl; + + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + headerControl = GetTemplateChild(partHeader) as FrameworkElement; + selectedControl = GetTemplateChild(partSelected) as FrameworkElement; + + ApplyBinding(); + } + + public object Data + { + get + { + return DataContext != null ? DataContext : this; + } + } + + + + /// + /// Gets or sets wheter the dropdown button is pressed. + /// + public bool IsDropDownPressed + { + get { return (bool)GetValue(IsDropDownPressedProperty); } + set { SetValue(IsDropDownPressedProperty, value); } + } + + + /// + /// Occurs when the IsDropDownPressed property is changed. + /// + /// + /// + public static void DropDownPressedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BreadcrumbItem item = d as BreadcrumbItem; + item.OnDropDownPressedChanged(); + } + + + /// + /// Occurs when the DropDown button is pressed or released. + /// + protected virtual void OnDropDownPressedChanged() + { + RoutedEventArgs args = new RoutedEventArgs(DropDownPressedChangedEvent); + RaiseEvent(args); + } + + + + /// + /// Gets whether the breadcrumb item is overflowed which means it is not visible in the breadcrumb bar but in the + /// drop down menu of the breadcrumb bar. + /// + public bool IsOverflow + { + get { return (bool)GetValue(IsOverflowProperty); } + private set { SetValue(IsOverflowProperty, value); } + } + + /// + /// Occurs when the Overflow property is changed. + /// + /// + /// + public static void OverflowPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BreadcrumbItem item = d as BreadcrumbItem; + item.OnOverflowChanged((bool)e.NewValue); + } + + + + /// + /// Set to true, to collapse the item if SelectedItem is not null. otherwise false. + /// + public bool IsRoot + { + get { return (bool)GetValue(IsRootProperty); } + set { SetValue(IsRootProperty, value); } + } + + + /// + /// Occurs when the Overflow property is changed. + /// + public static readonly RoutedEvent OverflowChangedEvent = EventManager.RegisterRoutedEvent("OverflowChanged", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(BreadcrumbItem)); + + /// + /// Occurs when the Overflow property is changed. + /// + protected virtual void OnOverflowChanged(bool newValue) + { + RoutedEventArgs args = new RoutedEventArgs(OverflowChangedEvent); + RaiseEvent(args); + } + + + /// + /// Perform a special measurement that checks whether to collapse the header. + /// + /// + /// + protected override Size MeasureOverride(Size constraint) + { + if (SelectedItem != null) + { + headerControl.Visibility = Visibility.Visible; + headerControl.Measure(constraint); + Size size = new Size(constraint.Width - headerControl.DesiredSize.Width, constraint.Height); + selectedControl.Measure(new Size(double.PositiveInfinity, constraint.Height)); + double width = headerControl.DesiredSize.Width + selectedControl.DesiredSize.Width; + if (width > constraint.Width || (IsRoot && SelectedItem != null)) + { + headerControl.Visibility = Visibility.Collapsed; + } + } + else if (headerControl != null) headerControl.Visibility = Visibility.Visible; + IsOverflow = headerControl != null ? headerControl.Visibility != Visibility.Visible : false; + + Size result = base.MeasureOverride(constraint); + return result; + } + + + protected override void OnSelectionChanged(SelectionChangedEventArgs e) + { + if (SelectedItem == null) + { + SelectedBreadcrumb = null; + } + else + { + SelectedBreadcrumb = ContainerFromItem(SelectedItem); + } + base.OnSelectionChanged(e); + + } + + /// + /// Generates a new BreadcrumbItem out of the specified item. + /// + /// The item for which to create a new BreadcrumbItem. + /// Item, if item is a BreadcrumbItem, otherwhise a newly created BreadcrumbItem. + public BreadcrumbItem ContainerFromItem(object item) + { + BreadcrumbItem result = item as BreadcrumbItem; + if (result == null) + { + result = CreateItem(item); + if (result != null) + { + AddLogicalChild(result); + result.ApplyTemplate(); + } + } + return result; + } + + + /// + /// Gets the selected BreadcrumbItem. + /// + public BreadcrumbItem SelectedBreadcrumb + { + get { return (BreadcrumbItem)GetValue(SelectedBreadcrumbProperty); } + private set { SetValue(SelectedBreadcrumbPropertyKey, value); } + } + + + public static readonly DependencyProperty SelectedBreadcrumbProperty = SelectedBreadcrumbPropertyKey.DependencyProperty; + + + public DataTemplateSelector BreadcrumbTemplateSelector { get; set; } + + public DataTemplate BreadcrumbItemTemplate { get; set; } + + + public DataTemplateSelector OverflowItemTemplateSelector + { + get { return (DataTemplateSelector)GetValue(OverflowItemTemplateSelectorProperty); } + set { SetValue(OverflowItemTemplateSelectorProperty, value); } + } + + public DataTemplate OverflowItemTemplate + { + get { return (DataTemplate)GetValue(OverflowItemTemplateProperty); } + set { SetValue(OverflowItemTemplateProperty, value); } + } + + + + + /// + /// Gets or sets the image that is used to display this item. + /// + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + /// + /// Gets or sets the Trace of the breadcrumb + /// + public object Trace + { + get { return (object)GetValue(TraceProperty); } + set { SetValue(TraceProperty, value); } + } + + + /// + /// Gets or sets the Binding to the Trace property. This is not a dependency property. + /// + public BindingBase TraceBinding { get; set; } + + + /// + /// Gets or sets the Binding to the Image property. This is not a dependency property. + /// + public BindingBase ImageBinding { get; set; } + + + /// + /// Gets or sets the header for the breadcrumb item. + /// + public object Header + { + + get { return (object)GetValue(HeaderProperty); } + set { SetValue(HeaderProperty, value); } + } + + /// + /// Gets or sets the header template. + /// + public DataTemplate HeaderTemplate + { + get { return (DataTemplate)GetValue(HeaderTemplateProperty); } + set { SetValue(HeaderTemplateProperty, value); } + } + + /// + /// Gets or sets the header template selector. + /// + public DataTemplateSelector HeaderTemplateSelector + { + get { return (DataTemplateSelector)GetValue(HeaderTemplateSelectorProperty); } + set { SetValue(HeaderTemplateSelectorProperty, value); } + } + + /// + /// Appies the binding to the breadcrumb item. + /// + public void ApplyBinding() + { + object item = DataContext; + if (item == null) return; + + BreadcrumbItem root = this; + DataTemplate template = HeaderTemplate; + DataTemplateSelector templateSelector = HeaderTemplateSelector; + if (templateSelector != null) + { + + template = templateSelector.SelectTemplate(item, root); + } + if (template == null) + { + DataTemplateKey key = GetResourceKey(item); + if (key != null) template = TryFindResource(key) as DataTemplate; + } + + root.SelectedItem = null; + + HierarchicalDataTemplate hdt = template as HierarchicalDataTemplate; + if (template != null) + { + root.Header = template.LoadContent(); + } + else + { + root.Header = item; + } + root.DataContext = item; + + if (hdt != null) + { + // bind the Items to the hierarchical data template: + root.SetBinding(BreadcrumbItem.ItemsSourceProperty, hdt.ItemsSource); + } + + BreadcrumbBar bar = BreadcrumbBar; + + if (bar != null) + { + if (TraceBinding == null) TraceBinding = bar.TraceBinding; + if (ImageBinding == null) ImageBinding = bar.ImageBinding; + } + + if (TraceBinding != null) + { + root.SetBinding(BreadcrumbItem.TraceProperty, TraceBinding); + } + if (ImageBinding != null) + { + root.SetBinding(BreadcrumbItem.ImageProperty, ImageBinding); + } + + + ApplyProperties(item); + } + + + + /// + /// Gets the parent BreadcrumbBar container. + /// + public BreadcrumbBar BreadcrumbBar + { + get + { + DependencyObject current = this; + while (current != null) + { + current = LogicalTreeHelper.GetParent(current); + if (current is BreadcrumbBar) return current as BreadcrumbBar; + } + return null; + } + } + + /// + /// Gets the parent BreadcrumbItem, otherwise null. + /// + public BreadcrumbItem ParentBreadcrumbItem + { + get + { + BreadcrumbItem parent = LogicalTreeHelper.GetParent(this) as BreadcrumbItem; + return parent; + } + } + + private static DataTemplateKey GetResourceKey(object item) + { + XmlDataProvider xml = item as XmlDataProvider; + DataTemplateKey key; + if (xml != null) + { + key = new DataTemplateKey(xml.XPath); + } + else + { + XmlNode node = item as XmlNode; + if (node != null) + { + key = new DataTemplateKey(node.Name); + } + else + { + key = new DataTemplateKey(item.GetType()); + } + } + return key; + } + + + private void ApplyProperties(object item) + { + ApplyPropertiesEventArgs e = new ApplyPropertiesEventArgs(item, this, BreadcrumbBar.ApplyPropertiesEvent); + e.Image = Image; + e.Trace = Trace; + e.TraceValue = TraceValue; + this.RaiseEvent(e); + Image = e.Image; + Trace = e.Trace; + } + + + /// + /// Gets the string trace that is used to build the path. + /// + /// + public string GetTracePathValue() + { + ApplyPropertiesEventArgs e = new ApplyPropertiesEventArgs(DataContext, this, BreadcrumbBar.ApplyPropertiesEvent); + e.Trace = Trace; + e.TraceValue = TraceValue; + this.RaiseEvent(e); + return e.TraceValue; + } + + /// + /// Gets the Trace string from the Trace property. + /// + public string TraceValue + { + get + { + XmlNode xml = Trace as XmlNode; + if (xml != null) + { + return xml.Value; + } + + if (Trace != null) return Trace.ToString(); + if (Header != null) return Header.ToString(); + return string.Empty; + } + } + + /// + /// Gets the item that represents the specified trace otherwise null. + /// + /// The Trace property of the associated BreadcrumbItem. + /// An object included in Items, otherwise null. + public object GetTraceItem(string trace) + { + this.ApplyTemplate(); + foreach (object item in Items) + { + BreadcrumbItem bcItem = ContainerFromItem(item); + if (bcItem != null) + { + ApplyProperties(item); + string value = bcItem.TraceValue; + if (value != null && value.Equals(trace, StringComparison.InvariantCultureIgnoreCase)) return item; + } + else return null; + } + return null; + } + + + protected override IEnumerator LogicalChildren + { + get + { + object content = this.SelectedBreadcrumb; ; + if (content == null) + { + return base.LogicalChildren; + } + if (base.TemplatedParent != null) + { + DependencyObject current = content as DependencyObject; + if (current != null) + { + DependencyObject parent = LogicalTreeHelper.GetParent(current); + if ((parent != null) && (parent != this)) + { + return base.LogicalChildren; + } + } + } + + object[] array = new object[] { SelectedBreadcrumb }; + return array.GetEnumerator(); + } + } + + /// + /// Gets or sets whether the button is visible. + /// + public bool IsButtonVisible + { + get { return (bool)GetValue(IsButtonVisibleProperty); } + set { SetValue(IsButtonVisibleProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsButtonVisible. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsButtonVisibleProperty = + DependencyProperty.Register("IsButtonVisible", typeof(bool), typeof(BreadcrumbItem), new UIPropertyMetadata(true)); + + + + /// + /// Gets or sets whether the Image is visible. + /// + public bool IsImageVisible + { + get { return (bool)GetValue(IsImageVisibleProperty); } + set { SetValue(IsImageVisibleProperty, value); } + } + + /// + /// Gets or sets whether the Image is visible. + /// + public static readonly DependencyProperty IsImageVisibleProperty = + DependencyProperty.Register("IsImageVisible", typeof(bool), typeof(BreadcrumbItem), new UIPropertyMetadata(false)); + + + } +} diff --git a/Odyssey/Odyssey/BreadcrumbBar/BreadcrumbItemEventArgs.cs b/Odyssey/Odyssey/BreadcrumbBar/BreadcrumbItemEventArgs.cs new file mode 100644 index 0000000..fb183c5 --- /dev/null +++ b/Odyssey/Odyssey/BreadcrumbBar/BreadcrumbItemEventArgs.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; + +#region Licence +// Copyright (c) 2008 Thomas Gerber +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#endregion +namespace Odyssey.Controls +{ + public class BreadcrumbItemEventArgs:RoutedEventArgs + { + public BreadcrumbItemEventArgs(BreadcrumbItem item, RoutedEvent routedEvent) + : base(routedEvent) + { + Item = item; + } + + public BreadcrumbItem Item { get; private set; } + } + + public delegate void BreadcrumbItemEventHandler(object sender, BreadcrumbItemEventArgs e); + +} diff --git a/Odyssey/Odyssey/BreadcrumbBar/ImageButton.cs b/Odyssey/Odyssey/BreadcrumbBar/ImageButton.cs new file mode 100644 index 0000000..0b6e138 --- /dev/null +++ b/Odyssey/Odyssey/BreadcrumbBar/ImageButton.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Odyssey.Controls +{ + public class ImageButton : Button + { + static ImageButton() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton))); + } + + + + public double ImageWidth + { + get { return (double)GetValue(ImageWidthProperty); } + set { SetValue(ImageWidthProperty, value); } + } + + public static readonly DependencyProperty ImageWidthProperty = + DependencyProperty.Register("ImageWidth", typeof(double), typeof(ImageButton), new UIPropertyMetadata(16.0)); + + + + + public double ImageHeight + { + get { return (double)GetValue(ImageHeightProperty); } + set { SetValue(ImageHeightProperty, value); } + } + + public static readonly DependencyProperty ImageHeightProperty = + DependencyProperty.Register("ImageHeight", typeof(double), typeof(ImageButton), new UIPropertyMetadata(16.0)); + + + + + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null)); + + } +} diff --git a/Odyssey/Odyssey/BreadcrumbBar/PathConversionEventArgs.cs b/Odyssey/Odyssey/BreadcrumbBar/PathConversionEventArgs.cs new file mode 100644 index 0000000..c4559ea --- /dev/null +++ b/Odyssey/Odyssey/BreadcrumbBar/PathConversionEventArgs.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; + +#region Licence +// Copyright (c) 2008 Thomas Gerber +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#endregion +namespace Odyssey.Controls +{ + /// + /// RoutedEventArgs to convert the display path to edit path and vice verca. + /// + public class PathConversionEventArgs:RoutedEventArgs + { + //TODO: Rename PathConverionMode to LogicalToVisual and VisualToLogical + /// + /// Specifies what property to convert. + /// + public enum ConversionMode + { + /// + /// Convert the display path to edit path. + /// + DisplayToEdit, + + /// + /// convert the edit path to display path. + /// + EditToDisplay, + } + + /// + /// Gets or sets the display path. + /// + public string DisplayPath { get; set; } + + /// + /// Gets or sets the edit path. + /// + public string EditPath { get; set; } + + /// + /// Specifies what path property to convert. + /// + public ConversionMode Mode { get; private set; } + + /// + /// Gets the root object of the breadcrumb bar. + /// + public object Root { get; private set; } + + /// + /// Creates a new PathConversionEventArgs class. + /// + /// The conversion mode. + /// The initial values for DisplayPath and EditPath. + /// The root object. + /// + public PathConversionEventArgs(ConversionMode mode, string path, object root, RoutedEvent routedEvent) + : base(routedEvent) + { + Mode = mode; + DisplayPath = EditPath = path; + Root = root; + } + } + + public delegate void PathConversionEventHandler(object sender, PathConversionEventArgs e); + +} diff --git a/Odyssey/Odyssey/ChangeLog.txt b/Odyssey/Odyssey/ChangeLog.txt new file mode 100644 index 0000000..91dbdf4 --- /dev/null +++ b/Odyssey/Odyssey/ChangeLog.txt @@ -0,0 +1,47 @@ +- New attached properties odc:ImageRenderOptions.LargeImageScalingMode and odc:ImageRenderOptions.SmallImageScalingMode: + Specify render options for either LargeImages or SmallImages. These properties are inheritable, so any child inherits the attached value from it's parent. + For instance. you can set to apply this option to all controls inside. + + +- fixed bug in RibbonWindow: + MeasureOverride is required to adjust the correct size, otherwhise controls inside the window do not correctly measure the size. + For instance, adding a to a RibbonWindow caused any row with RowDefinition.Height="*" to appear with a gap. + +- modified OdcExpander to show a vertical scrollbar when it is inside a control which does not allow it's height to be infinite. + +- modified OdcExdpander to correctly measure the available width. ínstead of an infinite width. this has caused controls inside an expander for instance + not to enable word wrap. + +- RibbonBar: switiching Visibility property of a RibbonTabItem now causes the RibbonBar to render again and ensure that only a visible + TabItem is set as SelectedTabItem. + + - added new RoutedEventHandler SelectedTabChanged. + + -added style and theme for TreeViewItem and ListViewItem to appear in the chosen skin. + - TreeViewItems are animated while expanding/collapsing. + + -changed duration for animations in BreadcrumbBar to smaller duration that is more eye friendly. + + - new dependency property BreadcrumbBar.PathBinding: This property is supposed to be used to bind a path to the breadcrumb instead of using + BreadcrumbBar.Path for binding. Since this value can be changed manually, the bindig would get lost. + + - new OdcTextBox control: A Textbox with optional inline buttons on the right and a style equal to the BreadcrumbBar. + - new ImageButton control: A Button control with Image property of type ImageSource to render a 16x16 button, intendet do be used with BreadcrumbBar or OdcTextBox. + + - improved handling of popups: nested popups are no longer accidenty closed. This is archived by deriving RibbonDropDownButton from MenuBase insed of MenuItem + as well as a modified handling for closing a Popup. + + -new Controls: SelectableTreeView and SelectableTreeViewItem. SelectableTreevVewItem adds a Click event and Command to a TreeViewItem as well as a IsPressed property. + - the overlay button of the application menu is now a Rectangle with a VisualBrush that renderes the original (orb) application menu button. It is now inside a Canvas and code changes + the Canvas.Left and Canvas.Top properties to be exactly over the original application menu button. + + - the blur effect of the outer elipse of the orb button caused the clone image not to be exact shape, so I removed the blur effect. + - binding to controls that are on a RibbonTab that was not the initial visible RibbonTab did not execute, so added ApplyTemplate() on EndInit() to make this work. + - RibbonBar is now derived from Selector instead of Control. + - RibbonTab is now derived from ItemsControl instead of Control. + - Optimized Disabled style: Images are now rendered in grayscale by Odyssey.Effects.Grayscale, and the text is also translucent. + - Commands for RibbonDropDownbutton and RibbonSplitButton now recognize Command.CanExecute. + - Improved positioning of window title in ribbon. + - RibbonBar.Title property has been removed, since the xaml now uses Window.Title to present the window title. + - New RibbonToolTip control + \ No newline at end of file diff --git a/Odyssey/Odyssey/Common/AeroChrome.cs b/Odyssey/Odyssey/Common/AeroChrome.cs new file mode 100644 index 0000000..51354f4 --- /dev/null +++ b/Odyssey/Odyssey/Common/AeroChrome.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Odyssey.Controls +{ + + public class AeroChrome : ContentControl + { + static AeroChrome() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(AeroChrome), new FrameworkPropertyMetadata(typeof(AeroChrome))); + } + + + public bool RenderPressed + { + get { return (bool)GetValue(RenderPressedProperty); } + set { SetValue(RenderPressedProperty, value); } + } + + public static readonly DependencyProperty RenderPressedProperty = + DependencyProperty.Register("RenderPressed", typeof(bool), typeof(AeroChrome), new UIPropertyMetadata(false)); + + + public bool RenderMouseOver + { + get { return (bool)GetValue(RenderMouseOverProperty); } + set { SetValue(RenderMouseOverProperty, value); } + } + + // Using a DependencyProperty as the backing store for RenderMouseOver. This enables animation, styling, binding, etc... + public static readonly DependencyProperty RenderMouseOverProperty = + DependencyProperty.Register("RenderMouseOver", typeof(bool), typeof(AeroChrome), new UIPropertyMetadata(false)); + + + + public Brush MouseOverBackground + { + get { return (Brush)GetValue(MouseOverBackgroundProperty); } + set { SetValue(MouseOverBackgroundProperty, value); } + } + + public static readonly DependencyProperty MouseOverBackgroundProperty = + DependencyProperty.Register("MouseOverBackground", typeof(Brush), typeof(AeroChrome), new UIPropertyMetadata(null)); + + + + public Brush MousePressedBackground + { + get { return (Brush)GetValue(MousePressedBackgroundProperty); } + set { SetValue(MousePressedBackgroundProperty, value); } + } + + // Using a DependencyProperty as the backing store for MousePressedBackground. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MousePressedBackgroundProperty = + DependencyProperty.Register("MousePressedBackground", typeof(Brush), typeof(AeroChrome), new UIPropertyMetadata(null)); + + + } +} diff --git a/Odyssey/Odyssey/Common/AnimationDecorator.cs b/Odyssey/Odyssey/Common/AnimationDecorator.cs new file mode 100644 index 0000000..9da0832 --- /dev/null +++ b/Odyssey/Odyssey/Common/AnimationDecorator.cs @@ -0,0 +1,327 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Diagnostics; + +#region Licence +// Copyright (c) 2008 Thomas Gerber +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#endregion +namespace Odyssey.Controls +{ + public class AnimationDecorator : Decorator + { + private double targetHeight = 0; + + static AnimationDecorator() + { + // AnimationDecorator.ActualHeightProperty.OverrideMetadata(typeof(AnimationDecorator), new UIPropertyMetadata((double)0.0)); + } + + public AnimationDecorator() + : base() + { + ClipToBounds = true; + } + + + /// + /// Specify whether to apply opactiy animation. + /// + public bool OpacityAnimation + { + get { return (bool)GetValue(OpacityAnimationProperty); } + set { SetValue(OpacityAnimationProperty, value); } + } + + public static readonly DependencyProperty OpacityAnimationProperty = + DependencyProperty.Register("OpacityAnimation", + typeof(bool), + typeof(AnimationDecorator), + new UIPropertyMetadata(true)); + + + + /// + /// Gets or sets whether the decorator is expanded or collapsed. + /// + public bool IsExpanded + { + get { return (bool)GetValue(IsExpandedProperty); } + set { SetValue(IsExpandedProperty, value); } + } + + public static readonly DependencyProperty IsExpandedProperty = + DependencyProperty.Register("IsExpanded", + typeof(bool), + typeof(AnimationDecorator), + new PropertyMetadata(true, IsExpandedChanged)); + + + public static void IsExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + AnimationDecorator expander = d as AnimationDecorator; + bool expanded = (bool)e.NewValue; + if (expander.CanAnimate) + { + expander.AnimateExpandedChanged(expanded); + } + else + { + expander.UnanimatedExpandedChanged(expanded); + } + } + + + /// + /// Specify whether to apply animation when IsExpanded is changed. + /// + public DoubleAnimation HeightAnimation + { + get { return (DoubleAnimation)GetValue(HeightAnimationProperty); } + set { SetValue(HeightAnimationProperty, value); } + } + + public static readonly DependencyProperty HeightAnimationProperty = + DependencyProperty.Register("HeightAnimation", + typeof(DoubleAnimation), + typeof(AnimationDecorator), + new UIPropertyMetadata(null)); + + + + /// + /// Gets or sets the duration for the animation. + /// + public Duration Duration + { + get { return (Duration)GetValue(DurationProperty); } + set { SetValue(DurationProperty, value); } + } + + public static readonly DependencyProperty DurationProperty = + DependencyProperty.Register("Duration", typeof(Duration), typeof(AnimationDecorator), new UIPropertyMetadata(new Duration(new TimeSpan(0, 0, 0, 0, 250)))); + + + private bool animating = false; + + + private void UnanimatedExpandedChanged(bool expanded) + { + if (Child != null) + { + YOffset = expanded ? 0 : -Child.DesiredSize.Height; + } + } + + /// + /// Perform the animation when teh IsExpanded has changed. + /// + /// + private void AnimateExpandedChanged(bool expanded) + { + if (Child != null) + { + if (YOffset > 0) YOffset = 0; + if (-YOffset > Child.DesiredSize.Height) YOffset = -Child.DesiredSize.Height; + DoubleAnimation animation = HeightAnimation; + if (animation == null) animation = CreateDoubleAnimation(); + animation.From = null; + animation.To = expanded ? 0 : -Child.DesiredSize.Height; + animation.Completed += new EventHandler(animation_Completed); + animating = true; + this.BeginAnimation(AnimationDecorator.YOffsetProperty, animation); + + if (OpacityAnimation) + { + animation.From = null; + animation.To = expanded ? 1 : 0; + this.BeginAnimation(AnimationDecorator.AnimationOpacityProperty, animation); + } + } + else + { + YOffset = int.MinValue; + } + } + + private DoubleAnimation CreateDoubleAnimation() + { + DoubleAnimation animation = new DoubleAnimation(); + animation.DecelerationRatio = 0.8; + animation.Duration = Duration; + return animation; + } + + void animation_Completed(object sender, EventArgs e) + { + animating = false; + } + + + /// + /// Perform the animation when the child's height has changed. + /// + /// + /// + private Double AnimatedResize(Double h) + { + Double delta = targetHeight - h; + DoubleAnimation animation = HeightAnimation; + if (animation == null) animation = CreateDoubleAnimation(); + targetHeight = h; + animation.From = delta; + animation.To = 0; + this.BeginAnimation(AnimationDecorator.HeightOffsetProperty, animation); + return delta; + } + + + + + + /// + /// Gets or sets the Opacity for animation. This dependency property can be used to modify the opacity of an outer control. + /// + public double AnimationOpacity + { + get { return (double)GetValue(AnimationOpacityProperty); } + set { SetValue(AnimationOpacityProperty, value); } + } + + // Using a DependencyProperty as the backing store for AnimationOpacity. This enables animation, styling, binding, etc... + public static readonly DependencyProperty AnimationOpacityProperty = + DependencyProperty.Register("AnimationOpacity", typeof(double), typeof(AnimationDecorator), new UIPropertyMetadata((double)1.0)); + + + /// + /// A helper value for the current state while in animation. + /// + internal Double HeightOffset + { + get { return (Double)GetValue(HeightOffsetProperty); } + set { SetValue(HeightOffsetProperty, value); } + } + + internal static readonly DependencyProperty HeightOffsetProperty = + DependencyProperty.Register("HeightOffset", typeof(Double), typeof(AnimationDecorator), new FrameworkPropertyMetadata((double)0.0, + FrameworkPropertyMetadataOptions.AffectsMeasure)); + + + /// + /// A helper value for the current state while in animation. + /// + internal Double YOffset + { + get { return (Double)GetValue(YOffsetProperty); } + set { SetValue(YOffsetProperty, value); } + } + + public static readonly DependencyProperty YOffsetProperty = + DependencyProperty.Register("YOffset", typeof(Double), typeof(AnimationDecorator), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure)); + + /// + /// Measures the child element of a to prepare for arranging it during the pass. + /// + /// An upper limit that should not be exceeded. + /// + /// The target of the element. + /// + protected override Size MeasureOverride(Size constraint) + { + if (Child == null) return new Size(0, 0); + Size size; + + if (double.IsInfinity(constraint.Height)) + { + Child.Measure(new Size(constraint.Width, Double.PositiveInfinity)); + Double childHeight = Child.DesiredSize.Height; + Double deltaHeight = 0; + if (this.AnimateOnContentHeightChanged && this.IsLoaded && IsVisible && CanAnimate) + { + if (targetHeight != childHeight) + { + deltaHeight = AnimatedResize(childHeight); + if (animating) + { + AnimateExpandedChanged(IsExpanded); + } + } + } + else targetHeight = childHeight; + + double w = IsExpanded ? Child.DesiredSize.Width : 0; + size = new Size(w, Math.Max(0d, childHeight + YOffset + HeightOffset + deltaHeight)); + + } + else + { + size = base.MeasureOverride(constraint); + } + if (Child != null) + { + Child.IsEnabled = size.Height > 0; + } + if (size.Height == 0) this.AnimationOpacity = 0; + return size; + } + + + /// + /// Arranges the content of a element. + /// + /// The this element uses to arrange its child content. + /// + /// The that represents the arranged size of this element and its child. + /// + protected override Size ArrangeOverride(Size arrangeSize) + { + if (Child == null) return arrangeSize; + + Child.Arrange(new Rect(0d, YOffset, arrangeSize.Width, Child.DesiredSize.Height)); + Double h = Math.Max(0, Child.DesiredSize.Height + YOffset); + return new Size(arrangeSize.Width, h); + } + + + + public bool CanAnimate + { + get { return (bool)GetValue(CanAnimateProperty); } + set { SetValue(CanAnimateProperty, value); } + } + + // Using a DependencyProperty as the backing store for CanAnimate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CanAnimateProperty = + DependencyProperty.Register("CanAnimate", typeof(bool), typeof(AnimationDecorator), new UIPropertyMetadata(true)); + + + + + public bool AnimateOnContentHeightChanged + { + get { return (bool)GetValue(AnimateOnContentHeightChangedProperty); } + set { SetValue(AnimateOnContentHeightChangedProperty, value); } + } + + // Using a DependencyProperty as the backing store for AnimateOnContentHeightChanged. This enables animation, styling, binding, etc... + public static readonly DependencyProperty AnimateOnContentHeightChangedProperty = + DependencyProperty.Register("AnimateOnContentHeightChanged", typeof(bool), typeof(AnimationDecorator), new UIPropertyMetadata(true)); + + + + } +} diff --git a/Odyssey/Odyssey/Common/EmptyStringVisibilityConverter.cs b/Odyssey/Odyssey/Common/EmptyStringVisibilityConverter.cs new file mode 100644 index 0000000..45ce08e --- /dev/null +++ b/Odyssey/Odyssey/Common/EmptyStringVisibilityConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using System.Windows; + +namespace Odyssey.Controls +{ + public class EmptyStringVisibilityConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + string s = value as string; + return string.IsNullOrEmpty(s) ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Common/IKeyTipControl.cs b/Odyssey/Odyssey/Common/IKeyTipControl.cs new file mode 100644 index 0000000..8ccd666 --- /dev/null +++ b/Odyssey/Odyssey/Common/IKeyTipControl.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Odyssey.Controls.Interfaces +{ + public interface IKeyTipControl + { + void ExecuteKeyTip(); + } +} diff --git a/Odyssey/Odyssey/Common/PopupHelper.cs b/Odyssey/Odyssey/Common/PopupHelper.cs new file mode 100644 index 0000000..0e2150b --- /dev/null +++ b/Odyssey/Odyssey/Common/PopupHelper.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Media; + +namespace Odyssey.Common +{ + public static class PopupHelper + { + public static bool IsLogicalAncestorOf(this UIElement ancestor, UIElement child) + { + if (child != null) + { + FrameworkElement obj = child as FrameworkElement; + while (obj != null) + { + FrameworkElement parent = VisualTreeHelper.GetParent(obj) as FrameworkElement; + obj = parent == null ? obj.Parent as FrameworkElement : parent as FrameworkElement; + if (obj == ancestor) return true; + } + } + return false; + } + } +} diff --git a/Odyssey/Odyssey/Common/SkinId.cs b/Odyssey/Odyssey/Common/SkinId.cs new file mode 100644 index 0000000..3ed3019 --- /dev/null +++ b/Odyssey/Odyssey/Common/SkinId.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls.Classes +{ + public enum SkinId + { + OfficeBlue, + OfficeSilver, + OfficeBlack, + Windows7, + Vista + } +} diff --git a/Odyssey/Odyssey/Common/SkinManager.cs b/Odyssey/Odyssey/Common/SkinManager.cs new file mode 100644 index 0000000..d5d479f --- /dev/null +++ b/Odyssey/Odyssey/Common/SkinManager.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls.Classes +{ + public static class SkinManager + { + private static SkinId skinId = SkinId.OfficeBlue; + + public static event EventHandler SkinChanged; + + public static SkinId SkinId + { + get { return skinId; } + set + { + if (skinId != value) + { + skinId = value; + ApplySkin(value); + } + } + } + + private static void ApplySkin(SkinId skin) + { + var dict = Application.Current.Resources.MergedDictionaries; + dict.Remove(OfficeBlack); + dict.Remove(OfficeSilver); + dict.Remove(WindowsSeven); + dict.Remove(Vista); + switch (skin) + { + case SkinId.OfficeBlack: + dict.Add(OfficeBlack); + break; + + case SkinId.OfficeSilver: + dict.Add(OfficeSilver); + break; + + case SkinId.Windows7: + dict.Add(WindowsSeven); + break; + + case SkinId.Vista: + dict.Add(Vista); + break; + } + OnSkinChanged(); + } + + private static void OnSkinChanged() + { + if (SkinChanged != null) SkinChanged(null, EventArgs.Empty); + } + + + + private static ResourceDictionary officeSilver; + public static ResourceDictionary OfficeSilver + { + get + { + if (officeSilver == null) + { + ResourceDictionary dictionary = new ResourceDictionary(); + dictionary.Source = new Uri("/Odyssey;Component/Skins/SilverSkin.xaml", UriKind.Relative); + officeSilver = dictionary; + } + return officeSilver; + } + } + + private static ResourceDictionary officeBlack; + public static ResourceDictionary OfficeBlack + { + get + { + if (officeBlack == null) + { + ResourceDictionary dictionary = new ResourceDictionary(); + dictionary.Source = new Uri("/Odyssey;Component/Skins/BlackSkin.xaml", UriKind.Relative); + officeBlack = dictionary; + } + return officeBlack; + } + } + + private static ResourceDictionary windowSeven; + public static ResourceDictionary WindowsSeven + { + get + { + if (windowSeven == null) + { + ResourceDictionary dictionary = new ResourceDictionary(); + dictionary.Source = new Uri("/Odyssey;Component/Skins/Win7Skin.xaml", UriKind.Relative); + windowSeven = dictionary; + } + return windowSeven; + } + } + + private static ResourceDictionary vista; + public static ResourceDictionary Vista + { + get + { + if (vista == null) + { + ResourceDictionary dictionary = new ResourceDictionary(); + dictionary.Source = new Uri("/Odyssey;Component/Skins/VistaSkin.xaml", UriKind.Relative); + vista = dictionary; + } + return vista; + } + } + + } +} diff --git a/Odyssey/Odyssey/Common/Skins.cs b/Odyssey/Odyssey/Common/Skins.cs new file mode 100644 index 0000000..0eaf1d0 --- /dev/null +++ b/Odyssey/Odyssey/Common/Skins.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public static class Skins + { + } +} diff --git a/Odyssey/Odyssey/Controls/ClickableTreeView.cs b/Odyssey/Odyssey/Controls/ClickableTreeView.cs new file mode 100644 index 0000000..ea6f945 --- /dev/null +++ b/Odyssey/Odyssey/Controls/ClickableTreeView.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; + +namespace Odyssey.Controls +{ + public class ClickableTreeView:TreeView + { + + protected override bool IsItemItsOwnContainerOverride(object item) + { + return (item is Separator) || (item is TreeViewItem); + } + + protected override System.Windows.DependencyObject GetContainerForItemOverride() + { + return new ClickableTreeViewItem(); + } + } +} diff --git a/Odyssey/Odyssey/Controls/ClickableTreeViewItem.cs b/Odyssey/Odyssey/Controls/ClickableTreeViewItem.cs new file mode 100644 index 0000000..77155a7 --- /dev/null +++ b/Odyssey/Odyssey/Controls/ClickableTreeViewItem.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Input; +using System.Security; + +namespace Odyssey.Controls +{ + /// + /// A TreeViewItem that adds support for Click events and Commands. + /// + public class ClickableTreeViewItem : TreeViewItem, ICommandSource + { + static ClickableTreeViewItem() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ClickableTreeViewItem), new FrameworkPropertyMetadata(typeof(ClickableTreeViewItem))); + } + + protected override DependencyObject GetContainerForItemOverride() + { + return new ClickableTreeViewItem(); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + switch (e.Key) + { + case Key.Enter: + case Key.Space: + if (ClickMode == ClickMode.Press) + { + OnClick(); + } + else + { + IsPressed = true; + } + e.Handled = true; + break; + } + base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyEventArgs e) + { + switch (e.Key) + { + case Key.Enter: + case Key.Space: + IsPressed = false; + if (ClickMode == ClickMode.Release) + { + OnClick(); + e.Handled = true; + } + break; + } base.OnKeyUp(e); + } + + + + public ClickMode ClickMode + { + get { return (ClickMode)GetValue(ClickModeProperty); } + set { SetValue(ClickModeProperty, value); } + } + + public static readonly DependencyProperty ClickModeProperty = + DependencyProperty.Register("ClickMode", typeof(ClickMode), typeof(ClickableTreeViewItem), new UIPropertyMetadata(ClickMode.Release)); + + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + base.Focus(); + IsPressed = true; + + if (ClickMode == ClickMode.Press) + { + bool flag = true; + try + { + OnClick(); + flag = false; + } + finally + { + if (flag) + { + IsPressed = false; + base.ReleaseMouseCapture(); + } + } + } + } + + bool IsSpaceKeyDown + { + get + { + return Keyboard.IsKeyDown(Key.Space); + } + } + + bool wasPressed = false; + + protected override void OnMouseLeave(MouseEventArgs e) + { + wasPressed = (e.LeftButton == MouseButtonState.Pressed && IsPressed); + base.OnMouseLeave(e); + } + + protected override void OnMouseEnter(MouseEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed && wasPressed) + { + IsPressed = true; + } + else if (wasPressed) + { + IsPressed = false; + wasPressed = false; + } + base.OnMouseEnter(e); + } + + + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonUp(e); + bool flag = (!IsSpaceKeyDown && IsPressed) && (this.ClickMode == ClickMode.Release); + if (base.IsMouseCaptured && !IsSpaceKeyDown) + { + base.ReleaseMouseCapture(); + } + if (flag) + { + OnClick(); + } + wasPressed = false; + IsPressed = false; + } + + + + /// + /// Gets whether the ClickableTreeViewItem is pressed. + /// + public bool IsPressed + { + get { return (bool)GetValue(IsPressedProperty); } + private set { SetValue(IsPressedPropertyKey, value); } + } + + private static readonly DependencyPropertyKey IsPressedPropertyKey = + DependencyProperty.RegisterReadOnly("IsPressed", typeof(bool), typeof(ClickableTreeViewItem), new UIPropertyMetadata(false)); + + public static readonly DependencyProperty IsPressedProperty = IsPressedPropertyKey.DependencyProperty; + + + + [SecurityTreatAsSafe, SecurityCritical] + protected virtual void OnClick() + { + OnClickImpl(false); + } + + public event RoutedEventHandler Click + { + add { AddHandler(ClickEvent, value); } + remove { RemoveHandler(ClickEvent, value); } + } + + [SecurityCritical] + private void OnClickImpl(bool userInitiated) + { + RoutedEventArgs args = new RoutedEventArgs(Button.ClickEvent, this); + if (Command != null) + { + if (Command.CanExecute(CommandParameter)) + { + Command.Execute(CommandParameter); + } + } + RaiseEvent(args); + } + + #region events + + public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent("ClickEvent", + RoutingStrategy.Bubble, typeof(RoutedEventArgs), typeof(Button)); + #endregion + + + #region ICommandSource Members + + + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public static readonly DependencyProperty CommandProperty = + DependencyProperty.Register("Command", typeof(ICommand), typeof(ClickableTreeViewItem), new UIPropertyMetadata(null)); + + + + + public object CommandParameter + { + get { return (object)GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + public static readonly DependencyProperty CommandParameterProperty = + DependencyProperty.Register("CommandParameter", typeof(object), typeof(ClickableTreeViewItem), new UIPropertyMetadata(null)); + + + + + public IInputElement CommandTarget + { + get { return (IInputElement)GetValue(CommandTargetProperty); } + set { SetValue(CommandTargetProperty, value); } + } + + public static readonly DependencyProperty CommandTargetProperty = + DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(ClickableTreeViewItem), new UIPropertyMetadata(null)); + + + + #endregion + } +} diff --git a/Odyssey/Odyssey/Controls/DropDownButton.cs b/Odyssey/Odyssey/Controls/DropDownButton.cs new file mode 100644 index 0000000..0240fcd --- /dev/null +++ b/Odyssey/Odyssey/Controls/DropDownButton.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Markup; + +namespace Odyssey.Controls +{ + + [TemplatePart(Name = "PART_Popup")] + [ContentProperty("Content")] + public class DropDownButton : RibbonSplitButton + { + static DropDownButton() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(DropDownButton), new FrameworkPropertyMetadata(typeof(DropDownButton))); + } + + public DropDownButton() + : base() + { + AddHandler(ClickableTreeViewItem.ClickEvent, new RoutedEventHandler(OnMenuItemClickedEvent)); + + } + + protected override UIElement PlacementTarget + { + get + { + return DropDownButton; + } + } + + + /// + /// Gets or sets the content in the dropdown button area. + /// + public object DropDownButtonContent + { + get { return (object)GetValue(DropDownButtonContentProperty); } + set { SetValue(DropDownButtonContentProperty, value); } + } + + public static readonly DependencyProperty DropDownButtonContentProperty = + DependencyProperty.Register("DropDownButtonContent", typeof(object), typeof(DropDownButton), new UIPropertyMetadata(null)); + + } +} diff --git a/Odyssey/Odyssey/Controls/OdcButton.cs b/Odyssey/Odyssey/Controls/OdcButton.cs new file mode 100644 index 0000000..4a53c1b --- /dev/null +++ b/Odyssey/Odyssey/Controls/OdcButton.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using Odyssey.Controls.Interfaces; +using System.Windows.Controls.Primitives; + +namespace Odyssey.Controls +{ + /// + /// Derived Button that implements IKeyTipControl. + /// + public class OdcButton:Button,IKeyTipControl + { + #region IKeyTipControl Members + + void IKeyTipControl.ExecuteKeyTip() + { + OnClick(); + } + + #endregion + } + + /// + /// Derived ToggleButon that implements IKeyTipControl. + /// + public class OdcToggleButton:ToggleButton,IKeyTipControl + { + #region IKeyTipControl Members + + void IKeyTipControl.ExecuteKeyTip() + { + OnClick(); + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Controls/OdcTextBox.cs b/Odyssey/Odyssey/Controls/OdcTextBox.cs new file mode 100644 index 0000000..c8902c5 --- /dev/null +++ b/Odyssey/Odyssey/Controls/OdcTextBox.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Collections.ObjectModel; +using System.Windows.Controls.Primitives; +using System.Diagnostics; +using Odyssey.Controls.Interfaces; + +namespace Odyssey.Controls +{ + + /// + /// A TextBox that contains buttons on the right. + /// + [TemplatePart(Name = partTextBox)] + public class OdcTextBox : TextBox,IKeyTipControl + { + private const string partTextBox = "PART_TextBox"; + + static OdcTextBox() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(OdcTextBox), new FrameworkPropertyMetadata(typeof(OdcTextBox))); + UIElement.FocusableProperty.OverrideMetadata(typeof(OdcTextBox),new FrameworkPropertyMetadata(true)); + KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(OdcTextBox), new FrameworkPropertyMetadata(KeyboardNavigationMode.Local)); + KeyboardNavigation.ControlTabNavigationProperty.OverrideMetadata(typeof(OdcTextBox), new FrameworkPropertyMetadata(KeyboardNavigationMode.None)); + KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(OdcTextBox), new FrameworkPropertyMetadata(KeyboardNavigationMode.None)); + } + + + + + private ObservableCollection buttons = new ObservableCollection(); + + /// + /// Gets the collection of buttons to appear on the right of the OdcTextBox. + /// + public ObservableCollection Buttons + { + get { return buttons; } + } + + + /// + /// Gets or sets the text that appears when the textbox is empty. + /// This is a dependency property. + /// + public string Info + { + get { return (string)GetValue(InfoProperty); } + set { SetValue(InfoProperty, value); } + } + + public static readonly DependencyProperty InfoProperty = + DependencyProperty.Register("Info", typeof(string), typeof(OdcTextBox), new UIPropertyMetadata("")); + + + + #region IKeyTipControl Members + + void IKeyTipControl.ExecuteKeyTip() + { + Focus(); + SelectAll(); + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Effects/GrayscaleEffect.cs b/Odyssey/Odyssey/Effects/GrayscaleEffect.cs new file mode 100644 index 0000000..79741f9 --- /dev/null +++ b/Odyssey/Odyssey/Effects/GrayscaleEffect.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Media.Effects; +using System.Windows; +using System.Windows.Media; + +namespace Odyssey.Effects +{ + /// + /// Source from + /// http://bursjootech.blogspot.com/2008/06/grayscale-effect-pixel-shader-effect-in.html + /// + public class GrayscaleEffect : ShaderEffect + { + private static PixelShader pixelShader = new PixelShader() { UriSource = new Uri(@"pack://application:,,,/Odyssey;Component/Effects/GrayScaleEffect.ps") }; + + public GrayscaleEffect() + { + PixelShader = pixelShader; + UpdateShaderValue(InputProperty); + UpdateShaderValue(DesaturationFactorProperty); + } + + public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(GrayscaleEffect), 0); + + public Brush Input + { + get { return (Brush)GetValue(InputProperty); } + set { SetValue(InputProperty, value); } + } + + public static readonly DependencyProperty DesaturationFactorProperty = DependencyProperty.Register("DesaturationFactor", + typeof(double), typeof(GrayscaleEffect), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(0), CoerceDesaturationFactor)); + + public double DesaturationFactor + { + get { return (double)GetValue(DesaturationFactorProperty); } + set { SetValue(DesaturationFactorProperty, value); } + } + + private static object CoerceDesaturationFactor(DependencyObject d, object value) + { + GrayscaleEffect effect = (GrayscaleEffect)d; + double newFactor = (double)value; + if (newFactor < 0.0 || newFactor > 1.0) + { + return effect.DesaturationFactor; + } + return newFactor; + } + } +} + diff --git a/Odyssey/Odyssey/Effects/GrayscaleEffect.fx b/Odyssey/Odyssey/Effects/GrayscaleEffect.fx new file mode 100644 index 0000000..5be422b --- /dev/null +++ b/Odyssey/Odyssey/Effects/GrayscaleEffect.fx @@ -0,0 +1,16 @@ +sampler2D implicitInput : register(s0); +float factor : register(c0); + +float4 main(float2 uv : TEXCOORD) : COLOR +{ + float4 color = tex2D(implicitInput, uv); + float gray = color.r * 0.3 + color.g * 0.59 + color.b *0.11; + + float4 result; + result.r = (color.r - gray) * factor + gray; + result.g = (color.g - gray) * factor + gray; + result.b = (color.b - gray) * factor + gray; + result.a = color.a; + + return result; +} \ No newline at end of file diff --git a/Odyssey/Odyssey/Effects/GrayscaleEffect.ps b/Odyssey/Odyssey/Effects/GrayscaleEffect.ps new file mode 100644 index 0000000000000000000000000000000000000000..80910e48f4f131e0be2f3b0cbb6c8652004dd933 GIT binary patch literal 400 zcmY+8K}*9x5QX2aDFhJ$Uc3nAsFxt22YaYuE2t0;T6+^pi53H?32D4}2>1uQwx>Ne zApJ{1euwMZBqHuIJM+Go-F=W+e`^oJ`}4s*fOiJSB$Lq)aNn3N7={U+0kc^Tj4g&? zu>oAOX_zM&O4AYbZg@0L<0y>s>zDL3M_F3f`PWYu6Mu4yn<&hZMKa6X!@DE*Di{Us z=z041l)1xXo<=c?p6B~suWM5G1h+s(z)}6}y|dMFdHQ+z)i*yLG+>>F8Y)yt@mvge2Q_buzljbg2{a!m~wanZ% S??TG@PhHpaRY+8Y*1!)G1xJ?v literal 0 HcmV?d00001 diff --git a/Odyssey/Odyssey/ExplorerBar/ExplorerBar.cs b/Odyssey/Odyssey/ExplorerBar/ExplorerBar.cs new file mode 100644 index 0000000..e8aa326 --- /dev/null +++ b/Odyssey/Odyssey/ExplorerBar/ExplorerBar.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +#region Licence +// Copyright (c) 2008 Thomas Gerber +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#endregion +namespace Odyssey.Controls +{ + public class ExplorerBar : ItemsControl + { + static ExplorerBar() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ExplorerBar), new FrameworkPropertyMetadata(typeof(ExplorerBar))); + } + } +} diff --git a/Odyssey/Odyssey/ExplorerBar/OdcExpander.cs b/Odyssey/Odyssey/ExplorerBar/OdcExpander.cs new file mode 100644 index 0000000..28c3ce2 --- /dev/null +++ b/Odyssey/Odyssey/ExplorerBar/OdcExpander.cs @@ -0,0 +1,348 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Markup; +using System.IO; +using System.Xml; +using Odyssey.Controls.Interfaces; + +#region Licence +// Copyright (c) 2008 Thomas Gerber +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#endregion +namespace Odyssey.Controls +{ + /// + /// An Expander with animation. + /// + public class OdcExpander : HeaderedContentControl,IKeyTipControl + { + static OdcExpander() + { + MarginProperty.OverrideMetadata( + typeof(OdcExpander), + new FrameworkPropertyMetadata(new Thickness(10, 10, 10, 2))); + + FocusableProperty.OverrideMetadata(typeof(OdcExpander), + new FrameworkPropertyMetadata(false)); + + DefaultStyleKeyProperty.OverrideMetadata(typeof(OdcExpander), + new FrameworkPropertyMetadata(typeof(OdcExpander))); + } + + /// + /// Gets or sets the custom skin for the control. + /// + public static string Skin { get; set; } + + + protected override void OnInitialized(EventArgs e) + { + base.OnInitialized(e); + ApplySkin(); + } + + public void ApplySkin() + { + if (!string.IsNullOrEmpty(Skin)) + { + Uri uri = new Uri(Skin, UriKind.Absolute); + ResourceDictionary skin = new ResourceDictionary(); + skin.Source = uri; + this.Resources = skin; + } + } + + public Brush HeaderBorderBrush + { + get { return (Brush)GetValue(HeaderBorderBrushProperty); } + set { SetValue(HeaderBorderBrushProperty, value); } + } + + public static readonly DependencyProperty HeaderBorderBrushProperty = + DependencyProperty.Register("HeaderBorderBrush", + typeof(Brush), typeof(OdcExpander), new UIPropertyMetadata(Brushes.Gray)); + + public Brush HeaderBackground + { + get { return (Brush)GetValue(HeaderBackgroundProperty); } + set { SetValue(HeaderBackgroundProperty, value); } + } + + public static readonly DependencyProperty HeaderBackgroundProperty = + DependencyProperty.Register("HeaderBackground", + typeof(Brush), typeof(OdcExpander), new UIPropertyMetadata(Brushes.Silver)); + + + public bool IsMinimized + { + get { return (bool)GetValue(IsMinimizedProperty); } + set { SetValue(IsMinimizedProperty, value); } + } + + public static readonly DependencyProperty IsMinimizedProperty = + DependencyProperty.Register("IsMinimized", + typeof(bool), typeof(OdcExpander), + new UIPropertyMetadata(false, OnMinimizedPropertyChanged)); + + public static void OnMinimizedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + bool minimized = (bool)e.NewValue; + OdcExpander expander = d as OdcExpander; + RoutedEventArgs args = new RoutedEventArgs(minimized ? MinimizedEvent : MaximizedEvent); + expander.IsEnabled = !minimized; + expander.RaiseEvent(args); + } + + + /// + /// Gets or sets the ImageSource for the image in the header. + /// + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(ImageSource), typeof(OdcExpander), new UIPropertyMetadata(null)); + + + + public bool IsExpanded + { + get { return (bool)GetValue(IsExpandedProperty); } + set { SetValue(IsExpandedProperty, value); } + } + + public event RoutedEventHandler Expanded + { + add { AddHandler(ExpandedEvent, value); } + remove { RemoveHandler(ExpandedEvent, value); } + } + + public event RoutedEventHandler Collapsed + { + add { AddHandler(CollapsedEvent, value); } + remove { RemoveHandler(CollapsedEvent, value); } + } + + public event RoutedEventHandler Minimized + { + add { AddHandler(MinimizedEvent, value); } + remove { RemoveHandler(MinimizedEvent, value); } + } + + public event RoutedEventHandler Maximized + { + add { AddHandler(MaximizedEvent, value); } + remove { RemoveHandler(MaximizedEvent, value); } + } + + #region dependency properties and routed events definition + + public static readonly DependencyProperty IsExpandedProperty = + DependencyProperty.Register( + "IsExpanded", + typeof(bool), + typeof(OdcExpander), + new UIPropertyMetadata(true, IsExpandedChanged)); + + + public static void IsExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OdcExpander expander = d as OdcExpander; + RoutedEventArgs args = new RoutedEventArgs((bool)e.NewValue ? ExpandedEvent : CollapsedEvent); + expander.RaiseEvent(args); + } + + public static readonly RoutedEvent ExpandedEvent = EventManager.RegisterRoutedEvent( + "ExpandedEvent", + RoutingStrategy.Bubble, + typeof(RoutedEventHandler), + typeof(OdcExpander)); + + + public static readonly RoutedEvent CollapsedEvent = EventManager.RegisterRoutedEvent( + "CollapsedEvent", + RoutingStrategy.Bubble, + typeof(RoutedEventHandler), + typeof(OdcExpander)); + + public static readonly RoutedEvent MinimizedEvent = EventManager.RegisterRoutedEvent( + "MinimizedEvent", + RoutingStrategy.Bubble, + typeof(RoutedEventHandler), + typeof(OdcExpander)); + + + public static readonly RoutedEvent MaximizedEvent = EventManager.RegisterRoutedEvent( + "MaximizedEvent", + RoutingStrategy.Bubble, + typeof(RoutedEventHandler), + typeof(OdcExpander)); + + #endregion + + + /// + /// Gets or sets the corner radius for the header. + /// + public CornerRadius CornerRadius + { + get { return (CornerRadius)GetValue(CornerRadiusProperty); } + set { SetValue(CornerRadiusProperty, value); } + } + + public static readonly DependencyProperty CornerRadiusProperty = + DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(OdcExpander), new UIPropertyMetadata(null)); + + + + /// + /// Gets or sets the background color of the header on mouse over. + /// + public Brush MouseOverHeaderBackground + { + get { return (Brush)GetValue(MouseOverHeaderBackgroundProperty); } + set { SetValue(MouseOverHeaderBackgroundProperty, value); } + } + + public static readonly DependencyProperty MouseOverHeaderBackgroundProperty = + DependencyProperty.Register("MouseOverHeaderBackground", typeof(Brush), typeof(OdcExpander), new UIPropertyMetadata(null)); + + + /// + /// Gets whether the PressedBackground is not null. + /// + public bool HasPressedBackground + { + get { return (bool)GetValue(HasPressedBackgroundProperty); } + set { SetValue(HasPressedBackgroundProperty, value); } + } + + public static readonly DependencyProperty HasPressedBackgroundProperty = + DependencyProperty.Register("HasPressedBackground", typeof(bool), typeof(OdcExpander), new UIPropertyMetadata(false)); + + + + /// + /// Gets or sets the background color of the header in pressed mode. + /// + public Brush PressedHeaderBackground + { + get { return (Brush)GetValue(PressedHeaderBackgroundProperty); } + set { SetValue(PressedHeaderBackgroundProperty, value); } + } + + public static readonly DependencyProperty PressedHeaderBackgroundProperty = + DependencyProperty.Register("PressedHeaderBackground", typeof(Brush), typeof(OdcExpander), + new UIPropertyMetadata(null, PressedHeaderBackgroundPropertyChangedCallback)); + + + public static void PressedHeaderBackgroundPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OdcExpander expander = (OdcExpander)d; + expander.HasPressedBackground = e.NewValue != null; + } + + public Thickness HeaderBorderThickness + { + get { return (Thickness)GetValue(HeaderBorderThicknessProperty); } + set { SetValue(HeaderBorderThicknessProperty, value); } + } + + // Using a DependencyProperty as the backing store for HeaderBorderThickness. This enables animation, styling, binding, etc... + public static readonly DependencyProperty HeaderBorderThicknessProperty = + DependencyProperty.Register("HeaderBorderThickness", typeof(Thickness), typeof(OdcExpander), new UIPropertyMetadata(null)); + + + + + /// + /// Gets or sets the foreground color of the header on mouse over. + /// + public Brush MouseOverHeaderForeground + { + get { return (Brush)GetValue(MouseOverHeaderForegroundProperty); } + set { SetValue(MouseOverHeaderForegroundProperty, value); } + } + + public static readonly DependencyProperty MouseOverHeaderForegroundProperty = + DependencyProperty.Register("MouseOverHeaderForeground", typeof(Brush), typeof(OdcExpander), new UIPropertyMetadata(null)); + + + + /// + /// Specifies whether to show a elipse with the expanded/collapsed image. + /// + public bool ShowEllipse + { + get { return (bool)GetValue(ShowEllipseProperty); } + set { SetValue(ShowEllipseProperty, value); } + } + + public static readonly DependencyProperty ShowEllipseProperty = + DependencyProperty.Register("ShowEllipse", typeof(bool), typeof(OdcExpander), new UIPropertyMetadata(false)); + + + + + /// + /// Gets or sets whether animation is possible + /// + public bool CanAnimate + { + get { return (bool)GetValue(CanAnimateProperty); } + set { SetValue(CanAnimateProperty, value); } + } + + public static readonly DependencyProperty CanAnimateProperty = + DependencyProperty.Register("CanAnimate", typeof(bool), typeof(OdcExpander), new UIPropertyMetadata(true)); + + + + + public bool IsHeaderVisible + { + get { return (bool)GetValue(IsHeaderVisibleProperty); } + set { SetValue(IsHeaderVisibleProperty, value); } + } + + public static readonly DependencyProperty IsHeaderVisibleProperty = + DependencyProperty.Register("IsHeaderVisible", typeof(bool), typeof(OdcExpander), new UIPropertyMetadata(true)); + + + + #region IKeyTipControl Members + + void IKeyTipControl.ExecuteKeyTip() + { + this.IsExpanded ^= true; + if (this.IsExpanded) + { + FrameworkElement e = Content as FrameworkElement; + if (e != null && e.Focusable) e.Focus(); + } + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/ExplorerBar/OdcExpanderHeader.cs b/Odyssey/Odyssey/ExplorerBar/OdcExpanderHeader.cs new file mode 100644 index 0000000..44ef645 --- /dev/null +++ b/Odyssey/Odyssey/ExplorerBar/OdcExpanderHeader.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Controls.Primitives; + +#region Licence +// Copyright (c) 2008 Thomas Gerber +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#endregion +namespace Odyssey.Controls +{ + /// + /// A helper class to specify the header of an OdcExpander. + /// + internal class OdcExpanderHeader : ToggleButton + { + static OdcExpanderHeader() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(OdcExpanderHeader), new FrameworkPropertyMetadata(typeof(OdcExpanderHeader))); + } + + + /// + /// Gets whether the expand geometry is not null. + /// + public bool HasExpandGeometry + { + get { return (bool)GetValue(HasExpandGeometryProperty); } + set { SetValue(HasExpandGeometryProperty, value); } + } + + public static readonly DependencyProperty HasExpandGeometryProperty = + DependencyProperty.Register("HasExpandGeometry", typeof(bool), typeof(OdcExpanderHeader), new UIPropertyMetadata(false)); + + + + /// + /// Gets or sets the geometry for the collapse symbol. + /// + public Geometry CollapseGeometry + { + get { return (Geometry)GetValue(CollapseGeometryProperty); } + set { SetValue(CollapseGeometryProperty, value); } + } + + public static readonly DependencyProperty CollapseGeometryProperty = + DependencyProperty.Register("CollapseGeometry", typeof(Geometry), typeof(OdcExpanderHeader), + new UIPropertyMetadata(null)); + + + public static void CollapseGeometryChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OdcExpanderHeader eh = d as OdcExpanderHeader; + eh.HasExpandGeometry = e.NewValue != null; + } + + + /// + /// Gets or sets the geometry for the expand symbol. + /// + public Geometry ExpandGeometry + { + get { return (Geometry)GetValue(ExpandGeometryProperty); } + set { SetValue(ExpandGeometryProperty, value); } + } + + public static readonly DependencyProperty ExpandGeometryProperty = + DependencyProperty.Register("ExpandGeometry", typeof(Geometry), typeof(OdcExpanderHeader), new UIPropertyMetadata(null, CollapseGeometryChangedCallback)); + + + + /// + /// Gets or sets the corner radius for the header. + /// + public CornerRadius CornerRadius + { + get { return (CornerRadius)GetValue(CornerRadiusProperty); } + set { SetValue(CornerRadiusProperty, value); } + } + + // Using a DependencyProperty as the backing store for CornerRadius. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CornerRadiusProperty = + DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(OdcExpanderHeader), new UIPropertyMetadata(null)); + + + + /// + /// Gets or sets whether to display the ellipse arround the collapse/expand symbol. + /// + public bool ShowEllipse + { + get { return (bool)GetValue(ShowEllipseProperty); } + set { SetValue(ShowEllipseProperty, value); } + } + + // Using a DependencyProperty as the backing store for ShowEllipse. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ShowEllipseProperty = + DependencyProperty.Register("ShowEllipse", typeof(bool), typeof(OdcExpanderHeader), new UIPropertyMetadata(true)); + + + + /// + /// Gets or sets the Image to display on the header. + /// + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for Image. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(ImageSource), typeof(OdcExpanderHeader), new UIPropertyMetadata(null)); + + } +} diff --git a/Odyssey/Odyssey/Odyssey.csproj b/Odyssey/Odyssey/Odyssey.csproj new file mode 100644 index 0000000..558fea2 --- /dev/null +++ b/Odyssey/Odyssey/Odyssey.csproj @@ -0,0 +1,725 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {333FDC55-6B47-4A64-A2DF-A4C5823FAC74} + Library + Properties + Odyssey + Odyssey + v3.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + SAK + SAK + SAK + SAK + + + + + + + 4.0 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + true + GlobalSuppressions.cs + prompt + AllRules.ruleset + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + true + GlobalSuppressions.cs + prompt + AllRules.ruleset + + + + 3.0 + + + + 3.5 + + + + + 3.0 + + + 3.0 + + + 3.0 + + + 3.0 + + + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/OutlookBar/ExpandPosition.cs b/Odyssey/Odyssey/OutlookBar/ExpandPosition.cs new file mode 100644 index 0000000..3ce2882 --- /dev/null +++ b/Odyssey/Odyssey/OutlookBar/ExpandPosition.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Odyssey.Controls +{ + public enum ExpandPosition + { + Left, + Right + } +} diff --git a/Odyssey/Odyssey/OutlookBar/OutlookBar.cs b/Odyssey/Odyssey/OutlookBar/OutlookBar.cs new file mode 100644 index 0000000..29204cf --- /dev/null +++ b/Odyssey/Odyssey/OutlookBar/OutlookBar.cs @@ -0,0 +1,973 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Collections.ObjectModel; +using System.Windows.Controls.Primitives; +using System.Diagnostics; +using System.Collections; +using System.ComponentModel; +using Odyssey.Controls.Interfaces; + +#region changelog +// Version 1.3.17 +// +// - do not catch the SizeChanged event in design mode: +// suggested by maze1610 http://www.codeplex.com/odyssey/WorkItem/View.aspx?WorkItemId=1074 +// - renamed Property Dock to DockPosition. +// suggested by maze1610 http://www.codeplex.com/odyssey/WorkItem/View.aspx?WorkItemId=1075 +// thanx for your help. +#endregion +namespace Odyssey.Controls +{ + //UNDONE: Section.Content sometimes not visible when IsMaximized has changed. + [TemplatePart(Name = partMinimizedButtonContainer)] + public class OutlookBar : HeaderedItemsControl,IKeyTipControl + { + const string partMinimizedButtonContainer = "PART_MinimizedContainer"; + const string partPopup = "PART_Popup"; + static OutlookBar() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(OutlookBar), new FrameworkPropertyMetadata(typeof(OutlookBar))); + } + + private Collection maximizedSections; + private Collection minimizedSections; + private FrameworkElement minimizedButtonContainer; + + + public OutlookBar() + : base() + { + overflowMenu = new Collection(); + SetValue(OutlookBar.OverflowMenuItemsPropertyKey, overflowMenu); + SetValue(OutlookBar.OptionButtonsPropertyKey, new Collection()); + + CommandBindings.Add(new CommandBinding(CollapseCommand, CollapseCommandExecuted)); + CommandBindings.Add(new CommandBinding(StartDraggingCommand, StartDraggingCommandExecuted)); + CommandBindings.Add(new CommandBinding(ShowPopupCommand, ShowPopupCommandExecuted)); + CommandBindings.Add(new CommandBinding(ResizeCommand, ResizeCommandExecuted)); + CommandBindings.Add(new CommandBinding(CloseCommand, CloseCommandExecuted)); + + maximizedSections = new Collection(); + minimizedSections = new Collection(); + sections = new ObservableCollection(); + sections.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(SectionsCollectionChanged); + + // do not catch the SizeChanged event in design mode: + // suggested by maze1610 http://www.codeplex.com/odyssey/WorkItem/View.aspx?WorkItemId=1074: + + if (!DesignerProperties.GetIsInDesignMode(this)) + { + this.SizeChanged += new SizeChangedEventHandler(OutlookBar_SizeChanged); + } + } + + void OutlookBar_SizeChanged(object sender, SizeChangedEventArgs e) + { + ApplySections(); + } + + + + private void CollapseCommandExecuted(object sender, ExecutedRoutedEventArgs e) + { + IsMaximized ^= true; + } + + private void ShowPopupCommandExecuted(object sender, ExecutedRoutedEventArgs e) + { + if (!IsMaximized) + { + IsPopupVisible = true; + } + } + + private void ResizeCommandExecuted(object sender, ExecutedRoutedEventArgs e) + { + Control c = e.OriginalSource as Control; + if (c != null) + { + c.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(DragMouseLeftButtonUp); + } + this.PreviewMouseMove += new MouseEventHandler(PreviewMouseMoveResize); + } + + private void CloseCommandExecuted(object sender, ExecutedRoutedEventArgs e) + { + this.Visibility = Visibility.Collapsed; + } + + void PreviewMouseMoveResize(object sender, MouseEventArgs e) + { + Control c = e.OriginalSource as Control; + if (e.LeftButton == MouseButtonState.Pressed) + { + if (DockPosition == HorizontalAlignment.Left) + { + ResizeFromRight(e); + } + else + { + ResizeFromLeft(e); + } + } + else this.PreviewMouseMove -= PreviewMouseMoveResize; + } + + private void ResizeFromLeft(MouseEventArgs e) + { + Point pos = e.GetPosition(this); + double w = this.ActualWidth - pos.X; + + if (w < 80) + { + w = double.NaN; + IsMaximized = false; + } + else + { + IsMaximized = true; + } + if (MaxWidth != double.NaN && w > MaxWidth) w = MaxWidth; + Width = w; + } + private void ResizeFromRight(MouseEventArgs e) + { + Point pos = e.GetPosition(this); + double w = pos.X; + + if (w < 80) + { + w = double.NaN; + IsMaximized = false; + } + else + { + IsMaximized = true; + } + if (MaxWidth != double.NaN && w > MaxWidth) w = MaxWidth; + Width = w; + } + + private void StartDraggingCommandExecuted(object sender, ExecutedRoutedEventArgs e) + { + Control c = e.OriginalSource as Control; + if (c != null) + { + c.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(DragMouseLeftButtonUp); + } + this.PreviewMouseMove += new MouseEventHandler(PreviewMouseMoveButtons); + } + + /// + /// Remove all PreviewMouseMove events from the outlookbar that have been possible set at the beginning of a drag command. + /// + void DragMouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + Control c = e.OriginalSource as Control; + if (c != null) + { + c.PreviewMouseLeftButtonUp -= DragMouseLeftButtonUp; + } + this.PreviewMouseMove -= PreviewMouseMoveButtons; + this.PreviewMouseMove -= PreviewMouseMoveResize; + } + + void PreviewMouseMoveButtons(object sender, MouseEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed) + { + Point pos = e.GetPosition(this); + double h = this.ActualHeight - 1 - ButtonHeight - pos.Y; + MaxNumberOfButtons = (int)(h / ButtonHeight); + } + else this.PreviewMouseMove -= PreviewMouseMoveButtons; + } + + protected override void OnInitialized(EventArgs e) + { + base.OnInitialized(e); + ApplySections(); + } + + + + /// + /// Determine the collection of MinimizedSections and MaximizedSections depending on the MaxVisibleButtons Property. + /// + protected virtual void ApplySections() + { + if (this.IsInitialized) + { + maximizedSections = new Collection(); + minimizedSections = new Collection(); + int max = MaxNumberOfButtons; + int index = 0; + int selectedIndex = SelectedSectionIndex; + OutlookSection selectedContent = null; + + int n = GetNumberOfMinimizedButtons(); + + foreach (OutlookSection e in sections) + { + e.OutlookBar = this; + e.Height = ButtonHeight; + if (max-- > 0) + { + e.IsMaximized = true; + maximizedSections.Add(e); + } + else + { + e.IsMaximized = false; + if (minimizedSections.Count < n) + { + minimizedSections.Add(e); + } + } + bool selected = index++ == selectedIndex; + e.IsSelected = selected; + if (selected) selectedContent = e; + } + SetValue(OutlookBar.MaximizedSectionsPropertyKey, maximizedSections); + SetValue(OutlookBar.MinimizedSectionsPropertyKey, minimizedSections); + SelectedSection = selectedContent; + } + + } + + + private Collection overflowMenu; + + /// + /// Gets or sets the default items for the overflow menu. + /// + [Bindable(true)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public Collection OverflowMenuItems + { + get { return overflowMenu; } + // private set { overflowMenu = value; } + } + + public static readonly DependencyProperty OverflowMenuProperty = + DependencyProperty.Register("OverflowMenu", typeof(ItemCollection), typeof(OutlookBar), new UIPropertyMetadata(null)); + + + + + private void ApplyOverflowMenu() + { + Collection overflowItems = new Collection(); + if (OverflowMenuItems.Count > 0) + { + foreach (object item in OverflowMenuItems) + { + overflowItems.Add(item); + } + } + + bool separatorAdded = false; + int visibleButtons = maximizedSections.Count + (IsMaximized ? minimizedSections.Count : 0); + + for (int i = visibleButtons; i < sections.Count; i++) + { + if (!separatorAdded) + { + overflowItems.Add(new Separator()); + separatorAdded = true; + } + OutlookSection section = sections[i]; + MenuItem item = new MenuItem(); + item.Header = section.Header; + Image image = new Image(); + image.Source = section.Image; + item.Icon = image; + item.Tag = section; + item.Click += new RoutedEventHandler(item_Click); + overflowItems.Add(item); + } + + SetValue(OutlookBar.OverflowMenuItemsPropertyKey, overflowItems); + } + + + + + + private int GetNumberOfMinimizedButtons() + { + if (minimizedButtonContainer != null) + { + const double width = 32; + const double overflowWidth = 18; + double fraction = (minimizedButtonContainer.ActualWidth - overflowWidth) / width; + int minimizedButtons = (int)Math.Truncate(fraction); + int visibleButtons = MaxNumberOfButtons + minimizedButtons; + return visibleButtons; + } + return 0; + } + + public event EventHandler OverflowMenuCreated; + + protected virtual void OnOverflowMenuCreated(Collection menuItems) + { + if (OverflowMenuCreated != null) + { + OverflowMenuCreatedEventArgs e = new OverflowMenuCreatedEventArgs(menuItems); + OverflowMenuCreated(this, e); + } + } + + void item_Click(object sender, RoutedEventArgs e) + { + MenuItem item = e.OriginalSource as MenuItem; + OutlookSection section = item.Tag as OutlookSection; + this.SelectedSection = section; + } + + + void SectionsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + ApplySections(); + } + + private Popup popup; + + public override void OnApplyTemplate() + { + if (popup != null) + { + popup.Closed -= OnPopupClosed; + popup.Opened -= OnPopupOpened; + } + minimizedButtonContainer = this.GetTemplateChild(partMinimizedButtonContainer) as FrameworkElement; + popup = this.GetTemplateChild(partPopup) as Popup; + if (popup != null) + { + popup.Closed += new EventHandler(OnPopupClosed); + popup.Opened += new EventHandler(OnPopupOpened); + } + + ToggleButton btn = GetTemplateChild("PART_ToggleButton") as ToggleButton; + if (btn != null) + { + btn.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(btn_PreviewMouseLeftButtonUp); + } + + base.OnApplyTemplate(); + } + + void btn_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + Mouse.Capture(null); + popup.StaysOpen = false; + } + + + + protected virtual void OnPopupOpened(object sender, EventArgs e) + { + IsPopupVisible = true; + Mouse.Capture(this, CaptureMode.SubTree); + } + + protected virtual void OnPopupClosed(object sender, EventArgs e) + { + IsPopupVisible = false; + Mouse.Capture(null); + } + + + + private ObservableCollection sections; + + /// + /// Gets the collection of sections. + /// + [Bindable(true)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public Collection Sections + { + get { return sections; } + // private set { sections = value as ObservableCollection; } + } + + + private ObservableCollection sideButtons = new ObservableCollection(); + + /// + /// Gets the buttons on the side when IsExpanded is set to false and ShowSideButtons is set to true. + /// + public Collection SideButtons + { + get { return sideButtons; } + } + + + + /// + /// Gets or sets whether to show the SideButtons when IsExpanded is set to false. + /// + public bool ShowSideButtons + { + get { return (bool)GetValue(ShowSideButtonsProperty); } + set { SetValue(ShowSideButtonsProperty, value); } + } + + public static readonly DependencyProperty ShowSideButtonsProperty = + DependencyProperty.Register("ShowSideButtons", typeof(bool), typeof(OutlookBar), new UIPropertyMetadata(true)); + + + /// + /// Gets or sets whether the Outlookbar is Maximized or Minimized. + /// + public bool IsMaximized + { + get { return (bool)GetValue(IsMaximizedProperty); } + set { SetValue(IsMaximizedProperty, value); } + } + + public static readonly DependencyProperty IsMaximizedProperty = + DependencyProperty.Register("IsMaximized", typeof(bool), typeof(OutlookBar), new UIPropertyMetadata(true, MaximizedPropertyChanged)); + + private static void MaximizedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OutlookBar bar = (OutlookBar)d; + bar.OnMaximizedChanged((bool)e.NewValue); + } + + private double previousMaxWidth = double.PositiveInfinity; + + /// + /// Occurs when the IsMaximized property has changed. + /// + /// + protected virtual void OnMaximizedChanged(bool isExpanded) + { + if (isExpanded) IsPopupVisible = false; + EnsureSectionContentIsVisible(); + + if (isExpanded) + { + MaxWidth = previousMaxWidth; + RaiseEvent(new RoutedEventArgs(ExpandedEvent)); + } + else + { + previousMaxWidth = MaxWidth; + MaxWidth = MinimizedWidth + (CanResize ? 4 : 0); + RaiseEvent(new RoutedEventArgs(CollapsedEvent)); + } + } + + + + /// + /// Occurs after the OutlookBar has collapsed. + /// + public event RoutedEventHandler Collapsed + { + add { AddHandler(OutlookBar.CollapsedEvent, value); } + remove { RemoveHandler(OutlookBar.CollapsedEvent, value); } + } + + /// + /// Occurs after the OutlookBar has expanded. + /// + public event RoutedEventHandler Expanded + { + add { AddHandler(OutlookBar.ExpandedEvent, value); } + remove { RemoveHandler(OutlookBar.ExpandedEvent, value); } + } + + public static readonly RoutedEvent CollapsedEvent = EventManager.RegisterRoutedEvent("CollapsedEvent", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(OutlookBar)); + + public static readonly RoutedEvent ExpandedEvent = EventManager.RegisterRoutedEvent("ExpandedEvent", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(OutlookBar)); + + /// + /// This code ensures that the section content is visible when the IsExpanded has changed, + /// since the parent of the content could have changed either. + /// + private void EnsureSectionContentIsVisible() + { + object content = SelectedSection != null ? SelectedSection.Content : null; + SectionContent = null; // set temporarily to null, so resetting to the current content will have an effect. + CollapsedSectionContent = IsMaximized ? null : content; + SectionContent = IsMaximized ? content : null; + } + + + /// + /// Gets or sets the width when IsExpanded is set to false. + /// + public double MinimizedWidth + { + get { return (double)GetValue(MinimizedWidthProperty); } + set { SetValue(MinimizedWidthProperty, value); } + } + + public static readonly DependencyProperty MinimizedWidthProperty = + DependencyProperty.Register("MinimizedWidth", typeof(double), typeof(OutlookBar), new UIPropertyMetadata((double)32)); + + + + + /// + /// Gets or sets how to align template of the OutlookBar. + /// Currently, only Left or Right is supported! + /// + /// + /// This property has been renamed from Dock to DockPosition due to a suggestion from maze6210: + /// http://www.codeplex.com/odyssey/WorkItem/View.aspx?WorkItemId=1075 + /// + public HorizontalAlignment DockPosition + { + get { return (HorizontalAlignment)GetValue(DockPositionProperty); } + set { SetValue(DockPositionProperty, value); } + } + + public static readonly DependencyProperty DockPositionProperty = + DependencyProperty.Register("DockPosition", typeof(HorizontalAlignment), typeof(OutlookBar), new UIPropertyMetadata(HorizontalAlignment.Left)); + + private static readonly DependencyPropertyKey MaximizedSectionsPropertyKey = + DependencyProperty.RegisterReadOnly("MaximizedSections", typeof(Collection), typeof(OutlookBar), new UIPropertyMetadata(null)); + public static readonly DependencyProperty MaximizedSectionsProperty = MaximizedSectionsPropertyKey.DependencyProperty; + + private static readonly DependencyPropertyKey MinimizedSectionsPropertyKey = + DependencyProperty.RegisterReadOnly("MinimizedSections", typeof(Collection), typeof(OutlookBar), new UIPropertyMetadata(null)); + public static readonly DependencyProperty MinimizedSectionsProperty = MinimizedSectionsPropertyKey.DependencyProperty; + + private static readonly DependencyPropertyKey OverflowMenuItemsPropertyKey = + DependencyProperty.RegisterReadOnly("OverflowMenuItems", typeof(Collection), typeof(OutlookBar), new UIPropertyMetadata(null)); + public static readonly DependencyProperty OverflowMenuItemsProperty = OverflowMenuItemsPropertyKey.DependencyProperty; + + + /// + /// Gets or sets how many buttons are completely visible. + /// + public int MaxNumberOfButtons + { + get { return (int)GetValue(MaxNumberOfButtonsProperty); } + set { SetValue(MaxNumberOfButtonsProperty, value); } + } + + public static readonly DependencyProperty MaxNumberOfButtonsProperty = + DependencyProperty.Register("MaxNumberOfButtons", typeof(int), typeof(OutlookBar), + new FrameworkPropertyMetadata(int.MaxValue, + FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, + MaxNumberOfButtonsPropertyChanged)); + + private static void MaxNumberOfButtonsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OutlookBar bar = (OutlookBar)d; + bar.ApplySections(); + } + + + /// + /// Gets or sets whether the popup panel is visible. + /// + public bool IsPopupVisible + { + get { return (bool)GetValue(IsPopupVisibleProperty); } + set { SetValue(IsPopupVisibleProperty, value); } + } + + public static readonly DependencyProperty IsPopupVisibleProperty = + DependencyProperty.Register("IsPopupVisible", typeof(bool), typeof(OutlookBar), new UIPropertyMetadata(false, PopupVisiblePropertyChanged)); + + private static void PopupVisiblePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OutlookBar bar = (OutlookBar)d; + bar.OnPopupVisibleChanged((bool)e.NewValue); + } + + /// + /// Occurs when the IsPopupVisible has changed. + /// + /// + protected virtual void OnPopupVisibleChanged(bool isPopupVisible) + { + if (popup != null) + { + popup.StaysOpen = true; + popup.IsOpen = isPopupVisible; + } + if (isPopupVisible) + { + RaiseEvent(new RoutedEventArgs(PopupOpenedEvent)); + } + else + { + RaiseEvent(new RoutedEventArgs(PopupClosedEvent)); + } + } + + /// + /// Occurs after the Popup has opened. + /// + public event RoutedEventHandler PopupOpened + { + add { AddHandler(OutlookBar.PopupOpenedEvent, value); } + remove { RemoveHandler(OutlookBar.PopupOpenedEvent, value); } + } + + /// + /// Occurs after the Popup has closed. + /// + public event RoutedEventHandler PopupClosed + { + add { AddHandler(OutlookBar.PopupClosedEvent, value); } + remove { RemoveHandler(OutlookBar.PopupClosedEvent, value); } + } + + public static readonly RoutedEvent PopupOpenedEvent = EventManager.RegisterRoutedEvent("PopupOpenedEvent", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(OutlookBar)); + + public static readonly RoutedEvent PopupClosedEvent = EventManager.RegisterRoutedEvent("PopupClosedEvent", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(OutlookBar)); + + /// + /// Gets or sets the index of the selected section. + /// + public int SelectedSectionIndex + { + get { return (int)GetValue(SelectedSectionIndexProperty); } + set { SetValue(SelectedSectionIndexProperty, value); } + } + + public static readonly DependencyProperty SelectedSectionIndexProperty = + DependencyProperty.Register("SelectedSectionIndex", typeof(int), typeof(OutlookBar), new + FrameworkPropertyMetadata( + 0, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + SelectedIndexPropertyChanged)); + + private static void SelectedIndexPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OutlookBar bar = (OutlookBar)d; + bar.ApplySections(); + } + + + + /// + /// Gets or sets the selected section. + /// + public OutlookSection SelectedSection + { + get { return (OutlookSection)GetValue(SelectedSectionProperty); } + set { SetValue(SelectedSectionProperty, value); } + } + + public static readonly DependencyProperty SelectedSectionProperty = + DependencyProperty.Register("SelectedSection", typeof(OutlookSection), typeof(OutlookBar), + new UIPropertyMetadata(null, SelectedSectionPropertyChanged)); + + + private static void SelectedSectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OutlookBar bar = (OutlookBar)d; + bar.OnSelectedSectionChanged((OutlookSection)e.OldValue, (OutlookSection)e.NewValue); + } + + /// + /// Occurs when the SelectedSection has changed. + /// + protected virtual void OnSelectedSectionChanged(OutlookSection oldSection, OutlookSection newSection) + { + + for (int index = 0; index < sections.Count; index++) + { + OutlookSection section = sections[index]; + bool selected = newSection == section; + section.IsSelected = newSection == section; + if (selected) + { + SelectedSectionIndex = index; + SectionContent = IsMaximized ? section.Content : null; + CollapsedSectionContent = IsMaximized ? null : section.Content; + } + } + RaiseEvent(new RoutedPropertyChangedEventArgs(oldSection, newSection, SelectedSectionChangedEvent)); + } + + + /// + /// Occurs when the SelectedSection has changed. + /// + public event RoutedPropertyChangedEventHandler SelectedSectionChanged + { + add { AddHandler(OutlookBar.SelectedSectionChangedEvent, value); } + remove { RemoveHandler(OutlookBar.SelectedSectionChangedEvent, value); } + } + + public static readonly RoutedEvent SelectedSectionChangedEvent = EventManager.RegisterRoutedEvent("SelectedSectionChangedEvent", + RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler), typeof(OutlookBar)); + + + + + /// + /// Gets the content of the selected section. + /// + internal object SectionContent + { + get { return (object)GetValue(SectionContentProperty); } + set { SetValue(SectionContentPropertyKey, value); } + } + + private static readonly DependencyPropertyKey SectionContentPropertyKey = + DependencyProperty.RegisterReadOnly("SectionContent", typeof(object), typeof(OutlookBar), new UIPropertyMetadata(null)); + public static readonly DependencyProperty SectionContentProperty = SectionContentPropertyKey.DependencyProperty; + + + + + /// + /// Gets or sets the content for the popup. + /// + internal object CollapsedSectionContent + { + get { return (object)GetValue(CollapsedSectionContentProperty); } + set { SetValue(CollapsedSectionContentPropertyKey, value); } + } + + + private static readonly DependencyPropertyKey CollapsedSectionContentPropertyKey = + DependencyProperty.RegisterReadOnly("CollapsedSectionContent", typeof(object), typeof(OutlookBar), new UIPropertyMetadata(null)); + public static readonly DependencyProperty CollapsedSectionContentProperty = SectionContentPropertyKey.DependencyProperty; + + + + /// + /// Gets or sets whether the overflow menu of the available sections is visible. + /// + public bool IsOverflowVisible + { + get { return (bool)GetValue(IsOverflowVisibleProperty); } + set { SetValue(IsOverflowVisibleProperty, value); } + } + + public static readonly DependencyProperty IsOverflowVisibleProperty = + DependencyProperty.Register("IsOverflowVisible", typeof(bool), typeof(OutlookBar), new UIPropertyMetadata(false, OverflowVisiblePropertyChanged)); + + + private static void OverflowVisiblePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OutlookBar bar = (OutlookBar)d; + bool newValue = (bool)e.NewValue; + bar.OnOverflowVisibleChanged(newValue); + } + + /// + /// Occurs when the IsOverflowVisible has changed. + /// + /// + protected virtual void OnOverflowVisibleChanged(bool newValue) + { + + if (newValue == true) + { + ApplyOverflowMenu(); + } + } + + /// + /// Toggles the IsExpanded property. + /// + public static RoutedUICommand CollapseCommand + { + get { return collapseCommand; } + } + + /// + /// Starts dragging the splitter for the visible section buttons (used for the xaml template). + /// + public static RoutedUICommand StartDraggingCommand + { + get { return startDraggingCommand; } + } + + /// + /// Shows the popup window. + /// + public static RoutedUICommand ShowPopupCommand + { + get { return showPopupCommand; } + } + + /// + /// Start to resize the Width of the OutlookBar (used for the xaml template to initiate resizing). + /// + public static RoutedUICommand ResizeCommand + { + get { return resizeCommand; } + } + private static RoutedUICommand resizeCommand = new RoutedUICommand("Resize", "ResizeCommand", typeof(OutlookBar)); + + /// + /// Close the OutlookBar + /// + public static RoutedUICommand CloseCommand + { + get { return closeCommand; } + } + private static RoutedUICommand collapseCommand = new RoutedUICommand("Collapse", "CollapseCommand", typeof(OutlookBar)); + private static RoutedUICommand startDraggingCommand = new RoutedUICommand("Drag", "StartDraggingCommand", typeof(OutlookBar)); + private static RoutedUICommand showPopupCommand = new RoutedUICommand("ShowPopup", "ShowPopupCommand", typeof(OutlookBar)); + private static RoutedUICommand closeCommand = new RoutedUICommand("Close", "CloseCommand", typeof(OutlookBar)); + + + /// + /// Gets or sets the height of the section buttons. + /// + public double ButtonHeight + { + get { return (double)GetValue(ButtonHeightProperty); } + set { SetValue(ButtonHeightProperty, value); } + } + + public static readonly DependencyProperty ButtonHeightProperty = + DependencyProperty.Register("ButtonHeight", typeof(double), typeof(OutlookBar), new UIPropertyMetadata((double)28.0)); + + + + + + /// + /// Gets or sets the with of the popup window. + /// + public double PopupWidth + { + get { return (double)GetValue(PopupWidthProperty); } + set { SetValue(PopupWidthProperty, value); } + } + + public static readonly DependencyProperty PopupWidthProperty = + DependencyProperty.Register("PopupWidth", typeof(double), typeof(OutlookBar), new UIPropertyMetadata((double)double.NaN)); + + + + + /// + /// Gets or sets whether the splitter for the section buttons is visible + /// + public bool IsButtonSplitterVisible + { + get { return (bool)GetValue(IsButtonSplitterVisibleProperty); } + set { SetValue(IsButtonSplitterVisibleProperty, value); } + } + + public static readonly DependencyProperty IsButtonSplitterVisibleProperty = + DependencyProperty.Register("IsButtonSplitterVisible", typeof(bool), typeof(OutlookBar), new UIPropertyMetadata(true)); + + + /// + /// Gets or sets whether the section buttons are visible. + /// + public bool ShowButtons + { + get { return (bool)GetValue(ShowButtonsProperty); } + set { SetValue(ShowButtonsProperty, value); } + } + + public static readonly DependencyProperty ShowButtonsProperty = + DependencyProperty.Register("ShowButtons", typeof(bool), typeof(OutlookBar), new UIPropertyMetadata(true)); + + + [Bindable(true)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public Collection OptionButtons + { + get { return (Collection)GetValue(OptionButtonsProperty); } + // private set { SetValue(OptionButtonsProperty, value); } + } + + private static readonly DependencyPropertyKey OptionButtonsPropertyKey = + DependencyProperty.RegisterReadOnly("OptionButtons", typeof(Collection), typeof(OutlookBar), new UIPropertyMetadata(null)); + public static readonly DependencyProperty OptionButtonsProperty = OptionButtonsPropertyKey.DependencyProperty; + + + + + /// + /// Gets or sets wether the width of the OutlookBar can be manually resized by a gripper at the right (or left). + /// + public bool CanResize + { + get { return (bool)GetValue(CanResizeProperty); } + set { SetValue(CanResizeProperty, value); } + } + + public static readonly DependencyProperty CanResizeProperty = + DependencyProperty.Register("CanResize", typeof(bool), typeof(OutlookBar), new UIPropertyMetadata(true)); + + + + /// + /// Gets or sets wether the close button is visible. + /// + public bool IsCloseButtonVisible + { + get { return (bool)GetValue(IsCloseButtonVisibleProperty); } + set { SetValue(IsCloseButtonVisibleProperty, value); } + } + + public static readonly DependencyProperty IsCloseButtonVisibleProperty = + DependencyProperty.Register("IsCloseButtonVisible", typeof(bool), typeof(OutlookBar), new UIPropertyMetadata(false)); + + + + /// + /// Gets or sets the text or content that is displayed on the minimized OutlookBar at the Button to open up the Navigation Pane. + /// + public object NavigationPaneText + { + get { return (object)GetValue(NavigationPaneTextProperty); } + set { SetValue(NavigationPaneTextProperty, value); } + } + + public static readonly DependencyProperty NavigationPaneTextProperty = + DependencyProperty.Register("NavigationPaneText", typeof(object), typeof(OutlookBar), new UIPropertyMetadata("Navigation Pane")); + + + protected override IEnumerator LogicalChildren + { + get + { + return GetLogicalChildren().GetEnumerator(); + } + } + + protected virtual IEnumerable GetLogicalChildren() + { + foreach (var section in Sections) yield return section; + if (SelectedSection != null) yield return SelectedSection.Content; + } + + + #region IKeyTipControl Members + + void IKeyTipControl.ExecuteKeyTip() + { + this.IsMaximized ^= true; + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/OutlookBar/OutlookSection.cs b/Odyssey/Odyssey/OutlookBar/OutlookSection.cs new file mode 100644 index 0000000..83ab2eb --- /dev/null +++ b/Odyssey/Odyssey/OutlookBar/OutlookSection.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Collections.ObjectModel; +using System.Windows.Controls.Primitives; +using Odyssey.Controls.Interfaces; + +namespace Odyssey.Controls +{ + public class OutlookSection:HeaderedContentControl,IKeyTipControl + { + static OutlookSection() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(OutlookSection), new FrameworkPropertyMetadata(typeof(OutlookSection))); + } + + public OutlookSection():base() + { + AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(buttonClickedEvent)); + } + + private void buttonClickedEvent(object sender, RoutedEventArgs e) + { + OutlookBar bar = OutlookBar; + ToggleButton b = e.OriginalSource as ToggleButton; + if (b != null) b.IsChecked = true; + if (bar != null) + { + bar.SelectedSection = this; + } + OnClick(); + } + + /// + /// Gets or sets the Image for the Section Button. + /// + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(ImageSource), typeof(OutlookSection), new UIPropertyMetadata(null)); + + + /// + /// Gets wether the Section is the selected section of the OutlookBar. + /// + public bool IsSelected + { + get { return (bool)GetValue(IsSelectedProperty); } + internal set { SetValue(IsSelectedProperty, value); } + } + + public static readonly DependencyProperty IsSelectedProperty = + DependencyProperty.Register("IsSelected", typeof(bool), typeof(OutlookSection), new UIPropertyMetadata(false, IsSelectedPropertyChanged)); + + private static void IsSelectedPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((OutlookSection)o).OnSelectedPropertyChanged((bool)e.OldValue, (bool)e.NewValue); + } + + protected virtual void OnSelectedPropertyChanged(bool oldValue, bool newValue) + { + if (newValue) OutlookBar.SelectedSection = this; + } + + /// + /// Occurs when the section button is clicked,. + /// + protected virtual void OnClick() + { + this.RaiseEvent(new RoutedEventArgs(OutlookSection.ClickEvent)); + } + + /// + /// Occurs when the section button is clicked,. + /// + public event RoutedEventHandler Click + { + add { AddHandler(OutlookSection.ClickEvent, value); } + remove { RemoveHandler(OutlookSection.ClickEvent, value); } + } + + public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent("Click", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(OutlookSection)); + + + + + /// + /// Gets or sets whether the Section is shown as a complete button with text (true), otherwise as small button with image only. + /// + public bool IsMaximized + { + get { return (bool)GetValue(IsMaximizedProperty); } + set { SetValue(IsMaximizedProperty, value); } + } + + public static readonly DependencyProperty IsMaximizedProperty = + DependencyProperty.Register("IsMaximized", typeof(bool), typeof(OutlookSection), new UIPropertyMetadata(true)); + + /// + /// Gets or sets the OutlookBar to which this Section is assigned. + /// + internal OutlookBar OutlookBar { get; set; } + + #region IKeyTipControl Members + + void IKeyTipControl.ExecuteKeyTip() + { + IsSelected = true; + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/OutlookBar/OverflowMenuCreatedEventArgs.cs b/Odyssey/Odyssey/OutlookBar/OverflowMenuCreatedEventArgs.cs new file mode 100644 index 0000000..c802fc0 --- /dev/null +++ b/Odyssey/Odyssey/OutlookBar/OverflowMenuCreatedEventArgs.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections.ObjectModel; + +namespace Odyssey.Controls +{ + public class OverflowMenuCreatedEventArgs:EventArgs + { + public OverflowMenuCreatedEventArgs(Collection menuItems) + : base() + { + this.MenuItems = menuItems; + } + + public Collection MenuItems { get; private set; } + } +} diff --git a/Odyssey/Odyssey/Properties/AssemblyInfo.cs b/Odyssey/Odyssey/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7bb32aa --- /dev/null +++ b/Odyssey/Odyssey/Properties/AssemblyInfo.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Markup; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Odyssey")] +[assembly: AssemblyDescription("WCF Control Library")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("tomssoftware.net")] +[assembly: AssemblyProduct("Odyssey")] +[assembly: AssemblyCopyright("Copyright © 2008-2009 Thomas Gerber")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + +[assembly: XmlnsDefinition("http://schemas.odyssey.com/wpf", "Odyssey.Controls")] + +[assembly: ThemeInfo( + ResourceDictionaryLocation.SourceAssembly, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.4.22.*")] +[assembly: AssemblyFileVersion("1.4.22.35")] +[assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Odyssey/Odyssey/Properties/Resources.Designer.cs b/Odyssey/Odyssey/Properties/Resources.Designer.cs new file mode 100644 index 0000000..76c9fe3 --- /dev/null +++ b/Odyssey/Odyssey/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.20506.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Odyssey.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", "4.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("Odyssey.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; + } + } + } +} diff --git a/Odyssey/Odyssey/Properties/Resources.resx b/Odyssey/Odyssey/Properties/Resources.resx new file mode 100644 index 0000000..4b8fd17 --- /dev/null +++ b/Odyssey/Odyssey/Properties/Resources.resx @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/Odyssey/Odyssey/Properties/Settings.Designer.cs b/Odyssey/Odyssey/Properties/Settings.Designer.cs new file mode 100644 index 0000000..6da2958 --- /dev/null +++ b/Odyssey/Odyssey/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.20506.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Odyssey.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Odyssey/Odyssey/Properties/Settings.settings b/Odyssey/Odyssey/Properties/Settings.settings new file mode 100644 index 0000000..2bd17f0 --- /dev/null +++ b/Odyssey/Odyssey/Properties/Settings.settings @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Ribbon/Classes/BoolConverter.cs b/Odyssey/Odyssey/Ribbon/Classes/BoolConverter.cs new file mode 100644 index 0000000..c471c4e --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/BoolConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [ValueConversion(typeof(bool), typeof(bool))] + public class BoolConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + bool bValue = (bool)value; + return !bValue; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/IRibbonSize.cs b/Odyssey/Odyssey/Ribbon/Classes/IRibbonSize.cs new file mode 100644 index 0000000..7aae63b --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/IRibbonSize.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Odyssey.Controls +{ + public interface IRibbonSize + { + RibbonSize Appearance { get; set; } + + RibbonSize PreferedAppearance { get; } + + RibbonSize MinAppearance { get; } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/ImageRenderOptions.cs b/Odyssey/Odyssey/Ribbon/Classes/ImageRenderOptions.cs new file mode 100644 index 0000000..ec2a9f3 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/ImageRenderOptions.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Media; +using System.Windows; +using System.Windows.Controls; + +namespace Odyssey.Controls +{ + /// + /// Specifies how to render Images in Ribbon Controls such as RibbonButton, RibbonSplitButton, etc. + /// + public class ImageRenderOptions + { + static ImageRenderOptions() + { + LargeImageScalingModeProperty.AddOwner(typeof(RibbonButton)); + LargeImageScalingModeProperty.AddOwner(typeof(TextBlock)); + } + + public static BitmapScalingMode GetLargeImageScalingMode(DependencyObject obj) + { + return (BitmapScalingMode)obj.GetValue(LargeImageScalingModeProperty); + } + + public static void SetLargeImageScalingMode(DependencyObject obj, BitmapScalingMode value) + { + obj.SetValue(LargeImageScalingModeProperty, value); + } + + public static readonly DependencyProperty LargeImageScalingModeProperty = + DependencyProperty.RegisterAttached("LargeImageScalingMode", + typeof(BitmapScalingMode), + typeof(ImageRenderOptions), + new FrameworkPropertyMetadata(BitmapScalingMode.NearestNeighbor, FrameworkPropertyMetadataOptions.Inherits)); + + + + public static BitmapScalingMode GetSmallImageScalingMode(DependencyObject obj) + { + return (BitmapScalingMode)obj.GetValue(SmallImageScalingModeProperty); + } + + public static void SetSmallImageScalingMode(DependencyObject obj, BitmapScalingMode value) + { + obj.SetValue(SmallImageScalingModeProperty, value); + } + + public static readonly DependencyProperty SmallImageScalingModeProperty = + DependencyProperty.RegisterAttached("SmallImageScalingMode", + typeof(BitmapScalingMode), + typeof(ImageRenderOptions), + new FrameworkPropertyMetadata(BitmapScalingMode.NearestNeighbor, FrameworkPropertyMetadataOptions.Inherits)); + + + + + + public static EdgeMode GetLargeEdgeMode(DependencyObject obj) + { + return (EdgeMode)obj.GetValue(LargeEdgeModeProperty); + } + + public static void SetLargeEdgeMode(DependencyObject obj, EdgeMode value) + { + obj.SetValue(LargeEdgeModeProperty, value); + } + + public static readonly DependencyProperty LargeEdgeModeProperty = + DependencyProperty.RegisterAttached("LargeEdgeMode", typeof(EdgeMode), typeof(ImageRenderOptions), new UIPropertyMetadata(EdgeMode.Aliased)); + + + + + public static EdgeMode GetSmallEdgeMode(DependencyObject obj) + { + return (EdgeMode)obj.GetValue(SmallEdgeModeProperty); + } + + public static void SetSmallEdgeMode(DependencyObject obj, EdgeMode value) + { + obj.SetValue(SmallEdgeModeProperty, value); + } + + public static readonly DependencyProperty SmallEdgeModeProperty = + DependencyProperty.RegisterAttached("SmallEdgeMode", typeof(EdgeMode), typeof(ImageRenderOptions), new UIPropertyMetadata(EdgeMode.Aliased)); + + + + + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/NativeMethods.cs b/Odyssey/Odyssey/Ribbon/Classes/NativeMethods.cs new file mode 100644 index 0000000..c9994f0 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/NativeMethods.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; +using System.Windows; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Native +{ + public static class NativeMethods + { + public const int NOREPOSITION = 0x200; + + [DllImport("gdi32.dll", SetLastError = true)] + public static extern IntPtr CreateRectRgn(int left, int top, int right, int bottom); + + [DllImport("user32.dll")] + public static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, [MarshalAs(UnmanagedType.Bool)] bool bRedraw); + + [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("user32.dll")] + public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); + + [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("user32.dll")] + public static extern bool IsWindowVisible(IntPtr hwnd); + + [DllImport("gdi32.dll")] + public static extern IntPtr CreateRoundRectRgn(int x1, int y1, int x2, int y2, int cx, int cy); + + [DllImport("gdi32.dll", SetLastError = true)] + public static extern int CombineRgn(IntPtr hrgnDest, IntPtr hrgnSrc1, IntPtr hrgnSrc2, int fnCombineMode); + + [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("user32.dll", SetLastError = true)] + public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SWP uFlags); + + [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")] + private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, GWL nIndex); + + [DllImport("user32.dll", EntryPoint = "GetWindowLong")] + private static extern IntPtr GetWindowLongPtr32(IntPtr hWnd, GWL nIndex); + + [DllImport("user32.dll", EntryPoint = "DefWindowProcW", CharSet = CharSet.Unicode)] + public static extern IntPtr DefWindowProc(IntPtr hWnd, WM Msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", SetLastError = true)] + private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, GWL nIndex, IntPtr dwNewLong); + + [DllImport("user32.dll", EntryPoint = "SetWindowLong", SetLastError = true)] + private static extern IntPtr SetWindowLongPtr32(IntPtr hWnd, GWL nIndex, IntPtr dwNewLong); + + [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("dwmapi.dll")] + public static extern bool DwmDefWindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, out IntPtr plResult); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS pMarInset); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern bool DwmIsCompositionEnabled(); + + [StructLayout(LayoutKind.Sequential)] + public struct MARGINS + { + public int cxLeftWidth; + public int cxRightWidth; + public int cyTopHeight; + public int cyBottomHeight; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NCCALCSIZE_PARAMS + { + public RECT rect0, rect1, rect2; + public IntPtr lppos; + } + + [Flags] + public enum SWP + { + ASYNCWINDOWPOS = 0x4000, + DEFERERASE = 0x2000, + DRAWFRAME = 0x20, + FRAMECHANGED = 0x20, + HIDEWINDOW = 0x80, + NOACTIVATE = 0x10, + NOCOPYBITS = 0x100, + NOMOVE = 2, + NOOWNERZORDER = 0x200, + NOREDRAW = 8, + NOREPOSITION = 0x200, + NOSENDCHANGING = 0x400, + NOSIZE = 1, + NOZORDER = 4, + SHOWWINDOW = 0x40 + } + + public enum WM + { + ACTIVATE = 6, + APP = 0x8000, + CLOSE = 0x10, + CREATE = 1, + DESTROY = 2, + DWMCOMPOSITIONCHANGED = 0x31e, + ENABLE = 10, + ERASEBKGND = 20, + GETDLGCODE = 0x87, + GETTEXT = 13, + GETTEXTLENGTH = 14, + KILLFOCUS = 8, + MOVE = 3, + NCACTIVATE = 0x86, + NCCALCSIZE = 0x83, + NCCREATE = 0x81, + NCDESTROY = 130, + NCHITTEST = 0x84, + NCLBUTTONDBLCLK = 0xa3, + NCLBUTTONDOWN = 0xa1, + NCLBUTTONUP = 0xa2, + NCMBUTTONDBLCLK = 0xa9, + NCMBUTTONDOWN = 0xa7, + NCMBUTTONUP = 0xa8, + NCMOUSEMOVE = 160, + NCPAINT = 0x85, + NCRBUTTONDBLCLK = 0xa6, + NCRBUTTONDOWN = 0xa4, + NCRBUTTONUP = 0xa5, + NULL = 0, + PAINT = 15, + QUERYENDSESSION = 0x11, + QUERYOPEN = 0x13, + QUIT = 0x12, + SETFOCUS = 7, + SETICON = 0x80, + SETREDRAW = 11, + SETTEXT = 12, + SIZE = 5, + SYNCPAINT = 0x88, + SYSCHAR = 0x106, + SYSCOLORCHANGE = 0x15, + SYSCOMMAND = 0x112, + SYSDEADCHAR = 0x107, + SYSKEYDOWN = 260, + SYSKEYUP = 0x105, + USER = 0x400, + WINDOWPOSCHANGED = 0x47, + WINDOWPOSCHANGING = 70, + MSG794=794 + } + + public enum HT + { + BORDER = 0x12, + BOTTOM = 15, + BOTTOMLEFT = 0x10, + BOTTOMRIGHT = 0x11, + CAPTION = 2, + CLIENT = 1, + CLOSE = 20, + ERROR = -2, + GROWBOX = 4, + HELP = 0x15, + HSCROLL = 6, + LEFT = 10, + MAXBUTTON = 9, + MENU = 5, + MINBUTTON = 8, + NOWHERE = 0, + OBJECT = 0x13, + RIGHT = 11, + SYSMENU = 3, + TOP = 12, + TOPLEFT = 13, + TOPRIGHT = 14, + TRANSPARENT = -1, + VSCROLL = 7 + } + + + public static IntPtr GetWindowLongPtr(IntPtr hwnd, GWL nIndex) + { + if (8 == IntPtr.Size) + { + return GetWindowLongPtr64(hwnd, nIndex); + } + return GetWindowLongPtr32(hwnd, nIndex); + } + + public enum GWL + { + EXSTYLE = -20, + HINSTANCE = -6, + HWNDPARENT = -8, + ID = -12, + STYLE = -16, + USERDATA = -21, + WNDPROC = -4 + } + + + [Flags] + public enum WS : uint + { + BORDER = 0x800000, + CAPTION = 0xc00000, + CHILD = 0x40000000, + CHILDWINDOW = 0x40000000, + CLIPCHILDREN = 0x2000000, + CLIPSIBLINGS = 0x4000000, + DISABLED = 0x8000000, + DLGFRAME = 0x400000, + GROUP = 0x20000, + HSCROLL = 0x100000, + ICONIC = 0x20000000, + MAXIMIZE = 0x1000000, + MAXIMIZEBOX = 0x10000, + MINIMIZE = 0x20000000, + MINIMIZEBOX = 0x20000, + OVERLAPPED = 0, + OVERLAPPEDWINDOW = 0xcf0000, + POPUP = 0x80000000, + POPUPWINDOW = 0x80880000, + SIZEBOX = 0x40000, + SYSMENU = 0x80000, + TABSTOP = 0x10000, + THICKFRAME = 0x40000, + TILED = 0, + TILEDWINDOW = 0xcf0000, + VISIBLE = 0x10000000, + VSCROLL = 0x200000 + } + + + + public static IntPtr SetWindowLongPtr(IntPtr hwnd, GWL nIndex, IntPtr dwNewLong) + { + if (8 == IntPtr.Size) + { + return SetWindowLongPtr64(hwnd, nIndex, dwNewLong); + } + return SetWindowLongPtr32(hwnd, nIndex, dwNewLong); + } + + + public static int SignedLoWord(int i) + { + return (short)(i & 0xffff); + } + + + public static int SignedHiWord(int i) + { + return (short)(i >> 0x10); + } + + + [Obsolete] + internal static bool xModifyHwndStyle(IntPtr hwnd, WS removeStyle, WS addStyle) + { + WS ws = (WS)GetWindowLongPtr(hwnd, GWL.STYLE).ToInt32(); + WS ws2 = (ws & ~removeStyle) | addStyle; + if (ws == ws2) + { + return false; + } + SetWindowLongPtr(hwnd, GWL.STYLE, new IntPtr((int)ws2)); + return true; + } + + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + public int Width { get { return Right - Left; } } + public int Height { get { return Bottom - Top; } } + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/QAItemPlacement.cs b/Odyssey/Odyssey/Ribbon/Classes/QAItemPlacement.cs new file mode 100644 index 0000000..9d484c3 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/QAItemPlacement.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public enum QAItemPlacement + { + ToolBar=1, + Menu=2, + ToolBarAndMenu=3 + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/QAPlacement.cs b/Odyssey/Odyssey/Ribbon/Classes/QAPlacement.cs new file mode 100644 index 0000000..faaa014 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/QAPlacement.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + /// + /// Specifies where to place the QuickAccessToolBar. + /// + public enum QAPlacement + { + Top, + Bottom + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/RibbonBarAlignment.cs b/Odyssey/Odyssey/Ribbon/Classes/RibbonBarAlignment.cs new file mode 100644 index 0000000..6b93317 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/RibbonBarAlignment.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public enum RibbonBarAlignment + { + Full, + Left, + Right + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/RibbonGalleryColumns.cs b/Odyssey/Odyssey/Ribbon/Classes/RibbonGalleryColumns.cs new file mode 100644 index 0000000..d29483a --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/RibbonGalleryColumns.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ComponentModel; +using Odyssey.Controls.Classes; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [TypeConverter(typeof(RibbonGalleryColumnsCollectionConverter))] + public class RibbonGalleryColumns : List + { + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/RibbonGalleryColumnsCollectionConverter.cs b/Odyssey/Odyssey/Ribbon/Classes/RibbonGalleryColumnsCollectionConverter.cs new file mode 100644 index 0000000..0718709 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/RibbonGalleryColumnsCollectionConverter.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ComponentModel; +using System.Collections.Specialized; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls.Classes +{ + public class RibbonGalleryColumnsCollectionConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) + { + string str = value as string; + if (str == null) + { + return base.ConvertFrom(context, culture, value); + } + str = str.Trim(); + if (str.Length == 0) return null; + + string[] names = str.Split(','); + RibbonGalleryColumns collection = new RibbonGalleryColumns(); + foreach (string name in names) + { + collection.Add(int.Parse(name)); + } + return collection; + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/RibbonGroupReductionOrderConverter.cs b/Odyssey/Odyssey/Ribbon/Classes/RibbonGroupReductionOrderConverter.cs new file mode 100644 index 0000000..f194c2b --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/RibbonGroupReductionOrderConverter.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ComponentModel; +using System.Collections.Specialized; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls.Classes +{ + public class RibbonGroupReductionOrderConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) + { + string str = value as string; + if (str == null) + { + return base.ConvertFrom(context, culture, value); + } + str = str.Trim(); + if (str.Length == 0) return null; + + string[] names = str.Split(','); + StringCollection collection = new StringCollection(); + foreach (string name in names) + { + collection.Add(name); + } + return collection; + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/RibbonOption.cs b/Odyssey/Odyssey/Ribbon/Classes/RibbonOption.cs new file mode 100644 index 0000000..44a857d --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/RibbonOption.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; + +namespace Odyssey.Controls +{ + public class RibbonOption + { + + public static bool GetCloseDropDownOnClick(DependencyObject obj) + { + return (bool)obj.GetValue(CloseDropDownOnClickProperty); + } + + public static void SetCloseDropDownOnClick(DependencyObject obj, bool value) + { + obj.SetValue(CloseDropDownOnClickProperty, value); + } + + public static readonly DependencyProperty CloseDropDownOnClickProperty = + DependencyProperty.RegisterAttached("CloseDropDownOnClick", typeof(bool), typeof(RibbonOption), + new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender | FrameworkPropertyMetadataOptions.Inherits)); + + + + /// + /// Gets whether a RibbonButton, RibbonSplitButton, RibbonToggleButton or RibbonDropDownButton should transite it state with animation. + /// This is an attached inheritable dependency property. The default value is false. + /// + public static bool GetAnimateTransition(DependencyObject obj) + { + return (bool)obj.GetValue(AnimateTransitionProperty); + } + + /// + /// Sets whether a RibbonButton, RibbonSplitButton, RibbonToggleButton or RibbonDropDownButton should transite it state with animation. + /// This is an attached inheritable dependency property. The default value is false. + /// + public static void SetAnimateTransition(DependencyObject obj, bool value) + { + obj.SetValue(AnimateTransitionProperty, value); + } + + public static readonly DependencyProperty AnimateTransitionProperty = + DependencyProperty.RegisterAttached("AnimateTransition", typeof(bool), typeof(RibbonOption), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits)); + + + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/RibbonReductionCollectionConverter.cs b/Odyssey/Odyssey/Ribbon/Classes/RibbonReductionCollectionConverter.cs new file mode 100644 index 0000000..43ba81d --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/RibbonReductionCollectionConverter.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ComponentModel; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls.Classes +{ + public class RibbonReductionCollectionConverter:TypeConverter + { + + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) + { + string str = value as string; + if (str == null) + { + return base.ConvertFrom(context, culture, value); + } + str = str.Trim(); + if (str.Length == 0) return null; + + string[] sizes = str.Split(','); + RibbonSizeCollection collection = new RibbonSizeCollection(); + foreach (string size in sizes) + { + RibbonSize rs; + switch (size.ToUpper()) + { + case "L": rs = RibbonSize.Large; break; + case "M": rs = RibbonSize.Medium; break; + case "S": rs = RibbonSize.Small; break; + case "0": rs = RibbonSize.Minimized; break; + + + default: + rs = (RibbonSize)Enum.Parse(typeof(RibbonSize), size, true); + break; + } + collection.Add(rs); + } + return collection; + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/RibbonSize.cs b/Odyssey/Odyssey/Ribbon/Classes/RibbonSize.cs new file mode 100644 index 0000000..1006987 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/RibbonSize.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + //Specifies the size of a RibbonControl within a RibbonGroup + public enum RibbonSize + { + + /// + /// the control appears with the large image and text. + /// + Large=3, + + /// + /// the control appears with the small image and text. + /// + Medium=2, + + /// + /// the control appears with the small image only. + /// + Small=1, + + /// + /// the control appears collapsed. + /// + Minimized=0 + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/RibbonSizeCollection.cs b/Odyssey/Odyssey/Ribbon/Classes/RibbonSizeCollection.cs new file mode 100644 index 0000000..c50d6d6 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/RibbonSizeCollection.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections.ObjectModel; +using System.ComponentModel; +using Odyssey.Controls.Classes; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [TypeConverter(typeof(RibbonReductionCollectionConverter))] + public class RibbonSizeCollection:Collection + { + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/RibbonWindowCornerMode.cs b/Odyssey/Odyssey/Ribbon/Classes/RibbonWindowCornerMode.cs new file mode 100644 index 0000000..087db3c --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/RibbonWindowCornerMode.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls.Classes +{ + public enum RibbonWindowCornerMode + { + Top=1, + All=2, + None=0 + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/RoundedCornerConverter.cs b/Odyssey/Odyssey/Ribbon/Classes/RoundedCornerConverter.cs new file mode 100644 index 0000000..e80d847 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/RoundedCornerConverter.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ComponentModel; +using System.Windows.Data; +using System.Windows; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [ValueConversion(typeof(CornerRadius),typeof(CornerRadius))] + class RoundedCornerConverter:IValueConverter + { + #region IValueConverter Members + + /// + /// Converts a RoundedCorner property to an appropriate value. + /// + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + CornerRadius cr = (CornerRadius)value; + string s = parameter as string; + switch (s) + { + case "left": + return new CornerRadius(cr.TopLeft, 0d, 0d, cr.BottomLeft); + + case "right": + return new CornerRadius(0d, cr.TopRight, cr.BottomRight, 0d); + + case "top": + return new CornerRadius(cr.TopLeft, cr.TopRight, 0d, 0d); + + case "bottom": + return new CornerRadius(0d, 0d, cr.BottomRight, cr.BottomLeft); + + default: + return null; + } + + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/RoundedCornerResizeConverter.cs b/Odyssey/Odyssey/Ribbon/Classes/RoundedCornerResizeConverter.cs new file mode 100644 index 0000000..40bd770 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/RoundedCornerResizeConverter.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using System.Windows; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [ValueConversion(typeof(double), typeof(CornerRadius))] + public class RoundedCornerResizeConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + double d = parameter != null ? double.Parse(parameter.ToString()) : -1; + CornerRadius r = (CornerRadius)value; + CornerRadius result = new CornerRadius( + Math.Max(0, r.TopLeft + d), + Math.Max(0, r.TopRight + d), + Math.Max(0, r.BottomRight + d), + Math.Max(0, r.BottomLeft + d)); + + return result; + + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/TwoLineConverter.cs b/Odyssey/Odyssey/Ribbon/Classes/TwoLineConverter.cs new file mode 100644 index 0000000..25e8559 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/TwoLineConverter.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using System.Windows.Controls; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + /// + /// Breaks a string into two lines by adding a LineBreak at the appropriate position. + /// + [ValueConversion(typeof(string), typeof(TextBlock))] + public class TwoLineConverter : IValueConverter + { + #region IValueConverter Members + + /// + /// Converts a string into a TextBlock with one or two lines. + /// + /// Either a string to convert, or any value to return directly. + /// ignored. + /// ignored. + /// ignored. + /// A TextBlock class if value is of string, otherwise value. + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + string s = value as string; + if (s == null) return value; + { + int l = SplitIn2Lines(s); + if (l > 0) + { + s = s.Remove(l,1).Insert(l, "\n"); + } + } + TextBlock tb = new TextBlock(); + tb.TextWrapping = System.Windows.TextWrapping.Wrap; + tb.TextAlignment = System.Windows.TextAlignment.Center; + tb.LineStackingStrategy = System.Windows.LineStackingStrategy.BlockLineHeight; + tb.LineHeight = 12.0; + tb.Text = s; + return tb; + } + + private static int SplitIn2Lines(string s) + { + int n = s.Length; + int l = n / 2; + int r = l + 1; + while (l > 0) + { + char c = s[l]; + if (char.IsSeparator(c)) break; + if (r < n) + { + c = s[r]; + if (char.IsSeparator(c)) + { + l = r; + break; + } + } + r++; + l--; + } + return l; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Ribbon/Classes/TwoLineTextConverter.cs b/Odyssey/Odyssey/Ribbon/Classes/TwoLineTextConverter.cs new file mode 100644 index 0000000..d32a753 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Classes/TwoLineTextConverter.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [ValueConversion(typeof(string), typeof(string))] + public class TwoLineTextConverter : IValueConverter + { + #region IValueConverter Members + + /// + /// Splits a string into two lines which have almost the same length if possible. + /// + /// The string to split. If this type is not a string, the value is returned directly. + /// Specifies the line number to return. The value must be either (int)1 or (int)2. + /// The first or second line of the string, otherwise value. + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + int line; + try + { + line = int.Parse(parameter as string); + } + catch + { + throw new ArgumentException("parameter must be either 1 or 2."); + } + string s = value as string; + if (s == null) return line == 1 ? value : null; + + int l = SplitIn2Lines(s); + if (l == 0) return line == 1 ? s : null; + + switch (line) + { + case 1: return s.Substring(0, l).Trim(); + case 2: return s.Substring(l + 1).Trim(); + default: throw new ArgumentException("parameter must be either 1 or 2."); + } + } + + private static int SplitIn2Lines(string s) + { + int n = s.Length; + int l = n / 2; + int r = l + 1; + while (l > 0) + { + char c = s[l]; + if (char.IsSeparator(c)) break; + if (r < n) + { + c = s[r]; + if (char.IsSeparator(c)) + { + l = r; + break; + } + } + r++; + l--; + } + return l; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/InternalGroupPanel.cs b/Odyssey/Odyssey/Ribbon/Controls/InternalGroupPanel.cs new file mode 100644 index 0000000..460f4a3 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/InternalGroupPanel.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Markup; +using System.Collections; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + internal class InternalGroupPanel : Panel + { + /// + /// Gets the maximum height to decide whether a control is small enough to be grouped vertically. + /// + public const double MaxSmallHeight = 24; + + + public int RowsToRender + { + get { return (int)GetValue(RowsToRenderProperty); } + set { SetValue(RowsToRenderProperty, value); } + } + + // Using a DependencyProperty as the backing store for RowsToRender. This enables animation, styling, binding, etc... + public static readonly DependencyProperty RowsToRenderProperty = + DependencyProperty.Register("RowsToRender", typeof(int), typeof(InternalGroupPanel), + new FrameworkPropertyMetadata(2, + FrameworkPropertyMetadataOptions.AffectsMeasure | + FrameworkPropertyMetadataOptions.AffectsArrange | + FrameworkPropertyMetadataOptions.AffectsParentMeasure)); + + + + private List dynamicOrder = new List(); + + static Size infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + + protected override Size MeasureOverride(Size availableSize) + { + dynamicOrder.Clear(); + + foreach (UIElement e in Children) e.Measure(infiniteSize); + if (RowsToRender == 2) + { + Size size = FitsInDynamicRows(availableSize, 2, Children); + if (!size.IsEmpty) return size; + } + else + { + + var ordered = (from UIElement e in Children orderby e.DesiredSize.Width descending select e).ToList(); + + bool swap = false; + IEnumerable elements; + while ((elements = Get3Elements(ordered, swap)) != null) + { + swap ^= true; + foreach (UIElement e in elements) dynamicOrder.Add(e); + } + + Size size = FitsInDynamicRows(availableSize, 3, dynamicOrder); + if (!size.IsEmpty) return size; + } + + return new Size(48, 3 * MaxSmallHeight); + } + + private IEnumerable Get3Elements(List ordered, bool swap) + { + if (ordered.Count == 0) return null; + + List result = ordered.Take(3).ToList(); + if (ordered.Count > 0) ordered.RemoveAt(0); + if (ordered.Count > 0) ordered.RemoveAt(0); + if (ordered.Count > 0) ordered.RemoveAt(0); + while (result.Count < 3) result.Add(null); + + if (swap) result.Reverse(); + return result; + } + + private Size FitsInDynamicRows(Size availableSize, int rows, IEnumerable children) + { + double[] rowWidth = new double[rows]; + int currentRow = 0; + + foreach (UIElement e in children) + { + if (e == null) + { + if (++currentRow >= rows) currentRow = 0; + continue; + } + Size size = e.DesiredSize; + bool isLarge = size.Height > MaxSmallHeight; + + if (isLarge) + { + currentRow = 0; + double maxWidth = rowWidth.Max() + size.Width; + if (maxWidth > availableSize.Width) return Size.Empty; + + for (int i = 0; i < rows; i++) rowWidth[i] = maxWidth; + } + else + { + rowWidth[currentRow] += size.Width; + if (rowWidth[currentRow] > availableSize.Width) return Size.Empty; + + if (++currentRow >= rows) + { + currentRow = 0; + } + } + } + + return new Size(rowWidth.Max(), 3 * MaxSmallHeight); + } + + protected override Size ArrangeOverride(Size finalSize) + { + if (finalSize.Width > 50) + { + return ArrangeDynamicRows(finalSize); + } + else + { + return ArrangeEmpty(finalSize); + } + } + + private Size ArrangeDynamicRows(Size finalSize) + { + int rowsToRender = RowsToRender; + double[] left = new double[rowsToRender]; + double rowHeight = MaxSmallHeight; + double topOffset = rowsToRender == 2 ? MaxSmallHeight / 3 : 0; + + int rowIndex = 0; + + IEnumerable children = dynamicOrder.Count > 0 ? (IEnumerable)dynamicOrder : Children; + foreach (UIElement e in children) + { + if (e != null) + { + double w = e.DesiredSize.Width; + e.Arrange(new Rect(left[rowIndex], topOffset + rowIndex * (rowHeight + topOffset), w, rowHeight)); + left[rowIndex] += w; + } + rowIndex++; + if (rowIndex >= rowsToRender) + { + rowIndex = 0; + } + } + return new Size(left.Max(), 3 * MaxSmallHeight); + } + + private Size ArrangeEmpty(Size finalSize) + { + Rect r = new Rect(0, 0, 0, 0); + foreach (UIElement e in Children) + { + e.Arrange(r); + } + return finalSize; + } + + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/KeyTip.cs b/Odyssey/Odyssey/Ribbon/Controls/KeyTip.cs new file mode 100644 index 0000000..77eab98 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/KeyTip.cs @@ -0,0 +1,406 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Diagnostics; +using Odyssey.Controls.Ribbon.Interfaces; +using System.Windows.Controls.Primitives; +using Odyssey.Controls.Interfaces; +using System.Windows.Automation.Peers; + +namespace Odyssey.Controls +{ + /// + /// Enables Quick Access keys. + /// + public class KeyTip : Control + { + static KeyTip() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(KeyTip), new FrameworkPropertyMetadata(typeof(KeyTip))); + EventManager.RegisterClassHandler(typeof(RibbonWindow), + FrameworkElement.PreviewKeyDownEvent, + new KeyEventHandler(OnPreviewWindowKeyDown), false); + + EventManager.RegisterClassHandler(typeof(RibbonWindow), + FrameworkElement.PreviewMouseDownEvent, + new RoutedEventHandler(OnPreviewMouseDown), false); + + + } + + static void OnPreviewMouseDown(object sender, RoutedEventArgs e) + { + Reset(); + } + + static void OnPreviewWindowKeyDown(object sender, KeyEventArgs e) + { + switch (e.SystemKey) + { + case Key.LeftAlt: + ToggleQuickAccessKeys(sender); + e.Handled = true; + break; + + default: + e.Handled = CheckQuickAccessKey(e); + break; + + } + switch (e.Key) + { + case Key.Escape: + if (current != null) + { + MoveBack(); + e.Handled = true; + //FrameworkElement x = FocusManager.GetFocusedElement(sender as DependencyObject) as FrameworkElement; + //x.MoveFocus(new TraversalRequest(FocusNavigationDirection.Up)); + } + break; + } + if (!e.Handled) + { + Reset(); + } + } + + private static void MoveBack() + { + selectStack.Pop(); + current = selectStack.Count > 0 ? selectStack.Pop() : null; + elements = null; + keySequence = ""; + Keyboard.Focus(null); + RibbonDropDownButton.CloseOpenedPopup(null); + RibbonGroup.CloseOpenedPopup(); + CloseQuickAccessKeys(); + if (current != null) ShowQuickAccessKeys(current); + } + + private static void ToggleQuickAccessKeys(object sender) + { + Window window = (Window)sender; + window.Deactivated -= window_LocationChanged; + window.Deactivated += window_LocationChanged; + window.LocationChanged -= window_LocationChanged; + window.LocationChanged += window_LocationChanged; + if (current == null) + { + ShowQuickAccessKeys(window); + } + else + { + Reset(); + } + } + + + static void window_LocationChanged(object sender, EventArgs e) + { + Reset(); + } + + private static void Reset() + { + if (elements != null) + { + RibbonDropDownButton.CloseOpenedPopup(null); + RibbonGroup.CloseOpenedPopup(); + HideQuickAccessKeys(); + } + } + + private static void HideQuickAccessKeys() + { + CloseQuickAccessKeys(); + elements = null; + keySequence = ""; + current = null; + selectStack.Clear(); + } + + private static bool CheckQuickAccessKey(KeyEventArgs e) + { + if (elements == null || elements.Count == 0) return false; + string c = new KeyConverter().ConvertToInvariantString(e.Key); + keySequence += c; + + FrameworkElement match = null; + List filtered = new List(); + foreach (FrameworkElement child in elements) + { + string key = KeyTip.GetKey(child); + if (keySequence.Equals(key, StringComparison.InvariantCultureIgnoreCase)) + { + match = child; + break; + } + if (key.StartsWith(keySequence, StringComparison.InvariantCultureIgnoreCase)) + { + filtered.Add(child); + } + } + if (match != null) + { + keySequence = null; + //Remove the key tips befor executing, since another window might be opened and thus the key tips would be desturbing: + HideQuickAccessKeys(); + ExecuteElement(match); + if (KeyTip.GetStop(match)) + { + // ensure the matched element to be measured: + match.UpdateLayout(); + + ShowQuickAccessKeys(match); + } + else + { + HideQuickAccessKeys(); + } + return true; + + } + if (filtered.Count > 0) + { + elements = filtered; + ShowKeys(elements); + return true; + } + else return false; + } + + private static void ExecuteElement(FrameworkElement e) + { + IKeyTipControl cmd = e as IKeyTipControl; + if (cmd != null) + { + cmd.ExecuteKeyTip(); + return; + } + CheckBox cb = e as CheckBox; + if (cb != null) + { + cb.IsChecked ^= true; + return; + } + ComboBox box = e as ComboBox; + if (box != null) + { + box.Focus(); + box.IsDropDownOpen = true; + return; + } + if (e.Focusable) e.Focus(); + } + + private static void ShowKeys(List elements) + { + CloseQuickAccessKeys(); + popups = new List(); + foreach (var e in elements) + { + double yOffset = KeyTip.GetYOffset(e); + double xOffset = KeyTip.GetXOffset(e); + if (xOffset < 0.0) xOffset = e.ActualWidth + xOffset; + if (yOffset < 0.0) yOffset = e.ActualHeight + yOffset; + + if (double.IsNaN(yOffset)) yOffset = e.ActualHeight - 16; + if (double.IsNaN(xOffset)) xOffset = 12; + string key = KeyTip.GetKey(e); + Popup popup = new Popup(); + popup.AllowsTransparency = true; + popup.Child = new KeyTip() { Text = key }; + popup.PlacementTarget = e; + popup.Placement = PlacementMode.Relative; + popup.HorizontalOffset = xOffset; + popup.VerticalOffset = yOffset; + popup.StaysOpen = true; + popups.Add(popup); + popup.IsOpen = true; + + } + } + + private static void CloseQuickAccessKeys() + { + if (popups != null) + { + foreach (var popup in popups) + { + popup.IsOpen = false; + } + popups = null; + } + } + + private static Stack selectStack = new Stack(); + private static FrameworkElement current; + private static List popups; + private static List elements; + private static string keySequence = ""; + + private static void ShowQuickAccessKeys(FrameworkElement root) + { + selectStack.Push(root); + current = root; + elements = new List(); + GatherChildElements(elements, root); + + if (elements.Count == 0) + { + HideQuickAccessKeys(); + } + else + { + ShowKeys(elements); + } + } + + private static void GatherChildElements(List elements, FrameworkElement root) + { + foreach (var o in LogicalTreeHelper.GetChildren(root)) + { + FrameworkElement e = o as FrameworkElement; + if (e != null) + { + GatherElements(elements, e); + } + } + } + + /// + /// Don't call this directly, it is only used in GatherChildElements. + /// + private static void GatherElements(List elements, FrameworkElement root) + { + if (root.Visibility != Visibility.Visible || root.IsEnabled == false) return; + string key = KeyTip.GetKey(root); + if (key != null) + { + elements.Add(root); + } + if (key == null || !KeyTip.GetStop(root)) + { + foreach (var o in LogicalTreeHelper.GetChildren(root)) + { + FrameworkElement e = o as FrameworkElement; + if (e != null) + { + GatherElements(elements, e); + } + } + } + } + + + + /// + /// Gets the Quick Access Key combination. + /// + /// + /// + public static string GetKey(DependencyObject obj) + { + return (string)obj.GetValue(KeyProperty); + } + + /// + /// Sets the Quick Access Key. + /// + /// + /// + public static void SetKey(DependencyObject obj, string value) + { + obj.SetValue(KeyProperty, value); + } + + public static readonly DependencyProperty KeyProperty = + DependencyProperty.RegisterAttached("Key", typeof(string), typeof(KeyTip), new UIPropertyMetadata(null)); + + + + + public static double GetXOffset(DependencyObject obj) + { + return (double)obj.GetValue(XOffsetProperty); + } + + public static void SetXOffset(DependencyObject obj, double value) + { + obj.SetValue(XOffsetProperty, value); + } + + public static readonly DependencyProperty XOffsetProperty = + DependencyProperty.RegisterAttached("XOffset", typeof(double), typeof(KeyTip), new UIPropertyMetadata(double.NaN)); + + + + + public static double GetYOffset(DependencyObject obj) + { + return (double)obj.GetValue(YOffsetProperty); + } + + public static void SetYOffset(DependencyObject obj, double value) + { + obj.SetValue(YOffsetProperty, value); + } + + // Using a DependencyProperty as the backing store for YOffset. This enables animation, styling, binding, etc... + public static readonly DependencyProperty YOffsetProperty = + DependencyProperty.RegisterAttached("YOffset", typeof(double), typeof(KeyTip), new UIPropertyMetadata(double.NaN)); + + + + + /// + /// Gets whether to stop gathering for KeyTips of child controls. + /// + public static bool GetStop(DependencyObject obj) + { + return (bool)obj.GetValue(StopProperty); + } + + /// + /// Sets whether to stop gathering for KeyTips of child controls. The default value is true. + /// + public static void SetStop(DependencyObject obj, bool value) + { + obj.SetValue(StopProperty, value); + } + + // Using a DependencyProperty as the backing store for Stop. This enables animation, styling, binding, etc... + public static readonly DependencyProperty StopProperty = + DependencyProperty.RegisterAttached("Stop", typeof(bool), typeof(KeyTip), new UIPropertyMetadata(true)); + + + + + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + // Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register("Text", typeof(string), typeof(KeyTip), new UIPropertyMetadata(null)); + + + + + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonApplicationMenu.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonApplicationMenu.cs new file mode 100644 index 0000000..942647b --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonApplicationMenu.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Diagnostics; +using System.Timers; +using System.Windows.Shapes; +using System.Windows.Data; +using Odyssey.Controls.Ribbon.Interfaces; +using Odyssey.Controls.Interfaces; +using System.Collections; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [ContentProperty("Items")] + [TemplatePart(Name = partRecentItemsList)] + [TemplatePart(Name = partAppButton)] + [TemplatePart(Name = partAppButtonClone)] + public class RibbonApplicationMenu : MenuItem,IKeyTipControl + { + const string partRecentItemsList = "PART_RecentItemsList"; + const string partAppButton = "PART_AppButton"; + const string partAppButtonClone = "PART_AppButtonClone"; + + static RibbonApplicationMenu() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonApplicationMenu), new FrameworkPropertyMetadata(typeof(RibbonApplicationMenu))); + } + + public RibbonApplicationMenu() + : base() + { + AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(OnMenuItemClick)); + } + + #region Obsolete +#if false + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + base.PrepareContainerForItemOverride(element, item); + RibbonMenuItem rItem = item as RibbonMenuItem; + if (rItem != null) + { + rItem.MouseEnter += new MouseEventHandler(rItem_MouseEnter); + rItem.MouseLeave += new MouseEventHandler(rItem_MouseLeave); + } + } + + + private System.Timers.Timer timer; + + private Timer EnsureTimer() + { + if (timer == null) + { + timer = new Timer(500); + timer.Elapsed += new ElapsedEventHandler(timer_Elapsed); + } + return timer; + } + + delegate void MyFunc(); + + void timer_Elapsed(object sender, ElapsedEventArgs e) + { + timer.Stop(); + RibbonMenuItem item = selectedItem; + if (item != null) + { + + MyFunc d = delegate() { item.IsSubmenuOpen = true; }; + this.Dispatcher.BeginInvoke(d); + } + } + + private RibbonMenuItem selectedItem; + + void rItem_MouseLeave(object sender, MouseEventArgs e) + { + if (timer != null) timer.Stop(); + selectedItem = null; + RibbonMenuItem item = sender as RibbonMenuItem; + item.IsSubmenuOpen = false; + } + + void rItem_MouseEnter(object sender, MouseEventArgs e) + { + RibbonMenuItem item = sender as RibbonMenuItem; + if (item.HasItems) + { + selectedItem = item; + EnsureTimer().Start(); + } + } +#endif + #endregion + + + void OnMenuItemClick(object sender, RoutedEventArgs e) + { + IsOpen = false; + } + + + protected override bool IsItemItsOwnContainerOverride(object item) + { + return item is RibbonMenuItem || item is Separator; + } + + protected override System.Windows.DependencyObject GetContainerForItemOverride() + { + return new RibbonMenuItem(); + } + + + private FrameworkElement recentItemsList; + private RibbonDropDownButton appButton; + private FrameworkElement appButtonClone; + + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + + appButton = GetTemplateChild(partAppButton) as RibbonDropDownButton; + appButtonClone = GetTemplateChild(partAppButtonClone) as FrameworkElement; + appButton.PopupOpened += new RoutedEventHandler(appButton_PopupOpened); + appButton.PopupClosed += new RoutedEventHandler(appButton_PopupClosed); + + recentItemsList = GetTemplateChild(partRecentItemsList) as FrameworkElement; + } + + void appButton_PopupClosed(object sender, RoutedEventArgs e) + { + IsOpen = false; + } + + + void appButton_PopupOpened(object sender, RoutedEventArgs e) + { + AdjustApplicationButtons(); + IsOpen = true; + } + + + + /// + /// Ensures that both ApplicationMenu buttons are at the same screen location: + /// + private void AdjustApplicationButtons() + { + if (appButtonClone != null && appButton != null) + { + Point p = appButton.PointToScreen(new Point()); + Point p2 = appButtonClone.PointFromScreen(p); + + double dx = p2.X + Canvas.GetLeft(appButtonClone); + double dy = p2.Y + Canvas.GetTop(appButtonClone); + appButtonClone.Visibility = dy >= -20 ? Visibility.Visible : Visibility.Hidden; + Canvas.SetLeft(appButtonClone, dx); + Canvas.SetTop(appButtonClone, dy); + } + } + + /// + /// Gets or sets the content of the footer for the ApplicationMenu. + /// This is a dependency property. + /// + public object Footer + { + get { return (object)GetValue(FooterProperty); } + set { SetValue(FooterProperty, value); } + } + + + // Using a DependencyProperty as the backing store for Footer. This enables animation, styling, binding, etc... + public static readonly DependencyProperty FooterProperty = + DependencyProperty.Register("Footer", typeof(object), typeof(RibbonApplicationMenu), new UIPropertyMetadata(null)); + + + + public object MenuHeader + { + get { return (object)GetValue(MenuHeaderProperty); } + set { SetValue(MenuHeaderProperty, value); } + } + + // Using a DependencyProperty as the backing store for Header. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MenuHeaderProperty = + DependencyProperty.Register("MenuHeader", typeof(object), typeof(RibbonApplicationMenu), new UIPropertyMetadata(null)); + + + + public DataTemplate MenuHeaderTemplate + { + get { return (DataTemplate)GetValue(MenuHeaderTemplateProperty); } + set { SetValue(MenuHeaderTemplateProperty, value); } + } + + // Using a DependencyProperty as the backing store for HeaderTemplate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MenuHeaderTemplateProperty = + DependencyProperty.Register("MenuHeaderTemplate", typeof(DataTemplate), typeof(RibbonApplicationMenu), new UIPropertyMetadata(null)); + + + + + + /// + /// Gets or sets the DataTemplate for the footer. + /// This is a dependency property. + /// + public DataTemplate FooterTemplate + { + get { return (DataTemplate)GetValue(FooterTemplateProperty); } + set { SetValue(FooterTemplateProperty, value); } + } + + // Using a DependencyProperty as the backing store for FooterTemplate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty FooterTemplateProperty = + DependencyProperty.Register("FooterTemplate", typeof(DataTemplate), typeof(RibbonApplicationMenu), new UIPropertyMetadata(null)); + + + + + public object RecentItemsSource + { + get { return (object)GetValue(RecentItemsSourceProperty); } + set { SetValue(RecentItemsSourceProperty, value); } + } + + // Using a DependencyProperty as the backing store for RecentItemsSource. This enables animation, styling, binding, etc... + public static readonly DependencyProperty RecentItemsSourceProperty = + DependencyProperty.Register("RecentItemsSource", typeof(object), typeof(RibbonApplicationMenu), new UIPropertyMetadata(null)); + + + + + + public bool IsOpen + { + get { return (bool)GetValue(IsOpenProperty); } + set { SetValue(IsOpenProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsOpen. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsOpenProperty = + DependencyProperty.Register("IsOpen", typeof(bool), typeof(RibbonApplicationMenu), + new UIPropertyMetadata(false, OpenPropertyChanged)); + + static void OpenPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonApplicationMenu menu = (RibbonApplicationMenu)o; + bool newValue = (bool)e.NewValue; + + if (menu.appButton != null) + { + menu.appButton.IsDropDownPressed = newValue; + } + if (newValue) menu.OnMenuOpened(); else menu.OnMenuClosed(); + } + + private object toolTip; + + public event EventHandler Opened; + + public event EventHandler OnClosed; + + protected virtual void OnMenuClosed() + { + // restore the tooltip when the menu is closed: + ToolTip = toolTip; + if (OnClosed != null) OnClosed(this, EventArgs.Empty); + } + + protected virtual void OnMenuOpened() + { + // when the menu is open, the tooltip must not be shown: + toolTip = this.ToolTip; + ToolTip = null; + if (Opened != null) Opened(this, EventArgs.Empty); + + } + + + /// Gets or sets the control that represents the recent items list. + /// This is a dependency property. + /// + public object RecentItemsList + { + get { return (object)GetValue(RecentItemsListProperty); } + set { SetValue(RecentItemsListProperty, value); } + } + + // Using a DependencyProperty as the backing store for RectentItemsList. This enables animation, styling, binding, etc... + public static readonly DependencyProperty RecentItemsListProperty = + DependencyProperty.Register("RecentItemsList", typeof(object), typeof(RibbonApplicationMenu), new UIPropertyMetadata(null)); + + + + /// + /// Gets or sets the DataTemplate for the RecentItemsList. + /// This is a dependency property. + /// + public DataTemplate RecentItemsListTemplate + { + get { return (DataTemplate)GetValue(RecentItemsListTemplateProperty); } + set { SetValue(RecentItemsListTemplateProperty, value); } + } + + // Using a DependencyProperty as the backing store for RectentItemsListTemplate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty RecentItemsListTemplateProperty = + DependencyProperty.Register("RecentItemsListTemplate", typeof(DataTemplate), typeof(RibbonApplicationMenu), new UIPropertyMetadata(null)); + + + + + public ImageSource MenuButtonImage + { + get { return (ImageSource)GetValue(MenuButtonImageProperty); } + set { SetValue(MenuButtonImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for MenuButtonImage. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MenuButtonImageProperty = + DependencyProperty.Register("MenuButtonImage", typeof(ImageSource), typeof(RibbonApplicationMenu), new UIPropertyMetadata(null)); + + + + + /// + /// Gets the rectangle where to place the sub menus. + /// + /// The ApplicationMenu class + /// A rectangle. + internal Rect GetSubMenuRect(Visual visual) + { + if (recentItemsList != null) + { + Rect rect = new Rect(0.0, 0.0, recentItemsList.ActualWidth, recentItemsList.ActualHeight); + rect = recentItemsList.TransformToVisual(visual).TransformBounds(rect); + return rect; + } + else return Rect.Empty; + } + + #region IKeyboardCommand Members + + void IKeyTipControl.ExecuteKeyTip() + { + this.Focus(); + this.IsOpen = true; + } + + #endregion + + protected override System.Collections.IEnumerator LogicalChildren + { + get + { + return GetLogicalChildren().GetEnumerator(); + } + } + + private IEnumerable GetLogicalChildren() + { + if (Header != null) yield return Header; + IEnumerator e = base.LogicalChildren; + while (e.MoveNext()) + { + yield return e.Current; + } + if (Footer != null) yield return Footer; + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonApplicationMenuItem.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonApplicationMenuItem.cs new file mode 100644 index 0000000..ff493a3 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonApplicationMenuItem.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public class RibbonApplicationMenuItem:RibbonMenuItem + { + static RibbonApplicationMenuItem() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonApplicationMenuItem), new FrameworkPropertyMetadata(typeof(RibbonApplicationMenuItem))); + } + + protected override bool IsItemItsOwnContainerOverride(object item) + { + return item is RibbonMenuItem; + } + + protected override DependencyObject GetContainerForItemOverride() + { + return new RibbonApplicationMenuItem(); + } + + + + /// + /// Gets or sets the title for the sub menu popup. + /// This is a dependency property. + /// + public object SubMenuTitle + { + get { return (object)GetValue(SubMenuTitleProperty); } + set { SetValue(SubMenuTitleProperty, value); } + } + + public static readonly DependencyProperty SubMenuTitleProperty = + DependencyProperty.Register("SubMenuTitle", typeof(object), typeof(RibbonApplicationMenuItem), new UIPropertyMetadata(null)); + + + + /// + /// Gets or sets the content that appears in the right part of the ApplicationMenu. + /// + public object SubMenuContent + { + get { return (object)GetValue(SubMenuContentProperty); } + set { SetValue(SubMenuContentProperty, value); } + } + + public static readonly DependencyProperty SubMenuContentProperty = + DependencyProperty.Register("SubMenuContent", typeof(object), typeof(RibbonApplicationMenuItem), new UIPropertyMetadata(null)); + + + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonBar.Commands.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonBar.Commands.cs new file mode 100644 index 0000000..2fc8d3f --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonBar.Commands.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Input; +using System.Windows; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + partial class RibbonBar + { + public static readonly RoutedUICommand AlignGroupsLeftCommand = new RoutedUICommand("Align Left", "AlignGroupsLeftCommand", typeof(RibbonBar)); + public static readonly RoutedUICommand AlignGroupsRightCommand = new RoutedUICommand("Align Right", "AlignGroupsRightCommand", typeof(RibbonBar)); + public static readonly RoutedUICommand CollapseRibbonBarCommand = new RoutedUICommand("", "CollapseRibbonBarCommand", typeof(RibbonBar)); + + public static readonly RoutedUICommand QAPlacementTopCommand = new RoutedUICommand("Show Above the Ribbon.", "QAPlacementTopCommand", typeof(RibbonBar)); + public static readonly RoutedUICommand QAPlacementBottomCommand = new RoutedUICommand("Show Below the Ribbon.", "QAPlacementBottomCommand", typeof(RibbonBar)); + + private static void RegisterCommands() + { + CommandManager.RegisterClassCommandBinding(typeof(RibbonBar), new CommandBinding(AlignGroupsLeftCommand, alignGroupsLeft)); + CommandManager.RegisterClassCommandBinding(typeof(RibbonBar), new CommandBinding(AlignGroupsRightCommand, alignGroupsRight)); + CommandManager.RegisterClassCommandBinding(typeof(RibbonBar), new CommandBinding(CollapseRibbonBarCommand, collapseRibbonBar)); + + CommandManager.RegisterClassCommandBinding(typeof(RibbonBar), new CommandBinding(QAPlacementTopCommand, QAPlacementTop, IsQAPlacementTopEnabled)); + CommandManager.RegisterClassCommandBinding(typeof(RibbonBar), new CommandBinding(QAPlacementBottomCommand, QAPlacementBottom, IsQAPlacementBottomEnabled)); + } + + private static void QAPlacementTop(object sender, ExecutedRoutedEventArgs e) + { + RibbonBar bar = (RibbonBar)sender; + bar.ToolbarPlacement = QAPlacement.Top; + } + + private static void QAPlacementBottom(object sender, ExecutedRoutedEventArgs e) + { + RibbonBar bar = (RibbonBar)sender; + bar.ToolbarPlacement = QAPlacement.Bottom; + } + + private static void IsQAPlacementTopEnabled(object sender, CanExecuteRoutedEventArgs e) + { + RibbonBar bar = (RibbonBar)sender; + e.CanExecute = bar.ToolbarPlacement == QAPlacement.Bottom; + } + + private static void IsQAPlacementBottomEnabled(object sender, CanExecuteRoutedEventArgs e) + { + RibbonBar bar = (RibbonBar)sender; + e.CanExecute = bar.ToolbarPlacement == QAPlacement.Top; + } + + private static void collapseRibbonBar(object sender, ExecutedRoutedEventArgs e) + { + RibbonBar bar = (RibbonBar)sender; + bar.IsExpanded = false; + + } + + private static void alignGroupsLeft(object sender, ExecutedRoutedEventArgs e) + { + RibbonBar bar = (RibbonBar)sender; + bar.AlignGroupsLeft(); + } + + + private static void alignGroupsRight(object sender, ExecutedRoutedEventArgs e) + { + RibbonBar bar = (RibbonBar)sender; + bar.AlignGroupsRight(); + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonBar.Handlers.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonBar.Handlers.cs new file mode 100644 index 0000000..1cb61c0 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonBar.Handlers.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Diagnostics; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + partial class RibbonBar + { + private void RegisterHandlers() + { + //AddHandler(RibbonBar.AlignGroupsLeftEvent, new RoutedEventHandler(OnAlignGroupsLeft)); + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonBar.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonBar.cs new file mode 100644 index 0000000..f42eb23 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonBar.cs @@ -0,0 +1,1436 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Controls.Primitives; +using System.Collections.ObjectModel; +using System.Windows.Markup; +using System.Diagnostics; +using System.ComponentModel; +using Odyssey.Controls.Classes; +using Odyssey.Controls.Ribbon.Interfaces; +using System.Collections.Specialized; +using System.Collections; +using Odyssey.Ribbon.EventArgs; +using Odyssey.Common; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [ContentProperty("Tabs")] + [TemplatePart(Name = partGroupPanel)] + [TemplatePart(Name = partPopup)] + [TemplatePart(Name = partTabItemContainer)] + [TemplatePart(Name = partPopupGroupPanel)] + public partial class RibbonBar : Selector + { + const string partGroupPanel = "PART_GroupPanel"; + const string partPopupGroupPanel = "PART_PopupGroupPanel"; + const string partPopup = "PART_Popup"; + const string partTabItemContainer = "PART_TabItemContainer"; + const string partLeftTitle = "PART_LeftWindowTitlePlaceHolder"; + const string partRightTitle = "PART_RightWindowTitlePlaceHolder"; + const string partTopQAPresenter = "PART_TopQaPresenter"; + + static RibbonBar() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonBar), new FrameworkPropertyMetadata(typeof(RibbonBar))); + RegisterCommands(); + } + + public RibbonBar() + : base() + { + AddHandler(LoadedEvent, new RoutedEventHandler(OnLoaded)); + + AddHandler(Button.ClickEvent, new RoutedEventHandler(OnChildClick)); + AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(OnMenuItemClick2)); + AddHandler(RibbonComboBox.DropDownClosedEvent, new RoutedEventHandler(OnMenuItemClick)); + AddHandler(RibbonSplitButton.ClickEvent, new RoutedEventHandler(OnMenuItemClick2)); + AddHandler(RibbonGallery.SelectionChangedEvent, new RoutedEventHandler(OnMenuItemClick)); + RegisterHandlers(); + } + + + /// + /// Closes any popups when some known routed events occured. + /// + private void OnMenuItemClick(object sender, RoutedEventArgs e) + { + FrameworkElement fe = e.OriginalSource as FrameworkElement; + if (fe != null && fe.TemplatedParent is RibbonGroup) return; + if (fe != null && fe.TemplatedParent != null) return; + if (popup != null && !(e.OriginalSource is RibbonDropDownButton)) popup.IsOpen = false; + IsMenuOpen = false; + RibbonGroup.CloseOpenedPopup(); + } + + /// + /// Closes any popups when some known routed events occured. + /// + private void OnMenuItemClick2(object sender, RoutedEventArgs e) + { + FrameworkElement fe = e.OriginalSource as FrameworkElement; + if (fe != null && fe.TemplatedParent is RibbonGroup) return; + if (fe != null && fe.TemplatedParent != null) return; + if (popup != null) popup.IsOpen = false; + IsMenuOpen = false; + RibbonGroup.CloseOpenedPopup(); + } + + + /// + /// Closes any popups when some known routed events occured. + /// + private void OnChildClick(object sender, RoutedEventArgs e) + { + if (((e.OriginalSource is IRibbonButton) || (e.OriginalSource is MenuItem))) + { + RibbonButton btn = e.OriginalSource as RibbonButton; + if (btn != null) + { + if (btn.TemplatedParent is RibbonGallery) + { + return; + } + } + RibbonGroup.CloseOpenedPopup(); + + if (popup != null) + { + // check if the source is either the left or right scrollbar button for the overlapped tab and don't + // collapse the ribbon in that case: + RibbonButton b = e.OriginalSource as RibbonButton; + if (b != null) + { + ICommand command = b.Command; + if (command == RibbonTabScroller.ScrollRightCommand || command == RibbonTabScroller.ScrollLeftCommand) return; + } + + popup.IsOpen = false; + } + } + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + AddHandler(RibbonBar.SelectedTabIndexChangedEvent, new RoutedEventHandler(SelectedIndexChanged)); + if (SelectedTabIndex >= 0) SelectedIndexChanged(this, e); + } + + /// + /// Occurs when the selected tab is changed. + /// + public event RoutedPropertyChangedEventHandler SelectedTabIndexChanged + { + add { AddHandler(RibbonBar.SelectedTabIndexChangedEvent, value); } + remove { RemoveHandler(RibbonBar.SelectedTabIndexChangedEvent, value); } + } + + private Control groupPanel; + private Control popupGroupPanel; + private Popup popup; + private Panel tabItemContainer; + private FrameworkElement leftTitleControl; + private FrameworkElement rightTitleControl; + private FrameworkElement topQAPresenterControl; + + /// + /// Gets the popup content. This property is used by RibbonToolTip. + /// + protected internal Popup Popup { get { return popup; } } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + groupPanel = GetTemplateChild(partGroupPanel) as Control; + popupGroupPanel = GetTemplateChild(partPopupGroupPanel) as Control; + + if (popup != null) + { + popup.Opened -= OnPopupOpened; + popup.Closed -= OnPopupClosed; + } + + leftTitleControl = GetTemplateChild(partLeftTitle) as FrameworkElement; + rightTitleControl = GetTemplateChild(partRightTitle) as FrameworkElement; + topQAPresenterControl = GetTemplateChild(partTopQAPresenter) as FrameworkElement; + + popup = GetTemplateChild(partPopup) as Popup; + + if (popup != null) + { + popup.Opened += new EventHandler(OnPopupOpened); + popup.Closed += new EventHandler(OnPopupClosed); + } + + if (tabItemContainer != null) tabItemContainer.Children.Clear(); + tabItemContainer = GetTemplateChild(partTabItemContainer) as Panel; + if (tabItemContainer != null) + { + foreach (RibbonTabItem tab in Tabs) + { + tabItemContainer.Children.Add(tab); + } + SetContextualTabs(ContextualTabSet); + } + + } + + protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + base.OnLostKeyboardFocus(e); + if (!this.IsKeyboardFocusWithin) + { + DependencyObject focused = Keyboard.FocusedElement as DependencyObject; + if (focused == null || !this.IsAncestorOf(focused)) + { + IsMenuOpen = false; + } + } + } + + + protected virtual void OnPopupOpened(object sender, EventArgs e) + { + MeasureGroups(); + Mouse.Capture(this, CaptureMode.SubTree); + } + + protected virtual void OnPopupClosed(object sender, EventArgs e) + { + IsMenuOpen = false; + IsExpanded = false; + Mouse.Capture(null); + } + + + protected override void OnVisualParentChanged(DependencyObject oldParent) + { + FrameworkElement parent = oldParent as FrameworkElement; + if (parent != null) parent.SizeChanged -= ParentSizeChanged; + base.OnVisualParentChanged(oldParent); + + parent = this.Parent as FrameworkElement; + if (parent != null) + { + parent.SizeChanged += new SizeChangedEventHandler(ParentSizeChanged); + } + } + + public static Size InfiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + + void ParentSizeChanged(object sender, SizeChangedEventArgs e) + { + if (e.WidthChanged) MeasureGroups(); + } + + protected override Size ArrangeOverride(Size arrangeBounds) + { + Size size = base.ArrangeOverride(arrangeBounds); + + CalculateContextualTabSetPos(); + CalculateTitleBarPosition(); + return size; + } + + private void CalculateContextualTabSetPos() + { + RibbonContextualTabSet set = ContextualTabSet; + if (set != null) + { + double w = 0.0; + foreach (var tab in set.Tabs) w += tab.DesiredSize.Width; + set.Width = w; + } + } + + /// + /// Calculates the position and size for the title bar depending on some named template controls. + /// + private void CalculateTitleBarPosition() + { + if (!IsContextualTabVisible) + { + CalculateTitleBarPosWithoutContextualTab(); + } + else + { + CalculateTitleBarPosWithContextualTab(); + } + } + + private FrameworkElement windowTitle + { + get { return this.GetTemplateChild("PART_WindowTitle") as FrameworkElement; } + } + + private FrameworkElement buttonsPlaceHolder + { + get { return this.GetTemplateChild("PART_ButtonsPlaceholder") as FrameworkElement; } + } + + private FrameworkElement titlePlaceHolder + { + get { return this.GetTemplateChild("PART_TitlePlaceholder") as FrameworkElement; } + } + + + + private void CalculateTitleBarPosWithContextualTab() + { + + Point p1 = rightTitleControl.TranslatePoint(new Point(), titlePlaceHolder); + + Point p2 = buttonsPlaceHolder.TranslatePoint(new Point(), titlePlaceHolder); + Double w = p2.X - p1.X; + Double w2 = leftTitleControl.ActualWidth; + if (w2 > w) + { + p1 = leftTitleControl.TranslatePoint(new Point(), titlePlaceHolder); + w = w2; + } + Canvas.SetLeft(windowTitle, p1.X); + windowTitle.Width = Math.Max(0.0, w); + } + + + private void CalculateTitleBarPosWithoutContextualTab() + { + Point p1 = topQAPresenterControl.TranslatePoint(new Point(topQAPresenterControl.ActualWidth, 0), titlePlaceHolder); + Point p2 = buttonsPlaceHolder.TranslatePoint(new Point(), titlePlaceHolder); + Canvas.SetLeft(windowTitle, p1.X); + windowTitle.Width = Math.Max(0.0, p2.X - p1.X); + } + + + + const double MIN_RIBBON_WIDTH = 240; + + private void MeasureGroups() + { + Control parent = null; + if (CanMinimize) + { + parent = popupGroupPanel != null ? popupGroupPanel.Parent as Control : null; + } + else + { + parent = groupPanel != null ? groupPanel.Parent as Control : null; + } + double actualWidth = parent != null ? parent.ActualWidth : this.ActualWidth; + IsMinimized = ActualWidth < MIN_RIBBON_WIDTH; + if (!IsMinimized) + { + double left = CalculateGroupsWidth(); + + left = ExpandOrReduceGroups(left, actualWidth); + + AdjustGroupAlignment(left, actualWidth); + } + } + + private void AdjustGroupAlignment(double left, double actualWidth) + { + if (left > actualWidth) + { + if (GroupAlignment == RibbonBarAlignment.Full) GroupAlignment = RibbonBarAlignment.Left; + } + else + { + GroupAlignment = RibbonBarAlignment.Full; + } + } + + private double ExpandOrReduceGroups(double left, double actualWidth) + { + int level = GetMaxLevel(); + while (level >= 0 && left < actualWidth) + { + left = ExpandGroups(left, actualWidth, level--); + } + + if (left > actualWidth) + { + level = GetMinLevel(); + while (CanReduce() && left > actualWidth) + { + left = ReduceGroups(left, actualWidth, level++); + } + } + return left; + } + + + private double CalculateGroupsWidth() + { + double width = 0; + + foreach (RibbonGroup group in GetSelectedGroups()) + { + if (!group.IsMeasureValid) + { + group.Measure(InfiniteSize); + } + width += group.DesiredSize.Width; + } + return width; + } + + private int GetMinLevel() + { + int level = int.MaxValue; + foreach (RibbonGroup g in GetWrapPanelsByReductionOrder()) + { + level = Math.Min(level, g.ReductionLevel); + if (level == 0) break; + } + return level; + } + + private int GetMaxLevel() + { + int level = 0; + foreach (RibbonGroup g in GetWrapPanelsByReductionOrder()) + { + level = Math.Max(level, g.ReductionLevel); + } + return level; + } + + private bool CanExpand() + { + foreach (RibbonGroup g in GetWrapPanelsByReductionOrder()) + { + if (g.ReductionLevel > 0) return true; + } + return false; + } + + private bool CanReduce() + { + foreach (RibbonGroup g in GetWrapPanelsByReductionOrder()) + { + if (!g.IsMinimized) return true; + } + return false; + } + + + + public static bool GetCloseDropDownOnClick(DependencyObject obj) + { + return (bool)obj.GetValue(CloseDropDownOnClickProperty); + } + + public static void SetCloseDropDownOnClick(DependencyObject obj, bool value) + { + obj.SetValue(CloseDropDownOnClickProperty, value); + } + + // Using a DependencyProperty as the backing store for AffectsControlDropDown. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CloseDropDownOnClickProperty = + DependencyProperty.RegisterAttached("CloseDropDownOnClickProperty", typeof(bool), typeof(Control), + new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender | FrameworkPropertyMetadataOptions.Inherits)); + + + + + /// + /// Gets wether the controls affects a dropdown window to close when it is clicked. + /// This is an ineritable attached property. + /// + public static bool GetAffectsDropDown(DependencyObject obj) + { + return (bool)obj.GetValue(AffectsDropDownProperty); + } + + /// + /// Sets wether the controls affects a dropdown window to close when it is clicked. + /// This is an inheritable attached property. + /// + public static void SetAffectsDropDown(DependencyObject obj, bool value) + { + obj.SetValue(AffectsDropDownProperty, value); + } + + public static readonly DependencyProperty AffectsDropDownProperty = + DependencyProperty.RegisterAttached("AffectsDropDown", typeof(bool), typeof(RibbonBar), + new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender | FrameworkPropertyMetadataOptions.Inherits)); + + + + /// + /// Gets or sets the selected TabItem + /// This is a dependency property. + /// + public RibbonTabItem SelectedTabItem + { + get { return GetTabItemFromIndex(SelectedTabIndex); } + set + { + Select(value); + } + } + + private double ReduceGroups(double left, double actualWidth, int level) + { + if (left > actualWidth) + { + foreach (RibbonGroup group in GetWrapPanelsByReductionOrder()) + { + if (group.ReductionLevel <= level) + { + double w0 = group.DesiredSize.Width; + group.Reduce(); + double w1 = group.DesiredSize.Width; + double dw = w0 - w1; + + left -= dw; + } + if (left <= actualWidth) break; + } + } + return left; + } + + private double ExpandGroups(double left, double actualWidth, int level) + { + foreach (RibbonGroup group in GetWrapPanelsByReductionOrder().Reverse()) + { + if (group.ReductionLevel > level) + { + double w0 = group.DesiredSize.Width; + group.Expand(); + group.UpdateLayout(); + double w1 = group.DesiredSize.Width; + double dw = w0 - w1; + + left -= dw; + } + if (left > actualWidth) break; + } + + return left; + } + + protected override void OnLostMouseCapture(MouseEventArgs e) + { + CheckPopupTabToClose(e); + base.OnLostMouseCapture(e); + } + + private void CheckPopupTabToClose(MouseEventArgs e) + { + if (popup == null) return; + if (!CanMinimize) return; + FrameworkElement fe = e.OriginalSource as FrameworkElement; + // if (fe != null && fe.TemplatedParent != null) return; + FrameworkElement captured = Mouse.Captured as FrameworkElement; + if (captured != this && popup != null) + { + UIElement child = this.popup.Child; + if (e.OriginalSource == this) + { + if (captured != null && captured.TemplatedParent != null) return; + if ((Mouse.Captured == null) || !child.IsAncestorOf(Mouse.Captured as DependencyObject)) + { + this.IsExpanded = false; + e.Handled = true; + } + } + else if (child.IsAncestorOf(e.OriginalSource as DependencyObject)) + { + if (this.IsExpanded && (Mouse.Captured == null)) + { + Mouse.Capture(this, CaptureMode.SubTree); + e.Handled = true; + } + } + else + { + + if (!child.IsLogicalAncestorOf(captured)) + { + this.IsExpanded = false; + e.Handled = true; + } + } + } + } + + protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnPreviewMouseLeftButtonDown(e); + if (e.OriginalSource == this) + { + Mouse.Capture(null); + } + } + + + private IEnumerable GetWrapPanelsByReductionOrder() + { + RibbonTabItem item = SelectedTabItem; + if (item != null && item.ReductionOrder != null) + { + return GetWrapPanelsFromCollection(item.ReductionOrder); + } + return GetDefaultWrapPanelOrder(); + } + + private IEnumerable GetDefaultWrapPanelOrder() + { + if (SelectedGroups != null) + { + for (int i = this.SelectedGroups.Count - 1; i >= 0; i--) + { + RibbonGroup group = SelectedGroups[i] as RibbonGroup; + if (group != null) yield return group; + } + } + } + + + private IEnumerable GetWrapPanelsFromCollection(StringCollection names) + { + if (SelectedGroups != null) + { + foreach (var item in SelectedGroups) + { + RibbonGroup group = (RibbonGroup)item; + foreach (string name in names) + { + if (group.Name.Equals(name)) yield return group; + } + } + } + } + + + private void SelectedIndexChanged(object sender, RoutedEventArgs e) + { + IsMenuOpen = false; + if (SelectedTabIndex >= 0) + { + RibbonTabItem item = GetTabItemFromIndex(SelectedTabIndex); + SetSelectedTabItem(item); + } + MeasureGroups(); + } + + + private RibbonTabItem GetTabItemFromIndex(int index) + { + if (index < 0) return null; + if (index < Tabs.Count && Tabs.Count > 0) return Tabs[index]; + + index -= Tabs.Count; + if (ContextualTabSet != null) + { + int c = ContextualTabSet.Tabs.Count; + if (c > 0 && c > index) return ContextualTabSet.Tabs[index]; + } + return null; + } + + private void SetSelectedTabItem(RibbonTabItem item) + { + SelectedGroups = item != null ? item.Items : null; + ItemsSource = SelectedGroups; + int index = SelectedTabIndex; + for (int i = 0; i < Tabs.Count; i++) + { + item = Tabs[i]; + item.IsSelected = i == index; + } + RibbonContextualTabSet currentSet = ContextualTabSet; + if (currentSet != null) + { + index -= Tabs.Count; + currentSet.SetSelectedTabItem(index); + } + if (contextualTabSets != null) + { + foreach (var set in contextualTabSets) + { + if (set != currentSet) + { + set.SetSelectedTabItem(-1); + } + } + } + } + + IEnumerable GetSelectedGroups() + { + if (SelectedGroups != null) + { + foreach (var item in SelectedGroups) + { + yield return (RibbonGroup)item; + } + } + } + + + + /// + /// Gets the groups of the selected tab item. + /// This is a dependency property. + /// + /// + /// This dependency property is required for the ControlTemplate to attach the groups. + /// + public ItemCollection SelectedGroups + { + get { return (ItemCollection)GetValue(SelectedGroupsProperty); } + private set { SetValue(SelectedGroupsProperty, value); } + } + + // Using a DependencyProperty as the backing store for SelectedGroup. This enables animation, styling, binding, etc... + public static readonly DependencyProperty SelectedGroupsProperty = + DependencyProperty.Register("SelectedGroups", typeof(ItemCollection), typeof(RibbonBar), + new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.None, + SelectedGroupsPropertyChanged)); + + public static void SelectedGroupsPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonBar bar = (RibbonBar)o; + + bar.SelectedItem = e.NewValue; + bar.OnSelectedGroupsChanged(e); + } + + protected virtual void OnSelectedGroupsChanged(DependencyPropertyChangedEventArgs e) + { + } + + + /// + /// Selects the specified Tabitem. + /// + private void Select(RibbonTabItem tabItem) + { + int index = GetTabIndex(tabItem); + bool isCurrent = index == this.SelectedTabIndex; + this.SelectedTabIndex = index; + if (CanMinimize) + { + if (isCurrent && IsExpanded) this.IsExpanded ^= true; else this.IsExpanded = true; + } + } + + private int GetTabIndex(RibbonTabItem tabItem) + { + int index = Tabs.IndexOf(tabItem); + if (index >= 0) return index; + + if (ContextualTabSet != null) + { + index = ContextualTabSet.Tabs.IndexOf(tabItem); + return index + Tabs.Count; + } + return -1; + } + + + + /// + /// Gets or sets whether the tab is expanded when CanMinimize is set to true. + /// This is a dependency property. + /// + public bool IsExpanded + { + get { return (bool)GetValue(IsExpandedProperty); } + set { SetValue(IsExpandedProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsExpanded. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsExpandedProperty = + DependencyProperty.Register("IsExpanded", typeof(bool), typeof(RibbonBar), + new UIPropertyMetadata(false, ExpandedPropertyChanged)); + + + static void ExpandedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + RibbonBar bar = (RibbonBar)d; + bar.OnExpandedChanged(e); + } + + /// + /// Occurs when the IsExpanded property has changed. + /// + /// + protected virtual void OnExpandedChanged(DependencyPropertyChangedEventArgs e) + { + bool isExpanded = (bool)e.NewValue; + if (popup != null) + { + if (GroupAlignment == RibbonBarAlignment.Right) GroupAlignment = RibbonBarAlignment.Left; + popup.IsOpen = isExpanded; + } + if (SelectedTabItem != null && CanMinimize) + { + SelectedTabItem.IsSelected = isExpanded; + } + } + + + internal RibbonBarAlignment GroupAlignment + { + get { return (RibbonBarAlignment)GetValue(GroupAlignmentProperty); } + set { SetValue(GroupAlignmentProperty, value); } + } + + // Using a DependencyProperty as the backing store for GroupAlignment. This enables animation, styling, binding, etc... + internal static readonly DependencyProperty GroupAlignmentProperty = + DependencyProperty.Register("GroupAlignment", typeof(RibbonBarAlignment), typeof(RibbonBar), + new UIPropertyMetadata(RibbonBarAlignment.Full, GroupAlignmentPropertyChanged)); + + + public static void GroupAlignmentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + RibbonBar bar = (RibbonBar)d; + RibbonBarAlignment alignment = (RibbonBarAlignment)e.NewValue; + + bar.SetGroupAlignment(alignment); + } + + protected virtual void SetGroupAlignment(RibbonBarAlignment alignment) + { + } + + + + /// + /// Gets or sets whether the ribbon bar can minimize the tab. + /// This is a dependency property. + /// + public bool CanMinimize + { + get { return (bool)GetValue(CanMinimizeProperty); } + set { SetValue(CanMinimizeProperty, value); } + } + + // Using a DependencyProperty as the backing store for CanMinimize. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CanMinimizeProperty = + DependencyProperty.Register("CanMinimize", typeof(bool), typeof(RibbonBar), + new FrameworkPropertyMetadata(false, + FrameworkPropertyMetadataOptions.AffectsRender | + FrameworkPropertyMetadataOptions.AffectsMeasure | + FrameworkPropertyMetadataOptions.AffectsRender, + MinimizePropertyChanged)); + + + private static void MinimizePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonBar bar = (RibbonBar)o; + bool canMinimize = (bool)e.NewValue; + + bar.OnMinimizeChanged(canMinimize); + } + + protected virtual void OnMinimizeChanged(bool canMinimize) + { + if (SelectedTabItem != null) + { + SelectedTabItem.IsSelected = !IsExpanded; + } + } + + + /// + /// Gets or sets the index of the selected tab. + /// The index is equivalent to the order of the visual tab items, including possible contextual tabs. + /// This is a dependency property. + /// + public int SelectedTabIndex + { + get { return (int)GetValue(SelectedTabIndexProperty); } + set { SetValue(SelectedTabIndexProperty, value); } + } + + // Using a DependencyProperty as the backing store for SelectedIndex. This enables animation, styling, binding, etc... + public static readonly DependencyProperty SelectedTabIndexProperty = + DependencyProperty.Register("SelectedTabIndex", typeof(int), typeof(RibbonBar), new UIPropertyMetadata(0, SelectedTabIndexPropertyChanged)); + + + public static readonly RoutedEvent SelectedTabIndexChangedEvent = EventManager.RegisterRoutedEvent("SelectedTabIndexChanged", + RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler), typeof(RibbonBar)); + + + static void SelectedTabIndexPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + RibbonBar bar = (RibbonBar)d; + int oldIndex = (int)e.OldValue; + int newIndex = (int)e.NewValue; + + + RoutedPropertyChangedEventArgs args = new RoutedPropertyChangedEventArgs(oldIndex, newIndex, SelectedTabIndexChangedEvent); + bar.RaiseEvent(args); + bar.OnSelectedTabIndexChanged(args); + } + + protected virtual void OnSelectedTabIndexChanged(RoutedPropertyChangedEventArgs e) + { + Color = SelectedTabItem != null && SelectedTabItem.tabSet != null ? SelectedTabItem.tabSet.Color : Colors.Transparent; + } + + + + private ObservableCollection tabs; + + /// + /// Gets the collection of RibbonTabItems. + /// + public Collection Tabs + { + get + { + if (tabs == null) + { + tabs = new ObservableCollection(); + tabs.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(OnTabItemCollectionChanged); + } + return tabs; + } + } + + + /// + /// Occurs when the TabItems collection has changed. + /// + protected virtual void OnTabItemCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (RibbonTabItem tab in e.NewItems) + { + tab.RibbonBar = this; + } + break; + + case NotifyCollectionChangedAction.Remove: + foreach (RibbonTabItem tab in e.OldItems) + { + tab.RibbonBar = null; + } + break; + } + } + + private ObservableCollection contextualTabSets; + + /// + /// Gets the collection of ContextualTabSets. + /// + public Collection ContextualTabSets + { + get + { + if (contextualTabSets == null) + { + contextualTabSets = new ObservableCollection(); + contextualTabSets.CollectionChanged += new NotifyCollectionChangedEventHandler(OnContextualTabSetsChanged); + } + return contextualTabSets; + } + } + + /// + /// Occurs when the ContextualTabSets collection has changed. + /// + protected virtual void OnContextualTabSetsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (RibbonContextualTabSet set in e.NewItems) + { + set.RibbonBar = this; + } + break; + + case NotifyCollectionChangedAction.Remove: + foreach (RibbonContextualTabSet set in e.OldItems) + { + set.RibbonBar = null; + } + break; + } + } + + + /// + /// Gets whether a contextual tab is visible. + /// + public bool IsContextualTabVisible + { + get { return (bool)GetValue(IsContextualTabVisibleProperty); } + protected set { SetValue(IsContextualTabVisiblePropertyKey, value); } + } + + private static readonly DependencyPropertyKey IsContextualTabVisiblePropertyKey = + DependencyProperty.RegisterReadOnly("IsContextualTabVisible", typeof(bool), typeof(RibbonBar), new FrameworkPropertyMetadata(false, + FrameworkPropertyMetadataOptions.AffectsArrange | + FrameworkPropertyMetadataOptions.AffectsMeasure | + FrameworkPropertyMetadataOptions.AffectsParentMeasure, + OnIsContextualTabVisiblePropertyChanged)); + + public static readonly DependencyProperty IsContextualTabVisibleProperty = IsContextualTabVisiblePropertyKey.DependencyProperty; + + + private static void OnIsContextualTabVisiblePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonBar bar = (RibbonBar)o; + bool newValue = (bool)e.NewValue; + + bar.InvalidateMeasure(); + } + + /// + /// Gets or sets the selected ContextualTabSet. + /// This is a dependency property. + /// + public RibbonContextualTabSet ContextualTabSet + { + get { return (RibbonContextualTabSet)GetValue(ContextualTabSetProperty); } + set { SetValue(ContextualTabSetProperty, value); } + } + + // Using a DependencyProperty as the backing store for ContextualTabSet. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ContextualTabSetProperty = + DependencyProperty.Register("ContextualTabSet", typeof(RibbonContextualTabSet), typeof(RibbonBar), + new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, + ContextualTabSetPropertyChanged)); + + public static void ContextualTabSetPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonBar bar = (RibbonBar)o; + bar.OnContextualTabSetChanged(e); + } + + protected virtual void OnContextualTabSetChanged(DependencyPropertyChangedEventArgs e) + { + RibbonContextualTabSet oldSet = e.OldValue as RibbonContextualTabSet; + RibbonContextualTabSet newSet = e.NewValue as RibbonContextualTabSet; + + IsContextualTabVisible = newSet != null; + + RemovePreviousContextualTabs(oldSet); + SetContextualTabs(newSet); + + AdjustContextualTabSet(e.NewValue as RibbonContextualTabSet); + int index = GetTabIndex(SelectedTabItem); + if (index < 0) SelectedTabIndex = 0; + + } + + /// + /// Gets the first visible RibbonTabItem otherwise null. + /// + public RibbonTabItem FirstVisibleTabItem + { + get + { + foreach (RibbonTabItem tab in Tabs) + { + if (tab.IsVisible) return tab; + } + return null; + } + } + + /// + /// Ensures that only a visible RibbonTabItem is selected. + /// + public void EnsureTabIsVisible() + { + + RibbonTabItem item = SelectedTabItem; + if (item == null || (item.Visibility != Visibility.Visible)) + { + SelectedTabItem = FirstVisibleTabItem; + } + } + + private void AdjustContextualTabSet(RibbonContextualTabSet set) + { + } + + private void SetContextualTabs(RibbonContextualTabSet newSet) + { + if (newSet != null) newSet.IsSelected = true; + if (tabItemContainer != null && newSet != null) + { + foreach (RibbonTabItem tab in newSet.Tabs) + { + tabItemContainer.Children.Add(tab); + tab.IsContextual = true; + } + } + } + + private void RemovePreviousContextualTabs(RibbonContextualTabSet oldSet) + { + if (oldSet != null) oldSet.IsSelected = false; + if (tabItemContainer != null && oldSet != null) + { + foreach (RibbonTabItem tab in oldSet.Tabs) + { + tab.IsContextual = false; + tabItemContainer.Children.Remove(tab); + } + } + } + + /// + /// Gets an enumeration of the currently visible tabs including the contextual tabs. + /// + public IEnumerable VisibleTabs + { + get + { + foreach (var tab in Tabs) if (tab.IsVisible) yield return tab; + if (ContextualTabSet != null) + { + foreach (var tab in ContextualTabSet.Tabs) if (tab.IsVisible) yield return tab; + } + } + } + + /// + /// Gets the index for VisibleTabs of the specified tab, otherwise -1. + /// + /// The TabItem for which to determine the index. + /// The index related to , otherwise -1. + public int IndexOfVisibleTab(RibbonTabItem item) + { + if (item == null) return -1; + int index = 0; + foreach (var tab in VisibleTabs) + { + if (tab == item) return index; + index++; + } + return -1; + } + + /// + /// Gets the by index. + /// + /// The index of the tab. + /// , otherwise null. + public RibbonTabItem VisibleTabFromIndex(int index) + { + if (index < 0) return null; + return VisibleTabs.Skip(index).FirstOrDefault(); + } + + + + + + /// + /// Gets an enumerator for logical child elements of this element. + /// + /// + /// + /// An enumerator for logical child elements of this element. + /// + protected override IEnumerator LogicalChildren + { + get + { + return GetLogicalChildren().GetEnumerator(); + } + } + + private IEnumerable GetLogicalChildren() + { + if (ApplicationMenu != null) yield return ApplicationMenu; + foreach (var tab in Tabs) yield return tab; + foreach (var ct in ContextualTabSets) yield return ct; + if (QAToolBar != null) yield return QAToolBar; + + } + + private void AlignGroupsLeft() + { + GroupAlignment = RibbonBarAlignment.Left; + InvalidateArrange(); + } + + private void AlignGroupsRight() + { + GroupAlignment = RibbonBarAlignment.Right; + InvalidateArrange(); + } + + + /// + /// Gets or sets the RibbonApplicationMenu for the RibbonBar. + /// This is a dependency property. + /// + public RibbonApplicationMenu ApplicationMenu + { + get { return (RibbonApplicationMenu)GetValue(ApplicationMenuProperty); } + set { SetValue(ApplicationMenuProperty, value); } + } + + // Using a DependencyProperty as the backing store for ApplicationMenu. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ApplicationMenuProperty = + DependencyProperty.Register("ApplicationMenu", typeof(RibbonApplicationMenu), typeof(RibbonBar), new UIPropertyMetadata(null)); + + + + /// + /// Gets or sets whether the ApplicationMenu is open. + /// Thisis a dependency property. + /// + public bool IsMenuOpen + { + get { return (bool)GetValue(IsMenuOpenProperty); } + set { SetValue(IsMenuOpenProperty, value); } + } + + public static readonly DependencyProperty IsMenuOpenProperty = + DependencyProperty.Register("IsMenuOpen", typeof(bool), typeof(RibbonBar), new UIPropertyMetadata(false, MenuOpenPropertyChanged)); + + private static void MenuOpenPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonBar bar = o as RibbonBar; + bool newValue = (bool)e.NewValue; + if (newValue && bar.IsExpanded && bar.CanMinimize) + { + bar.IsExpanded = false; + } + + if (((bool)e.NewValue) == false) Mouse.Capture(null); + bar.OnMenuOpenedChanged(newValue); + } + + protected virtual void OnMenuOpenedChanged(bool newValue) + { + } + + [AttachedPropertyBrowsableForChildren] + public static RibbonSize GetSize(DependencyObject obj) + { + return (RibbonSize)obj.GetValue(SizeProperty); + } + + public static void SetSize(DependencyObject obj, RibbonSize value) + { + obj.SetValue(SizeProperty, value); + } + + public static readonly DependencyProperty SizeProperty = + DependencyProperty.RegisterAttached("Size", typeof(RibbonSize), typeof(RibbonBar), + new FrameworkPropertyMetadata(RibbonSize.Large, + FrameworkPropertyMetadataOptions.None)); + + /// + /// Gets the custom Reduction sizes for a IRibbonControl. + /// This is an attached dependency property. + /// + [AttachedPropertyBrowsableForChildren] + public static RibbonSizeCollection GetReduction(DependencyObject obj) + { + return (RibbonSizeCollection)obj.GetValue(ReductionProperty); + } + + /// + /// Sets the custom Reduction sizes for a IRibbonControl. + /// This is an attached dependency property. + /// + [TypeConverter(typeof(RibbonReductionCollectionConverter))] + public static void SetReduction(DependencyObject obj, RibbonSizeCollection value) + { + obj.SetValue(ReductionProperty, value); + } + + public static readonly DependencyProperty ReductionProperty = + DependencyProperty.RegisterAttached("Reduction", typeof(RibbonSizeCollection), typeof(RibbonBar), new UIPropertyMetadata(null)); + + /// + /// Gets the minimum size for a IRibbonControl. + /// This is an attached dependency property. + /// + public static RibbonSize GetMinSize(DependencyObject obj) + { + return (RibbonSize)obj.GetValue(MinSizeProperty); + } + + /// + /// Sets the minimum size for a IRibbonControl. + /// This is an attached dependency property. + /// + public static void SetMinSize(DependencyObject obj, RibbonSize value) + { + obj.SetValue(MinSizeProperty, value); + } + + public static readonly DependencyProperty MinSizeProperty = + DependencyProperty.RegisterAttached("MinSize", typeof(RibbonSize), typeof(RibbonBar), new UIPropertyMetadata(RibbonSize.Minimized)); + + /// + /// Gets the maximum size for a IRibbonControl. + /// This is an attached dependency property. + /// + public static RibbonSize GetMaxSize(DependencyObject obj) + { + return (RibbonSize)obj.GetValue(MaxSizeProperty); + } + + /// + /// Sets the maximum size for a IRibbonControl. + /// This is an attached dependency property. + /// + public static void SetMaxSize(DependencyObject obj, RibbonSize value) + { + obj.SetValue(MaxSizeProperty, value); + } + + public static readonly DependencyProperty MaxSizeProperty = + DependencyProperty.RegisterAttached("MaxSize", typeof(RibbonSize), typeof(RibbonBar), new UIPropertyMetadata(RibbonSize.Large)); + + /// + /// Gets the color for the active tab. + /// This is a dependency property. + /// + public Color Color + { + get { return (Color)GetValue(ColorProperty); } + private set { SetValue(ColorPropertyKey, value); } + } + + private static readonly DependencyPropertyKey ColorPropertyKey = + DependencyProperty.RegisterReadOnly("Color", typeof(Color), typeof(RibbonBar), new UIPropertyMetadata(Colors.Transparent)); + + + public static readonly DependencyProperty ColorProperty = ColorPropertyKey.DependencyProperty; + + + /// + /// Gets or sets the QuickAccess Toolbar. + /// This is a dependency property. + /// + public RibbonQAToolBar QAToolBar + { + get { return (RibbonQAToolBar)GetValue(QAToolBarProperty); } + set { SetValue(QAToolBarProperty, value); } + } + + public static readonly DependencyProperty QAToolBarProperty = + DependencyProperty.Register("QAToolBar", typeof(RibbonQAToolBar), typeof(RibbonBar), new UIPropertyMetadata(null)); + + + + /// + /// Gets or sets the placement for the QuickAccess Toolbar. + /// This is a dependency property. + /// + public QAPlacement ToolbarPlacement + { + get { return (QAPlacement)GetValue(ToolbarPlacementProperty); } + set { SetValue(ToolbarPlacementProperty, value); } + } + + public static readonly DependencyProperty ToolbarPlacementProperty = + DependencyProperty.Register("ToolbarPlacement", typeof(QAPlacement), typeof(RibbonBar), + new FrameworkPropertyMetadata(QAPlacement.Top, + FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, + ToolbarPlacementPropertyChanged)); + + public static void ToolbarPlacementPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonBar bar = (RibbonBar)o; + if (bar.QAToolBar != null) bar.QAToolBar.ToolBarPlacement = (QAPlacement)e.NewValue; + } + + + + + /// + /// Gets whether the ribbon bar and tabs are visible. + /// If set to false, only the ApplicationMenu and the QuickAccessToolbar is available. + /// This is a dependency property. + /// + public bool IsRibbonVisible + { + get { return (bool)GetValue(IsRibbonVisibleProperty); } + set { SetValue(IsRibbonVisibleProperty, value); } + } + + public static readonly DependencyProperty IsRibbonVisibleProperty = + DependencyProperty.Register("IsRibbonVisible", typeof(bool), typeof(RibbonBar), new FrameworkPropertyMetadata(true)); + + + /// + /// Gets whether the Ribbonbar is minimized due to a thresold underrun of the minimum width. + /// This is a dependency property. + /// + public bool IsMinimized + { + get { return (bool)GetValue(IsMinimizedProperty); } + private set { SetValue(IsMinimizedPropertyKey, value); } + } + + private static readonly DependencyPropertyKey IsMinimizedPropertyKey = + DependencyProperty.RegisterReadOnly("IsMinimized", typeof(bool), typeof(RibbonBar), new FrameworkPropertyMetadata(false)); + + public static readonly DependencyProperty IsMinimizedProperty = IsMinimizedPropertyKey.DependencyProperty; + + + + + /// + /// Gets the left position for the Window Title. + /// + public double TitleLeft + { + get { return (double)GetValue(TitleLeftProperty); } + private set { SetValue(TitleLeftProperty, value); } + } + + // Using a DependencyProperty as the backing store for TitleLeft. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TitleLeftProperty = + DependencyProperty.Register("TitleLeft", typeof(double), typeof(RibbonBar), new UIPropertyMetadata(32.0)); + + + + public bool ShowTitleOnRight + { + get { return (bool)GetValue(ShowTitleOnRightProperty); } + set { SetValue(ShowTitleOnRightProperty, value); } + } + + // Using a DependencyProperty as the backing store for ShowTitleOnRight. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ShowTitleOnRightProperty = + DependencyProperty.Register("ShowTitleOnRight", typeof(bool), typeof(RibbonBar), new UIPropertyMetadata(false)); + + + + } + +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonButton.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonButton.cs new file mode 100644 index 0000000..fd9d017 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonButton.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Controls.Primitives; +using System.Windows.Markup; +using Odyssey.Controls.Ribbon.Interfaces; +using System.Windows.Media.Effects; +using Odyssey.Controls.Interfaces; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [ContentProperty("Content")] + public class RibbonButton : Button, IRibbonButton,IKeyTipControl + { + + static RibbonButton() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonButton), new FrameworkPropertyMetadata(typeof(RibbonButton))); + } + + public CornerRadius CornerRadius + { + get { return (CornerRadius)GetValue(CornerRadiusProperty); } + set { SetValue(CornerRadiusProperty, value); } + } + + // Using a DependencyProperty as the backing store for CornerRadius. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CornerRadiusProperty = + DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(RibbonButton), new UIPropertyMetadata(new CornerRadius(3))); + + + public ImageSource LargeImage + { + get { return (ImageSource)GetValue(LargeImageProperty); } + set { SetValue(LargeImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for LargeImage. This enables animation, styling, binding, etc... + public static readonly DependencyProperty LargeImageProperty = + DependencyProperty.Register("LargeImage", typeof(ImageSource), typeof(RibbonButton), new FrameworkPropertyMetadata(null)); + + + public ImageSource SmallImage + { + get { return (ImageSource)GetValue(SmallImageProperty); } + set { SetValue(SmallImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for SmallImage. This enables animation, styling, binding, etc... + public static readonly DependencyProperty SmallImageProperty = + DependencyProperty.Register("SmallImage", typeof(ImageSource), typeof(RibbonButton), new FrameworkPropertyMetadata(null)); + + + + public bool IsFlat + { + get { return (bool)GetValue(IsFlatProperty); } + set { SetValue(IsFlatProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsFlat. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsFlatProperty = + DependencyProperty.Register("IsFlat", typeof(bool), typeof(RibbonButton), new UIPropertyMetadata(true)); + + + + + /// + /// Gets or sets how to stretch an image inside an IRibbonButton + /// This is an attached dependency property. + /// + public static Stretch GetImageStretch(DependencyObject obj) + { + return (Stretch)obj.GetValue(ImageStretchProperty); + } + + public static void SetImageStretch(DependencyObject obj, Stretch value) + { + obj.SetValue(ImageStretchProperty, value); + } + + // Using a DependencyProperty as the backing store for ImageStretch. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ImageStretchProperty = + DependencyProperty.RegisterAttached("ImageStretch", typeof(Stretch), typeof(RibbonButton), new UIPropertyMetadata(Stretch.Uniform)); + + + + + //public BitmapScalingMode LargeImageScalingMode + //{ + // get { return (BitmapScalingMode)GetValue(LargeImageScalingModeProperty); } + // set { SetValue(LargeImageScalingModeProperty, value); } + //} + + //public static readonly DependencyProperty LargeImageScalingModeProperty = RibbonBar.LargeImageScalingModeProperty.AddOwner(typeof(RibbonButton)); + + + + #region IKeyboardCommand Members + + public void ExecuteKeyTip() + { + OnClick(); + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonButtonGroup.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonButtonGroup.cs new file mode 100644 index 0000000..fa0fac1 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonButtonGroup.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Markup; +using Odyssey.Controls.Ribbon.Interfaces; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + + [ContentProperty("Items")] + public class RibbonButtonGroup : ItemsControl + { + static RibbonButtonGroup() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonButtonGroup), new FrameworkPropertyMetadata(typeof(RibbonButtonGroup))); + } + + + public CornerRadius CornerRadius + { + get { return (CornerRadius)GetValue(CornerRadiusProperty); } + set { SetValue(CornerRadiusProperty, value); } + } + + // Using a DependencyProperty as the backing store for CornerRadius. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CornerRadiusProperty = + DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(RibbonButtonGroup), new UIPropertyMetadata(new CornerRadius(3))); + + + + protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + base.OnItemsChanged(e); + int max = Items.Count-1; + CornerRadius r = CornerRadius; + for (int i = 0; i <= max; i++) + { + IRibbonButton b = Items[i] as IRibbonButton; + if (i == 0 && i == max) b.CornerRadius = CornerRadius; + else if (i == 0) b.CornerRadius = new CornerRadius(r.TopLeft, 0d, 0d, r.BottomLeft); + else if (i == max) b.CornerRadius = new CornerRadius(0d, r.TopRight, r.BottomRight, 0d); + else b.CornerRadius = new CornerRadius(0); + } + } + + + protected override DependencyObject GetContainerForItemOverride() + { + return new RibbonButton(); + } + + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + base.PrepareContainerForItemOverride(element, item); + IRibbonButton b = element as IRibbonButton; + if (b != null) + { + RibbonBar.SetSize(element, RibbonSize.Small); + } + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonButtonStyle.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonButtonStyle.cs new file mode 100644 index 0000000..fc7722b --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonButtonStyle.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public enum RibbonButtonStyle + { + /// + /// Button only. + /// + Button, + + /// + /// Includes a drop down button. + /// + DropDown, + + /// + /// Appears as a popup button with a popup symbol. + /// + Popup + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonChrome.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonChrome.cs new file mode 100644 index 0000000..32d4130 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonChrome.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Media; +using System.Windows.Markup; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + /// + /// Renders the chrome for all Ribbon controls. + /// + [ContentProperty("Content")] + public class RibbonChrome:ContentControl + { + static RibbonChrome() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonChrome), new FrameworkPropertyMetadata(typeof(RibbonChrome))); + } + + public bool RenderPressed + { + get { return (bool)GetValue(RenderPressedProperty); } + set { SetValue(RenderPressedProperty, value); } + } + + public static readonly DependencyProperty RenderPressedProperty = + DependencyProperty.Register("RenderPressed", typeof(bool), typeof(RibbonChrome), new UIPropertyMetadata(false)); + + + public bool RenderMouseOver + { + get { return (bool)GetValue(RenderMouseOverProperty); } + set { SetValue(RenderMouseOverProperty, value); } + } + + // Using a DependencyProperty as the backing store for RenderMouseOver. This enables animation, styling, binding, etc... + public static readonly DependencyProperty RenderMouseOverProperty = + DependencyProperty.Register("RenderMouseOver", typeof(bool), typeof(RibbonChrome), new UIPropertyMetadata(false)); + + + + public Brush MouseOverBackground + { + get { return (Brush)GetValue(MouseOverBackgroundProperty); } + set { SetValue(MouseOverBackgroundProperty, value); } + } + + public static readonly DependencyProperty MouseOverBackgroundProperty = + DependencyProperty.Register("MouseOverBackground", typeof(Brush), typeof(RibbonChrome), new UIPropertyMetadata(null)); + + + + public Brush MousePressedBackground + { + get { return (Brush)GetValue(MousePressedBackgroundProperty); } + set { SetValue(MousePressedBackgroundProperty, value); } + } + + // Using a DependencyProperty as the backing store for MousePressedBackground. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MousePressedBackgroundProperty = + DependencyProperty.Register("MousePressedBackground", typeof(Brush), typeof(RibbonChrome), new UIPropertyMetadata(null)); + + + public Brush MouseCheckedBackground + { + get { return (Brush)GetValue(MouseCheckedBackgroundProperty); } + set { SetValue(MouseCheckedBackgroundProperty, value); } + } + + // Using a DependencyProperty as the backing store for MouseCheckedBackground. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MouseCheckedBackgroundProperty = + DependencyProperty.Register("MouseCheckedBackground", typeof(Brush), typeof(RibbonChrome), new UIPropertyMetadata(null)); + + + public CornerRadius CornerRadius + { + get { return (CornerRadius)GetValue(CornerRadiusProperty); } + set { SetValue(CornerRadiusProperty, value); } + } + + // Using a DependencyProperty as the backing store for CornerRadius. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CornerRadiusProperty = + DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(RibbonChrome), new UIPropertyMetadata(new CornerRadius(0))); + + + public bool RenderFlat + { + get { return (bool)GetValue(RenderFlatProperty); } + set { SetValue(RenderFlatProperty, value); } + } + + // Using a DependencyProperty as the backing store for RenderFlat. This enables animation, styling, binding, etc... + public static readonly DependencyProperty RenderFlatProperty = + DependencyProperty.Register("RenderFlat", typeof(bool), typeof(RibbonChrome), new UIPropertyMetadata(true)); + + + + + + public static bool GetAnimateTransition(DependencyObject obj) + { + return (bool)obj.GetValue(AnimateTransitionProperty); + } + + public static void SetAnimateTransition(DependencyObject obj, bool value) + { + obj.SetValue(AnimateTransitionProperty, value); + } + + public static readonly DependencyProperty AnimateTransitionProperty = + DependencyProperty.RegisterAttached("AnimateTransition", typeof(bool), typeof(RibbonChrome), new FrameworkPropertyMetadata(false,FrameworkPropertyMetadataOptions.Inherits)); + + + + public bool ShowInnerMouseOverBorder + { + get { return (bool)GetValue(ShowInnerMouseOverBorderProperty); } + set { SetValue(ShowInnerMouseOverBorderProperty, value); } + } + + public static readonly DependencyProperty ShowInnerMouseOverBorderProperty = + DependencyProperty.Register("ShowInnerMouseOverBorder", typeof(bool), typeof(RibbonChrome), new UIPropertyMetadata(true)); + + + public Thickness InnerBorderThickness + { + get { return (Thickness)GetValue(InnerBorderThicknessProperty); } + set { SetValue(InnerBorderThicknessProperty, value); } + } + + public static readonly DependencyProperty InnerBorderThicknessProperty = + DependencyProperty.Register("InnerBorderThickness", typeof(Thickness), typeof(RibbonChrome), new UIPropertyMetadata(new Thickness(1,1,0,0))); + + + public bool RenderEnabled + { + get { return (bool)GetValue(RenderEnabledProperty); } + set { SetValue(RenderEnabledProperty, value); } + } + + public static readonly DependencyProperty RenderEnabledProperty = + DependencyProperty.Register("RenderEnabled", typeof(bool), typeof(RibbonChrome), new UIPropertyMetadata(true)); + + + public Brush MouseOverBorderBrush + { + get { return (Brush)GetValue(MouseOverBorderBrushProperty); } + set { SetValue(MouseOverBorderBrushProperty, value); } + } + + // Using a DependencyProperty as the backing store for MouseOverBorderBrush. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MouseOverBorderBrushProperty = + DependencyProperty.Register("MouseOverBorderBrush", typeof(Brush), typeof(RibbonChrome), new UIPropertyMetadata(null)); + + + + /// + /// Gets wether a control is rendered in grayscale when IsEnabled is set to true. + /// This is an attached inheritable dependency property. The default value is true. + /// + public static bool GetIsGrayScaleEnabled(DependencyObject obj) + { + return (bool)obj.GetValue(IsGrayScaleEnabledProperty); + } + + /// + /// Sets wether to render a control in grayscale when IsEnabled is set to true. + /// This is an attached inheritable dependency property. The default value is true. + /// + public static void SetIsGrayScaleEnabled(DependencyObject obj, bool value) + { + obj.SetValue(IsGrayScaleEnabledProperty, value); + } + + // Using a DependencyProperty as the backing store for IsGrayScaleEnabled. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsGrayScaleEnabledProperty = + DependencyProperty.RegisterAttached("IsGrayScaleEnabled", typeof(bool), typeof(RibbonChrome), + new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits)); + + + + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonComboBox.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonComboBox.cs new file mode 100644 index 0000000..18670cf --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonComboBox.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Media; +using System.Windows.Controls.Primitives; +using System.ComponentModel; +using Odyssey.Controls.Ribbon.Interfaces; +using System.Diagnostics; +using Odyssey.Controls.Interfaces; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public class RibbonComboBox : ComboBox, IRibbonControl, IRibbonStretch,IKeyTipControl + { + static RibbonComboBox() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonComboBox), new FrameworkPropertyMetadata(typeof(RibbonComboBox))); + } + + public RibbonComboBox() + : base() + { + } + + + /// + /// Gets or sets the Image of the combobox. + /// + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(ImageSource), typeof(RibbonComboBox), new UIPropertyMetadata(null)); + + + /// + /// Gets or sets the title of the combobox that appears with Appearance = Medium. + /// + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(string), typeof(RibbonComboBox), new UIPropertyMetadata("")); + + + + /// + /// Gets or sets the width for the label. + /// + public double LabelWidth + { + get { return (double)GetValue(LabelWidthProperty); } + set { SetValue(LabelWidthProperty, value); } + } + + public static readonly DependencyProperty LabelWidthProperty = + DependencyProperty.Register("LabelWidth", typeof(double), typeof(RibbonComboBox), new UIPropertyMetadata(double.NaN)); + + + + /// + /// Gets or sets the with for the combobox. + /// This is a dependency property. + /// + public double ContentWidth + { + get { return (double)GetValue(ContentWidthProperty); } + set { SetValue(ContentWidthProperty, value); } + } + + public static readonly DependencyProperty ContentWidthProperty = + DependencyProperty.Register("ContentWidth", typeof(double), typeof(RibbonComboBox), new UIPropertyMetadata(double.NaN)); + + + protected override bool IsItemItsOwnContainerOverride(object item) + { + //return item is RibbonMenuItem; + return item is RibbonComboBoxItem; + } + + protected override DependencyObject GetContainerForItemOverride() + { + return new RibbonComboBoxItem(); + } + + protected override void OnDropDownClosed(EventArgs e) + { + base.OnDropDownClosed(e); + RoutedEventArgs args = new RoutedEventArgs(RibbonComboBox.DropDownClosedEvent); + RaiseEvent(args); + } + + public static readonly RoutedEvent DropDownClosedEvent = EventManager.RegisterRoutedEvent("DropDownClosedEvent", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(RibbonComboBox)); + + /// + /// Occurs when the DropDown is closed. + /// The Ribbonbar uses this event to determine wether to collapse a collapsible ribbon after a combobox is closed. + /// + public event RoutedEventHandler RoutedDropDownClosed + { + add { AddHandler(DropDownClosedEvent, value); } + remove { RemoveHandler(DropDownClosedEvent, value); } + } + + public object DropDownFooter + { + get { return (object)GetValue(DropDownFooterProperty); } + set { SetValue(DropDownFooterProperty, value); } + } + + // Using a DependencyProperty as the backing store for DropDownFooter. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DropDownFooterProperty = + DependencyProperty.Register("DropDownFooter", typeof(object), typeof(RibbonComboBox), new UIPropertyMetadata(null)); + + + + + public object DropDownHeader + { + get { return (object)GetValue(DropDownHeaderProperty); } + set { SetValue(DropDownHeaderProperty, value); } + } + + // Using a DependencyProperty as the backing store for DropDownHeader. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DropDownHeaderProperty = + DependencyProperty.Register("DropDownHeader", typeof(object), typeof(RibbonComboBox), new UIPropertyMetadata(null)); + + + + public DataTemplate DropDownHeaderTemplate + { + get { return (DataTemplate)GetValue(DropDownHeaderTemplateProperty); } + set { SetValue(DropDownHeaderTemplateProperty, value); } + } + + // Using a DependencyProperty as the backing store for DropDownHeaderTemplate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DropDownHeaderTemplateProperty = + DependencyProperty.Register("DropDownHeaderTemplate", typeof(DataTemplate), typeof(RibbonComboBox), new UIPropertyMetadata(null)); + + + + public DataTemplate DropDownFooterTemplate + { + get { return (DataTemplate)GetValue(DropDownFooterTemplateProperty); } + set { SetValue(DropDownFooterTemplateProperty, value); } + } + + // Using a DependencyProperty as the backing store for DropDownFooterTemplate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DropDownFooterTemplateProperty = + DependencyProperty.Register("DropDownFooterTemplate", typeof(DataTemplate), typeof(RibbonComboBox), new UIPropertyMetadata(null)); + + + + protected override void OnPreviewMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e) + { + if (!IsEditable && e.Source==this) + { + this.IsDropDownOpen ^= true; + e.Handled = true; + } + base.OnPreviewMouseLeftButtonDown(e); + } + + #region IKeyboardCommand Members + + public void ExecuteKeyTip() + { + Focus(); + if (HasItems) + { + IsDropDownOpen = true; + } + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonComboBoxItem.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonComboBoxItem.cs new file mode 100644 index 0000000..f9794d0 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonComboBoxItem.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Media; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public class RibbonComboBoxItem:ComboBoxItem + { + static RibbonComboBoxItem() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonComboBoxItem), new FrameworkPropertyMetadata(typeof(RibbonComboBoxItem))); + } + + + + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for Image. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(ImageSource), typeof(RibbonComboBoxItem), new UIPropertyMetadata(null)); + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonContextualTabSet.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonContextualTabSet.cs new file mode 100644 index 0000000..0d302a0 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonContextualTabSet.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Collections.ObjectModel; +using System.Windows.Media; +using Odyssey.Controls.Ribbon.Interfaces; +using System.Windows; +using System.Windows.Markup; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [ContentProperty("Tabs")] + public class RibbonContextualTabSet:Control,IRibbonControl + { + static RibbonContextualTabSet() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonContextualTabSet), new FrameworkPropertyMetadata(typeof(RibbonContextualTabSet))); + } + + + private ObservableCollection tabs; + + /// + /// Gets the collection of RibbonTabItems. + /// + public Collection Tabs + { + get + { + if (tabs == null) + { + tabs = new ObservableCollection(); + tabs.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(OnTabItemCollectionChanged); + } + return tabs; + } + } + + /// + /// Occurs when the TabItems collection has changed. + /// + protected virtual void OnTabItemCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case System.Collections.Specialized.NotifyCollectionChangedAction.Add: + foreach (RibbonTabItem tab in e.NewItems) + { + tab.tabSet = this; + tab.RibbonBar = RibbonBar; + } + break; + + case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: + foreach (RibbonTabItem tab in e.OldItems) + { + tab.tabSet = null; + tab.RibbonBar = null; + } + break; + } + } + + private RibbonBar ribbonBar; + public RibbonBar RibbonBar + { + get {return ribbonBar;} + internal set + { + if (ribbonBar != value) + { + ribbonBar = value; + foreach (var tab in Tabs) tab.RibbonBar = value; + } + } + } + + + /// + /// Gets or sets the color for the ContextualTabSet. + /// This is a dependency property. + /// + public Color Color + { + get { return (Color)GetValue(ColorProperty); } + set { SetValue(ColorProperty, value); } + } + + // Using a DependencyProperty as the backing store for Color. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ColorProperty = + DependencyProperty.Register("Color", typeof(Color), typeof(RibbonContextualTabSet), new UIPropertyMetadata(Color.FromArgb(64, 255, 255, 255))); + + + + + /// + /// Gets or sets the title for the ContextualTabSet. + /// This is a dependency property. + /// + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + // Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(string), typeof(RibbonContextualTabSet), new UIPropertyMetadata("")); + + + internal void SetSelectedTabItem(int index) + { + for (int i = 0; i < Tabs.Count; i++) + { + RibbonTabItem item = Tabs[i]; + item.IsSelected = i == index; + } + } + + + + /// + /// Gets whether the Tabset is selected. + /// + public bool IsSelected + { + get { return (bool)GetValue(IsSelectedProperty); } + internal set { SetValue(IsSelectedPropertyKey, value); } + } + + + + // Using a DependencyProperty as the backing store for IsSelected. This enables animation, styling, binding, etc... + private static readonly DependencyPropertyKey IsSelectedPropertyKey = + DependencyProperty.RegisterReadOnly("IsSelected", typeof(bool), typeof(RibbonContextualTabSet), new UIPropertyMetadata(false)); + + private static readonly DependencyProperty IsSelectedProperty = IsSelectedPropertyKey.DependencyProperty; + + /// + /// Select the first tab in the RibbonBar of the contextual tab set when the control is clicked: + /// + /// + protected override void OnMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + if (this.tabs != null && this.tabs.Count > 0) + { + RibbonBar.SelectedTabItem = tabs[0]; + } + } + + protected override System.Collections.IEnumerator LogicalChildren + { + get + { + return Tabs.GetEnumerator(); + } + } + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonDropDownButton.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonDropDownButton.cs new file mode 100644 index 0000000..8aed1fc --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonDropDownButton.cs @@ -0,0 +1,887 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Controls.Primitives; +using System.Windows.Markup; +using System.Diagnostics; +using Odyssey.Controls.Ribbon.Interfaces; +using System.ComponentModel; +using Odyssey.Common; +using Odyssey.Controls.Interfaces; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + + [TemplatePart(Name = partPopup)] + [ContentProperty("Items")] + public class RibbonDropDownButton : ItemsControl, IRibbonButton, ICommandSource,IKeyTipControl + { + const string partPopup = "PART_Popup"; + + static RibbonDropDownButton() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonDropDownButton), new FrameworkPropertyMetadata(typeof(RibbonDropDownButton))); + } + + public RibbonDropDownButton() + { + AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(OnMenuItemClickedEvent)); + AddHandler(RibbonButton.ClickEvent, new RoutedEventHandler(OnButtonClickEvent)); + AddHandler(RibbonComboBox.DropDownClosedEvent, new RoutedEventHandler(OnMenuItemClickedEvent)); + AddHandler(RibbonDropDownButton.PopupClosedEvent, new RoutedEventHandler(OnPopupClosedEvent)); + AddHandler(RibbonGallery.SelectionChangedEvent, new RoutedEventHandler(OnGalerySelected)); + + } + + protected virtual void OnGalerySelected(object sender, RoutedEventArgs e) + { + IsDropDownPressed = false; + } + + /// + /// If any RibbonDropDownButton has closed it's popup, so also close it. + /// This is necassary for nested RibbonDropDownButtons to ensure that all are properly closed. + /// + protected virtual void OnPopupClosedEvent(object sender, RoutedEventArgs e) + { + if (IsDropDownPressed) + { + IsDropDownPressed = false; + } + } + + protected virtual void OnButtonClickEvent(object sender, RoutedEventArgs e) + { + if (IsDropDownPressed) + { + if (e.OriginalSource == this) return; + DependencyObject dep = e.OriginalSource as DependencyObject; + if (!(e.OriginalSource is RibbonButton) && !(e.OriginalSource is RibbonDropDownButton)) + { + if (dep != null && !RibbonOption.GetCloseDropDownOnClick(dep)) return; + } + else + { + if (dep != null && !RibbonBar.GetAffectsDropDown(dep)) return; + } + if (IsAncestorType(e.OriginalSource, typeof(RibbonComboBox))) return; + IsDropDownPressed = false; + } + } + + protected virtual void OnMenuItemClickedEvent(object sender, RoutedEventArgs e) + { + if (IsDropDownPressed) + { + if (e.OriginalSource == this) return; + IsDropDownPressed = false; + } + } + + private bool IsAncestorType(object child, Type type) + { + FrameworkElement parent = child as FrameworkElement; + while (parent != null) + { + if (parent.TemplatedParent != null && parent.TemplatedParent.GetType() == type) return true; + if (parent.GetType() == type) return true; + parent = parent.Parent as FrameworkElement; + } + return false; + } + + + internal protected Popup Popup { get; private set; } + + + + + public override void OnApplyTemplate() + { + if (Popup != null) + { + Popup.Closed -= OnPopupClosed; + Popup.Opened -= OnPopupOpened; + } + Popup = GetTemplateChild(partPopup) as Popup; + if (Popup != null) + { + Popup.Closed += new EventHandler(OnPopupClosed); + Popup.Opened += new EventHandler(OnPopupOpened); + Popup.StaysOpen = true; + } + + // base.OnApplyTemplate(); + } + + public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent("Click", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(RibbonDropDownButton)); + + + + + public bool IsCheckable + { + get { return (bool)GetValue(IsCheckableProperty); } + set { SetValue(IsCheckableProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsCheckable. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsCheckableProperty = + DependencyProperty.Register("IsCheckable", typeof(bool), typeof(RibbonDropDownButton), new UIPropertyMetadata(false)); + + + + public bool IsChecked + { + get { return (bool)GetValue(IsCheckedProperty); } + set { SetValue(IsCheckedProperty, value); } + } + + public static readonly DependencyProperty IsCheckedProperty = + DependencyProperty.Register("IsChecked", typeof(bool), typeof(RibbonDropDownButton), new UIPropertyMetadata(false, CheckedPropertyChanged)); + + + + private static void CheckedPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + } + + public bool IsPressed + { + get { return (bool)GetValue(IsPressedProperty); } + protected set { SetValue(IsPressedPropertyKey, value); } + } + + private static readonly DependencyPropertyKey IsPressedPropertyKey = + DependencyProperty.RegisterReadOnly("IsPressed", typeof(bool), typeof(RibbonDropDownButton), new UIPropertyMetadata(false)); + + public static DependencyProperty IsPressedProperty = IsPressedPropertyKey.DependencyProperty; + + + protected virtual void OnClick() + { + ToggleChecked(); + } + + private void ToggleChecked() + { + if (IsCheckable) + { + Rect rect = new Rect(new Point(), base.RenderSize); + if (((Mouse.LeftButton == MouseButtonState.Pressed) && base.IsMouseOver) && rect.Contains(Mouse.GetPosition(this))) + { + + if (IsChecked) base.ClearValue(IsCheckedProperty); else IsChecked = true; + } + } + } + + + public static readonly RoutedEvent PopupOpenedEvent = EventManager.RegisterRoutedEvent("PopupOpened", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(RibbonDropDownButton)); + + public static readonly RoutedEvent PopupClosedEvent = EventManager.RegisterRoutedEvent("PopupClosed", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(RibbonDropDownButton)); + + public event RoutedEventHandler PopupOpened + { + add { AddHandler(PopupOpenedEvent, value); } + remove { RemoveHandler(PopupOpenedEvent, value); } + } + + public event RoutedEventHandler PopupClosed + { + add { AddHandler(PopupClosedEvent, value); } + remove { RemoveHandler(PopupClosedEvent, value); } + } + + protected virtual void OnPopupOpened(object sender, EventArgs e) + { + + IsDropDownPressed = true; + RoutedEventArgs args = new RoutedEventArgs(RibbonDropDownButton.PopupOpenedEvent); + if (Popup != null && Popup.Child != null) Popup.Child.Focus(); + RaiseEvent(args); + // Mouse.Capture(this, CaptureMode.SubTree); + } + + protected virtual void OnPopupClosed(object sender, EventArgs e) + { + if (Mouse.Captured == this) + { + Mouse.Capture(null); + } + + IsChecked = false; + //base.ClearValue(IsDropDownPressedProperty); + isDropDownOpen = false; + RoutedEventArgs args = new RoutedEventArgs(RibbonDropDownButton.PopupClosedEvent); + RaiseEvent(args); + } + + + + public object Content + { + get { return (object)GetValue(ContentProperty); } + set { SetValue(ContentProperty, value); } + } + + // Using a DependencyProperty as the backing store for Content. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ContentProperty = + DependencyProperty.Register("Content", typeof(object), typeof(RibbonDropDownButton), new UIPropertyMetadata(null)); + + + + + public ImageSource SmallImage + { + get { return (ImageSource)GetValue(SmallImageProperty); } + set { SetValue(SmallImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for SmallImage. This enables animation, styling, binding, etc... + public static readonly DependencyProperty SmallImageProperty = + DependencyProperty.Register("SmallImage", typeof(ImageSource), typeof(RibbonDropDownButton), new UIPropertyMetadata(null)); + + + + public ImageSource LargeImage + { + get { return (ImageSource)GetValue(LargeImageProperty); } + set { SetValue(LargeImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for LargeImage. This enables animation, styling, binding, etc... + public static readonly DependencyProperty LargeImageProperty = + DependencyProperty.Register("LargeImage", typeof(ImageSource), typeof(RibbonDropDownButton), new UIPropertyMetadata(null)); + + public CornerRadius CornerRadius + { + get { return (CornerRadius)GetValue(CornerRadiusProperty); } + set { SetValue(CornerRadiusProperty, value); } + } + + // Using a DependencyProperty as the backing store for CornerRadius. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CornerRadiusProperty = + DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(RibbonDropDownButton), new UIPropertyMetadata(new CornerRadius(3))); + + + /// + /// Gets or sets whether the drop down button is down. + /// + public bool IsDropDownPressed + { + get { return (bool)GetValue(IsDropDownPressedProperty); } + set + { + isDropDownOpen = value; + SetValue(IsDropDownPressedProperty, value); + } + } + + // Using a DependencyProperty as the backing store for IsDropDown. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsDropDownPressedProperty = + DependencyProperty.Register("IsDropDownPressed", typeof(bool), typeof(RibbonDropDownButton), + new UIPropertyMetadata(false, IsDropDownPressedPropertyChangedCallback)); + + static void IsDropDownPressedPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + bool newValue = (bool)e.NewValue; + RibbonDropDownButton btn = d as RibbonDropDownButton; + + if (newValue) Mouse.Capture(btn, CaptureMode.SubTree); + btn.OnDropDownPressedChanged((bool)e.OldValue, (bool)e.NewValue); + + } + + + /// + /// Gets the target for the popup placement. + /// + protected virtual UIElement PlacementTarget + { + get { return this; } + } + + protected virtual void OnDropDownPressedChanged(bool oldValue, bool newValue) + { + UpdateToolTip(newValue); + + if (Popup != null) + { + if (newValue) + { + Popup.PlacementTarget = PlacementTarget; + CloseOpenedPopup(this); + } + else + { + CloseOpenedPopup(null); + } + Popup.IsOpen = newValue; + } + } + + object toolTip; + + /// + /// When the popup is open, the tooltip must not be shown. + /// + private void UpdateToolTip(bool newValue) + { + if (newValue) + { + toolTip = ToolTip; + ToolTip = null; + } + else + { + ToolTip = toolTip; + } + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.Key == Key.Enter || e.Key == Key.Space) { IsDropDownPressed = true; e.Handled = true; } + switch (e.Key) + { + case Key.Escape: + IsDropDownPressed = false; + e.Handled = true; + break; + + //case Key.Down: + // SelectNext(); + // e.Handled = true; + // break; + } + base.OnKeyDown(e); + } + + private void SelectNext() + { + ItemsPresenter ip = GetTemplateChild("PART_Items") as ItemsPresenter; + if (ip != null) + { + ip.Focus(); + } + } + + private bool isDropDownOpen = false; + + private static RibbonDropDownButton DroppedDownButton; + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + HandleMouseLeftButtonDown(e); + base.OnMouseLeftButtonDown(e); + } + + protected virtual void HandleMouseLeftButtonDown(MouseButtonEventArgs e) + { + if (!e.Handled) + { + UpdateIsPressed(); + if (!IsDropDownPressed) + { + EnsurePopupRemainsOnMouseUp(); + if (this.IsAncestorOf(e.OriginalSource as DependencyObject)) + { + ToggleDropDownState(); + e.Handled = true; + } + } + else + { + ToggleDropDownState(); + } + OnClick(); + } + } + + protected override void OnMouseLeave(MouseEventArgs e) + { + base.OnMouseLeave(e); + this.UpdateIsPressed(); + } + + /// + /// Code snipped from original MenuItem class using Reflector: + /// + private void UpdateIsPressed() + { + Rect rect = new Rect(new Point(), base.RenderSize); + if (((Mouse.LeftButton == MouseButtonState.Pressed) && base.IsMouseOver) && rect.Contains(Mouse.GetPosition(this))) + { + this.IsPressed = true; + } + else + { + base.ClearValue(IsPressedPropertyKey); + } + + } + + protected override void OnIsKeyboardFocusedChanged(DependencyPropertyChangedEventArgs e) + { + base.OnIsKeyboardFocusedChanged(e); + if (this.IsDropDownPressed && !base.IsKeyboardFocusWithin) + { + DependencyObject focusedElement = Keyboard.FocusedElement as DependencyObject; + if ((focusedElement == null) || (!this.IsDropDownPressed && (ItemsControl.ItemsControlFromItemContainer(focusedElement) != this))) + { + IsDropDownPressed = false; + } + } + } + protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) + { + if (IsDropDownPressed) + { + UIElement originalSource = e.OriginalSource as UIElement; + + if (!(this.IsLogicalAncestorOf(originalSource))) + { + IsDropDownPressed = false; + e.Handled = true; + } + } + base.OnPreviewMouseLeftButtonDown(e); + + } + + protected override void OnLostMouseCapture(MouseEventArgs e) + { + base.OnLostMouseCapture(e); + if (IsDropDownPressed) + { + FrameworkElement fe = e.OriginalSource as FrameworkElement; + FrameworkElement captured = Mouse.Captured as FrameworkElement; + if (captured != this && Popup != null) + { + UIElement child = this.Popup.Child; + if (e.OriginalSource == this) + { + if ((Mouse.Captured == null) || !child.IsLogicalAncestorOf(Mouse.Captured as UIElement)) + { + this.IsDropDownPressed = false; + e.Handled = true; + } + } + else if (child.IsAncestorOf(e.OriginalSource as DependencyObject)) + { + if (this.IsDropDownPressed && (Mouse.Captured == null)) + { + Mouse.Capture(this, CaptureMode.SubTree); + e.Handled = true; + } + } + else if (!this.IsLogicalAncestorOf(Mouse.Captured as UIElement)) + { + IsDropDownPressed = false; + } + } + // if (IsDropDownPressed) Mouse.Capture(this, CaptureMode.SubTree); + } + } + + + + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + HandleMouseLeftButtonUp(e); + base.OnMouseLeftButtonUp(e); + } + + protected virtual void HandleMouseLeftButtonUp(MouseButtonEventArgs e) + { + if (!e.Handled) + { + UpdateIsPressed(); + isDropDownOpen = IsDropDownPressed; + } + } + + protected virtual void ToggleDropDownState() + { + Rect rect = new Rect(new Point(), base.RenderSize); + if (((Mouse.LeftButton == MouseButtonState.Pressed) && base.IsMouseOver) && rect.Contains(Mouse.GetPosition(this))) + { + isDropDownOpen ^= true; + SetValue(IsDropDownPressedProperty, isDropDownOpen); + } + } + + + public static void CloseOpenedPopup(RibbonDropDownButton caller) + { + RibbonDropDownButton btn = DroppedDownButton; + if (btn != null && (btn != caller)) + { + FrameworkElement parent = btn.Popup != null ? btn.Popup.Child as FrameworkElement : btn; + if (!parent.IsLogicalAncestorOf(caller)) + { + if (btn.Popup != null) btn.Popup.IsOpen = false; + btn.IsDropDownPressed = false; + } + } + DroppedDownButton = caller; + } + + + public bool IsFlat + { + get { return (bool)GetValue(IsFlatProperty); } + set { SetValue(IsFlatProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsFlat. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsFlatProperty = + DependencyProperty.Register("IsFlat", typeof(bool), typeof(RibbonDropDownButton), new UIPropertyMetadata(true)); + + + public bool ShowDropDownSymbol + { + get { return (bool)GetValue(ShowDropDownSymbolProperty); } + set { SetValue(ShowDropDownSymbolProperty, value); } + } + + // Using a DependencyProperty as the backing store for ShowDropDownSymbol. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ShowDropDownSymbolProperty = + DependencyProperty.Register("ShowDropDownSymbol", typeof(bool), typeof(RibbonDropDownButton), new UIPropertyMetadata(true)); + + + + /// + /// Gets or sets the maximum height for the dropdown panel. + /// This is a dependency property. + /// + public double MaxDropDownHeight + { + get { return (double)GetValue(MaxDropDownHeightProperty); } + set { SetValue(MaxDropDownHeightProperty, value); } + } + + // Using a DependencyProperty as the backing store for MaxDropDownHeight. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MaxDropDownHeightProperty = + DependencyProperty.Register("MaxDropDownHeight", typeof(double), typeof(RibbonDropDownButton), new UIPropertyMetadata(double.NaN)); + + + /// + /// An item can be any possible element, not necassarily (but prefered) a RibbonMenuItem: + /// + protected override bool IsItemItsOwnContainerOverride(object item) + { + return item is MenuItem || item is Separator; + } + + protected override DependencyObject GetContainerForItemOverride() + { + return new RibbonMenuItem(); + } + + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + base.PrepareContainerForItemOverride(element, item); + if (item is Separator) + { + Control c = element as Control; + if (c != null) + { + c.IsEnabled = false; + c.HorizontalContentAlignment = HorizontalAlignment.Stretch; + } + } + } + + + public object DropDownFooter + { + get { return (object)GetValue(DropDownFooterProperty); } + set { SetValue(DropDownFooterProperty, value); } + } + + // Using a DependencyProperty as the backing store for DropDownFooter. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DropDownFooterProperty = + DependencyProperty.Register("DropDownFooter", typeof(object), typeof(RibbonDropDownButton), new UIPropertyMetadata(null)); + + + + + public object DropDownHeader + { + get { return (object)GetValue(DropDownHeaderProperty); } + set { SetValue(DropDownHeaderProperty, value); } + } + + // Using a DependencyProperty as the backing store for DropDownHeader. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DropDownHeaderProperty = + DependencyProperty.Register("DropDownHeader", typeof(object), typeof(RibbonDropDownButton), new UIPropertyMetadata(null)); + + + + public DataTemplate DropDownHeaderTemplate + { + get { return (DataTemplate)GetValue(DropDownHeaderTemplateProperty); } + set { SetValue(DropDownHeaderTemplateProperty, value); } + } + + // Using a DependencyProperty as the backing store for DropDownHeaderTemplate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DropDownHeaderTemplateProperty = + DependencyProperty.Register("DropDownHeaderTemplate", typeof(DataTemplate), typeof(RibbonDropDownButton), new UIPropertyMetadata(null)); + + + + public DataTemplate DropDownFooterTemplate + { + get { return (DataTemplate)GetValue(DropDownFooterTemplateProperty); } + set { SetValue(DropDownFooterTemplateProperty, value); } + } + + // Using a DependencyProperty as the backing store for DropDownFooterTemplate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DropDownFooterTemplateProperty = + DependencyProperty.Register("DropDownFooterTemplate", typeof(DataTemplate), typeof(RibbonDropDownButton), new UIPropertyMetadata(null)); + + protected void EnsurePopupRemainsOnMouseUp() + { + if (Popup != null) Popup.StaysOpen = true; + } + + protected void EnsurePopupDoesNotStayOpen() + { + //if (popup != null) + //{ + // popup.StaysOpen = false; + //} + } + + + + public PopupAnimation PopupAnimation + { + get { return (PopupAnimation)GetValue(PopupAnimationProperty); } + set { SetValue(PopupAnimationProperty, value); } + } + + // Using a DependencyProperty as the backing store for PopupAnimation. This enables animation, styling, binding, etc... + public static readonly DependencyProperty PopupAnimationProperty = + DependencyProperty.Register("PopupAnimation", typeof(PopupAnimation), typeof(RibbonDropDownButton), new UIPropertyMetadata(PopupAnimation.Fade)); + + + + + public PlacementMode PopupPlacement + { + get { return (PlacementMode)GetValue(PopupPlacementProperty); } + set { SetValue(PopupPlacementProperty, value); } + } + + // Using a DependencyProperty as the backing store for PopupPlacement. This enables animation, styling, binding, etc... + public static readonly DependencyProperty PopupPlacementProperty = + DependencyProperty.Register("PopupPlacement", typeof(PlacementMode), typeof(RibbonDropDownButton), new UIPropertyMetadata(PlacementMode.Bottom)); + + + + + /// + /// Gets or sets the with for the drop down menu. + /// This is a dependency property. + /// + public double DropDownWidth + { + get { return (double)GetValue(DropDownWidthProperty); } + set { SetValue(DropDownWidthProperty, value); } + } + + // Using a DependencyProperty as the backing store for DropDownWidth. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DropDownWidthProperty = + DependencyProperty.Register("DropDownWidth", typeof(double), typeof(RibbonDropDownButton), new UIPropertyMetadata(double.NaN)); + + + + public BitmapScalingMode BitmapScalingMode + { + get { return (BitmapScalingMode)GetValue(BitmapScalingModeProperty); } + set { SetValue(BitmapScalingModeProperty, value); } + } + + // Using a DependencyProperty as the backing store for BitmapScalingMode. This enables animation, styling, binding, etc... + public static readonly DependencyProperty BitmapScalingModeProperty = + DependencyProperty.Register("BitmapScalingMode", typeof(BitmapScalingMode), typeof(RibbonDropDownButton), new UIPropertyMetadata(BitmapScalingMode.NearestNeighbor)); + + + + + public EdgeMode EdgeMode + { + get { return (EdgeMode)GetValue(EdgeModeProperty); } + set { SetValue(EdgeModeProperty, value); } + } + + // Using a DependencyProperty as the backing store for EdgeMode. This enables animation, styling, binding, etc... + public static readonly DependencyProperty EdgeModeProperty = + DependencyProperty.Register("EdgeMode", typeof(EdgeMode), typeof(RibbonDropDownButton), new UIPropertyMetadata(EdgeMode.Aliased)); + + + protected override System.Collections.IEnumerator LogicalChildren + { + get + { + List list = new List(); + if (DropDownHeader != null) list.Add(DropDownHeader); + foreach (var item in Items) + { + list.Add(item); + } + if (DropDownFooter != null) list.Add(DropDownFooter); + return list.GetEnumerator(); + } + } + + #region ICommandSource Members + + + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public static readonly DependencyProperty CommandProperty = + DependencyProperty.Register("Command", typeof(ICommand), typeof(RibbonDropDownButton), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnCommandPropertyChanged))); + + + private static void OnCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + RibbonDropDownButton btn = (RibbonDropDownButton)d; + btn.OnCommandChanged(btn, (ICommand)e.OldValue, (ICommand)e.NewValue); + } + + // Keep a copy of the handler so it doesn't get garbage collected. + private EventHandler canExecuteChangedHandler; + + protected virtual void OnCommandChanged(object sender, ICommand oldCommand, ICommand newCommand) + { + if (oldCommand != null) oldCommand.CanExecuteChanged -= OnCanExecuteChanged; + if (newCommand != null) + { + canExecuteChangedHandler = new EventHandler(OnCanExecuteChanged); + newCommand.CanExecuteChanged += canExecuteChangedHandler; + } + + UpdateCanExecute(); + } + + protected virtual void OnCanExecuteChanged(object sender, EventArgs e) + { + this.UpdateCanExecute(); + } + + private void UpdateCanExecute() + { + if (this.Command != null) + { + this.CanExecute = CanExecuteCommandSource(this); + } + else + { + this.CanExecute = true; + } + + // finally notify the ui to reflect the changes: + CoerceValue(IsEnabledProperty); + } + + private bool CanExecuteCommandSource(ICommandSource commandSource) + { + ICommand command = commandSource.Command; + if (command == null) + { + return false; + } + object commandParameter = commandSource.CommandParameter; + IInputElement commandTarget = commandSource.CommandTarget; + RoutedCommand command2 = command as RoutedCommand; + if (command2 == null) + { + return command.CanExecute(commandParameter); + } + if (commandTarget == null) + { + commandTarget = commandSource as IInputElement; + } + return command2.CanExecute(commandParameter, commandTarget); + } + + + private bool CanExecute = true; + + + + protected override bool IsEnabledCore + { + get + { + return base.IsEnabledCore && CanExecute; + } + } + + + public object CommandParameter + { + get { return (object)GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + // Using a DependencyProperty as the backing store for CommandParameter. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CommandParameterProperty = + DependencyProperty.Register("CommandParameter", typeof(object), typeof(RibbonDropDownButton), new UIPropertyMetadata(null)); + + + public IInputElement CommandTarget + { + get { return (IInputElement)GetValue(CommandTargetProperty); } + set { SetValue(CommandTargetProperty, value); } + } + + // Using a DependencyProperty as the backing store for CommandTarget. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CommandTargetProperty = + DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(RibbonDropDownButton), new UIPropertyMetadata(null)); + + + + #endregion + + #region IKeyboardCommand Members + + public void ExecuteKeyTip() + { + Focus(); + IsDropDownPressed = true; + } + + #endregion + + + } +} + diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonFlowGroup.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonFlowGroup.cs new file mode 100644 index 0000000..bfe4733 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonFlowGroup.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Markup; +using Odyssey.Controls.Classes; +using Odyssey.Controls.Ribbon.Interfaces; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [ContentProperty("Items")] + public class RibbonFlowGroup : ItemsControl, IRibbonControl, IRibbonLargeControl + { + static RibbonFlowGroup() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonFlowGroup), new FrameworkPropertyMetadata(typeof(RibbonFlowGroup))); + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonGallery.Commands.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonGallery.Commands.cs new file mode 100644 index 0000000..9299e49 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonGallery.Commands.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Input; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media.Animation; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + partial class RibbonGallery + { + public static RoutedUICommand ScrollUpCommand = new RoutedUICommand("ScrollUpCommand", "ScrollUpCommand", typeof(RibbonGallery)); + public static RoutedUICommand ScrollDownCommand = new RoutedUICommand("ScrollDownCommand", "ScrollDownCommand", typeof(RibbonGallery)); + + private static void RegisterCommands() + { + CommandManager.RegisterClassCommandBinding(typeof(RibbonGallery), new CommandBinding(ScrollUpCommand, scrollUpCommand)); + CommandManager.RegisterClassCommandBinding(typeof(RibbonGallery), new CommandBinding(ScrollDownCommand, scrollDownCommand)); + } + + private static void scrollUpCommand(object sender, ExecutedRoutedEventArgs e) + { + RibbonGallery gallery = (RibbonGallery)sender; + //RoutedEventArgs args = new RoutedEventArgs(LaunchDialogEvent); + //gallery.RaiseEvent(args); + gallery.ScrollUp(); + } + + + + private static void scrollDownCommand(object sender, ExecutedRoutedEventArgs e) + { + RibbonGallery gallery = (RibbonGallery)sender; + //RoutedEventArgs args = new RoutedEventArgs(LaunchDialogEvent); + //gallery.RaiseEvent(args); + gallery.ScrollDown(); + + + } + + /// + /// Gets the ScrollViewer for the In-Ribbon Panel. + /// + protected virtual ScrollViewer ScrollViewer + { + get { return wrapPanel.Parent as ScrollViewer; } + } + + /// + /// Scrolls the In-Ribbon Gallery on row down. + /// + public void ScrollDown() + { + ScrollViewer sv = ScrollViewer; + if (sv != null) + { + double h = CalculateInRibbonThumbnailHeight(); + double offset = Math.Floor(sv.VerticalOffset / h) * h + h; + ScrollTo(offset); + } + } + + + /// + /// Scrolls the In-Ribbon Gallery on row up. + /// + public void ScrollUp() + { + ScrollViewer sv = ScrollViewer; + if (sv != null) + { + double h = CalculateInRibbonThumbnailHeight(); + double offset = Math.Ceiling(sv.VerticalOffset / h) * h - h; + ScrollTo(offset); + } + } + + private void ScrollTo(double verticalOffset) + { + VerticalScrollOffset = ScrollViewer.VerticalOffset; + DoubleAnimation a = new DoubleAnimation(verticalOffset, new Duration(TimeSpan.FromMilliseconds(250))); + a.DecelerationRatio = 1.0; + BeginAnimation(VerticalScrollOffsetProperty, a); + } + + + /// + /// Gets or set the vertical scroll offset for the In-Ribbon Gallery. + /// This a dependency property + /// + /// + /// This property is used to perform scrolling animation. + /// + public double VerticalScrollOffset + { + get { return (double)GetValue(VerticalScrollOffsetProperty); } + set { SetValue(VerticalScrollOffsetProperty, value); } + } + + // Using a DependencyProperty as the backing store for VerticalScrollOffset. This enables animation, styling, binding, etc... + public static readonly DependencyProperty VerticalScrollOffsetProperty = + DependencyProperty.Register("VerticalScrollOffset", typeof(double), typeof(RibbonGallery), new UIPropertyMetadata(0.0, VerticalScrollOffsetPropertyChanged)); + + private static void VerticalScrollOffsetPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonGallery g = (RibbonGallery)o; + double value = (double)e.NewValue; + g.OnVerticalScrollOffsetPropertychanged(value); + } + + private void OnVerticalScrollOffsetPropertychanged(double value) + { + ScrollViewer sv = ScrollViewer; + if (sv != null) sv.ScrollToVerticalOffset(value); + } + + + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonGallery.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonGallery.cs new file mode 100644 index 0000000..b00b470 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonGallery.cs @@ -0,0 +1,708 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using Odyssey.Controls.Ribbon.Interfaces; +using System.Windows.Controls.Primitives; +using System.Diagnostics; +using System.Windows.Data; +using System.ComponentModel; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Windows.Media; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public partial class RibbonGallery : Selector, IRibbonControl, IRibbonLargeControl, IRibbonGallery + { + const string partPopupupBtn = "PART_PopupBtn"; + const string partWrapPanel = "PART_WrapPanel"; + const string partItemsPresenter = "PART_ItemsPresenter"; + + static RibbonGallery() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonGallery), new FrameworkPropertyMetadata(typeof(RibbonGallery))); + } + + public RibbonGallery() + : base() + { + RegisterCommands(); + } + + + + /// + /// Gets the item that is under the mouse cursor. + /// This is a dependency property. + /// + public object HotItem + { + get { return (object)GetValue(HotItemProperty); } + private set { SetValue(HotItemPropertyKey, value); } + } + + // Using a DependencyProperty as the backing store for HotItem. This enables animation, styling, binding, etc... + private static readonly DependencyPropertyKey HotItemPropertyKey = + DependencyProperty.RegisterReadOnly("HotItem", typeof(object), typeof(RibbonGallery), new UIPropertyMetadata(null)); + + public static readonly DependencyProperty HotItemProperty = HotItemPropertyKey.DependencyProperty; + + + + /// + /// Gets the RibbonThumbnail that is under the mouse cursor. + /// This is a dependency property. + /// + public RibbonThumbnail HotThumbnail + { + get { return (RibbonThumbnail)GetValue(HotThumbnailProperty); } + internal set { SetValue(HotThumbnailPropertyKey, value); } + } + + // Using a DependencyProperty as the backing store for HotThumbnail. This enables animation, styling, binding, etc... + private static readonly DependencyPropertyKey HotThumbnailPropertyKey = + DependencyProperty.RegisterReadOnly("HotThumbnail", typeof(RibbonThumbnail), typeof(RibbonGallery), + new UIPropertyMetadata(null, HotThumbnailPropertyChanged)); + + public static readonly DependencyProperty HotThumbnailProperty = HotThumbnailPropertyKey.DependencyProperty; + + public static void HotThumbnailPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonGallery g = (RibbonGallery)o; + g.OnHotThumbnailChanged(e); + } + + protected virtual void OnHotThumbnailChanged(DependencyPropertyChangedEventArgs e) + { + RibbonThumbnail hot = HotThumbnail; + HotItem = hot == null || IsItemItsOwnContainerOverride(hot) ? hot : ItemContainerGenerator.ItemFromContainer(hot); + + RoutedEventArgs args = new RoutedEventArgs(RibbonGallery.HotThumbnailChangedEvent); + RaiseEvent(args); + } + + public static readonly RoutedEvent HotThumbnailChangedEvent = EventManager.RegisterRoutedEvent("HotThumbnailChangedEvent", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(RibbonGallery)); + + public event RoutedEventHandler HotThumbnailChanged + { + add { AddHandler(HotThumbnailChangedEvent, value); } + remove { RemoveHandler(HotThumbnailChangedEvent, value); } + } + + + private RibbonThumbnail SelectedThumbnail + { + get { return SelectedItem as RibbonThumbnail; } + set { SelectedItem = value; } + } + + + protected override void OnSelectionChanged(SelectionChangedEventArgs e) + { + base.OnSelectionChanged(e); + IsDropDownOpen = false; + if (SelectedItem != null) + { + MakeSelectedItemVisible(); + } + } + + private void MakeSelectedItemVisible() + { + foreach (RibbonThumbnail thumb in WrapPanel.Children) + { + if (thumb.IsSelected) + { + thumb.BringIntoView(); + break; + } + } + } + + protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) + { + base.OnItemsChanged(e); + AttachThumbnailsToPanel(); + + } + + + private RibbonDropDownButton popupBtn; + private Panel wrapPanel; + private FrameworkElement itemsPresenter; + + public Panel WrapPanel + { + get + { + if (wrapPanel == null) + { + wrapPanel = GetTemplateChild(partWrapPanel) as Panel; + if (wrapPanel == null) + { + ApplyTemplate(); + } + } + return wrapPanel; + } + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (popupBtn != null) + { + popupBtn.PopupOpened -= OnPopupOpenend; + } + popupBtn = GetTemplateChild(partPopupupBtn) as RibbonDropDownButton; + if (popupBtn != null) + { + popupBtn.PopupOpened += new RoutedEventHandler(OnPopupOpenend); + } + + if (wrapPanel != null) wrapPanel.Children.Clear(); + wrapPanel = GetTemplateChild(partWrapPanel) as Panel; + if (wrapPanel == null) throw new ArgumentNullException(partWrapPanel); + + itemsPresenter = GetTemplateChild(partItemsPresenter) as FrameworkElement; + if (wrapPanel == null) throw new ArgumentNullException(partItemsPresenter); + AttachThumbnailsToPanel(); + } + + private void AttachThumbnailsToPanel() + { + if (wrapPanel != null) + { + wrapPanel.Children.Clear(); + foreach (object item in Items) + { + RibbonThumbnail container = item as RibbonThumbnail; + if (container == null) container = ItemContainerGenerator.ContainerFromItem(item) as RibbonThumbnail; + if (container != null) + { + RibbonThumbnail thumbnail = new RibbonThumbnail(); + thumbnail.ImageSource = container.ImageSource; + Binding b = new Binding("IsSelected") + { + Mode = BindingMode.TwoWay, + Source = container + }; + thumbnail.Original = container; + thumbnail.SetBinding(RibbonThumbnail.IsSelectedProperty, b); + PrepareContainerForItemOverride(thumbnail, item); + wrapPanel.Children.Add(thumbnail); + } + } + } + } + + public RibbonThumbnail Original { get; internal set; } + + + protected virtual void OnPopupOpenend(object sender, RoutedEventArgs e) + { + Popup popup = popupBtn.Popup; + + + Thickness margin = new Thickness(); + + FrameworkElement fe = popup.Child as FrameworkElement; + while (fe != null) + { + margin.Left += fe.Margin.Left; + margin.Right += fe.Margin.Right; + margin.Top += fe.Margin.Top; + margin.Bottom += fe.Margin.Bottom; + Decorator c = fe as Decorator; + fe = c != null ? c.Child as FrameworkElement : null; + } + + double w = ActualWidth + margin.Right; + double h = ActualHeight + margin.Bottom; + Rect rect = new Rect(-margin.Left, -margin.Top, w, h); + + rect = this.TransformToVisual(popup).TransformBounds(rect); + if (!IsCollapsed) + { + popup.Placement = PlacementMode.Relative; + popup.VerticalOffset = rect.Top; + popup.HorizontalOffset = rect.Left; + + } + + + if (Columns > 0) popup.MinWidth = rect.Width; + popup.MinHeight = rect.Height; + itemsPresenter.MaxWidth = DropDownMaxSize.Width; + itemsPresenter.MaxHeight = DropDownMaxSize.Height; + itemsPresenter.Width = ActualWidth; + } + + + /// + /// Gets or sets the size that a RibbonThumbnail will have. + /// This is a dependency property. + /// + public Size ThumbnailSize + { + get { return (Size)GetValue(ThumbnailSizeProperty); } + set { SetValue(ThumbnailSizeProperty, value); } + } + + // Using a DependencyProperty as the backing store for ItemSize. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ThumbnailSizeProperty = + DependencyProperty.Register("ThumbnailSize", typeof(Size), typeof(RibbonGallery), new UIPropertyMetadata(new Size(48.0, 48.0))); + + + + /// + /// Gets or sets how many columns are visible in the collapsed Gallery. + /// This is a dependency property. + /// + public int Columns + { + get { return (int)GetValue(ColumnsProperty); } + set { SetValue(ColumnsProperty, value); } + } + + public static readonly DependencyProperty ColumnsProperty = + DependencyProperty.Register("Columns", typeof(int), typeof(RibbonGallery), + new FrameworkPropertyMetadata(3, + FrameworkPropertyMetadataOptions.AffectsArrange | + FrameworkPropertyMetadataOptions.AffectsRender | + FrameworkPropertyMetadataOptions.AffectsMeasure + , ColumnsPropertyChanged)); + + + + /// + /// Gets or sets the number of columns that appear in the drop down menu. + /// Thisis a dependency property. + /// + public int DropDownColumns + { + get { return (int)GetValue(DropDownColumnsProperty); } + set { SetValue(DropDownColumnsProperty, value); } + } + + // Using a DependencyProperty as the backing store for DropDownColumns. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DropDownColumnsProperty = + DependencyProperty.Register("DropDownColumns", typeof(int), typeof(RibbonGallery), new UIPropertyMetadata(0)); + + + + public static void ColumnsPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonGallery g = (RibbonGallery)o; + g.OnColumnsChanged(e); + } + + protected virtual void OnColumnsChanged(DependencyPropertyChangedEventArgs e) + { + UpdateLayout(); + } + + + + /// + /// Gets or sets whether the ListBox with details is collapsed. + /// This is a dependency property. + /// + public bool IsCollapsed + { + get { return (bool)GetValue(IsCollapsedProperty); } + set { SetValue(IsCollapsedProperty, value); } + } + + public static readonly DependencyProperty IsCollapsedProperty = + DependencyProperty.Register("IsCollapsed", typeof(bool), typeof(RibbonGallery), new UIPropertyMetadata(false)); + + + /// + /// Gets how many columns are available for the in-ribbon panel. + /// + public int ActualColumns + { + get + { + double w = WrapPanel.DesiredSize.Width; + return (int)Math.Floor(w / ThumbnailSize.Width); + } + } + + protected override Size MeasureOverride(Size constraint) + { + double maxWrapPanelWidth = ThumbnailSize.Width * Columns; + if (WrapPanel.MaxWidth != maxWrapPanelWidth) + { + WrapPanel.MaxWidth = maxWrapPanelWidth; + WrapPanel.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + } + Size size = base.MeasureOverride(constraint); + + double w = CalculateInRibbonThumbnailWidth(); + if (WrapPanel.MaxWidth != w) + { + WrapPanel.MaxWidth = w; + WrapPanel.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + size = base.MeasureOverride(constraint); + } + int visibleItems = (int)(size.Width / ThumbnailSize.Width); + IsDropDownEnabled = visibleItems < Items.Count; + + + return size; + } + + public int VisibleItems + { + get + { + return (int)(ActualWidth / ThumbnailSize.Width); + } + } + + + + /// + /// Gets whether the drop down button is enabled. this is dependency property. + /// + public bool IsDropDownEnabled + { + get { return (bool)GetValue(IsDropDownEnabledProperty); } + private set { SetValue(IsDropDownEnabledPropertyKey, value); } + } + + private static readonly DependencyPropertyKey IsDropDownEnabledPropertyKey = + DependencyProperty.RegisterReadOnly("IsDropDownEnabled", typeof(bool), typeof(RibbonGallery), new UIPropertyMetadata(false)); + + public static readonly DependencyProperty IsDropDownEnabledProperty = IsDropDownEnabledPropertyKey.DependencyProperty; + + + private double CalculateInRibbonThumbnailWidth() + { + double w = WrapPanel.DesiredSize.Width; + return this.ThumbnailSize.Width * Math.Floor(w / ThumbnailSize.Width); + } + + private double CalculateInRibbonThumbnailHeight() + { + double height = ThumbnailSize.Height; + double smallHeight = InternalGroupPanel.MaxSmallHeight; + double rows; + if (height <= smallHeight) rows = 3.0; + else if (height <= (2.0 * smallHeight)) rows = 2.0; + else rows = 1.0; + + + return ((3.0 * smallHeight - 3.0) / rows); + } + + + public bool IsDropDownOpen + { + get { return (bool)GetValue(IsDropDownOpenProperty); } + set { SetValue(IsDropDownOpenProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsDropDownOpen. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsDropDownOpenProperty = + DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(RibbonGallery), + new FrameworkPropertyMetadata(false, + FrameworkPropertyMetadataOptions.AffectsArrange | + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | + FrameworkPropertyMetadataOptions.AffectsRender | + FrameworkPropertyMetadataOptions.AffectsMeasure, + DropDownOpenPropertyChanged)); + + + public static void DropDownOpenPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonGallery g = (RibbonGallery)o; + g.DropDownOpenChanged(e); + } + + protected virtual void DropDownOpenChanged(DependencyPropertyChangedEventArgs e) + { + return; + //Panel dropDownPanel = GetTemplateChild("xPART_ListBox") as Panel; + //Panel mainPanel = GetTemplateChild("PART_WrapPanel") as Panel; + //if (dropDownPanel != null) + //{ + // bool opened = (bool)e.NewValue; + // if (opened) + // { + // dropDownPanel.Children.Clear(); + // foreach (object o in Items) + // { + // RibbonMenuItem item = o as RibbonMenuItem; + // if (item == null) + // { + // Binding b = new Binding(); + // b.Source = o; + // item = new RibbonMenuItem(); + // item.SetBinding(RibbonMenuItem.HeaderProperty, b); + // } + // dropDownPanel.Children.Add(item); + // } + // } + // else + // { + // mainPanel.Children.Clear(); + // foreach (object o in Items) + // { + // RibbonMenuItem item = o as RibbonMenuItem; + // if (item == null) + // { + // Binding b = new Binding(); + // b.Source = o; + // item = new RibbonMenuItem(); + // item.SetBinding(RibbonMenuItem.HeaderProperty, b); + // } + // mainPanel.Children.Add(item); + // } + // } + //} + } + + protected override bool IsItemItsOwnContainerOverride(object item) + { + return item is RibbonThumbnail; + } + + protected override DependencyObject GetContainerForItemOverride() + { + return new RibbonThumbnail(); + } + + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + base.PrepareContainerForItemOverride(element, item); + RibbonThumbnail tn = element as RibbonThumbnail; + if (tn != null) + { + tn.Width = ThumbnailSize.Width; + tn.Height = CalculateInRibbonThumbnailHeight(); + Stretch stretch = RibbonGallery.GetStretch(this); + RibbonGallery.SetStretch(tn, stretch); + } + } + + + public Size DropDownMaxSize + { + get { return (Size)GetValue(DropDownMaxSizeProperty); } + set { SetValue(DropDownMaxSizeProperty, value); } + } + + public static readonly DependencyProperty DropDownMaxSizeProperty = + DependencyProperty.Register("DropDownMaxSize", typeof(Size), typeof(RibbonGallery), new UIPropertyMetadata(new Size(double.PositiveInfinity, double.PositiveInfinity))); + + + + /// + /// Gets the collection of RibbonThumbnails. + /// This is a dependency property + /// + /// + /// This dependency property is used to bind the generated collection of RibbonThumbnails with the listboxes inside the ControlTemplate. + /// + public object Thumbnails + { + get { return (object)GetValue(ThumbnailsProperty); } + set { SetValue(ThumbnailsProperty, value); } + } + + public static readonly DependencyProperty ThumbnailsProperty = + DependencyProperty.Register("Thumbnails", typeof(object), typeof(RibbonGallery), new UIPropertyMetadata(null)); + + + public object Header + { + get { return (object)GetValue(HeaderProperty); } + set { SetValue(HeaderProperty, value); } + } + + public static readonly DependencyProperty HeaderProperty = + DependencyProperty.Register("Header", typeof(object), typeof(RibbonGallery), new UIPropertyMetadata(null)); + + + public object Footer + { + get { return (object)GetValue(FooterProperty); } + set { SetValue(FooterProperty, value); } + } + + public static readonly DependencyProperty FooterProperty = + DependencyProperty.Register("Footer", typeof(object), typeof(RibbonGallery), new UIPropertyMetadata(null)); + + + public DataTemplate HeaderTemplate + { + get { return (DataTemplate)GetValue(HeaderTemplateProperty); } + set { SetValue(HeaderTemplateProperty, value); } + } + + public static readonly DependencyProperty HeaderTemplateProperty = + DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(RibbonGallery), new UIPropertyMetadata(null)); + + + public DataTemplate FooterTemplate + { + get { return (DataTemplate)GetValue(FooterTemplateProperty); } + set { SetValue(FooterTemplateProperty, value); } + } + + public static readonly DependencyProperty FooterTemplateProperty = + DependencyProperty.Register("FooterTemplate", typeof(DataTemplate), typeof(RibbonGallery), new UIPropertyMetadata(null)); + + public static RibbonGalleryColumns GetReductionColumns(DependencyObject obj) + { + return (RibbonGalleryColumns)obj.GetValue(ReductionColumnsProperty); + } + + public static void SetReductionColumns(DependencyObject obj, RibbonGalleryColumns value) + { + obj.SetValue(ReductionColumnsProperty, value); + } + + public static readonly DependencyProperty ReductionColumnsProperty = + DependencyProperty.RegisterAttached("ReductionColumns", typeof(RibbonGalleryColumns), typeof(RibbonGallery), + new UIPropertyMetadata(null, ReductionColumnsPropertyChanged)); + + static void ReductionColumnsPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonGallery g = o as RibbonGallery; + if (g != null) + { + RibbonGalleryColumns columns = e.NewValue as RibbonGalleryColumns; + if (columns != null && columns.Count > 0) + { + g.Columns = columns[0]; + } + } + } + + private int ActualDropDownColumns; + + void IRibbonGallery.SetDropDownColumns(int columns) + { + ActualDropDownColumns = DropDownColumns > 0 ? DropDownColumns : columns; + } + + + /// + /// Gets or sets the image that appears when the Gallery is collapsed. + /// This is a dependency property. + /// + public ImageSource LargeImage + { + get { return (ImageSource)GetValue(LargeImageProperty); } + set { SetValue(LargeImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for LargeImage. This enables animation, styling, binding, etc... + public static readonly DependencyProperty LargeImageProperty = + DependencyProperty.Register("LargeImage", typeof(ImageSource), typeof(RibbonGallery), new UIPropertyMetadata(null)); + + + + + /// + /// Gets or sets the title that appears when the gallery is collapsed. + /// This is a dependency property. + /// + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + // Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(string), typeof(RibbonGallery), new UIPropertyMetadata("")); + + + + /// + /// Specifies how to stretch each image of a RibbonThumbnail. + /// Attach this property to the RibbonGallery rather than to each RibbonThumbnail (see example). + /// This is a dependency property. + /// + /// + /// ]]> + /// + [AttachedPropertyBrowsableForType(typeof(DependencyObject))] + public static Stretch GetStretch(DependencyObject obj) + { + return (Stretch)obj.GetValue(StretchProperty); + } + + public static void SetStretch(DependencyObject obj, Stretch value) + { + obj.SetValue(StretchProperty, value); + } + + // Using a DependencyProperty as the backing store for Stretch. This enables animation, styling, binding, etc... + public static readonly DependencyProperty StretchProperty = + DependencyProperty.RegisterAttached("Stretch", typeof(Stretch), typeof(RibbonGallery), new UIPropertyMetadata(Stretch.None)); + + + /// + /// Gets or sets the with for the drop down menu. + /// This is a dependency property. + /// + public double DropDownWidth + { + get { return (double)GetValue(DropDownWidthProperty); } + set { SetValue(DropDownWidthProperty, value); } + } + + // Using a DependencyProperty as the backing store for DropDownWidth. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DropDownWidthProperty = + DependencyProperty.Register("DropDownWidth", typeof(double), typeof(RibbonGallery), new UIPropertyMetadata(double.NaN)); + + + + /// + /// Gets or sets how a thumbnail image is scaled. + /// This is a dependency property. + /// + public BitmapScalingMode BitmapScalingMode + { + get { return (BitmapScalingMode)GetValue(BitmapScalingModeProperty); } + set { SetValue(BitmapScalingModeProperty, value); } + } + + public static readonly DependencyProperty BitmapScalingModeProperty = + DependencyProperty.Register("BitmapScalingMode", typeof(BitmapScalingMode), typeof(RibbonGallery), new UIPropertyMetadata(BitmapScalingMode.NearestNeighbor)); + + + + + /// + /// Gets or sets the edge mode when rendering a thumbnail image. + /// This is a dependency property. + /// + public EdgeMode EdgeMode + { + get { return (EdgeMode)GetValue(EdgeModeProperty); } + set { SetValue(EdgeModeProperty, value); } + } + + public static readonly DependencyProperty EdgeModeProperty = + DependencyProperty.Register("EdgeMode", typeof(EdgeMode), typeof(RibbonGallery), new UIPropertyMetadata(EdgeMode.Aliased)); + + + } +} \ No newline at end of file diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonGroup.Commands.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonGroup.Commands.cs new file mode 100644 index 0000000..26ea857 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonGroup.Commands.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Input; +using System.Windows; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + partial class RibbonGroup + { + private static RoutedUICommand launchDialogCommand = new RoutedUICommand("Launch", "LaunchDialogCommand", typeof(RibbonGroup)); + + private static void RegisterCommands() + { + CommandManager.RegisterClassCommandBinding(typeof(RibbonGroup), + new CommandBinding(launchDialogCommand, launchDialog)); + } + + private static void launchDialog(object sender, ExecutedRoutedEventArgs e) + { + RibbonGroup group = (RibbonGroup)sender; + RoutedEventArgs args = new RoutedEventArgs(ExecuteLauncherEvent); + group.RaiseEvent(args); + + } + + public static RoutedUICommand LaunchDialogCommand + { + get { return launchDialogCommand; } + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonGroup.Handlers.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonGroup.Handlers.cs new file mode 100644 index 0000000..ca09708 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonGroup.Handlers.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + partial class RibbonGroup + { + private void RegisterHandlers() + { + AddHandler(RibbonGroup.ExecuteLauncherEvent, new RoutedEventHandler(OnExecuteLauncher)); + } + + /// + /// Occurs when the DialogLauncher Button is clicked. + /// + public static readonly RoutedEvent ExecuteLauncherEvent = EventManager.RegisterRoutedEvent("ExecuteLauncherEvent", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(RibbonGroup)); + + /// + /// Occurs when the DialogLauncher Button is clicked. + /// + public event RoutedEventHandler ExecuteLauncher + { + add { AddHandler(ExecuteLauncherEvent, value); } + remove { RemoveHandler(ExecuteLauncherEvent, value); } + } + + /// + /// Occurs when the DialogLauncher Button is clicked. + /// + protected virtual void OnExecuteLauncher(object sender, RoutedEventArgs e) + { + } + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonGroup.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonGroup.cs new file mode 100644 index 0000000..93401c8 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonGroup.cs @@ -0,0 +1,626 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Markup; +using System.Collections.ObjectModel; +using System.Windows.Controls.Primitives; +using System.Diagnostics; +using Odyssey.Controls.Classes; +using Odyssey.Controls.Ribbon.Interfaces; +using System.Collections.Specialized; +using Odyssey.Controls.Interfaces; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + + [ContentProperty("Controls")] + [TemplatePart(Name = partItemsPanelHost)] + [TemplatePart(Name = partPopupItemsPanelHost)] + [TemplatePart(Name = partPopup)] + public partial class RibbonGroup : Control,IKeyTipControl + { + + const string partItemsPanelHost = "PART_ItemsPanelHost"; + const string partPopupItemsPanelHost = "PART_PopupItemsPanelHost"; + const string partPopup = "PART_Popup"; + + static RibbonGroup() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonGroup), new FrameworkPropertyMetadata(typeof(RibbonGroup))); + RegisterCommands(); + } + + + public RibbonGroup() + : base() + { + controls.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(OnControlCollectionChanged); + RegisterHandlers(); + } + + /// + /// Occurs when the Controls collection has changed. + /// + protected virtual void OnControlCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + reducableControlIndexes = null; + ChangeControlSizes(); + } + + private Panel itemPanelInstance = null; + private Panel ItemPanelInstance + { + get + { + if (itemPanelInstance == null) + { + if (ItemsPanel != null) + { + itemPanelInstance = ItemsPanel; + } + else itemPanelInstance = new RibbonWrapPanel(); + } + return itemPanelInstance; + } + } + + private Decorator itemsPanelHost; + private Decorator popupItemsPanelHost; + private Popup popup; + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (ItemPanelInstance != null) + { + ItemPanelInstance.Children.Clear(); + foreach (UIElement e in Controls) + { + ItemPanelInstance.Children.Add(e); + } + } + + itemsPanelHost = GetTemplateChild(partItemsPanelHost) as Decorator; + popupItemsPanelHost = GetTemplateChild(partPopupItemsPanelHost) as Decorator; + + if (popup != null) + { + popup.Opened -= OnPopupOpened; + popup.Closed -= OnPopupClosed; + } + popup = GetTemplateChild(partPopup) as Popup; + if (popup != null) + { + popup.Opened += new EventHandler(OnPopupOpened); + popup.Closed += new EventHandler(OnPopupClosed); + } + + AttachControlsToItemsPanel(); + } + + + public static RibbonGroup PoppedUpGroup; + + void OnPopupClosed(object sender, EventArgs e) + { + if (PoppedUpGroup.popup == sender) PoppedUpGroup = null; + } + + void OnPopupOpened(object sender, EventArgs e) + { + if (PoppedUpGroup != null && PoppedUpGroup.popup != null && PoppedUpGroup.popup != sender) + { + PoppedUpGroup.popup.IsOpen = false; + } + PoppedUpGroup = this; + + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (IsMinimized) + { + switch (e.Key) + { + case Key.Space: + case Key.Down: + IsDropDownOpen = true; + e.Handled = true; + break; + } + } + base.OnKeyDown(e); + } + + + /// + /// CAUTION: + /// Call ApplyTemplate after EndInit to ensure that all controls properties could have applied to a Binding. + /// If ApplyTemplate is not executed here, bindings like "Binding IsChecked, ElementName=anyname" would not work if this group is + /// not yet visibile, for instance, if it is in a RibbonTab that is not the initial first tab. + /// + public override void EndInit() + { + base.EndInit(); + ApplyTemplate(); + } + + public bool IsDropDownOpen + { + get { return (bool)GetValue(IsDropDownOpenProperty); } + set { SetValue(IsDropDownOpenProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsDropDownOpen. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsDropDownOpenProperty = + DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(RibbonGroup), + new UIPropertyMetadata(false, DropDownOpenPropertyChanged)); + + private static void DropDownOpenPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonGroup g = (RibbonGroup)o; + } + + + + public bool IsMinimized + { + get { return (bool)GetValue(IsMinimizedProperty); } + set { SetValue(IsMinimizedProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsMinimized. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsMinimizedProperty = + DependencyProperty.Register("IsMinimized", typeof(bool), typeof(RibbonGroup), + new UIPropertyMetadata(false, MinimizedPropertyChanged)); + + public static void MinimizedPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonGroup g = (RibbonGroup)o; + g.AttachControlsToItemsPanel(); + } + + + private void AttachControlsToItemsPanel() + { + + if (ItemPanelInstance != null) + { + Decorator parent = ItemPanelInstance.Parent as Decorator; + if (parent != null) parent.Child = null; + + Decorator host = IsMinimized ? popupItemsPanelHost : itemsPanelHost; + if (host != null) + { + host.Child = ItemPanelInstance; + } + } + } + + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + // Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(string), typeof(RibbonGroup), new UIPropertyMetadata("")); + + + + public bool IsDialogLauncherVisible + { + get { return (bool)GetValue(IsDialogLauncherVisibleProperty); } + set { SetValue(IsDialogLauncherVisibleProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsDialogLauncherVisible. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsDialogLauncherVisibleProperty = + DependencyProperty.Register("IsDialogLauncherVisible", typeof(bool), typeof(RibbonGroup), new UIPropertyMetadata(false)); + + + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for Image. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(ImageSource), typeof(RibbonGroup), new UIPropertyMetadata(null)); + + /// + /// Gets or sets the Panel that contains the Controls. + /// This is a dependency property. + /// + public Panel ItemsPanel + { + get { return (Panel)GetValue(ItemsPanelProperty); } + set { SetValue(ItemsPanelProperty, value); } + } + + // Using a DependencyProperty as the backing store for ItemsPanel. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ItemsPanelProperty = + DependencyProperty.Register("ItemsPanel", typeof(Panel), typeof(RibbonGroup), new UIPropertyMetadata(null, ItemsPanelPropertyChanged)); + + static void ItemsPanelPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + RibbonGroup grp = (RibbonGroup)d; + grp.OnItemsPanelChanged(e); + } + + protected virtual void OnItemsPanelChanged(DependencyPropertyChangedEventArgs e) + { + itemPanelInstance = null; + AttachControlsToItemsPanel(); + } + + //protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) + //{ + // base.OnPreviewMouseLeftButtonDown(e); + + // if (!(e.Source is MenuItem) && !(e.Source is RibbonThumbnail)) + // { + // RibbonDropDownButton.CloseOpenedPopup(null); + // } + //} + + private ObservableCollection controls = new ObservableCollection(); + + /// + /// Gets the Collection of controls for this gorup. + /// + public Collection Controls { get { return controls; } } + + protected override System.Collections.IEnumerator LogicalChildren + { + get + { + return controls.GetEnumerator(); + } + } + + private void ClosePopup() + { + if (popup != null) popup.IsOpen = false; + } + + public static void CloseOpenedPopup() + { + if (PoppedUpGroup != null) + { + PoppedUpGroup.ClosePopup(); + } + } + + static RibbonSizeCollection first = new RibbonSizeCollection { RibbonSize.Large, RibbonSize.Large, RibbonSize.Large, RibbonSize.Minimized }; + + int GetGalleryColumns(DependencyObject control, int level, int maxLevels) + { + RibbonGalleryColumns columns = RibbonGallery.GetReductionColumns(control); + if (columns != null && columns.Count > 0) + { + level = Math.Max(0, Math.Min(level, columns.Count - 1)); + return columns[level]; + } + + if (level == maxLevels) return 0; + + if (maxLevels > 4) + { + level = Math.Max(level, 8); + return 3 + (4 - level / 2); + } + else + { + return 3 + (2 - level) * 2; + } + + } + + /// + /// Gets the RibbonSize for a control for a specific level. + /// + /// The control for which to retreive a RibbonSize. + /// The reduction Level (0=large, 2=medium,3=small,4=minimized,...). + /// The index of the control in the group. + /// The RibbonSize for the control. + RibbonSize GetControlSize(DependencyObject control, int level, int index) + { + RibbonSizeCollection reductions = RibbonBar.GetReduction(control); + if (reductions != null && reductions.Count > 0) + { + level = Math.Max(0, Math.Min(level, reductions.Count - 1)); + return reductions[level]; + } + RibbonSize size; + switch (level) + { + case 0: size = GetDefaultSizeForLevel0(index); break; + case 1: size = GetDefaultSizeForLevel1(index); break; + case 2: size = GetDefaultSizeForLevel2(index); break; + default: size = RibbonSize.Minimized; break; + } + + RibbonSize min = RibbonBar.GetMinSize(control); + RibbonSize max = RibbonBar.GetMaxSize(control); + if (size < min) size = min; + if (size > max) size = max; + + return size; + } + + private int CountRibbonControls() + { + int count = 0; + foreach (UIElement e in Controls) + { + if (e is IRibbonControl) count++; + } + return count; + } + + private RibbonSize GetDefaultSizeForLevel2(int index) + { + if (!ReducableControlIndexes.ContainsKey(index)) return RibbonSize.Large; + return ReducableControlIndexes[index]; + } + + private RibbonSize GetDefaultSizeForLevel1(int index) + { + if (!ReducableControlIndexes.ContainsKey(index)) return RibbonSize.Large; + return RibbonSize.Medium; + } + + private RibbonSize GetDefaultSizeForLevel0(int index) + { + return RibbonSize.Large; + } + + + private int reductionLevel; + /// + /// Gets or sets the reduction level. + /// + public int ReductionLevel + { + get { return reductionLevel; } + set + { + if (value < 0) value = 0; + if (reductionLevel != value) + { + reductionLevel = value; + ChangeControlSizes(); + } + } + } + + /// + /// Resets the ReductionLevel to it's default value (0); + /// + public void ResetSize() + { + ReductionLevel = 0; + } + + /// + /// Reduce the ReductionLevel one step. + /// + /// + public bool Reduce() + { + if (ReductionLevel < GetMaxLevel() + 1) + { + ReductionLevel++; + return true; + } + else return false; + } + + /// + /// Expand the ReductionLevel one step. + /// + /// + public bool Expand() + { + if (ReductionLevel > 0) + { + ReductionLevel--; + return true; + } + else return false; + } + + //Gets the maximum possible ReductionLevel that would change any of the controls. + private int GetMaxLevel() + { + int max = 1; + foreach (UIElement e in Controls) + { + if (e is IRibbonControl) + { + RibbonSizeCollection reduction = RibbonBar.GetReduction(e); + int m = reduction != null ? reduction.Count : 3; + max = Math.Max(max, m); + } + if (e is IRibbonGallery) + { + RibbonGalleryColumns columns = RibbonGallery.GetReductionColumns(e); + int m = columns != null ? columns.Count : 3; + max = Math.Max(max, m); + } + } + return max; + } + + + /// + /// Change the RibbonSize for every IRibbonControl inside this group. + /// + private void ChangeControlSizes() + { + int level = ReductionLevel; + int maxLevels = GetMaxLevel(); + bool minimized = level >= maxLevels; + IsMinimized = minimized; + if (minimized) level = 0; + + for (int i = 0; i < Controls.Count; i++) + { + UIElement e = Controls[i]; + IRibbonGallery gallery = e as IRibbonGallery; + if (gallery != null) + { + int columns = GetGalleryColumns(e, level, maxLevels); + gallery.Columns = columns; + gallery.IsCollapsed = columns == 0; + gallery.SetDropDownColumns(GetGalleryColumns(e, 0, maxLevels)); + } + else + { + if (e is IRibbonControl) + { + RibbonSize size = GetControlSize(e, level, i); + RibbonBar.SetSize(e, size); + } + } + } + InvalidateMeasure(); + UpdateLayout(); + + } + + + #region AutoReduction + + class GroupBucket : List + { + } + + class GroupBucketCollection : List + { + } + + GroupBucketCollection GetBuckets() + { + GroupBucketCollection buckets = new GroupBucketCollection(); + GroupBucket bucket = new GroupBucket(); + buckets.Add(bucket); + + int index = 0; + foreach (UIElement e in Controls) + { + bool canBeSmall = CanControlBeSmall(e); + if (!canBeSmall && bucket.Count > 0) + { + bucket = new GroupBucket(); + buckets.Add(bucket); + } + else + { + bucket.Add(index); + } + index++; + } + return buckets; + } + + private bool CanControlBeSmall(UIElement e) + { + + if ((e is IRibbonControl) && !(e is IRibbonLargeControl)) + { + RibbonSize min = RibbonBar.GetMinSize(e); + return min < RibbonSize.Large; + } + return e.DesiredSize.Height <= InternalGroupPanel.MaxSmallHeight; + } + + + private Dictionary reducableControlIndexes; + + private Dictionary ReducableControlIndexes + { + get + { + if (reducableControlIndexes == null) reducableControlIndexes = GetReducableControlIndexes(); + return reducableControlIndexes; + } + } + + + /// + /// Gets a Dictionary of all control indexs (from Controls) which can be reduced and to which size. + /// + /// + /// To automatically determine which controls can be reduced and to which level, the controls are first grouped into buckets that contain continous controls + /// that can be reduced to a small size so that 3 of them can share a row. For each of this buckets, the available RibbonSize is determined of where the + /// control is placed inside the bucket. + /// + /// + private Dictionary GetReducableControlIndexes() + { + Dictionary reducable = new Dictionary(); + GroupBucketCollection buckets = GetBuckets(); + + foreach (GroupBucket b in buckets) + { + int smallLevel = Math.Max(0, b.Count - 3); + int startIndex = Controls.Count == 2 ? 0 : b.Count % 3; + + for (int i = startIndex; i < b.Count; i++) + { + RibbonSize minSize = i >= smallLevel ? RibbonSize.Small : RibbonSize.Medium; + reducable.Add(b[i], minSize); + } + } + int index = 0; + foreach (UIElement e in Controls) + { + if (e is IRibbonLargeControl) reducable.Add(index, RibbonSize.Small); + index++; + } + return reducable; + } + + + + #endregion + + /// + /// Executes the launcher command. + /// + public virtual void DoExecuteLauncher() + { + RoutedEventArgs args = new RoutedEventArgs(RibbonGroup.ExecuteLauncherEvent); + RaiseEvent(args); + } + + #region IKeyTipControl Members + + void IKeyTipControl.ExecuteKeyTip() + { + DoExecuteLauncher(); + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonMenuItem.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonMenuItem.cs new file mode 100644 index 0000000..241b1e1 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonMenuItem.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Markup; +using System.Windows.Controls.Primitives; +using System.Windows.Media; +using System.Windows.Input; +using Odyssey.Controls.Ribbon.Interfaces; +using Odyssey.Controls.Interfaces; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [ContentProperty("Items")] + public class RibbonMenuItem:MenuItem,IKeyTipControl + { + const string partPopup = "PART_Popup"; + + static RibbonMenuItem() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonMenuItem), new FrameworkPropertyMetadata(typeof(RibbonMenuItem))); + } + + protected override DependencyObject GetContainerForItemOverride() + { + return new RibbonMenuItem(); + } + + + + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for Image. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(ImageSource), typeof(RibbonMenuItem), new UIPropertyMetadata(null)); + + + private Popup popup; + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + if (popup != null) + { + popup.Opened -= OnPopupOpenend; + popup.Closed -= OnPopupClosed; + } + popup = GetTemplateChild(partPopup) as Popup; + if (popup != null) + { + popup.Opened += new EventHandler(OnPopupOpenend); + popup.Closed += new EventHandler(OnPopupClosed); + } + } + + protected virtual void OnPopupClosed(object sender, EventArgs e) + { + IsSubmenuOpen = false; + } + + protected virtual void OnPopupOpenend(object sender, EventArgs e) + { + RibbonApplicationMenu menu = ItemsControl.ItemsControlFromItemContainer(this) as RibbonApplicationMenu; + if (menu != null) + { + Rect subMenuRect = menu.GetSubMenuRect(this); + popup.Placement = PlacementMode.Relative; + popup.VerticalOffset = subMenuRect.Top; + popup.HorizontalOffset = subMenuRect.Left; + popup.Width = subMenuRect.Width; + popup.Height = subMenuRect.Height; + } + } + + + + protected override void OnSubmenuOpened(RoutedEventArgs e) + { + base.OnSubmenuOpened(e); + if (popup != null) popup.IsOpen = true; + } + + protected override void OnSubmenuClosed(RoutedEventArgs e) + { + base.OnSubmenuClosed(e); + if (popup != null) popup.IsOpen = false; + } + + + + #region IKeyboardCommand Members + + public void ExecuteKeyTip() + { + if (this.HasItems) + { + IsSubmenuOpen ^= true; + } + else + { + OnClick(); + } + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonQAToolBar.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonQAToolBar.cs new file mode 100644 index 0000000..d21ac40 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonQAToolBar.cs @@ -0,0 +1,364 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using Odyssey.Controls.Ribbon.Interfaces; +using System.Diagnostics; +using System.Windows.Media; +using System.Windows.Input; +using System.Windows.Threading; +using System.Windows.Data; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + /// + /// RibbonQuickAccessToolbar + /// + [TemplatePart(Name = partItemsHost)] + [TemplatePart(Name = partOverflowHost)] + [TemplatePart(Name = partMenuItemsHost)] + public class RibbonQAToolBar : ItemsControl + { + const string partItemsHost = "PART_ToolBarPanel"; + const string partOverflowHost = "PART_OverflowHost"; + const string partMenuItemsHost = "PART_MenuItemHost"; + const string partMenuItemsOverflowHost = "PART_MenuItemOverflowHost"; + + static RibbonQAToolBar() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonQAToolBar), new FrameworkPropertyMetadata(typeof(RibbonQAToolBar))); + } + + public RibbonQAToolBar() + : base() + { + AddHandler(RibbonButton.ClickEvent, new RoutedEventHandler(OnClick)); + AddHandler(RibbonSplitButton.ClickEvent, new RoutedEventHandler(OnClick)); + AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(OnMenuItemClick)); + } + + void OnClick(object sender, RoutedEventArgs e) + { + if (e.Source != this) + { + IsOverflowOpen = false; + IsMenuOpen = false; + } + } + + void OnMenuItemClick(object sender, RoutedEventArgs e) + { + IsMenuOpen = false; + IsOverflowOpen = false; + } + + + private Panel itemsHost; + private Panel overflowHost; + private Panel menuItemsHost; + private Panel menuItemsOverflowHost; + + + public override void OnApplyTemplate() + { + if (menuItemsOverflowHost != null) menuItemsOverflowHost.Children.Clear(); + if (menuItemsHost != null) menuItemsHost.Children.Clear(); + if (itemsHost != null) itemsHost.Children.Clear(); + if (overflowHost != null) overflowHost.Children.Clear(); + + base.OnApplyTemplate(); + itemsHost = GetTemplateChild(partItemsHost) as Panel; + overflowHost = GetTemplateChild(partOverflowHost) as Panel; + + menuItemsHost = GetTemplateChild(partMenuItemsHost) as Panel; + menuItemsOverflowHost = GetTemplateChild(partMenuItemsOverflowHost) as Panel; + + if (itemsHost == null) throw new ArgumentException(partItemsHost + " is not defined in ControlTemplate."); + } + + private IEnumerable GetMenuItems() + { + foreach (UIElement e in Items) + { + if (!(e is IRibbonButton)) yield return e; + } + } + + private IEnumerable GetToolbarItems() + { + foreach (UIElement e in Items) + { + if ((e is IRibbonButton)) yield return e; + } + } + + private void CreateMenuItems() + { + if (menuItemsHost != null) + { + HasMenuItems = false; + if (menuItemsOverflowHost != null) menuItemsOverflowHost.Children.Clear(); + menuItemsHost.Children.Clear(); + Panel host = HasOverflowItems && menuItemsOverflowHost != null ? menuItemsOverflowHost : menuItemsHost; + foreach (UIElement e in GetMenuItems()) + { + host.Children.Add(e); + //RibbonMenuItem mi = new RibbonMenuItem(); + //RibbonMenuItem rm = e as RibbonMenuItem; + //if (rm != null) + //{ + // Binding b = new Binding("IsChecked"); + // b.Source = e; + // b.Mode = BindingMode.TwoWay; + // mi.SetBinding(RibbonMenuItem.IsCheckedProperty, b); + // mi.Image = (e as RibbonMenuItem).Image; + // mi.Header = (e as RibbonMenuItem).Header; + // mi.IsCheckable = rm.IsCheckable; + //} + //host.Children.Add(mi); + HasMenuItems = true; + } + } + } + + + protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint) + { + itemsHost.Children.Clear(); + if (overflowHost != null) overflowHost.Children.Clear(); + CreateMenuItems(); + foreach (UIElement e in GetToolbarItems()) + { + RibbonBar.SetSize(e, RibbonSize.Small); + itemsHost.Children.Add(e); + } + HasOverflowItems = false; + Size maxSize = base.MeasureOverride(new Size(double.PositiveInfinity, constraint.Height)); + while (maxSize.Width > constraint.Width) + { + int n = itemsHost.Children.Count; + if (n == 0) break; + UIElement e = itemsHost.Children[n - 1]; + InvalidateAncestorMeasure(e); + itemsHost.Children.RemoveAt(n - 1); + if (overflowHost != null) + { + overflowHost.Children.Insert(0, e); + HasOverflowItems = true; + } + maxSize = base.MeasureOverride(new Size(double.PositiveInfinity, constraint.Height)); + } + Size size = base.MeasureOverride(constraint); + return size; + } + + private void InvalidateAncestorMeasure(DependencyObject obj) + { + UIElement element = obj as UIElement; + if (element != null) + { + element.InvalidateMeasure(); + } + if ((obj != this) && (obj != null)) + { + this.InvalidateAncestorMeasure(VisualTreeHelper.GetParent(obj)); + } + } + + + /// + /// Specifies where the QuickAccessToolBar is supposed to be placed. + /// Depending on this information, the layout and design may vary. + /// This is a dependency property. + /// + public QAPlacement ToolBarPlacement + { + get { return (QAPlacement)GetValue(ToolBarPlacementProperty); } + set { SetValue(ToolBarPlacementProperty, value); } + } + + // Using a DependencyProperty as the backing store for Placement. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ToolBarPlacementProperty = + DependencyProperty.Register("ToolBarPlacement", typeof(QAPlacement), typeof(RibbonQAToolBar), + new FrameworkPropertyMetadata(QAPlacement.Top, + FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsRender)); + + + + + + /// + /// Gets or sets whether the overflow panel button is visible. + /// This is a dependency property. + /// + public bool HasOverflowItems + { + get { return (bool)GetValue(IsOverflowVisibleProperty); } + set { SetValue(IsOverflowVisibleProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsDropDownVisible. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsOverflowVisibleProperty = + DependencyProperty.Register("HasOverflowItems", typeof(bool), typeof(RibbonQAToolBar), + new UIPropertyMetadata(false, HasOverflowItemsPropertyChanged)); + + private static void HasOverflowItemsPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonQAToolBar toolbar = (RibbonQAToolBar)o; + toolbar.CreateMenuItems(); + } + + /// + /// Gets or sets whether the overflow panel is open. + /// This is a dependency property. + /// + public bool IsOverflowOpen + { + get { return (bool)GetValue(IsOverflowOpenProperty); } + set { SetValue(IsOverflowOpenProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsDropDownVisible. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsOverflowOpenProperty = + DependencyProperty.Register("IsOverflowOpen", typeof(bool), typeof(RibbonQAToolBar), new FrameworkPropertyMetadata(false, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OverflowOpenPropertyChanged)); + + public static void OverflowOpenPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonQAToolBar tb = (RibbonQAToolBar)o; + tb.IsMenuOpen=false; + } + + + + /// + /// Gets or sets whether the menu is open. + /// This is a dependency property. + /// + public bool IsMenuOpen + { + get { return (bool)GetValue(IsMenuOpenProperty); } + set { SetValue(IsMenuOpenProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsMenuOpen. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsMenuOpenProperty = + DependencyProperty.Register("IsMenuOpen", typeof(bool), typeof(RibbonQAToolBar), + new FrameworkPropertyMetadata(false, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); + + + + /// + /// Gets or sets whether the menu button is visible. + /// This is a dependency property. + /// + public bool HasMenuItems + { + get { return (bool)GetValue(HasMenuItemsProperty); } + set { SetValue(HasMenuItemsProperty, value); } + } + + // Using a DependencyProperty as the backing store for HasMenuItems. This enables animation, styling, binding, etc... + public static readonly DependencyProperty HasMenuItemsProperty = + DependencyProperty.Register("HasMenuItems", typeof(bool), typeof(RibbonQAToolBar), new UIPropertyMetadata(false)); + + + + + + /// + /// Gets or sets the header that appears in the menu. + /// + public object MenuHeader + { + get { return (object)GetValue(MenuHeaderProperty); } + set { SetValue(MenuHeaderProperty, value); } + } + + // Using a DependencyProperty as the backing store for MenuHeader. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MenuHeaderProperty = + DependencyProperty.Register("MenuHeader", typeof(object), typeof(RibbonQAToolBar), new UIPropertyMetadata(null)); + + + + + public DataTemplate MenuHeaderTemplate + { + get { return (DataTemplate)GetValue(MenuHeaderTemplateProperty); } + set { SetValue(MenuHeaderTemplateProperty, value); } + } + + // Using a DependencyProperty as the backing store for MenuHeaderTemplate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MenuHeaderTemplateProperty = + DependencyProperty.Register("MenuHeaderTemplate", typeof(DataTemplate), typeof(RibbonQAToolBar), new UIPropertyMetadata(null)); + + + + /// + /// Gets or sets the footer that appears in the menu panel. + /// + public object MenuFooter + { + get { return (object)GetValue(MenuFooterProperty); } + set { SetValue(MenuFooterProperty, value); } + } + + // Using a DependencyProperty as the backing store for PopupFooter. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MenuFooterProperty = + DependencyProperty.Register("MenuFooter", typeof(object), typeof(RibbonQAToolBar), new UIPropertyMetadata(null)); + + + + + public DataTemplate MenuFooterTemplate + { + get { return (DataTemplate)GetValue(MenuFooterTemplateProperty); } + set { SetValue(MenuFooterTemplateProperty, value); } + } + + // Using a DependencyProperty as the backing store for MenuFooterTemplate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MenuFooterTemplateProperty = + DependencyProperty.Register("MenuFooterTemplate", typeof(DataTemplate), typeof(RibbonQAToolBar), new UIPropertyMetadata(null)); + + + + + [AttachedPropertyBrowsableForChildren] + public static QAItemPlacement GetPlacement(DependencyObject obj) + { + return (QAItemPlacement)obj.GetValue(PlacementProperty); + } + + public static void SetPlacement(DependencyObject obj, QAItemPlacement value) + { + obj.SetValue(PlacementProperty, value); + } + + // Using a DependencyProperty as the backing store for Placement. This enables animation, styling, binding, etc... + public static readonly DependencyProperty PlacementProperty = + DependencyProperty.RegisterAttached("Placement", typeof(QAItemPlacement), typeof(RibbonQAToolBar), new UIPropertyMetadata(QAItemPlacement.ToolBar)); + + + + + public DataTemplate ItemsTemplate + { + get { return (DataTemplate)GetValue(ItemsTemplateProperty); } + set { SetValue(ItemsTemplateProperty, value); } + } + + // Using a DependencyProperty as the backing store for ItemsTemplate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ItemsTemplateProperty = + DependencyProperty.Register("ItemsTemplate", typeof(DataTemplate), typeof(RibbonQAToolBar), new UIPropertyMetadata(null)); + + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonQAToolbarPanel.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonQAToolbarPanel.cs new file mode 100644 index 0000000..f9cca46 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonQAToolbarPanel.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public class RibbonQAToolbarPanel:StackPanel + { + public RibbonQAToolbarPanel() + : base() + { + //Orientation = Orientation.Horizontal; + } + + /// + /// Overriding this to enable a templated parent to add or remove children to this panel within OnMeasureOverride or somewhere else. + /// + protected override UIElementCollection CreateUIElementCollection(System.Windows.FrameworkElement logicalParent) + { + return new UIElementCollection(this, (base.TemplatedParent == null) ? logicalParent : null); + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonSeparator.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonSeparator.cs new file mode 100644 index 0000000..f9eeda8 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonSeparator.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using Odyssey.Controls.Ribbon.Interfaces; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public class RibbonSeparator:Separator,IRibbonControl + { + static RibbonSeparator() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonSeparator), new FrameworkPropertyMetadata(typeof(RibbonSeparator))); + } + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonSplitButton.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonSplitButton.cs new file mode 100644 index 0000000..039cf27 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonSplitButton.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Input; +using System.Windows.Controls; +using System.Windows.Markup; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + [TemplatePart(Name = partDropDown)] + [ContentProperty("Items")] + public class RibbonSplitButton : RibbonDropDownButton + { + const string partDropDown = "PART_DropDown"; + + static RibbonSplitButton() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonSplitButton), new FrameworkPropertyMetadata(typeof(RibbonSplitButton))); + } + + + protected Control DropDownButton {get;private set;} + + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + if (DropDownButton != null) + { + DropDownButton.MouseDown -= OnDropDownButtonDown; + DropDownButton.MouseUp -= OnDropDownButtonUp; + } + DropDownButton = GetTemplateChild(partDropDown) as Control; + if (DropDownButton != null) + { + DropDownButton.MouseLeftButtonDown += new MouseButtonEventHandler(OnDropDownButtonDown); + DropDownButton.MouseLeftButtonUp += new MouseButtonEventHandler(OnDropDownButtonUp); + } + } + + protected virtual void OnDropDownButtonDown(object sender, MouseButtonEventArgs e) + { + e.Handled = true; + + IsDropDownPressed ^= true; + EnsurePopupRemainsOnMouseUp(); + } + + protected virtual void OnDropDownButtonUp(object sender, MouseButtonEventArgs e) + { + EnsurePopupDoesNotStayOpen(); + } + + + + + public ClickMode ClickMode + { + get { return (ClickMode)GetValue(ClickModeProperty); } + set { SetValue(ClickModeProperty, value); } + } + + public static readonly DependencyProperty ClickModeProperty = + DependencyProperty.Register("ClickMode", typeof(ClickMode), typeof(RibbonSplitButton), new UIPropertyMetadata(ClickMode.Release)); + + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + IsPressed = true; + EnsurePopupRemainsOnMouseUp(); + if (ClickMode == ClickMode.Press) PerformClick(); + } + + protected override void HandleMouseLeftButtonDown(MouseButtonEventArgs e) + { + //base.HandleMouseLeftButtonDown(e); + } + + protected override void HandleMouseLeftButtonUp(MouseButtonEventArgs e) + { + //base.HandleMouseLeftButtonUp(e); + } + + + private void PerformClick() + { + OnClick(); + } + + protected override void OnClick() + { + if ((Command != null) && Command.CanExecute(CommandParameter)) Command.Execute(CommandParameter); + base.OnClick(); + } + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + bool wasPressed = IsPressed; + IsPressed = false; + base.OnMouseLeftButtonUp(e); + EnsurePopupDoesNotStayOpen(); + if (wasPressed && ClickMode == ClickMode.Release) PerformClick(); + + } + + protected override void ToggleDropDownState() + { + // do not show the popup menu at this place. + } + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonTabItem.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonTabItem.cs new file mode 100644 index 0000000..0edad13 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonTabItem.cs @@ -0,0 +1,276 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Markup; +using System.Diagnostics; +using System.Collections.ObjectModel; +using System.Collections; +using System.Collections.Specialized; +using System.ComponentModel; +using Odyssey.Controls.Classes; +using Odyssey.Controls.Ribbon.Interfaces; +using Odyssey.Controls.Interfaces; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + + [ContentProperty("Items")] + public class RibbonTabItem : ItemsControl, IKeyTipControl + { + + static RibbonTabItem() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonTabItem), new FrameworkPropertyMetadata(typeof(RibbonTabItem))); + + // changing the visibility requires the RibbonBar to be rendered again, so ensure the correct metadata options to be set: + VisibilityProperty.OverrideMetadata( + typeof(RibbonTabItem), + new FrameworkPropertyMetadata(Visibility.Visible, + FrameworkPropertyMetadataOptions.AffectsParentArrange + | FrameworkPropertyMetadataOptions.AffectsParentArrange + | FrameworkPropertyMetadataOptions.AffectsParentMeasure + | FrameworkPropertyMetadataOptions.AffectsRender)); + } + + public RibbonTabItem() + : base() + { + IsVisibleChanged += new DependencyPropertyChangedEventHandler(OnVisibleChanged); + } + + /// + /// CAUTION: + /// Call ApplyTemplate after EndInit to ensure that all controls properties could have applied to a Binding. + /// If ApplyTemplate is not executed here, bindings like "Binding IsChecked, ElementName=anyname" would not work if this group is + /// not yet visibile, for instance, if it is in a RibbonTab that is not the initial first tab. + /// + public override void EndInit() + { + base.EndInit(); + ApplyTemplate(); + } + + protected virtual void OnVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + bool visible = (bool)e.NewValue; + if (!visible) this.RibbonBar.EnsureTabIsVisible(); + } + + protected override bool IsItemItsOwnContainerOverride(object item) + { + return item is RibbonGroup; + } + + protected override DependencyObject GetContainerForItemOverride() + { + return new RibbonGroup(); + } + + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + } + + static Size infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + + private ObservableCollection groups = new ObservableCollection(); + + [Obsolete("for backward compatibility only. Use Items instead!")] + public ItemCollection Groups { get { return Items; } } + + /// + /// Gets or sets the title for the tab item. + /// This is a dependency property. + /// + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + // Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(string), typeof(RibbonTabItem), new UIPropertyMetadata("")); + + + /// + /// Gets whether the tab item is a contextual tab item. + /// This is a dependency property. + /// + public bool IsContextual + { + get { return (bool)GetValue(IsContextualProperty); } + internal set { SetValue(IsContextualPropertyKey, value); } + } + + internal RibbonContextualTabSet tabSet; + + // Using a DependencyProperty as the backing store for IsContextual. This enables animation, styling, binding, etc... + private static readonly DependencyPropertyKey IsContextualPropertyKey = + DependencyProperty.RegisterReadOnly("IsContextual", typeof(bool), typeof(RibbonTabItem), new UIPropertyMetadata(false)); + + public static DependencyProperty IsContextualProperty = IsContextualPropertyKey.DependencyProperty; + + + /// + /// Gets whether the tab item is selected. + /// This is a dependency propert. + /// + public bool IsSelected + { + get { return (bool)GetValue(IsSelectedProperty); } + internal set { SetValue(IsSelectedPropertyKey, value); } + } + + // Using a DependencyProperty as the backing store for IsSelected. This enables animation, styling, binding, etc... + private static readonly DependencyPropertyKey IsSelectedPropertyKey = + DependencyProperty.RegisterReadOnly("IsSelected", typeof(bool), typeof(RibbonTabItem), new UIPropertyMetadata(false, null, CoerceIsSelected)); + + public static object CoerceIsSelected(DependencyObject d, object baseValue) + { + RibbonTabItem item = (RibbonTabItem)d; + if (item.RibbonBar.CanMinimize && !item.RibbonBar.IsExpanded) + { + return false; + } + return baseValue; + } + + public static readonly DependencyProperty IsSelectedProperty = IsSelectedPropertyKey.DependencyProperty; + + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + if (RibbonBar != null) RibbonBar.SelectedTabItem = this; + e.Handled = true; + } + + private RibbonBar ribbon; + + /// + /// Gets the RibbonBar to which this tab item is added. + /// + public RibbonBar RibbonBar + { + get { return ribbon; } + internal set { ribbon = value; } + } + + protected override void OnMouseDoubleClick(MouseButtonEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed) + { + RibbonBar.IsExpanded = false; + RibbonBar.CanMinimize ^= true; + e.Handled = true; + } + base.OnMouseDoubleClick(e); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (!e.Handled) + { + switch (e.Key) + { + case Key.Left: + SelectPreviousTab(); + e.Handled = true; + break; + + case Key.Right: + SelectNextTab(); + e.Handled = true; + break; + } + } + base.OnKeyDown(e); + if (e.Key == Key.Space) RibbonBar.SelectedTabItem = this; + } + + private StringCollection reductionOrder; + + /// + /// Gets or sets the StringCollection with Name of each group to be reducted in that order. + /// + [TypeConverter(typeof(RibbonGroupReductionOrderConverter))] + public StringCollection ReductionOrder + { + get { return reductionOrder; } + set { reductionOrder = value; } + } + + + /// + /// Gets the color for the TabItem. + /// + public Color Color + { + get { return tabSet != null ? tabSet.Color : Colors.Transparent; } + } + + + private void SelectNextTab() + { + if (IsKeyboardFocused) + { + int index = ribbon.IndexOfVisibleTab(ribbon.SelectedTabItem) + 1; + RibbonTabItem tab = ribbon.VisibleTabFromIndex(index); + if (tab != null) + { + ribbon.SelectedTabItem = tab; + FocusSelectedTab(); + } + } + } + + private void FocusSelectedTab() + { + RibbonTabItem tab = ribbon.SelectedTabItem; + if (tab != null) tab.Focus(); + } + + private void SelectPreviousTab() + { + if (IsKeyboardFocused) + { + int index = ribbon.IndexOfVisibleTab(ribbon.SelectedTabItem) - 1; + RibbonTabItem tab = ribbon.VisibleTabFromIndex(index); + if (tab != null) + { + ribbon.SelectedTabItem = tab; + FocusSelectedTab(); + } + } + } + + + + #region IKeyboardCommand Members + + void IKeyTipControl.ExecuteKeyTip() + { + Focus(); + ribbon.SelectedTabItem = this; + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonTabItemPanel.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonTabItemPanel.cs new file mode 100644 index 0000000..48e360c --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonTabItemPanel.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Media; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + /// + /// Uses to layout tab items and paint the separator if necassary. + /// + internal class RibbonTabItemPanel : Panel + { + + static Size infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + + private double scaleX; + + protected override Size MeasureOverride(Size availableSize) + { + double width = 0; + double height = 0; + foreach (UIElement e in VisibleChildren) + { + e.Measure(infiniteSize); + height = Math.Max(height, e.DesiredSize.Height); + width += e.DesiredSize.Width; + } + + width = Math.Min(width, availableSize.Width); + double scaleX = CalculateScaleX(new Size(width, availableSize.Height)); + + if (this.scaleX != scaleX) InvalidateVisual(); + this.scaleX = scaleX; + if (scaleX < 1) + { + width = 0; + foreach (UIElement e in VisibleChildren) + { + e.Measure(new Size(e.DesiredSize.Width * scaleX, e.DesiredSize.Height)); + width += e.DesiredSize.Width; + } + } + return new Size(width, height); + } + + protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) + { + double scaleX = CalculateScaleX(finalSize); + + double x = 0; + double height = finalSize.Height; + foreach (UIElement e in VisibleChildren) + { + double w = e.DesiredSize.Width * scaleX; + e.Arrange(new Rect(x, 0.0, w, height)); + x += w; + } + return new Size(x, height); + } + + + IEnumerable VisibleChildren + { + get + { + foreach (UIElement e in Children) + { + if (e!=null && e.Visibility == Visibility.Visible) yield return e; + } + } + } + + private double GetMaxWidth() + { + double width = 0; + foreach (UIElement e in VisibleChildren) + { + width += e.DesiredSize.Width; + } + return width; + } + + double CalculateScaleX(Size finalSize) + { + double MaxWidth = GetMaxWidth(); + double scaleX = MaxWidth > 0 ? Math.Min(1, finalSize.Width / MaxWidth) : 1.0; + if (double.IsPositiveInfinity(scaleX)) scaleX = 1.0; + return scaleX; + } + + protected override void OnRender(System.Windows.Media.DrawingContext dc) + { + base.OnRender(dc); + double scaleX = this.scaleX; + if (scaleX < 1d) + { + Brush brush = SeparatorBrush.Clone(); + brush.Opacity = 1d - scaleX * 0.8d; + Pen pen = new Pen(brush, 1); + + double x = 0; + foreach (UIElement e in Children) + { + if (x > 0) + { + dc.DrawLine(pen, new Point(x, 2), new Point(x, ActualHeight - 8)); + } + x += e.DesiredSize.Width; + } + } + } + + + + /// + /// Gets or sets the brush that is used to paint the separators between each tab item if + /// the available width is too small. + /// + public Brush SeparatorBrush + { + get { return (Brush)GetValue(SeparatorBrushProperty); } + set { SetValue(SeparatorBrushProperty, value); } + } + + // Using a DependencyProperty as the backing store for SeparatorBrush. This enables animation, styling, binding, etc... + public static readonly DependencyProperty SeparatorBrushProperty = + DependencyProperty.Register("SeparatorBrush", typeof(Brush), typeof(RibbonTabItemPanel), new UIPropertyMetadata(new SolidColorBrush(Colors.SlateBlue))); + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonTabScroller.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonTabScroller.cs new file mode 100644 index 0000000..87d68e2 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonTabScroller.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Media; +using System.Windows.Input; +using System.Windows.Media.Animation; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + /// + /// used to scroll a RibbonTab on demand if it does not fit the available width. + /// + [TemplatePart(Name = partScrollViewer)] + internal class RibbonTabScroller : ContentControl + { + const string partScrollViewer = "PART_ScrollViewer"; + + static RibbonTabScroller() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonTabScroller), new FrameworkPropertyMetadata(typeof(RibbonTabScroller))); + RegisterCommands(); + } + + + private static void RegisterCommands() + { + CommandManager.RegisterClassCommandBinding(typeof(RibbonTabScroller), new CommandBinding(scrollLeftCommand, ScrollLeftExecute)); + CommandManager.RegisterClassCommandBinding(typeof(RibbonTabScroller), new CommandBinding(scrollRightCommand, ScrollRightExecute)); + } + + private static void ScrollLeftExecute(object sender, ExecutedRoutedEventArgs e) + { + RibbonTabScroller scroller = (RibbonTabScroller)sender; + scroller.Alignment = RibbonBarAlignment.Left; + } + + + private static void ScrollRightExecute(object sender, ExecutedRoutedEventArgs e) + { + RibbonTabScroller scroller = (RibbonTabScroller)sender; + scroller.Alignment = RibbonBarAlignment.Right; + } + + + private static RoutedUICommand scrollLeftCommand = new RoutedUICommand("Scroll Left", "ScrollLeftCommand", typeof(RibbonTabScroller)); + private static RoutedUICommand scrollRightCommand = new RoutedUICommand("Scroll Right", "ScrollRightCommand", typeof(RibbonTabScroller)); + + public static RoutedUICommand ScrollLeftCommand { get { return scrollLeftCommand; } } + public static RoutedUICommand ScrollRightCommand { get { return scrollRightCommand; } } + + + + private ScrollViewer scrollViewer; + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + scrollViewer = GetTemplateChild(partScrollViewer) as ScrollViewer; + } + + + + public RibbonBarAlignment Alignment + { + get { return (RibbonBarAlignment)GetValue(AlignmentProperty); } + set { SetValue(AlignmentProperty, value); } + } + + + + public bool CanAnimate + { + get { return (bool)GetValue(CanAnimateProperty); } + set { SetValue(CanAnimateProperty, value); } + } + + // Using a DependencyProperty as the backing store for CanAnimate. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CanAnimateProperty = + DependencyProperty.Register("CanAnimate", typeof(bool), typeof(RibbonTabScroller), new UIPropertyMetadata(true)); + + + + private void SetOffset(double offset) + { + if (scrollViewer != null) + { + scrollViewer.ScrollToHorizontalOffset(offset); + } + } + + + public double Offset + { + get { return (double)GetValue(OffsetProperty); } + set { SetValue(OffsetProperty, value); } + } + + + // Using a DependencyProperty as the backing store for Offset. This enables animation, styling, binding, etc... + public static readonly DependencyProperty OffsetProperty = + DependencyProperty.Register("Offset", typeof(double), typeof(RibbonTabScroller), + new UIPropertyMetadata(0.0, OffsetPropertyChanged)); + + public static void OffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + RibbonTabScroller scroller = (RibbonTabScroller)d; + double offset = (double)e.NewValue; + scroller.SetOffset(offset); + + } + + + // Using a DependencyProperty as the backing store for Alignment. This enables animation, styling, binding, etc... + public static readonly DependencyProperty AlignmentProperty = + DependencyProperty.Register("Alignment", typeof(RibbonBarAlignment), typeof(RibbonTabScroller), + new UIPropertyMetadata(RibbonBarAlignment.Full, AlignmentPropertyChanged)); + + public static void AlignmentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + RibbonTabScroller scroller = (RibbonTabScroller)d; + RibbonBarAlignment alignment = (RibbonBarAlignment)e.NewValue; + + switch(alignment) + { + case RibbonBarAlignment.Right: + scroller.ScrollRight(); + break; + + default: + scroller.ScrollLeft(); + break; + } + + scroller.InvalidateMeasure(); + scroller.InvalidateArrange(); + + } + + + + public Color Color + { + get { return (Color)GetValue(ColorProperty); } + set { SetValue(ColorProperty, value); } + } + + // Using a DependencyProperty as the backing store for Color. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ColorProperty = + DependencyProperty.Register("Color", typeof(Color), typeof(RibbonTabScroller), new UIPropertyMetadata(Colors.Transparent, ColorPropertyChanged)); + + public static void ColorPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonTabScroller ts = (RibbonTabScroller)o; + Color color = (Color)e.NewValue; + ts.IsColorized = color != Colors.Transparent; + ts.OnColorPopertyChanged(e); + } + + protected virtual void OnColorPopertyChanged(DependencyPropertyChangedEventArgs e) + { + + } + + + + + public bool IsColorized + { + get { return (bool)GetValue(IsColorizedProperty); } + set { SetValue(IsColorizedProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsColorized. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsColorizedProperty = + DependencyProperty.Register("IsColorized", typeof(bool), typeof(RibbonTabScroller), new UIPropertyMetadata(false)); + + + + private void ScrollLeft() + { + MoveTo(0.0); + } + + private void MoveTo(double offset) + { + if (CanAnimate) + { + DoubleAnimation a = new DoubleAnimation(offset, new Duration(TimeSpan.FromMilliseconds(250))); + a.DecelerationRatio = 1.0; + BeginAnimation(OffsetProperty, a); + } + else + { + Offset = offset; + } + } + + private void ScrollRight() + { + if (scrollViewer!=null) + { + double w = Math.Max(0, scrollViewer.ExtentWidth - scrollViewer.ActualWidth); + MoveTo(w); + } + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonTextBox.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonTextBox.cs new file mode 100644 index 0000000..4c13e14 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonTextBox.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Media; +using System.Windows.Controls.Primitives; +using Odyssey.Controls.Ribbon.Interfaces; +using System.Diagnostics; +using System.Windows.Input; +using Odyssey.Controls.Interfaces; + + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public class RibbonTextBox : TextBox, IRibbonControl, IRibbonStretch,IKeyTipControl + { + + static RibbonTextBox() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonTextBox), new FrameworkPropertyMetadata(typeof(RibbonTextBox))); + KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(RibbonTextBox), new FrameworkPropertyMetadata(KeyboardNavigationMode.Local)); + KeyboardNavigation.ControlTabNavigationProperty.OverrideMetadata(typeof(RibbonTextBox), new FrameworkPropertyMetadata(KeyboardNavigationMode.None)); + KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(RibbonTextBox), new FrameworkPropertyMetadata(KeyboardNavigationMode.None)); + + } + + + + /// + /// Gets or sets the image of the textbox. + /// + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for Image. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(ImageSource), typeof(RibbonTextBox), new UIPropertyMetadata(null)); + + + /// + /// Gets or sets the title of the textbox that appears with Appearance = Medium. + /// + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + // Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(string), typeof(RibbonTextBox), new UIPropertyMetadata("")); + + + + + /// + /// Gets or sets the width for the label. + /// + public double LabelWidth + { + get { return (double)GetValue(LabelWidthProperty); } + set { SetValue(LabelWidthProperty, value); } + } + + public static readonly DependencyProperty LabelWidthProperty = + DependencyProperty.Register("LabelWidth", typeof(double), typeof(RibbonTextBox), new UIPropertyMetadata(double.NaN)); + + + + /// + /// Gets or sets the with for the textbox. + /// This is a dependency property. + /// + public double ContentWidth + { + get { return (double)GetValue(ContentWidthProperty); } + set { SetValue(ContentWidthProperty, value); } + } + + // Using a DependencyProperty as the backing store for ContentWidth. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ContentWidthProperty = + DependencyProperty.Register("ContentWidth", typeof(double), typeof(RibbonTextBox), new UIPropertyMetadata(double.NaN)); + + + #region IKeyboardCommand Members + + public void ExecuteKeyTip() + { + Focus(); + SelectAll(); + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonThumbnail.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonThumbnail.cs new file mode 100644 index 0000000..392149e --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonThumbnail.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + /// + /// Represents an item container for a RibbonGallery. + /// + public class RibbonThumbnail:ListBoxItem + { + static RibbonThumbnail() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonThumbnail), new FrameworkPropertyMetadata(typeof(RibbonThumbnail))); + } + + public ImageSource ImageSource + { + get { return (ImageSource)GetValue(ImageSourceProperty); } + set { SetValue(ImageSourceProperty, value); } + } + + // Using a DependencyProperty as the backing store for ImageSource. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ImageSourceProperty = + DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(RibbonThumbnail), new UIPropertyMetadata(null)); + + + public RibbonThumbnail Original { get; internal set; } + + protected override void OnMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + Select(); + e.Handled = true; + } + + private void Select() + { + RibbonThumbnail original = this.Original != null ? Original : this; + if (Gallery != null) Gallery.SelectedItem = original; + } + + private RibbonGallery Gallery + { + get { + RibbonGallery gallery = Parent as RibbonGallery; + if (gallery == null) + { + FrameworkElement e = Parent as FrameworkElement; + gallery = e != null ? e.TemplatedParent as RibbonGallery : null; + } + return gallery; + } + } + + protected override void OnMouseLeftButtonUp(System.Windows.Input.MouseButtonEventArgs e) + { + base.OnMouseLeftButtonUp(e); + + // also close the poupup when the item is already selected: + if (this.IsFocused) + { + RibbonGallery g = Gallery; + if (g != null) g.IsDropDownOpen = false; + } + } + + protected override void OnMouseEnter(System.Windows.Input.MouseEventArgs e) + { + base.OnMouseEnter(e); + RibbonThumbnail thumb = Original != null ? Original : this; + if (Gallery != null) Gallery.HotThumbnail = thumb; + } + + protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e) + { + base.OnMouseLeave(e); + if (Gallery != null) Gallery.HotThumbnail = null; + } + + + protected override void OnKeyUp(System.Windows.Input.KeyEventArgs e) + { + if (!e.Handled) + { + switch (e.Key) + { + case System.Windows.Input.Key.Enter: + case System.Windows.Input.Key.Space: + Select(); + break; + } + } + base.OnKeyUp(e); + } + + public Stretch Stretch + { + get { return (Stretch)GetValue(StretchProperty); } + set { SetValue(StretchProperty, value); } + } + + // Using a DependencyProperty as the backing store for Stretch. This enables animation, styling, binding, etc... + public static readonly DependencyProperty StretchProperty = + DependencyProperty.Register("Stretch", typeof(Stretch), typeof(RibbonThumbnail), new UIPropertyMetadata(Stretch.Fill)); + + + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonToggleButton.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonToggleButton.cs new file mode 100644 index 0000000..edafc3c --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonToggleButton.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Controls.Primitives; +using Odyssey.Controls.Ribbon.Interfaces; +using Odyssey.Controls.Interfaces; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + + public class RibbonToggleButton : ToggleButton,IRibbonButton,IKeyTipControl + { + static RibbonToggleButton() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonToggleButton), new FrameworkPropertyMetadata(typeof(RibbonToggleButton))); + } + + + + public bool IsFlat + { + get { return (bool)GetValue(IsFlatProperty); } + set { SetValue(IsFlatProperty, value); } + } + + // Using a DependencyProperty as the backing store for IsFlat. This enables animation, styling, binding, etc... + public static readonly DependencyProperty IsFlatProperty = + DependencyProperty.Register("IsFlat", typeof(bool), typeof(RibbonToggleButton), new UIPropertyMetadata(true)); + + + public ImageSource LargeImage + { + get { return (ImageSource)GetValue(LargeImageProperty); } + set { SetValue(LargeImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for LargeImage. This enables animation, styling, binding, etc... + public static readonly DependencyProperty LargeImageProperty = + DependencyProperty.Register("LargeImage", typeof(ImageSource), typeof(RibbonToggleButton), new UIPropertyMetadata(null)); + + + + public ImageSource SmallImage + { + get { return (ImageSource)GetValue(SmallImageProperty); } + set { SetValue(SmallImageProperty, value); } + } + + // Using a DependencyProperty as the backing store for SmallImage. This enables animation, styling, binding, etc... + public static readonly DependencyProperty SmallImageProperty = + DependencyProperty.Register("SmallImage", typeof(ImageSource), typeof(RibbonToggleButton), new UIPropertyMetadata(null)); + + + + public CornerRadius CornerRadius + { + get { return (CornerRadius)GetValue(CornerRadiusProperty); } + set { SetValue(CornerRadiusProperty, value); } + } + + // Using a DependencyProperty as the backing store for CornerRadius. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CornerRadiusProperty = + DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(RibbonToggleButton), new UIPropertyMetadata(new CornerRadius(4))); + + + #region IKeyboardCommand Members + + public void ExecuteKeyTip() + { + OnClick(); + } + + #endregion + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonToolTip.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonToolTip.cs new file mode 100644 index 0000000..d35846b --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonToolTip.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Controls.Primitives; + +namespace Odyssey.Controls +{ + /// + /// Enhanced tooltip with additional Properties that appears below the ribbon bar. + /// + public class RibbonToolTip : ToolTip + { + static RibbonToolTip() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonToolTip), new FrameworkPropertyMetadata(typeof(RibbonToolTip))); + } + + public RibbonToolTip() + : base() + { + Placement = System.Windows.Controls.Primitives.PlacementMode.Custom; + CustomPopupPlacementCallback = new System.Windows.Controls.Primitives.CustomPopupPlacementCallback(PopupPlacement); + } + + /// + /// Ensure the position of the tooltip to be exactly below the if available, otherwise use default positioning. + /// + private CustomPopupPlacement[] PopupPlacement(Size popupSize, Size targetSize, Point offset) + { + RibbonBar ribbon = GetRibbonBar(); + double y = ActualHeight; + double x = 0.0d; + + if (ribbon != null) + { + FrameworkElement owner = this.PlacementTarget as FrameworkElement; + if (!(owner is RibbonApplicationMenu)) + { + if (owner.IsDescendantOf(ribbon)) + { + Point p = ribbon.TranslatePoint(new Point(), owner); + y = ribbon.ActualHeight + p.Y; + } + else + { + Popup popup = ribbon.Popup; + FrameworkElement child = popup.Child as FrameworkElement; + if (child != null) + { + Point p = child.TranslatePoint(new Point(), owner); + y = child.ActualHeight + p.Y; + } + } + } + else + { + y = owner.ActualHeight + 4.0d; + x = 0d; + } + } + + CustomPopupPlacement placement = new CustomPopupPlacement(new Point(x, y), PopupPrimaryAxis.Vertical); + return new CustomPopupPlacement[] { placement }; + } + + private RibbonBar GetRibbonBar() + { + FrameworkElement parent = this.PlacementTarget as FrameworkElement; + while (parent != null && !(parent is RibbonBar)) + { + parent = parent.Parent != null ? parent.Parent as FrameworkElement : parent.TemplatedParent as FrameworkElement; + } + return parent as RibbonBar; + } + + + /// + /// Gets or sets the title for the tooltip. + /// This is a dependency property. + /// + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(string), typeof(RibbonToolTip), new UIPropertyMetadata(null)); + + + + /// + /// Gets or sets the footer for the tooltip. + /// This is a dependency property. + /// + public string Footer + { + get { return (string)GetValue(FooterProperty); } + set { SetValue(FooterProperty, value); } + } + + public static readonly DependencyProperty FooterProperty = + DependencyProperty.Register("Footer", typeof(string), typeof(RibbonToolTip), new UIPropertyMetadata(null)); + + + + /// + /// Gets or sets the description for the tooltip. + /// This is a dependency property. + /// + public string Description + { + get { return (string)GetValue(DescriptionProperty); } + set { SetValue(DescriptionProperty, value); } + } + + public static readonly DependencyProperty DescriptionProperty = + DependencyProperty.Register("Description", typeof(string), typeof(RibbonToolTip), new UIPropertyMetadata(null)); + + + /// + /// Gets or sets the Image for the tooltip. + /// This is a dependency property. + /// + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(ImageSource), typeof(RibbonToolTip), new UIPropertyMetadata(null)); + + + /// + /// Gets or sets the Image for the footer of the tooltip. + /// This is a dependency property. + /// + public ImageSource FooterImage + { + get { return (ImageSource)GetValue(FooterImageProperty); } + set { SetValue(FooterImageProperty, value); } + } + + public static readonly DependencyProperty FooterImageProperty = + DependencyProperty.Register("FooterImage", typeof(ImageSource), typeof(RibbonToolTip), new UIPropertyMetadata(null)); + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonWindow.Commands.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonWindow.Commands.cs new file mode 100644 index 0000000..acef6a9 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonWindow.Commands.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Input; +using System.Windows; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public partial class RibbonWindow + { + public static readonly RoutedUICommand CloseCommand = new RoutedUICommand("Close", "CloseCommand", typeof(RibbonWindow)); + public static readonly RoutedUICommand MinimizeCommand = new RoutedUICommand("Minimize", "MinimizeCommand", typeof(RibbonWindow)); + public static readonly RoutedUICommand MaximizeCommand = new RoutedUICommand("Maximize", "MaximizeCommand", typeof(RibbonWindow)); + + private static void RegisterCommands() + { + CommandManager.RegisterClassCommandBinding(typeof(RibbonWindow), new CommandBinding(CloseCommand, PerformClose)); + CommandManager.RegisterClassCommandBinding(typeof(RibbonWindow), new CommandBinding(MinimizeCommand, PerformMinimize)); + CommandManager.RegisterClassCommandBinding(typeof(RibbonWindow), new CommandBinding(MaximizeCommand, PerformMaximize)); + + } + + + private static void PerformClose(object sender, ExecutedRoutedEventArgs e) + { + RibbonWindow window = (RibbonWindow)sender; + window.Close(); + } + + private static void PerformMinimize(object sender, ExecutedRoutedEventArgs e) + { + RibbonWindow window = (RibbonWindow)sender; + window.WindowState = WindowState.Minimized; + } + + private static void PerformMaximize(object sender, ExecutedRoutedEventArgs e) + { + RibbonWindow window = (RibbonWindow)sender; + window.WindowState = window.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; + } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonWindow.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonWindow.cs new file mode 100644 index 0000000..d8045f4 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonWindow.cs @@ -0,0 +1,462 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Interop; +using Odyssey.Native; +using System.Windows.Media; +using System.Diagnostics; +using System.Windows.Controls; +using Odyssey.Controls.Classes; +using System.Runtime.InteropServices; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public partial class RibbonWindow : Window + { + const string partOuterBorder = "PART_OuterBorder"; + + static RibbonWindow() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonWindow), new FrameworkPropertyMetadata(typeof(RibbonWindow))); + } + + public RibbonWindow() + : base() + { + SizeChanged += new SizeChangedEventHandler(OnSizeChanged); + HookWndProc(); + RegisterCommands(); + SkinManager.SkinChanged += new EventHandler(OnSkinChanged); + } + + protected virtual void OnSkinChanged(object sender, EventArgs e) + { + SetWindowTitleBrush(); + } + + protected override void OnSourceInitialized(EventArgs e) + { + base.OnSourceInitialized(e); + SetGlassOn(); + } + + /// + /// Gets whether glass is available. + /// + public bool IsGlassAvailable + { + get + { + if (Environment.OSVersion.Version.Major >= 6) + { + if (NativeMethods.DwmIsCompositionEnabled()) return true; + } + return false; + } + } + + private void SetGlassOn() + { + if (IsGlassAvailable) + { + IntPtr hwnd = new WindowInteropHelper(this).Handle; + HwndSource src = HwndSource.FromHwnd(hwnd); + + // settings the following value is necassary to have a transparent glass background instead of black: + src.CompositionTarget.BackgroundColor = Color.FromArgb(0, 0, 0, 0); + + const int GlassBorderSize = 8; + const int GlassTitleBorderHeight = 31; + + NativeMethods.MARGINS margins = new NativeMethods.MARGINS(); + margins.cxLeftWidth = GlassBorderSize; + margins.cxRightWidth = GlassBorderSize; + margins.cyTopHeight = GlassTitleBorderHeight; + margins.cyBottomHeight = GlassBorderSize; + NativeMethods.DwmExtendFrameIntoClientArea(hwnd, ref margins); + } + } + + + private void HookWndProc() + { + EventHandler handler = delegate(object sender, EventArgs e) + { + ((HwndSource)PresentationSource.FromVisual(this)).AddHook(new HwndSourceHook(this.WndProc)); + }; + base.SourceInitialized += handler; + } + + protected override Size ArrangeOverride(Size arrangeBounds) + { + VerifyAccess(); + UIElement child = this.VisualChildrenCount > 0 ? GetVisualChild(0) as UIElement : null; + if (child != null) + { + Size size = new Size(arrangeBounds.Width, arrangeBounds.Height); + + child.Arrange(new Rect(size)); + return size; + + } + return arrangeBounds; + } + + protected override Size MeasureOverride(Size availableSize) + { + if (VisualChildrenCount > 0) + { + UIElement visualChild = GetVisualChild(0) as UIElement; + if (visualChild != null) + { + visualChild.Measure(availableSize); + return visualChild.DesiredSize; + } + } + return base.MeasureOverride(availableSize); + } + + + /// + /// Gets or sets whether Glass is enabled for Vista Aero. This does not necassarily mean that glass is applied but only when the conditions for glass match. + /// This is a dependency property. + /// + public bool IsGlassEnabled + { + get { return (bool)GetValue(IsGlassEnabledProperty); } + set { SetValue(IsGlassEnabledProperty, value); } + } + + public static readonly DependencyProperty IsGlassEnabledProperty = + DependencyProperty.Register("IsGlassEnabled", typeof(bool), typeof(RibbonWindow), + new FrameworkPropertyMetadata(false, GlassEnabledPropertyChanged)); + + + + + /// + /// Gets whether Glass is on. + /// This is a dependency property. + /// + public bool IsGlassOn + { + get { return (bool)GetValue(IsGlassOnProperty); } + private set { SetValue(IsGlassOnPropertyKey, value); } + } + + // Using a DependencyProperty as the backing store for IsGlassOn. This enables animation, styling, binding, etc... + private static readonly DependencyPropertyKey IsGlassOnPropertyKey = + DependencyProperty.RegisterReadOnly("IsGlassOn", typeof(bool), typeof(RibbonWindow), new UIPropertyMetadata(false, GlassOnPropertyChanged)); + + public static DependencyProperty IsGlassOnProperty = IsGlassOnPropertyKey.DependencyProperty; + + static void GlassOnPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonWindow w = (RibbonWindow)o; + w.SetWindowTitleBrush(); + } + + static void GlassEnabledPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonWindow w = (RibbonWindow)o; + w.SetIsGlassOnState(); + } + + private void SetIsGlassOnState() + { + IsGlassOn = IsGlassEnabled && (Environment.OSVersion.Version.Major >= 6) && NativeMethods.DwmIsCompositionEnabled(); + AttachRegion(); + } + + + + protected virtual void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + AttachRegion(); + SetWindowTitleBrush(); + } + + + + private void AttachRegion() + { + if (!IsGlassOn) + { + NativeMethods.RECT rect; + IntPtr hwnd = new WindowInteropHelper(this).Handle; + NativeMethods.GetWindowRect(hwnd, out rect); + int w = rect.Width + 1; + int h = rect.Height + 1; + if (WindowState != WindowState.Maximized && RoundedCornerMode != RibbonWindowCornerMode.None) + { + // note: the last two parameters are the diameter, not the radius: + IntPtr rgn = NativeMethods.CreateRoundRectRgn(0, 0, w, h, 12, 12); + if (RoundedCornerMode == RibbonWindowCornerMode.Top) + { + IntPtr rgn2 = NativeMethods.CreateRectRgn(0, 6, w, h); + NativeMethods.CombineRgn(rgn, rgn2, rgn, 2); + } + NativeMethods.SetWindowRgn(hwnd, rgn, NativeMethods.IsWindowVisible(hwnd)); + } + else + { + IntPtr rgn = NativeMethods.CreateRectRgn(0, 0, w, h); + NativeMethods.SetWindowRgn(hwnd, rgn, NativeMethods.IsWindowVisible(hwnd)); + } + } + else + { + IntPtr hwnd = new WindowInteropHelper(this).Handle; + NativeMethods.SetWindowRgn(hwnd, IntPtr.Zero, NativeMethods.IsWindowVisible(hwnd)); + } + } + + /// + /// Handles the WndProc events to enable drawing inside the title bar. + /// + protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + IntPtr result = IntPtr.Zero; + NativeMethods.WM wm = (NativeMethods.WM)msg; + if (Environment.OSVersion.Version.Major >= 6) + { + handled = NativeMethods.DwmDefWindowProc(hwnd, msg, wParam, lParam, out result); + } + if (!handled) + switch (wm) + { + + case NativeMethods.WM.SIZE: + handled = false; + break; + + //TODO: This causes a uncontrolled moving of the window in windows 7 while dragging from WindowState.Maximized. + // don't paint the border and title: + case NativeMethods.WM.NCCALCSIZE: + handled = true; + break; + + case NativeMethods.WM.SETICON: + handled = true; + return IntPtr.Zero; + + case NativeMethods.WM.SETTEXT: + handled = true; + InvalidateArrange(); + break; + + + case NativeMethods.WM.NCACTIVATE: + IsWindowActive = wParam.ToInt32() == 1; + handled = true; + result = NativeMethods.DefWindowProc(hwnd, NativeMethods.WM.NCACTIVATE, wParam, new IntPtr(-1)); + break; + + // determine if the titlebar, or any of the borders is under the cursor position coded in m.lParam: + case NativeMethods.WM.NCHITTEST: + if (result == IntPtr.Zero) + { + WndProcHitTest(hwnd, lParam, ref handled, ref result); + } + break; + + + //TODO: changing the DWMCOMPOSITION currently causes a "This freezable cannot be frozen" exception. + case NativeMethods.WM.DWMCOMPOSITIONCHANGED: + SetIsGlassOnState(); + AttachRegion(); + InvalidateVisual(); + UpdateLayout(); + handled = true; + result = IntPtr.Zero; + break; + + } + + return result; + } + + + /// + /// Determine if any of the window borders or the titlebar is hit: + /// + private void WndProcHitTest(IntPtr hwnd, IntPtr lParam, ref bool handled, ref IntPtr result) + { + int xy = lParam.ToInt32(); + Point p = new Point(NativeMethods.SignedLoWord(xy), NativeMethods.SignedHiWord(xy)); + NativeMethods.RECT rect = new NativeMethods.RECT(); + NativeMethods.GetWindowRect(hwnd, out rect); + Rect windowRect = new Rect(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top); + NativeMethods.HT ht = NCHitTest(p, windowRect); + result = new IntPtr((int)ht); + handled = ht != NativeMethods.HT.NOWHERE; + } + + + private NativeMethods.HT NCHitTest(Point p, Rect rect) + { + const double borderSize = 6.0; + const double titleHeight = 34.0; + + if (p.Y < rect.Top + borderSize) + { + if (p.X < rect.Left + borderSize) return NativeMethods.HT.TOPLEFT; + if (p.X > rect.Right - borderSize) return NativeMethods.HT.TOPRIGHT; + return NativeMethods.HT.TOP; + } + if (p.Y > rect.Bottom - borderSize) + { + if (p.X < rect.Left + borderSize) return NativeMethods.HT.BOTTOMLEFT; + if (p.X > rect.Right - borderSize) return NativeMethods.HT.BOTTOMRIGHT; + return NativeMethods.HT.BOTTOM; + } + if (p.X < rect.Left + borderSize) return NativeMethods.HT.LEFT; + if (p.X > rect.Right - borderSize) return NativeMethods.HT.RIGHT; + if (p.Y < rect.Top + titleHeight) + { + Point localPoint = ScreenToLocal(p); + IInputElement e = InputHitTest(localPoint); + if (e == null) + { + return NativeMethods.HT.CAPTION; + } + else + { + if (e == outerBorder) return NativeMethods.HT.CAPTION; + UIElement ue = e as UIElement; + if (ue == null || !ue.IsHitTestVisible) return NativeMethods.HT.CAPTION; + } + } + + return NativeMethods.HT.NOWHERE; + } + + private Point ScreenToLocal(Point point) + { + NativeMethods.RECT rect; + NativeMethods.GetWindowRect(new WindowInteropHelper(this).Handle, out rect); + Matrix matrix = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice; + point.Offset((double)(-1 * rect.Left), (double)(-1 * rect.Top)); + point.X *= matrix.M11; + point.Y *= matrix.M22; + return point; + } + + private DependencyObject outerBorder; + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + outerBorder = GetTemplateChild(partOuterBorder); + } + + + /// + /// Gets whether the window is active. + /// This is a dependency property. + /// + public bool IsWindowActive + { + get { return (bool)GetValue(IsWindowActiveProperty); } + private set { SetValue(IsWindowActivePropertyKey, value); } + } + + // Using a DependencyProperty as the backing store for IsWindowActive. This enables animation, styling, binding, etc... + private static readonly DependencyPropertyKey IsWindowActivePropertyKey = + DependencyProperty.RegisterReadOnly("IsWindowActive", typeof(bool), typeof(RibbonWindow), + new FrameworkPropertyMetadata(false, + FrameworkPropertyMetadataOptions.AffectsRender, + WindowActivePropertyChanged)); + + public static readonly DependencyProperty IsWindowActiveProperty = IsWindowActivePropertyKey.DependencyProperty; + + static void WindowActivePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonWindow w = (RibbonWindow)o; + w.SetWindowTitleBrush(); + } + + void SetWindowTitleBrush() + { + if (IsGlassOn) + { + WindowTitleBrush = IsWindowActive ? SystemColors.ActiveCaptionTextBrush : SystemColors.InactiveCaptionTextBrush; + } + else + { + WindowTitleBrush = IsWindowActive ? ActiveTitleBrush : InactiveTitleBrush; + } + } + + + public RibbonWindowCornerMode RoundedCornerMode + { + get { return (RibbonWindowCornerMode)GetValue(RoundedCornerModeProperty); } + set { SetValue(RoundedCornerModeProperty, value); } + } + + // Using a DependencyProperty as the backing store for RoundedCornerMode. This enables animation, styling, binding, etc... + public static readonly DependencyProperty RoundedCornerModeProperty = + DependencyProperty.Register("RoundedCornerMode", typeof(RibbonWindowCornerMode), typeof(RibbonWindow), + new FrameworkPropertyMetadata(RibbonWindowCornerMode.Top, + FrameworkPropertyMetadataOptions.AffectsRender, + RoundedCornerModePropertyChanged)); + + + public static void RoundedCornerModePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + RibbonWindow window = (RibbonWindow)o; + window.AttachRegion(); + } + + + /// + /// Gets the Brush for the window title. + /// This is a dependency property. + /// + public Brush WindowTitleBrush + { + get { return (Brush)GetValue(WindowTitleBrushProperty); } + private set { SetValue(WindowTitleBrushPropertyKey, value); } + } + + private static readonly DependencyPropertyKey WindowTitleBrushPropertyKey = + DependencyProperty.RegisterReadOnly("WindowTitleBrush", typeof(Brush), typeof(RibbonWindow), new UIPropertyMetadata(null)); + + public static DependencyProperty WindowTitleBrushProperty = WindowTitleBrushPropertyKey.DependencyProperty; + + + + + public Brush ActiveTitleBrush + { + get { return (Brush)GetValue(ActiveTitleBrushProperty); } + set { SetValue(ActiveTitleBrushProperty, value); } + } + + public static readonly DependencyProperty ActiveTitleBrushProperty = + DependencyProperty.Register("ActiveTitleBrush", typeof(Brush), typeof(RibbonWindow), new UIPropertyMetadata(null)); + + + + + public Brush InactiveTitleBrush + { + get { return (Brush)GetValue(InactiveTitleBrushProperty); } + set { SetValue(InactiveTitleBrushProperty, value); } + } + + public static readonly DependencyProperty InactiveTitleBrushProperty = + DependencyProperty.Register("InactiveTitleBrush", typeof(Brush), typeof(RibbonWindow), new UIPropertyMetadata(null)); + + + + } +} diff --git a/Odyssey/Odyssey/Ribbon/Controls/RibbonWrapPanel.cs b/Odyssey/Odyssey/Ribbon/Controls/RibbonWrapPanel.cs new file mode 100644 index 0000000..44433d9 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Controls/RibbonWrapPanel.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Markup; +using System.Collections; +using System.Diagnostics; +using Odyssey.Controls.Ribbon.Interfaces; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls +{ + public class RibbonWrapPanel : Panel + { + static RibbonWrapPanel() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonWrapPanel), new FrameworkPropertyMetadata(typeof(RibbonWrapPanel))); + } + + + const double smallHeight = 24; + + + protected override Size MeasureOverride(Size availableSize) + { + foreach (UIElement e in Children) e.Measure(infiniteSize); + return ArrangeOrMeasure(false); + } + + + private static Size infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + + protected override Size ArrangeOverride(Size finalSize) + { + return ArrangeOrMeasure(true); + } + + private Size ArrangeOrMeasure(bool arrange) + { + double left = 0; + int rowIndex = 0; + int maxRows = 3; + + List rowElements = new List(maxRows); + + foreach (UIElement e in Children) + { + if (e.Visibility != Visibility.Visible) continue; + IRibbonControl ribbonControl = e as IRibbonControl; + Size dsize = e.DesiredSize; + if (dsize.Height > smallHeight) + { + if (rowIndex > 0) + { + left += ArrangeRow(rowElements, left, arrange); + rowIndex = 0; + } + if (arrange) + { + Size size = e.DesiredSize; + double h = Math.Max(smallHeight, size.Height); + e.Arrange(new Rect(left, 0, size.Width, h)); + } + left += e.DesiredSize.Width; + } + else + { + RibbonSize size = RibbonBar.GetSize(e); + if (size != RibbonSize.Minimized) + { + rowElements.Add(e); + if (++rowIndex == maxRows) + { + left += ArrangeRow(rowElements, left, arrange); + rowIndex = 0; + } + } + } + } + left += ArrangeRow(rowElements, left, arrange); + + left = Math.Max(32, left); + return new Size(left, smallHeight * 3); + } + + + protected double ArrangeRow(List rowElements, double left, bool arrange) + { + double max = 0; + + double rowHeight = smallHeight + (rowElements.Count == 2 ? (smallHeight / 3) : 0); + double topOffset = rowElements.Count == 2 ? smallHeight / 3 : 0; + + foreach (UIElement e in rowElements) + { + max = Math.Max(e.DesiredSize.Width, max); + } + foreach (UIElement e in rowElements) + { + if (arrange) + { + double h = Math.Max(smallHeight, e.DesiredSize.Height); + double w = e is IRibbonStretch ? max : e.DesiredSize.Width; + + FrameworkElement fe = e as FrameworkElement; + if (fe != null && fe.HorizontalAlignment != HorizontalAlignment.Left) + { + switch (fe.HorizontalAlignment) + { + case HorizontalAlignment.Right: + e.Arrange(new Rect(max - w + left, topOffset, w, h)); + break; + + case HorizontalAlignment.Center: + e.Arrange(new Rect((max - w) / 2 + left, topOffset, w, h)); + break; + + case HorizontalAlignment.Left: + case HorizontalAlignment.Stretch: + e.Arrange(new Rect(left, topOffset, w, h)); + break; + } + } + else e.Arrange(new Rect(left, topOffset, w, h)); + } + topOffset += rowHeight; + } + rowElements.Clear(); + return max; + } + + + } +} diff --git a/Odyssey/Odyssey/Ribbon/EventArgs/SelectedTabIndexChangedEvent.cs b/Odyssey/Odyssey/Ribbon/EventArgs/SelectedTabIndexChangedEvent.cs new file mode 100644 index 0000000..8a5ee79 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/EventArgs/SelectedTabIndexChangedEvent.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; + +namespace Odyssey.Ribbon.EventArgs +{ + public class SelectedTabIndexChangedEvent : RoutedPropertyChangedEventArgs + { + public SelectedTabIndexChangedEvent(int oldValue, int newValue, RoutedEvent routedEvent) + : base(oldValue, newValue, routedEvent) + { + } + } + + // public delegate void SelectedTabIndexChangedHandler(object sender, SelectedTabIndexChangedEvent e); +} diff --git a/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonButton.cs b/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonButton.cs new file mode 100644 index 0000000..ae6f0c3 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonButton.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Media; +using System.Windows; +using Odyssey.Controls.Interfaces; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls.Ribbon.Interfaces +{ + public interface IRibbonButton:IRibbonControl,IKeyTipControl + { + object Content { get; set; } + + ImageSource LargeImage { get; set; } + + ImageSource SmallImage { get; set; } + + CornerRadius CornerRadius { get; set; } + } +} diff --git a/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonControl.cs b/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonControl.cs new file mode 100644 index 0000000..e79713d --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonControl.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls.Ribbon.Interfaces +{ + public interface IRibbonControl + { + } +} diff --git a/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonGallery.cs b/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonGallery.cs new file mode 100644 index 0000000..71337a2 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonGallery.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls.Ribbon.Interfaces +{ + public interface IRibbonGallery:IRibbonControl + { + int Columns { get; set; } + bool IsCollapsed { get; set; } + int DropDownColumns { get; set; } + + void SetDropDownColumns(int columns); + } +} diff --git a/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonLargeControl.cs b/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonLargeControl.cs new file mode 100644 index 0000000..516676e --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonLargeControl.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls.Ribbon.Interfaces +{ + /// + /// Marks a control to always be large. + /// + public interface IRibbonLargeControl + { + } +} diff --git a/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonStretch.cs b/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonStretch.cs new file mode 100644 index 0000000..a38a000 --- /dev/null +++ b/Odyssey/Odyssey/Ribbon/Interfaces/IRibbonStretch.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#region Copyright +// Odyssey.Controls.Ribbonbar +// (c) copyright 2009 Thomas Gerber +// This source code and files, is licensed under The Microsoft Public License (Ms-PL) +#endregion +namespace Odyssey.Controls.Ribbon.Interfaces +{ + /// + /// Defines controls that are stretched horizontally in a RibbonWrapPanel. + /// + public interface IRibbonStretch + { + } +} diff --git a/Odyssey/Odyssey/Skins/BlackSkin.xaml b/Odyssey/Odyssey/Skins/BlackSkin.xaml new file mode 100644 index 0000000..d979aff --- /dev/null +++ b/Odyssey/Odyssey/Skins/BlackSkin.xaml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Skins/BlueSkin.xaml b/Odyssey/Odyssey/Skins/BlueSkin.xaml new file mode 100644 index 0000000..a6ad92e --- /dev/null +++ b/Odyssey/Odyssey/Skins/BlueSkin.xaml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Skins/OutlookBar/OutlookBlackSkin.xaml b/Odyssey/Odyssey/Skins/OutlookBar/OutlookBlackSkin.xaml new file mode 100644 index 0000000..5071f46 --- /dev/null +++ b/Odyssey/Odyssey/Skins/OutlookBar/OutlookBlackSkin.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Skins/OutlookBar/OutlookBlueSkin.xaml b/Odyssey/Odyssey/Skins/OutlookBar/OutlookBlueSkin.xaml new file mode 100644 index 0000000..b8d3e66 --- /dev/null +++ b/Odyssey/Odyssey/Skins/OutlookBar/OutlookBlueSkin.xaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Skins/OutlookBar/OutlookSilverSkin.xaml b/Odyssey/Odyssey/Skins/OutlookBar/OutlookSilverSkin.xaml new file mode 100644 index 0000000..524a605 --- /dev/null +++ b/Odyssey/Odyssey/Skins/OutlookBar/OutlookSilverSkin.xaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Skins/OutlookBar/Win7Skin.xaml b/Odyssey/Odyssey/Skins/OutlookBar/Win7Skin.xaml new file mode 100644 index 0000000..34ef156 --- /dev/null +++ b/Odyssey/Odyssey/Skins/OutlookBar/Win7Skin.xaml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Skins/Ribbon/OfficeBlackSkin.xaml b/Odyssey/Odyssey/Skins/Ribbon/OfficeBlackSkin.xaml new file mode 100644 index 0000000..b7418ca --- /dev/null +++ b/Odyssey/Odyssey/Skins/Ribbon/OfficeBlackSkin.xaml @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Skins/Ribbon/OfficeBlueSkin.xaml b/Odyssey/Odyssey/Skins/Ribbon/OfficeBlueSkin.xaml new file mode 100644 index 0000000..c43fb40 --- /dev/null +++ b/Odyssey/Odyssey/Skins/Ribbon/OfficeBlueSkin.xaml @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Skins/Ribbon/VistaSkin.xaml b/Odyssey/Odyssey/Skins/Ribbon/VistaSkin.xaml new file mode 100644 index 0000000..53a8657 --- /dev/null +++ b/Odyssey/Odyssey/Skins/Ribbon/VistaSkin.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Skins/Ribbon/Window7Skin.xaml b/Odyssey/Odyssey/Skins/Ribbon/Window7Skin.xaml new file mode 100644 index 0000000..235c62c --- /dev/null +++ b/Odyssey/Odyssey/Skins/Ribbon/Window7Skin.xaml @@ -0,0 +1,343 @@ + + + + + + + + + + 0,2,0,2 + 0 + 0 + + 1,2,3,0 + 0 + 0 + Collapsed + + + + 1,0,1,0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Skins/SilverSkin.xaml b/Odyssey/Odyssey/Skins/SilverSkin.xaml new file mode 100644 index 0000000..36044aa --- /dev/null +++ b/Odyssey/Odyssey/Skins/SilverSkin.xaml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Skins/VistaSkin.xaml b/Odyssey/Odyssey/Skins/VistaSkin.xaml new file mode 100644 index 0000000..ea9eb8a --- /dev/null +++ b/Odyssey/Odyssey/Skins/VistaSkin.xaml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Skins/Win7Skin.xaml b/Odyssey/Odyssey/Skins/Win7Skin.xaml new file mode 100644 index 0000000..a78678a --- /dev/null +++ b/Odyssey/Odyssey/Skins/Win7Skin.xaml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Aero.NormalColor.xaml b/Odyssey/Odyssey/Themes/Aero.NormalColor.xaml new file mode 100644 index 0000000..19b9d54 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Aero.NormalColor.xaml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/BreadcrumbBar/AeroChrome.xaml b/Odyssey/Odyssey/Themes/BreadcrumbBar/AeroChrome.xaml new file mode 100644 index 0000000..a9e5a5a --- /dev/null +++ b/Odyssey/Odyssey/Themes/BreadcrumbBar/AeroChrome.xaml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/BreadcrumbBar/BreadcrumbButton.xaml b/Odyssey/Odyssey/Themes/BreadcrumbBar/BreadcrumbButton.xaml new file mode 100644 index 0000000..9637919 --- /dev/null +++ b/Odyssey/Odyssey/Themes/BreadcrumbBar/BreadcrumbButton.xaml @@ -0,0 +1,168 @@ + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/BreadcrumbBar/BreadcrumbItem.xaml b/Odyssey/Odyssey/Themes/BreadcrumbBar/BreadcrumbItem.xaml new file mode 100644 index 0000000..6c6f703 --- /dev/null +++ b/Odyssey/Odyssey/Themes/BreadcrumbBar/BreadcrumbItem.xaml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/BreadcrumbBar/Brushes.xaml b/Odyssey/Odyssey/Themes/BreadcrumbBar/Brushes.xaml new file mode 100644 index 0000000..43cae9b --- /dev/null +++ b/Odyssey/Odyssey/Themes/BreadcrumbBar/Brushes.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + M0,0 L5,3.5 0,7 z + M0,3 L7,3 3.5,7 z + F1 M7,0 L4,3 7,6 M3,0 L0,3 3,6 + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/BreadcrumbBar/ButtonTemplates.xaml b/Odyssey/Odyssey/Themes/BreadcrumbBar/ButtonTemplates.xaml new file mode 100644 index 0000000..9711a07 --- /dev/null +++ b/Odyssey/Odyssey/Themes/BreadcrumbBar/ButtonTemplates.xaml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/BreadcrumbBar/Generic.xaml b/Odyssey/Odyssey/Themes/BreadcrumbBar/Generic.xaml new file mode 100644 index 0000000..c0e297e --- /dev/null +++ b/Odyssey/Odyssey/Themes/BreadcrumbBar/Generic.xaml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Odyssey/Odyssey/Themes/BreadcrumbBar/OdcTextBox.xaml b/Odyssey/Odyssey/Themes/BreadcrumbBar/OdcTextBox.xaml new file mode 100644 index 0000000..cdf10b9 --- /dev/null +++ b/Odyssey/Odyssey/Themes/BreadcrumbBar/OdcTextBox.xaml @@ -0,0 +1,108 @@ + + + + + + + + + + White + + + 0.65 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Controls/OdcTextBox.xaml b/Odyssey/Odyssey/Themes/Controls/OdcTextBox.xaml new file mode 100644 index 0000000..e5f4a9e --- /dev/null +++ b/Odyssey/Odyssey/Themes/Controls/OdcTextBox.xaml @@ -0,0 +1,94 @@ + + + + + + + + + + White + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Expander/Aero.NormalColor.ExpandHeader.xaml b/Odyssey/Odyssey/Themes/Expander/Aero.NormalColor.ExpandHeader.xaml new file mode 100644 index 0000000..c0ed939 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Expander/Aero.NormalColor.ExpandHeader.xaml @@ -0,0 +1,89 @@ + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Expander/Aero.NormalColor.xaml b/Odyssey/Odyssey/Themes/Expander/Aero.NormalColor.xaml new file mode 100644 index 0000000..0a2e43e --- /dev/null +++ b/Odyssey/Odyssey/Themes/Expander/Aero.NormalColor.xaml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Odyssey/Odyssey/Themes/Expander/Classic.ExpandHeader.xaml b/Odyssey/Odyssey/Themes/Expander/Classic.ExpandHeader.xaml new file mode 100644 index 0000000..dae4a73 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Expander/Classic.ExpandHeader.xaml @@ -0,0 +1,81 @@ + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Expander/Classic.xaml b/Odyssey/Odyssey/Themes/Expander/Classic.xaml new file mode 100644 index 0000000..e300e14 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Expander/Classic.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/Odyssey/Odyssey/Themes/Expander/Generic.ExpandHeader.xaml b/Odyssey/Odyssey/Themes/Expander/Generic.ExpandHeader.xaml new file mode 100644 index 0000000..e090e78 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Expander/Generic.ExpandHeader.xaml @@ -0,0 +1,78 @@ + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Expander/Generic.Expander.xaml b/Odyssey/Odyssey/Themes/Expander/Generic.Expander.xaml new file mode 100644 index 0000000..43680d6 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Expander/Generic.Expander.xaml @@ -0,0 +1,132 @@ + + + + 0:0:0.20 + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Expander/Generic.xaml b/Odyssey/Odyssey/Themes/Expander/Generic.xaml new file mode 100644 index 0000000..1bb5dd7 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Expander/Generic.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + diff --git a/Odyssey/Odyssey/Themes/Expander/Luna.Homestead.xaml b/Odyssey/Odyssey/Themes/Expander/Luna.Homestead.xaml new file mode 100644 index 0000000..5a9cd0a --- /dev/null +++ b/Odyssey/Odyssey/Themes/Expander/Luna.Homestead.xaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Odyssey/Odyssey/Themes/Expander/Luna.Metallic.xaml b/Odyssey/Odyssey/Themes/Expander/Luna.Metallic.xaml new file mode 100644 index 0000000..200ffc6 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Expander/Luna.Metallic.xaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Odyssey/Odyssey/Themes/Expander/Luna.NormalColor.xaml b/Odyssey/Odyssey/Themes/Expander/Luna.NormalColor.xaml new file mode 100644 index 0000000..0ec7c02 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Expander/Luna.NormalColor.xaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Odyssey/Odyssey/Themes/Generic.xaml b/Odyssey/Odyssey/Themes/Generic.xaml new file mode 100644 index 0000000..353f888 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Generic.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/Odyssey/Odyssey/Themes/Luna.Homestead.xaml b/Odyssey/Odyssey/Themes/Luna.Homestead.xaml new file mode 100644 index 0000000..2acb5b4 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Luna.Homestead.xaml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Luna.Metallic.xaml b/Odyssey/Odyssey/Themes/Luna.Metallic.xaml new file mode 100644 index 0000000..fdecda1 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Luna.Metallic.xaml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Luna.NormalColor.xaml b/Odyssey/Odyssey/Themes/Luna.NormalColor.xaml new file mode 100644 index 0000000..fd95e20 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Luna.NormalColor.xaml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/OutlookBar/Generic.xaml b/Odyssey/Odyssey/Themes/OutlookBar/Generic.xaml new file mode 100644 index 0000000..1546c2d --- /dev/null +++ b/Odyssey/Odyssey/Themes/OutlookBar/Generic.xaml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/OutlookBar/OutlookBar.xaml b/Odyssey/Odyssey/Themes/OutlookBar/OutlookBar.xaml new file mode 100644 index 0000000..4cbb122 --- /dev/null +++ b/Odyssey/Odyssey/Themes/OutlookBar/OutlookBar.xaml @@ -0,0 +1,364 @@ + + + + + #FFFFD76A + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/OutlookBar/ToggleButton.xaml b/Odyssey/Odyssey/Themes/OutlookBar/ToggleButton.xaml new file mode 100644 index 0000000..4b6ca9d --- /dev/null +++ b/Odyssey/Odyssey/Themes/OutlookBar/ToggleButton.xaml @@ -0,0 +1,37 @@ + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/DefaultRibbonButtonBrushes.xaml b/Odyssey/Odyssey/Themes/Ribbon/DefaultRibbonButtonBrushes.xaml new file mode 100644 index 0000000..80674d1 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/DefaultRibbonButtonBrushes.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #FF0094FF + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/DefaultWindowButtonStyles.xaml b/Odyssey/Odyssey/Themes/Ribbon/DefaultWindowButtonStyles.xaml new file mode 100644 index 0000000..2b6f174 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/DefaultWindowButtonStyles.xaml @@ -0,0 +1,88 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/Generic.xaml b/Odyssey/Odyssey/Themes/Ribbon/Generic.xaml new file mode 100644 index 0000000..b20c4d4 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/Generic.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Odyssey/Odyssey/Themes/Ribbon/HighlightedBackgrounds.xaml b/Odyssey/Odyssey/Themes/Ribbon/HighlightedBackgrounds.xaml new file mode 100644 index 0000000..8d2a94f --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/HighlightedBackgrounds.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/InternalRibbonButton.xaml b/Odyssey/Odyssey/Themes/Ribbon/InternalRibbonButton.xaml new file mode 100644 index 0000000..6968e03 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/InternalRibbonButton.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/QuickAccessKey.xaml b/Odyssey/Odyssey/Themes/Ribbon/QuickAccessKey.xaml new file mode 100644 index 0000000..30475bf --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/QuickAccessKey.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonAppMenuItem.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonAppMenuItem.xaml new file mode 100644 index 0000000..6b4989a --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonAppMenuItem.xaml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonApplicationButton.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonApplicationButton.xaml new file mode 100644 index 0000000..b9df9e4 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonApplicationButton.xaml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonApplicationMenu.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonApplicationMenu.xaml new file mode 100644 index 0000000..cf17b7e --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonApplicationMenu.xaml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonBar.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonBar.xaml new file mode 100644 index 0000000..0fecd54 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonBar.xaml @@ -0,0 +1,185 @@ + + + + + + + Center + Center + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonButton.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonButton.xaml new file mode 100644 index 0000000..837c368 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonButton.xaml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonButtonGroup.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonButtonGroup.xaml new file mode 100644 index 0000000..d88756f --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonButtonGroup.xaml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonChrome.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonChrome.xaml new file mode 100644 index 0000000..34324dd --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonChrome.xaml @@ -0,0 +1,116 @@ + + + + + + + + 0.50 + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonComboBox.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonComboBox.xaml new file mode 100644 index 0000000..7f677ef --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonComboBox.xaml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonComboBoxItem.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonComboBoxItem.xaml new file mode 100644 index 0000000..d1c4fb0 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonComboBoxItem.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonContextualTabSet.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonContextualTabSet.xaml new file mode 100644 index 0000000..a13df9f --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonContextualTabSet.xaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonDropDownButton.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonDropDownButton.xaml new file mode 100644 index 0000000..fbaf0cc --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonDropDownButton.xaml @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonFlowGroup.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonFlowGroup.xaml new file mode 100644 index 0000000..517ebaf --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonFlowGroup.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonGallery.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonGallery.xaml new file mode 100644 index 0000000..94ba150 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonGallery.xaml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonGroup.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonGroup.xaml new file mode 100644 index 0000000..2fbd200 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonGroup.xaml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonGroupBrushes.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonGroupBrushes.xaml new file mode 100644 index 0000000..4e4c424 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonGroupBrushes.xaml @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonGroupDropDownButton.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonGroupDropDownButton.xaml new file mode 100644 index 0000000..32115a3 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonGroupDropDownButton.xaml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonHLChromeStyle.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonHLChromeStyle.xaml new file mode 100644 index 0000000..22f11e8 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonHLChromeStyle.xaml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonImages.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonImages.xaml new file mode 100644 index 0000000..9fc15b0 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonImages.xaml @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonMenuItem.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonMenuItem.xaml new file mode 100644 index 0000000..ae6d389 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonMenuItem.xaml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonQAToolBar.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonQAToolBar.xaml new file mode 100644 index 0000000..6434ff9 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonQAToolBar.xaml @@ -0,0 +1,232 @@ + + + + + + + 2,4,2,4 + 3 + 1 + + + -16,2,0,0 + 0,10,10,0 + 0,1,1,1 + Visible + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonSeparator.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonSeparator.xaml new file mode 100644 index 0000000..31dbc91 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonSeparator.xaml @@ -0,0 +1,42 @@ + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonSplitButton.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonSplitButton.xaml new file mode 100644 index 0000000..def0d02 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonSplitButton.xaml @@ -0,0 +1,274 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonTabItem.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonTabItem.xaml new file mode 100644 index 0000000..2a8748e --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonTabItem.xaml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonTabScroller.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonTabScroller.xaml new file mode 100644 index 0000000..4470828 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonTabScroller.xaml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonTextBox.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonTextBox.xaml new file mode 100644 index 0000000..1f56182 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonTextBox.xaml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonThumbnail.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonThumbnail.xaml new file mode 100644 index 0000000..8a82fa7 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonThumbnail.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonToggleButton.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonToggleButton.xaml new file mode 100644 index 0000000..52c861f --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonToggleButton.xaml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonToolTip.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonToolTip.xaml new file mode 100644 index 0000000..d546dd4 --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonToolTip.xaml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/Ribbon/RibbonWindow.xaml b/Odyssey/Odyssey/Themes/Ribbon/RibbonWindow.xaml new file mode 100644 index 0000000..ef0af8e --- /dev/null +++ b/Odyssey/Odyssey/Themes/Ribbon/RibbonWindow.xaml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/Themes/TreeViewStyle.xaml b/Odyssey/Odyssey/Themes/TreeViewStyle.xaml new file mode 100644 index 0000000..2c2050c --- /dev/null +++ b/Odyssey/Odyssey/Themes/TreeViewStyle.xaml @@ -0,0 +1,190 @@ + + + 0:0:0.15 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Odyssey/Odyssey/app.config b/Odyssey/Odyssey/app.config new file mode 100644 index 0000000..fa2534b --- /dev/null +++ b/Odyssey/Odyssey/app.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/PasswordSafe/PasswordSafe.Data/Biz/BaseObject.cs b/PasswordSafe/PasswordSafe.Data/Biz/BaseObject.cs new file mode 100644 index 0000000..5787b97 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/BaseObject.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ComponentModel; +using System.Diagnostics; + +namespace PasswordSafe.Data.Biz +{ + + /// + /// Represents the base type of all business objects. + /// + public class BaseObject : INotifyPropertyChanged + { + public BaseObject() + : base() + { + } + + public BaseObject(int id, string name) + : base() + { + this.name = name; + this.Id = id; + } + + #region INotifyPropertyChanged Members + + + protected virtual void OnPropertyChanged(string propertyName) + { + if (!Notifying) + { + // Notifying = true; + try + { + //Debug.WriteLine(string.Format("> Notify on {0}.{1}: {2}", GetType().Name, Name, propertyName)); + //Debug.Indent(); + IsModified = true; + if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + //Debug.Unindent(); + } + finally + { + Notifying = false; + } + } + } + + + protected bool Notifying { get; private set; } + + public void RaisePropertyChanged(string propertyName) + { + OnPropertyChanged(propertyName); + } + + public event PropertyChangedEventHandler PropertyChanged; + + #endregion + + private bool isModified; + + /// + /// Gets whether data is modified. + /// + public bool IsModified + { + get { return isModified; } + internal set + { + if (isModified != value) + { + isModified = value; + OnModifiedChanged(); + if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsModified")); + isModified = value; + } + } + } + + protected virtual void OnModifiedChanged() + { + } + + /// + /// Gets the id of the data repository. + /// + public int Id { get; internal set; } + + protected short order; + + /// + /// Gets the order in the repository. + /// + public short Order + { + get { return order; } + internal set + { + if (order != value) + { + order = value; + IsModified = true; + } + } + } + + private string name; + + /// + /// Gets or sets the name. + /// + public virtual string Name + { + get + { + return name != null ? name : string.Empty; + } + set + { + if (name != null) + { + name = value; + OnPropertyChanged("Name"); + } + } + } + + /// + /// Gets the . + /// + internal protected virtual BizContext Context + { + get { return BizContext.Instance; } + } + + /// + /// Resets all data. + /// + internal protected virtual void Reset() + { + IsModified = false; + } + + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Biz/BizContext.cs b/PasswordSafe/PasswordSafe.Data/Biz/BizContext.cs new file mode 100644 index 0000000..c778b65 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/BizContext.cs @@ -0,0 +1,548 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PasswordSafe.Data.Biz; +using System.Diagnostics; + +namespace PasswordSafe.Data.Biz +{ + /// + /// The business context to exchange business objects with the Data Access Layer. + /// + public class BizContext : IDisposable + { + private static object lockObject = new object(); + private static BizContext instance; + + /// + /// Opens the connection to use for the database. + /// + /// The connection string about the database. + /// True, if the connection is opened, otherwise false. + public static bool OpenConnection(string connectionString) + { + lock (lockObject) + { + CloseConnection(); + instance = new BizContext(connectionString); + } + return true; + } + + /// + /// Closes an existing connection. + /// + public static void CloseConnection() + { + if (Instance != null) + { + Instance.Dispose(); + instance = null; + } + } + + /// + /// Gets the instance of . + /// + public static BizContext Instance + { + get + { + if (instance == null) instance = new BizContext(""); + return instance; + } + } + + #region IDisposable Members + + public void Dispose() + { + Dal.Dispose(); + } + + #endregion + + private BizContext(string connectionString) + : base() + { + Dal = new PasswordSafe.Data.DAL.Context(connectionString); + } + + /// + /// Gets the data access layer. + /// + internal DAL.Context Dal { get; private set; } + + + private NotifyList folders; + private NotifyList categories; + + /// + /// Gets a list of all root s. + /// + public NotifyList Folders + { + get + { + if (folders == null) + { + lock (Dal) + { + if (folders == null) + { + folders = new NotifyList(PersistentFolders(Dal.GetRootFolders().ToArray())); + if (folders.Count == 0 && Dal.IsConnected) + { + // add a root folder if none exists: + folders.Add(new Folder(0, "Folders")); + } + } + } + } + return folders; + } + } + + /// + /// Gets the root . + /// + public Folder RootFolder + { + get + { + return Folders.FirstOrDefault(); + } + } + + public Category RootCategory + { + get { return Categories.FirstOrDefault(); } + } + + + /// + /// Gets a list of all root s. + /// + public NotifyList Categories + { + get + { + if (categories == null || categories.Count == 0) + { + lock (Dal) + { + if (categories == null || categories.Count == 0) + { + categories = new NotifyList(PersistentCategories(Dal.GetRootCategories())); + if (categories.Count == 0 && Dal.IsConnected) + { + // add a root if it does not exist: + //categories.Add(new Category(0, "Categories", (short)categories.Count)); + } + } + } + } + return categories; + } + } + + internal IEnumerable GetCategories(Category parent) + { + return PersistentCategories(Dal.GetCategories(parent)); + } + + internal void SavePassword(Password password) + { + using (var transaction = Dal.BeginTransaction()) + { + if (password.Id != 0) + { + Dal.UpdatePassword(password); + } + else + { + Dal.CreatePassword(password); + } + ReorderObjects(password.Fields); + UpdateFields(password); + UpdateFavorites(password); + password.IsModified = false; + transaction.Commit(); + } + } + + internal void UndoPassword(Password password) + { + if (password.Id != 0) + { + if (password.IsModified) + { + Password original = Dal.GetPassword(password.Id); + password.Name = original.Name; + password.Category = original.Category; + password.Order = original.Order; + password.Reset(); + password.IsModified = false; + } + } + else + { + // if the Id is null, the the password is new created and not commited, hence undoing means deleting: + password.Delete(); + } + } + + internal IEnumerable GetPasswords(Category category) + { + return PersistentPasswords(Dal.GetPasswordsByCategory(category)); + } + + /// + /// Using dictionaries to ensure that there is always only one instance of Password, Folder or Category with the same Id persistent + /// + private Dictionary passwordDictionary = new Dictionary(); + private Dictionary folderDictionary = new Dictionary(); + private Dictionary categoryDictionary = new Dictionary(); + + /// + /// Checks to not return duplicate Folders with same id. + /// + private IEnumerable PersistentCategories(IEnumerable categories) + { + foreach (var c in categories) + { + if (categoryDictionary.ContainsKey(c.Id)) yield return categoryDictionary[c.Id]; + else + { + categoryDictionary.Add(c.Id, c); + yield return c; + } + } + } + /// + /// Checks to not return duplicate Folders with same id. + /// + private IEnumerable PersistentFolders(IEnumerable folders) + { + foreach (var f in folders) + { + int key = f.Id; + if (folderDictionary.ContainsKey(key)) yield return folderDictionary[key]; + else + { + //folderDictionary[key] = f; + folderDictionary.Add(key, f); + yield return f; + } + } + } + + /// + /// Checks to not return duplicate passwords with same id. + /// + private IEnumerable PersistentPasswords(IEnumerable passwords) + { + foreach (var p in passwords) + { + if (passwordDictionary.ContainsKey(p.Id)) yield return passwordDictionary[p.Id]; + else + { + passwordDictionary.Add(p.Id, p); + yield return p; + } + } + } + + internal IEnumerable GetFolders(int passwordId) + { + return PersistentFolders(Dal.GetFolderByPasswordId(passwordId)).ToArray(); + } + + internal IEnumerable GetFoldersByParentFolder(Folder parent) + { + return PersistentFolders(Dal.GetFoldersByParentFolder(parent)); + } + + internal IEnumerable GetPasswordsByFolderId(int folderId) + { + return PersistentPasswords(Dal.GetPasswordsByFolderId(folderId)); + } + + internal void DeletePassword(Password password) + { + if (password.Id != 0) + { + passwordDictionary.Remove(password.Id); + Dal.DeletePassword(password.Id); + } + } + + internal IEnumerable GetFields(Password password) + { + return Dal.GetFields(password); + } + + private void ReorderObjects(IEnumerable objects) where T : BaseObject + { + short order = 0; + foreach (BaseObject o in objects) o.Order = order++; + } + + private void ResetModified(IEnumerable objects) where T : BaseObject + { + foreach (BaseObject o in objects) o.IsModified = false; + } + + private void UpdateFavorites(Password password) + { + int pwId = password.Id; + + IEnumerable existingFaves = Dal.GetExistingFaves(password.Id); + IEnumerable deletedFaves = existingFaves.Except(password.Folders.Select(f => f.Id)); + IEnumerable newFaves = password.Folders.Select(f => f.Id).Except(existingFaves); + + foreach (int id in deletedFaves) + { + Dal.DeleteFavorite(password.Id, id); + } + + foreach (int id in newFaves) + { + Dal.CreateFavorite(password.Id, id); + } + + } + + private void UpdateFields(Password password) + { + foreach (FieldType type in new FieldType[] { FieldType.Text, FieldType.Date, FieldType.Int, FieldType.Memo }) + { + UpdateFields(password, type); + } + } + + private void UpdateFields(Password password, FieldType type) + { + + int[] fieldIds = Dal.GetFieldIds(password.Id, type).ToArray(); + + // determine the field ids to delete: + IEnumerable deletedIds = fieldIds.Except(password.Fields.Where(f => f.Id != 0).Select(f => f.Id)); + foreach (var id in deletedIds) + { + Dal.DeleteField(id, type); + } + + // modify existing fields: + var existingFields = password.Fields.Where(f => f.Id > 0); + foreach (var field in existingFields) + { + if (field.IsModified) + { + Dal.UpdateField(field); + } + } + + // create the new fields: + var newFields = password.Fields.Where(f => f.Id == 0); + foreach (var field in newFields) + { + field.Id = Dal.CreateField(field); + } + + ResetModified(password.Fields); + } + + + internal Category GetCategory(int categoryId) + { + if (categoryDictionary.ContainsKey(categoryId)) return categoryDictionary[categoryId]; + + Category c = Dal.GetCategoryById(categoryId); + categoryDictionary.Add(c.Id, c); + return c; + } + + internal Folder GetFolderById(int folderId) + { + if (folderDictionary.ContainsKey(folderId)) return folderDictionary[folderId]; + Folder folder = Dal.GetFolderById(folderId); + folderDictionary.Add(folder.Id, folder); + return folder; + } + + public Folder CreateFolder(Folder folder) + { + Dal.CreateFolder(folder); + folderDictionary.Add(folder.Id, folder); + return folder; + } + + internal void DeleteFolder(Folder folder) + { + if (folder.Folders.Count > 0) + { + throw new ArgumentException("Cannot delete Folder that has Children."); + } + + + if (folder.Id != 0) + { + Dal.DeleteFolder(folder.Id); + if (folderDictionary.ContainsKey(folder.Id)) + { + folderDictionary.Remove(folder.Id); + } + } + } + + internal Category CreateCategory(Category parent) + { + Category category = Dal.CreateCategory(parent); + categoryDictionary.Add(category.Id, category); + return category; + } + + internal void DeleteCategory(Category category) + { + if (category.Passwords.Count > 0) + { + throw new ArgumentException("Cannot delete category that has passwords associated."); + } + if (category.Categories.Count > 0) + { + throw new ArgumentException("Cannot delete category that has Children."); + } + + if (category.Id != 0) + { + Dal.DeleteCategory(category.Id); + if (categoryDictionary.ContainsKey(category.Id)) + { + categoryDictionary.Remove(category.Id); + } + } + } + + internal IEnumerable GetTemplates(Category category) + { + return Dal.GetTemplates(category); + } + + public void Commit() + { + // throw new NotImplementedException(); + } + + + internal void SaveCategory(Category category) + { + using (var transaction = Dal.BeginTransaction()) + { + if (category.Id == 0) Dal.CreateCategory(category); + Dal.UpdateCategory(category); + category.IsModified = false; + transaction.Commit(); + } + } + + internal void SaveTemplate(Category category) + { + using (var transaction = Dal.BeginTransaction()) + { + ReorderObjects(category.Fields); + UpdateTemplateFields(category.Id, category.Fields); + category.IsModified = false; + transaction.Commit(); + } + } + + private void UpdateTemplateFields(int categoryId, IEnumerable fields) + { + IEnumerable ids = Dal.GetTemplateFields(categoryId); + + IEnumerable deleted = ids.Except(fields.Select(f => f.Id)); + foreach (int deletedId in deleted) + { + Dal.DeleteTemplateField(deletedId); + } + + foreach (TemplateField field in fields) + { + if (field.IsModified) + { + if (field.Id == 0) Dal.CreateTemplateField(field); + Dal.UpdateTemplateField(field); + field.IsModified = false; + } + } + } + + internal void SaveFolder(Folder folder) + { + using (var transaction = Dal.BeginTransaction()) + { + if (folder.Id == 0) Dal.CreateFolder(folder); + Dal.UpdateFolder(folder); + folder.IsModified = false; + transaction.Commit(); + } + } + + public bool ChangePassword(string connectionString, string oldPassword, string newPassword) + { + return Dal.ChangePassword(connectionString, oldPassword, newPassword); + } + + /// + /// Saves all changes that where made to any password or template. + /// + public void Save() + { + Category root = RootCategory; + if (root != null) + { + SaveCategoryNested(root); + foreach (var password in root.NestedPasswords) + { + if (password.IsModified) password.Save(); + } + } + } + + /// + /// Undos all changes that where made to any password or template. + /// + public void Undo() + { + Category root = RootCategory; + if (root != null) + { + UndoCategoryNested(root); + foreach (var password in root.NestedPasswords) + { + if (password.IsModified) password.Undo(); + } + } + } + + private void UndoCategoryNested(Category c) + { + if (c.IsModified) c.Save(); + foreach (var sub in c.Categories) + { + sub.UndoTemplate(); + } + } + + private void SaveCategoryNested(Category c) + { + if (c.IsModified) c.Save(); + foreach (var sub in c.Categories) + { + sub.Save(); + } + } + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Biz/Category.cs b/PasswordSafe/PasswordSafe.Data/Biz/Category.cs new file mode 100644 index 0000000..e45cbc8 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/Category.cs @@ -0,0 +1,379 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ComponentModel; + +namespace PasswordSafe.Data.Biz +{ + public class Category : NodeBase + { + public Category(int id, string name, short order, Category parent) + : base(id, name) + { + this.parent = parent; + this.order = order; + } + public override IEnumerable Children + { + get + { + return Categories.Cast(); + } + set + { + base.Children = value; + } + } + + internal Category(int id, string name, short order, int? parentId) + : base(id, name) + { + this.parentId = parentId; + this.Order = order; + } + + public Category(int id, string name, short order) + : base(id, name) + { + this.Order = order; + } + protected override void OnModifiedChanged() + { + base.OnModifiedChanged(); + } + + protected override void OnPropertyChanged(string propertyName) + { + base.OnPropertyChanged(propertyName); + switch (propertyName) + { + case "Order": + if (Parent != null) Parent.ReorderChildren(); + break; + + case "Name": + Save(); + break; + + case "Passwords": + OnPasswordPropertyChanged(); + break; + + case "NestedPasswords": + OnPropertyChanged("NestedPasswordCount"); + break; + } + } + + private void ReorderChildren() + { + short order = 0; + foreach (Category c in Categories) + { + short oldOrder = c.Order; + c.Order = order; + if (oldOrder != order) c.Save(); + order++; + } + } + + /// + /// Saves the to the data repositoy. + /// + public void Save() + { + Context.SaveCategory(this); + IsModified = false; + } + + private void OnPasswordPropertyChanged() + { + Category category = this; + while (category != null) + { + category.OnPropertyChanged("NestedPasswords"); + category = category.Parent; + } + } + + private Category parent; + private int? parentId; + + /// + /// Gets the parent , otherwise null. + /// + public Category Parent + { + get + { + if (parentId.HasValue && parent == null) + { + parent = Context.GetCategory(parentId.Value); + } + return parent; + } + } + + private NotifyList passwords; + + /// + /// Gets a list of all s associated with to category. + /// + public NotifyList Passwords + { + get + { + if (passwords == null) + { + passwords = Id == 0 ? new NotifyList() : new NotifyList(Context.GetPasswords(this)); + passwords.Changed += new EventHandler>(OnPasswordsChanged); + } + return passwords; + } + } + + private NotifyList fields; + + /// + /// Gets a list of all s associated to this category. + /// + public NotifyList Fields + { + get + { + if (fields == null) + { + fields = Id == 0 ? new NotifyList() : new NotifyList(Context.GetTemplates(this)); + fields.Changed += new EventHandler>(OnFieldsChanged); + } + return fields; + } + } + + void OnFieldsChanged(object sender, NotifyEventArgs e) + { + OnPropertyChanged("Fields"); + } + + /// + /// Gets an enumeration of all nested s that are associated + /// either to this Category or any descendent . + /// + public IEnumerable NestedPasswords + { + get + { + foreach (var p in Passwords) yield return p; + foreach (var c in Categories) + { + foreach (var p in c.NestedPasswords) + { + yield return p; + } + } + } + } + + + public NotifyList categories; + + /// + /// Gets a list ofild ch s. + /// + public NotifyList Categories + { + get + { + if (categories == null) + { + categories = Id == 0 ? new NotifyList() : new NotifyList(Context.GetCategories(this)); + categories.Changed += new EventHandler>(OnCategoriesChanged); + + } + return categories; + } + } + + /// + /// Occurs when the list of categories has changed. + /// + protected void OnCategoriesChanged(object sender, NotifyEventArgs e) + { + ReorderChildren(); + OnPropertyChanged("Categories"); + } + + /// + /// Occurs when the list of passwords has changed. + /// + protected void OnPasswordsChanged(object sender, NotifyEventArgs e) + { + OnPropertyChanged("Passwords"); + + Category parent = Parent; + while (parent != null) + { + parent.OnPropertyChanged("NestedPasswords"); + parent = parent.Parent; + } + } + + /// + /// Gets the number of nested s. + /// + public int NestedPasswordCount + { + get { return this.NestedPasswords.Count(); } + } + + /// + /// Resets all data. + /// + protected internal override void Reset() + { + categories = null; + passwords = null; + fields = null; + OnPropertyChanged("Categories"); + OnPropertyChanged("Passwords"); + OnPropertyChanged("Fields"); + base.Reset(); + } + + /// + /// Gets the parent of this + /// + protected internal override NodeBase ParentNode + { + get + { + return Parent != null ? Parent : base.ParentNode; + } + set + { + base.ParentNode = value; + } + } + + + /// + /// Creates a new password for this . + /// + /// The new + public Password CreatePassword() + { + Password newPassword = new Password(this, 0, "New Password"); + + short order = 0; + foreach (TemplateField tempField in this.Fields) + { + Field field = new Field(newPassword, 0, tempField.Name, tempField.Type, null, order++); + newPassword.Fields.Add(field); + } + + this.Passwords.Add(newPassword); + return newPassword; + } + + + internal void ResetPasswords() + { + OnPropertyChanged("Passwords"); + } + + /// + /// Moves the on level up in order. + /// + public void Up() + { + if (Parent != null) Parent.Categories.MoveUp(this); + } + + /// + /// Moves the on level down in order. + /// + public void Down() + { + if (Parent != null) Parent.Categories.MoveDown(this); + } + + /// + /// Moves the to the top of order. + /// + public void Top() + { + if (Parent != null) Parent.Categories.MoveToTop(this); + } + + + /// + /// Moves the to the bottom of order. + /// + public void Bottom() + { + if (Parent != null) Parent.Categories.MoveToBottom(this); + } + + public void Delete() + { + if (Categories.Any()) throw new ArgumentOutOfRangeException("Category has descendants."); + if (Passwords.Any()) throw new ArgumentOutOfRangeException("Category has associated Passwords."); + if (Parent != null) + { + Parent.Categories.Remove(this); + Context.DeleteCategory(this); + } + } + + /// + /// Adds a new child Category. + /// + /// The name of the new child Category. + /// The new Category. + public Category AddCategory(string name) + { + Category category = new Category(0, name, (short)Categories.Count, this); + Categories.Add(category); + category.Save(); + foreach (TemplateField field in Fields) + { + TemplateField copy = new TemplateField(0, field.Name, field.Type, category, field.MinRange, field.MaxRange, (short)Fields.Count); + copy.IsModified = true; + category.Fields.Add(copy); + } + category.SaveTemplate(); + return category; + } + + public void SaveTemplate() + { + Context.SaveTemplate(this); + } + + /// + /// Undoing a template is very easy: + /// Since there are only the TemplateFields affected, the Fields list must only be recreated to gather them from Database again. + /// + public void UndoTemplate() + { + fields = null; + RaisePropertyChanged("Fields"); + IsModified = false; + } + + /// + /// Adds a to the template. + /// + /// The type of the new field. + /// The name of the new field. + /// The new . + public TemplateField AddField(FieldType type, string name) + { + TemplateField field = new TemplateField(0, name, type, this, "", "", (short)Fields.Count); + field.IsModified = true; + Fields.Add(field); + return field; + } + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Biz/CustomNode.cs b/PasswordSafe/PasswordSafe.Data/Biz/CustomNode.cs new file mode 100644 index 0000000..b17ed40 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/CustomNode.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PasswordSafe.Data.Biz +{ + public class CustomNode:NodeBase + { + public CustomNode(string name, IEnumerable children) + : base(0, name) + { + this.Children = children; + } + + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Biz/Field.cs b/PasswordSafe/PasswordSafe.Data/Biz/Field.cs new file mode 100644 index 0000000..9c3b2a8 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/Field.cs @@ -0,0 +1,431 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Security; +using System.Runtime.InteropServices; + +namespace PasswordSafe.Data.Biz +{ + /// + /// is disposable since it contains confidential data that should be disposed from memory asap. + /// + public class Field : BaseObject, IDisposable + { + public Field(Password password, int id, string name, FieldType type, object value, short order) + : base(id, name) + { + this.Password = password; + this.Type = type; + if (IsString) ChangeSecureString(value, false); else this.value = value; + this.Order = order; + } + + #region IDisposable Members + + public void Dispose() + { + secureStringValue.Dispose(); + } + + #endregion + + protected override void OnPropertyChanged(string propertyName) + { + base.OnPropertyChanged(propertyName); + switch (propertyName) + { + case "Value": + base.OnPropertyChanged("StringValue"); + break; + + case "StringValue": + base.OnPropertyChanged("Value"); + break; + } + if (propertyName != "IsSelected") + { + Password.IsModified = true; + Password.RaisePropertyChanged("Fields"); + } + } + + protected internal override BizContext Context + { + get + { + return Password.Context; + } + } + + public Password Password { get; private set; } + + private bool isSelected; + + public bool IsSelected + { + get { return isSelected; } + set + { + if (isSelected != value) + { + isSelected = value; + OnPropertyChanged("IsSelected"); + } + } + } + + public bool HasLines { get { return Type == FieldType.Memo; } } + public bool HasRange { get { return Type != FieldType.Separator && Type != FieldType.Bool && Type != FieldType.Memo; } } + + private int? minLines; + private int? maxLines; + private object minRange; + private object maxRange; + + /// + /// Gets or sets the minimum number of lines, otherwise null. + /// + public int? MinLines + { + get { return minLines; } + set + { + if (minLines != value) + { + minLines = value; + OnPropertyChanged("MinLines"); + } + } + } + + /// + /// Gets or sets the maximum number of lines, otherwise null. + /// + public int? MaxLines + { + get { return maxLines; } + set + { + if (maxLines != value) + { + maxLines = value; + OnPropertyChanged("MaxLines"); + } + } + } + + /// + /// Gets or sets the minimum range converted as string. + /// + public string MinRangeString + { + get { return ValueToString(minRange, RangeType); } + set + { + minRange = StringToValue(value, RangeType); + OnPropertyChanged("MinRangeString"); + } + } + + /// + /// Gets or sets the maximum range converted as string. + /// + public string MaxRangeString + { + get { return ValueToString(maxRange, RangeType); } + set + { + maxRange = StringToValue(value, RangeType); + OnPropertyChanged("MaxRangeString"); + } + } + + /// + /// Gets or sets the minimum range. The value type is depending on . + /// + public object MinRange + { + get { return minRange; } + set + { + if (minRange != value) + { + minRange = value; + OnPropertyChanged("MinRange"); + } + } + } + + /// + /// Gets or sets the maximum range. The value type is depending on . + /// + public object MaxRange + { + get { return maxRange; } + set + { + if (maxRange != value) + { + maxRange = value; + OnPropertyChanged("MaxRange"); + } + } + } + + /// + /// Gets the type to be used for MimimumRange and MaximumRange. + /// + protected FieldType RangeType + { + get + { + switch (Type) + { + case FieldType.Memo: + case FieldType.Text: + case FieldType.Password: + return FieldType.Int; + + default: return Type; + } + } + } + + /// + /// Converts a string to a value type. + /// + /// The string representation of the value to convert. + /// The type to convert to. + /// The converted value. + protected object StringToValue(string str, FieldType type) + { + switch (type) + { + case FieldType.Date: + case FieldType.Time: + if (str == null) return null; + DateTime dval; + if (DateTime.TryParse(str, out dval)) return dval; + return null; + + case FieldType.Int: + if (str == null) return null; + int iVal; + if (int.TryParse(str, out iVal)) return iVal; + return null; + + default: + return str; + } + } + + /// + /// Converts a value to string. + /// + /// The value to convert. + /// The value type. + /// The converted value as string representation. + protected string ValueToString(object value, FieldType type) + { + if (value == null) return null; + switch (type) + { + case FieldType.Date: + return ((DateTime)value).ToShortDateString(); + + case FieldType.Time: + return ((DateTime)value).ToLongTimeString(); + + case FieldType.Int: + return ((int)value).ToString(); + + case FieldType.Text: + case FieldType.Password: + case FieldType.Memo: + return value.ToString(); + + default: + return String.Empty; + } + } + + internal void SetRange(object min, object max) + { + minRange = min; + maxRange = max; + } + + internal void SetLines(int? min, int? max) + { + minLines = min; + maxLines = max; + } + + private object value; + + private SecureString secureStringValue = new SecureString(); + + /// + /// Gets or sets the Value of the Field. The type of the Value depends on . + /// + public object Value + { + get + { + return IsString ? Marshal.PtrToStringBSTR(Marshal.SecureStringToBSTR(secureStringValue)) : value; + } + set + { + if (IsString) + { + ChangeSecureString(value, true); + } + else + { + ChangeValue(value); + } + } + } + + /// + /// Gets or sets the Value as string representation. + /// This Property is usefull for instance for transfering a value from and to a TextBox. + /// + public string StringValue + { + get { return ValueToString(Value, Type); } + set + { + object v = StringToValue(value, Type); + if (v != this.value) + { + this.Value = v; + OnPropertyChanged("StringValue"); + } + } + } + + public bool BoolValue + { + get { return (value != null && value.Equals(1)); } + set + { + Value = value ? 1 : 0; + } + } + + private void ChangeValue(object value) + { + if (this.value != value) + { + this.value = value; + OnPropertyChanged("Value"); + } + } + + private void ChangeSecureString(object value, bool signal) + { + if (Value != value) + { + string s = value as string; + secureStringValue.Clear(); + if (!string.IsNullOrEmpty(s)) + { + foreach (char c in s.ToCharArray()) secureStringValue.AppendChar(c); + } + } + if (signal) OnPropertyChanged("Value"); + } + + private static FieldType[] stringTypes = new FieldType[] { FieldType.Text, FieldType.Password, FieldType.Memo }; + + public bool IsString + { + get + { + return stringTypes.Contains(Type); + } + } + + public FieldType Type { get; private set; } + + /// + /// Deletes the Field and all dependencies. + /// + public void Delete() + { + Password.Fields.Remove(this); + } + + /// + /// Moves the Field on level up in the Password. + /// + public void Up() + { + Password.Fields.MoveUp(this); + } + + /// + /// Moves the Field on level down in the Password. + /// + public void Down() + { + Password.Fields.MoveDown(this); + } + + /// + /// Moves the Field to the top of the Password. + /// + public void Top() + { + Password.Fields.MoveToTop(this); + } + + /// + /// Moves the Field to the bottom of the Password. + /// + public void Bottom() + { + Password.Fields.MoveToBottom(this); + } + + /// + /// Converts the value to string. + /// + /// + public string ValueAsString() + { + string value = ValueToString(this.value, Type); + return value != null ? value : String.Empty; + } + + /// + /// Validates a value by Range. + /// + /// The value to validate. + /// True, if the value is valid and not outside range, otherwise false. + public bool Validate(string value) + { + object native = StringToValue(value, Type); + if (native == null) return true; + if (minRange != null && Compare(minRange, native) < 0) return false; + if (maxRange != null && Compare(maxRange, native) > 0) return false; + return true; + } + + private int Compare(object a, object b) + { + if ((a is DateTime) && (b is DateTime)) return ((DateTime)a).CompareTo((DateTime)b); + if ((a is int) && (b is int)) return ((int)a).CompareTo((int)b); + + if (a is int) + { + int n = (b.ToString()).Length; + return ((int)a).CompareTo(n); + } + return 0; + } + + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Biz/FieldType.cs b/PasswordSafe/PasswordSafe.Data/Biz/FieldType.cs new file mode 100644 index 0000000..da9b3cd --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/FieldType.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PasswordSafe.Data.Biz +{ + /// + /// Enumeration of possible field types.S + /// + public enum FieldType + { + Unknown = 0, + Text = 1, + Password = 2, + Date = 3, + Time = 4, + Int = 5, + Memo = 6, + Separator = 7, + Bool = 8 + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Biz/Folder.cs b/PasswordSafe/PasswordSafe.Data/Biz/Folder.cs new file mode 100644 index 0000000..5be4d9b --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/Folder.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ComponentModel; + +namespace PasswordSafe.Data.Biz +{ + public class Folder : NodeBase + { + public Folder(int id, string name) + : base(id, name) + { + } + + public Folder(int id, string name, Folder parent) + : base(id, name) + { + this.parent = parent; + } + + public Folder(int id, string name, int? parentId) + : base(id, name) + { + this.parentId = parentId; + } + + public int title; + + protected override void OnPropertyChanged(string propertyName) + { + base.OnPropertyChanged(propertyName); + switch (propertyName) + { + case "Name": + Save(); + break; + + case "Passwords": + OnPasswordPropertyChanged(); + break; + + case "NestedPasswords": + OnPropertyChanged("NestedPasswordCount"); + break; + } + } + + + private int? parentId; + private Folder parent; + + /// + /// Gets the parent , otherwise null. + /// + public Folder Parent + { + get + { + if (parent == null && parentId.HasValue) + { + parent = Context.GetFolderById(parentId.Value); + } + return parent; + } + } + + private NotifyList folders; + private NotifyList passwords; + + /// + /// Resets the list of s. + /// + public void ResetPasswords() + { + passwords = null; + OnPropertyChanged("Passwords"); + } + + /// + /// Gets the list of all s associated to this Folder. + /// + public NotifyList Passwords + { + get + { + EnsurePasswords(); + return passwords; + } + } + + private void EnsurePasswords() + { + if (passwords == null) + { + passwords = Id != 0 ? new NotifyList(Context.GetPasswordsByFolderId(Id)) : new NotifyList(); + passwords.Changed += new EventHandler>(OnPasswordsChanged); + } + } + + /// + /// Gets the list of all s that are either associated to this . + /// or any descendend . + /// + private IEnumerable AllNestedPasswords() + { + foreach (var p in Passwords) yield return p; + foreach (var folder in Folders) + { + foreach (var p in folder.NestedPasswords) + { + yield return p; + } + } + } + + public IEnumerable NestedPasswords + { + get + { + var array = AllNestedPasswords().ToArray(); + return array.Distinct().OrderBy(p => p.Order); + } + } + + public int NestedPasswordCount + { + get { return this.NestedPasswords.Count(); } + } + + + void OnPasswordsChanged(object sender, NotifyEventArgs e) + { + OnPropertyChanged("Passwords"); + } + + void OnPasswordPropertyChanged() + { + Folder folder = this; + while (folder != null) + { + folder.EnsurePasswords(); + folder.OnPropertyChanged("NestedPasswords"); + folder = folder.Parent; + } + } + + /// + /// Gets the list of child s. + /// + public NotifyList Folders + { + get + { + if (folders == null) + { + folders = Id == 0 ? new NotifyList() : new NotifyList(Context.GetFoldersByParentFolder(this)); + folders.Changed += new EventHandler>(OnFoldersChanged); + } + return folders; + } + } + + void OnFoldersChanged(object sender, NotifyEventArgs e) + { + ReorderChildren(); + + Folder folder = e.Item; + if (folder != null) folder.NotifyPasswordChanged(); + OnPropertyChanged("Folders"); + } + + private void ReorderChildren() + { + short order = 0; + foreach (Folder folder in Folders) + { + short oldOrder = folder.Order; + folder.Order = order; + if (oldOrder != order) folder.Save(); + order++; + } + } + + /// + /// Saves the to the data repository. + /// + public void Save() + { + Context.SaveFolder(this); + IsModified = false; + } + + /// + /// Notify the folder that the list of s has changed. + /// + public void NotifyPasswordChanged() + { + OnPropertyChanged("Passwords"); + } + + /// + /// Resets all data. + /// + protected internal override void Reset() + { + folders = null; + passwords = null; + OnPropertyChanged("Folders"); + OnPropertyChanged("Passwords"); + base.Reset(); + } + + + /// + /// Gets the parent of this + /// + protected internal override NodeBase ParentNode + { + get + { + return Parent != null ? Parent : base.ParentNode; + } + set + { + base.ParentNode = value; + } + } + + /// + /// Moves this on level up in order. + /// + public void Up() + { + if (Parent != null) Parent.Folders.MoveUp(this); + } + + /// + /// Moves this on level down in order. + /// + public void Down() + { + if (Parent != null) Parent.Folders.MoveDown(this); + } + + /// + /// Moves this to the top of order. + /// + public void Top() + { + if (Parent != null) Parent.Folders.MoveToTop(this); + } + + /// + /// Moves this to the bottom of order. + /// + public void Bottom() + { + if (Parent != null) Parent.Folders.MoveToBottom(this); + } + + public Folder AddFolder(string name) + { + Folder folder = new Folder(0, name, this); + Folders.Add(folder); + folder.Save(); + return folder; + } + + public void Delete() + { + if (Folders.Any()) throw new ArgumentOutOfRangeException("Folder has descendants."); + if (Parent != null) + { + foreach (Password pw in Passwords) + { + foreach (PasswordFolder pf in pw.PasswordFolders.Where(x => x.Folder == this)) + { + pf.IsFavorite = false; + } + } + Parent.Folders.Remove(this); + Context.DeleteFolder(this); + } + } + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Biz/NodeBase.cs b/PasswordSafe/PasswordSafe.Data/Biz/NodeBase.cs new file mode 100644 index 0000000..2913c7f --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/NodeBase.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PasswordSafe.Data.Biz +{ + /// + /// Represents the base class for hierarchical objects. + /// + public class NodeBase:BaseObject + { + public NodeBase(int id, string name) + : base(id, name) + { + } + + /// + /// Gets the hierarchical Path of all descendant Name properties separated with a backslash. + /// + public string Path + { + get + { + StringBuilder sb = new StringBuilder(); + NodeBase parent = this; + + while (parent != null) + { + sb.Insert(0, parent.Name); + parent = parent.ParentNode; + if (parent != null) sb.Insert(0, '\\'); + } + return sb.ToString(); + } + } + + + /// + /// Gets the parent otherwise null. + /// + internal protected virtual NodeBase ParentNode { get; set; } + + public string GetPath(int depth) + { + StringBuilder sb = new StringBuilder(); + NodeBase parent = this; + + while (parent != null && parent.ParentNode != null) + { + sb.Insert(0, parent.Name); + parent = parent.ParentNode; + if (parent != null && parent.ParentNode != null) sb.Insert(0, '\\'); + } + string s = sb.ToString(); + if (depth > 1) + { + while (--depth > 0) + { + int idx = s.IndexOf('\\'); + if (idx > 0) s = s.Substring(idx + 1); + } + } + return s; + } + + private IEnumerable children; + + /// + /// Gets all child of this NodeBase. + /// + public virtual IEnumerable Children + { + get { return children; } + set + { + children = value; + foreach (var child in children) child.ParentNode = this; + } + } + + } +} \ No newline at end of file diff --git a/PasswordSafe/PasswordSafe.Data/Biz/NotifyEventArgs.cs b/PasswordSafe/PasswordSafe.Data/Biz/NotifyEventArgs.cs new file mode 100644 index 0000000..383d4c8 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/NotifyEventArgs.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PasswordSafe.Data.Biz +{ + /// + /// EventArgs for . + /// + /// + public class NotifyEventArgs : EventArgs + { + public NotifyEventArgs(ChangedEventType type, T item) + : base() + { + Type = type; + Item = item; + } + + /// + /// Gets the change type. + /// + public ChangedEventType Type { get; private set; } + + /// + /// Gets the item that has changed, otherwise null + /// + public T Item { get; private set; } + } + + /// + /// Enumeration of possible changes. + /// + public enum ChangedEventType + { + Added, + Removed, + Modified, + Reset + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Biz/NotifyList.cs b/PasswordSafe/PasswordSafe.Data/Biz/NotifyList.cs new file mode 100644 index 0000000..847ed22 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/NotifyList.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ComponentModel; +using System.Diagnostics; + +namespace PasswordSafe.Data.Biz +{ + + /// + /// A List that supports notifications when the list or any item has changed. + /// + /// The type of item in this list. + public class NotifyList : BindingList where T : BaseObject + { + public NotifyList() + : base() + { + } + + public NotifyList(IEnumerable array) + : base() + { + this.RaiseListChangedEvents = false; + foreach (var item in array.ToArray()) + { + this.Add(item); + } + this.RaiseListChangedEvents = true; + } + + + public void MoveUp(T item) + { + int oldIndex = IndexOf(item); + int newIndex = Math.Max(0, oldIndex - 1); + ChangePosition(item, oldIndex, newIndex); + } + + private void ChangePosition(T item, int oldIndex, int newIndex) + { + if (oldIndex != newIndex) + { + RaiseListChangedEvents = false; + RemoveAt(oldIndex); + Insert(newIndex, item); + RaiseListChangedEvents = true; + // OnChanged(ChangedEventType.Reset, null); + ResetBindings(); + OnChanged(ChangedEventType.Reset, null); + } + } + + public void MoveDown(T item) + { + int oldIndex = IndexOf(item); + int newIndex = Math.Min(Count - 1, oldIndex + 1); + ChangePosition(item, oldIndex, newIndex); + } + + public void MoveToTop(T item) + { + int oldIndex = IndexOf(item); + ChangePosition(item, oldIndex, 0); + } + + public void MoveToBottom(T item) + { + int oldIndex = IndexOf(item); + ChangePosition(item, oldIndex, Count - 1); + } + + protected void OnChanged(ChangedEventType type, T item) + { + // if (RaiseListChangedEvents) + { + if (Changed != null) Changed(this, new NotifyEventArgs(type, item)); + } + } + + /// + /// Occurs when the list has changed. + /// + public event EventHandler> Changed; + + protected override void RemoveItem(int index) + { + T item = this[index]; + base.RemoveItem(index); + if (RaiseListChangedEvents) OnChanged(ChangedEventType.Removed, item); + } + + + protected override void InsertItem(int index, T item) + { + base.InsertItem(index, item); + if (RaiseListChangedEvents) OnChanged(ChangedEventType.Added, this[index]); + } + + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Biz/Password.cs b/PasswordSafe/PasswordSafe.Data/Biz/Password.cs new file mode 100644 index 0000000..31df954 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/Password.cs @@ -0,0 +1,304 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ComponentModel; +using System.Diagnostics; + +namespace PasswordSafe.Data.Biz +{ + public class Password : BaseObject, IDisposable + { + /// + /// Creates a new instance. + /// + public Password(Category category) + : base() + { + this.category = category; + } + + /// + /// Creates a new instance. + /// + public Password(Category category, int id, string name) + : base(id, name) + { + this.category = category; + } + + /// + /// Creates a new instance. + /// + public Password(int categoryId, int id, string name) + : base(id, name) + { + this.category = Context.GetCategory(categoryId); + } + + #region IDisposable Members + + public void Dispose() + { + ClearFields(); + } + + #endregion + + protected override void OnModifiedChanged() + { + base.OnModifiedChanged(); + } + + + private Category category; + + /// + /// Gets or sets the category of this password. + /// + public Category Category + { + get { return category; } + set + { + if (category != value) + { + Category oldCategory = category; + oldCategory.Passwords.RaiseListChangedEvents = false; + oldCategory.Passwords.Remove(this); + category = value; + category.Passwords.RaiseListChangedEvents = false; + category.Passwords.Add(this); + oldCategory.Passwords.RaiseListChangedEvents = true; + category.Passwords.RaiseListChangedEvents = true; + category.ResetPasswords(); + oldCategory.ResetPasswords(); + OnPropertyChanged("Category"); + } + } + } + + public string CategoryPath + { + get + { + if (Category == null) return string.Empty; + string path = Category.Path; + int index = path.IndexOf('\\'); + if (index > 0) path = path.Substring(index + 1); + return path; + } + + set + { + if (CategoryPath != value) + { + SetCategoryByPath(value); + } + } + } + + private void SetCategoryByPath(string value) + { + if (!string.IsNullOrEmpty(value)) + { + // first get the root: + Category category = Category; + while (category.Parent is Category) category = category.Parent; + + string[] names = value.Split('\\'); + + foreach (string name in names) + { + category = GetCategoryByName(category, name); + if (category == null) break; + } + if (category != null) + { + Category = category; + } + } + } + + private Category GetCategoryByName(Category category, string name) + { + return category.Categories.Where(c => c.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); + } + + private NotifyList folders; + private NotifyList fields; + + public NotifyList Folders + { + get + { + if (folders == null) + { + lock (this) + { + if (folders == null) + { + folders = Id != 0 ? new NotifyList(Context.GetFolders(Id)) : new NotifyList(); + folders.Changed += new EventHandler>(OnFoldersChanged); + } + } + } + return folders; + } + } + + /// + /// Gets whether this password is added to the favorites. + /// + public bool IsFavorite + { + get + { + return Folders.Any(); + } + } + + void OnFoldersChanged(object sender, NotifyEventArgs e) + { + OnPropertyChanged("Folders"); + switch (e.Type) + { + case ChangedEventType.Added: + e.Item.Passwords.Add(this); + break; + + case ChangedEventType.Removed: + e.Item.Passwords.Remove(this); + break; + } + } + + /// + /// Gets a list with all fields of this password. + /// + public NotifyList Fields + { + get + { + if (fields == null) + { + fields = Id == 0 ? new NotifyList() : new NotifyList(Context.GetFields(this)); + + fields.Changed += new EventHandler>(OnFieldsChanged); + } + return fields; + } + } + + private void OnFieldsChanged(object sender, NotifyEventArgs e) + { + OnPropertyChanged("Fields"); + } + + + /// + /// Gets an enumeration of associated to this password. + /// + public IEnumerable PasswordFolders + { + get + { + // this list is temporarily for good reasons: + // since the folders might change this list is recreated every time to reflect the actual hierarchy of folders. + return GetPasswordFolders(); + } + } + + private NotifyList GetPasswordFolders() + { + return new NotifyList(Context.Folders.Select(f => new PasswordFolder(f, this))); + //foreach (var folder in Context.Folders) + //{ + // yield return new PasswordFolder(folder, this); + //} + } + + /// + /// Deletes the password from datastore. + /// + public void Delete() + { + Category.Passwords.Remove(this); + + foreach (var folder in Folders) + { + + folder.Passwords.Remove(this); + } + Folders.Clear(); + Context.DeletePassword(this); + } + + /// + /// Saves the changes to datastore. + /// + public void Save() + { + Context.SavePassword(this); + IsModified = false; + } + + /// + /// Undos the changes made. + /// + public void Undo() + { + Context.UndoPassword(this); + IsModified = false; + } + + protected internal override void Reset() + { + foreach (var folder in Folders) + { + folder.ResetPasswords(); + } + + fields = null; + folders = null; + OnPropertyChanged("Fields"); + OnPropertyChanged("Folders"); + OnPropertyChanged("Name"); + OnPropertyChanged("IsFavorite"); + OnPropertyChanged("PasswordFolders"); + base.Reset(); + } + + /// + /// Adds a new to the password. + /// + /// The type of the new + /// The name of the new + public void AddField(FieldType type, string name) + { + Field field = new Field(this, 0, name, type, null, (short)Fields.Count); + this.Fields.Add(field); + } + + /// + /// Clears all fields from this password. + /// + private void ClearFields() + { + if (fields != null) + { + foreach (Field field in fields) + { + field.Dispose(); + } + fields.Clear(); + fields = null; + } + } + + public Field this[int index] + { + get { return Fields[index]; } + } + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Biz/PasswordFolder.cs b/PasswordSafe/PasswordSafe.Data/Biz/PasswordFolder.cs new file mode 100644 index 0000000..f24c912 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/PasswordFolder.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Diagnostics; + +namespace PasswordSafe.Data.Biz +{ + /// + /// Represents a link of one n:m relation between and + /// This is a helper class for UI. + /// + public class PasswordFolder:BaseObject + { + public PasswordFolder(Folder folder, Password password) + : base() + { + this.Folder = folder; + this.Password = password; + } + + public PasswordFolder(Folder folder, Password password, PasswordFolder parent) + : base() + { + this.Folder = folder; + this.Password = password; + this.Parent = parent; + } + + public PasswordFolder Parent { get; private set; } + + + protected override void OnPropertyChanged(string propertyName) + { + if (propertyName == "Folders") + { + folders = null; + OnPropertyChanged("IsFavorite"); + } + base.OnPropertyChanged(propertyName); + } + + /// + /// Gets the + /// + public Password Password { get; private set; } + + /// + /// Gets the + /// + public Folder Folder { get; private set; } + + /// + /// Gets the name of the . + /// + public override string Name { get { return Folder.Name; } } + + private IEnumerable folders; + + /// + /// Gets an enumeration of all child s otherwise null. + /// + public IEnumerable Folders + { + get + { + if (folders == null) + { + folders = Folder.Folders.Select(x => new PasswordFolder(x, Password, this)).ToArray(); + } + return folders; + } + } + + /// + /// Gets sets whether the link is a favorite. + /// + public bool IsFavorite + { + get + { + return Password.Folders.Where(f => f.Id == Folder.Id).Any(); + } + set + { + if (IsFavorite != value) + { + + bool isPasswordFavorite = Password.IsFavorite; + Folder folder = Folder; + if (folder != null) + { + if (!value) + { + folder.Passwords.Remove(Password); + Password.Folders.Remove(folder); + } + else + { + folder.Passwords.Add(Password); + Password.Folders.Add(folder); + } + OnPropertyChanged("IsFavorite"); + + if (!value) + { + ForwardIsFaovriteChanged(); + } + else + { + if (Parent != null) Parent.IsFavorite = true; + } + } + Password.IsModified = true; + + //// allways notify, even if IsFavorite has not changed just to mark Password as being modified: + Password.RaisePropertyChanged("IsFavorite"); + folder.RaisePropertyChanged("PasswordFolders"); + } + } + } + + + private void ForwardIsFaovriteChanged() + { + foreach (PasswordFolder folder in this.Folders) + { + folder.IsFavorite = false; + } + } + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Biz/TemplateField.cs b/PasswordSafe/PasswordSafe.Data/Biz/TemplateField.cs new file mode 100644 index 0000000..13c4d1b --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Biz/TemplateField.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PasswordSafe.Data.Biz +{ + /// + /// Represents a field for a categegory template. + /// + public class TemplateField : BaseObject + { + /// + /// Creates a new . + /// + /// The id from the database, 0 if not attaced. + /// The name of the field. + /// The type of the field. + /// + public TemplateField(int id, string name, FieldType type, Category category, string minRange, string maxRange, short order) + : base(id, name) + { + this.Category = category; + this.Type = type; + this.minRange = minRange; + this.minRange = maxRange; + this.order = order; + } + + private bool isSelected; + + public bool IsSelected + { + get { return isSelected; } + set + { + if (isSelected != value) + { + isSelected = value; + OnPropertyChanged("IsSelected"); + } + } + } + + + protected override void OnPropertyChanged(string propertyName) + { + base.OnPropertyChanged(propertyName); + if (propertyName != "IsSelected") + { + Category.IsModified = true; + Category.RaisePropertyChanged("Fields"); + } + } + + private string minRange; + private string maxRange; + + public string MinRange + { + get { return FormatRange(minRange); } + set + { + if (minRange != value) + { + minRange = value; + OnPropertyChanged("MinRange"); + } + } + } + + public string MaxRange + { + get { return FormatRange(maxRange); } + + set + { + if (maxRange != value) + { + maxRange = value; + OnPropertyChanged("MaxRange"); + } + } + } + + private static DateTime? ToDateTime(string value) + { + if (value == null) return null; + DateTime result; + if (DateTime.TryParse(value, out result)) return result; + return null; + } + + private static int? ToInt(string value) + { + if (value == null) return null; + int result; + if (int.TryParse(value,out result)) return result; + return null; + } + + + private string FormatRange(string value) + { + if (value == null) return null; + switch (Type) + { + case FieldType.Date: + DateTime? dt = ToDateTime(value); + return dt.HasValue ? dt.Value.ToShortDateString() : ""; + + case FieldType.Time: + dt = ToDateTime(value); + return dt.HasValue ? dt.Value.ToLongTimeString() : ""; + + case FieldType.Int: + int? ival = ToInt(value); + return ival.HasValue ? ival.Value.ToString() : ""; + + + default: + return value; + + } + } + + /// + /// Gets the . + /// + protected internal override BizContext Context + { + get + { + return Category.Context; + } + } + + /// + /// Gets the . + /// + public Category Category { get; private set; } + + /// + /// Gets the . + /// + public FieldType Type { get; private set; } + + + + public void Top() + { + Category.Fields.MoveToTop(this); + } + + public void Bottom() + { + Category.Fields.MoveToBottom(this); + } + + public void Up() + { + Category.Fields.MoveUp(this); + + } + + public void Down() + { + Category.Fields.MoveDown(this); + } + + + public void Delete() + { + Category.Fields.Remove(this); + } + } +} diff --git a/PasswordSafe/PasswordSafe.Data/DAL/DAL.cs b/PasswordSafe/PasswordSafe.Data/DAL/DAL.cs new file mode 100644 index 0000000..16bc28d --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/DAL/DAL.cs @@ -0,0 +1,667 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PasswordSafe.Data.Biz; +using System.Data.SqlServerCe; +using System.Data; +using System.Diagnostics; +using System.Data.SqlClient; + +namespace PasswordSafe.Data.DAL +{ + /// + /// Data Access Layer to exchange data from business objects with the Database. + /// + internal class Context : IDisposable + { + internal Context(string connectionString) + : base() + { + if (connectionString != null) Open(connectionString); + } + + private void Open(string connectionString) + { + if (connection != null) + { + connection.Close(); + connection.Dispose(); + } + if (!string.IsNullOrEmpty(connectionString)) + { + connection = new SqlCeConnection(connectionString); + } + if (connection != null && connection.State == ConnectionState.Closed) connection.Open(); + } + + + #region IDisposable Members + + public void Dispose() + { + if (connection != null) + { + connection.Close(); + connection.Dispose(); + } + } + + #endregion + + public bool IsConnected + { + get + { + return Connection != null && Connection.State == ConnectionState.Open; + } + } + + private SqlCeConnection connection; + + /// + /// Gets the Connection String for the database. + /// + public SqlCeConnection Connection + { + get + { + return connection; + } + } + + /// + /// Executes a T-SQL with parameters. + /// + /// The T-SQL command to execute. + /// The parameters. + /// The number data affected. + protected int Execute(string command, params object[] param) + { + if (Connection.State == System.Data.ConnectionState.Closed) Connection.Open(); + using (SqlCeCommand cmd = Connection.CreateCommand()) + { + cmd.CommandText = command; + int id = 1; + foreach (object value in param) + { + cmd.Parameters.AddWithValue("@v" + id.ToString(), value != null ? value : DBNull.Value); + id++; + } + return cmd.ExecuteNonQuery(); + } + } + + /// + /// Reads data from database and converts it into business objects. + /// + /// The type of business object. + /// The T-SQL command to read from database. + /// A that converts a to . + /// The parameters for the T-SQL. + /// An enumeration of + protected IEnumerable ReadData(string command, Func convert, params object[] param) + { + if (Connection.State == System.Data.ConnectionState.Closed) Connection.Open(); + lock (this) + { + using (DataTable table = new DataTable()) + { + using (SqlCeCommand cmd = Connection.CreateCommand()) + { + cmd.CommandText = command; + int id = 1; + foreach (object value in param) + { + cmd.Parameters.AddWithValue("@v" + id.ToString(), value); + id++; + } + using (SqlCeDataAdapter adapter = new SqlCeDataAdapter(cmd)) + { + adapter.Fill(table); + } + } + return table.AsEnumerable().Select(f => convert(f)); + } + } + } + + private int GetIdentity() + { + if (Connection.State == System.Data.ConnectionState.Closed) Connection.Open(); + using (SqlCeCommand cmd = Connection.CreateCommand()) + { + cmd.CommandText = "select @@IDENTITY;"; + return (int)(decimal)cmd.ExecuteScalar(); + } + } + + public void CreateFavorite(int passwordId, int folderId) + { + int n = Execute("insert into Favorite (PasswordId,folderId) VALUES (@v1,@v2)", passwordId, folderId); + if (n != 1) throw new DBEntityNotUpdatedException(); + } + + public void DeleteFavorite(int passwordId, int folderId) + { + int n = Execute("delete from Favorite where passwordId=@v1 and folderId=@v2", passwordId, folderId); + if (n != 1) throw new DBEntityNotUpdatedException(); + + } + + public IEnumerable GetExistingFaves(int passwordId) + { + + return ReadData("select FolderId from Favorite where PasswordId=@v1", + r => (int)r[0], + passwordId); + } + + private string TableNameFromFieldType(FieldType type) + { + switch (type) + { + case FieldType.Text: + case FieldType.Password: + return "StringField"; + + case FieldType.Int: + case FieldType.Bool: + case FieldType.Separator: + return "IntField"; + + case FieldType.Date: + case FieldType.Time: + return "DateField"; + + case FieldType.Memo: + return "MemoField"; + + default: + throw new NotImplementedException(); + } + } + + public void UpdateField(Field field) + { + string sql = string.Format("update {0} set Name=@v1, [Value]=@v2,[Order]=@v3", TableNameFromFieldType(field.Type)); + + object p1 = null; + object p2 = null; + if (field.HasRange) + { + sql += ",[MinRange]=@v5, [MaxRange]=@v6"; + p1 = CheckOptValue(field.MinRange, field); + p2 = CheckOptValue(field.MaxRange, field); + } + if (field.HasLines) + { + sql += ",[MinLines]=@v5,[MaxLines]=@v6"; + p1 = field.MinLines; + p2 = field.MaxLines; + } + sql += " where Id=@v4"; + + int n = Execute( + sql, + field.Name, CheckValue(field.Value, field), field.Order, field.Id, p1, p2); + if (n != 1) throw new DBEntityNotUpdatedException(); + } + + public void UpdateTemplateField(TemplateField field) + { + string sql = "update Template set [Type]=@v1, [Name]=@v2, [Order]=@v3, [MinRange]=@v4, [MaxRange]=@v5 where [Id]=@v6"; + + int n = Execute( + sql, + (int)field.Type, field.Name, field.Order, field.MinRange, field.MaxRange, field.Id); + if (n != 1) throw new DBEntityNotUpdatedException(); + } + + public int CreateTemplateField(TemplateField field) + { + int n = Execute( + "Insert into Template ([Name], [CategoryId], [Type], [Order]) values (@v1, @v2, @v3, 3200)", + field.Name, field.Category.Id, (int)field.Type, field.Order); + + if (n != 1) throw new DBEntityNotUpdatedException(); + int id = GetIdentity(); + field.Id = id; + return id; + } + + private readonly DateTime minDate = new DateTime(1753, 1, 1); + private readonly DateTime maxDate = new DateTime(9999, 12, 31); + + private object CheckValue(object value, Field field) + { + switch (field.Type) + { + case FieldType.Text: + case FieldType.Password: + case FieldType.Memo: + if (value is string) return (string)value; else return ""; + + case FieldType.Separator: + case FieldType.Int: + if (value is int) return (int)value; else return default(int); + + case FieldType.Bool: + if (value is int) + { + return ((int)value) == 0 ? 0 : 1; + } + else return 0; + + case FieldType.Time: + case FieldType.Date: + if (value is DateTime) + { + DateTime dt = (DateTime)value; + if (dt < minDate) dt = minDate; + if (dt > maxDate) dt = maxDate; + return dt; + } + else return maxDate; + + default: + throw new NotImplementedException(); + } + } + + private object CheckOptValue(object value, Field field) + { + switch (field.Type) + { + case FieldType.Text: + case FieldType.Password: + case FieldType.Memo: + if (value is string) return (string)value; else return null; + + case FieldType.Separator: + case FieldType.Int: + if (value is int) return (int)value; else return null; + + + case FieldType.Time: + case FieldType.Date: + if (value is DateTime) + { + DateTime dt = (DateTime)value; + if (dt < minDate) dt = minDate; + if (dt > maxDate) dt = maxDate; + return dt; + } + else return null; + + default: + return null; + } + } + + public int CreateField(Field field) + { + int n = Execute( + string.Format("Insert into {0} (Name,[Value],PasswordId,Type,[Order]) values (@v1,@v2,@v3,@v4,@v5)", TableNameFromFieldType(field.Type)), + field.Name, CheckValue(field.Value, field), field.Password.Id, (short)field.Type, (short)field.Order); + + if (n != 1) throw new DBEntityNotUpdatedException(); + return GetIdentity(); + } + + public void DeleteField(int id, FieldType type) + { + int n = Execute( + string.Format("delete from {0} where Id=@v1", TableNameFromFieldType(type)), + id); + } + + public IEnumerable GetFieldIds(int passwordId, FieldType type) + { + return ReadData( + string.Format("select Id from {0} where PasswordId=@v1 order by [Order]", TableNameFromFieldType(type)), + r => r.Field("Id"), + passwordId); + } + + private Category ToCategory(DataRow row, Category parent) + { + return new Category( + row.Field("Id"), + row.Field("Name"), + row.Field("Order"), + parent); + } + + private Category ToCategory2(DataRow row) + { + return new Category( + row.Field("Id"), + row.Field("Name"), + row.Field("Order"), + row.Field("ParentId")); + } + + private Password ToPassword(DataRow row, Category category) + { + return new Password( + category, + row.Field("Id"), + row.Field("Name")); + } + + private Password ToPassword2(DataRow row) + { + return new Password( + row.Field("CategoryId"), + row.Field("Id"), + row.Field("Name")); + } + + + public Folder ToFolder(DataRow row, Folder parent) + { + var folder = new Folder( + row.Field("Id"), + row.Field("Name"), + parent); + + folder.Order = row.Field("Order"); + return folder; + } + + public Folder ToFolder2(DataRow row) + { + var folder = new Folder( + row.Field("Id"), + row.Field("Name"), + row.Field("ParentId")); + folder.Order = row.Field("Order"); + return folder; + } + + + public Field ToField(DataRow row, Password password) + { + Field field = new Field(password, + row.Field("Id"), + row.Field("Name"), + (FieldType)row.Field("Type"), + row["Value"], + row.Field("Order")); + + if (field.HasRange) + { + try + { + field.SetRange(row["MinRange"], row["MaxRange"]); + } + catch (Exception) + { + } + } + if (field.HasLines) + { + try + { + field.SetLines(row.Field("MinLines"), row.Field("MaxLines")); + } + catch (Exception) + { + } + } + return field; + } + + public TemplateField ToTemplateField(DataRow row, Category category) + { + return new TemplateField( + row.Field("Id"), + row.Field("Name"), + (FieldType)row.Field("Type"), + category, + row.Field("MinRange"), + row.Field("MaxRange"), + row.Field("Order") + ); + } + + public IEnumerable GetRootCategories() + { + if (!IsDbAvailable()) return Enumerable.Empty(); + return ReadData( + "select * from Category where ParentId is null order by [Order],Name", + r => ToCategory(r, null)); + } + + private bool IsDbAvailable() + { + return IsConnected; + } + + public IEnumerable GetCategories(Category parent) + { + return ReadData( + "select * from Category where ParentId=@v1 order by [Order],Name", + r => ToCategory(r, parent), + parent.Id); + } + + public void UpdatePassword(Password password) + { + int n = Execute("update Password set Name=@v1,Modified=@v2, [Order]=@v3 where Id=@v4", + password.Name, + DateTime.Now, + password.Order, + password.Id); + if (n != 1) throw new DBEntityNotUpdatedException(); + } + + public IEnumerable GetPasswordsByCategory(Category category) + { + return ReadData("select * from Password where CategoryId=@v1 order by [Order],Name", + r => ToPassword(r, category), + category.Id); + } + + public IEnumerable GetFolderByPasswordId(int passwordId) + { + return ReadData("select f.* from Folder as f join Favorite as x on x.FolderId=f.Id where x.PasswordId=@v1", + r => ToFolder2(r), passwordId); + } + + public IEnumerable GetFoldersByParentFolder(Folder parent) + { + return ReadData("select * from Folder where ParentId=@v1 order by [Order],Name", + r => ToFolder(r, parent), + parent.Id); + } + + public IEnumerable GetPasswordsByFolderId(int folderId) + { + return ReadData("select p.* from Password as p join Favorite as f on f.PasswordId=p.Id where f.FolderId=@v1", + r => ToPassword2(r), + folderId); + } + + public void DeletePassword(int passwordId) + { + using (var transaction = Connection.BeginTransaction()) + { + Execute("delete from StringField where PasswordId=@v1", passwordId); + Execute("delete from IntField where PasswordId=@v1", passwordId); + Execute("delete from MemoField where PasswordId=@v1", passwordId); + Execute("delete from DateField where PasswordId=@v1", passwordId); + Execute("delete from Favorite where PasswordId=@v1", passwordId); + int n = Execute("delete from Password where Id=@v1", passwordId); + // if (n != 1) throw new DBEntityNotUpdatedException(); + transaction.Commit(); + } + } + + public IEnumerable GetRootFolders() + { + + if (!IsDbAvailable()) return Enumerable.Empty(); + return ReadData("select * from Folder where ParentId is null order by [Order],Name", + r => ToFolder(r, null)); + } + + public IEnumerable GetFields(Password password) + { + var f1 = GetFields(password, FieldType.Text).ToArray(); + var f2 = GetFields(password, FieldType.Int).ToArray(); + var f3 = GetFields(password, FieldType.Date).ToArray(); + var f4 = GetFields(password, FieldType.Memo).ToArray(); + + var result = f1.Union(f2).Union(f2).Union(f3).Union(f4).OrderBy(f => f.Order).ToArray(); + return result; + } + + private IEnumerable GetFields(Password password, FieldType fieldType) + { + return ReadData(string.Format("select * from {0} where PasswordId=@v1", TableNameFromFieldType(fieldType)), + r => ToField(r, password), + password.Id); + } + + public Category GetCategoryById(int categoryId) + { + var c = ReadData("select * from Category where Id=@v1", + r => ToCategory2(r), + categoryId); + + return c.FirstOrDefault(); + } + + public Folder GetFolderById(int folderId) + { + var folders = ReadData("select * from Folder where Id=@v1", + r => ToFolder2(r), + folderId); + + return folders.FirstOrDefault(); + } + + public Folder CreateFolder(Folder folder) + { + int n = Execute("insert into Folder ([Order], [ParentId]) values (3200, @v1)", folder.Parent.Id); + if (n != 1) throw new DBEntityNotUpdatedException(); + int id = GetIdentity(); + folder.Id = id; + return folder; + } + + public void DeleteFolder(int folderId) + { + using (var transaction = Connection.BeginTransaction()) + { + Execute("delete from Favorite where FolderId=@v1", folderId); + Execute("delete from Folder where Id=@v1", folderId); + transaction.Commit(); + } + } + + public Category CreateCategory(Category category) + { + int n = Execute("insert into Category ([Order], [ParentId], [Name]) values (@v1, @v2, @v3)", (short)category.Order, category.Parent.Id, category.Name); + if (n != 1) throw new DBEntityNotUpdatedException(); + int id = GetIdentity(); + category.Id = id; + return category; + } + + public void DeleteCategory(int categoryId) + { + using (var scope = Connection.BeginTransaction()) + { + Execute("delete from Template where [CategoryId]=@v1", categoryId); + Execute("delete from Category where [Id]=@v1", categoryId); + scope.Commit(); + } + } + + + public void CreatePassword(Password password) + { + DateTime now = DateTime.Now; + int n = Execute("insert into Password (Name,CategoryId,Created,Modified,[Order]) values(@v1,@v2,@v3,@v4,32700)", + password.Name, + password.Category.Id, + now, now); + + if (n != 1) throw new DBEntityNotUpdatedException(); + password.Id = GetIdentity(); + } + + public IEnumerable GetTemplates(Category category) + { + return ReadData("select * from Template where CategoryId=@v1", + r => ToTemplateField(r, category), + category.Id).OrderBy(f => f.Order); + } + + public void DeleteTemplateField(int id) + { + Execute("delete from Template where Id=@v1", id); + } + + public Password GetPassword(int passwordId) + { + return ReadData("select * from Password where Id=@v1", + r => ToPassword2(r), + passwordId).FirstOrDefault(); + } + + public IDbTransaction BeginTransaction() + { + return Connection.BeginTransaction(); + } + + public void UpdateCategory(Category category) + { + int n = Execute("update Category set Name=@v1, [Order]=@v2 where Id=@v3", + category.Name, + category.Order, + category.Id); + if (n != 1) throw new DBEntityNotUpdatedException(); + } + + internal void UpdateFolder(Folder folder) + { + int n = Execute("update Folder set Name=@v1, [Order]=@v2 where Id=@v3", + folder.Name, + folder.Order, + folder.Id); + if (n != 1) throw new DBEntityNotUpdatedException(); + } + + public IEnumerable GetTemplateFields(int categoryId) + { + return ReadData( + "select Id from Template where CategoryId=@v1", + row => row.Field("Id"), + categoryId); + } + + internal bool ChangePassword(string connectionString, string oldPassword, string newPassword) + { + try + { + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString); + builder.Password = oldPassword; + SqlCeConnection con = new SqlCeConnection(builder.ConnectionString); + SqlCeEngine engine = new SqlCeEngine(builder.ConnectionString); + builder.Encrypt = true; + + builder.Password = newPassword; + engine.Compact(builder.ConnectionString); + return true; + } + catch (Exception) + { + return false; + } + } + } +} diff --git a/PasswordSafe/PasswordSafe.Data/DAL/DBEntityNotUpdatedException.cs b/PasswordSafe/PasswordSafe.Data/DAL/DBEntityNotUpdatedException.cs new file mode 100644 index 0000000..eb76685 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/DAL/DBEntityNotUpdatedException.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PasswordSafe.Data.DAL +{ + public class DBEntityNotUpdatedException : ArgumentNullException + { + } +} diff --git a/PasswordSafe/PasswordSafe.Data/PasswordSafe.Data.csproj b/PasswordSafe/PasswordSafe.Data/PasswordSafe.Data.csproj new file mode 100644 index 0000000..2e86fd5 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/PasswordSafe.Data.csproj @@ -0,0 +1,157 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {E4DA0115-54F3-4DA0-813B-CDEBF4D205E5} + Library + Properties + PasswordSafe.Data + PasswordSafe.Data + v3.5 + 512 + SAK + SAK + SAK + SAK + + + false + + + + + 3.5 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + + + 3.5 + + + + + + 3.5 + + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + False + .NET Framework Client Profile + false + + + False + .NET Framework 2.0 %28x86%29 + false + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + false + + + False + .NET Framework 3.5 SP1 + true + + + False + SQL Server Compact 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + \ No newline at end of file diff --git a/PasswordSafe/PasswordSafe.Data/Properties/AssemblyInfo.cs b/PasswordSafe/PasswordSafe.Data/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4de3678 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PasswordSafe.Data")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("www.tomssoftware.net")] +[assembly: AssemblyProduct("PasswordSafe.Data")] +[assembly: AssemblyCopyright("Copyright © Thomas Gerber 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("abd75e9a-48e9-4216-8823-6015d8e65e35")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.7.1.*")] +[assembly: AssemblyFileVersion("0.7.1.200")] diff --git a/PasswordSafe/PasswordSafe.Data/Properties/Resources.Designer.cs b/PasswordSafe/PasswordSafe.Data/Properties/Resources.Designer.cs new file mode 100644 index 0000000..a955ba5 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.20506.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PasswordSafe.Data.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", "4.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("PasswordSafe.Data.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; + } + } + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Properties/Resources.resx b/PasswordSafe/PasswordSafe.Data/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/PasswordSafe/PasswordSafe.Data/Properties/Settings.Designer.cs b/PasswordSafe/PasswordSafe.Data/Properties/Settings.Designer.cs new file mode 100644 index 0000000..cbe598c --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.20506.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PasswordSafe.Data.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/PasswordSafe/PasswordSafe.Data/Properties/Settings.settings b/PasswordSafe/PasswordSafe.Data/Properties/Settings.settings new file mode 100644 index 0000000..abf36c5 --- /dev/null +++ b/PasswordSafe/PasswordSafe.Data/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/PasswordSafe/PasswordSafe/App.xaml b/PasswordSafe/PasswordSafe/App.xaml new file mode 100644 index 0000000..71ad6d6 --- /dev/null +++ b/PasswordSafe/PasswordSafe/App.xaml @@ -0,0 +1,5 @@ + + + + diff --git a/PasswordSafe/PasswordSafe/App.xaml.cs b/PasswordSafe/PasswordSafe/App.xaml.cs new file mode 100644 index 0000000..cda9a6b --- /dev/null +++ b/PasswordSafe/PasswordSafe/App.xaml.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Windows; + +namespace PasswordSafe +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/PasswordSafe/PasswordSafe/Classes/UIContext.cs b/PasswordSafe/PasswordSafe/Classes/UIContext.cs new file mode 100644 index 0000000..fef2cc9 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Classes/UIContext.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PasswordSafe.Data.Biz; + +namespace PasswordSafe.Classes +{ + /// + /// Gets that from and is used by s. + /// + public class UIContext + { + private BizContext context + { + get + { + return BizContext.Instance; + } + } + + /// + /// Gets the root . + /// + public Category RootCategory + { + get + { + return context.Categories.FirstOrDefault(); + } + } + + /// + /// Gets the root . + /// + public Folder RootFolder + { + get + { + return context.Folders.FirstOrDefault(); + } + } + + + /// + /// Gets an enumeration of all root s. + /// + public IEnumerable GetCategories() + { + return context.Categories; + } + + /// + /// Gets an enumeration of all root s. + /// + public IEnumerable GetFolders() + { + return context.Folders; + } + + /// + /// Gets an enumeration of all s. + /// + /// An enumeration of see cref="T:FieldType"/>. + public IEnumerable Types() + { + return Enum.GetValues(typeof(FieldType)).Cast(); + } + + private NodeBase breadcrumbRoot; + + /// + /// Get the root Node of type for the main breadcrumb. + /// + /// An enumeration of see cref="T:NodeBase"/>. + public NodeBase GetBreadcrumbRoot() + { + lock (this) + { + if (breadcrumbRoot == null) + { + if (RootFolder != null && RootCategory != null) + { + breadcrumbRoot = new CustomNode( + "Home", + new NodeBase[] + { + RootFolder, + RootCategory + } + ); + } + else return null; + } + return breadcrumbRoot; + } + } + + + /// + /// Gets the root Category. + /// + /// The root Category. + public Category GetCategoryRoot() + { + return RootCategory; + } + + /// + /// Gets an enumeration of strings of all avaiable s used for + /// a breadcrumb bar. + /// + /// An enumeration of + public static IEnumerable GetBreadcrumbDropDownData(NodeBase root) + { + if (root != null && root.Children != null) + { + foreach (NodeBase node in root.Children) + { + yield return node.GetPath(1); + var subNodes = GetSubPath(node); + if (subNodes != null) + { + foreach (string subPath in subNodes) + { + yield return subPath; + } + } + } + } + } + + private static IEnumerable GetSubPath(NodeBase node) + { + foreach (NodeBase child in GetChildren(node)) + { + yield return child.GetPath(1); + foreach (string subPath in GetSubPath(child)) yield return subPath; + } + } + + private static IEnumerable GetChildren(NodeBase node) + { + Category c = node as Category; + if (c != null) return c.Categories.Cast(); + + Folder f = node as Folder; + if (f != null) return f.Folders.Cast(); + + CustomNode n = node as CustomNode; + return n.Children; + } + } +} diff --git a/PasswordSafe/PasswordSafe/Commands.cs b/PasswordSafe/PasswordSafe/Commands.cs new file mode 100644 index 0000000..4902e90 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Commands.cs @@ -0,0 +1,793 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Input; +using Odyssey.Controls; +using System.Windows; +using PasswordSafe.Controls; +using System.Data.Linq; +using System.Diagnostics; +using System.Windows.Controls; +using PasswordSafe.Classes; +using PasswordSafe.Data; +using PasswordSafe.Data.Biz; +using System.Windows.Controls.Primitives; +using PasswordSafe.Export; +using Microsoft.Win32; +using System.IO; + +namespace PasswordSafe.Controls +{ + public class Commands + { + public static readonly RoutedUICommand TestCommand = new RoutedUICommand("Test", "TestCommand", typeof(Main)); + public static readonly RoutedUICommand ExportCommand = new RoutedUICommand("Test", "ExportCommand", typeof(Main)); + + public static readonly RoutedUICommand SelectNextFieldCommand = new RoutedUICommand("Next", "SelectNextFieldCommand", typeof(Main)); + public static readonly RoutedUICommand SelectPreviousFieldCommand = new RoutedUICommand("Previous", "SelectPreviousFieldCommand", typeof(Main)); + + public static readonly RoutedUICommand LogoutCommand = new RoutedUICommand("Logout", "LogoutCommand", typeof(Main), + new InputGestureCollection(new KeyGesture[] { + new KeyGesture(Key.L,ModifierKeys.Control,"Ctrl+L")})); + + public static readonly RoutedUICommand ChangePasswordCommand = new RoutedUICommand("New", "ChangePasswordCommand", typeof(Main)); + public static readonly RoutedUICommand NewPasswordCommand = new RoutedUICommand("New", "NewPasswordCommand", typeof(Main)); + public static readonly RoutedUICommand DuplicateCommand = new RoutedUICommand("Duplicate", "DuplicateCommand", typeof(Main)); + + public static readonly RoutedUICommand SaveTemplateCommand = new RoutedUICommand("Save", "SaveTemplateCommand", typeof(Main), + new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.S, ModifierKeys.Control, "Ctrl+S") })); + + public static readonly RoutedUICommand SaveCommand = new RoutedUICommand("Save", "SaveCommand", typeof(Main), + new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.S, ModifierKeys.Control, "Ctrl+S") })); + + /// + /// Used for templates to copy the Value property of the DataContext of a FrameworkControl to clipboard. + /// + public static readonly RoutedUICommand CopyCommand = new RoutedUICommand("Copy", "CopyCommand", typeof(Main), + new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.C, ModifierKeys.Control, "Ctrl+C") })); + + public static readonly RoutedUICommand UndoAllCommand = new RoutedUICommand("Undo All", "UndoAllCommand", typeof(Main), + new InputGestureCollection(new KeyGesture[] { + new KeyGesture(Key.Z,ModifierKeys.Control|ModifierKeys.Shift,"Shift+Ctrl+Z"), + new KeyGesture(Key.U,ModifierKeys.Control|ModifierKeys.Shift,"Shift+Ctrl+U") + })); + + public static readonly RoutedUICommand SaveAllCommand = new RoutedUICommand("Save All", "SaveAllCommand", typeof(Main), + new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.S, ModifierKeys.Control | ModifierKeys.Shift, "Shift+Ctrl+S") })); + + public static readonly RoutedUICommand UndoTemplateCommand = new RoutedUICommand("Undo", "UndoTemplateCommand", typeof(Main), + new InputGestureCollection(new KeyGesture[] { + new KeyGesture(Key.Z,ModifierKeys.Control,"Ctrl+Z"), + new KeyGesture(Key.U,ModifierKeys.Control,"Ctrl+U") + })); + + public static readonly RoutedUICommand UndoCommand = new RoutedUICommand("Undo", "UndoCommand", typeof(Main), + new InputGestureCollection(new KeyGesture[] { + new KeyGesture(Key.Z,ModifierKeys.Control,"Ctrl+Z"), + new KeyGesture(Key.U,ModifierKeys.Control,"Ctrl+U") + })); + + + public static readonly RoutedUICommand DeleteCommand = new RoutedUICommand("Delete", "DeleteCommand", typeof(Main)); + public static readonly RoutedUICommand SelectItemCommand = new RoutedUICommand("Select", "SelectItemCommand", typeof(Main)); + public static readonly RoutedUICommand SelectTemplateItemCommand = new RoutedUICommand("Select", "SelectTemplateItemCommand", typeof(Main)); + public static readonly RoutedUICommand CopyUserNameCommand = new RoutedUICommand("Copy User Name", "CopyUserNameCommand", typeof(Main)); + public static readonly RoutedUICommand DataModifiedCommand = new RoutedUICommand("", "DataModifiedCommand", typeof(Main)); + + public static readonly RoutedUICommand FieldUpCommand = new RoutedUICommand("Up", "FieldUpCommand", typeof(Main)); + public static readonly RoutedUICommand FieldTopCommand = new RoutedUICommand("Down", "FieldDownCommand", typeof(Main)); + public static readonly RoutedUICommand FieldBottomCommand = new RoutedUICommand("Top", "FieldTopCommand", typeof(Main)); + public static readonly RoutedUICommand FieldDownCommand = new RoutedUICommand("Bottom", "FieldBottomCommand", typeof(Main)); + public static readonly RoutedUICommand FieldDeleteCommand = new RoutedUICommand("Delete", "FieldDeleteCommand", typeof(Main)); + public static readonly RoutedUICommand NewFieldCommand = new RoutedUICommand("New Field", "NewFieldCommand", typeof(Main), + new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.N, ModifierKeys.Control, "Ctrl+N") })); + + public static readonly RoutedUICommand NewTemplateFieldCommand = new RoutedUICommand("New Field", "NewTemplateFieldCommand", typeof(Main), + new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.N, ModifierKeys.Control, "Ctrl+N") })); + public static readonly RoutedUICommand DeleteTemplateFieldCommand = new RoutedUICommand("Delete", "DeleteTemplateFieldCommand", typeof(Main)); + + public static readonly RoutedUICommand CategoryUpCommand = new RoutedUICommand("Up", "CategoryUpCommand", typeof(Main)); + public static readonly RoutedUICommand CategoryTopCommand = new RoutedUICommand("Down", "CategoryTopCommand", typeof(Main)); + public static readonly RoutedUICommand CategoryBottomCommand = new RoutedUICommand("Top", "CategoryBottomCommand", typeof(Main)); + public static readonly RoutedUICommand CategoryDownCommand = new RoutedUICommand("Bottom", "CategoryDownCommand", typeof(Main)); + + public static readonly RoutedUICommand TemplateUpCommand = new RoutedUICommand("Up", "TemplateUpCommand", typeof(Main)); + public static readonly RoutedUICommand TemplateTopCommand = new RoutedUICommand("Down", "TemplateTopCommand", typeof(Main)); + public static readonly RoutedUICommand TemplateBottomCommand = new RoutedUICommand("Top", "TemplateBottomCommand", typeof(Main)); + public static readonly RoutedUICommand TemplateDownCommand = new RoutedUICommand("Bottom", "TemplateDownCommand", typeof(Main)); + + + public static readonly RoutedUICommand FolderUpCommand = new RoutedUICommand("Up", "FolderUpCommand", typeof(Main)); + public static readonly RoutedUICommand FolderTopCommand = new RoutedUICommand("Down", "FolderTopCommand", typeof(Main)); + public static readonly RoutedUICommand FolderBottomCommand = new RoutedUICommand("Top", "FolderBottomCommand", typeof(Main)); + public static readonly RoutedUICommand FolderDownCommand = new RoutedUICommand("Bottom", "FolderDownCommand", typeof(Main)); + + /// + /// Does nothing but changing IsEnabled. + /// + public static readonly RoutedUICommand PasswordSelectedCommand = new RoutedUICommand("", "PasswordSelectedCommand", typeof(Main)); + public static readonly RoutedUICommand FieldSelectedCommand = new RoutedUICommand("", "FieldSelectedCommand", typeof(Main)); + + /// + /// Toggles the IsFavorite state of a class which is the DataContext of a FrameworkControl. + /// This command is used for the TreeViews to change the favorite association of a . + /// + public static readonly RoutedUICommand ToggleFaveCommand = new RoutedUICommand("", "ToggleFaveCommand", typeof(Main)); + + public static readonly RoutedUICommand AddFolderCommand = new RoutedUICommand("Add Folder", "AddFolderCommand", typeof(Main)); + public static readonly RoutedUICommand DeleteFolderCommand = new RoutedUICommand("Delete Folder", "DeleteFolderCommand", typeof(Main)); + + public static readonly RoutedUICommand AddCategoryCommand = new RoutedUICommand("Add Category", "AddCategoryCommand", typeof(Main)); + public static readonly RoutedUICommand DeleteCategoryCommand = new RoutedUICommand("Delete Category", "DeleteCategoryCommand", typeof(Main)); + + static Commands() + { + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(TestCommand, Test)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(ExportCommand, Export)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(SelectNextFieldCommand, SelectNextPasswordField, IsPasswordSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(SelectPreviousFieldCommand, SelectPrevPasswordField, IsPasswordSelected)); + + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(LogoutCommand, Logout)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(ChangePasswordCommand, ChangePassword)); + + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(SaveAllCommand, SaveAll, IsAnyPasswordOrTemplateModified)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(UndoAllCommand, UndoAll, IsAnyPasswordOrTemplateModified)); + + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(SaveTemplateCommand, SaveTemplate, IsTemplateModified)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(UndoTemplateCommand, UndoTemplate, IsTemplateModified)); + + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(NewPasswordCommand, NewPassword)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(DuplicateCommand, Duplicate, IsPasswordSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(SaveCommand, SavePassword, IsPasswordModified)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(UndoCommand, UndoPassword, IsPasswordModified)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(DeleteCommand, DeletePassword, IsPasswordSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(SelectItemCommand, SelectItem)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(SelectTemplateItemCommand, SelectTemplateItem)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(CopyUserNameCommand, CopyUserName)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(DataModifiedCommand, DataModified)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(FieldUpCommand, FieldUp, IsFieldSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(FieldDownCommand, FieldDown, IsFieldSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(FieldTopCommand, FieldTop, IsFieldSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(FieldBottomCommand, FieldBottom, IsFieldSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(FieldDeleteCommand, FieldDelete, IsFieldSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(NewFieldCommand, NewField, IsPasswordSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(PasswordSelectedCommand, Nothing, IsPasswordSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(FieldSelectedCommand, Nothing, IsFieldSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(NewTemplateFieldCommand, NewTemplateField, IsCategorySelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(DeleteTemplateFieldCommand, DeleteTemplateField, IsTemplateSelected)); + + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(CategoryUpCommand, CategoryUp, IsCategorySelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(CategoryDownCommand, CategoryDown, IsCategorySelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(CategoryTopCommand, CategoryTop, IsCategorySelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(CategoryBottomCommand, CategoryBottom, IsCategorySelected)); + + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(TemplateUpCommand, TemplateUp, IsTemplateSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(TemplateDownCommand, TemplateDown, IsTemplateSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(TemplateTopCommand, TemplateTop, IsTemplateSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(TemplateBottomCommand, TemplateBottom, IsTemplateSelected)); + + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(FolderUpCommand, FolderUp, IsFolderSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(FolderDownCommand, FolderDown, IsFolderSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(FolderTopCommand, FolderTop, IsFolderSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(FolderBottomCommand, FolderBottom, IsFolderSelected)); + + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(CopyCommand, CopyField, IsCopyEnabled)); + + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(ToggleFaveCommand, ToggleFave)); + + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(AddFolderCommand, AddFolder, IsFolderSelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(DeleteFolderCommand, DeleteFolder, CanDeleteFolder)); + + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(AddCategoryCommand, AddCategory, IsCategorySelected)); + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(DeleteCategoryCommand, DeleteCategory, CanDeleteCategory)); + } + + + + private static void Nothing(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + } + + private static void SaveAll(object sender, ExecutedRoutedEventArgs e) + { + BizContext.Instance.Save(); + } + + private static void UndoAll(object sender, ExecutedRoutedEventArgs e) + { + BizContext.Instance.Undo(); + } + + private static void Export(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + SaveFileDialog saveDialog = new SaveFileDialog(); + saveDialog.DefaultExt = ".xml"; + saveDialog.Title = "Export to..."; + saveDialog.AddExtension = true; + if (saveDialog.ShowDialog(main).Value) + { + using (FileStream stream = new FileStream(saveDialog.FileName, FileMode.Create, FileAccess.Write)) + { + XmlExporter.Export(stream); + } + } + } + + + private static void SelectPrevPasswordField(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Password pw = main.SelectedPassword; + + + if (pw != null && pw.Fields.Count > 0) + { + int n = pw.Fields.Count - 1; + int index = n; + for (int i = n; i > 0; i--) + { + if (pw[i].IsSelected) + { + index = i - 1; + break; + } + } + main.SelectedPasswordField = pw[index]; + } + } + + + private static void SelectNextPasswordField(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Password pw = main.SelectedPassword; + + if (pw != null && pw.Fields.Count > 0) + { + int index = 0; + for (int i = 0; i < pw.Fields.Count - 1; i++) + { + if (pw[i].IsSelected) + { + index = i + 1; + break; + } + } + main.SelectedPasswordField = pw[index]; + } + } + + + private static void Logout(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + main.IsLocked = true; + main.DisplayType = DisplayType.Login; + } + + private static void AddCategory(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + + Category category = main.SelectedCategory; + if (category != null) + { + Category newCategory = category.AddCategory("New Category"); + if (newCategory != null) + { + main.SelectedCategory = newCategory; + main.FocusEditCategoryName(); + } + } + } + + private static void DeleteCategory(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + + Category category = main.SelectedCategory; + if (category != null) + { + category.Delete(); + main.SelectedCategory = category.Parent; + } + } + + private static void CanDeleteCategory(object sender, CanExecuteRoutedEventArgs e) + { + Main main = (Main)sender; + Category category = main.SelectedCategory; + e.CanExecute = category != null && category.Parent != null && !category.Categories.Any() && !category.Passwords.Any(); + } + + + private static void AddFolder(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + + Folder folder = main.SelectedFolder; + if (folder != null) + { + Folder newFolder = folder.AddFolder("New Folder"); + if (newFolder != null) + { + main.SelectedFolder = newFolder; + main.FocusEditFolderName(); + } + } + } + + private static void DeleteFolder(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + + Folder folder = main.SelectedFolder; + if (folder != null) + { + folder.Delete(); + main.SelectedFolder = folder.Parent; + } + } + + private static void CanDeleteFolder(object sender, CanExecuteRoutedEventArgs e) + { + Main main = (Main)sender; + Folder folder = main.SelectedFolder; + e.CanExecute = folder != null && folder.Parent != null && !folder.Folders.Any(); + } + + + private static void ToggleFave(object sender, ExecutedRoutedEventArgs e) + { + FrameworkElement fe = (e.OriginalSource as FrameworkElement); + PasswordFolder folder = fe != null ? fe.DataContext as PasswordFolder : null; + if (folder != null) folder.IsFavorite ^= true; + + } + + private static void CopyField(object sender, ExecutedRoutedEventArgs e) + { + FrameworkElement fe = (e.OriginalSource) as FrameworkElement; + Field field = (fe != null) ? fe.DataContext as Field : null; + if (field != null && field.Value != null) Clipboard.SetData(DataFormats.Text, field.ValueAsString()); + } + + private static void IsCopyEnabled(object sender, CanExecuteRoutedEventArgs e) + { + FrameworkElement fe = (e.OriginalSource) as FrameworkElement; + Field field = (fe != null) ? fe.DataContext as Field : null; + e.CanExecute = field != null && field.Value != null && !string.IsNullOrEmpty(field.Value.ToString()); + } + + private static void FolderTop(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Folder folder = main.SelectedFolder; + if (folder != null) + { + folder.Top(); + main.SelectedFolder = folder; + + EnsureFolderTreeDoesNotSelectRoot(main); + } + } + + + private static void FolderBottom(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Folder folder = main.SelectedFolder; + if (folder != null) + { + folder.Bottom(); + main.SelectedFolder = folder; + EnsureFolderTreeDoesNotSelectRoot(main); + } + } + + private static void FolderUp(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Folder folder = main.SelectedFolder; + if (folder != null) + { + folder.Up(); + main.SelectedFolder = folder; + EnsureFolderTreeDoesNotSelectRoot(main); + + } + } + + private static void FolderDown(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Folder folder = main.SelectedFolder; + if (folder != null) + { + folder.Down(); + main.SelectedFolder = folder; + EnsureFolderTreeDoesNotSelectRoot(main); + } + } + + private static void EnsureFolderTreeDoesNotSelectRoot(Main main) + { + main.foldersTreeView.Focus(); + } + + private static void TemplateTop(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + TemplateField field = main.SelectedTemplateField; + if (field != null) + { + field.Top(); + main.SelectedTemplateField = field; + + EnsureTemplateTreeDoesNotSelectRoot(main); + } + } + + private static void TemplateBottom(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + TemplateField field = main.SelectedTemplateField; + if (field != null) + { + field.Bottom(); + main.SelectedTemplateField = field; + EnsureTemplateTreeDoesNotSelectRoot(main); + } + } + + private static void TemplateUp(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + TemplateField field = main.SelectedTemplateField; + if (field != null) + { + field.Up(); + main.SelectedTemplateField = field; + EnsureTemplateTreeDoesNotSelectRoot(main); + + } + } + + private static void TemplateDown(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + TemplateField field = main.SelectedTemplateField; + if (field != null) + { + field.Down(); + main.SelectedTemplateField = field; + EnsureTemplateTreeDoesNotSelectRoot(main); + } + } + + private static void EnsureTemplateTreeDoesNotSelectRoot(Main main) + { + main.foldersTreeView.Focus(); + } + + private static void IsTemplateSelected(object sender, CanExecuteRoutedEventArgs e) + { + Main main = (Main)sender; + e.CanExecute = main.SelectedTemplateField != null; + } + + private static void CategoryTop(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Category category = main.SelectedCategory; + if (category != null) + { + category.Top(); + main.SelectedCategory = category; + + EnsureCategoryTreeDoesNotSelectRoot(main); + } + } + + private static void CategoryBottom(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Category category = main.SelectedCategory; + if (category != null) + { + category.Bottom(); + main.SelectedCategory = category; + EnsureCategoryTreeDoesNotSelectRoot(main); + } + } + + private static void CategoryUp(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Category category = main.SelectedCategory; + if (category != null) + { + category.Up(); + main.SelectedCategory = category; + EnsureCategoryTreeDoesNotSelectRoot(main); + + } + } + + private static void CategoryDown(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Category category = main.SelectedCategory; + if (category != null) + { + category.Down(); + main.SelectedCategory = category; + EnsureCategoryTreeDoesNotSelectRoot(main); + } + } + + /// + /// When performing an Up,Down, Top or Bottom to the Category TreeView, + /// the TreeView automatically selects the root node. + /// To prevent this, thr workaround is to Focus the TreeView directly after one of this operations. + /// + private static void EnsureCategoryTreeDoesNotSelectRoot(Main main) + { + main.categoriesTree.Focus(); + } + + private static void NewTemplateField(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + + Category category = main.SelectedCategory; + if (category != null) + { + FieldType type = (e.Parameter is FieldType) ? (FieldType)e.Parameter : (FieldType)Enum.Parse(typeof(FieldType), e.Parameter as string); + TemplateField field = category.AddField(type, "New Field"); + main.SelectedTemplateField = field; + main.SelectTemplateFieldName(); + } + } + + + private static void NewField(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + + Password password = main.SelectedPassword; + if (password != null) + { + FieldType type = (e.Parameter is FieldType) ? (FieldType)e.Parameter : (FieldType)Enum.Parse(typeof(FieldType), e.Parameter as string); + password.AddField(type, "New Field"); + Field field = password.Fields[password.Fields.Count - 1]; + main.SelectedPasswordField = field; + main.SelectPasswordFieldName(); + } + } + + private static void DeleteTemplateField(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + TemplateField field = main.SelectedTemplateField; + if (field != null) + { + field.Delete(); + main.SelectedTemplateField = null; + } + } + + + + private static void FieldDelete(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Field field = main.SelectedPasswordField; + if (field != null) + { + field.Delete(); + main.SelectedPasswordField = null; + } + } + + + private static void FieldTop(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Field field = main.SelectedPasswordField; + if (field != null) + { + field.Top(); + } + } + + private static void FieldBottom(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Field field = main.SelectedPasswordField; + if (field != null) + { + field.Bottom(); + } + } + + private static void FieldUp(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Field field = main.SelectedPasswordField; + if (field != null) + { + field.Up(); + } + } + + private static void FieldDown(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Field field = main.SelectedPasswordField; + if (field != null) + { + field.Down(); + } + } + + private static void DataModified(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + BaseObject entity = (e.OriginalSource as FrameworkElement).DataContext as BaseObject; + } + + + + private static void CopyUserName(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + //main.IsModified ^= true; + + } + + private static void SelectItem(object sender, ExecutedRoutedEventArgs e) + { + RibbonToggleButton btn = e.OriginalSource as RibbonToggleButton; + if (btn != null) + { + if (!btn.IsChecked.Value) btn = null; + Main main = (Main)sender; + main.SelectedPasswordField = btn != null ? btn.DataContext as Field : null; + } + } + + private static void SelectTemplateItem(object sender, ExecutedRoutedEventArgs e) + { + RibbonToggleButton btn = e.OriginalSource as RibbonToggleButton; + if (btn != null) + { + if (!btn.IsChecked.Value) btn = null; + Main main = (Main)sender; + main.SelectedTemplateField = btn != null ? btn.DataContext as TemplateField : null; + } + } + private static void NewPassword(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + FrameworkElement fe = e.OriginalSource as FrameworkElement; + Category category = fe != null ? fe.DataContext as Category : null; + if (category != null) + { + main.NewPassword(category); + } + } + + private static void IsFolderSelected(object sender, CanExecuteRoutedEventArgs e) + { + Main main = (Main)sender; + e.CanExecute = main.SelectedFolder != null; + } + + private static void IsPasswordSelected(object sender, CanExecuteRoutedEventArgs e) + { + Main main = (Main)sender; + e.CanExecute = main.SelectedPassword != null; + } + + private static void IsFieldSelected(object sender, CanExecuteRoutedEventArgs e) + { + Main main = (Main)sender; + e.CanExecute = main.SelectedPasswordField != null; + } + + private static void IsCategorySelected(object sender, CanExecuteRoutedEventArgs e) + { + Main main = (Main)sender; + e.CanExecute = main.SelectedCategory != null; + } + + private static void IsPasswordModified(object sender, CanExecuteRoutedEventArgs e) + { + Main main = (Main)sender; + e.CanExecute = main.IsPasswordModified; + } + + private static void Test(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + } + + private static void ChangePassword(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + main.DisplayType = DisplayType.ChangePassword; + } + + + private static void Duplicate(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + } + + private static void SavePassword(object sender, ExecutedRoutedEventArgs e) + { + + Main main = (Main)sender; + Password password = main.SelectedPassword; + if (password != null) password.Save(); + + } + + private static void UndoPassword(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Password password = main.SelectedPassword; + if (password != null) + { + main.SelectedPasswordField = null; + password.Undo(); + } + } + + private static void SaveTemplate(object sender, ExecutedRoutedEventArgs e) + { + + Main main = (Main)sender; + Category category = main.SelectedCategory; + if (category != null) category.SaveTemplate(); + + } + + private static void UndoTemplate(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Category category = main.SelectedCategory; + if (category != null) + { + main.SelectedPasswordField = null; + category.UndoTemplate(); + } + } + + private static void IsTemplateModified(object sender, CanExecuteRoutedEventArgs e) + { + Main main = (Main)sender; + + e.CanExecute = main.IsTemplateModified; + } + + private static void IsAnyPasswordOrTemplateModified(object sender, CanExecuteRoutedEventArgs e) + { + Main main = (Main)sender; + + e.CanExecute = main.IsModified; + } + + private static void DeletePassword(object sender, ExecutedRoutedEventArgs e) + { + Main main = (Main)sender; + Password password = main.SelectedPassword; + if (password != null) password.Delete(); + + } + } +} diff --git a/PasswordSafe/PasswordSafe/Controls/EditLabel.cs b/PasswordSafe/PasswordSafe/Controls/EditLabel.cs new file mode 100644 index 0000000..b175eee --- /dev/null +++ b/PasswordSafe/PasswordSafe/Controls/EditLabel.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Controls.Primitives; +using System.Diagnostics; +using System.Windows.Threading; +using System.Threading; + +namespace PasswordSafe.Controls +{ + public class EditLabel : Control + { + public static readonly RoutedCommand EditCommand = new RoutedCommand("Edit", typeof(EditLabel), + new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.F2, ModifierKeys.None, "F2") })); + + static EditLabel() + { + CommandManager.RegisterClassCommandBinding(typeof(MenuItem), new CommandBinding(EditCommand, OnEditLabel)); + DefaultStyleKeyProperty.OverrideMetadata(typeof(EditLabel), new FrameworkPropertyMetadata(typeof(EditLabel))); + } + + public EditLabel() + : base() + { + } + + + /// + /// This is a very special command execution that expects the sender to be a MenuItem that is a direct child of ContextMenu which again + /// belongs to an EditLabel. + /// + private static void OnEditLabel(object sender, ExecutedRoutedEventArgs e) + { + MenuItem item = sender as MenuItem; + if (item != null) + { + + EditLabel label = (item.Parent as ContextMenu).PlacementTarget as EditLabel; + if (label != null) + { + label.EditMode = true; + label.SelectAll(); + } + } + } + + public void SelectAll() + { + TextBox.Focus(); + TextBox.SelectAll(); + } + + + public object Text + { + get { return (object)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register("Text", typeof(object), typeof(EditLabel), new UIPropertyMetadata(null)); + + + + + public bool EditMode + { + get { return (bool)GetValue(EditModeProperty); } + set { SetValue(EditModeProperty, value); } + } + + public static readonly DependencyProperty EditModeProperty = + DependencyProperty.Register("EditMode", typeof(bool), typeof(EditLabel), new FrameworkPropertyMetadata(false, OnEditModePropertyChanged)); + + + private static void OnEditModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + bool newValue = (bool)e.NewValue; + if (newValue) (d as EditLabel).FocusTextBox(); else (d as EditLabel).UnfocusTextBox(); + } + + private void UnfocusTextBox() + { + } + + private void FocusTextBox() + { + TextBox.Focus(); + TextBox.SelectAll(); + TextBox.Focus(); + } + + public override void OnApplyTemplate() + { + TextBox.LostFocus += new RoutedEventHandler(TextBox_LostFocus); + base.OnApplyTemplate(); + } + + void TextBox_LostFocus(object sender, RoutedEventArgs e) + { + EditMode = false; + } + + protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + EditMode = false; + base.OnLostKeyboardFocus(e); + } + + protected override void OnLostFocus(RoutedEventArgs e) + { + base.OnLostFocus(e); + EditMode = false; + } + + + private TextBox TextBox { get { return GetTemplateChild("PART_TextBox") as TextBox; } } + + + + protected override void OnMouseDoubleClick(MouseButtonEventArgs e) + { + EditMode = true; + e.Handled = true; + base.OnMouseDoubleClick(e); + TextBox.Focus(); + TextBox.SelectAll(); + + } + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + // if (EditMode) + { + base.OnMouseDown(e); + } + } + + + + protected override void OnKeyDown(KeyEventArgs e) + { + switch (e.Key) + { + case Key.F2: + if (!EditMode) + { + e.Handled = true; + EditMode = true; + // FocusTextBox(); + } + break; + + case Key.Return: + EditMode = false; + e.Handled = true; + break; + + case Key.Escape: + TextBox.Undo(); + EditMode = false; + e.Handled = true; + break; + } + base.OnKeyDown(e); + } + + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/CopyFieldConverter.cs b/PasswordSafe/PasswordSafe/Converter/CopyFieldConverter.cs new file mode 100644 index 0000000..2a7b25d --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/CopyFieldConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using PasswordSafe.Data.Biz; + +namespace PasswordSafe.Converter +{ + public class CopyFieldConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + IEnumerable fields = value as IEnumerable; + if (fields == null) return Binding.DoNothing; + + return fields.Where(f => f.Type != FieldType.Separator); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/CountConverter.cs b/PasswordSafe/PasswordSafe/Converter/CountConverter.cs new file mode 100644 index 0000000..35a0812 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/CountConverter.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; + +namespace PasswordSafe.Converter +{ + public class CountConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + int count = value is int ? (int)value : 0; + return count > 0 ? string.Format("({0})", count) : ""; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/DateConverter.cs b/PasswordSafe/PasswordSafe/Converter/DateConverter.cs new file mode 100644 index 0000000..cf44f6f --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/DateConverter.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; + +namespace PasswordSafe.Converter +{ + public class DateConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is DateTime) + { + DateTime date = (DateTime)value; + return date.ToShortDateString(); + } + else return ""; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + DateTime date; + if (DateTime.TryParse(value as string,out date)) return date; else return null; + } + + #endregion + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/DisplayTypeToVisibleConverter.cs b/PasswordSafe/PasswordSafe/Converter/DisplayTypeToVisibleConverter.cs new file mode 100644 index 0000000..778ff2d --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/DisplayTypeToVisibleConverter.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using PasswordSafe.Controls; +using System.Windows; + +namespace PasswordSafe.Converter +{ + public class DisplayTypeToVisibleConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + DisplayType desiredType = (DisplayType)Enum.Parse(typeof(DisplayType), parameter.ToString()); + DisplayType type = (DisplayType)value; + + return type == desiredType ? Visibility.Visible : Visibility.Hidden; + + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/FavoriteImageConverter.cs b/PasswordSafe/PasswordSafe/Converter/FavoriteImageConverter.cs new file mode 100644 index 0000000..f8737b3 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/FavoriteImageConverter.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using System.Windows.Media.Imaging; +using PasswordSafe.Classes; +using System.Windows; +using PasswordSafe.Data.Biz; + +namespace PasswordSafe.Converter +{ + /// + /// A Value Converter that expects a FrameworkElement as Value that has a DataContext of Password. + /// It returns an ImageSource that displays the state of Password.IsFavorite. + /// + public class FavoriteImageConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + //Password password = value as Password; + //bool isFavorite = password != null && password.IsFavorite; + + bool isFavorite = (value!=null) && (bool)value; + + int size = (parameter == null) ? 32 : int.Parse(parameter as string); + + string name = isFavorite ? "Favorites_" : "NoFaves"; + return new BitmapImage(new Uri(string.Format("pack://application:,,,/img/{0}{1}.png", name, size))); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/FieldTypeConverter.cs b/PasswordSafe/PasswordSafe/Converter/FieldTypeConverter.cs new file mode 100644 index 0000000..b6ade87 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/FieldTypeConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; + +namespace PasswordSafe.Converter +{ + public class FieldTypeConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return Binding.DoNothing; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/FieldValidatonRule.cs b/PasswordSafe/PasswordSafe/Converter/FieldValidatonRule.cs new file mode 100644 index 0000000..2ca191c --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/FieldValidatonRule.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using System.Windows.Controls; +using PasswordSafe.Data.Biz; + +namespace PasswordSafe.Converter +{ + public class FieldValidationRule : ValidationRule + { + private FieldType type = FieldType.Text; + + public FieldType Type { get { return type; } set { type = value; } } + + + public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) + { + if (value is string) + { + switch (Type) + { + case FieldType.Date: + DateTime dt; + if (!DateTime.TryParse(value as string, out dt) || dt.Hour != 0 || dt.Minute != 0) return new ValidationResult(false, "Invalid time format."); + break; + + case FieldType.Time: + if (!DateTime.TryParse(value as string, out dt)) return new ValidationResult(false, "Invalid date format."); + break; + + case FieldType.Int: + int ival; + if (!(int.TryParse(value as string, out ival))) return new ValidationResult(false, "Invalid integer format."); + break; + } + + } + return new ValidationResult(true, null); + } + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/ImageConverter.cs b/PasswordSafe/PasswordSafe/Converter/ImageConverter.cs new file mode 100644 index 0000000..fa97d9d --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/ImageConverter.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using System.Windows.Media.Imaging; +using PasswordSafe.Data.Biz; + +namespace PasswordSafe.Converter +{ + /// + /// Gets the Images for the breadcrumbs from a Node. + /// + public class ImageConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + string image = "pack://application:,,,/img/category_16.png"; + CustomNode node = value as CustomNode; + + if (node != null) image = "pack://application:,,,/img/home_16.png"; + else + { + Category category = value as Category; + Folder folder = value as Folder; + + if (category != null ) image = "pack://application:,,,/img/category_16.png"; + else if (folder != null) image = "pack://application:,,,/img/faves16.png"; + } + return new BitmapImage(new Uri(image)); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/IntConverter.cs b/PasswordSafe/PasswordSafe/Converter/IntConverter.cs new file mode 100644 index 0000000..8cc244a --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/IntConverter.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using System.Windows.Controls; + +namespace PasswordSafe.Converter +{ + public class IntConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + int ival = (int)value; + return ival.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + int ival; + if (!int.TryParse(value as string, out ival)) ival = default(int); + return ival; + } + + #endregion + } + public class Rule : ValidationRule + { + public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) + { + //xx + throw new NotImplementedException(); + } + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/IsModifiedFromUIPasswordConverter.cs b/PasswordSafe/PasswordSafe/Converter/IsModifiedFromUIPasswordConverter.cs new file mode 100644 index 0000000..2db3c4d --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/IsModifiedFromUIPasswordConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using PasswordSafe.Classes; +using PasswordSafe.Data.Biz; + +namespace PasswordSafe.Converter +{ + public class IsModifiedFromUIPasswordConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + Password password = value as Password; + if (password != null) + { + return password.IsModified; + } + else return Binding.DoNothing; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/NullToBoolConverter.cs b/PasswordSafe/PasswordSafe/Converter/NullToBoolConverter.cs new file mode 100644 index 0000000..479190e --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/NullToBoolConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using PasswordSafe.Data.Biz; + +namespace PasswordSafe.Converter +{ + class NullToBoolConverter : IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value == null) return false; + if ("HasLines".Equals(parameter)) return (value as Field).HasLines; + if ("HasRange".Equals(parameter)) return (value as Field).HasRange; + return true; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/NullToVisibleConverter.cs b/PasswordSafe/PasswordSafe/Converter/NullToVisibleConverter.cs new file mode 100644 index 0000000..4ad5cea --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/NullToVisibleConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using System.Windows; + +namespace PasswordSafe.Controls +{ + [ValueConversion(typeof(object), typeof(Visibility))] + class NullToVisibleConverter : IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return value != null ? Visibility.Visible : Visibility.Hidden; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/TimeConverter.cs b/PasswordSafe/PasswordSafe/Converter/TimeConverter.cs new file mode 100644 index 0000000..d66aa51 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/TimeConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; + +namespace PasswordSafe.Converter +{ + public class TimeConverter:IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is DateTime) + { + DateTime time = (DateTime)value; + return time.ToLongTimeString(); + } + else return ""; + + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + DateTime date; + if (DateTime.TryParse(value as string, out date)) return date; else return null; + } + + #endregion + } +} diff --git a/PasswordSafe/PasswordSafe/Converter/VisibilityConverter.cs b/PasswordSafe/PasswordSafe/Converter/VisibilityConverter.cs new file mode 100644 index 0000000..219b0eb --- /dev/null +++ b/PasswordSafe/PasswordSafe/Converter/VisibilityConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using System.Windows; + +namespace PasswordSafe.Controls +{ + public class VisibilityConverter : IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + bool idNegative = parameter != null && parameter.Equals("-"); + bool bit = (bool)value; + if (idNegative) bit = !bit; + + return bit ? Visibility.Visible : Visibility.Hidden; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/PasswordSafe/PasswordSafe/DataTemplateSelectors/BreadcrumbItemSelector.cs b/PasswordSafe/PasswordSafe/DataTemplateSelectors/BreadcrumbItemSelector.cs new file mode 100644 index 0000000..513b2df --- /dev/null +++ b/PasswordSafe/PasswordSafe/DataTemplateSelectors/BreadcrumbItemSelector.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using PasswordSafe.Classes; +using System.Windows; +using System.ComponentModel; +using PasswordSafe.Data.Biz; + +namespace PasswordSafe.DataTemplateSelectors +{ + public class BreadcrumbItemSelector : DataTemplateSelector + { + public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container) + { + if (!DesignerProperties.GetIsInDesignMode(container)) + { + Window window = Application.Current.MainWindow; + + CustomNode node = item as CustomNode; + if (node != null) return window.FindResource("bcBreadcrumbItemTemplate") as DataTemplate; + + Category category = item as Category; + if (category != null) return window.FindResource("bcCategoryItemTemplate") as DataTemplate; + + Folder folder = item as Folder; + if (folder != null) return window.FindResource("bcFolderItemTemplate") as DataTemplate; + } + + + return base.SelectTemplate(item, container); + } + } +} diff --git a/PasswordSafe/PasswordSafe/DataTemplateSelectors/CopyPasswordSelector.cs b/PasswordSafe/PasswordSafe/DataTemplateSelectors/CopyPasswordSelector.cs new file mode 100644 index 0000000..bc8fb6f --- /dev/null +++ b/PasswordSafe/PasswordSafe/DataTemplateSelectors/CopyPasswordSelector.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using PasswordSafe.Classes; +using PasswordSafe.Data.Biz; + +namespace PasswordSafe.DataTemplateSelectors +{ + public class CopyPasswordSelector:DataTemplateSelector + { + public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container) + { + Field field = item as Field; + Window window = Application.Current.MainWindow; + if (field != null) + { + switch (field.Type) + { + case FieldType.Separator: + return window.FindResource("DropDownPasswordSeparatorFieldTemplate") as DataTemplate; + } + } + return base.SelectTemplate(item, container); + } + } +} diff --git a/PasswordSafe/PasswordSafe/DataTemplateSelectors/FieldSelector.cs b/PasswordSafe/PasswordSafe/DataTemplateSelectors/FieldSelector.cs new file mode 100644 index 0000000..bd0404c --- /dev/null +++ b/PasswordSafe/PasswordSafe/DataTemplateSelectors/FieldSelector.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using PasswordSafe.Classes; +using PasswordSafe.Data.Biz; + +namespace PasswordSafe.DataTemplateSelectors +{ + public class FieldSelector : DataTemplateSelector + { + public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container) + { + Field field = item as Field; + Window window = Application.Current.MainWindow; + if (field != null && field.Type != FieldType.Unknown) + { + switch (field.Type) + { + case FieldType.Bool: + return window.FindResource("BoolFieldTemplate") as DataTemplate; + + case FieldType.Int: + return window.FindResource("IntFieldTemplate") as DataTemplate; + + case FieldType.Date: + return window.FindResource("DateFieldTemplate") as DataTemplate; + + case FieldType.Time: + return window.FindResource("TimeFieldTemplate") as DataTemplate; + + case FieldType.Memo: + return window.FindResource("MemoFieldTemplate") as DataTemplate; + + case FieldType.Separator: + return window.FindResource("SeparatorFieldTemplate") as DataTemplate; + + default: + return window.FindResource("PasswordFieldTemplate") as DataTemplate; + } + } + return base.SelectTemplate(item, container); + } + } +} diff --git a/PasswordSafe/PasswordSafe/DataTemplateSelectors/TemplateFieldSelector.cs b/PasswordSafe/PasswordSafe/DataTemplateSelectors/TemplateFieldSelector.cs new file mode 100644 index 0000000..63a8298 --- /dev/null +++ b/PasswordSafe/PasswordSafe/DataTemplateSelectors/TemplateFieldSelector.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using PasswordSafe.Data.Biz; +using System.Windows; + +namespace PasswordSafe.DataTemplateSelectors +{ + public class TemplateFieldSelector : DataTemplateSelector + { + public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container) + { + TemplateField field = item as TemplateField; + Window window = Application.Current.MainWindow; + if (field != null && field.Type != FieldType.Unknown) + { + switch (field.Type) + { + case FieldType.Bool: + return window.FindResource("BoolFieldTempTemplate") as DataTemplate; + + case FieldType.Int: + return window.FindResource("IntFieldTempTemplate") as DataTemplate; + + case FieldType.Time: + case FieldType.Date: + return window.FindResource("DateFieldTempTemplate") as DataTemplate; + + case FieldType.Memo: + return window.FindResource("MemoFieldTempTemplate") as DataTemplate; + + case FieldType.Separator: + return window.FindResource("SeparatorFieldTempTemplate") as DataTemplate; + + default: + return window.FindResource("PasswordFieldTempTemplate") as DataTemplate; + } + } + return base.SelectTemplate(item, container); + } + } +} diff --git a/PasswordSafe/PasswordSafe/DataTemplates.xaml b/PasswordSafe/PasswordSafe/DataTemplates.xaml new file mode 100644 index 0000000..780965b --- /dev/null +++ b/PasswordSafe/PasswordSafe/DataTemplates.xaml @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PasswordSafe/PasswordSafe/Enums/DisplayMode.cs b/PasswordSafe/PasswordSafe/Enums/DisplayMode.cs new file mode 100644 index 0000000..e81efcb --- /dev/null +++ b/PasswordSafe/PasswordSafe/Enums/DisplayMode.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PasswordSafe.Controls +{ + public enum DisplayMode + { + Passwords=0, + Templates=1 + } +} diff --git a/PasswordSafe/PasswordSafe/Enums/DisplayType.cs b/PasswordSafe/PasswordSafe/Enums/DisplayType.cs new file mode 100644 index 0000000..773e587 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Enums/DisplayType.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PasswordSafe.Controls +{ + public enum DisplayType + { + /// + /// Show the login screen + /// + Login, + + /// + /// Show the change password screen + /// + ChangePassword, + + /// + /// Show the passwords + /// + Passwords + } +} diff --git a/PasswordSafe/PasswordSafe/Export/XmlExporter.cs b/PasswordSafe/PasswordSafe/Export/XmlExporter.cs new file mode 100644 index 0000000..c57239a --- /dev/null +++ b/PasswordSafe/PasswordSafe/Export/XmlExporter.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PasswordSafe.Data.Biz; +using System.Xml.Serialization; +using System.IO; + +namespace PasswordSafe.Export +{ + static class XmlExporter + { + + public static string Export() + { + XmlSerializer serializer = new XmlSerializer(typeof(PasswordExport)); + + PasswordExport data = new PasswordExport { Passwords = GetPasswords() }; + using (StringWriter writer = new StringWriter()) + { + serializer.Serialize(writer, data); + writer.Flush(); + string s = writer.ToString(); + return s; + } + } + + public static void Export(Stream stream) + { + XmlSerializer serializer = new XmlSerializer(typeof(PasswordExport)); + + PasswordExport data = new PasswordExport { Passwords = GetPasswords() }; + serializer.Serialize(stream, data); + } + + + private static XmlPassword[] GetPasswords() + { + BizContext context = BizContext.Instance; + + return context.Categories.FirstOrDefault().NestedPasswords.Select(p => new XmlPassword + { + Name = p.Name, + Category = p.Category.GetPath(2), + Fields = p.Fields.Where(f => f.Type != FieldType.Separator).Select(f => new XmlField { Name = f.Name, Value = f.StringValue, Type = f.Type.ToString() }).ToArray() + }).ToArray(); + } + + + } + + [XmlRoot(ElementName = "PasswordSafe")] + public class PasswordExport + { + [XmlArrayItem("Password")] + public XmlPassword[] Passwords { get; set; } + } +} diff --git a/PasswordSafe/PasswordSafe/Export/XmlField.cs b/PasswordSafe/PasswordSafe/Export/XmlField.cs new file mode 100644 index 0000000..3717a77 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Export/XmlField.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace PasswordSafe.Export +{ + [XmlRoot(ElementName="Field")] + public class XmlField + { + [XmlAttribute] + public string Name { get; set; } + + [XmlText] + public string Value { get; set; } + + [XmlAttribute] + public string Type { get; set; } + } +} diff --git a/PasswordSafe/PasswordSafe/Export/XmlPassword.cs b/PasswordSafe/PasswordSafe/Export/XmlPassword.cs new file mode 100644 index 0000000..d8c155a --- /dev/null +++ b/PasswordSafe/PasswordSafe/Export/XmlPassword.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace PasswordSafe.Export +{ + [XmlRoot(ElementName="Password")] + public class XmlPassword + { + [XmlAttribute] + public string Name { get; set; } + + [XmlAttribute] + public string Category { get; set; } + + [XmlArrayItem("Field")] + public XmlField[] Fields { get; set; } + + } +} diff --git a/PasswordSafe/PasswordSafe/FieldTemplates.xaml b/PasswordSafe/PasswordSafe/FieldTemplates.xaml new file mode 100644 index 0000000..b7090d4 --- /dev/null +++ b/PasswordSafe/PasswordSafe/FieldTemplates.xaml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PasswordSafe/PasswordSafe/Main.xaml b/PasswordSafe/PasswordSafe/Main.xaml new file mode 100644 index 0000000..9331a91 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Main.xaml @@ -0,0 +1,552 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Exports Passwords to an unencrypted xml file. + + + + + + Exit PasswordSafe + + + + + + + + + + + + + + Save + Save + Print + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PasswordSafe/PasswordSafe/Main.xaml.cs b/PasswordSafe/PasswordSafe/Main.xaml.cs new file mode 100644 index 0000000..b816442 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Main.xaml.cs @@ -0,0 +1,939 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Odyssey.Controls; +using System.Diagnostics; +using Odyssey.Controls.Classes; +using PasswordSafe.Tools; +using System.Threading; +using PasswordSafe.Data; +using PasswordSafe.Properties; +using System.Windows.Media.Effects; +using Odyssey.Ribbon.EventArgs; +using PasswordSafe.Classes; +using PasswordSafe.Data.Biz; +using PasswordSafe.Converter; +using System.Collections.ObjectModel; +using System.Data.SqlClient; +using System.Reflection; +using System.Data.SqlServerCe; +using System.Globalization; + +namespace PasswordSafe.Controls +{ + /// + /// Interaction logic for Window1.xaml + /// + public partial class Main : RibbonWindow + { + public Main() + { + //BizContext.ConnectionString = Settings.Default.PasswordsConnectionString; + InitializeComponent(); + LoadSettings(); + ribbon.ApplicationMenu.Opened += new EventHandler(OnMenuOpened); + IsModified = true; + IsModified = false; + + } + + /// + /// Get all categories when the menu is opened to select one for a new password: + /// + void OnMenuOpened(object sender, EventArgs e) + { + } + + static Main() + { + // Register a static MouseDownEvent for a TreeViewItem to enable select with a mouse-right-click: + EventManager.RegisterClassHandler(typeof(TreeViewItem), + Mouse.MouseDownEvent, new MouseButtonEventHandler(OnTreeViewItemMouseDown), false); + } + + private static void OnTreeViewItemMouseDown(object sender, MouseButtonEventArgs e) + { + var item = sender as TreeViewItem; + if (e.RightButton == MouseButtonState.Pressed) + { + item.IsSelected = true; + e.Handled = true; + } + } + + + private void LoadData(string connectionString) + { + BizContext.OpenConnection(connectionString); + RefreshData(); + InitialShow(); + connectionString = ""; + SelectedNode = BizContext.Instance.RootCategory; + } + + private void UnloadData() + { + BizContext.CloseConnection(); + RefreshData(); + } + + private void RefreshData() + { + UIContext context = new UIContext(); + PasswordGrid.BreadcrumbRoot = context.GetBreadcrumbRoot(); + + TemplateGrid.BreadcrumbRoot = + PasswordGrid.RootCategory = context.GetCategoryRoot(); + + foreach (string res in new string[] { + "fieldTypesData" + ,"categoryData" + ,"folderData" + }) + { + ObjectDataProvider provider = Resources[res] as ObjectDataProvider; + if (provider != null) + { + provider.InitialLoad(); + provider.Refresh(); + } + } + + } + + private void LoadSettings() + { + Settings settings = Settings.Default; + outlook.MaxNumberOfButtons = settings.MaxSections; + outlook.IsMaximized = settings.IsSidebarExpanded; + ribbon.CanMinimize = settings.CanRibbonMinimize; + this.PasswordGrid.PasswordColumn.Width = new GridLength(settings.PasswordWith); + ribbon.IsExpanded = settings.IsRibbonExpanded && ribbon.CanMinimize; + if (settings.Left != -1) Left = settings.Left; + if (settings.Top != -1) Top = settings.Top; + if (settings.Width > 48) Width = settings.Width; + if (settings.Height > 32) Height = settings.Height; + // IsGlassEnabled = settings.IsGlassEnabled; + Skin = settings.Skin; + } + + private void SaveSettings() + { + Settings settings = Settings.Default; + settings.IsSidebarExpanded = outlook.IsMaximized; + settings.IsRibbonExpanded = ribbon.IsExpanded; + settings.CanRibbonMinimize = ribbon.CanMinimize; + Rect bounds = RestoreBounds; + settings.Left = bounds.Left; + settings.Top = bounds.Top; + settings.Width = bounds.Width; + settings.Height = bounds.Height; + settings.WindowState = WindowState; + settings.Skin = Skin; + settings.MaxSections = outlook.MaxNumberOfButtons; + settings.IsGlassEnabled = IsGlassEnabled; + settings.PasswordWith = this.PasswordGrid.PasswordColumn.ActualWidth; + settings.Save(); + } + + + + protected override void OnContentRendered(EventArgs e) + { + WindowState = Settings.Default.WindowState; + IsGlassEnabled = Settings.Default.IsGlassEnabled; + + base.OnContentRendered(e); + lockScreen.FocusPassword(); + } + + private void InitialShow() + { + TreeViewItem item = this.categoriesTree.FirstItem(); + if (item != null) + { + item.IsSelected = true; + item.IsExpanded = true; + } + + item = this.foldersTreeView.FirstItem(); + if (item != null) + { + item.IsExpanded = true; + } + SelectedCategory = BizContext.Instance.RootCategory; + } + + protected override void OnClosing(System.ComponentModel.CancelEventArgs e) + { + BizContext.Instance.Save(); + SaveSettings(); + this.ribbon.Focus(); + BizContext.Instance.Commit(); + base.OnClosing(e); + } + + +#if false + private void InitializeDataAsync() + { + var ctx = SynchronizationContext.Current; + ThreadPool.QueueUserWorkItem(state => + { + // this runs in a separate thread: + var bcRoot = Domain.Nodes; + var categories = new CategoryBase[] { Domain.RootCategory }; + var rootFolders = new NodeBase[] { Domain.RootFolder }; + var rootCategory = Domain.RootCategory; + + ctx.Post(ctxState => + { + // this runs in the application thread: + categoriesTree.SelectFirst(); + }, null); + }); + } +#endif + + protected void OnSelectedCategoryChanged(object sender, RoutedPropertyChangedEventArgs e) + { + Category c = e.NewValue as Category; + SelectedCategory = c; + } + + protected void OnSelectedFolderChanged(object sender, RoutedPropertyChangedEventArgs e) + { + Folder folder = e.NewValue as Folder; + SelectedFolder = folder; + } + + + + /// + /// Occurs when the Category TreeView got the focus. + /// + private void OnCategoriesGotFocus(object sender, RoutedEventArgs e) + { + if (this.categoriesTree.SelectedItem == null) categoriesTree.Select(new UIContext().RootCategory); + ChangedTabsVisibility(); + if (ribbon.SelectedTabItem == folderTab) ribbon.SelectedTabItem = categoryTab; + } + + /// + /// Occurs when the Favorites TreeView got the focus. + /// + private void OnFavoritesGotFocus(object sender, RoutedEventArgs e) + { + if (this.foldersTreeView.SelectedItem == null) foldersTreeView.Select(new UIContext().RootFolder); + ChangedTabsVisibility(); + if (ribbon.SelectedTabItem == categoryTab) ribbon.SelectedTabItem = folderTab; + } + + private void ChangedTabsVisibility() + { + if (ribbon.ContextualTabSet != SidebarTabs) + { + ribbon.ContextualTabSet = null; + ribbon.ContextualTabSet = SidebarTabs; + } + } + + + /// + /// Occurs when the Password Area (Passwords List, Password Details, Search, Breadcrumb,etc.) got the focus. + /// + private void OnPasswordsGotFocus(object sender, RoutedEventArgs e) + { + ribbon.ContextualTabSet = null; + } + + + /// + /// Gets whether the currently selected password has been modified. + /// + public bool IsTemplateModified + { + get { return (bool)GetValue(IsTemplateModifiedProperty); } + private set { SetValue(IsTemplateModifiedPropertyKey, value); } + } + + public static readonly DependencyPropertyKey IsTemplateModifiedPropertyKey = + DependencyProperty.RegisterReadOnly("IsTemplateModified", typeof(bool), typeof(Main), new UIPropertyMetadata(false, TemplateModifiedPropertyChanged)); + + private static readonly DependencyProperty IsTemplateModifiedProperty = IsTemplateModifiedPropertyKey.DependencyProperty; + + private static void TemplateModifiedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Main)d).TemplateModified((bool)e.NewValue); + } + + protected virtual void TemplateModified(bool newValue) + { + CheckIsModified(newValue); + } + + private void CheckIsModified(bool value) + { + if (value) + { + IsModified = true; + } + else + { + BizContext context = BizContext.Instance; + Category root = context.Categories.FirstOrDefault(); + if (root != null) + { + if (root.NestedPasswords.Any(p => p.IsModified)) + { + IsModified = true; + return; + } + if (IsNestedTemplateModified(root)) + { + IsModified = true; + return; + } + } + IsModified = false; + } + } + + private bool IsNestedTemplateModified(Category category) + { + if (category.IsModified) return true; + return category.Categories.Any(c => IsNestedTemplateModified(c)); + } + + /// + /// Gets or sets whether either a template or password is modified. + /// + public bool IsModified + { + get { return (bool)GetValue(IsModifiedProperty); } + set { SetValue(IsModifiedProperty, value); } + } + + public static readonly DependencyProperty IsModifiedProperty = + DependencyProperty.Register("IsModified", typeof(bool), typeof(Main), new UIPropertyMetadata(false, ModifiedPropertyChanged)); + + private static void ModifiedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Main)d).ModifiedChanged((bool)e.NewValue); + } + + protected virtual void ModifiedChanged(bool newValue) + { + UpdateMenuItemEnabledStates(newValue); + } + + //TODO: + /// + /// There is currently a bug that Command Bindings to MenuItems in the RibbonApplicatioMenu do not update the IsEnabled state, so this is done handcrafted: + /// + private void UpdateMenuItemEnabledStates(bool isEnabled) + { + SaveAllMenuItem.IsEnabled = isEnabled; + UndoAllMenuItem.IsEnabled = isEnabled; + } + + + + /// + /// Gets whether the currently selected password has been modified. + /// + public bool IsPasswordModified + { + get { return (bool)GetValue(IsPasswordModifiedProperty); } + private set { SetValue(IsPasswordModifiedPropertyKey, value); } + } + + public static readonly DependencyPropertyKey IsPasswordModifiedPropertyKey = + DependencyProperty.RegisterReadOnly("IsPasswordModified", typeof(bool), typeof(Main), new UIPropertyMetadata(false, PasswordModifiedPropertyChanged)); + + private static void PasswordModifiedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Main)d).PasswordModified((bool)e.NewValue); + } + + protected virtual void PasswordModified(bool newValue) + { + CheckIsModified(newValue); + } + + private static readonly DependencyProperty IsPasswordModifiedProperty = IsPasswordModifiedPropertyKey.DependencyProperty; + + + + private void OnSelectedTabIndexChanged(object sender, RoutedPropertyChangedEventArgs e) + { + + RibbonTabItem selectedTab = ribbon.SelectedTabItem; + DisplayMode = selectedTab == categoryTab ? DisplayMode.Templates : DisplayMode.Passwords; + } + + + private void SelectCategoryTreeViewItemClicked(object sender, RoutedEventArgs e) + { + //ClickableTreeViewItem item = e.OriginalSource as ClickableTreeViewItem; + //WPFNode node = item.DataContext as WPFNode; + + //MessageBox.Show("Clicked: " + node.Path); + } + + + public DisplayMode DisplayMode + { + get { return (DisplayMode)GetValue(DisplayModeProperty); } + set { SetValue(DisplayModeProperty, value); } + } + + public static readonly DependencyProperty DisplayModeProperty = + DependencyProperty.Register("DisplayMode", typeof(DisplayMode), typeof(Main), + new FrameworkPropertyMetadata( + DisplayMode.Passwords, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + DisplayModePropertyChanged + )); + + + private static void DisplayModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Main)d).OnDisplayModeChanged(d, (DisplayMode)e.OldValue, (DisplayMode)e.NewValue); + } + + protected virtual void OnDisplayModeChanged(object sende, DisplayMode oldMode, DisplayMode newMode) + { + } + + + + public int SectionIndex + { + get { return (int)GetValue(SectionIndexProperty); } + set { SetValue(SectionIndexProperty, value); } + } + + // Using a DependencyProperty as the backing store for SectionIndex. This enables animation, styling, binding, etc... + public static readonly DependencyProperty SectionIndexProperty = + DependencyProperty.Register("SectionIndex", typeof(int), typeof(Main), new FrameworkPropertyMetadata( + 0, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnSectionIndexPropertyChanged + )); + + + private static void OnSectionIndexPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Main)d).OnSectionIndexChanged((int)e.OldValue, (int)e.NewValue); + } + + protected virtual void OnSectionIndexChanged(int oldIndex, int newIndex) + { + ribbon.ContextualTabSet = null; + foreach (RibbonTabItem item in ribbon.Tabs) + { + item.Visibility = (item == TemplateTab) ^ (newIndex == 0) ? Visibility.Visible : Visibility.Collapsed; + } + ribbon.EnsureTabIsVisible(); + + PasswordGrid.Visibility = newIndex == 0 ? Visibility.Visible : Visibility.Hidden; + TemplateCategoryTab.Visibility = + TemplateGrid.Visibility = (newIndex == 1 ? Visibility.Visible : Visibility.Hidden); + + } + + public SkinId Skin + { + get { return (SkinId)GetValue(SkinProperty); } + set { SetValue(SkinProperty, value); } + } + + public static readonly DependencyProperty SkinProperty = + DependencyProperty.Register("Skin", typeof(SkinId), typeof(Main), new FrameworkPropertyMetadata(SkinId.OfficeBlue, SkinPropertyChanged)); + + + private static void SkinPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + SkinId id = (SkinId)e.NewValue; + SkinManager.SkinId = id; + Main main = (Main)o; + int index = SkinToId(id); + if (main.StyleGallery.SelectedIndex != index) + { + main.StyleGallery.SelectedIndex = index; + } + + } + + private static int SkinToId(SkinId id) + { + switch (id) + { + case SkinId.Vista: return 0; + case SkinId.Windows7: return 1; + case SkinId.OfficeBlue: return 2; + case SkinId.OfficeSilver: return 3; + case SkinId.OfficeBlack: return 4; + default: throw new ArgumentOutOfRangeException(); + } + } + + private void RibbonGallery_HotThumbnailChanged(object sender, RoutedEventArgs e) + { + RibbonGallery g = sender as RibbonGallery; + RibbonThumbnail thumb = g.HotThumbnail; + if (thumb != null) + { + SkinManager.SkinId = IdToSkin(g.Items.IndexOf(g.HotItem)); + } + else SkinManager.SkinId = Skin; + } + + private SkinId IdToSkin(int id) + { + + switch (id) + { + case 0: return SkinId.Vista; + case 1: return SkinId.Windows7; + case 2: return SkinId.OfficeBlue; + case 3: return SkinId.OfficeSilver; + case 4: return SkinId.OfficeBlack; + default: throw new ArgumentOutOfRangeException(); + } + } + + private void RibbonGallery_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + RibbonGallery g = sender as RibbonGallery; + SkinId skin = IdToSkin(g.SelectedIndex); + if (Skin != skin) Skin = skin; + } + + + + /// + /// Gets or sets whether the database is locked. This is a dp. + /// + public bool IsLocked + { + get { return (bool)GetValue(IsLockedProperty); } + set { SetValue(IsLockedProperty, value); } + } + + public static readonly DependencyProperty IsLockedProperty = + DependencyProperty.Register("IsLocked", typeof(bool), typeof(Main), new UIPropertyMetadata(true, OnLockedPropertyChanged)); + + private static void OnLockedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Main)d).OnLockedChanged(d, (bool)e.OldValue, (bool)e.NewValue); + } + + protected virtual void OnLockedChanged(object sender, bool oldvalue, bool newValue) + { + if (newValue) + { + UnloadData(); + lockScreen.Dispatcher.BeginInvoke((Func)delegate() + { + lockScreen.FocusPassword(); + return 0; + }); + } + } + + + + protected override void OnPreviewKeyDown(KeyEventArgs e) + { + if (e.SystemKey == (Key.M) && Keyboard.Modifiers == ModifierKeys.Alt) + { + ribbon.ApplicationMenu.IsOpen ^= true; + } + + base.OnPreviewKeyDown(e); + } + + + public Folder SelectedFolder + { + get { return (Folder)GetValue(SelectedFolderProperty); } + set { SetValue(SelectedFolderProperty, value); } + } + + public static readonly DependencyProperty SelectedFolderProperty = + DependencyProperty.Register("SelectedFolder", typeof(Folder), typeof(Main), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnSelectedFolderPropertyChanged + )); + + private static void OnSelectedFolderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Main)d).OnSelectedFolderChanged((Folder)e.OldValue, (Folder)e.NewValue); + + } + + protected virtual void OnSelectedFolderChanged(Folder oldFolder, Folder newFolder) + { + if (newFolder != null) this.categoriesTree.Deselect(); + this.PasswordGrid.DataContext = newFolder; + foldersTreeView.Select(newFolder); + SelectedNode = newFolder; + + } + + public TemplateField SelectedTemplateField + { + get { return (TemplateField)GetValue(SelectedTemplateFieldProperty); } + set { SetValue(SelectedTemplateFieldProperty, value); } + } + + public static readonly DependencyProperty SelectedTemplateFieldProperty = + DependencyProperty.Register("SelectedTemplateField", typeof(TemplateField), typeof(Main), new FrameworkPropertyMetadata(null, + SelectedTemplateFieldPropertyChanged)); + + + private static void SelectedTemplateFieldPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TemplateField oldField = e.OldValue as TemplateField; + if (oldField != null) oldField.IsSelected = false; + Main main = (Main)d; + TemplateField newField = (e.NewValue as TemplateField); + if (newField != null) newField.IsSelected = true; + main.OnSelectedTemplateFieldChanged(main, oldField, newField); + } + + protected virtual void OnSelectedTemplateFieldChanged(object sender, TemplateField oldField, TemplateField newField) + { + } + + /// + /// Gets or sets the selected password field. + /// This is a dependency property. + /// + public Field SelectedPasswordField + { + get { return (Field)GetValue(SelectedPasswordFieldProperty); } + set { SetValue(SelectedPasswordFieldProperty, value); } + } + + public static readonly DependencyProperty SelectedPasswordFieldProperty = + DependencyProperty.Register("SelectedPasswordField", typeof(Field), typeof(Main), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + SelectedPasswordFieldPropertyChanged, CoerceSelectedPasswordField)); + + private static void SelectedPasswordFieldPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + Field oldField = e.OldValue as Field; + if (oldField != null) oldField.IsSelected = false; + Main main = (Main)d; + Field newField = (e.NewValue as Field); + if (newField != null) + { + newField.IsSelected = true; + } + main.OnSelectedPasswordFieldChanged(main, oldField, newField); + } + + private void OnSelectedPasswordFieldChanged(object sender, Field oldField, Field newField) + { + } + + private static object CoerceSelectedPasswordField(DependencyObject d, object baseValue) + { + Main main = (Main)d; + return main.SelectedPassword == null ? null : baseValue; + } + + + + public Category SelectedCategory + { + get { return (Category)GetValue(SelectedCategoryProperty); } + set { SetValue(SelectedCategoryProperty, value); } + } + + public static readonly DependencyProperty SelectedCategoryProperty = + DependencyProperty.Register("SelectedCategory", typeof(Category), typeof(Main), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + SelectedCategoryPropertyChanged)); + + + private static void SelectedCategoryPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Main)d).OnSelectedCategoryChanged(d, (Category)e.OldValue, (Category)e.NewValue); + + } + + protected virtual void OnSelectedCategoryChanged(object sender, Category oldCategory, Category newCategory) + { + if (newCategory != null) + { + newCategory.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(SelectedCategoryPropertyChanged); + } + if (oldCategory != null) + { + oldCategory.PropertyChanged -= SelectedCategoryPropertyChanged; + } + if (newCategory != null) this.foldersTreeView.Deselect(); + this.PasswordGrid.DataContext = newCategory; + + templateCategoryTree.Select(newCategory); + categoriesTree.Select(newCategory); + SelectedNode = newCategory; + SelectedTemplateField = null; + + IsTemplateModified = newCategory != null ? newCategory.IsModified : false; + } + + void SelectedCategoryPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + // some bindings have IsAsync=True, so we need to do this invoked: + Dispatcher.BeginInvoke((Func)delegate() + { + Category category = (Category)sender; + IsTemplateModified = category != null ? category.IsModified : false; + return 0; + }); + } + + public void NewPassword(Category category) + { + Password password = category.CreatePassword(); + if (password != null) + { + password.Name = "New Password"; + SelectedCategory = password.Category; + this.SelectedPassword = password; + this.PasswordGrid.FocusPassword(); + + } + } + + + + /// + /// Gets the currently selected password. + /// + public Password SelectedPassword + { + get { return (Password)GetValue(SelectedPasswordProperty); } + set { SetValue(SelectedPasswordProperty, value); } + } + + public static readonly DependencyProperty SelectedPasswordProperty = + DependencyProperty.Register("SelectedPassword", typeof(Password), typeof(Main), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnSelectedPasswordPropertyChanged)); + + + private static void OnSelectedPasswordPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (e.OldValue != e.NewValue) + { + Password oldPassword = (Password)e.OldValue; + Password newPassword = (Password)e.NewValue; + Main main = (Main)d; + + if (oldPassword != null) oldPassword.PropertyChanged -= main.OnPasswordPropertyChanged; + if (newPassword != null) newPassword.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(main.OnPasswordPropertyChanged); + main.SelectedPasswordField = null; + main.IsPasswordModified = newPassword != null && newPassword.IsModified; + main.IsPasswordSelected = newPassword != null; + + } + } + + void OnPasswordPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + // some bindings have IsAsync=True, so we need to do this invoked: + Dispatcher.BeginInvoke((Func)delegate() + { + Password password = (Password)sender; + IsPasswordModified = password != null ? password.IsModified : false; + return 0; + }); + } + + + + /// + /// Gets whether a password is selected. + /// This is a dependency property. + /// + public bool IsPasswordSelected + { + get { return (bool)GetValue(IsPasswordSelectedProperty); } + private set { SetValue(IsPasswordSelectedPropertyKey, value); } + } + + public static readonly DependencyPropertyKey IsPasswordSelectedPropertyKey = + DependencyProperty.RegisterReadOnly("IsPasswordSelected", typeof(bool), typeof(Main), new UIPropertyMetadata(false)); + + private static readonly DependencyProperty IsPasswordSelectedProperty = IsPasswordModifiedPropertyKey.DependencyProperty; + + + + /// + /// Gets or sets the selected node for the breadcrumb. + /// + public NodeBase SelectedNode + { + get { return (NodeBase)GetValue(SelectedNodeProperty); } + set { SetValue(SelectedNodeProperty, value); } + } + + public static readonly DependencyProperty SelectedNodeProperty = + DependencyProperty.Register("SelectedNode", typeof(NodeBase), typeof(Main), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedNodePropertyChanged)); + + private static void OnSelectedNodePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Main)d).OnSelectedNodeChanged((NodeBase)e.OldValue, (NodeBase)e.NewValue); + } + + protected virtual void OnSelectedNodeChanged(NodeBase oldNode, NodeBase newNode) + { + SelectedCategory = newNode as Category; + SelectedFolder = newNode as Folder; + } + + + + /// + /// Selects the textbox to edit the name of the selected Password Field. + /// + public void SelectPasswordFieldName() + { + fieldName.Focus(); + fieldName.SelectAll(); + } + + + public void FocusEditFolderName() + { + folderName.Focus(); + folderName.SelectAll(); + } + + public void FocusEditCategoryName() + { + if (!TemplateTab.IsVisible) + { + categoryName.Focus(); + categoryName.SelectAll(); + } + else + { + categoryName2.Focus(); + categoryName2.SelectAll(); + } + } + + + internal void SelectTemplateFieldName() + { + TemplateFieldName.Focus(); + TemplateFieldName.SelectAll(); + + } + + private void LaunchConfiguration(object sender, RoutedEventArgs e) + { + MessageBox.Show("Configuration..."); + } + + public bool Login(string password) + { + SqlConnectionStringBuilder sb = new SqlConnectionStringBuilder(Settings.Default.PasswordsConnectionString); + try + { + sb.PersistSecurityInfo = false; + sb.Password = password; + LoadData(sb.ConnectionString); + DisplayType = DisplayType.Passwords; + IsLocked = false; + sb.Password = ""; + return true; + } + catch (SqlCeException) + { + sb.Password = ""; + DisplayType = DisplayType.Login; + IsLocked = true; + return false; + } + } + + + + public DisplayType DisplayType + { + get { return (DisplayType)GetValue(DisplayTypeProperty); } + set { SetValue(DisplayTypeProperty, value); } + } + + public static readonly DependencyProperty DisplayTypeProperty = + DependencyProperty.Register("DisplayType", typeof(DisplayType), typeof(Main), new UIPropertyMetadata(DisplayType.Login, OnDisplayTypePropertyChanged)); + + private static void OnDisplayTypePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Main)d).OnDisplayTypeChanged(d, (DisplayType)e.OldValue, (DisplayType)e.NewValue); + } + + protected virtual void OnDisplayTypeChanged(object sender, DisplayType oldDisplayType, DisplayType newDisplayType) + { + switch (newDisplayType) + { + case DisplayType.Login: + BizContext.CloseConnection(); + lockScreen.Dispatcher.BeginInvoke((Func)delegate() + { + lockScreen.FocusPassword(); + return 0; + }); + break; + + case DisplayType.ChangePassword: + BizContext.CloseConnection(); + this.changePassword.Dispatcher.BeginInvoke((Func)delegate() + { + changePassword.FocusPassword(); + return 0; + }); + break; + } + } + + + + + public IEnumerable Categories + { + get { return (IEnumerable)GetValue(CategoriesProperty); } + set { SetValue(CategoriesProperty, value); } + } + + public static readonly DependencyProperty CategoriesProperty = + DependencyProperty.Register("Categories", typeof(IEnumerable), typeof(Main), new UIPropertyMetadata(null)); + + + + + } +} diff --git a/PasswordSafe/PasswordSafe/PasswordSafe.csproj b/PasswordSafe/PasswordSafe/PasswordSafe.csproj new file mode 100644 index 0000000..d8ae3dc --- /dev/null +++ b/PasswordSafe/PasswordSafe/PasswordSafe.csproj @@ -0,0 +1,432 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {24CBC0A1-F7C4-45A0-8D58-E1BB1B4F0629} + WinExe + Properties + PasswordSafe + PasswordSafe + v3.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + SAK + SAK + SAK + SAK + DC5153CC1108F6979177444F8EE11070DD681C50 + WPFPasswordSafe_TemporaryKey.pfx + true + false + false + + + true + + + + + 4.0 + c:\temp\pws\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 18 + 1.0.0.%2a + false + true + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + true + GlobalSuppressions.cs + prompt + AllRules.ruleset + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + true + GlobalSuppressions.cs + prompt + AllRules.ruleset + + + + + + 3.5 + + + 3.5 + + + + + 3.5 + + + 3.5 + + + + + 3.0 + + + 3.0 + + + 3.0 + + + 3.0 + + + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + App.xaml + Code + + + + + Main.xaml + Code + + + + + + + + + + + + + + + + + + + + + + + + + ChangePassword.xaml + + + LockScreen.xaml + + + TemplateGrid.xaml + + + + + Code + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + + + + PasswordGrid.xaml + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + .NET Framework Client Profile + false + + + False + .NET Framework 2.0 %28x86%29 + false + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + false + + + False + .NET Framework 3.5 SP1 + true + + + False + SQL Server Compact 3.5 + true + + + False + Windows Installer 3.1 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + + {333FDC55-6B47-4A64-A2DF-A4C5823FAC74} + Odyssey + + + {E4DA0115-54F3-4DA0-813B-CDEBF4D205E5} + PasswordSafe.Data + + + + + + + + + + + + \ No newline at end of file diff --git a/PasswordSafe/PasswordSafe/Properties/AssemblyInfo.cs b/PasswordSafe/PasswordSafe/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..cae0ce5 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PasswordSafe")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("www.tomssoftware.net")] +[assembly: AssemblyProduct("PasswordSafe")] +[assembly: AssemblyCopyright("Copyright © Thomas Gerber 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.7.1.*")] +[assembly: AssemblyFileVersion("0.7.1.200")] diff --git a/PasswordSafe/PasswordSafe/Properties/Resources.Designer.cs b/PasswordSafe/PasswordSafe/Properties/Resources.Designer.cs new file mode 100644 index 0000000..78b18b4 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.20506.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PasswordSafe.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", "4.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("PasswordSafe.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; + } + } + } +} diff --git a/PasswordSafe/PasswordSafe/Properties/Resources.resx b/PasswordSafe/PasswordSafe/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/PasswordSafe/PasswordSafe/Properties/Settings.Designer.cs b/PasswordSafe/PasswordSafe/Properties/Settings.Designer.cs new file mode 100644 index 0000000..19d1cd4 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Properties/Settings.Designer.cs @@ -0,0 +1,180 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4918 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PasswordSafe.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.SpecialSettingAttribute(global::System.Configuration.SpecialSetting.ConnectionString)] + [global::System.Configuration.DefaultSettingValueAttribute("Data Source=|DataDirectory|\\Passwords.sdf")] + public string PasswordsConnectionString { + get { + return ((string)(this["PasswordsConnectionString"])); + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool IsSidebarExpanded { + get { + return ((bool)(this["IsSidebarExpanded"])); + } + set { + this["IsSidebarExpanded"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool IsRibbonExpanded { + get { + return ((bool)(this["IsRibbonExpanded"])); + } + set { + this["IsRibbonExpanded"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CanRibbonMinimize { + get { + return ((bool)(this["CanRibbonMinimize"])); + } + set { + this["CanRibbonMinimize"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double Left { + get { + return ((double)(this["Left"])); + } + set { + this["Left"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double Top { + get { + return ((double)(this["Top"])); + } + set { + this["Top"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double Width { + get { + return ((double)(this["Width"])); + } + set { + this["Width"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double Height { + get { + return ((double)(this["Height"])); + } + set { + this["Height"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Normal")] + public global::System.Windows.WindowState WindowState { + get { + return ((global::System.Windows.WindowState)(this["WindowState"])); + } + set { + this["WindowState"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("OfficeBlue")] + public global::Odyssey.Controls.Classes.SkinId Skin { + get { + return ((global::Odyssey.Controls.Classes.SkinId)(this["Skin"])); + } + set { + this["Skin"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool IsGlassEnabled { + get { + return ((bool)(this["IsGlassEnabled"])); + } + set { + this["IsGlassEnabled"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("240")] + public double PasswordWith { + get { + return ((double)(this["PasswordWith"])); + } + set { + this["PasswordWith"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("4")] + public int MaxSections { + get { + return ((int)(this["MaxSections"])); + } + set { + this["MaxSections"] = value; + } + } + } +} diff --git a/PasswordSafe/PasswordSafe/Properties/Settings.settings b/PasswordSafe/PasswordSafe/Properties/Settings.settings new file mode 100644 index 0000000..d2c5156 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Properties/Settings.settings @@ -0,0 +1,50 @@ + + + + + + <?xml version="1.0" encoding="utf-16"?> +<SerializableConnectionString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <ConnectionString>Data Source=|DataDirectory|\Passwords.sdf</ConnectionString> + <ProviderName>Microsoft.SqlServerCe.Client.3.5</ProviderName> +</SerializableConnectionString> + Data Source=|DataDirectory|\Passwords.sdf + + + True + + + True + + + False + + + -1 + + + -1 + + + -1 + + + -1 + + + Normal + + + OfficeBlue + + + True + + + 240 + + + 4 + + + \ No newline at end of file diff --git a/PasswordSafe/PasswordSafe/Settings.cs b/PasswordSafe/PasswordSafe/Settings.cs new file mode 100644 index 0000000..71a11d4 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Settings.cs @@ -0,0 +1,28 @@ +namespace PasswordSafe.Properties { + + + // This class allows you to handle specific events on the settings class: + // The SettingChanging event is raised before a setting's value is changed. + // The PropertyChanged event is raised after a setting's value is changed. + // The SettingsLoaded event is raised after the setting values are loaded. + // The SettingsSaving event is raised before the setting values are saved. + internal sealed partial class Settings { + + public Settings() { + // // To add event handlers for saving and changing settings, uncomment the lines below: + // + // this.SettingChanging += this.SettingChangingEventHandler; + // + // this.SettingsSaving += this.SettingsSavingEventHandler; + // + } + + private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) { + // Add code to handle the SettingChangingEvent event here. + } + + private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) { + // Add code to handle the SettingsSaving event here. + } + } +} diff --git a/PasswordSafe/PasswordSafe/TemplateFieldTemplates.xaml b/PasswordSafe/PasswordSafe/TemplateFieldTemplates.xaml new file mode 100644 index 0000000..cc5108a --- /dev/null +++ b/PasswordSafe/PasswordSafe/TemplateFieldTemplates.xaml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PasswordSafe/PasswordSafe/Themes/Generic.xaml b/PasswordSafe/PasswordSafe/Themes/Generic.xaml new file mode 100644 index 0000000..de13a94 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Themes/Generic.xaml @@ -0,0 +1,35 @@ + + + + + diff --git a/PasswordSafe/PasswordSafe/Tools/RibbonSrc.cs b/PasswordSafe/PasswordSafe/Tools/RibbonSrc.cs new file mode 100644 index 0000000..a35db09 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Tools/RibbonSrc.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using Odyssey.Controls.Ribbon.Interfaces; +using PasswordSafe.Properties; +using System.Windows.Media.Imaging; +using System.Diagnostics; +using System.Windows.Data; + +namespace Odyssey.Controls +{ + public class RibbonSrc : FrameworkElement + { + private static string formatString = "pack://application:,,,/img/{0}_{1}.png"; + + /// + /// Gets or sets the Format String to convert a Name value to a path the contains the image. + /// Warning: this value has a global scope! + /// + public string FormatString + { + get { return formatString; } + set { formatString = value; } + } + + + public static string GetName(DependencyObject obj) + { + return (string)obj.GetValue(NameProperty); + } + + /// + /// Set the name of an image to attach to an IRibbonButton control. + /// + public static void SetName(DependencyObject obj, string value) + { + obj.SetValue(NameProperty, value); + } + + public static new readonly DependencyProperty NameProperty = + DependencyProperty.RegisterAttached("Name", typeof(string), typeof(RibbonSrc), new UIPropertyMetadata(null, NamePropertyChanged)); + + private static void NamePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + string value = e.NewValue as string; + + IRibbonButton btn = o as IRibbonButton; + if (btn != null) + { + btn.SmallImage = CreateImageSource(value, 16); + btn.LargeImage = CreateImageSource(value, 32); + return; + } + + RibbonApplicationMenuItem apItem = o as RibbonApplicationMenuItem; + if (apItem != null) + { + apItem.Image = CreateImageSource(value, 32); + return; + } + + RibbonMenuItem item = o as RibbonMenuItem; + if (item != null) + { + item.Image = CreateImageSource(value, 16); + return; + } + + RibbonComboBox box = o as RibbonComboBox; + if (box != null) + { + box.Image = CreateImageSource(value, 16); + return; + } + + RibbonTextBox tb = o as RibbonTextBox; + if (tb != null) + { + tb.Image = CreateImageSource(value, 16); + return; + } + + throw new ArgumentNullException("RibbonSrc.Name can only by attached to controls that implement IRibbonButton."); + } + + private static ImageSource CreateImageSource(string value, int size) + { + string s; + if (size == 16 && value.Equals("favorites", StringComparison.InvariantCultureIgnoreCase)) s = "pack://application:,,,/img/faves16.png"; + else s = string.Format(formatString, value, size); + BitmapImage img = new BitmapImage(new Uri(s)); + return img; + } + } + +} diff --git a/PasswordSafe/PasswordSafe/Tools/TreeViewExtender.cs b/PasswordSafe/PasswordSafe/Tools/TreeViewExtender.cs new file mode 100644 index 0000000..f263b55 --- /dev/null +++ b/PasswordSafe/PasswordSafe/Tools/TreeViewExtender.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using PasswordSafe.Data.Biz; +using System.Windows.Media; +using System.Diagnostics; + +namespace PasswordSafe.Tools +{ + //EXPLAIN: + /// + /// Extends a TreeView to modify the selected item. + /// + public static class TreeViewExtender + { + /// + /// Deselects a selected item from a TreeView. + /// + /// The TreeView. + public static void Deselect(this TreeView treeView) + { + if (treeView.Items.Count > 0 && treeView.SelectedItem != null) + { + foreach (var o in treeView.Items) + { + var item = treeView.ItemContainerGenerator.ContainerFromItem(o) as TreeViewItem; + item.IsSelected = false; + DeselectItems(item); + } + } + } + + private static void DeselectItems(TreeViewItem item) + { + foreach (var o in item.Items) + { + var sub = item.ItemContainerGenerator.ContainerFromItem(o) as TreeViewItem; + if (sub != null) + { + sub.IsSelected = false; + DeselectItems(sub); + } + } + } + + public static void SelectFirst(this TreeView treeView) + { + if (treeView.Items.Count > 0) + { + var item = treeView.ItemContainerGenerator.ContainerFromItem(treeView.Items[0]) as TreeViewItem; + if (item != null) item.IsSelected = true; + + } + } + + public static void Select(this TreeView treeView, int index) + { + object o = treeView.Items[index]; + TreeViewItem item = Select(treeView, o); + } + + public static void Select(this TreeView treeView, Category category) + { + SelectItem(treeView, category); + } + + + private static void SelectItem(ItemsControl itemsControl, Category category) + { + ItemContainerGenerator gen = itemsControl.ItemContainerGenerator; + if (gen.Status != System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated) + { + EventHandler eh = null; + eh = new EventHandler(delegate + { + if (gen.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated) + { + gen.StatusChanged -= eh; + SelectItem(itemsControl, category); + } + }); + gen.StatusChanged += eh; + } + else + { + TreeViewItem root = gen.ContainerFromItem(category) as TreeViewItem; + if (root != null) + { + root.IsSelected = true; + return; + } + + Category original = category; + while (category != null) + { + foreach (var item in itemsControl.Items) + { + TreeViewItem container = gen.ContainerFromItem(item) as TreeViewItem; + if (item == original) + { + if (container != null) + { + container.IsSelected = true; + } + return; + } + else + { + if (item == category) + { + + if (container != null) + { + container.IsExpanded = true; + SelectItem(container, original); + } + } + } + } + category = category.Parent; + } + } + } + + public static TreeViewItem Select(this TreeView treeView, object value) + { + if (treeView.SelectedItem == value) return null; + + TreeViewItem container = treeView.ItemContainerGenerator.ContainerFromItem(value) as TreeViewItem; + if (container == null) + { + foreach (var item in treeView.Items) + { + TreeViewItem c = treeView.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem; + if (c != null) + { + container = Select(c, value); + if (container != null) break; + } + } + } + if (container != null) container.IsSelected = true; + return container; + } + + private static TreeViewItem Select(TreeViewItem tvItem, object value) + { + TreeViewItem tv = tvItem.ItemContainerGenerator.ContainerFromItem(value) as TreeViewItem; + if (tv != null) + { + tvItem.IsExpanded = true; + return tv; + } + foreach (var item in tvItem.Items) + { + TreeViewItem c = tvItem.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem; + if (c != null) + { + tv = Select(c, value); + if (tv != null) + { + tvItem.IsExpanded = true; + return tv; + } + } + + } + return null; + } + + public static TreeViewItem FirstItem(this TreeView treeView) + { + if (treeView.Items.Count > 0) + { + object value = treeView.Items[0]; + TreeViewItem item = treeView.ItemContainerGenerator.ContainerFromItem(value) as TreeViewItem; + return item; + } + else return null; + } + } +} diff --git a/PasswordSafe/PasswordSafe/UserControls/ChangePassword.xaml b/PasswordSafe/PasswordSafe/UserControls/ChangePassword.xaml new file mode 100644 index 0000000..b3b53b6 --- /dev/null +++ b/PasswordSafe/PasswordSafe/UserControls/ChangePassword.xaml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PasswordSafe/PasswordSafe/UserControls/LockScreen.xaml.cs b/PasswordSafe/PasswordSafe/UserControls/LockScreen.xaml.cs new file mode 100644 index 0000000..42bc319 --- /dev/null +++ b/PasswordSafe/PasswordSafe/UserControls/LockScreen.xaml.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using PasswordSafe.Controls; + +namespace PasswordSafe.UserControls +{ + /// + /// Interaction logic for LockScreen.xaml + /// + public partial class LockScreen : UserControl + { + + public LockScreen() + { + InitializeComponent(); + } + + static LockScreen() + { + CommandManager.RegisterClassCommandBinding(typeof(LockScreen), new CommandBinding(LoginCommand, Login)); + } + + public static readonly RoutedUICommand LoginCommand = new RoutedUICommand("Login", "LoginCommand", typeof(LockScreen)); + + private static void Login(object sender, ExecutedRoutedEventArgs e) + { + LockScreen screen = (LockScreen)sender; + screen.Login(); + } + + public void FocusPassword() + { + password.Focus(); + password.SelectAll(); + } + + private void Login() + { + Main main = GetMain(); + if (main != null) + { + string password = this.password.Password; + if (!main.Login(password)) + { + LoginInfo = "Wrong Password, try again..."; + FocusPassword(); + } + else + { + // clear the typed in right after login to make sure it won't appear after logging out: + this.password.Password = ""; + LoginInfo = ""; + } + } + + } + + private Main GetMain() + { + FrameworkElement parent = this.Parent as FrameworkElement; + while (parent != null && !(parent is Main)) + { + parent = parent.Parent as FrameworkElement; + } + Main main = parent as Main; + return main; + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + e.Handled = true; + Login(); + } + base.OnKeyDown(e); + } + + + + public string LoginInfo + { + get { return (string)GetValue(LoginInfoProperty); } + set { SetValue(LoginInfoProperty, value); } + } + + // Using a DependencyProperty as the backing store for LoginInfo. This enables animation, styling, binding, etc... + public static readonly DependencyProperty LoginInfoProperty = + DependencyProperty.Register("LoginInfo", typeof(string), typeof(LockScreen), new UIPropertyMetadata("")); + + private void OnChangePasswordClick(object sender, RoutedEventArgs e) + { + Main main = GetMain(); + main.DisplayType = DisplayType.ChangePassword; + } + + + } +} diff --git a/PasswordSafe/PasswordSafe/UserControls/PasswordGrid.xaml b/PasswordSafe/PasswordSafe/UserControls/PasswordGrid.xaml new file mode 100644 index 0000000..08daba7 --- /dev/null +++ b/PasswordSafe/PasswordSafe/UserControls/PasswordGrid.xaml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PasswordSafe/PasswordSafe/UserControls/PasswordGrid.xaml.cs b/PasswordSafe/PasswordSafe/UserControls/PasswordGrid.xaml.cs new file mode 100644 index 0000000..571fd69 --- /dev/null +++ b/PasswordSafe/PasswordSafe/UserControls/PasswordGrid.xaml.cs @@ -0,0 +1,325 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Diagnostics; +using PasswordSafe.Data; +using PasswordSafe.Classes; +using System.Collections.ObjectModel; +using PasswordSafe.Data.Biz; +using Odyssey.Controls; +using System.Threading; +using PasswordSafe.Controls; + +namespace PasswordSafe.UserControls +{ + /// + /// Interaction logic for PasswordGrid.xaml + /// + public partial class PasswordGrid : UserControl + { + public PasswordGrid() + { + InitializeComponent(); + CommandBindings.Add(new CommandBinding(FocusSearchBoxCommand, FocusSearchBox)); + } + + static PasswordGrid() + { + CommandManager.RegisterClassCommandBinding(typeof(Main), new CommandBinding(FocusSearchBoxCommand, FocusSearchBoxStatic, CanFocusSearchBox)); + } + + + + /// + /// Gets whether data in the datacontext has changed since last call of ResetChanges. + /// + public bool DataChanged + { + get { return (bool)GetValue(DataChangedProperty); } + private set { SetValue(DataChangedProperty, value); } + } + + public static readonly DependencyProperty DataChangedProperty = + DependencyProperty.Register("DataChanged", typeof(bool), typeof(PasswordGrid), new UIPropertyMetadata(false)); + + + public Password SelectedPassword + { + get { return (Password)GetValue(SelectedPasswordProperty); } + set { SetValue(SelectedPasswordProperty, value); } + } + + public static readonly DependencyProperty SelectedPasswordProperty = + DependencyProperty.Register("SelectedPassword", typeof(Password), typeof(PasswordGrid), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedPasswordPropertyChanged)); + + + private static void OnSelectedPasswordPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((PasswordGrid)d).OnSelectedPasswordChanged(d, (Password)e.OldValue, (Password)e.NewValue); + } + + protected virtual void OnSelectedPasswordChanged(object sender, Password oldPassword, Password newPassword) + { + + } + + + /// + /// Gets the password of the selected category including only those that match with the text typed in the Search box. + /// + public IEnumerable Passwords + { + get { return (IEnumerable)GetValue(PasswordsProperty); } + set { SetValue(PasswordsProperty, value); } + } + + public static readonly DependencyProperty PasswordsProperty = + DependencyProperty.Register("Passwords", typeof(IEnumerable), typeof(PasswordGrid), new UIPropertyMetadata(null)); + + + + /// + /// Gets or sets the selected node of the breadcrumb. + /// + public NodeBase SelectedNode + { + get { return (NodeBase)GetValue(SelectedNodeProperty); } + set { SetValue(SelectedNodeProperty, value); } + } + + public static readonly DependencyProperty SelectedNodeProperty = + DependencyProperty.Register("SelectedNode", typeof(NodeBase), typeof(PasswordGrid), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedNodePropertyChanged)); + + private static void OnSelectedNodePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((PasswordGrid)d).OnSelectedNodeChanged((NodeBase)e.OldValue, (NodeBase)e.NewValue); + } + + protected virtual void OnSelectedNodeChanged(NodeBase oldNode, NodeBase newNode) + { + PasswordsListView.SelectedIndex = 0; + UpdatePasswords(newNode); + if (newNode != null) + { + newNode.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(newNode_PropertyChanged); + } + if (oldNode != null) + { + oldNode.PropertyChanged -= newNode_PropertyChanged; + } + + } + + private void UpdatePasswords(NodeBase newNode) + { + Category c = newNode as Category; + if (c != null) + { + SetPasswords(c.NestedPasswords); + } + else + { + Folder f = newNode as Folder; + SetPasswords(f != null ? f.NestedPasswords : null); + } + } + + void newNode_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == "NestedPasswords") + { + UpdatePasswords(SelectedNode); + } + } + + private Thread filterThread; + + private void SetPasswords(IEnumerable passwords) + { + if (filterThread != null) + { + filterThread.Abort(); + filterThread.Join(); + } + + if (!string.IsNullOrEmpty(SearchText)) + { + filterThread = new Thread(delegate(object text) + { + passwords = FilteredPasswords(passwords, text as string); + this.Dispatcher.Invoke((ThreadStart)delegate() + { + Passwords = passwords != null ? new ObservableCollection(passwords) : null; + if (Passwords != null) SelectedPassword = Passwords.FirstOrDefault(); + }); + }); + filterThread.Start(SearchText); + } + else + { + Passwords = passwords != null ? new ObservableCollection(passwords) : null; + if (Passwords != null) SelectedPassword = Passwords.FirstOrDefault(); + } + + } + + private IEnumerable FilteredPasswords(IEnumerable passwords, string text) + { + if (passwords != null) + { + return string.IsNullOrEmpty(text) ? passwords : passwords.Where(p => MatchFilter(p, text)); + } + else + { + return null; + } + } + + private bool MatchFilter(Password password, string text) + { + if (password.Name.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) >= 0) return true; + foreach (Field field in password.Fields) + { + string value = field.StringValue; + if (value!=null && value.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) >= 0) return true; + } + return false; + } + + + internal void FocusPassword() + { + this.PasswordName.Focus(); + this.PasswordName.SelectAll(); + } + + private void OnCategoryBreadcrumbItemSelected(object sender, RoutedPropertyChangedEventArgs e) + { + if (e.NewValue != null) + { + NodeBase node = e.NewValue.Data as NodeBase; + SelectedNode = node; + } + } + + public NodeBase BreadcrumbRoot + { + get { return (NodeBase)GetValue(BreadcrumbRootProperty); } + set { SetValue(BreadcrumbRootProperty, value); } + } + + public static readonly DependencyProperty BreadcrumbRootProperty = + DependencyProperty.Register("BreadcrumbRoot", typeof(NodeBase), typeof(PasswordGrid), new UIPropertyMetadata(null, OnBreadcrumbRootChanged)); + + private static void OnBreadcrumbRootChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((PasswordGrid)d).OnBreadcrumbRootChanged((NodeBase)e.OldValue, (NodeBase)e.NewValue); + } + + protected virtual void OnBreadcrumbRootChanged(NodeBase oldNode, NodeBase newNode) + { + BreadcrumbHierarchy = UIContext.GetBreadcrumbDropDownData(newNode); + CategoryHierarchy = UIContext.GetBreadcrumbDropDownData(BizContext.Instance.Categories.FirstOrDefault()); + } + + + public Category RootCategory + { + get { return (Category)GetValue(RootCategoryProperty); } + set { SetValue(RootCategoryProperty, value); } + } + + public static readonly DependencyProperty RootCategoryProperty = + DependencyProperty.Register("RootCategory", typeof(Category), typeof(PasswordGrid), new UIPropertyMetadata(null)); + + + + public IEnumerable BreadcrumbHierarchy + { + get { return (IEnumerable)GetValue(BreadcrumbHierarchyProperty); } + set { SetValue(BreadcrumbHierarchyProperty, value); } + } + + public static readonly DependencyProperty BreadcrumbHierarchyProperty = + DependencyProperty.Register("BreadcrumbHierarchy", typeof(IEnumerable), typeof(PasswordGrid), new UIPropertyMetadata(null)); + + + + + public IEnumerable CategoryHierarchy + { + get { return (IEnumerable)GetValue(CategoryHierarchyProperty); } + set { SetValue(CategoryHierarchyProperty, value); } + } + + public static readonly DependencyProperty CategoryHierarchyProperty = + DependencyProperty.Register("CategoryHierarchy", typeof(IEnumerable), typeof(PasswordGrid), new UIPropertyMetadata(null)); + + + /// + /// Gets or sets the text for the search textbox. + /// + public string SearchText + { + get { return (string)GetValue(SearchTextProperty); } + set { SetValue(SearchTextProperty, value); } + } + + public static readonly DependencyProperty SearchTextProperty = + DependencyProperty.Register("SearchText", typeof(string), typeof(PasswordGrid), new UIPropertyMetadata("", OnSearchTextPropertyChanged)); + + private static void OnSearchTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((PasswordGrid)d).OnSearchTextChanged((string)e.OldValue, (string)e.NewValue); + } + + protected virtual void OnSearchTextChanged(string oldValue, string newValue) + { + SelectedNode = new UIContext().RootCategory; + bool hasText = !string.IsNullOrEmpty(newValue); + delBtn.Visibility = hasText ? Visibility.Visible : Visibility.Collapsed; + fndBtn.Visibility = !hasText ? Visibility.Visible : Visibility.Collapsed; + UpdatePasswords(SelectedNode); + } + + private void OnSearchBoxDelBtnClick(object sender, RoutedEventArgs e) + { + SearchText = ""; + } + + + public static readonly RoutedUICommand FocusSearchBoxCommand = new RoutedUICommand("Focus", "FocusSearchBoxCommand", typeof(PasswordGrid)); + + public void FocusSearchBox(object sender, ExecutedRoutedEventArgs e) + { + searchBox.Focus(); + searchBox.SelectAll(); + } + + + + public static void FocusSearchBoxStatic(object sender, ExecutedRoutedEventArgs e) + { + ((Main)sender).PasswordGrid.FocusSearchBox(sender, e); + } + + public static void CanFocusSearchBox(object sender, CanExecuteRoutedEventArgs e) + { + Main main = (Main)sender; + e.CanExecute = (main.DisplayType == DisplayType.Passwords && main.DisplayMode == DisplayMode.Passwords); + } + + } +} diff --git a/PasswordSafe/PasswordSafe/UserControls/TemplateGrid.xaml b/PasswordSafe/PasswordSafe/UserControls/TemplateGrid.xaml new file mode 100644 index 0000000..78afca4 --- /dev/null +++ b/PasswordSafe/PasswordSafe/UserControls/TemplateGrid.xaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PasswordSafe/PasswordSafe/UserControls/TemplateGrid.xaml.cs b/PasswordSafe/PasswordSafe/UserControls/TemplateGrid.xaml.cs new file mode 100644 index 0000000..a838ffa --- /dev/null +++ b/PasswordSafe/PasswordSafe/UserControls/TemplateGrid.xaml.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using PasswordSafe.Classes; +using Odyssey.Controls; +using PasswordSafe.Data.Biz; + +namespace PasswordSafe.UserControls +{ + /// + /// Interaction logic for TemplateGrid.xaml + /// + public partial class TemplateGrid : UserControl + { + public TemplateGrid() + { + InitializeComponent(); + } + + private void edit_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) + { + + //SelectableCategoryField field = (e.OriginalSource as Control).DataContext as SelectableCategoryField; + //field.IsSelected = true; + + } + + private void OnCategoryBreadcrumbItemSelected(object sender, RoutedPropertyChangedEventArgs e) + { + if (e.NewValue != null) + { + NodeBase node = e.NewValue.Data as NodeBase; + SelectedNode = node; + } + } + + + public Category SelectedCategory + { + get { return (Category)GetValue(SelectedCategoryProperty); } + set { SetValue(SelectedCategoryProperty, value); } + } + + public static readonly DependencyProperty SelectedCategoryProperty = + DependencyProperty.Register("SelectedCategory", typeof(Category), typeof(TemplateGrid), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + SelectedCategoryPropertyChanged)); + + + private static void SelectedCategoryPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((TemplateGrid)d).OnSelectedCategoryChanged(d, (Category)e.OldValue, (Category)e.NewValue); + + } + + protected virtual void OnSelectedCategoryChanged(object sender, Category oldCategory, Category newCategory) + { + } + + /// + /// Gets or sets the selected node of the breadcrumb. + /// + public NodeBase SelectedNode + { + get { return (NodeBase)GetValue(SelectedNodeProperty); } + set { SetValue(SelectedNodeProperty, value); } + } + + public static readonly DependencyProperty SelectedNodeProperty = + DependencyProperty.Register("SelectedNode", typeof(NodeBase), typeof(TemplateGrid), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedNodePropertyChanged)); + + private static void OnSelectedNodePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((TemplateGrid)d).OnSelectedNodeChanged((NodeBase)e.OldValue, (NodeBase)e.NewValue); + } + + protected virtual void OnSelectedNodeChanged(NodeBase oldNode, NodeBase newNode) + { + } + + public NodeBase BreadcrumbRoot + { + get { return (NodeBase)GetValue(BreadcrumbRootProperty); } + set { SetValue(BreadcrumbRootProperty, value); } + } + + public static readonly DependencyProperty BreadcrumbRootProperty = + DependencyProperty.Register("BreadcrumbRoot", typeof(NodeBase), typeof(TemplateGrid), new UIPropertyMetadata(null, OnBreadcrumbRootChanged)); + + private static void OnBreadcrumbRootChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((TemplateGrid)d).OnBreadcrumbRootChanged(d, (NodeBase)e.OldValue, (NodeBase)e.NewValue); + } + + protected virtual void OnBreadcrumbRootChanged(object sender, NodeBase oldNode, NodeBase newNode) + { + BreadcrumbHierarchy = UIContext.GetBreadcrumbDropDownData(newNode); + } + + + + public IEnumerable BreadcrumbHierarchy + { + get { return (IEnumerable)GetValue(BreadcrumbHierarchyProperty); } + set { SetValue(BreadcrumbHierarchyProperty, value); } + } + + public static readonly DependencyProperty BreadcrumbHierarchyProperty = + DependencyProperty.Register("BreadcrumbHierarchy", typeof(IEnumerable), typeof(TemplateGrid), new UIPropertyMetadata(null)); + + + } +} diff --git a/PasswordSafe/PasswordSafe/app.config b/PasswordSafe/PasswordSafe/app.config new file mode 100644 index 0000000..c68c049 --- /dev/null +++ b/PasswordSafe/PasswordSafe/app.config @@ -0,0 +1,53 @@ + + + + +
+ + + + + + + + + True + + + True + + + False + + + -1 + + + -1 + + + -1 + + + -1 + + + Normal + + + OfficeBlue + + + True + + + 240 + + + 4 + + + + \ No newline at end of file diff --git a/PasswordSafe/PasswordSafe/img/Accept_16.png b/PasswordSafe/PasswordSafe/img/Accept_16.png new file mode 100644 index 0000000000000000000000000000000000000000..88664f96c892d902c3b0d34928db59bde351d4f6 GIT binary patch literal 795 zcmV+$1LXXPP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@Gd8K`;w1n80*pySK~y+Tg_CPYlTjST{T>BHP!uK9m#~5) ziWE}QX_-1pH`>y7YR=1aE@o}s^&PemzU@Fod4wnh@>P!B&{N6C6z>wT~7GoQj(NWM7SaaiDxd7fVGYX zK(KV!#YM2JPj2@DZnx-=tGwFtUj~z3Zz}&K;ee88Q-4QkJ zw!g=e*@~E$Py})J;8H#n1BMriWiW{cI1X8k*C<1|91(qnw;C+bKJ2A(PK z>R}1C2k-vV6Mj}Hp=t7(N1(=O%Pd+AeF#hMK!~sf-^QjP+cTIzn+h7O(L>{G1$24p#Nf>_5f%J!=pd2kE_KFP75k;w1X%` z_|V2hr#ccDyeM>bnXuI}1;3?A6e)>y#_6s7^UxS-8}!g>nqbnFLa%0!#*2o*Xu_bU z96ClnKmwy67OMkhiw%?J84Qh#1K9}Mk&SC$AyF#~C2 z1agIOxOV*}dSCW=lRphnjjs-89`9&(^DOHeADJ~)BLN&6NwBilcdaY~y Z{{@}{^PrVo$EW}R002ovPDHLkV1j@IVVM8` literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Accept_32.png b/PasswordSafe/PasswordSafe/img/Accept_32.png new file mode 100644 index 0000000000000000000000000000000000000000..cf427bf69e796b8dadf8bee8dc06e4f91551b344 GIT binary patch literal 1966 zcmV;f2T}NmP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@Gd8K`;w1n82Omj9K~z{rt(R*|Q)d{*{oapD_I1fVEK8Op z`?Ad~X5tJOw`^fkql@C4PGndFCl1kYnKDJBw&f0j2qL0R$3SsVw*~K_fCA;>Wpb%N zp)HrT^osvI&wI`(OhtuxnkVlmZO`xjT;G>@^QJBp-OkmfJ!THK1r--iP+>s+aRc(o z4ah04Lrz&8vde0bRr&`q$n+&O&=uD}d-xnQMdy%SbQbA_XF<3($zGoWHt-V_3wYM` z_%RSL`U2liPLk0L8w)6?_>)C^Bnz374psgW0t8gN7%IUg6*%4aWn%%xZaZT47XB|0 zDh|DJ2`ImW_fud6%B#;$Mk@>8i$#31WzU>NObL)f9`Kk1_}SQFf=cj7VP1N#2r$`z zD{W?cvO@hbB9ua(s)0O;0DCKpb1Xuvtg^@KHJO*;K3d793ux_eqUvf7KKOFuj3P=; z)#p!STYa6J+vwUn(o=~cZ&s;S4=3Y>%vJt!EcKeL9;7RGxJnA#|)yq0)-|*&0%J5Web4)LV(NT$Ao(l&IuoW{c{+} zMFViT{lX^bGZsYxLiZfZL2_C$8XFqWZfwPwZ`a~+cqpKre_{w9n>tc*;+j>Hsvo45gQtbH44B?r-AG@_yQ z9Mln88NO|rfZD0BE8%9(6|V{Gmci`Z1ic@#`w+JKK0@MKu`ud3`pgq>5Zp20hi%k} zG+hpwD%YW_vli#E()SG66O9D#I3-7HI+D-d=@o983_$9QIbKwC2CbJt6 z1h2T@#Ie6!ShDSoC@QXK6M6pi&Q6@V=t5Ms7w>)7N^m=MffqwI2l6sg(NP@=3}*wq zdQA3a435(P@@Lt$ibPtTf%%1C0V5|%RdiDUFJK^d`!LNIkApMs_T)N%_V^3b7#@meF=x_Z$+>`?Xsj=)O@I7|X~6XDH;RvxEgRN)OqGZuuOMpgX-F@UaI zyM@!K>w)`qa5NWV%$Sbi9YHvdl7Nd>n+V>6M+7%h;mVuQ%LzD0CtYdaC<17*(H!9Z zXCYkiocGkY2)J(2kQulEU-x;G#0MigLqqUJv=ZE8whOq~HcJ6~%ocEYH{&71a3O%Y zV}t~ps5phjBipIZ>+$$r73y+gk*(7T_(PLbfCmXYXmg2mRuRz98J{D-0vs-%n1T}A zC$`)N-TlbjwT=qz40;V(WbTf|(ehJNaLs7xkUSr>O5j1O8v|ss62SjthyvvNlL+3` zr0`2AC@L<+Uj;wmZ0=5IH5$}kzKO;=o#^_f9|IPrX}!IKuW zXfbxg#pAr89yc1>(0;cMeS@PS*GlLmuq?nb2LWQ^adY^kE>I%C1;{nPU0@ya&{@EP z;Zcv+oQ7y8WD&x9ih>Y9@cvl~D6Gc5+zO}@^jM|-dB#KLOoz|s97_4r^Wr%`U*cKK z2Sok>_wK7OeilEIpZ(lPDE0&Si>&rl+6&?T0)T$NY0%ULMF0Q*07*qoM6N<$f}Nn1 AV*mgE literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Add_16.png b/PasswordSafe/PasswordSafe/img/Add_16.png new file mode 100644 index 0000000000000000000000000000000000000000..b77b5ab30f7ae4677a65ff10d41d1d2d8df20a07 GIT binary patch literal 572 zcmV-C0>k}@P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0ntfBK~y+TrIXJq z1YsD*-&wnoT^3<8IQS74`LXtbgWQx_7v=2W=7NiqI8iQ=Qe6E3LQYCr2T8JRaT~43 zvRb>wn%$juW?s)TyxU|=DQ|sxpLgEp`99D4zRwWy&mq{wi=x=x;PX}CenOU2Ns=O9 zY5@CF&ZUL<-J9@*j%;skJVQ_);QSfvuv2rEIg3htB@V>lCT{TS28#YHkTE_;N>7@k zjWovnn#{I>tezn$4Fmv1HwAtj<|PJ(yQbUxO##M8{!$y8>j!&>Iz`;Sy}r{!`{!5Q z8jmChf}v(8Iodg1mlK&wGhl3SWUqg`Cs>d|usjl|^P$LDvKri4ljH9z<`HBgCB$fq z6retm7{{_7r%!}ZS`DEt^I4*i`>4W{N2al2o~5kW?XC4!xNx0*P}tMiYZA-HHO*;6 z&Cwj%yv7Q!2LLY@Xq^;iTPEuKxQ>TX%GI2-2_`m^{_P{fe9HY%2&>7{FjIceb5hTc zrsXPphA9C1!RqJW&xkjKZJH;oy|WiJP7s;CVWI*b_-5GlLGFR6A7QEidREeT*}UTX zZ2L2jV$uPPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1UgAXK~z{r?Uzk# z8$}ey-`Z<$8apNlF%g2)4XN_crV-&=s1iaIUssN~Q3+5H5_iN2ajH~~2reL1AcRCO zRDwf$p_fV&M4_l33LybEGLB6{yxv{!*9`yX#@4R0wzCx}7e@NY-r1S=-n@D9<}K0x z?LYQ_5?6mFlUXPf3eRM-Sv5FU2tmbSu~aUXzd^qaj-&)JP^zS2lEs8M>S3D8NJz|%=53jv$Qm`_)ut^({SnZ$uDac&R)Ej z(vp|cT6!g!OqjtX`@*MEapNk2s$V7fImt!k5pC-x1E zWI{uPFR8e^E;oL^djm9(TUH#F0GV5fCTnJsT~7+gc`!6x>D$>+r@f5g+mk|g;k4#~Z?&*7m;hETYpJRv9bW?ZR0D%B8;Q_<1?-{&6(Z0TJ zZ*;P?-`a5M@zMF*3p6NIvu;v1EYl({>5;3rIF3#Ln+zRJ{@ds}cdJ~ZmGkR=I=XuW zdRIWT9ZG6eeRlXj_PCl(6g*Fk;zirIU{I!W>5(HRCdWsnbD>!E9iNszFE(yosI23K z8x_dDEogJKwk92U9`DbAYr$9^Kt}=ab$S4#qJ(H;=_ek0clyQA#i5XaEd0u;)k^W~ z1`98NI*7{6Y}^|79;^n1aAL3l7ZSV*cG0h$r1Q!A@X^61ljD?hrHh;KHje91_0pE1 zU$=h396n+{fa?)5ena0MV1xB_x|x$X8&Cn|D73sflX*%#mKYN%UrH?;&!)}q48yqL zu<{=P8&Zsr!HSzn2*}R6fW5#jVoyZkC0y6yd?D&#d;^zMbL&|KFQlssymJ6=Pso5) z9_#swib!(tVc|#x;Uk8#!3J<^rQp6YW8a1>9iIB&iyS4qa?XT(i^MzGf>t@wYi{uP^>o5N`Is6qf;Ehw00000NkvXXu0mjf DmI*gH literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Black48.png b/PasswordSafe/PasswordSafe/img/Black48.png new file mode 100644 index 0000000000000000000000000000000000000000..d1985a46f0c834dd0d21c7a5fe5baa43f7cd63a7 GIT binary patch literal 4265 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 LNkvXXu0mjfR$ora literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Blue48.png b/PasswordSafe/PasswordSafe/img/Blue48.png new file mode 100644 index 0000000000000000000000000000000000000000..8b457647cf8c0d9c4a79be639c53d0bc91d505dd GIT binary patch literal 4145 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Bottom_16.png b/PasswordSafe/PasswordSafe/img/Bottom_16.png new file mode 100644 index 0000000000000000000000000000000000000000..bc92f76d756d1a3d794bf27cb7b208118ace57b9 GIT binary patch literal 565 zcmV-50?Pe~P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0m?~4K~y+T#Zuo( zQ(+i>jysb%HrhskV4xzzTr>x>ZVSm#t zN|sun%^_`TJKJeH-`RKO`ze#J!?v3)df|chd*A1M&ims4|JxL)0T=mxf*kACpH2xb zk}}zU{r0^V;Twso-4t3>u(P@JA&Op+*q<>}hm}3!|R6 z_T3YGQUKIP#10esoK^D00=Rf50aws{MrtYGwQ2C`Fpc{W?!hb+)dK%*p`fi0p(yqs z2@G99TuZ|{?(Nx|@s%MneHUC`CEg!t2c94TlXI|ZDpPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1N2EmK~z{r?Uq|_ z6Gs%s&#c!@(l`mRh$***O05DaBqo*0wWLrBZ=X2_e1!DLf;PDwRro zp^_7|RRD95sA|CEB0Jv1iIcUx-ksgqot|UaR_w5jVEBz#|cjnB0{^!gEVCUI! z;D2_2`HcS_{y&5$+oaFKI4g*O$n`P(1RU9OaP;cPr{_Knb&;0NABUIV<!$>F&YtKH&y|qj~L8ESiv{~fN-=r(7j9FM5 zQU{L4Sog5X9R?PP*o?B}f%YaYUQPY<*ea$UW2{-^Uu2;=`?~Rd4%8ZOUd}Jrju{I^ zP92niKIRC9slZBV!CF2IGt)oL>Y1rYoW~3XOIAhHRzZCwY$kweWtA7QmJ*7b4i1io zJy5|#1O2eL1-XAF^yTEOxO7dLbqaZj}BQp$kJyrjPs&6iWt)!sVcSV?4Aw$uVvUe=(2p7z`+XZWWA`V-yzEcON zp9h|i@%YMKFFblol+s|Qen@~d{}8{?PFLG--dgqTI?#m;cO(5sUlU<`S0&equ9X{i z^MBZ&PvhW{4jI~R>DZ-XyceOi)SrIS9zZlaKF3_oXAt@PTj&{yfZstVUEGoQN5Dh~ zq+G>SfhSzZfIxub`1QTMGMbUpVRiBbKX4Nw+5;2E`iW~!T&X+Touvc#=|fRN)H~`+W=F7Ae|3!&|#$EhSTE8)o^oaPTdyY zaqk96^-!sw^__kE-Sq`Tzq$tD(R~1zV_@%=hvE|xR(`z?o^>Awr`1AfsMLvHU{X4Q z97PTxv?|`^Avz@;j(q!3lK=n!AY({UO#lFTB>(_`g8%^e{{R4h=>PzA zFaQARU;qF*m;eA5Z<1fdMgRZ;QAtEWRCwB)lgmq#VHAg-Z)P+UEj6J+sbHB5%d?hk6dRagt^fQl}Rx~i8MqEwPtj3W}5H%-oCemrbt29 zZ*$IEGdAwbhkAoMrngo8YwlYvJ{CVq>>I5RpnIfRh&FlPj01$UZH{sPH)9E2v+Rp5N-}YZ4PL`8h z^8pSe0F*Kq6=I}^mM&Vl41Cy6{lONze0B?d*WXS}brQF@PX0#;r41VbZ7|Bi2p1zf zjPw9#I(vuo*aDqhV+_B%N>xRYFz_klvV701BbDB`f>MT9`3w#(v9g?FX>koxd4x+> zUeouYkIBIfD)&I(c}O7$J)a;F#O38U)>?*ppV(8U51_>w)+l15Fj}LPLP?1d0x1L| zlhqW8E}85)nanzcqPx999IG906a<96PvE)uu8UtPQ7F2!U#?|#?kh(R8$zMBSFlDi z`{f&2X|$9mDUm{KPLGg6kjW~p-}=afwra|fAz`#BFec{d;~unj5Yj=)jjFYSRGLU? zA}Oh@f#&07B=L~ZM$h^Ejs4Fb*s=G6|CEG65k`tgN+PMZ1^=600|4!V#P$yJ2}%F} N002ovPDHLkV1iaYIAQ<* literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/DateTime_16.png b/PasswordSafe/PasswordSafe/img/DateTime_16.png new file mode 100644 index 0000000000000000000000000000000000000000..35b631a1443ee1c96355887191a60916f738c5ea GIT binary patch literal 1424 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 BDS-e0 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Delete_32.png b/PasswordSafe/PasswordSafe/img/Delete_32.png new file mode 100644 index 0000000000000000000000000000000000000000..2c29f11b7640fd74003ec065a502cd5c614320d9 GIT binary patch literal 4080 zcmV004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000U( zX+uL$P-t&-Z*ypGa3D!TLm+T+Z)Rz1WdHz3$DNjUR8-d%htIutdZEoQ0#b(FyTAa_ zdy`&8VVD_UC<6{NG_fI~0ue<-nj%P0#DLLIBvwSR5EN9f2P6n6F&ITuEN@2Ei>|D^ z_ww@lRz|vC zuzLs)$;-`!o*{AqUjza0dRV*yaMRE;fKCVhpQKsoe1Yhg01=zBIT!&C1$=TK@rP|Ibo3vKKm@PqnO#LJhq6%Ij6Hz*<$V$@wQAMN5qJ)hzm2h zoGcOF60t^#FqJFfH{#e-4l@G)6iI9sa9D{VHW4w29}?su;^hF~NC{tY+*d5%WDCTX za!E_i;d2ub1#}&jF5T4HnnCyEWTkKf0>c0%E1Ah>(_PY1)0w;+02c53Su*0<(nUqK zG_|(0G&D0Z{i;y^b@OjZ+}lNZ8Th$p5Uu}MTtq^NHl*T1?CO*}7&0ztZsv2j*bmJyf3G7=Z`5B*PvzoDiKdLpOAxi2$L0#SX*@cY z_n(^h55xYX#km%V()bZjV~l{*bt*u9?FT3d5g^g~#a;iSZ@&02Abxq_DwB(I|L-^b zXThc7C4-yrInE_0gw7K3GZ**7&k~>k0Z0NWkO#^@9q0fwx1%qj zZ=)yBuQ3=54Wo^*!gyjLF-e%Um=erBOdIALW)L%unZshS@>qSW9o8Sq#0s#5*edK% z>{;v(b^`kbN5rY%%y90wC>#%$kE_5P!JWYk;U;klcqzOl-UjcFXXA75rT9jCH~u<) z0>40zCTJ7v2qAyk54cquI@7b&LHdZ`+zlTss6bJ7%PQ)z$cROu4wBhpu-r)01) zS~6}jY?%U?gEALn#wiFzo#H}aQ8rT=DHkadR18&{>P1bW7E`~Y4p3)hWn`DhhRJ5j z*2tcg9i<^OEt(fCg;q*CP8+7ZTcWhYX$fb^_9d-LhL+6BEtPYWVlfKTBusSTASKKb%HuWJzl+By+?gkLq)?+BTu761 zjmyXF)a;mc^>(B7bo*HQ1NNg1st!zt28YLv>W*y3CdWx9U8f|cqfXDAO`Q48?auQq zHZJR2&bcD49Ip>EY~kKEPV6Wm+eXFV)D)_R=tM0@&p?(!V*Qu1PXHG9o^ zTY0bZ?)4%01p8F`JoeS|<@=<@RE7GY07EYX@lwd>4oW|Yi!o+Su@M`;WuSK z8LKk71XR(_RKHM1xJ5XYX`fk>`6eqY>qNG6HZQwBM=xi4&Sb88?zd}EYguc1@>KIS z<&CX#T35dwS|7K*XM_5Nf(;WJJvJWRMA($P>8E^?{IdL4o5MGE7bq2MEEwP7v8AO@ zqL5!WvekBL-8R%V?zVyL=G&{be=K4bT`e{#t|)$A!YaA?jp;X)-+bB;zhj`(vULAW z%ue3U;av{94wp%n<(7@__S@Z2PA@Mif3+uO&y|X06?J#oSi8M;ejj_^(0<4Lt#wLu#dYrva1Y$6_o(k^&}yhSh&h;f@JVA>W8b%o zZ=0JGnu?n~9O4}sJsfnnx7n(>`H13?(iXTy*fM=I`sj`CT)*pTHEgYKqqP+u1IL8N zo_-(u{qS+0<2@%BCt82d{Gqm;(q7a7b>wu+b|!X?c13m#p7cK1({0<`{-e>4hfb-U zsyQuty7Ua;Ou?B?XLHZaol8GAb3Wnxcu!2v{R_`T4=x`(GvqLI{-*2AOSimk zUAw*F_TX^n@STz9kDQ z$NC=!KfXWC8h`dn#xL(D3Z9UkR7|Q&Hcy#Notk!^zVUSB(}`#4&lYA1f0h2V_PNgU zAAWQEt$#LRcH#y9#i!p(Udq2b^lI6wp1FXzN3T;~FU%Lck$-deE#qz9yYP3D3t8{6 z?<+s(e(3(_^YOu_)K8!O1p}D#{JO;G(*OVf32;bRa{vGf5&!@T5&_cPe*6Fc02p*d zSaefwW^{L9a%BKeVQFr3E>1;MAa*k@Gd8K`;w1n81pP@wK~z{rt(R$NR96(oUskhg z6ixa;qqemMv=kfMmlUH3(%KeckrYWzv{C<-M7g(t$ts&dj;z|2ya2_hz19m|lO^*VF$YP8-b% zGUxaD9_Dy0EG#H3E-os0dPxk%xw$!NYHFg4jEwKuEQaOh)l?4v0Funk&MILJ&Ck!< zN@5U?H~_S?v`}ttZZ_wRV);9UST2B=3p4;)TU)8Et&PUU#waHz2M{kghOh&$>w*;k zs&8y;RJ%4gIZ4^s*;ia=!h0vo0QxrQ+o2olTZRraG&E3YX(`p#)~b1Pb2DXSWo0_r z4gr{%nOSlU%%=L@>u09ByPL|&%BZ}&oT{sRneCLu~YTb?5+2AjB9W;-Ts3 zY4;rOr>3UVc|dM9f7`Vk*5?vINTw&zP3-2nIo84y`#&);LF41&DxV7=&D90(0Om@p z%*9wL7jrWJU>pNT+mOr*p&L3iM+g2B0IGll#M3CTFw$EObL>C1!f^*+B#9)k9fG6>xYCg<{VmvrFND%Thd~%xZ?cPnr zJ9kpmhbi<`RMb)k03az6NDE~&1BrR%(WCVE@L{S*NT4d7cb_{)g98J~7#iStO=>DV z+`E@b8K8s#isItv{_%HlYyS5O05C+`Ab{x7p;VRU&r=1PSF-sN?m`uJ0TA_EEsXp7 z`>8S|g&rL^@B(1Z9xBeDCx7sBh7NDYzjt-^H&Q3@Ah7VZ*S6PUbGeiN8w_KIO{3kZg_V=f&zP@xKBAnhb3}4$r z2!QtXcDDq^Zv1lFcDlJ@1>NFGVTv)IVbY_bDQ)dqRk@g7H4Mt~@u8%E0D8mfsR@S= zXF%TqPxH^Wyhb@(SsnxY%$1^A=+v4u)baee%JY6mC|%*ol{xD&16<kY0ed>P5t*fh}1V2Bzl00008+0d literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Down_16.png b/PasswordSafe/PasswordSafe/img/Down_16.png new file mode 100644 index 0000000000000000000000000000000000000000..cede028f71107064d96fbd4b5fb3209585241ed4 GIT binary patch literal 454 zcmV;%0XhDOP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0b5B#K~y+TV_+C) zz=-4kW+0mnh>^vxx$i$v@DmXK1xX?+<~MgO-lA?As)@^t|Ns9pTsb!X_W6S|lYn|} z1Mwe_tuXNG*Pnl%Bs8NVIRy218QG<|(D8@&?^sXoXxRPx=cnsHP0xY&AF=@q-#$LQ z`|Ufk0;8aV!Kd#m3?IKT!122eUm4DC&p-X~`KdKPjmLoa8^~r913)Hzd~xAC1Fx1Z z6OWeAuiuOeKYueY{P_K!;qk>eFJD~jU(CSpe;3qtP*`9X0P@zS?;oGMU>0`p`^zlE z^6L*U#9m$g{rp(^n*V=3Z2>B{0#XF?H@XYJ;(xzCx%2xUn*yVNt-;TqzZhPhEjan- z{i)SJsUt9_V~ZYSF<|U)$a5I3y3MXV4H#HVl|V@qAZ8+@5!Db*oj?W_(RQGqEfBK- wF`+bqVLl5`L>7p7acL%GBCPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D16xT%K~z{r?Uvh4 z6Hyd~*R+&!vLKpp5Fr?iM58EFF`D=Y-uW0lfEQj8Li7ntG%E4JD-#p%j7C6(fC|b% z4i+lV!nB1RW~S4bJ$qb>bVz|tTMCI6c9K7n%*<^Q-j0Cvfubfv(O}?p)%P@UM>S|nKtWDLBYyCY z^)1H&BSVOO34_h0O;8zV73{2pdDOKr>{F}FI1XXicA8Uu0m|_SuEjuK5Q~&HUs@;HuMKIKpPe_S!|7I z@pj=@R|hUnCr?vgh6&R8XBMC7n=llACi(!5%ZmP>2CVO8Ll!qdaCyPwZ2$+A0`~?v z2-0l-A3gjLyPjK3mlJ)l25_7n(p9k>+-+W{>ZsJ20gC@cBDL~l7z`;J`fRnAMr|ovv(VGoLu?8r?5Xg%vC^IX-HLCXtik4j@-Ss)qTQBX7<9vf@9O(qA z73D#RC^~GELeL-90F5UDM6d=$e=YR4Lwo(c?KW37#S*Y$(w_>&pgYyvI%f0M{wN4o o6!s=q@Z|aGTzX$3@P82a3quk7E?+IRC;$Ke07*qoM6N<$f~aq=EC2ui literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Export_32.png b/PasswordSafe/PasswordSafe/img/Export_32.png new file mode 100644 index 0000000000000000000000000000000000000000..6370ae8a4903808f28d57e2d90309fb42a14de6e GIT binary patch literal 1809 zcmV+s2k!WZP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@Gd8K`;w1n827*aMK~z{r&6n9zl-U`F=YPoUROQY})!gL5 zNlI&~oN>k_8ZqL=4iRSn!3{y65gKSV!A1drZa|Qc(HbK?sN*;+g5ttzl%;95W>pjw zfoRFjlXDJBpu~WQrqa~!qN)1&eSYtD&iBbD{|_I{J`?kG<3=g}tq!?dV7`RUPv7ZP z2G2^ZB0*P1@WG#$`T&12c)Ye;$@klfh~PWl2_XS;GOFJOn3`g8MiY~hOlyLPi3!G~ z#>W|x8XIGDR->Z|Ujk_T^YL`Z;_zMt;QL+x)z#H3rmCtcDk>|fs3@nryqvpt?{Vk$ zZOU$yar5R)N=r-U0Q?Ua@vzV0fZLDn5AbiD3-`*oka8|u&uU~|24cgmqCNbp0J!k} z04s`D@Skc*luec(T2n&Eq3Z(R{L%nt4GViB1r!s! z|2jU-KP;VrY(wRODUh8$tB_y~CFw^ohPaV$`Wydv^=d)QM@FZe4-ZQXtLLTx3eoud zf|vbr0V4jj2in`)k+NE?7>!0Q=?_AaiJELbf;@I{=1hig+^AeD=jI0GzP`R*ji>|& z_b(*K`x>71xl3mtE431R_^r7`G=J93jg6!xCD2`T61s0OT%C>S_#O;NCm0$UnsGm@ z+)K&*AOXVs3KYP1yS`r<;7m#d@nJXT0HllQQ5&PDr-$mAYSN?nc)=kud!Tsk z&cJ1BJhorCV?4BmuCj1w@PjTL5A)VyyVj2Vj^8hMR=N+C0Na)Uke+mp*x(Xkgrb9r zi54JH0YyX#MI0?ed*nJtye_cM)qvv`FN}x3;B~!LG^Pd~#Zr4|J$9S+5a4xLxW1;G zOC1wgaNGI;0K!E>h4_jeJ|GSl7Xv%iIdL{%4a1g5Xby#5J+*n81cW<*`>t?IEFdYl z3Ega#0bochCq{FF7_q&gL~D)n-GMv4t41J~3%puV<&W=k_2Z5=%7>gMU=Cqj>D zZnSW>tc>_@e=f)ViP1Jabi~2)6lzV|aMt@_H)R}2% zYQ@Jblf8CHO8w8CG1%WvUvIB~tD~g2g!qV~n4&i_)_F?oi9{Gk=Rw{M9BuZA5?sS+ z^D+QZ1%Pb0NU=U6#0HZYXl-txv%M2<*9`T)K<|78Ui8!7_e=qFicMKnQK=xl{QWwv z>1~+oP7zI+3WM4F{nBm$;e*2l!_pZ@O}L|Wx9oV?a1x-aqm$mA#|j|Kzd!-Jc-ALM z(5uSP-e$#I-zY9FHLPFl%f>%$rR4NBCLS5#X%@UXCop3r{UpO6okA& z$=Q_vLjwa0y?nU(_ydRb{D_ZBF1ywxbMD-E@jxz_nK`7TWs;nnhF*V~ z*w_Sgx;R3#(P%U}0)xV^-(Xa-Tc5_ZuTHS}i!e5>w!=2Uf!{csE_Vh7 z29g#ZgUQu}YG)_R9^a7`6Epj=^3h)orI}U70s`>(>@)N$SE6xum%&fYWc;H8%m!E$ zt<@6h=Qlt2;$!&{kyJ@$^gjua{Z)c1b^P4C1*-{jMF00000NkvXXu0mjfkG^Z9 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Glass_32.png b/PasswordSafe/PasswordSafe/img/Glass_32.png new file mode 100644 index 0000000000000000000000000000000000000000..1000cf4367db877e4356c77e17bef3f35f3a4c0e GIT binary patch literal 2306 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Layout_32.png b/PasswordSafe/PasswordSafe/img/Layout_32.png new file mode 100644 index 0000000000000000000000000000000000000000..000148d50a2bdf661dd5196f6dafb4a3edd116ab GIT binary patch literal 1592 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Lock_16.png b/PasswordSafe/PasswordSafe/img/Lock_16.png new file mode 100644 index 0000000000000000000000000000000000000000..1b4c8ea3eb6195f23d6c6ddddba70ca89b4af8f9 GIT binary patch literal 653 zcmV;80&@L{P)gJr(_~CcYeB57&^i^0pk^h4S-8o@E?imY!bROw>Y^KwR5yaZz(s_b zr3*LWLJGFv4~U4R#flC(>ex0dX_`qg^PY=ozFOUQ;lX>CbI!f@Tpr<2WV6{oCX>0J zN~NYtrP6?9S>!m*He>9CX`1teLZQ@&=Z6L6=H?3Nbovev_1Lz(RxX!qB1*+#u}g_W z;#xQyezv~8E}D4ONuHXT8Xp-Mx#>8LSYBSfGBYzXwy>~Zn5JpCuDh%2`US%3}}i8zFW6avx{kU{{hyKKGf+Uj=xtvUGcIjRZ(aE4@$7mwGsb^$3Nh4d>Qlpx;l zkLrQAgcgd%Ij1BbzqUq@Btby}6b{A&D$Y+VeJCLm+{4w;5L89&aJ1&P-u8eHNJSBd zXmGb|h`kb)RyXnJg#c+Hd!)Hz-qSnzL z{tQu#wxPl|7LZK=iMq7|g~dRMpBjoC$M7i~T5ty^dIq{yN1}FgjVz>qsqHz*Yk)U4 zKvV|nRl5XOLjh5TfCTDZK!8Qsz@X%qzy8A~1hg&zu`eMZ>%MyjQOrl4CiT&2&I3n& nXct*DJbQC3e|~!JHURtq?K$RYqNa7B00000NkvXXu0mjf%B(Qg literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Lock_32.png b/PasswordSafe/PasswordSafe/img/Lock_32.png new file mode 100644 index 0000000000000000000000000000000000000000..6735b75396514c15a6aea82e2982b84164daf87d GIT binary patch literal 1498 zcmV<01tt24P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@Gd8K`;w1n81xrapK~z{r%~$JF)MXU*59xE$OidqaDyMIL zV5ZFLC~{M*(GsaqH128y7%d@Inl_xTZbNZZ8w#uJD6Fufpu&c8 z)~h&6$h*{nGvzJFz0i#8vSwJynqVny!hfQl1uXY@@&qtJf57L31p*xyETE*O{pm%V zUnD?C<%dZX*@7mg!@L0&@YBE);xbB~P6S)8KSIhslD&^ulXCRV6DYC({;-J9*tGSF zFa;Qvn;DaUc?M@pq>6k&;Gt(NV8{vFzCVmNcgCzwL@_zQ*$TMEVzG`!#+)_5!{lCy z1b+d&gEMHnGl*96J%kE-AD(}G zOK>8Js_kZHbC;WSHnR2_8M{41PH-7u0Uv!*jL^d;m&KY#w6L;G#>;Fpv6^k>Yz>&i z^CDo*Jx`$MqDYvf^Q=of&(iN$R9;cptu=`#thAftY`L~uTdgw|LLnv+lhioV^URz9 zItMAh=CfVf&6(@0iP)uynVhrLWE7|6?3Fk@%kTy#fLAV$nW{B4nunUqzurtJP}$B% zwoIrnMTApCtf0<8IZ*JWO^ujQ@8+97U@;o6_n@Ku9_re@L#?e7ms{_ks_7Og8^49M zp#zudu3>V5?cd{~5@g)idg}T~-SKQY*PGzvniJ{h zgd2|;<8EZdM^d-f0NoVt8Q^nwgz-#Keiwm}E3e{Y?DoJ?4b0^ zxklhVb$oBE#LmhPMD2PB*S@00>^*G4VcUe%n5}^Va_FY3nokJ`nD>CD>XonsWnDyKwmN;1^6Wekvm^N^xiiJ8-ae``{DL&NLeg^uh~Lwe8I2)y;iyYs^g8i zll^QvlaCO&W6Xt&V|y^yn}*4b5QX*-xbH>_xwq$_`HtWJTKOW9KX@n5fo!_tvjeLB z-r!3>4tV=>;pz$}gh{M9w*&hkU&9xfo8UIyyF0`2d)>yR4)8x2(ty(aYWAi3Wz8DP zhFlmW7o_>b0heU29l#JxK;Or}&+$|OiVQgu`i|3CJwLK3Xec$|*R*WtdtenfZiFr^fjA)F^ZbRQE*07Vz4c`7jRw>LxB*!NFPFiN zI5_HGUKJ0@pF`q-w*q^>QhEiMMKy>?$;0lLQ!8FFSGs&g9g3snvV-QEaA*ro#B7sM zOQmxPHq)$ literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Max_16.png b/PasswordSafe/PasswordSafe/img/Max_16.png new file mode 100644 index 0000000000000000000000000000000000000000..ddc27b1ee46c199a825b67a414cf52c196f4e25d GIT binary patch literal 1285 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NSs56Z83KGlT!G>S0SyKL6AU6ISVl~Bh*;p9INKp%fkVQEu!`l8WxE3^E;v*? z2mqpn3jqxe0$U!2w>}8yc#za}8mM;^jE2CF4gpJ1#^1nv!cY?A7yN%@1BT%5?5e=X z^pupp-owpyb@*e-@VG_y5=w1&RWfAt-7s% Tt7fMVD7|^Q`njxgN@xNAL?>%x literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Min_16.png b/PasswordSafe/PasswordSafe/img/Min_16.png new file mode 100644 index 0000000000000000000000000000000000000000..4693dc34ba29a9633b17fcb620fe0e3131111495 GIT binary patch literal 1284 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NSs56Z83KGlT!G>S0SyKL6AU6ISVl~Bh*;p9INKp%fkVQEh|1*=RV$*(cL!8l zaHx0?07MNJ0va9!wmb}PeGt&`Af@LFQ2Qtt4S}H>0(XlGvw-=8p(MyJ`2WZT48h;o zRe>?dS>O>_%)r2R7=#&*=dVZs3QCl?MwA5Sr@GFQ{XnBe7ZYQ-(WVz%$AaEX zvY7eXQ+-3_che<(Wd^bhEFmi^Pu`br&fbtCV({VJ+c#FbBG=m_ZoB-#A-FhVgSf%w VZ4ED`*%^Y;ny0Iu%Q~loCID}!aGwAG literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/NewPassword_32.png b/PasswordSafe/PasswordSafe/img/NewPassword_32.png new file mode 100644 index 0000000000000000000000000000000000000000..788e81ca11a24b2695506d2ce3ac3c2f74e22980 GIT binary patch literal 3954 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 M07*qoM6N<$f_yG?DF6Tf literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/NoFaves32.png b/PasswordSafe/PasswordSafe/img/NoFaves32.png new file mode 100644 index 0000000000000000000000000000000000000000..d8ab69916784a9558e69580931c7268b9d7b2c15 GIT binary patch literal 4031 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 V5v&Wp-qi;s5>Hn@mvv4FO#oFC*CGG_ literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Pushpin_16.png b/PasswordSafe/PasswordSafe/img/Pushpin_16.png new file mode 100644 index 0000000000000000000000000000000000000000..a4e0ac27a006db03335fd207e6a11ba6708e9448 GIT binary patch literal 660 zcmV;F0&D$=P)z@;j(q!3lK=n!AY({UO#lFTB>(_`g8%^e{{R4h=>PzA zFaQARU;qF*m;eA5Z<1fdMgRZ;3Q0skRCwB?lU-<1VHn5%zJBdgb}sZ-Zo3l=B7|t& zbhlk}A$?rTybv1a!n<&E)t2nWzP7O}EU-Ioq7@>ZAT2;kjF>o9QhGC%9YSC`DZvdF@0R#ep%XYi{HP7?1eD8VvF{5E2Ez-U=k)-v`tlWS&dws6&BA80VPj*n-fT+8E0tg0G#bslz5r1a z4U!~5mStRXO`}*WBAren8jV6$=#C;EJRyYa=5o0NfZieMbUNuvCWA_)0zwERNkTrK z$Mu;RXmz@C+=-L#lgVVFI*6mky zCvkb@@d8!ach65uAQ%k7VzFR^839qNvAwlL6@|X}^fB)03s_!xG)I+#rTMvA7#$nO zsnd>kM6G7)zxOT>75T2!Gdr7~u*;F-4WHW+ek2^{tf`J%^K@I_6zR-0000NSs56Z83KGlT!G>S9tjQ+3mhUgI3#QcC^!&MaUh}LLPE!df*BVI7CiXh!0^9A z;QtJT{|gNMZ*cg(BVfXU4HIS@n6LtfE;Ouo(6He{!;TLNRvcKc0f;Uv*l}UQjtdhG zeAsZ{!h#b(^1*=v7Y>}baNxp&3qW+^!G#AO9stpc4F*{~zd#Q7{?;^bdi3 zAL>(p`Glb)$S)X_5Jxaz2>#Bl3XB2H0*}aI1_r*vAk26?e+4jr7$izuBT9nv(@M${ zi&7Z^5;OBk^!!{y6v~YCjLlX)e+1Il=IP=Xq7j^W{-)R=1s;|QVXvdMM@dNdmXd+#^;Ifp% hDUQrDzo);qVeGkFGHu(+v_4Q`@^tlcS?83{1OQM8!}S0F literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/QuickParts_32.png b/PasswordSafe/PasswordSafe/img/QuickParts_32.png new file mode 100644 index 0000000000000000000000000000000000000000..6bcbffe12115ab9e0ba7cbaa059d4e5e478cbd77 GIT binary patch literal 1537 zcmV+c2LAbpP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@Gd8K`;w1n81#(G5K~z{rwO9F1TxAsP@B9}vMq_L=MiY%% zt+tvr)}?7H1X}k74Q*1CDu(V!3zeO+Q<$>NP}v5C0cIFrD1}l;irNAdS~Y4*DGWQq zEbkrf{ob4D6Q`9|>mw(;41{y;x#xa-cg>p6PZ)jtf8ZW;nvV-iZ749=k#DpkuhNd3 zN*i*@ZOATf#p%)(WD+vYT9IC2MOtwaQqD9X`HTh0MfV3$tW}G;uZS=f5bBX-APD*e zewQQyz1$B=9zVWF$N;f!T8)UX{1pNOsrX{j0GUcI$?&VVmk1URvG)iIh$5`}Hy6U# z`rzZ-1$0CLj`soEHptekK*O7mYd1iydJ$6jvmqfH)od3vw6TtS#osp2+!y((Y1J<{J#?3(8>yXdA1i9il$Y-Al390CD zNO@}?OhB&Db~n|Oat@i#uYpg7m*DYA0?R5^5yI7FAg97E#*2xa12@pwe*+ztuA{y0 zIvf|rVDBA+t!Gpa^Uoc9V`%BVCeY9^gmW!Fq11dCd8R(Nm;L%fm{Va3j+V3kdQbop zDFQ}eyYMsI9!UseF_z9@fg1Z2lr<1})g`2zy?{lxAB!YH9mCZ+(0bSQfZaZg-9)@h zv+X9dk)Y=D*HGJbRiLbK5cyPjn&ARY6?I`@$xra<6TnBVpLR(Pptbm)_WIC74p^KcXr%s|DS+noA=EJ(N=s%*#O#937G#w+3vv%ZYw=2Z zxwnGcIgADt(9Re_y@Ld(nV4Ddf}zg=5)hbgH;o#no%_8+Y34)=%gK7Snr-F^XBQPB zfq5^cHJF+A=o64xYE=&xzX+kyh7r`aU4gM_5GA$!6t*6u7CUhw*MUPBt%y&y;y{u` zL81k5>bq;s1BNaKG7MH>KUXfQ=fgy;Tm(%Gm(pKN#Y8kc=M{ugj9D*1dDksqS)Fx9 z->5j@>M2wv>mUrZ1IRV@(x`Qb>JMky@U2=sR~RZM z?}2{1LR39DL&StTGs^BZgi5KZE*oBjM4jkd?Q<@Q{ z>3J{*Y6d2zJ(!r*E1;MJh`vio>eT~gjw?_!pI*88``|;_~i})a+8?S_)!??>$&@I3s90&^NnMdyV8Juo+A-#11-!D+7XIa{s}tKTOdK!7s;^siQ2DEz@QKXA^A(@sr) zKmd;%ix_8e>b(#_?%K n5!(&pDg}-#6!Q?m%m$ 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Separator_16.png b/PasswordSafe/PasswordSafe/img/Separator_16.png new file mode 100644 index 0000000000000000000000000000000000000000..74407b9485036d4fd250c421d475cb9299ff2009 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL5ULAh?3y^w370~qEv=}#LT=BJwMkFg)(D3 zW3!ddAE^S>D0{j%hFJI~CrGd!R%J=5_WP&KzWB%e3&yqbmK8f#9&vnUXOK6tNnC!r RIs>Sj!PC{xWt~$(696|#GC2SM literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Silver48.png b/PasswordSafe/PasswordSafe/img/Silver48.png new file mode 100644 index 0000000000000000000000000000000000000000..5bfde1a27232b62b100e005a853d3462c3183378 GIT binary patch literal 4081 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@Gd8K`;w1n84`N9~K~!i%Z`qDq3FMaAu z|Q4A9Dl}rrweHE5nPP*zm~o zsV{m$B6!6ai^bvflbj>OzWJ5o(*d8w%e?l}*Pnat`~EP+h#Ew_eFHG* z66bsS-}~TqFTD8De;@xmNU~j89ip1m6|uP@hC{oTv^auBIg$_uCl`$}NH~&UWG|R>}bsr$#AJ z(#qW%{%>l6gd?FSgC@>9+?k1&oS*(xZs@zSHYbq~5^S1R3J(P*=P5RgzWtv8NI2pP z#-j<*8zMFPyTbJAs_m#dKoD^u92ZI}jRY&z8k?QS6sm>VS~$kT%DNoHJrrdlJ3ny` zL}(NY1PNx*5r8YBY!seOfP@wiVUi=_3@gYXGKn&p3nJ8f#bmMf4NhDinI4`nk5116 z{Ug(Az6v6sHg2+>?;ZT|{MAcWM^X6d^3dtCS8z9aJ7aO4`RW>eLez7i-{p@zoh+bL zEg~pZ5mMRvz)6`>tx>M6KZs(r0qKJ#f21|&ev1e(ozE6mo(5p7X@%OMw287|$ zebXm;CO$oLWi|wzw$vF^d{K2OnECAm${vu7dL>%{j53~BDAn@i6)oSYY1ORK#qH*>fk&yF$(PMh%nZ9TLCeEszqNC>)`z(vyR*h9v!WJ_QBjf#MyO6HD`w4?Ft#&pv(4=ab&AYh z_UKvDrHSQ}edagM%|7UJIjX)p%p>(QL=>+9ZMAq(AQD*D*+`tb&o;>^M2v+1o9)4lN?$tSqGqfGFn% z$7UVwusc9fyOpT7I{eGth|^2VSeAXEgxgOpI(%&fr;mUFY8IBfc6SH^$M9yrwj6Nz z2yEE!Ry*sX3EmSRUH&Ncr0{8o7%2l7WtfCX7A^6H;+>6w2%{hpYl>igp9m%p!82Nw zU{alK(J+{xk+4D+pc9BBp@8nUh)8gxLF=*)$>rRl zbJ-J~x4BSAvMEFA2}G?9?~>CGD~LErYa61l>l-Y`QB3%X-P(b&0GfPfRX9`aPcsT=1&)Liij%l+MBo2<6hQ?>cr!6z{OQ2iP z@>d7PAu`R`a7QKX=r+yY(}fua)!7}xZM(+erjbOoe^PBClDwj2i%$c@bQXvwxg;y% zE-6Ul)o6lA&^*JZC`M4TMbr>+IvQtoboe;ULlXpbwjWtv=TF0JyXHPAy5AZRIBO8) z;=$0|r;;4aY1o)2IW@s3_$kN;HwSUZrLw@jws;nvi)YcC%1OC>^?5mqyo;nH?5}7n zOgrWwl0$=GOL4nIB)Jq37Y_!D&40Nc+tb;LeWMqyjPwmmUAaCPi5<{ej85TGwZ8{?deD$@T{p6KbfA#DCeD$@TzVgqnzWnl!S61uY z5P>r;Pk=CpUijn%bv43kQ2wOConm#W!( z1v`I5LoS#XkBW8&*;?dWOcsoYWoXl}tUF%&T-ZPu$21mxHOy-HH z+40Gl6QBQnY+`zTVF~%nz6cRyn22Ew%H&FU+F2pS%8}9LofvHvqRpQ`)hG}?O&`)0-dw!`V2 zoHn01`_;hk*tP3Jm;3sAuUtc6UlyRiOA25RQCj+J;e+qazyH0^$>-yze{gI3<-M`( z{N)|$7u(}6mcm2jayg&RKQ0kH7y2&s4Sw?Zmvaktyt{)Cfw{L@Upp-13S|&c_I}|z z^St-V=Uu1&K63sCw-#R68`}%d-?4rB*6fSr*sv_ii3CN)smB@-s+PxsC&?;y9kB#^ z5F+rrTeoiAxN$?jv#qbw`pxDs)cX3mzH^}-Cv--6uj|e&y%E>9)@mzjAfhPyE5b~f zpTXTIRE1gGn+35|sO;3V6u9YT0rw1*$z&LYi;{^1Eg|B3-H1`U-!(wQ#>NKLeErs% zF8qrgKjwD3r>3TchldAuuB%tCUcP*pW!b%m*sAI5k97K^K?F2YsZ=hP!#FP#3j48$ z6lFtSi|E@;#$o7odqD)^d0Iot&>hsAPx6sMq*|+Yk6gZtByjh;hMa9Rbo-`WBJ@J^ z_U+p@Z{EZ(!C1f@7ns`K-rlP}o2z;?t`}o%BETc)1CN0~V1FVib9(qWeT9c6P16Gd zgO4J@=?Q!M#DdN3a0NU*!sU(lf@Dg`B^mxR$9wM#zt~v65sN3{iDao%>Aya3@lvnH z>l+)NK>dR#m1}p2z^Z{)+S=Nmh$^kuSbe=|*q#~>9T7M&wc5St z|DxS9VOjtk_=!=2Q~y&_pPd-(M{l#aLT#m<%NLiOF0apT#lJ!L0$mY-m>-Og!7v%4 z*!BWJ#FE(Rce7p?I{0*oyRmBQ-}47GwyH+m-b6fVrl=XbOT_8!QOY;tdXZ{R4U7nM z;Oh0Eo^u!8_{A^Zdgs``{`>21y?yM%kBZ;yWbyAxe2Fq)?APef)>r#{i^&1i)+3m5W;$07nFB44Ol>RmSU~v&7DYQB4p;C=`mt zVs^WoBuQA-A8|Em%I(srP2oB|Ng71JFdPnJEriwnBjTo}S4QCr-@oTqcvr<#OQ*=tHk^M%oPhM}xRf+9kqjwZdv_Yz$WW6Hzef-tXyaPc$`h z_-@`M3eBluwc1Rk*?WjE8j7V3iNljz$Ud-c9b{_Us5G;wn`>Q;h;Fm2y&`BKnM?+P zp@$H$|9k5~qn%JS8Vzs?YB2A*u+#Mox%(J#C`3GDYIN1m?v&a@T)K4W+_`goeSKYJ zmNl!Md_hNA)m^Z>j|c=#40Rk~;J;iVB-0&D1qo*`f=DK~2U6_ry1KVkn+R;%5Loe_ z1i1EhTny-{SzpWFM}(plBsse`cu}2OSVopPGGPu!llb=y#Lm4<@KrIb6%g#OLOo>p z+`qY9XtaswLib*k?bZy!J}n-PKW-j@jy=|yn4;!YRYj)rwG)9=6FDL_wIF_fQnu@Q zBdTvT_x^ZGu-co55J6!gBxn)cz`QW>2yC>XWL(uUdnXZ)8p|g#2IN$CiNN74zC;`r z@QEb(WM_>y`vs8rqhZ(@J`~HI2w-=G@NWnBx(^*Z6zq&o&M|!Rqs+d*VMlXsvGbH> jjIcWc`1Ai9AmM)ie^B6rHkatr00000NkvXXu0mjf3f}S- literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Task_16.png b/PasswordSafe/PasswordSafe/img/Task_16.png new file mode 100644 index 0000000000000000000000000000000000000000..471d1a32888cfecd3dd44872f20ee7689c18144d GIT binary patch literal 819 zcmV-31I+x1P)z@;j(q!3lK=n!AY({UO#lFTB>(_`g8%^e{{R4h=>PzA zFaQARU;qF*m;eA5Z<1fdMgRZ;sYygZRCwB?lS^n+XBdW`bLPw>CLz-%ieQH}y(um# zQ3O#JwhbhWiM=dZT2!PI7hOmwWFeHIAfL0*Od1VdrzRwb zim0iVnK&~!b8_a){QviNQD)GFuJW!vzQyxhcwfsH!$Rczw=wd|XSU@;)*6)`OQ*A> zQ(3NzPsKhz_WShz0#1B?u+E7!T`(dwRBR@EuY--RuED`)CYj>O_|%=_-9u>6h!~>Y z_{;Bo&^A;8II*VjH#V)T1klQ-zq^{=5n*yNm)Ozpa7iQ*VN1h0TyG8t@#+4t!NyDw zsg0Hy8$d)V0SqwEbBI;-i+O8<$474`mXz^;wJ(L~II!&{R=il})JDrpX#)`f zz!-4ne17h|3S#4VLP04iDxTu$XO~koc#K@nH`vX4naKoBZL~}|09tE`gg3YDL203& z4f(vn_p;dG2#bFDg2Zn>aI3W)Z!QO*0qlPP3K4@AgAze$gHnPLu(YB2yL}`Ey14z? zhg7X^;o8k+9sy{y7$k8Y>(VfVAWuOFDwU+V_b|z^Q%o+aC)WBtvvUE7nG~aa zN2T-wtu#s-!l%9^l$~YK*+Fs*+lUT#k%^z-FY5`OY5NjC5N6UouIrY3P&z?rVWi_I z!RbCC*}K$qenN2Z58~Hvuyog9v}KdY`sDH&tu^afc7`51L21Kt2Y%(eC(Oprkes;8 zxrqs)?|uwVE+ZFc{6JGsq2h2U0g5V*JmuUwMbf`ew8?C~dH;5XI<> zo&5T79~6{u??DRJ%VXMqM9%h;Ufqa$H%pM$1Y#%@6j?7fKMU^E$qZ0Z02 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/TextBox_32.png b/PasswordSafe/PasswordSafe/img/TextBox_32.png new file mode 100644 index 0000000000000000000000000000000000000000..e832c3a1ed96dd4fb7f8cbc155a518cc3d328161 GIT binary patch literal 1409 zcmV-{1%CR8P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@Gd8K`;w1n81oBBlK~z{r(?Rq6oBzx0bfK&uD=@oJgRu9Rt2a@1n!J;_}W zbb!iJsgz_gnI`px63F3zDz^ecqg!$mqgr8JT+}v6&qxcLii}C3B#; z0H@p$GxKOPDs63TT112GR1Vb_u$587R->h)W7ka=^%0Kxb#C zbaizxFD*u!PRY&os?4eKJxQ$Y$>ft=8S~1A=U>Xu?2Zg3U&y1xGbKMJvj10rsmF77 zcUKEw$I}76tL-d;-5%=rGd3L5v*WetG;^0bt^prRBhu%<*FJ5mMM>k}(`@)*jQ`N-mH8J>gknHMsUe6GqLPHoGB$t@+O`j!ra=iO$#qoYF-iG-r3 zr$=L8Awlk;dDo8j<7VpF?S!g_^=TB4MgfE$!+H~10gk+?$}NAA5-f;Ogqx1b#=1Q| zw<9slyixi6iB0JrPpR@F;-p0ZY&<3R1(2L}+qf}%7DX3s-|nezI&M4dcFwxXUT0mO zuH$LFDNlp^=%m=}faVV6W$axNQ>}Hp%{!Zp4qU(fwJX3cnz&}Z<1>FM4E{wnJe3Ek{4Z7BKbC^> zHMuvuDtCuhP<$ajVu38MU1q&w0n3`$I-otghMS+-bX6 zPy0W%iolN7jrTIU{!WtEZd2Ip6U%R83^P75|4QQM^cXsQVEUOvr^3oN zkU5l(y3Fs!p**%MeX&L98(h%rKSDjx6hW!sY^QQNUZI}z?rk^quIu4|&bm8bxa-3m z&yhQ1?!NJ;oa6_s3b2a>mCr!=v?_lq6WDI5d;!Wi=iG3C@-5B0f83P&%;Qfq^S;=U zD*tU@9+(T%AEXP-4!GQ1=E1E7YnF?K>h(+yoo>44gWUa!^{du(yc;)lAY?ts57I3n zuyHfxlY#P=KJ))5pJ2B``Dm!y1LZDrD({8z-bb_28%@Icf_wljFF?=r#yefZj&Hcx z*!k`@-7#lfyWM4eNSs56Z83KGlT!G>S0TT=&CRj#H_05{%kg>rde{)3T^1!m~QMJ3n%MJzB9!+lD zUp8rH#pLsomK>Y8`t;&0*OqL(zUk=Qb%&p9KmPE@M!{$Z48;)85b#x{?4unj6==>kH}&M2EM}}%y>M1MG8<*qQo_#Bsf2NSs56Z83KGlT!G>W5(*%oVPF76K;V%O5Rnj2FrlEJp`hbHLqo%ajtL7^EZDGN zL&Jmv4Kof*0HOs4CaeIW3k!f~!-WMqE^Gjz0~dhcz=;P3ZhScK;KKzVdhy`_5Pblm z{||or|M23&2O#RZafenF;mLHD;^9e&qkY6w;QI2515d5886&M?w z1s;*b3=DjSL74G){)!Z!phSslL`iUdT1k0gQ7S_~VrE{6o}X)oLYc9ivDwP!k3bsR zJY5_^G=fvlKNMNlmwEoKn`dp!th7dL)6=FC%kD2_ zd0aVXPuMEPl#Kqh-{l0W-3?^^Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0lY~>K~y+TV_={V zFe2$^2C_MbFz-K5&37OM(F_O!SXB*J)GJmnvI>9{f0=Og2zDo4`BeW znI((SvLlQfl1z9Fc(vo>-}AvKKsB3y_!p8FB;^^z99kLv|CNEcjD`8H*x!E)-~Rt+ z`U-R`*vSmPp8jC?cyu<*0A$NRhKT{O4iM`Cu~mIj&(+}2=siHT9aIb?4#KiPjNw&~ z0Wi$u7nD-F|Ln(qE4E(yB_pSr3zTC=l833oGzH`&30ZBE;KcUnoO}u#jI1KeOf1~m z=ZNxPX;gO<&2jebzsAWv=%? zjmY>HqZrFy1~#pC_qTyu@djoHvH@JYS`~9y#JqGreE!4m;qz}a{DH&^sotH=+rK|t|002ovPDHLkV1g6v@>2i+ literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Top_32.png b/PasswordSafe/PasswordSafe/img/Top_32.png new file mode 100644 index 0000000000000000000000000000000000000000..db73e2130556390c5bf0107228453bb07757c05a GIT binary patch literal 1194 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/Win7.48.png b/PasswordSafe/PasswordSafe/img/Win7.48.png new file mode 100644 index 0000000000000000000000000000000000000000..312691103dc093fc8d006e81aca2afa86183c8aa GIT binary patch literal 4206 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 ADgXcg literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/arrow_left_16.png b/PasswordSafe/PasswordSafe/img/arrow_left_16.png new file mode 100644 index 0000000000000000000000000000000000000000..09cf661bb362846805971293e7eda37c8d914bb5 GIT binary patch literal 3563 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/arrow_right_24.png b/PasswordSafe/PasswordSafe/img/arrow_right_24.png new file mode 100644 index 0000000000000000000000000000000000000000..5e0c1fbb4725d200c273c25d25b5482092293245 GIT binary patch literal 4127 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/arrow_right_24_d.png b/PasswordSafe/PasswordSafe/img/arrow_right_24_d.png new file mode 100644 index 0000000000000000000000000000000000000000..b51dbff2b8f6b3e94d678e5f916a1893d8e60ebd GIT binary patch literal 3761 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 bMf+y}aaeWo8ALn600000NkvXXu0mjfsJsa$ literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/arrow_right_24_h.png b/PasswordSafe/PasswordSafe/img/arrow_right_24_h.png new file mode 100644 index 0000000000000000000000000000000000000000..cbb26510713b24fe24ce145b57208bcf269c3651 GIT binary patch literal 4189 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/arrow_right_32.png b/PasswordSafe/PasswordSafe/img/arrow_right_32.png new file mode 100644 index 0000000000000000000000000000000000000000..e7d8a459a4a567e28ceda0b060a11e6420e86f43 GIT binary patch literal 4632 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/checkbox_16.png b/PasswordSafe/PasswordSafe/img/checkbox_16.png new file mode 100644 index 0000000000000000000000000000000000000000..7ce3a480fa58b4e0b5312c86a5f3d032fffc8438 GIT binary patch literal 1332 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NSs56Z83KGlT!G?-CN(DJbw2*Rsp)eIik1|WEUlL#>ufM;eYjaQUww}Ii z6DRLnw|VFKEju@D-?Mtn$xWNj?bvx~?|~!x4;?vj?8M2_XHT3yfBO8@Q>X8qy>R*5 z#p@TZT)TYj_KjP&Zr#3f`O4#K*PmR!@$}~H2e72V@pScbS?83{1OTbT_mKbq literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/copy_16.png b/PasswordSafe/PasswordSafe/img/copy_16.png new file mode 100644 index 0000000000000000000000000000000000000000..dc3dbf44e8d2a68a27662ffa388f3f4f3dfa6ef3 GIT binary patch literal 3036 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 ANB{r; literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/cut_clipboard_16.png b/PasswordSafe/PasswordSafe/img/cut_clipboard_16.png new file mode 100644 index 0000000000000000000000000000000000000000..150740afbbe83b7a441bc2ab44d22032a143ae78 GIT binary patch literal 3589 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 LNkvXXu0mjfGEm8d literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/del16.png b/PasswordSafe/PasswordSafe/img/del16.png new file mode 100644 index 0000000000000000000000000000000000000000..3dacb2efa771ca419eb570afb97a062b945ee793 GIT binary patch literal 286 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL5ULAh?3y^w370~qEv=}#LT=BJwMkFg)(D3 zW3!ddAE^S>^n1EEhFJI~CrGeLC_V_?_+XCz$wdz?TnIg5;Q4-4>$|vA?sY<YhjTt^$4_}1X!Cr2 z`OPMoMY(R@HoKqPbN=2_ZndU8&2nlLye{93&se)iKZ-Q_Z@O!~V_U`4v-NJ9!re>- a#F-d=HoHuE$$JjuVFpiEKbLh*2~7a|CTFq$ literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/delete_16.png b/PasswordSafe/PasswordSafe/img/delete_16.png new file mode 100644 index 0000000000000000000000000000000000000000..198af936c77f6afcf2f2703ee6e89b9c7753e7d3 GIT binary patch literal 3487 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/favorites_24.png b/PasswordSafe/PasswordSafe/img/favorites_24.png new file mode 100644 index 0000000000000000000000000000000000000000..4c52a8141cba5897bb395c4eef90255750ed908d GIT binary patch literal 3632 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/halfselected_16.png b/PasswordSafe/PasswordSafe/img/halfselected_16.png new file mode 100644 index 0000000000000000000000000000000000000000..45dd78a0616b16ee69d90f442007990991763d90 GIT binary patch literal 3534 zcmV;<4KebGP)004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000U( 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/home_24.png b/PasswordSafe/PasswordSafe/img/home_24.png new file mode 100644 index 0000000000000000000000000000000000000000..f1b435f6a31810a1d6d95ff52414e2c5578c4a7c GIT binary patch literal 3840 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 FV1lv<_D=u+ literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/open_document_32.png b/PasswordSafe/PasswordSafe/img/open_document_32.png new file mode 100644 index 0000000000000000000000000000000000000000..26cbc5b4159fbda743d06f9b33be3bfdf6da3c99 GIT binary patch literal 4274 zcmV;j5KZriP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=00004XF*Lt006O$eEU(80000WV@Og>004R=004l4008;_004mL004C` z008P>0026e000+nl3&F}000HeNkler2&od{kSmISwh{tLDFmmr z1Wt$$rylkU-3Wx?)N-? z<{Vq^{fX!ULTnF1Ozwo3-V_P%i1=F~{1tpLgw8F*2O=Stkhr)*NI`_~=-2iPAQ>N+ z8O7jz+UX*lkAIB97wC64v#P(i(8`w=k|dm!&_ZHDkbo1rA?$&IMJNVu|ekTA|!)*${6c9IKFj4?wjCediw_2bq z6*q6BGt3lsdoTdg5}54-;coPL>nwMN+}S8VB6BC7VSTf}tis)}F%zcu1t7Us0$0OD z5OEMe1Wc8ii%YcI9kMJ#TE_gzlXPcKG0gu)mW9dv_P_w9BpAQA-V5G<%kv>Ksk6LU z@~`DLIX3?(s^ve@N)uud!DSx|Oi3^SKn*~(0^neluvI80AN>ePn$ll=m;NAUI6Gu^ zHmiHdtNlr^8v|xa){YDd1&Ii7FiYtJrWNZOeLC%g7zEeqL7UL;al!PlA_!`AY|nHi zxN%bkTO)?U95=;nzYI(ysQoM=xGKYpaK6h0PW+~l% zF&TL5PHwa?1XD#|g>z_8~z{{WylV9S|5474tx=v7=coLV^@@FhzO4yJE@=Y7{6 z7!5YQ9b)SzrVio_1E5V0q%jbJP?{5haAY>62dFAW1E68xJHLK{mmYU+-(Dev05BAh z;pNMh{Xg39=@)+f)47LEe#6Yxr?bGdqAdk!++G(RXh#OQjwt}@CE3`z^<*^)OG`_> zyu3VITU+bx_VN#`pg;G*t3Lyt!&$FqM#rj(8#iwJa`EOr?}oC^vAGriMH5Wg#IUPU zspBqOTKKvimX*=U%1U=_ZEfl5)vKfb8ARiH>MK8f19%QthgyOQ7cPAN`RAWM?e1S) zUtfQP1Nivavu7W;M1& literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/print_16.png b/PasswordSafe/PasswordSafe/img/print_16.png new file mode 100644 index 0000000000000000000000000000000000000000..1afce8f41433347da1ac6cf6778b9f610ce0eed5 GIT binary patch literal 3312 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/save_16.png b/PasswordSafe/PasswordSafe/img/save_16.png new file mode 100644 index 0000000000000000000000000000000000000000..f843724f06e9bec5fe9ba9219e50f28cbd56c028 GIT binary patch literal 3141 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 literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/search_16.png b/PasswordSafe/PasswordSafe/img/search_16.png new file mode 100644 index 0000000000000000000000000000000000000000..d61dfba397495dd9fb03bc78730c09519a8caf33 GIT binary patch literal 3494 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 Rrp*8V002ovPDHLkV1jm16@vf( literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/stop_16.png b/PasswordSafe/PasswordSafe/img/stop_16.png new file mode 100644 index 0000000000000000000000000000000000000000..4cb911ff8b55fb4d2bc18e2de870afa2db7660e1 GIT binary patch literal 3620 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 Ef-RF{lK=n! literal 0 HcmV?d00001 diff --git a/PasswordSafe/PasswordSafe/img/undo_16.png b/PasswordSafe/PasswordSafe/img/undo_16.png new file mode 100644 index 0000000000000000000000000000000000000000..7440090bc7e92c2f762413ecdb74f565fe98945a GIT binary patch literal 3404 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 M07*qoM6N<$f|eSf&j0`b literal 0 HcmV?d00001