From 1dabd761e1c37224fb2ba0e6e81bb17cce736a03 Mon Sep 17 00:00:00 2001 From: Enrico Giordani Date: Tue, 8 Jun 2021 08:53:11 -0700 Subject: [PATCH] [PT Run][New Plugin] Unit Converter (#9800) (#11406) --- .github/actions/spell-check/expect.txt | 5 + .../ci/templates/build-powertoys-steps.yml | 1 + .pipelines/pipeline.user.windows.yml | 1 + PowerToys.sln | 18 ++ .../plugins/community.unitconverter.md | 31 +++ .../plugins/community.unitconverter.png | Bin 0 -> 6685 bytes installer/PowerToysSetup/Product.wxs | 22 ++- ...s.Run.Plugin.UnitConverter.UnitTest.csproj | 37 ++++ .../InputInterpreterTests.cs | 67 +++++++ .../UnitHandlerTests.cs | 42 ++++ ....PowerToys.Run.Plugin.UnitConverter.csproj | 99 ++++++++++ .../ConvertModel.cs | 26 +++ .../Images/unitconverter.dark.png | Bin 0 -> 2328 bytes .../Images/unitconverter.light.png | Bin 0 -> 2300 bytes .../InputInterpreter.cs | 181 ++++++++++++++++++ .../LocProject.json | 14 ++ .../Main.cs | 180 +++++++++++++++++ .../Properties/Resources.Designer.cs | 108 +++++++++++ .../Properties/Resources.resx | 135 +++++++++++++ .../UnitConversionResult.cs | 20 ++ .../UnitHandler.cs | 90 +++++++++ .../plugin.json | 13 ++ .../Main.cs | 1 - .../PowerLauncher/PowerLauncher.csproj | 1 + 24 files changed, 1089 insertions(+), 3 deletions(-) create mode 100644 doc/devdocs/modules/launcher/plugins/community.unitconverter.md create mode 100644 doc/images/launcher/plugins/community.unitconverter.png create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.csproj create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/InputInterpreterTests.cs create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/UnitHandlerTests.cs create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Community.PowerToys.Run.Plugin.UnitConverter.csproj create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/ConvertModel.cs create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Images/unitconverter.dark.png create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Images/unitconverter.light.png create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/InputInterpreter.cs create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/LocProject.json create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Main.cs create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.Designer.cs create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.resx create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/UnitConversionResult.cs create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/UnitHandler.cs create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/plugin.json diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index be5d10eae6..ece73691eb 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -49,6 +49,7 @@ AModifier AMPROPERTY AMPROPSETID anges +angularsen ansicolor ANull AOC @@ -1496,6 +1497,7 @@ ppv pragma prc precomp +Prefixer Preinstalled preload PREMULTIPLIED @@ -1744,6 +1746,7 @@ shldisp shlobj shlwapi shobjidl +shortsplit SHORTCUTATLEAST shortcutcontrol Shortcutguide @@ -2027,6 +2030,8 @@ uninstantiated Uniq uniquifier Uniquifies +unitconvert +unitconverter unittests unk unknwn diff --git a/.pipelines/ci/templates/build-powertoys-steps.yml b/.pipelines/ci/templates/build-powertoys-steps.yml index 84847b5923..2b915c4bda 100644 --- a/.pipelines/ci/templates/build-powertoys-steps.yml +++ b/.pipelines/ci/templates/build-powertoys-steps.yml @@ -147,6 +147,7 @@ steps: configuration: '$(BuildConfiguration)' testSelector: 'testAssemblies' testAssemblyVer2: | + **\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.dll **\Microsoft.Plugin.Folder.UnitTests.dll **\Microsoft.Plugin.Program.UnitTests.dll **\Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest.dll diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index d74791fa16..cf82c18ed8 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -112,6 +112,7 @@ build: - 'modules\launcher\ManagedTelemetry.dll' - 'modules\launcher\Microsoft.PowerToys.Common.UI.dll' - 'modules\launcher\Microsoft.Launcher.dll' + - 'modules\launcher\Plugins\Community.PowerToys.Run.Plugin.UnitConverter\Community.PowerToys.Run.Plugin.UnitConverter.dll' - 'modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Calculator\Microsoft.PowerToys.Run.Plugin.Calculator.dll' - 'modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Calculator\Wox.Infrastructure.dll' - 'modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Calculator\Wox.Plugin.dll' diff --git a/PowerToys.sln b/PowerToys.sln index 0aa7e6074b..10a4e9b818 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -145,14 +145,18 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Launcher", "src\m EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\modules\launcher\PowerLauncher\PowerLauncher.csproj", "{F97E5003-F263-4D4A-A964-0F1F3C82DEF2}" ProjectSection(ProjectDependencies) = postProject + {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B} = {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B} {03276A39-D4E9-417C-8FFD-200B0EE5E871} = {03276A39-D4E9-417C-8FFD-200B0EE5E871} + {4D971245-7A70-41D5-BAA0-DDB5684CAF51} = {4D971245-7A70-41D5-BAA0-DDB5684CAF51} {FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4} + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} = {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E} {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} {0351ADA4-0C32-4652-9BA0-41F7B602372B} = {0351ADA4-0C32-4652-9BA0-41F7B602372B} {787B8AA6-CA93-4C84-96FE-DF31110AD1C4} = {787B8AA6-CA93-4C84-96FE-DF31110AD1C4} {F8B870EB-D5F5-45BA-9CF7-A5C459818820} = {F8B870EB-D5F5-45BA-9CF7-A5C459818820} {74F1B9ED-F59C-4FE7-B473-7B453E30837E} = {74F1B9ED-F59C-4FE7-B473-7B453E30837E} + {4BABF3FE-3451-42FD-873F-3C332E18DCEF} = {4BABF3FE-3451-42FD-873F-3C332E18DCEF} EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{E775CC2C-24CB-48D6-9C3A-BE4CCE0DB17A}" @@ -325,8 +329,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditorTest", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "espresso", "espresso", "{127F38E0-40AA-4594-B955-5616BF206882}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.UnitConverter", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.UnitConverter\Community.PowerToys.Run.Plugin.UnitConverter.csproj", "{BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EspressoModuleInterface", "src\modules\espresso\EspressoModuleInterface\EspressoModuleInterface.vcxproj", "{5E7360A8-D048-4ED3-8F09-0BFD64C5529A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.UnitConverter.UnitTest", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.csproj", "{3E424AD2-19E5-4AE6-B833-F53963EB5FC1}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Espresso", "src\modules\espresso\Espresso\Espresso.csproj", "{D940E07F-532C-4FF3-883F-790DA014F19A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shortcutguide", "shortcutguide", "{106CBECA-0701-4FC3-838C-9DF816A19AE2}" @@ -675,6 +683,14 @@ Global {D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|x64.Build.0 = Debug|x64 {D940E07F-532C-4FF3-883F-790DA014F19A}.Release|x64.ActiveCfg = Release|x64 {D940E07F-532C-4FF3-883F-790DA014F19A}.Release|x64.Build.0 = Release|x64 + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|x64.ActiveCfg = Debug|x64 + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|x64.Build.0 = Debug|x64 + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|x64.ActiveCfg = Release|x64 + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|x64.Build.0 = Release|x64 + {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|x64.ActiveCfg = Debug|x64 + {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|x64.Build.0 = Debug|x64 + {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|x64.ActiveCfg = Release|x64 + {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|x64.Build.0 = Release|x64 {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|x64.ActiveCfg = Debug|x64 {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|x64.Build.0 = Debug|x64 {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Release|x64.ActiveCfg = Release|x64 @@ -783,6 +799,8 @@ Global {62173D9A-6724-4C00-A1C8-FB646480A9EC} = {38BDB927-829B-4C65-9CD9-93FB05D66D65} {127F38E0-40AA-4594-B955-5616BF206882} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {5E7360A8-D048-4ED3-8F09-0BFD64C5529A} = {127F38E0-40AA-4594-B955-5616BF206882} + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} = {4AFC9975-2456-4C70-94A4-84073C1CED93} + {3E424AD2-19E5-4AE6-B833-F53963EB5FC1} = {4AFC9975-2456-4C70-94A4-84073C1CED93} {D940E07F-532C-4FF3-883F-790DA014F19A} = {127F38E0-40AA-4594-B955-5616BF206882} {106CBECA-0701-4FC3-838C-9DF816A19AE2} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {2D604C07-51FC-46BB-9EB7-75AECC7F5E81} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} diff --git a/doc/devdocs/modules/launcher/plugins/community.unitconverter.md b/doc/devdocs/modules/launcher/plugins/community.unitconverter.md new file mode 100644 index 0000000000..aff29968b0 --- /dev/null +++ b/doc/devdocs/modules/launcher/plugins/community.unitconverter.md @@ -0,0 +1,31 @@ +# Unit Converter Plugin +The Unit Convert plugin as the name suggests is used to perform unit conversion on the user entered query. +This plugin uses a package called [UnitsNet](https://github.com/angularsen/UnitsNet). + +![Image of Calculator plugin](/doc/images/launcher/plugins/community.unitconverter.png) + +### Currently Supported Units + - [Acceleration](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/AccelerationUnit.g.cs) + - [Angle](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/AngleUnit.g.cs) + - [Area](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/AreaUnit.g.cs) + - [Duration](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/DurationUnit.g.cs) + - [Energy](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/EnergyUnit.g.cs) + - [Information](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/InformationUnit.g.cs) + - [Length](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/LengthUnit.g.cs) + - [Mass](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/MassUnit.g.cs) + - [Power](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/PowerUnit.g.cs) + - [Pressure](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/PressureUnit.g.cs) + - [Speed](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/SpeedUnit.g.cs) + - [Temperature](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/TemperatureUnit.g.cs) + - [Volume](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/VolumeUnit.g.cs) + + These are the ones that are currently enabled (though UnitsNet supports many more). They are defined in [`Main.cs`](/src/modules/launcher/Plugins/Community.PowerToys.Run.UnitConverter/Main.cs). + + +### [`InputInterpreter`](/src/modules/launcher/Plugins/Community.PowerToys.Run.UnitConverter/InputInterpreter.cs) + - Class which manipulates user input such that it may be interpreted correctly and thus converted. + - Uses a regex amongst other things to do this. + +### [`UnitHandler`](/src/modules/launcher/Plugins/Community.PowerToys.Run.UnitConverter/UnitHandler.cs) + - Class that does the actual conversion. + - Supports abbreviations in user input (single, double, or none). \ No newline at end of file diff --git a/doc/images/launcher/plugins/community.unitconverter.png b/doc/images/launcher/plugins/community.unitconverter.png new file mode 100644 index 0000000000000000000000000000000000000000..f7bcdbf04c9aa42aa84f1854055c4900500d12af GIT binary patch literal 6685 zcmZWudpy(s_n-7hEF?_1q*Rg`s;Q7juH}*_v_>uoG0Od}GMC(nkK9tZL@KvjR)&=& zl>2R%@sayw=91gY_IE!aWS0Mt^Qn?(6%0g@y?v zpPO%3_IF%W!;gAWex?Nzf6JwByi_?|wlTiE-rPTKa^YkvOA-b%JSpBenzKdK^5uuY zRKs=sGRh0lQ}ZyGW<%#ycO!`^9F_+LGk>%rsO;tpq6%1ZRYiO??%qfA)JGWXbrotw zqT3MP{+Gn!T^Q{4M`!u7`lCcFzb_n zP8!gZnxP?7@2gOyV6Z8}^r`V~UgLaqs@B<_sMB1qFMgfh+@9(N^7cGRpP50Z+VE1Z zsBA@X&NxHP<+9Nle?2%&z_prd!}+W3xO(I_f>Wh;bQQ zFxbu44+jpk&?Q?oWn6fVq|YI$T+`=E!$Ytzn9@E}!WF0I`OY(9rlxY&-lM0eDpj{# zSLgaYCtEQkx4&w^VC@fGquMgg(#>8m{?-lDEYr}#Wa*QrS}9Mwy}dI7=RdzJ*pwc{mK_^{J?+NX2u) zshP)o+XHj)PVrepl~(f;T#qDG0Qke>%wU@u{rsz?gDYW@$r0thuvxxiQ+%a=!4V5` z+hJkyR-Zgmh7b*^Vg$h^a{@GFCUlF!O*>&%vu8I%Y$kuccXz0gZe`6cMR{H#=$ zi<-29?I6fKoY#zVsC92Ozj17;4yZ!00a-T|CHn`%~09C#GKa zu1@m%VV$g>5WP7T>IJAw}@x^<3>+(v{WK)dM~Ua||1Nq2k$F zgqcmwCKB0P>bg=7}D^(f8HO|I{gGYMr7=2-R+W@H4$; zZ75z!tz7z?|I%0yhEQ(y?FnUZv>`GwvSE}`1`0Yh`MAYR%sDo5xx%KaIqmdWw}JA2 z6&D)*bvpG|?dE)$<87%&K$FzU-a<5ac(^!IwQq5jTue5Gzw1R}KaW2T-dwc`^l0%0 zE|mXgBFbmxADh;8W4L?|a(~FWZL!GX$B&C^8SmU6e|I6M+58{SbHhY7Ym<(5yC(`f z|HZ4)u|@o4bh%j^)>JT%o^>g*TsWAUB+>N2s;ts|IApHxBt<=hxt`%(>p%S_W#)DI zTpTwyHxRt4dsF>aW=sC4L-Cs{ZMeXgIz*{VqO?#5uJ^n0jC#|u*D}lbDRD#F zVVX$Rduiaa&JZIE2IJ$ANi@GELCsFB!Gqk*V7TkJk`RRoe%x=rGW0t4`p}1JJSUCa zk2bmQ3B;EWR+1Efm` zCU~XC$HT_v)hb#OFXu>a@r6sw@CfdIq2^npI1nMYS%}6nF7FFWk#f{I=z0)XbB|oK z;zmc1NTjCx16x~L){61_)x}vNH?U0e0Up@vL2okVZd}$&V&GMuY%xIk4rE(nr6I+c*f;1oDuEpT zc{E{hTmoH&8R6IbEu*%fmcD5W72+3Gm#^&ahpj%vgJY%@^BR}O0sKEgzc@g*}yxjA& zVWbbdENElN!rH%{Et$VhQc`m7Uj5pC3;>XoJ3SGBp9Y25cch^hyrmRum9i|{-gfNj1s)#CgAOX&Tq`bsIS=lV~ zUd$8d&bS}GJvYIL*&PFYZUo1BqU>xoD>$9`l{sX84_v-ZB2m6a8PS#6X7~acoY# z&)S$2^MO}9L>2SztEbjY8F6K*Elit)R=t0J1!)7 z+MuV#a#~xT(NrD)f1dP{_7N3NmRr@@z6WPgwc-xu_h4-&n$zs@AC&-Kfq5N}81F^8 z_TtyGaK~r}U3a4{ooznC`+9?Ce0PABC*4A>a`tOLQMp}UD(~Pbalu$yugtLdBSve$ z+B~H=T3BA7CjS9FAbAAbDhR_KHG-&9-eLZ^u!(^5@1gPHmyAhm+OZ1Bc^N-CXn^k& zTokT~iU`mwBSy_=8J`B0J>sI8^KPMl^RGE8iFIh3bp6w2Y5~!Yy`UXiTujDx=#rZ|_@8%~@WNoS zPaHi|j20!0wq)e_bc{jZTA$_Jo0!`Fb2=X|C1IV@pJe%$P)1vl*TeVFn1for7RDT* zmg(QdQ9rv-D=}~7M4Ttrkg?d3@Q@22&ZVg6pUzJ?G|58lRMzl#cN^e^vM(a;^R_^e zWM=qu(*0$#;d#@&Kcv1@o9qL^dip-t9rDHDk;4%R>l#+Ah&2)UH7ajQOa_1KTr=t7MZ#smik5?Axs#7{V#E}IX@GsLC(aMyVgg%LXXk=QSGgNaF^$dz=sv5I-^}sE zd3XRr0n>?hH5k4VzFTYO^p_K5rbEKpt|Am}`OTm+SAxB=hMx9n;@dyl`&=gkO+)|zyE{9w(BP?qxtYgi z5xO`IC3DBOTXB2Ap{oG~Rk)7EgaYB*0@dw&Rk(PgE-U*GgQ~y4tJg}~ksJ}N;ec^1 zG?>G;xd=XVtil!P;tznM3mgt};#Y2-OR&D$oF!#UmI zL>kr>#X97&!&!cJX#@c{_>WqmS7=yU4uF)p;@eG~Rjsti(VSzSKB*_@SSohI?Bsd4 zyrcRcN-omn-Fm*O=<=WApUYO^Oyi>dD0vEu5I|?GfwUmJ(*J<&5oEjlzb;6d!e$-U zen&>$DE@qZQrbsKJz&n^+nJCRW=xv>2?{H4^Vy+GK3}4RLzxTh|D2^)t9fc#`7oFK zHI}8=9`2u5ZS1Ag%my3Pp!FzPMDZRVTR1Hjy*G}jTKgbNTqp}2J6`b6?F>~EI|(6}^_ZBn~Lirim{ zc@Ka5Sh|1y<=W61x69aCG5z;idM>(e--q7DwTah_ zWRZwRQx=jJ%J_ll4t1yEs_NfK^wzAb5-wdL9m>feZqSZfSeL1KYEniYv%f`a3{g6K(`Snq~gkAhrHG9b>it{z}5O$s8xd@XMTGCPc`Gwmb?H21wM^oA&V{C`~eeu#y zgDwk&$d0sKUJWi`zq?0V+EB!lP1dZron1bXV8|R5US4dF29ZRQN&**Bf`UC34x!6a zg2tOP1aB_3jv~VxH;DwP8{3r6*FeqX!0_E z@#W!T={XILjiuyZF`LEpBy_{*NF(t=X%7pGlQF}rWa$fBi}egbJ#`Ix;r_3LCcv{0 zY9Sld8UyPf>sj19!D`cd8+~kEpD%Qq;WVqY7pj4bf;j2W1=6LaA8=ww(~ZOgRgk1l zl|<m3cj9locD_h+aw z%SU#_P^&BWX8D>HH?Si_Kf8u?3z-wE3hbBP%(y8*N0O$t+vER^CW_aRUp0nfzG-A| z%J-OIGC^laZfx4E?Uq;n1N8Lg1yAY~jSNYpB0;_LA>2^*(ixNcSRMzXLpz^JuPS~I z<;{sz-o^fi!yF09QH&tQ?U4<*x2h&>!0ZXa@6@=#kz4TWhY#es+FgJ|G zt&FE3T_=U&S84Bh1})T7J@DM23fDG$b~xau%9l`{o*5mM#2)tYAe`lQ#9&=a!);As z7|dSu>U#u!Z+Ku%&CS*JvF`q91jx~w`pN{Ib-A$t#ZcNfD2{;A@rRKNg$*sg)EbwD zzGpOfgd&si=6%uD<&PQXh5hFiLzhJ1Rah zZ(r6${r;3oTcf+xCZb&1S(cl2TtEDv<9(|_MeM> z3D-!G^jWLPh>VCRwER$Ii}~1R$IK9BWoh*fGgnRB67A>K$K0J5b z&ESsWS;`HoA0gMj^(KL7obCT$cAtUif;WV?PxH(wuvvNsNu-7ZY4&vJ1~PN0S6j?}Qe=X5*G4xF_nA68Yt!U(L!T2~g=T{<$J+XCf{IJ3o@u*U*RemZ z1l`9Aaz=oS7XL0DkxBeu={MJ7qt4EjnszS@G_KwD>o})v8RcK2%J_>s045e%&h;;| zyx$X_t9-Y6{JADUYhW*1;8TF{5O)xAFLhXOAW{MpuVGjr*n}i6D0DWiVvX-Y2cHFFAq}^eei)QtKX!E59$ajs@~rW4$HThP^qvC5A@i zR-%WBQ0lo!SW`7loZJlV`ogbFj5Lm2;M+%5;q>iJq3Lz3Ew6OE;V*Yw-))7!U$?b|S)fvm6W882k58_#;BAV#{mDVu-7 z!Yz_q97#2)OgembwQupc2g&&5ipUSc8CZ}>~JteA# z6H>)_PnAx_Xjk_U?jgs*;_yr4i2IHmc8~jSBvO?^s%uX6`;462uMu5_35+2g?=Qh0 zy<5T`YB7@iW?>?@USPSt1Z|nPy#B076*6mgRsGoEeamSM5x>vXE7*$tI03OEH(sjU zgKNFWGYvL(_L4bFE!JiVk(3mKFR`Ty3f0x$=c59S*0?@iPSx5vV5BUg#UXc%maVIP_hr2#Em_q2LWOLYQa*QwBh!OQY6j~AfkFr(}aOoG*`LtvI$S(yg;NL~$ z4GSRujy70QLN%8h!|xln0c|NOpe<#uAzmp&{l66Y--(u^@sa;O(?ZZue@X$2ZVRZ) zbB-yuJ~^4Lb+$RqM?;b`Gycz93uf@LTmn9y-r1}!1a9VD>Z0OZJANI(QMKF-7}Xps z{Dj_HbId3|0eNe%i%LoPjD$}0atcuRK%?-S4*^z$Z;|2HEB zXKLTbnR8n@-gGki4Zjz#yTO2%8UW2%-CZb_Cz!Jaz;by0eN+foJS!ETQaw>WJyDyU z-J(yrXArta8$14rnc9D^6PowFCr!*xJB3d=akk|T(!m5AFNvuP$AUeAp{+PY2Sty0 zEAzKgE&3dW7>3F+0&8pv5> Tc>KU30cN0MtX-sa`_X>@hC9`a literal 0 HcmV?d00001 diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index bc88817663..821264acbb 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -276,6 +276,10 @@ + + + + @@ -907,7 +911,7 @@ - + @@ -980,6 +984,9 @@ + + + @@ -1015,7 +1022,7 @@ - + @@ -1090,6 +1097,17 @@ + + + + + + + + + + + diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.csproj b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.csproj new file mode 100644 index 0000000000..d30c233c44 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.csproj @@ -0,0 +1,37 @@ + + + + netcoreapp3.1 + + false + + x64 + + + + x64 + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/InputInterpreterTests.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/InputInterpreterTests.cs new file mode 100644 index 0000000000..0d6dfb4274 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/InputInterpreterTests.cs @@ -0,0 +1,67 @@ +using System.Globalization; +using NUnit.Framework; +using Wox.Plugin; + +namespace Community.PowerToys.Run.Plugin.UnitConverter.UnitTest +{ + [TestFixture] + public class InputInterpreterTests + { + [TestCase(new string[] { "1,5'" }, new string[] { "1,5", "'" })] + [TestCase(new string[] { "1.5'" }, new string[] { "1.5", "'" })] + [TestCase(new string[] { "1'" }, new string[] { "1", "'" })] + [TestCase(new string[] { "1'5\"" }, new string[] { "1", "'", "5", "\"" })] + [TestCase(new string[] { "5\"" }, new string[] { "5", "\"" })] + [TestCase(new string[] { "1'5" }, new string[] { "1", "'", "5" })] + public void RegexSplitsInput(string[] input, string[] expectedResult) + { + string[] shortsplit = InputInterpreter.RegexSplitter(input); + Assert.AreEqual(expectedResult, shortsplit); + } + + [TestCase(new string[] { "1cm", "to", "mm" }, new string[] { "1", "cm", "to", "mm" })] + public void InsertsSpaces(string[] input, string[] expectedResult) + { + InputInterpreter.InputSpaceInserter(ref input); + Assert.AreEqual(expectedResult, input); + } + + [TestCase(new string[] { "1'", "in", "cm" }, new string[] { "1", "foot", "in", "cm" })] + [TestCase(new string[] { "1\"", "in", "cm" }, new string[] { "1", "inch", "in", "cm" })] + [TestCase(new string[] { "1'6", "in", "cm" }, new string[] { "1.5", "foot", "in", "cm" })] + [TestCase(new string[] { "1'6\"", "in", "cm" }, new string[] { "1.5", "foot", "in", "cm" })] + public void HandlesShorthandFeetInchNotation(string[] input, string[] expectedResult) + { + InputInterpreter.ShorthandFeetInchHandler(ref input, CultureInfo.InvariantCulture); + Assert.AreEqual(expectedResult, input); + } + + [TestCase(new string[] { "5", "CeLsIuS", "in", "faHrenheiT" }, new string[] { "5", "DegreeCelsius", "in", "DegreeFahrenheit" })] + [TestCase(new string[] { "5", "f", "in", "celsius" }, new string[] { "5", "°f", "in", "DegreeCelsius" })] + [TestCase(new string[] { "5", "c", "in", "f" }, new string[] { "5", "°c", "in", "°f" })] + [TestCase(new string[] { "5", "f", "in", "c" }, new string[] { "5", "°f", "in", "°c" })] + public void PrefixesDegrees(string[] input, string[] expectedResult) + { + InputInterpreter.DegreePrefixer(ref input); + Assert.AreEqual(expectedResult, input); + } + + [TestCase("a f in c")] + [TestCase("12 f in")] + public void ParseInvalidQueries(string queryString) + { + Query query = new Query(queryString); + var result = InputInterpreter.Parse(query); + Assert.AreEqual(null, result); + } + + [TestCase("12 f in c", 12)] + [TestCase("10m to cm", 10)] + public void ParseValidQueries(string queryString, double result) + { + Query query = new Query(queryString); + var convertModel = InputInterpreter.Parse(query); + Assert.AreEqual(result, convertModel.Value); + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/UnitHandlerTests.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/UnitHandlerTests.cs new file mode 100644 index 0000000000..aef9210a04 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/UnitHandlerTests.cs @@ -0,0 +1,42 @@ +using System.Globalization; +using System.Linq; +using NUnit.Framework; + +namespace Community.PowerToys.Run.Plugin.UnitConverter.UnitTest +{ + [TestFixture] + public class UnitHandlerTests + { + [Test] + public void HandleTemperature() + { + var convertModel = new ConvertModel(1, "DegreeCelsius", "DegreeFahrenheit"); + double result = UnitHandler.ConvertInput(convertModel, UnitsNet.QuantityType.Temperature); + Assert.AreEqual(33.79999999999999d, result); + } + + [Test] + public void HandleLength() + { + var convertModel = new ConvertModel(1, "meter", "centimeter"); + double result = UnitHandler.ConvertInput(convertModel, UnitsNet.QuantityType.Length); + Assert.AreEqual(100, result); + } + + [Test] + public void HandlesByteCapitals() + { + var convertModel = new ConvertModel(1, "kB", "kb"); + double result = UnitHandler.ConvertInput(convertModel, UnitsNet.QuantityType.Information); + Assert.AreEqual(8, result); + } + + [Test] + public void HandleInvalidModel() + { + var convertModel = new ConvertModel(1, "aa", "bb"); + var results = UnitHandler.Convert(convertModel); + Assert.AreEqual(0, results.Count()); + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Community.PowerToys.Run.Plugin.UnitConverter.csproj b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Community.PowerToys.Run.Plugin.UnitConverter.csproj new file mode 100644 index 0000000000..fe32784f51 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Community.PowerToys.Run.Plugin.UnitConverter.csproj @@ -0,0 +1,99 @@ + + + + + netcoreapp3.1 + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} + Properties + Community.PowerToys.Run.Plugin.UnitConverter + Community.PowerToys.Run.Plugin.UnitConverter + $(Version).0 + true + false + false + x64 + en-US + + + + true + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Community.UnitConverter\ + DEBUG;TRACE + full + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + 4 + false + false + + + + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Community.UnitConverter\ + TRACE + true + pdbonly + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + 4 + + + + + + + + + + + + + false + + + false + + + + + + PreserveNewest + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + True + True + Resources.resx + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/ConvertModel.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/ConvertModel.cs new file mode 100644 index 0000000000..e82ee9ae08 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/ConvertModel.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Community.PowerToys.Run.Plugin.UnitConverter +{ + public class ConvertModel + { + public double Value { get; set; } + + public string FromUnit { get; set; } + + public string ToUnit { get; set; } + + public ConvertModel() + { + } + + public ConvertModel(double value, string fromUnit, string toUnit) + { + Value = value; + FromUnit = fromUnit; + ToUnit = toUnit; + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Images/unitconverter.dark.png b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Images/unitconverter.dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e2553f953f1aeeb4e17e23f06ba86112cbe9459b GIT binary patch literal 2328 zcmcImeQXnD9KM1KC~P2#=uo-b)ChRJk6u6ST!F0}tYqDm(qzLZ?Crg8+Y4>)yt}RK zz=^1U31&ni@`uhT5RD3wU=$*PB7ceUp??@dfQ%RziO8aYGx+shzcvWqVq(+leZ0^6 z{GQ+M_q_M^h1TXJmG){oK@gR}#y}XKS?icofq&b^{k{R8CToq$4T7jTY#p{&=2y)o zh>44oNW0k{YLa9%^Y)JE? zgwoipqh;OA5xIMnEJAAG0N0BR zLTR(66iiE~rfMYAn`kQ!U!xGp<$zkJp3IjA$_z@P6y`QCmdoqYB)^V;sp=6`O_mzf zT8c>W7)L(brlg^oG3Hu|iW5)(n8-)ruCX-h#@`4hNKTK$i}P7tV%Y*Tgna;k3CduO zW(ArPBDlo@FFD=UfK{l(a$|vpLQ*hom>?~qV8BOVQ4U3clIV)EqBH8EfeaxHMS-J5 zKIX<&gRBd{lfeeRn-kq`ns;y%31um!>M4MYrldd|FNC0lMlkSab_Zcyg2B~+lzBoHROg-Z%B zLyegkprg7tZppv8khz(719aV5x&VM%hzg4g4M-csuS zi}YeywzYW9tL&aE^W(6eeAl3}d}m3l3whH{XcMBPeS& zfgI9}Vl(SOe59=-xICsAq_F9K+OvN2ddQDhG=&kXj``39B4&i*noxPjsBNG$3-2w~ zWqDJ>H|1>+rE#Zpyy+b~yBZRNttS|$izJ@8`2C5U&$3e|9RKd)y0$_4kB6I=zq!ZU zvP-)vKLDq8&N#DmY2fU{#x*TJywrP|_~Xx?hmTbqf7G$)%3k8a__evpcb_1>I-0ro z+Hzv@!A&1*TxN@8XPp&@nFok$Zu;BAJ3}q|GQW}E*cPAMZ);i+n78QC{^wRspEixE z>_0SXpC>r{*6C9X+llvw-i!CXEX3CCq!n^ngX#|I23m=!J=chbBqe*ArLlvp1e=d9bqIzF|%{LVV<#)6;l@ zusCMicVKeI%qt(-Yu3!~STXZy+ukGf{>*`U$d{WA4-S33_O9D%*39a!_}qS`a){hI z;3t+$>KbN0t6F#ef;&6N!GSMq73-?|#%*pmfBV3WExA+s!7-`lua#d;+3dr`8zw^D WDHj)rp*_}rmtaG4U{`&|x_(* z2*HZD#h932KnyA@fta9CAb^rEWPuSALJ$)Z0tt#HxC8@R0tquA>U;g#Ab^XBO|SRy zKJW8;e!t)I-rM)u+m_W<&8{K{qBa}~cHlGX98)Xt@2V+(_TkeLsnBYRAf6m@jG?N1AdSsIdifg)`8aaNFA~oozR(T?9 zBo#niT4`L4KxUE&-I6nYD#t71Yv(aTkvWLH-cL0< z0m;rtI~g!cM0#B;t+K3_^hqwxEBHL_#U#)29K-Sq=b<@H_V6<2CJP@5M>Ane?g+k6 zh=uR`RF`e1WQIwn)2_7JWtee>lO)OE;CUJ&XsbuJL59|?`9+2xvQ$$`*_xq~4kJ*E zZre{`rTG++sS>Sj70QGQ#$-T>;asefQV|%5JQ|uzme7{nx&cQi=zSou741nOrUO|< zx2dAm4M?}=7lWm`4BN1}jE86|4d0^>s-=LbZZnZ94^$bHKuOGPVJw%^my!b}0=8jB z4I@!(RC_TZ$zvS(+$v3nM%r5BC@M@qL0}_4g}cVmtQUWyoFKb>GA}i-yv(wBXaxHJ z0vnXT9L)+eCq!|J1>9oaJzxWBv7X65BM~{QTQ<;D6b||+EXt*6Q05g*^uz$91>U33 zk|^-BBJv82M4t!*w@>91Pfiz}A2if%rxMQmyltVOVvbTZWx=bsV~FKwP81>SQ3Xhg zG09CsRm4M_Cnf^E$o7({;gtgt6ImTq5Hli4^okIQwB$p8hJwJ-06|FeKEO+E6=HP~ zMUKY}7zx9S7!X(2J@I)s5NJ1zn3li;R>!gyGTa*Qa+23e^Dd4ep(@7=GYPQKv?Pcl zCZ)$6+hZ9x-H>MC9`_U$(hBru;VPk#Id{rHbryx6QXMxSNEIGy4=D5G63Wx2yAURQ zgiG=;%ZS-&V4~(YZppv8ka?JR3v76_bO8XbponhTCqaqkV}eAB3h$-`$oW)75Pcpf z7E}LUq!-Gnb^$$(@Eew){(CZ$v-^F0c-R7*TpxIsd*a(5Kk2??80W>BcNlZO`Nmxy zMHzcJ=s~7cXl4tDkGFLkm&3GyBsTrc!@U#NLvF;O$&Wa7%!SSqG2;~XgvvoCYy-tv zcyDnoOPdFZVJ zPAy(rzo}{A%nz>$Qsa(w`=9B*@N@O&E6+UDGPv!J-{$roZOf)VBkokyFPOIX72@2k zcQ1YVLAIi4?CG~=>@9H+?Sw$G#sv@nt19h15T(8(jPTXys7j$*Q5q zJEP@8{%Hg0&2zic9Ch*UOI9b*5qNRs&GWZ>0~P(kKKSy;Kt+>#-@5bL?)G+Ochw(lTvB~7 z9=cKW197Bg|Mk%mWmo#P|NhtsVtYNR=&xN=O>~D2mEEpCQ~q(yqFIB9&>`aRv9XyW zeKmub;Wfm + /// Separates input like: "1ft in cm" to "1 ft in cm" + /// + public static void InputSpaceInserter(ref string[] split) + { + if (split.Length != 3) + { + return; + } + + string[] parseInputWithoutSpace = Regex.Split(split[0], pattern); + + if (parseInputWithoutSpace.Length > 1) + { + string[] firstEntryRemoved = split.Skip(1).ToArray(); + string[] newSplit = new string[] { parseInputWithoutSpace[0], parseInputWithoutSpace[1] }; + + split = newSplit.Concat(firstEntryRemoved).ToArray(); + } + } + + /// + /// Replaces a split input array with shorthand feet/inch notation (1', 1'2" etc) to 'x foot in cm'. + /// + public static void ShorthandFeetInchHandler(ref string[] split, CultureInfo culture) + { + if (!split[0].Contains('\'') && !split[0].Contains('\"')) + { + return; + } + + // catches 1' || 1" || 1'2 || 1'2" in cm + // by converting it to "x foot in cm" + if (split.Length == 3) + { + string[] shortsplit = RegexSplitter(split); + + switch (shortsplit.Length) + { + case 2: + // ex: 1' & 1" + if (shortsplit[1] == "\'") + { + string[] newInput = new string[] { shortsplit[0], "foot", split[1], split[2] }; + split = newInput; + } + else if (shortsplit[1] == "\"") + { + string[] newInput = new string[] { shortsplit[0], "inch", split[1], split[2] }; + split = newInput; + } + + break; + + case 3: + case 4: + // ex: 1'2 and 1'2" + if (shortsplit[1] == "\'") + { + bool isFeet = double.TryParse(shortsplit[0], NumberStyles.AllowDecimalPoint, culture, out double feet); + bool isInches = double.TryParse(shortsplit[2], NumberStyles.AllowDecimalPoint, culture, out double inches); + + if (!isFeet || !isInches) + { + // atleast one could not be parsed correctly + break; + } + + string convertedTotalInFeet = Length.FromFeetInches(feet, inches).Feet.ToString(culture); + + string[] newInput = new string[] { convertedTotalInFeet, "foot", split[1], split[2] }; + split = newInput; + } + + break; + + default: + break; + } + } + } + + /// + /// Adds degree prefixes to degree units for shorthand notation. E.g. '10 c in fahrenheit' becomes '10 °c in DegreeFahrenheit'. + /// + public static void DegreePrefixer(ref string[] split) + { + switch (split[1].ToLower()) + { + case "celsius": + split[1] = "DegreeCelsius"; + break; + + case "fahrenheit": + split[1] = "DegreeFahrenheit"; + break; + + case "c": + split[1] = "°c"; + break; + + case "f": + split[1] = "°f"; + break; + + default: + break; + } + + switch (split[3].ToLower()) + { + case "celsius": + split[3] = "DegreeCelsius"; + break; + + case "fahrenheit": + split[3] = "DegreeFahrenheit"; + break; + + case "c": + split[3] = "°c"; + break; + + case "f": + split[3] = "°f"; + break; + + default: + break; + } + } + + public static ConvertModel Parse(Query query) + { + string[] split = query.Search.Split(' '); + + InputInterpreter.ShorthandFeetInchHandler(ref split, CultureInfo.CurrentCulture); + InputInterpreter.InputSpaceInserter(ref split); + + if (split.Length != 4) + { + // deny any other queries than: + // 10 ft in cm + // 10 ft to cm + return null; + } + + InputInterpreter.DegreePrefixer(ref split); + if (!double.TryParse(split[0], out double value)) + { + return null; + } + + return new ConvertModel() + { + Value = value, + FromUnit = split[1], + ToUnit = split[3], + }; + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/LocProject.json b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/LocProject.json new file mode 100644 index 0000000000..4976e045c6 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/LocProject.json @@ -0,0 +1,14 @@ +{ + "Projects": [ + { + "LanguageSet": "Azure_Languages", + "LocItems": [ + { + "SourceFile": "src\\modules\\launcher\\Plugins\\Community.PowerToys.Run.Plugin.UnitConverter\\Properties\\Resources.resx", + "CopyOption": "LangIDOnName", + "OutputPath": "src\\modules\\launcher\\Plugins\\Community.PowerToys.Run.Plugin.UnitConverter\\Properties" + } + ] + } + ] +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Main.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Main.cs new file mode 100644 index 0000000000..2c59859693 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Main.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Windows; +using System.Windows.Input; +using ManagedCommon; +using UnitsNet; +using Wox.Plugin; + +namespace Community.PowerToys.Run.Plugin.UnitConverter +{ + public class Main : IPlugin, IPluginI18n, IContextMenu, IDisposable + { + public string Name => Properties.Resources.plugin_name; + + public string Description => Properties.Resources.plugin_description; + + private PluginInitContext _context; + private static string _icon_path; + private bool _disposed; + + public void Init(PluginInitContext context) + { + if (context == null) + { + throw new ArgumentNullException(paramName: nameof(context)); + } + + _context = context; + _context.API.ThemeChanged += OnThemeChanged; + UpdateIconPath(_context.API.GetCurrentTheme()); + } + + public List Query(Query query) + { + if (query == null) + { + throw new ArgumentNullException(paramName: nameof(query)); + } + + // Parse + ConvertModel convertModel = InputInterpreter.Parse(query); + if (convertModel == null) + { + return new List(); + } + + // Convert + return UnitHandler.Convert(convertModel) + .Select(x => GetResult(x)) + .ToList(); + } + + private Result GetResult(UnitConversionResult result) + { + return new Result + { + ContextData = result, + Title = string.Format("{0} {1}", result.ConvertedValue, result.UnitName), + IcoPath = _icon_path, + Score = 300, + SubTitle = string.Format(Properties.Resources.copy_to_clipboard, result.QuantityType), + Action = c => + { + var ret = false; + var thread = new Thread(() => + { + try + { + Clipboard.SetText(result.ConvertedValue.ToString()); + ret = true; + } + catch (ExternalException) + { + MessageBox.Show(Properties.Resources.copy_failed); + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + return ret; + }, + }; + } + + private ContextMenuResult CreateContextMenuEntry(UnitConversionResult result) + { + return new ContextMenuResult + { + PluginName = Name, + Title = Properties.Resources.context_menu_copy, + Glyph = "\xE8C8", + FontFamily = "Segoe MDL2 Assets", + AcceleratorKey = Key.Enter, + Action = _ => + { + bool ret = false; + var thread = new Thread(() => + { + try + { + Clipboard.SetText(result.ConvertedValue.ToString()); + ret = true; + } + catch (ExternalException) + { + MessageBox.Show(Properties.Resources.copy_failed); + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + return ret; + }, + }; + } + + public List LoadContextMenus(Result selectedResult) + { + if (!(selectedResult?.ContextData is UnitConversionResult)) + { + return new List(); + } + + List contextResults = new List(); + UnitConversionResult result = selectedResult.ContextData as UnitConversionResult; + contextResults.Add(CreateContextMenuEntry(result)); + + return contextResults; + } + + public string GetTranslatedPluginTitle() + { + return Properties.Resources.plugin_name; + } + + public string GetTranslatedPluginDescription() + { + return Properties.Resources.plugin_description; + } + + private void OnThemeChanged(Theme _, Theme newTheme) + { + UpdateIconPath(newTheme); + } + + private static void UpdateIconPath(Theme theme) + { + if (theme == Theme.Light || theme == Theme.HighContrastWhite) + { + _icon_path = "Images/unitconverter.light.png"; + } + else + { + _icon_path = "Images/unitconverter.dark.png"; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _context.API.ThemeChanged -= OnThemeChanged; + _disposed = true; + } + } + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..657e7ce987 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.Designer.cs @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Community.PowerToys.Run.Plugin.UnitConverter.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public 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)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Community.PowerToys.Run.Plugin.UnitConverter.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)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Copy (Enter). + /// + public static string context_menu_copy { + get { + return ResourceManager.GetString("context_menu_copy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy failed. + /// + public static string copy_failed { + get { + return ResourceManager.GetString("copy_failed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy {0} to clipboard. + /// + public static string copy_to_clipboard { + get { + return ResourceManager.GetString("copy_to_clipboard", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Provides unit conversion (e.g. 10 ft in m).. + /// + public static string plugin_description { + get { + return ResourceManager.GetString("plugin_description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unit Converter. + /// + public static string plugin_name { + get { + return ResourceManager.GetString("plugin_name", resourceCulture); + } + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.resx b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.resx new file mode 100644 index 0000000000..22e07ab650 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Copy (Enter) + + + Copy failed + + + Copy {0} to clipboard + + + Provides unit conversion (e.g. 10 ft in m). + + + Unit Converter + + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/UnitConversionResult.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/UnitConversionResult.cs new file mode 100644 index 0000000000..7a3eb75283 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/UnitConversionResult.cs @@ -0,0 +1,20 @@ +using UnitsNet; + +namespace Community.PowerToys.Run.Plugin.UnitConverter +{ + public class UnitConversionResult + { + public double ConvertedValue { get; } + + public string UnitName { get; } + + public QuantityType QuantityType { get; } + + public UnitConversionResult(double convertedValue, string unitName, QuantityType quantityType) + { + ConvertedValue = convertedValue; + UnitName = unitName; + QuantityType = quantityType; + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/UnitHandler.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/UnitHandler.cs new file mode 100644 index 0000000000..a084205dcc --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/UnitHandler.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using UnitsNet; + +namespace Community.PowerToys.Run.Plugin.UnitConverter +{ + public static class UnitHandler + { + private static readonly int _roundingFractionalDigits = 4; + + private static readonly QuantityType[] _included = new QuantityType[] + { + QuantityType.Acceleration, + QuantityType.Angle, + QuantityType.Area, + QuantityType.Duration, + QuantityType.Energy, + QuantityType.Information, + QuantityType.Length, + QuantityType.Mass, + QuantityType.Power, + QuantityType.Pressure, + QuantityType.Speed, + QuantityType.Temperature, + QuantityType.Volume, + }; + + /// + /// Given string representation of unit, converts it to the enum. + /// + /// Corresponding enum or null. + private static Enum GetUnitEnum(string unit, QuantityInfo unitInfo) + { + UnitInfo first = Array.Find(unitInfo.UnitInfos, info => info.Name.ToLower() == unit.ToLower()); + if (first != null) + { + return first.Value; + } + + if (UnitParser.Default.TryParse(unit, unitInfo.UnitType, out Enum enum_unit)) + { + return enum_unit; + } + + return null; + } + + /// + /// Given parsed ConvertModel, computes result. (E.g "1 foot in cm"). + /// + /// The converted value as a double. + public static double ConvertInput(ConvertModel convertModel, QuantityType quantityType) + { + QuantityInfo unitInfo = Quantity.GetInfo(quantityType); + + var fromUnit = GetUnitEnum(convertModel.FromUnit, unitInfo); + var toUnit = GetUnitEnum(convertModel.ToUnit, unitInfo); + + if (fromUnit != null && toUnit != null) + { + return UnitsNet.UnitConverter.Convert(convertModel.Value, fromUnit, toUnit); + } + + return double.NaN; + } + + /// + /// Given ConvertModel returns collection of possible results. + /// + /// The converted value as a double. + public static IEnumerable Convert(ConvertModel convertModel) + { + var results = new List(); + foreach (QuantityType quantityType in _included) + { + double convertedValue = UnitHandler.ConvertInput(convertModel, quantityType); + + if (!double.IsNaN(convertedValue)) + { + UnitConversionResult result = new UnitConversionResult(Math.Round(convertedValue, _roundingFractionalDigits), convertModel.ToUnit, quantityType); + results.Add(result); + } + } + + return results; + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/plugin.json b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/plugin.json new file mode 100644 index 0000000000..267dd5687b --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/plugin.json @@ -0,0 +1,13 @@ +{ + "ID": "aa0ee9daff654fb7be452c2d77c471b9", + "ActionKeyword": "%%", + "IsGlobal": false, + "Name": "Unit Converter", + "Author": "ThiefZero", + "Version": "0.0.1", + "Language": "csharp", + "Website": "https://github.com/ThiefZero", + "IcoPathDark": "Images\\unitconverter.dark.png", + "IcoPathLight": "Images\\unitconverter.light.png", + "ExecuteFileName": "Community.PowerToys.Run.Plugin.UnitConverter.dll" +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Main.cs index 87d60488cf..822531f2c4 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Main.cs @@ -74,7 +74,6 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator UpdateIconPath(Context.API.GetCurrentTheme()); } - // Todo : Update with theme based IconPath private void UpdateIconPath(Theme theme) { if (theme == Theme.Light || theme == Theme.HighContrastWhite) diff --git a/src/modules/launcher/PowerLauncher/PowerLauncher.csproj b/src/modules/launcher/PowerLauncher/PowerLauncher.csproj index 9745aa61d8..c804746332 100644 --- a/src/modules/launcher/PowerLauncher/PowerLauncher.csproj +++ b/src/modules/launcher/PowerLauncher/PowerLauncher.csproj @@ -113,6 +113,7 @@ NU1701 +