├── .gitattributes
├── .gitignore
├── README.md
├── SolutionColor
├── Commands
│ ├── EnableAutoPickColorCommand.cs
│ ├── PickColorCommand.cs
│ └── ResetColorCommand.cs
├── Key.snk
├── Resources
│ ├── icon.png
│ └── toolbarIcons.png
├── SettingsStore.cs
├── SolutionColor.args.json
├── SolutionColor.csproj
├── SolutionColorPackage.cs
├── SolutionColorPackage.vsct
├── SolutionListener.cs
├── TitleBarColorController.cs
├── VSPackage.resx
├── VSUtils.cs
├── license.txt
├── screenshot.png
└── source.extension.vsixmanifest
├── dependencies
├── RandomColorGenerator.Forms.dll
└── Readme.md
├── solutioncolor.sln
├── solutioncolor
└── source.extension.vsixmanifest
└── toolbarIcons.pdn
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | [Xx]64/
19 | [Xx]86/
20 | [Bb]uild/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 |
85 | # Visual Studio profiler
86 | *.psess
87 | *.vsp
88 | *.vspx
89 | *.sap
90 |
91 | # TFS 2012 Local Workspace
92 | $tf/
93 |
94 | # Guidance Automation Toolkit
95 | *.gpState
96 |
97 | # ReSharper is a .NET coding add-in
98 | _ReSharper*/
99 | *.[Rr]e[Ss]harper
100 | *.DotSettings.user
101 |
102 | # JustCode is a .NET coding add-in
103 | .JustCode
104 |
105 | # TeamCity is a build add-in
106 | _TeamCity*
107 |
108 | # DotCover is a Code Coverage Tool
109 | *.dotCover
110 |
111 | # NCrunch
112 | _NCrunch_*
113 | .*crunch*.local.xml
114 | nCrunchTemp_*
115 |
116 | # MightyMoose
117 | *.mm.*
118 | AutoTest.Net/
119 |
120 | # Web workbench (sass)
121 | .sass-cache/
122 |
123 | # Installshield output folder
124 | [Ee]xpress/
125 |
126 | # DocProject is a documentation generator add-in
127 | DocProject/buildhelp/
128 | DocProject/Help/*.HxT
129 | DocProject/Help/*.HxC
130 | DocProject/Help/*.hhc
131 | DocProject/Help/*.hhk
132 | DocProject/Help/*.hhp
133 | DocProject/Help/Html2
134 | DocProject/Help/html
135 |
136 | # Click-Once directory
137 | publish/
138 |
139 | # Publish Web Output
140 | *.[Pp]ublish.xml
141 | *.azurePubxml
142 |
143 | # TODO: Un-comment the next line if you do not want to checkin
144 | # your web deploy settings because they may include unencrypted
145 | # passwords
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # NuGet Packages
150 | *.nupkg
151 | # The packages folder can be ignored because of Package Restore
152 | **/packages/*
153 | # except build/, which is used as an MSBuild target.
154 | !**/packages/build/
155 | # Uncomment if necessary however generally it will be regenerated when needed
156 | #!**/packages/repositories.config
157 | # NuGet v3's project.json files produces more ignoreable files
158 | *.nuget.props
159 | *.nuget.targets
160 |
161 | # Microsoft Azure Build Output
162 | csx/
163 | *.build.csdef
164 |
165 | # Microsoft Azure Emulator
166 | ecf/
167 | rcf/
168 |
169 | # Windows Store app package directory
170 | AppPackages/
171 | BundleArtifacts/
172 |
173 | # Visual Studio cache files
174 | # files ending in .cache can be ignored
175 | *.[Cc]ache
176 | # but keep track of directories ending in .cache
177 | !*.[Cc]ache/
178 |
179 | # Others
180 | ClientBin/
181 | [Ss]tyle[Cc]op.*
182 | ~$*
183 | *~
184 | *.dbmdl
185 | *.dbproj.schemaview
186 | *.pfx
187 | *.publishsettings
188 | node_modules/
189 | orleans.codegen.cs
190 |
191 | # RIA/Silverlight projects
192 | Generated_Code/
193 |
194 | # Backup & report files from converting an old project file
195 | # to a newer Visual Studio version. Backup files are not needed,
196 | # because we have git ;-)
197 | _UpgradeReport_Files/
198 | Backup*/
199 | UpgradeLog*.XML
200 | UpgradeLog*.htm
201 |
202 | # SQL Server files
203 | *.mdf
204 | *.ldf
205 |
206 | # Business Intelligence projects
207 | *.rdl.data
208 | *.bim.layout
209 | *.bim_*.settings
210 |
211 | # Microsoft Fakes
212 | FakesAssemblies/
213 |
214 | # GhostDoc plugin setting file
215 | *.GhostDoc.xml
216 |
217 | # Node.js Tools for Visual Studio
218 | .ntvs_analysis.dat
219 |
220 | # Visual Studio 6 build log
221 | *.plg
222 |
223 | # Visual Studio 6 workspace options file
224 | *.opt
225 |
226 | # Visual Studio LightSwitch build output
227 | **/*.HTMLClient/GeneratedArtifacts
228 | **/*.DesktopClient/GeneratedArtifacts
229 | **/*.DesktopClient/ModelManifest.xml
230 | **/*.Server/GeneratedArtifacts
231 | **/*.Server/ModelManifest.xml
232 | _Pvt_Extensions
233 |
234 | # LightSwitch generated files
235 | GeneratedArtifacts/
236 | ModelManifest.xml
237 |
238 | # Paket dependency manager
239 | .paket/paket.exe
240 |
241 | # FAKE - F# Make
242 | .fake/
243 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SolutionColor
2 |
3 | Small Visual Studio extension that allows to choose a different title bar color for each solution. Useful if you often work with multiple Visual Studio instances simultaneously.
4 |
5 | 
6 |
7 | More information and download on the [Visual Studio Extension Gallery page](https://visualstudiogallery.msdn.microsoft.com/68d6015f-5818-4c06-ba3f-f471513899e5)
--------------------------------------------------------------------------------
/SolutionColor/Commands/EnableAutoPickColorCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.Design;
3 | using Microsoft.VisualStudio.Shell;
4 |
5 | namespace SolutionColor
6 | {
7 | ///
8 | /// Command enable/disable automatic color picking.
9 | ///
10 | internal sealed class EnableAutoPickColorCommand
11 | {
12 | public const int CommandId = 0x0102;
13 |
14 | private SolutionColorPackage package;
15 | private MenuCommand menuItem;
16 |
17 | ///
18 | /// Initializes a new instance of the class.
19 | /// Adds our command handlers for menu (commands must exist in the command table file)
20 | ///
21 | /// Owner package, not null.
22 | private EnableAutoPickColorCommand(SolutionColorPackage package)
23 | {
24 | ThreadHelper.ThrowIfNotOnUIThread();
25 |
26 | this.package = package;
27 | if (package == null)
28 | {
29 | throw new ArgumentNullException("package");
30 | }
31 |
32 | OleMenuCommandService commandService = ((IServiceProvider)package).GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
33 | if (commandService != null)
34 | {
35 | var menuCommandID = new CommandID(SolutionColorPackage.ToolbarCommandSetGuid, CommandId);
36 | menuItem = new MenuCommand(this.Execute, menuCommandID);
37 | commandService.AddCommand(menuItem);
38 |
39 | menuItem.Checked = package.Settings.IsAutomaticColorPickEnabled();
40 | }
41 | }
42 |
43 | ///
44 | /// Gets the instance of the command.
45 | ///
46 | public static EnableAutoPickColorCommand Instance
47 | {
48 | get;
49 | private set;
50 | }
51 |
52 | ///
53 | /// Initializes the singleton instance of the command.
54 | ///
55 | /// Owner package, not null.
56 | public static void Initialize(SolutionColorPackage package)
57 | {
58 | Instance = new EnableAutoPickColorCommand(package);
59 | }
60 |
61 | private void Execute(object sender, EventArgs e)
62 | {
63 | ThreadHelper.ThrowIfNotOnUIThread();
64 | menuItem.Checked = !menuItem.Checked;
65 | package.Settings.SetAutomaticColorPickEnabled(menuItem.Checked);
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/SolutionColor/Commands/PickColorCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.Design;
3 | using Microsoft.VisualStudio.Shell;
4 | using System.Windows.Forms;
5 |
6 | namespace SolutionColor
7 | {
8 | ///
9 | /// Command to open color picker in order to choose a color for the titlebar.
10 | ///
11 | internal sealed class PickColorCommand
12 | {
13 | public const int CommandId = 0x0100;
14 |
15 | private SolutionColorPackage package;
16 |
17 | ///
18 | /// Initializes a new instance of the class.
19 | /// Adds our command handlers for menu (commands must exist in the command table file)
20 | ///
21 | /// Owner package, not null.
22 | private PickColorCommand(SolutionColorPackage package)
23 | {
24 | this.package = package;
25 | if (package == null)
26 | {
27 | throw new ArgumentNullException("package");
28 | }
29 |
30 | OleMenuCommandService commandService = ((IServiceProvider)package).GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
31 | if (commandService != null)
32 | {
33 | var menuCommandID = new CommandID(SolutionColorPackage.ToolbarCommandSetGuid, CommandId);
34 | var menuItem = new MenuCommand(this.Execute, menuCommandID);
35 | commandService.AddCommand(menuItem);
36 | }
37 | }
38 |
39 | ///
40 | /// Gets the instance of the command.
41 | ///
42 | public static PickColorCommand Instance
43 | {
44 | get;
45 | private set;
46 | }
47 |
48 | ///
49 | /// Initializes the singleton instance of the command.
50 | ///
51 | /// Owner package, not null.
52 | public static void Initialize(SolutionColorPackage package)
53 | {
54 | Instance = new PickColorCommand(package);
55 | }
56 |
57 | private void Execute(object sender, EventArgs e)
58 | {
59 | ThreadHelper.ThrowIfNotOnUIThread();
60 |
61 | var dialog = new ColorDialog();
62 | dialog.AllowFullOpen = true;
63 | dialog.Color = package.GetMainTitleBarColor();
64 | dialog.CustomColors = package.Settings.GetCustomColorList();
65 |
66 | if (dialog.ShowDialog() == DialogResult.OK)
67 | {
68 | package.SetTitleBarColor(dialog.Color);
69 | package.Settings.SaveOrOverwriteSolutionColor(VSUtils.GetCurrentSolutionPath(), dialog.Color);
70 | }
71 |
72 | package.Settings.SaveCustomColorList(dialog.CustomColors);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/SolutionColor/Commands/ResetColorCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.Design;
3 | using Microsoft.VisualStudio.Shell;
4 |
5 | namespace SolutionColor
6 | {
7 | ///
8 | /// Command to reset the title bar color.
9 | ///
10 | internal sealed class ResetColorCommand
11 | {
12 | public const int CommandId = 0x0101;
13 |
14 | private SolutionColorPackage package;
15 |
16 | ///
17 | /// Initializes a new instance of the class.
18 | /// Adds our command handlers for menu (commands must exist in the command table file)
19 | ///
20 | /// Owner package, not null.
21 | private ResetColorCommand(SolutionColorPackage package)
22 | {
23 | this.package = package;
24 | if (package == null)
25 | {
26 | throw new ArgumentNullException("package");
27 | }
28 |
29 | OleMenuCommandService commandService = ((IServiceProvider)package).GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
30 | if (commandService != null)
31 | {
32 | var menuCommandID = new CommandID(SolutionColorPackage.ToolbarCommandSetGuid, CommandId);
33 | var menuItem = new MenuCommand(this.Execute, menuCommandID);
34 | commandService.AddCommand(menuItem);
35 | }
36 | }
37 |
38 | ///
39 | /// Gets the instance of the command.
40 | ///
41 | public static ResetColorCommand Instance
42 | {
43 | get;
44 | private set;
45 | }
46 |
47 | ///
48 | /// Initializes the singleton instance of the command.
49 | ///
50 | /// Owner package, not null.
51 | public static void Initialize(SolutionColorPackage package)
52 | {
53 | Instance = new ResetColorCommand(package);
54 | }
55 |
56 | private void Execute(object sender, EventArgs e)
57 | {
58 | ThreadHelper.ThrowIfNotOnUIThread();
59 | package.ResetTitleBarColor();
60 | package.Settings.RemoveSolutionColorSetting(VSUtils.GetCurrentSolutionPath());
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/SolutionColor/Key.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wumpf/VSSolutionColor/9831b02a7c1cb7b330683ceda06fe8e29079044e/SolutionColor/Key.snk
--------------------------------------------------------------------------------
/SolutionColor/Resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wumpf/VSSolutionColor/9831b02a7c1cb7b330683ceda06fe8e29079044e/SolutionColor/Resources/icon.png
--------------------------------------------------------------------------------
/SolutionColor/Resources/toolbarIcons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wumpf/VSSolutionColor/9831b02a7c1cb7b330683ceda06fe8e29079044e/SolutionColor/Resources/toolbarIcons.png
--------------------------------------------------------------------------------
/SolutionColor/SettingsStore.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Settings;
2 | using Microsoft.VisualStudio.Shell;
3 | using Microsoft.VisualStudio.Shell.Settings;
4 | using System.IO;
5 | using System.Linq;
6 |
7 | namespace SolutionColor
8 | {
9 | public class SolutionColorSettingStore
10 | {
11 | private const string CollectionName = "SolutionColorSettings";
12 | private const string AutomaticColorPickIdentifier = "AutomaticColorPick";
13 |
14 | public SolutionColorSettingStore()
15 | {
16 | }
17 |
18 | public bool IsAutomaticColorPickEnabled()
19 | {
20 | ThreadHelper.ThrowIfNotOnUIThread();
21 |
22 | var settingsStore = GetSettingsStore();
23 | if (settingsStore.PropertyExists(CollectionName, AutomaticColorPickIdentifier))
24 | return settingsStore.GetBoolean(CollectionName, AutomaticColorPickIdentifier);
25 | else
26 | return false; // off by default.
27 | }
28 |
29 | public void SetAutomaticColorPickEnabled(bool enabled)
30 | {
31 | ThreadHelper.ThrowIfNotOnUIThread();
32 |
33 | var settingsStore = GetSettingsStore();
34 | settingsStore.SetBoolean(CollectionName, AutomaticColorPickIdentifier, enabled);
35 | }
36 |
37 | public void SaveOrOverwriteSolutionColor(string solutionPath, System.Drawing.Color color)
38 | {
39 | ThreadHelper.ThrowIfNotOnUIThread();
40 |
41 | if (string.IsNullOrEmpty(solutionPath)) return;
42 | solutionPath = Path.GetFullPath(solutionPath);
43 |
44 | byte[] colorBytes = { color.A, color.R, color.G, color.B };
45 |
46 | var settingsStore = GetSettingsStore();
47 | settingsStore.SetMemoryStream(CollectionName, solutionPath, new MemoryStream(colorBytes));
48 | }
49 |
50 | public void RemoveSolutionColorSetting(string solutionPath)
51 | {
52 | ThreadHelper.ThrowIfNotOnUIThread();
53 |
54 | if (string.IsNullOrEmpty(solutionPath)) return;
55 | solutionPath = Path.GetFullPath(solutionPath);
56 |
57 | var settingsStore = GetSettingsStore();
58 | if (settingsStore.PropertyExists(CollectionName, solutionPath))
59 | settingsStore.DeleteProperty(CollectionName, solutionPath);
60 | }
61 |
62 | ///
63 | /// Retrieves the color setting for a given solution.
64 | ///
65 | /// Path for the solution to check.
66 | /// Color we saved for the solution
67 | /// true if there was a color saved, false if not.
68 | public bool GetSolutionColorSetting(string solutionPath, out System.Drawing.Color color)
69 | {
70 | ThreadHelper.ThrowIfNotOnUIThread();
71 |
72 | color = System.Drawing.Color.Black;
73 |
74 | if (string.IsNullOrEmpty(solutionPath)) return false;
75 | solutionPath = Path.GetFullPath(solutionPath);
76 |
77 | var settingsStore = GetSettingsStore();
78 | if (settingsStore.PropertyExists(CollectionName, solutionPath))
79 | {
80 | MemoryStream colorBytes = settingsStore.GetMemoryStream(CollectionName, solutionPath);
81 | color = System.Drawing.Color.FromArgb(colorBytes.ReadByte(), colorBytes.ReadByte(), colorBytes.ReadByte(), colorBytes.ReadByte());
82 | return true;
83 | }
84 | else
85 | return false;
86 | }
87 |
88 |
89 | private const string CustomColorPaletteName = "CustomColorPalette";
90 |
91 | public int[] GetCustomColorList()
92 | {
93 | ThreadHelper.ThrowIfNotOnUIThread();
94 |
95 | var settingsStore = GetSettingsStore();
96 | if (settingsStore.PropertyExists(CollectionName, CustomColorPaletteName))
97 | {
98 | string customColorPaletteString = settingsStore.GetString(CollectionName, CustomColorPaletteName);
99 | return customColorPaletteString.Split(new char[]{ ' ' }, System.StringSplitOptions.RemoveEmptyEntries).Select(x =>
100 | {
101 | // Can't be cautious enough when reading user string.
102 | int color = -1;
103 | if (!int.TryParse(x, out color))
104 | return -1;
105 | else
106 | return color;
107 | }).ToArray();
108 | }
109 | else
110 | {
111 | return new int[0];
112 | }
113 | }
114 |
115 | public void SaveCustomColorList(int[] colorList)
116 | {
117 | ThreadHelper.ThrowIfNotOnUIThread();
118 |
119 | // Save as string since it is easy and save.
120 | // Alternative would be memorystream like we do with the color per solution path.
121 | // Then however, we'd need to save the count separately [...]
122 | var settingsStore = GetSettingsStore();
123 | settingsStore.SetString(CollectionName, CustomColorPaletteName, colorList.Aggregate(string.Empty, (s, i) => s + " " + i.ToString()));
124 | }
125 |
126 | private WritableSettingsStore GetSettingsStore()
127 | {
128 | ThreadHelper.ThrowIfNotOnUIThread();
129 | var settingsManager = new ShellSettingsManager(ServiceProvider.GlobalProvider);
130 | WritableSettingsStore settingsStore = settingsManager.GetWritableSettingsStore(SettingsScope.UserSettings);
131 |
132 | // Ensure our settings collection exists.
133 | if (!settingsStore.CollectionExists(CollectionName))
134 | settingsStore.CreateCollection(CollectionName);
135 |
136 | return settingsStore;
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/SolutionColor/SolutionColor.args.json:
--------------------------------------------------------------------------------
1 | {
2 | "DataCollection": [
3 | {
4 | "Id": "535670a5-880d-40db-aa99-208a55d4ecd5",
5 | "Command": "/rootsuffix Exp"
6 | }
7 | ]
8 | }
--------------------------------------------------------------------------------
/SolutionColor/SolutionColor.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 16.0
6 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
7 |
8 |
9 | true
10 | Program
11 | $(DevEnvDir)\devenv.exe
12 | /rootsuffix Exp
13 |
14 |
15 |
16 |
17 | 14.0
18 |
19 |
20 |
21 | true
22 |
23 |
24 | Key.snk
25 |
26 |
27 |
28 | Debug
29 | AnyCPU
30 | 2.0
31 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
32 | {43D6AD8C-3B27-4B6F-AF3F-4C9B9E70DDAA}
33 | Library
34 | Properties
35 | SolutionColor
36 | SolutionColor
37 | v4.6
38 | true
39 | true
40 | true
41 | true
42 | true
43 | false
44 |
45 |
46 | true
47 | full
48 | false
49 | bin\Debug\
50 | DEBUG;TRACE
51 | prompt
52 | 4
53 | x86
54 |
55 |
56 | pdbonly
57 | true
58 | bin\Release\
59 | TRACE
60 | prompt
61 | 4
62 | x86
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Designer
78 |
79 |
80 |
81 |
82 | true
83 |
84 |
85 | true
86 | Always
87 |
88 |
89 |
90 | true
91 |
92 |
93 | Menus.ctmenu
94 |
95 |
96 |
97 |
98 | False
99 |
100 |
101 | True
102 |
103 |
104 |
105 |
106 |
107 |
108 | True
109 |
110 |
111 | True
112 |
113 |
114 |
115 |
116 |
117 |
118 | ..\dependencies\RandomColorGenerator.Forms.dll
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | 14.3.25407
130 |
131 |
132 |
133 |
134 |
135 | true
136 | VSPackage
137 | Designer
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
156 |
--------------------------------------------------------------------------------
/SolutionColor/SolutionColorPackage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Automation;
4 | using System.Diagnostics.CodeAnalysis;
5 | using System.Runtime.InteropServices;
6 | using Microsoft.VisualStudio.Shell;
7 | using System.Collections.Generic;
8 | using System.Windows.Interop;
9 | using System.Linq;
10 | using System.Threading;
11 | using System.Threading.Tasks;
12 | using Task = System.Threading.Tasks.Task;
13 |
14 | namespace SolutionColor
15 | {
16 | ///
17 | /// This is the class that implements the package exposed by this assembly.
18 | ///
19 | ///
20 | ///
21 | /// The minimum requirement for a class to be considered a valid package for Visual Studio
22 | /// is to implement the IVsPackage interface and register itself with the shell.
23 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
24 | /// to do it: it derives from the Package class that provides the implementation of the
25 | /// IVsPackage interface and uses the registration attributes defined in the framework to
26 | /// register itself and its components with the shell. These attributes tell the pkgdef creation
27 | /// utility what data to put into .pkgdef file.
28 | ///
29 | ///
30 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file.
31 | ///
32 | ///
33 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
34 | [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About
35 | [ProvideMenuResource("Menus.ctmenu", 1)]
36 | [Guid(SolutionColorPackage.PackageGuidString)]
37 | [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")]
38 | [ProvideAutoLoad(Microsoft.VisualStudio.VSConstants.UICONTEXT.NoSolution_string, PackageAutoLoadFlags.BackgroundLoad)]
39 | public sealed class SolutionColorPackage : AsyncPackage
40 | {
41 | public const string PackageGuidString = "8fa74b3d-8744-465c-b06e-a719e1d63ddf";
42 |
43 | public static readonly Guid ToolbarCommandSetGuid = new Guid("00d80876-3407-4666-bf62-7262028ea83b");
44 |
45 | public SolutionColorSettingStore Settings { get; private set; } = new SolutionColorSettingStore();
46 | private EnvDTE.WindowEvents windowEvents;
47 |
48 |
49 | ///
50 | /// Store process id, since we use this on a very regular basis (whenever any windows opens anywhere...) and we don't want to do GetCurrentProcess every time.
51 | ///
52 | private readonly int currentProcessId = System.Diagnostics.Process.GetCurrentProcess().Id;
53 |
54 | private Dictionary windowTitleBarController = new Dictionary();
55 |
56 | ///
57 | /// Listener to opened solutions. Sets title bar color settings in effect if any.
58 | ///
59 | private class SolutionOpenListener : SolutionListener
60 | {
61 | private SolutionColorPackage package;
62 |
63 | public SolutionOpenListener(SolutionColorPackage package) : base(package)
64 | {
65 | this.package = package;
66 | }
67 |
68 | ///
69 | /// IVsSolutionEvents3.OnAfterOpenSolution is called BEFORE a solution is fully loaded.
70 | /// This is different from EnvDTE.Events.SolutionEvents.Opened which is loaded after a resolution was loaded due to historical reasons:
71 | /// In earlier Visual Studio versions there was no asynchronous loading, so a solution was either fully loaded or not at all.
72 | ///
73 | public override int OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
74 | {
75 | ThreadHelper.ThrowIfNotOnUIThread();
76 |
77 | // Check if we already saved something for this solution.
78 | string solutionPath = VSUtils.GetCurrentSolutionPath();
79 | System.Drawing.Color color;
80 | if (package.Settings.GetSolutionColorSetting(solutionPath, out color))
81 | package.SetTitleBarColor(color);
82 | else if (package.Settings.IsAutomaticColorPickEnabled())
83 | {
84 | color = RandomColorGenerator.RandomColor.GetColor(RandomColorGenerator.ColorScheme.Random, VSUtils.IsUsingDarkTheme() ? RandomColorGenerator.Luminosity.Dark : RandomColorGenerator.Luminosity.Light);
85 | package.SetTitleBarColor(color);
86 | package.Settings.SaveOrOverwriteSolutionColor(solutionPath, color);
87 | }
88 |
89 | return 0;
90 | }
91 |
92 | public override int OnAfterCloseSolution(object pUnkReserved)
93 | {
94 | package.ResetTitleBarColor();
95 | return 0;
96 | }
97 | }
98 |
99 | private SolutionListener listener;
100 |
101 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress)
102 | {
103 | await base.InitializeAsync(cancellationToken, progress);
104 |
105 | await JoinableTaskFactory.SwitchToMainThreadAsync(); // Command constructors need to be on main thread.
106 | PickColorCommand.Initialize(this);
107 | ResetColorCommand.Initialize(this);
108 | EnableAutoPickColorCommand.Initialize(this);
109 |
110 | listener = new SolutionOpenListener(this);
111 |
112 | await UpdateTitleBarControllerListAsync();
113 |
114 | // Use window event to find removed/added windows.
115 | // TODO: Doesn't always reliably catch undocking events.
116 | var dte = VSUtils.GetDTE();
117 | windowEvents = dte.Events.WindowEvents;
118 | windowEvents.WindowCreated += async (Window) => await UpdateTitleBarControllerListAsync();
119 | windowEvents.WindowClosing += async (Window) => await UpdateTitleBarControllerListAsync();
120 | windowEvents.WindowActivated += async (a, b) => await UpdateTitleBarControllerListAsync();
121 | }
122 |
123 | protected override void Dispose(bool disposing)
124 | {
125 | ThreadHelper.ThrowIfNotOnUIThread();
126 | if (disposing)
127 | listener.Dispose();
128 | base.Dispose(disposing);
129 | }
130 |
131 | private async Task UpdateTitleBarControllerListAsync()
132 | {
133 | await JoinableTaskFactory.SwitchToMainThreadAsync();
134 |
135 | Window[] windows = new Window[Application.Current.Windows.Count];
136 | Application.Current.Windows.CopyTo(windows, 0);
137 |
138 | // Destroy old window controller.
139 | windowTitleBarController = windowTitleBarController.Where(x => windows.Contains(x.Key))
140 | .ToDictionary(x=>x.Key, x=>x.Value);
141 |
142 | // Go through new windows.
143 | foreach (Window window in windows.Where(w => !windowTitleBarController.ContainsKey(w)))
144 | {
145 | var newController = TitleBarColorController.CreateFromWindow(window);
146 | if (newController != null)
147 | {
148 | windowTitleBarController.Add(window, newController);
149 |
150 | // Check if we already saved something for this solution.
151 | // Do this in here since we call UpdateTitleBarControllerList fairly regularly and in the most cases won't have any new controllers.
152 | if (Settings.GetSolutionColorSetting(VSUtils.GetCurrentSolutionPath(), out var color))
153 | newController.SetTitleBarColor(color);
154 | }
155 | }
156 | }
157 |
158 | #region Color Manipulation
159 |
160 | public void SetTitleBarColor(System.Drawing.Color color)
161 | {
162 | foreach (var bar in windowTitleBarController)
163 | bar.Value.SetTitleBarColor(color);
164 | }
165 |
166 | public void ResetTitleBarColor()
167 | {
168 | foreach(var bar in windowTitleBarController)
169 | bar.Value.ResetTitleBarColor();
170 | }
171 |
172 | public System.Drawing.Color GetMainTitleBarColor()
173 | {
174 | TitleBarColorController titleBar;
175 | if (windowTitleBarController.TryGetValue(Application.Current.MainWindow, out titleBar))
176 | return titleBar.TryGetTitleBarColor();
177 | else
178 | return System.Drawing.Color.Black;
179 | }
180 |
181 | #endregion
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/SolutionColor/SolutionColorPackage.vsct:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
12 |
13 |
14 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
38 |
39 |
46 |
47 |
54 |
55 |
56 |
57 |
58 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/SolutionColor/SolutionListener.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio;
2 | using Microsoft.VisualStudio.Shell;
3 | using Microsoft.VisualStudio.Shell.Interop;
4 | using System;
5 |
6 | namespace SolutionColor
7 | {
8 | ///
9 | /// Abstract listener for solution events.
10 | /// This is much more powerful than EnvDTE.Events.SolutionEvents!
11 | ///
12 | /// More events, intentionally not implemented: IVsSolutionEvents4, IVsSolutionEvents, IVsSolutionEvents2
13 | ///
14 | abstract public class SolutionListener : IVsSolutionEvents3, IDisposable
15 | {
16 | private IVsSolution solutionService;
17 | private uint eventsCookie = (uint)Constants.VSCOOKIE_NIL;
18 | private bool isDisposed;
19 |
20 | protected SolutionListener(IServiceProvider serviceProvider)
21 | {
22 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();
23 |
24 | solutionService = serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution;
25 | if (solutionService == null)
26 | {
27 | throw new InvalidOperationException();
28 | }
29 |
30 | ErrorHandler.ThrowOnFailure(solutionService.AdviseSolutionEvents(this, out eventsCookie));
31 | }
32 |
33 | public void Dispose()
34 | {
35 | ThreadHelper.ThrowIfNotOnUIThread();
36 | Dispose(true);
37 | GC.SuppressFinalize(this);
38 | }
39 |
40 | protected virtual void Dispose(bool disposing)
41 | {
42 | ThreadHelper.ThrowIfNotOnUIThread();
43 |
44 | if (!isDisposed)
45 | {
46 | if (disposing && solutionService != null && eventsCookie != (uint)Constants.VSCOOKIE_NIL)
47 | {
48 | ErrorHandler.ThrowOnFailure(solutionService.UnadviseSolutionEvents(eventsCookie));
49 | eventsCookie = (uint)Constants.VSCOOKIE_NIL;
50 | }
51 | isDisposed = true;
52 | }
53 | }
54 |
55 | #region Event Impls
56 |
57 | public virtual int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded)
58 | {
59 | return 0;
60 | }
61 |
62 | public virtual int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel)
63 | {
64 | return 0;
65 | }
66 |
67 | public virtual int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved)
68 | {
69 | return 0;
70 | }
71 |
72 | public virtual int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy)
73 | {
74 | return 0;
75 | }
76 |
77 | public virtual int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel)
78 | {
79 | return 0;
80 | }
81 |
82 | public virtual int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy)
83 | {
84 | return 0;
85 | }
86 |
87 | public virtual int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel)
88 | {
89 | return 0;
90 | }
91 |
92 | public virtual int OnBeforeCloseSolution(object pUnkReserved)
93 | {
94 | return 0;
95 | }
96 |
97 | public virtual int OnAfterCloseSolution(object pUnkReserved)
98 | {
99 | return 0;
100 | }
101 |
102 | public virtual int OnAfterMergeSolution(object pUnkReserved)
103 | {
104 | return 0;
105 | }
106 |
107 | public virtual int OnBeforeOpeningChildren(IVsHierarchy pHierarchy)
108 | {
109 | return 0;
110 | }
111 |
112 | public virtual int OnAfterOpeningChildren(IVsHierarchy pHierarchy)
113 | {
114 | return 0;
115 | }
116 |
117 | public virtual int OnBeforeClosingChildren(IVsHierarchy pHierarchy)
118 | {
119 | return 0;
120 | }
121 |
122 | public virtual int OnAfterClosingChildren(IVsHierarchy pHierarchy)
123 | {
124 | return 0;
125 | }
126 |
127 | public virtual int OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
128 | {
129 | return 0;
130 | }
131 |
132 | #endregion
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/SolutionColor/TitleBarColorController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Media;
4 | using System.Windows.Controls;
5 |
6 | namespace SolutionColor
7 | {
8 | ///
9 | /// All the important magic to manipulate the main window's title bar happens here.
10 | ///
11 | public class TitleBarColorController
12 | {
13 | private DependencyObject titleBarContainer = null;
14 | private DependencyObject mainMenuControl = null;
15 | private DependencyObject mainMenuItemsWrapperControl = null;
16 | private TextBlock titleBarTextBox = null;
17 |
18 | private object defaultBackgroundValue = null;
19 | private Brush defaultTextForeground = null;
20 | private object defaultMenuBackgroundValue = null;
21 | private Style defaultMenuItemStyle = null;
22 | private const string ColorPropertyName = "Background";
23 | private const string ForegroundPropertyName = "Foreground";
24 |
25 | private TitleBarColorController()
26 | {
27 | }
28 |
29 | ///
30 | /// Uses knowledge of the VS window structure to retrieve pointers to the titlebar and its text element.
31 | ///
32 | static public TitleBarColorController CreateFromWindow(Window window)
33 | {
34 | TitleBarColorController newController = new TitleBarColorController();
35 | try
36 | {
37 | // Apply knowledge of basic Visual Studio 2015/2017/2019 window structure.
38 |
39 | if (window == Application.Current.MainWindow)
40 | {
41 | var windowContentPresenter = VisualTreeHelper.GetChild(window, 0);
42 | var rootGrid = VisualTreeHelper.GetChild(windowContentPresenter, 0);
43 |
44 | newController.titleBarContainer = VisualTreeHelper.GetChild(rootGrid, 0);
45 |
46 | // Note that this part doesn't work for the VS2019 main windows as there is simply no title text like this.
47 | // However docked-out code windows are just like in previous versions.
48 | try
49 | {
50 | var dockPanel = VisualTreeHelper.GetChild(newController.titleBarContainer, 0);
51 | newController.titleBarTextBox = VisualTreeHelper.GetChild(dockPanel, 3) as TextBlock;
52 | }
53 | catch
54 | {
55 | // We can do without the text box!
56 | }
57 |
58 | // In VS2019+ the main menu has been integrated with the title bar.
59 | // We can set the opacity to 0 and color the text as we did/do with the title text.
60 | newController.mainMenuControl = GetDecendantFirstInLine(newController.titleBarContainer, 6);
61 | if (newController.mainMenuControl != null)
62 | {
63 | newController.mainMenuItemsWrapperControl = GetDecendantFirstInLine(newController.mainMenuControl, 3);
64 | if (newController.mainMenuItemsWrapperControl is MenuItem) // Nestedness of the layout changed a bit over different versions;
65 | newController.mainMenuItemsWrapperControl = GetDecendantFirstInLine(newController.mainMenuControl, 2);
66 |
67 | System.Reflection.PropertyInfo propertyInfo = newController.mainMenuControl.GetType().GetProperty(ColorPropertyName);
68 | newController.defaultMenuBackgroundValue = propertyInfo.GetValue(newController.mainMenuControl);
69 | if (newController.mainMenuItemsWrapperControl != null && VisualTreeHelper.GetChildrenCount(newController.mainMenuItemsWrapperControl) > 0)
70 | {
71 | var child = VisualTreeHelper.GetChild(newController.mainMenuItemsWrapperControl, 0);
72 | newController.defaultMenuItemStyle = child.GetType().GetProperty("Style")?.GetValue(child) as Style;
73 | }
74 | }
75 | }
76 | else
77 | {
78 | var windowContentPresenter = VisualTreeHelper.GetChild(window, 0);
79 | var rootGrid = VisualTreeHelper.GetChild(windowContentPresenter, 0);
80 | var rootDockPanel = VisualTreeHelper.GetChild(rootGrid, 0);
81 | var titleBarContainer = VisualTreeHelper.GetChild(rootDockPanel, 0);
82 | var titleBar = VisualTreeHelper.GetChild(titleBarContainer, 0);
83 | var border = VisualTreeHelper.GetChild(titleBar, 0);
84 | var contentPresenter = VisualTreeHelper.GetChild(border, 0);
85 | var grid = VisualTreeHelper.GetChild(contentPresenter, 0);
86 |
87 | newController.titleBarContainer = grid;
88 |
89 | newController.titleBarTextBox = VisualTreeHelper.GetChild(grid, 1) as TextBlock;
90 | }
91 |
92 | if (newController.titleBarContainer != null)
93 | {
94 | System.Reflection.PropertyInfo propertyInfo = newController.titleBarContainer.GetType().GetProperty(ColorPropertyName);
95 | newController.defaultBackgroundValue = propertyInfo.GetValue(newController.titleBarContainer);
96 | }
97 |
98 | if (newController.titleBarTextBox != null)
99 | newController.defaultTextForeground = newController.titleBarTextBox.Foreground;
100 | }
101 | catch
102 | {
103 | return null;
104 | }
105 |
106 | if (newController.titleBarContainer == null)
107 | return null;
108 |
109 | return newController;
110 | }
111 |
112 | ///
113 | /// Tries to set a given color to the titlebar. Will color the text either black or white dependingon the color's brightness.
114 | /// Opens message box if something goes wrong.
115 | ///
116 | public void SetTitleBarColor(System.Drawing.Color color)
117 | {
118 | try
119 | {
120 | float luminance = 0.299f * color.R + 0.587f * color.G + 0.114f * color.B;
121 | var textColor = (luminance > 128.0f) ? Color.FromRgb(0, 0, 0) : Color.FromRgb(255, 255, 255);
122 | var textBrush = new SolidColorBrush(textColor);
123 |
124 | if (titleBarContainer != null)
125 | {
126 | System.Reflection.PropertyInfo propertyInfo = titleBarContainer.GetType().GetProperty(ColorPropertyName);
127 | propertyInfo.SetValue(titleBarContainer, new SolidColorBrush(Color.FromArgb(color.A, color.R, color.G, color.B)), null);
128 | }
129 |
130 | if (titleBarTextBox != null)
131 | {
132 | titleBarTextBox.Foreground = textBrush;
133 | }
134 |
135 | if (mainMenuControl != null)
136 | {
137 | System.Reflection.PropertyInfo propertyInfo = mainMenuControl.GetType().GetProperty(ColorPropertyName);
138 | propertyInfo.SetValue(mainMenuControl, new SolidColorBrush(Colors.Transparent));
139 | }
140 |
141 | if (mainMenuItemsWrapperControl != null)
142 | {
143 | var newMenuItemStyle = CreateNewMenuItemStyle(mainMenuItemsWrapperControl, textBrush);
144 | if (newMenuItemStyle != null)
145 | {
146 | ApplyStyleOnAllChildren(mainMenuItemsWrapperControl, newMenuItemStyle);
147 | }
148 | }
149 | }
150 | catch (Exception e)
151 | {
152 | MessageBox.Show("Failed to set the color of the title bar:\n" + e.ToString(), "Failed to set Titlebar Color");
153 | }
154 | }
155 |
156 | ///
157 | /// Resets titlebar (and text) color to the default that was saved in the first successful UpdateWidgetPointer() call.
158 | ///
159 | public void ResetTitleBarColor()
160 | {
161 | try
162 | {
163 | if (titleBarContainer != null)
164 | {
165 | System.Reflection.PropertyInfo propertyInfo = titleBarContainer.GetType().GetProperty(ColorPropertyName);
166 | propertyInfo.SetValue(titleBarContainer, defaultBackgroundValue, null);
167 | }
168 |
169 | if (titleBarTextBox != null)
170 | {
171 | titleBarTextBox.Foreground = defaultTextForeground;
172 | }
173 |
174 | if (mainMenuControl != null)
175 | {
176 | System.Reflection.PropertyInfo propertyInfo = mainMenuControl.GetType().GetProperty(ColorPropertyName);
177 | propertyInfo.SetValue(mainMenuControl, defaultMenuBackgroundValue);
178 | }
179 |
180 | if (mainMenuItemsWrapperControl != null)
181 | {
182 | ApplyStyleOnAllChildren(mainMenuItemsWrapperControl, defaultMenuItemStyle);
183 | }
184 | }
185 | catch (Exception e)
186 | {
187 | MessageBox.Show("Failed to reset the color of the title bar:\n" + e.ToString(), "Failed to reset Titlebar Color");
188 | }
189 | }
190 |
191 | ///
192 | /// Tries to retrieve the color of the title bar. Falls back to black if it fails.
193 | ///
194 | public System.Drawing.Color TryGetTitleBarColor()
195 | {
196 | try
197 | {
198 | System.Reflection.PropertyInfo propertyInfo = titleBarContainer.GetType().GetProperty(ColorPropertyName);
199 | var colorBrush = propertyInfo.GetValue(titleBarContainer) as SolidColorBrush;
200 | if (colorBrush != null)
201 | {
202 | return System.Drawing.Color.FromArgb(colorBrush.Color.A, colorBrush.Color.R, colorBrush.Color.G, colorBrush.Color.B);
203 | }
204 | else
205 | return System.Drawing.Color.Black;
206 | }
207 | catch (Exception e)
208 | {
209 | MessageBox.Show("Failed to get the color of the title bar:\n" + e.ToString(), "Failed to get Titlebar Color");
210 | }
211 |
212 | return System.Drawing.Color.Black;
213 | }
214 |
215 | ///
216 | /// Creates a new Style with the supplied based on the Style of the first Child object of
217 | ///
218 | ///
219 | ///
220 | ///
221 | private Style CreateNewMenuItemStyle(DependencyObject menuItemWrapper, SolidColorBrush newTextBrush)
222 | {
223 | if (defaultMenuItemStyle == null)
224 | return null;
225 |
226 | var newStyle = new Style(defaultMenuItemStyle.TargetType, defaultMenuItemStyle);
227 | foreach (var setter in defaultMenuItemStyle.Setters)
228 | {
229 | if ((setter is Setter) && ((setter as Setter).Property.ToString() == ForegroundPropertyName))
230 | {
231 | newStyle.Setters.Remove(setter);
232 | newStyle.Setters.Add(new Setter((setter as Setter).Property, newTextBrush));
233 | }
234 | }
235 | return newStyle;
236 | }
237 |
238 | ///
239 | /// Applies the to all children of
240 | ///
241 | /// The object that has all the relevant MenuItems as children
242 | /// The new style to apply to all children
243 | private void ApplyStyleOnAllChildren(DependencyObject menuItemWrapper, Style styleToApply)
244 | {
245 | if (menuItemWrapper == null)
246 | throw new ArgumentNullException(nameof(menuItemWrapper));
247 |
248 | for (int i = 0; i < VisualTreeHelper.GetChildrenCount(menuItemWrapper); i++)
249 | {
250 | var menuItem = VisualTreeHelper.GetChild(menuItemWrapper, i) as MenuItem;
251 | if (menuItem != null)
252 | {
253 | menuItem.Style = styleToApply;
254 | }
255 | }
256 | }
257 |
258 | ///
259 | /// Call VisualTreeHelper.GetChild(ref, 0) multiple times, to get a first-in-line decendant x levels deep.
260 | ///
261 | /// The parent visual to get the decendant of
262 | /// The amount of levels deep (if 1 is passed, this method behaves as GetChild).
263 | static private DependencyObject GetDecendantFirstInLine(DependencyObject reference, int levelsDeep)
264 | {
265 | while ((reference != null) && (levelsDeep > 0))
266 | {
267 | if (VisualTreeHelper.GetChildrenCount(reference) < 1)
268 | return null;
269 | reference = VisualTreeHelper.GetChild(reference, 0);
270 | levelsDeep--;
271 | }
272 | return reference;
273 | }
274 |
275 | }
276 | }
277 |
--------------------------------------------------------------------------------
/SolutionColor/VSPackage.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | Solution Color Extension
122 |
123 |
124 | Pick a different titlebar color depending on the current sln file you're working on.
125 |
126 |
127 |
--------------------------------------------------------------------------------
/SolutionColor/VSUtils.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Shell;
2 |
3 | namespace SolutionColor
4 | {
5 | ///
6 | /// A few VS extension utils. Basically shortcuts to commonly functionality.
7 | ///
8 | internal static class VSUtils
9 | {
10 | public static EnvDTE80.DTE2 GetDTE()
11 | {
12 | var dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE80.DTE2;
13 | if (dte == null)
14 | {
15 | throw new System.Exception("Failed to retrieve DTE2!");
16 | }
17 | return dte;
18 | }
19 |
20 | public static string GetCurrentSolutionPath()
21 | {
22 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();
23 | return GetDTE().Solution.FileName;
24 | }
25 |
26 | public static bool IsUsingDarkTheme()
27 | {
28 | // Probe the current theme.
29 | // Inspired by https://github.com/Irdis/VSTalk/blob/2d49471422a42513ac84179c27472ca6d9112047/trunk/VSTalk/VSTalk.Extension/Integration/ThemeManager.cs#L131
30 | uint colorSample = GetDTE().GetThemeColor(EnvDTE80.vsThemeColors.vsThemeColorToolboxBackground);
31 | int colorSum = (byte)(colorSample >> 16) + (byte)(colorSample >> 8) + (byte)(colorSample >> 0); // rgb
32 | return colorSum < 128 * 3;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/SolutionColor/license.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2019 Andreas Reich
4 |
5 | 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:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | 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.
10 |
--------------------------------------------------------------------------------
/SolutionColor/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wumpf/VSSolutionColor/9831b02a7c1cb7b330683ceda06fe8e29079044e/SolutionColor/screenshot.png
--------------------------------------------------------------------------------
/SolutionColor/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SolutionColor
6 | Pick a different titlebar color depending on the current sln file you're working on.
7 | license.txt
8 | Resources\icon.png
9 | screenshot.png
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/dependencies/RandomColorGenerator.Forms.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wumpf/VSSolutionColor/9831b02a7c1cb7b330683ceda06fe8e29079044e/dependencies/RandomColorGenerator.Forms.dll
--------------------------------------------------------------------------------
/dependencies/Readme.md:
--------------------------------------------------------------------------------
1 | # RandomColorGeneratorSharped
2 | A small library for pleasing random colors by Nathan P Jones
3 | [Github Link](https://github.com/nathanpjones/randomColorSharped/)
4 |
5 | To use it inside a Visual Studio extension I needed to sign the assembly with my key, which is why I couldn't rely on the NuGet package.
6 |
7 |
8 |
9 |
10 |
11 | The MIT License (MIT)
12 |
13 | Copyright (c) 2014 Nathan P Jones
14 |
15 | Permission is hereby granted, free of charge, to any person obtaining a copy
16 | of this software and associated documentation files (the "Software"), to deal
17 | in the Software without restriction, including without limitation the rights
18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19 | copies of the Software, and to permit persons to whom the Software is
20 | furnished to do so, subject to the following conditions:
21 |
22 | The above copyright notice and this permission notice shall be included in all
23 | copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 | SOFTWARE.
--------------------------------------------------------------------------------
/solutioncolor.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.25920.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolutionColor", "solutioncolor\SolutionColor.csproj", "{43D6AD8C-3B27-4B6F-AF3F-4C9B9E70DDAA}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {43D6AD8C-3B27-4B6F-AF3F-4C9B9E70DDAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {43D6AD8C-3B27-4B6F-AF3F-4C9B9E70DDAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {43D6AD8C-3B27-4B6F-AF3F-4C9B9E70DDAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {43D6AD8C-3B27-4B6F-AF3F-4C9B9E70DDAA}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/solutioncolor/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SolutionColor
6 | Pick a different titlebar color depending on the current sln file you're working on.
7 | license.txt
8 | Resources\icon.png
9 | screenshot.png
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/toolbarIcons.pdn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wumpf/VSSolutionColor/9831b02a7c1cb7b330683ceda06fe8e29079044e/toolbarIcons.pdn
--------------------------------------------------------------------------------