From c80a6a363e7594550c639637d8fd311c242a4ad1 Mon Sep 17 00:00:00 2001 From: Philosoph228 Date: Mon, 29 Apr 2024 06:48:01 +0500 Subject: [PATCH] Version 0.4.3 (#13) --- panitent.vcxproj | 64 +- panitent_test/main.c | 2 +- panitent_test/test_application.c | 20 +- panitent_test/test_application.h | 5 +- panitent_test/test_window.c | 9 +- res/appmeta.aps | Bin 0 -> 448 bytes res/appmeta.rc | 29 + res/dockhostbg.bmp | Bin 0 -> 205366 bytes res/floatingglyphs.bmp | Bin 1014 -> 2176 bytes res/floatingglyphs2.bmp | Bin 0 -> 1206 bytes res/panitent.aps | Bin 766460 -> 0 bytes res/panitent.rc | 346 +- res/resource.h | 105 +- res/wisp.bmp | Bin 0 -> 65590 bytes src/aboutbox.c | 15 +- src/bresenham.c | 189 +- src/brush.c | 285 +- src/brush.h | 13 +- src/canvas.c | 16 +- src/color_context.c | 6 +- src/controllibrary.c | 3 +- src/crefptr.c | 1 + src/dockhost.c | 98 +- src/dockhost.h | 4 +- src/dockinspectordialog.c | 49 +- src/document.c | 15 +- src/experimental/bubble.c | 126 + src/experimental/bubble.h | 11 + src/experimental/captionglyphs.c | 0 src/experimental/captionglyphs.h | 1 + src/experimental/dockhostcomposite.c | 252 ++ src/experimental/dockhostcomposite.h | 17 + src/experimental/docklib.h | 5 + src/experimental/dockpanel.c | 871 +++++ src/experimental/dockpanel.h | 4 + src/experimental/dockwindow.c | 145 + src/experimental/dockwindow.h | 6 + src/flexible.c | 8 +- src/floatingwindowcontainer.c | 83 +- src/glwindow.c | 33 +- src/history.c | 191 +- src/layeredwindow.c | 9 +- src/layerswindow.c | 3 +- src/log.c | 166 +- src/log.h | 9 +- src/log_window.c | 475 ++- src/log_window.h | 15 +- src/new.c | 14 +- src/option_bar.c | 9 +- src/palette.c | 3 +- src/palette_window.c | 377 +- src/palette_window.h | 8 +- src/panitent.c | 791 ++-- src/panitent.h | 15 +- src/panitentwindow.c | 395 +- src/panitentwindow.h | 2 + src/pntxml.c | 1132 ++++++ src/pntxml.h | 146 + src/primitives_context.c | 24 +- src/propgriddialog.c | 86 + src/propgriddialog.h | 13 + src/res2.aps | Bin 0 -> 448 bytes src/resource.h | 7 + src/settings.c | 9 +- src/settings_dialog.c | 5 +- src/sharing/activitysharingclient.c | 3 + src/sharing/activitysharingclient.h | 7 + src/sharing/activitysharingmanager.c | 82 + src/sharing/activitysharingmanager.h | 17 + src/sharing/activitystubdialog.c | 74 + src/sharing/activitystubdialog.h | 13 + src/sharing/discordasp.c | 33 + src/sharing/discordasp.h | 10 + src/{ => sharing}/discordsdk.c | 21 +- src/{ => sharing}/discordsdk.h | 0 src/splittercontainer.c | 10 +- src/swatch.c | 3 +- src/swatch2.c | 3 +- src/toolbox.c | 196 +- src/toolbox.h | 2 +- src/tools/brushtool.c | 3 +- src/tools/circletool.c | 3 +- src/tools/erasertool.c | 3 +- src/tools/filltool.c | 3 +- src/tools/linetool.c | 3 +- src/tools/penciltool.c | 3 +- src/tools/pickertool.c | 3 +- src/tools/pointertool.c | 3 +- src/tools/rectangletool.c | 3 +- src/tools/texttool.c | 3 +- src/toolwndframe.c | 38 + src/toolwndframe.h | 21 + src/util.h | 4 +- src/util/assert.h | 9 + src/util/bytestream.c | 199 + src/util/bytestream.h | 99 + src/util/hashmap.c | 301 ++ src/util/hashmap.h | 108 + src/util/list.c | 91 + src/util/list.h | 13 + src/util/pntrtti.c | 46 + src/util/pntrtti.h | 7 + src/util/pntstring.c | 312 ++ src/util/pntstring.h | 89 + src/{ => util}/queue.c | 8 +- src/{ => util}/queue.h | 0 src/{ => util}/stack.c | 4 +- src/{ => util}/stack.h | 0 src/{ => util}/tree.c | 6 +- src/{ => util}/tree.h | 0 src/util/utf.c | 179 + src/util/utf.h | 16 + src/util/vector.c | 74 + src/util/vector.h | 15 + src/viewport.c | 20 +- src/wic.c | 1 + src/win32/application.c | 3 +- src/win32/common.h | 14 +- src/win32/dialog.c | 21 +- src/win32/framework.c | 5 +- src/win32/listbox.c | 1 + src/win32/propertygrid.c | 39 + src/win32/propertygrid.h | 14 + src/win32/propertygridimpl.c | 5282 ++++++++++++++++++++++++++ src/win32/propertygridimpl.h | 414 ++ src/win32/tree_view.c | 9 +- src/win32/util.c | 24 +- src/win32/util.h | 12 + src/win32/window.c | 302 +- src/win32/window.h | 1 + src/win32/windowmap.c | 86 +- src/win32/windowmap.h | 12 +- src/windowstub.c | 13 +- src/workspacecontainer.c | 9 +- src/xml.c | 1132 ++++++ src/xml.h | 196 + win32fw.vcxproj | 4 + win32fw_test/test_application.c | 9 +- win32fw_test/test_window.c | 9 +- 139 files changed, 14582 insertions(+), 1933 deletions(-) create mode 100644 res/appmeta.aps create mode 100644 res/appmeta.rc create mode 100644 res/dockhostbg.bmp create mode 100644 res/floatingglyphs2.bmp delete mode 100644 res/panitent.aps create mode 100644 res/wisp.bmp create mode 100644 src/experimental/bubble.c create mode 100644 src/experimental/bubble.h create mode 100644 src/experimental/captionglyphs.c create mode 100644 src/experimental/captionglyphs.h create mode 100644 src/experimental/dockhostcomposite.c create mode 100644 src/experimental/dockhostcomposite.h create mode 100644 src/experimental/docklib.h create mode 100644 src/experimental/dockpanel.c create mode 100644 src/experimental/dockpanel.h create mode 100644 src/experimental/dockwindow.c create mode 100644 src/experimental/dockwindow.h create mode 100644 src/pntxml.c create mode 100644 src/pntxml.h create mode 100644 src/propgriddialog.c create mode 100644 src/propgriddialog.h create mode 100644 src/res2.aps create mode 100644 src/sharing/activitysharingclient.c create mode 100644 src/sharing/activitysharingclient.h create mode 100644 src/sharing/activitysharingmanager.c create mode 100644 src/sharing/activitysharingmanager.h create mode 100644 src/sharing/activitystubdialog.c create mode 100644 src/sharing/activitystubdialog.h create mode 100644 src/sharing/discordasp.c create mode 100644 src/sharing/discordasp.h rename src/{ => sharing}/discordsdk.c (91%) rename src/{ => sharing}/discordsdk.h (100%) create mode 100644 src/toolwndframe.c create mode 100644 src/toolwndframe.h create mode 100644 src/util/assert.h create mode 100644 src/util/bytestream.c create mode 100644 src/util/bytestream.h create mode 100644 src/util/hashmap.c create mode 100644 src/util/hashmap.h create mode 100644 src/util/list.c create mode 100644 src/util/list.h create mode 100644 src/util/pntrtti.c create mode 100644 src/util/pntrtti.h create mode 100644 src/util/pntstring.c create mode 100644 src/util/pntstring.h rename src/{ => util}/queue.c (88%) rename src/{ => util}/queue.h (100%) rename src/{ => util}/stack.c (85%) rename src/{ => util}/stack.h (100%) rename src/{ => util}/tree.c (96%) rename src/{ => util}/tree.h (100%) create mode 100644 src/util/utf.c create mode 100644 src/util/utf.h create mode 100644 src/util/vector.c create mode 100644 src/util/vector.h create mode 100644 src/win32/propertygrid.c create mode 100644 src/win32/propertygrid.h create mode 100644 src/win32/propertygridimpl.c create mode 100644 src/win32/propertygridimpl.h create mode 100644 src/xml.c create mode 100644 src/xml.h diff --git a/panitent.vcxproj b/panitent.vcxproj index edb85d1..d0cbd7e 100644 --- a/panitent.vcxproj +++ b/panitent.vcxproj @@ -33,6 +33,11 @@ + + + + + @@ -50,12 +55,16 @@ + - + + + + + - @@ -69,8 +78,18 @@ - + + + + + + + + + + + @@ -90,15 +109,21 @@ - + + + + + + + @@ -113,14 +138,19 @@ + - + + + + + + - @@ -137,8 +167,19 @@ - + + + + + + + + + + + + @@ -162,6 +203,7 @@ + @@ -233,7 +275,7 @@ Windows true - shlwapi.lib;comctl32.lib;uxtheme.lib;msimg32.lib;windowscodecs.lib;dbghelp.lib;opengl32.lib;%(AdditionalDependencies) + shlwapi.lib;comctl32.lib;uxtheme.lib;msimg32.lib;windowscodecs.lib;dbghelp.lib;opengl32.lib;dwmapi.lib;%(AdditionalDependencies) @@ -250,7 +292,7 @@ true true true - shlwapi.lib;comctl32.lib;uxtheme.lib;msimg32.lib;windowscodecs.lib;dbghelp.lib;opengl32.lib;%(AdditionalDependencies) + shlwapi.lib;comctl32.lib;uxtheme.lib;msimg32.lib;windowscodecs.lib;dbghelp.lib;opengl32.lib;dwmapi.lib;%(AdditionalDependencies) @@ -263,7 +305,7 @@ Windows true - shlwapi.lib;comctl32.lib;uxtheme.lib;msimg32.lib;windowscodecs.lib;dbghelp.lib;opengl32.lib;%(AdditionalDependencies) + shlwapi.lib;comctl32.lib;uxtheme.lib;msimg32.lib;windowscodecs.lib;dbghelp.lib;opengl32.lib;dwmapi.lib;%(AdditionalDependencies) @@ -280,7 +322,7 @@ true true true - shlwapi.lib;comctl32.lib;uxtheme.lib;msimg32.lib;windowscodecs.lib;dbghelp.lib;opengl32.lib;%(AdditionalDependencies) + shlwapi.lib;comctl32.lib;uxtheme.lib;msimg32.lib;windowscodecs.lib;dbghelp.lib;opengl32.lib;dwmapi.lib;%(AdditionalDependencies) diff --git a/panitent_test/main.c b/panitent_test/main.c index 9d77249..2b537c7 100644 --- a/panitent_test/main.c +++ b/panitent_test/main.c @@ -4,7 +4,7 @@ int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR szCmdLine, int nCmdShow) { - struct TestApplication* app = TestApplication_Create(); + TestApplication* app = (TestApplication*)TestApplication_Create(); return TestApplication_Run(app); } diff --git a/panitent_test/test_application.c b/panitent_test/test_application.c index 0c5209a..6130b82 100644 --- a/panitent_test/test_application.c +++ b/panitent_test/test_application.c @@ -2,8 +2,9 @@ #include "win32/window.h" #include "test_application.h" #include "test_window.h" +#include -void TestApplication_Init(struct TestApplication* app) +void TestApplication_Init(TestApplication* app) { Application_Init(&app->base); @@ -11,23 +12,24 @@ void TestApplication_Init(struct TestApplication* app) app->mainWindow = TestWindow_Create(app); } -struct TestApplication* TestApplication_Create() +TestApplication* TestApplication_Create() { - struct TestApplication* app = calloc(1, sizeof(struct TestApplication)); + TestApplication* pTestApplication = (TestApplication*)malloc(sizeof(struct TestApplication)); + memset(pTestApplication, 0, sizeof(TestApplication)); - if (app) + if (pTestApplication) { - TestApplication_Init(app); + TestApplication_Init(pTestApplication); } - return app; + return pTestApplication; } -int TestApplication_Run(struct TestApplication* app) +int TestApplication_Run(TestApplication* pTestApplication) { // Application_Run(app); - Window_CreateWindow((Window*)app->mainWindow, NULL); + Window_CreateWindow((Window*)pTestApplication->mainWindow, NULL); - return Application_Run(app); + return Application_Run(pTestApplication); } diff --git a/panitent_test/test_application.h b/panitent_test/test_application.h index e5bf8c4..a8e6684 100644 --- a/panitent_test/test_application.h +++ b/panitent_test/test_application.h @@ -6,10 +6,11 @@ #include "palette.h" +typedef struct TestApplication TestApplication; struct TestApplication { - struct Application base; + Application base; - struct TestWindow* mainWindow; + TestWindow* mainWindow; Palette* palette; }; diff --git a/panitent_test/test_window.c b/panitent_test/test_window.c index 9dede36..23ec0a9 100644 --- a/panitent_test/test_window.c +++ b/panitent_test/test_window.c @@ -19,14 +19,15 @@ LRESULT CALLBACK TestWindow_UserProc(struct Window*, HWND hWnd, UINT message, WP TestWindow* TestWindow_Create(struct Application* app) { - TestWindow* window = calloc(1, sizeof(TestWindow)); + TestWindow* pTestWindow = (TestWindow*)malloc(sizeof(TestWindow)); + memset(pTestWindow, 0, sizeof(TestApplication)); - if (window) + if (pTestWindow) { - TestWindow_Init(window, app); + TestWindow_Init(pTestWindow, app); } - return window; + return pTestWindow; } void TestWindow_Init(TestWindow* window, struct Application* app) diff --git a/res/appmeta.aps b/res/appmeta.aps new file mode 100644 index 0000000000000000000000000000000000000000..c83cfd0863c9df212b6653d6bf2f4d09243d0481 GIT binary patch literal 448 zcmZQzU|>)H;{X347|28cE7)-_# z=a&{Gr^Xbe7UUPl1Y~69p_6V-gDra#KqZ^@@@i1c8R3 z+H(SIya9tG3lr2LMKBE{9|IkW1W@&z1j``n1345`%_$5uDnOr2z)%OV8QClspc)Xy n7akzLtU*zO@C#5F4xRv62y+nbhAChHGB|)?i%^4bH%tux@V`al literal 0 HcmV?d00001 diff --git a/res/appmeta.rc b/res/appmeta.rc new file mode 100644 index 0000000..7df1e1e --- /dev/null +++ b/res/appmeta.rc @@ -0,0 +1,29 @@ +#include + +2 VERSIONINFO +FILEVERSION 1, 0, 0, 0 +PRODUCTVERSION 1, 0, 0, 0 +FILEOS VOS_NT +FILETYPE VFT_APP +FILEFLAGS VS_FF_PRERELEASE +BEGIN +BLOCK "StringFileInfo" +BEGIN +BLOCK "000004b0" +BEGIN +VALUE "CompanyName", "Aragajaga" +VALUE "FileDescription", "Panit.ent Graphics Editor" +VALUE "FileVersion", "1.0.0.0" +VALUE "InternalName", "panitent" +VALUE "LegalCopyright", "Copyright 2017-2021 Aragajaga" +VALUE "OriginalFilename", "panitent.exe" +VALUE "ProductName", "Panit.ent" +VALUE "ProductVersion", "1.0.0.0" +END +END + +BLOCK "VarFileInfo" +BEGIN +VALUE "Translation", 0x00, 1200 +END +END diff --git a/res/dockhostbg.bmp b/res/dockhostbg.bmp new file mode 100644 index 0000000000000000000000000000000000000000..9ce09d7b0697516e8643f812d1edfbbf527db7af GIT binary patch literal 205366 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 nkN^p+AkdRXm_Jq!rwtMy0TQ?tfk+-<{d~s4^ literal 0 HcmV?d00001 diff --git a/res/floatingglyphs.bmp b/res/floatingglyphs.bmp index 4f2d89025736ca96f3aa0c148e58a7284b934830..f3398c184b7ef5e3a1fc8f5f91501f6c0564fc43 100644 GIT binary patch literal 2176 zcmbVOOKw6j5KDK6?pSdIPQU?>=mM#C>dhY}2G>a@la`1VIpfb{&_m+#{j)1|ow1+r zeBs&S`6x&1rQEgn7bEiZMsR>0nZT@7#q<} zfy*ML&m^sYF5(r(5`-X8b`*|dWz3|eQ$p5OG*eq{RF6YlYJ!t+0uotA)%Yf}6R-=5 z2~}mBX{8E&AFzZjA2AU)B`kmt$I4_eq|AbuW)f5VeuGL)T-_CPbRfF`Ssekw1sRt1 zC{ypAlR0LqTtk-4L*?vV5WCBoE8#4fLvV@)Z7nW!&c4BXGge&^n2@An34YJUbyj*Am~Y O;oqG~ALCe?T&oR}2~oj>!*!Vv5q@piLo*gD8NcCe3oF*iIe-2|iyi;~ literal 0 HcmV?d00001 diff --git a/res/panitent.aps b/res/panitent.aps deleted file mode 100644 index 7a92b0a65cde170b7856eed081c774930b62523b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 766460 zcmeEP1$-38_unMAQ(OYUEx3DdElyinw0K*f6bhxZB~_rsCGPI-?(T|&Bm_bTP7Kff zzi;pMdO32*#em6HhpYaeh0LeSZ1^mz zAiUqLkubmvwr|PInX-QEf$iTN;Qp^au&fn2SgVmzS~N9-Bsl4q9iJ(T|2RIkVXZh22x-I#jE2P-nHWcoUhI> z_+!-71s?t*2vTQ)f`V{^UYI;q;NicZ4OphVn4>($in&U!yPn|PSnj@T*T1%F-&K@e zy&YV$6QXOpA)M&GI@6SCuh3O=U!D2k2L^@m>D8MaS8u=L-|G|2eo$y%CU&i^-gyfW z@aa#SQ0Tn{3B<1HrJMbG{TGspU!O&C@#_;v(5~IT=}#bmLVG2#_g6zXDw!NBh(D@n-vf5X84X%=zl=y9m-Nv={it+jroe zKfWK$&HcXk-TK1yQ{da5{~;Crt$tI~yC$TBHhT(s4tm2deet*oh-KS=K5m2{GfE6k zsPz;X6_s{eD-!xBFyKP&ihcuX`HH^<3V^H0Z^1nv`lpVD#|3@>&clBMEB+R^jmo}- zNla7v5f0N`I5bLI^c1!-pKgiABYE4JT(^ZodI^_{R}ZQUA*<3&4AT+$selB(#j-|} zz2t9EvGtW0JM;v>GYWW)d!$0!6dEl_z22y_wpkNfbckvT;~^6%t)bB&n~h`~Ura;&6 z19Tmupb(*5T}0}*R__g+2=3<|6VP3jWgmgcKrcX**@Vny*1{rW2`od_@a<8JPNa#1 zPDHSGM{W}$YCd^D-U9L#khg%m1>`LtZ-HuBfQ1b-Q8mdL>^4_E=^cdicpr5U)q&T6 zyyNzGt9)U5G+3icWL#-v0Eb(7^@gO(58`)TCJkuTb`Y4Z%f z$=8wsK;l$dHm|&6=4b4e?`592*tjSt~*z9L5YyW7*me zJ&$*+fK!UNZrZ5PEA)F=`xC2w(XLh`tbc{JQdCqtj|iOH#yO>yEnD7?i;H8sbm>xp zm~p~*aj$vf#!ZWQ_aDmk>N~i&S@V|ZxQUUrMvi#TfA-92tiPMR)ZfC-)s9u0K9$N+ky0)0Ge*svmZcXt-!UnZYG(x>03d#`@Qb|JUfMXR=y zD=I0`dwnUHu8Ql+#Cwtc9@YL9lt##d7bWtWj_X3uY>D{lE~_n5T0cO>m9<}W<;i+q zH2zgnPJQrSljhBu`A?lRp^SKse_t^kB-xU8Un(dt5Dk@?F z0|Qwc=15~F$wIO?QF(m1viiuJ>%(>7KdK<=+DQrH-uK|WGoOCJn%cOs7>6kD$#lOO zSMU7y-FM%;1P@*Xmc8Mkg#)8HFc-ksyH$C zb%2|L%)bMTgF;zUaz0oa9+bIU$$o*l<9BLAb5yP$Nb`}VTv4&92JUwQ$?EFFe=}#U zVNaj-tCBZS5vn)Gd($RQtiXHxyH>)3iw5*!ihFlsO2CJkg)gIszVaYAIG80KjEah? z2@k&2{&Uo3u&y>{T)d&?1$NkwL2UoNeb|ft{8b^Xu>O^ff32o1WCuqViSD%+H-jCT z@>+DwkAK4YGbD4)7&g4z(!rCRIB6=|tXXTcO;gf6R3>xqkCFct{|y;5oYg#I!Nq^Q z?R&Pja$mM{r>^YB3)irE7i>l8=~?lNgkdO!SoYg*zkLB5dvWE;6}Cx}CeLw?`g&g> z2@XLKJ|OhQ(YQyvH)G;LcHD^R#Dj!`8!IMz>+0OjFE>_bSN^xJ%BT4taw0vAq^l_p z*0^lGUMQIAz^bc5=T-$#ntuIuKUY#}UMA$ju9B6m>*Ub6CkB7WlMfz6sWXD^&IV*mR2 z0^8xEPVBEo&afB%HfDzo9?5R}Y^Nx_u>O*{a{NdC9zJ}?IypJ9ojZ3f?%1*8Qyk$= zb3l?U4@o{m*NERlJU4E{G>!+yj}-CXmder#& z8kf!231!R7e{0v`H1dxN*#7;$exan)qD)Dt8I6Zk@nN0#&(bmnbA}G=`ST%Cd64Q4 zeT(hL;loNoyqv3S2mQ8Pg-n@Cxi?#H?1KfItU*z!+!m~dakJ0xD@xKxjOM5=~q7c?0a_h?9Zy? zKNPM6`NnpH>d@j~PXP~7zu&z6GfoFX?;Psw!j2g=;$@5GO(eB!;zWX}qD)Q|^@X`Ml`Z&!CsJc~j}KCjBj!hWc%E;6@hmIwpOE)TW#v6SqZ;sFI?eIJXa1uxaMh|k?2;whs^dSZ0LO#FhYjK87z~p}@5bW#dCL|}ULXCEDR(@@ zuzEi;?94GtS@UL02JW|_2P<)kicOkC@Z(;qK_l4q6K1mAS8Zk?ixOt`7klQd_UHMJ z=AG|B{uHYk>C2jC<8-l@po~0tZr7Ka-ueyrJ}%9tHF^|Mnf^tx8gyM1 z57xMBzD~$jy#1=>!_%kJ*gk#6gAbdNK89=wxaS)wG9;I=LLWiSRmy*rq)XfdJ6c>zEuk*w{=S`F#_^u&2}4#DI*IP9HnED0n?SY`@t~;vjkGSX>(;K~c#hY< z^yuD=J+Nmtd*ZjB+19OFRjYsLZ5YUQ-1Y;j*s?XJe^I{d%68iQ3(H_Vp(_0g>Lblz z9j<8_mbEa~wMKhQhu<^;iTMv^iTLTj*FSS(T$9F4IQ@(6c^kHEoA&I{1IO9Rr%YMp z9xCjy!~clVBM-^;r#f)s-^-UT*#!$0(0s2}yhj<40}ocv$&hXaHfv-{^QRyMGaTy= zZ)!3Jq`4p2$>4d?$;#@a@u2AU=lM!j9|c#|eqAvZUdB5DGr9Ye<}HFA6tZg*dIxeG zueEK$kbKsYixI}r8a&+>#woW$8i}~K`0Cg(Ypmh6t3bc}V)<4&v3!Nua~|gQWD}Cg zmrJ_Knz{|my7el-e6LKYX|q=}?h)_t1Ri_?9;CHeSVR7+fAMm(X20C{@I@%bO=JUe`Oh;24-1h@V$z&o4$Xv4Q`vmv9aTz^15Rk^lc zw|JOXl`)Jt${Yi~De(pAK}7uY&u?1ng4xSBKI7LPe*XR>I|vxmy_+)o#oBG6v@}lA zvy~Le))(*bd5iPlh{5Aorcujsrg5v6V9pJ~G@5f}PM3N8o!08{+$BE$8u*p*V1?gQ zR^Fp@D7Uiqt3h={Njd)8qsp+&8!?2fJt%M9{)NJzo-G7 z@xEyY!;FTkVvOeoq5e&D@o)v6;^%y_`1EWgg|qYL&#&{iCz&ttAjX6;j`yZ(mowvk zf5Rv>%NM4ju{?lyu&Qy8?g@F%AL+&I&m@bh>RJsq$yRNWFwuu#f5!3D^Q{a^JV@hU ztHw+@&wmuAIO07@)4=?vB*uUI_$+LyT6+V)gAc)jZ}>3~{6{kP{}TVv%cIXVafA6C zc<&r|r_po7)jR(|=JZCsjj6!;bl}0`+_*=)*QmuKFPf14p}zTVGxDr=-s3CJC@5~i zEdMU9#cg;+w-Fc)ItNMv0cSX(FvY2Y{@*!Xr-B!wJ}(oA>1rBc{N99mgoDeIDD zW;=vLyhpCC^{?ymf?Cx6f1S!7{EPTLLKkjbU2-4c3Ls)v)K9dJ5YG(gETGy~n|pFH z{(lTosxBXlomrSKIwD=)>e4pAebR}14e|hGg2?8Rh{EomuRt_sle_wFfkEhgoScWO zrD7A(Kri{$+M;-E{=&tps`9AiJ<5~vuF4VaXoIF!l}N^W1CUO9{YJ*es!i6=?bFct zSNno>Oz@%AzY%OENrr?zTPY9zVNesP?)2d>m9};$HH%u@=#FP}K{StL zReCG43+BvX*R5H}5rrv^?tzGph)24C3JBBdqi3yd{_Q4`tomoY(FH}{h za5i0J*HvFcd7{i0Rg~Kfc9(JV4)955F{6sK)!m}GGvD9z5$5Eb>K>Bu{tL2I*|uqY zy%Ob#@~M4Zv>dO-cKS-aUWxLgyc;!Y6he4X3Ejdyf3*Ds8QVcxvKO=tv3&6-^-7c{ z)n8hnRm88rJ@)Y7!|dqMqsu7_s;Y1GRZ!rtlhU!+YjGd`1`tG#264-fYZ3Nla%;kN zrutI#iHUcjJ3lm>seVo6gLRO?%K7B zJ#^?0S0?6UHi)Y)4CQdMChKpqq$ND2dJ_?LR<|Zp)BBXCFn=CH`0ENjK!OE-9zxpM zIu18wSy!q_8p2~#h)9~-h#U0VS8(|4eR}rn`5KPwG|Dk<6d*x$eSu_X?4MC&d#ViN z+6(d?WgqlJ-F+?4D;4(5i~BQ%KGg7oa)NT~*4C#5+n<$i6W zKeYKpZQn!kxz31i!}$$a*e}XPulN^3BM~OPz`g5~21IG4xp$49ZGoM!=+|%l`QsSZ zhddoDF<&w!&ZO9ea0}Q^qusHOTCtz34I+{5ec|UDycfxe;%j}R!zrgekd>81 z`w0mPk4ezkM?q zYVj`d^DXS;`-%)0+7;u(1H6wOh?X5h4t!#a3--i`6C8#b8XB^zSFa}iA@abzx?t%4 zI(~BP1g!ay+Gs`R&LaH0xOEr%<1xW_gS~bJT*8|Fdtdm;kCW6VczfH^G^e?H_b&VW z_utcRPZ-!k##f>_JBOW)5rynI(;%`3CcP-iQGk6{m3H0L6+v|<9z1XW%kO36`+ZG( z!caQ%0(sF~UYb6%zA?0+`+s$9_b%?U$HxAPpA<%+grBmwcZHucXOD$)UnF-*_8r2| zJ}Oe>P?7=YomiX|aToq@v6pKEUa_NlCMG5vhSFIP7cXAqWRN;yXhZj(hMvFk&D4bE zB$L{;kH3t2OhNT-MK ziFL9xmU+%D$s7DN&E{c+!=M=|4qHgz@WDn?{Y;OFyc!9{!Q6XFwD?edcVg?>27S67xdE zEF|tCdO)^-RQx=8;4kiMAMJBiB3ncXf3jjDJD}fSZtu%v?0^0J$FrjQypB``{U>|u z*fEyQ;lepUgq5^@&W419us9o!=f|4rf+ix5a-7{v>qzX70VBEdf(G{+Q5OtVMxL*~ z_+)jRv6JsRv`&vaXr)$Fy#M#@JC~h1_k_$u>GAFyDJUpCM7XXv z+MxKkV#U|oT)3(;k0jTX#PCzFRs^|I{EVTh+FS~TLdIlX?pn>T=6?wEyJ$DEO%Zp0 zfse{BU%o8D&w7wUrS=~>HxPxh7f&4*@dmAjQC#Li=#?%aCEJY}c;cxVkoJLO#von-Yf7*_A8*F0sjZb+2IcV+eKCe~Qs++mUZeg~35JsXoz*@nIA>?fUOx=8ws4lsjKWvi~#@mdTzyd$xicN^&XH1>_ALalnIY^PYWx7aXtc zm&{0bEw%qh;-`V;d5&j)KJ=aFU1`53<^RKXdqr`Q{pVfcr`S98fvlN>B}V9xenTAz zKZ(c&Oc5Bt$yv?B(n|}m_J*PrIJz?lovWKq=)>QWFyxBAHJ26+J{U{Rgb-_@n{lBt$$r@Lm_{rmF zCGzOk673-Evcd6FS2qFjN@L*Ymo>sqRJa-N%9C*G$AjP2s2|a}H~spm&|D1mHB33p z+xc@IH!YQlpVUTDG4#TP3oQ2QQQu>GZ~m5@XdA^g@1a7vXlHt`5+}9)Na82GAD(9o zG|q|MlVnop9oaQ2J{HAM9~19C?+HKYRk4Pvn2v4D7(;$U-FXb%gD}Zi3e+am*-pxg zpLE{j{;z(jfRi+*@7lRLOW!8o@w7JbCy$+yzLk{~?L#8_EI+&p*{aCuWW~_x5OuN$o$9`1#Gw ztz2G`b9J0)$L-G(?^oje=RM*ly)VuoR75)}p|6qdmd8-RyWqYFNRbjsaWdnlI6l(% zTIlTT31g>{T*!VaHm9#I{Is^Vt}S-*uUa1%T3nxNM|>Y5OhzSXuY65hZ7@`7|L5yc z8(+Nt;{|vgtdT!__z8C9%Hj%VfAbhRbf`c_Dr4ik6a3WBh~jKIR;}7wQ~adY5Mk)| z`@ZHd^wQ}Q!uA>ozYT|@^jiqA6Lv}p>v?IXg_Q>RX`%U7)6 z^lxurr-H(Li1(lOjGy${(B;v5 zon+3Pcn2OsKNbEyRi3F>EBvJIUr}2%Z{C7EyzjRPvS)qZrxDzU`IG+qdEDm&n$p8J}?#K9%Xt&)Jo~_?nxu6NXYfTJ-AA zogt0;v}S^}gSVX2{v(N>KOPXsvZM#)`xHNaU%h;(D30!l^`C0(KkIA#{jE2v7=b+o z3QmYZyU-czJccgg+6nRM6_bmuYK5P)ZnSm%H{6-NWXnY7*o(`jHC;avpSRZ|cJeP- z7Yz06*GHwoZ_oFQy7+y1_oClLmh~h49mM^bV5rpoUzhre^?#}tjc@y4Z+j0u@Nf?H zkVRGa)`jG^FX(#zejBz!hn`&jY}~jVOPIN0#S=~*{ZR4q?AbtWocQ94A8Ui3)K(%4 z#dn2;yh&$@f6avj>qQFOn$FCzO!=w(3~PN{udM@(Rc9oJ_GQ|TOjU}!kfNn)tf{v(N>r1zw9em;Ca#4q#>7mE7;*N;%G zw_IbySbST|8%vW4-ySMB^A&*bpW!>7C`PjWThe>8sle0(#cJxkxh zl(hTgucgSJf)d3rl*$);ljtkKy9xHdFF^bcz{!V#`=a-z^LTVYok6lXvaS6WJ2&p- z%Hgq-Ket->^WB7@<-kzM`QBUo$HG91lb`?i`3RR6Z~w{LFOl7RDAtXrPc2z6m$MUc zwlb-}KVto-Qu%X8(o44Mk@IXH>sVG?{u~_jlEZfgHovo?;r;H6oc3e8tW(>0c zMEFU>ou@_pX1hc_Z{a7+$4RzavrKSC3UB|Zeb$m&gB1T>8IPY^H|(i1el{R>@~>PQ z4DE%7U$6gkO@-gTrg$TsUa0$|HP4=W#cJuxf}wBqfAPB1HZ0cvE2}z^NRq{G@W}OvYw9ej3h@H6R+rmn~Y$t-ZHz*MYtCw@IDw z)7I9uq2x}nSLK3)p@C%EL^k!}_M!jU*fY0~QhcpnziHDZtmGIf*(YSVrgNKw`!`9B zs1H{ihQ8JRWvOR%X~g<}Rqs#%N0E^0SF-tZ_J!grVdm=e44n^Rq$bK^wD2Bz)!`@Y zyai za(48)w{5QV{>?!VkJxVNI&8}cvpAnW!){>kUAF5F=hzmbr?ScyoMhgAREtKX{ayH7 z#-KgO#@-AxgfNu)OvSwXO=W{|EW~|Cnh29FND=!qL~bQQ zZ|11Ys1=41b_&07M?6A%@#@=2!_fZ^=hvyDu|((7IWOS8=}hN)79##E-IH?u(mABL zhi?k8u2_5F&$pxWAaxMAD&^0b;wGP7M*RH!U;HGy8SZ-qT%{WLUn^i}IO#hp!%%*0 z9(5Q4`mf_B{S@ng70|c!2k#oGt+3FS`0&VAw;)^nJ{ z66E=dSib)s&QDgy`-t$5a4>ld!_Mo$u+5YNXKB#9p2tT+gvnMN-wfvTr71|u-y@&$ z0#Tmi-Y0%y{;q_&ko>uW>cs0SxQaFKKdBhnafV#}tfcLi0YkBVrwrVLe8tfIK}DKJ zzBl}&vLSayz?Za{sHFN076lCD^qnj-U5jNFxbWCX^TW!{VIeuRnDn8vretqsAay=Z z_h_w%&c35(Kr28E?Fc)`X5jymQ^(6xz(8?4tZA6?0#Tmi-Y0$@6Ud)r|H-Y@k{#{4 zbOH>;T&v^E#oT_*jx!d~JdEbS8C#L`T(C6>szsGF+UL}a2{zG(z{P!V$ zVr}XfurmzhzYBdQimB4IfUFtF&Dj+jW8SV5ht!o}C!a}A1oS}|ix{?F9~G8t>}gLX z5rxSHn(hhH=)vy+lDpcqfI1#y509!JqP#$qC%O0a{WvOnDhP9u?x;^AsuP{*@va@s z`!qp>nN$bzFM~*~AlK0Pp9cMYoZuDe^?xtqMsrA#g=n4reSJT!FUscEXApevjxf|4 zMDlQ38EZprNwOa4Nw0!vY$Bq4MV_E^_utPsLzv~f@B>Ux_uft|AubQ2Ce@&MXXlg4OkXReAcxQy^Q2{aL`+&R;$oqi256Js~ybs9xfV>aL`+&R;$oqi256Js~ybs9x zfV>aL`+&R;$oqi256Js~ybs9xfV>aL`+&R;$oqi256Js~ybs9xfV>aL`+&R;$oqi2 z4}7S70H0pMmu=+qp|pWBehc%`QfBar7ffgPimzTV%N{&ndOdo?tSu;Dt{^_=v@IYb1njtTysE8SR`!>_%Dz;<4e$BLf{+wx+ zm&Y(UIm{?fcf>b^-?k7(Cn9}k)IlLPmuZ7=D?G=PV{0&;Y4`9U(-~>o6yw;SvNA5M zBFbxua);x3C@Mu-zMFGsv$m>eqql@CHXBpiSh|Fm3Rm^m}u&~wX`}aGXEiCMG=wqx@0+H}m#ZP~7{uvzOTPnz`0&sUmKP@vFC$p7Pj5go7L(;(o%*1)2s za{m8+g++3{`>i9ns<9{y3=@g8sGcZ24#<<=dd_SzPu+gym{5CUh-|jj)H@DA% z?Chb7uU#9p@XD15d#_xXb@a-W`QK+`EciJ!bwV*fvg-BgoqJxp)~@x_r_H-QdD5sUaD~CxJQg_6 z4Y;6)aBIjIG-h|g7%m_G8)lqtDEUXu|NmS3PqP1Q$iy+vpDS#7@}$|){QOQ!^78tv z2CuKm$e6l3HFfUdl$1sD5)+rrii=ydCN}nquVQ1jZHS86_C-X*_VwZ6J3a{s*}6C| zaO22;fOP``0zMfW7`S#|P|)f@!NDsAhlDKa9~!!3Ph6&5mqR8`fB90;ucV~2PGRA&lQ}t)k6gVvcUx-elH~~rtLDeX zem*NIYRk0n@ST%FLiSDy3_Q3rFz~k(0RhJs`}&?(;_dyXw(+y3Os|jT=K}zyPn> zU{2CeKL7t9=M8fH|FHRA0dwxf;Qzkhq0I#at&PC<$Fs8L9LdaF_H}adx{YyhTUSO# zemyHBTx4F52iiL$fNJqueQd7m+`l5=B&DlP-wx?8VZIAb{xBp$m(eaoHFha%I`CxBX z*L^B(ZeR8G@Ytr}?Y&XO*Y|T3fB!WqK|#y=1P3ox2@RdsH!^Z&zu4GGy;D*~_R7rc z-zg`jYZvGry56{<)CzMJ2J!&S6%;Uck?a57zg!^a{|}S@hk;jGLB~%r^s>8m71!tI zcTvyDnRF;4W68FJgmqs;NALP1EbND+K|#My_x1e;{I0Rs)6;aFhlkU4FE8J3yu8Bq zczGr4^zca8UH+QA`hwv|=VNK@08frf_RJ@xg2y6Nc!_5gL)*Y^YYbT=^Y>S1W; z*2BoisfV$#T~AX}%bw=u#>(dAy2_T8mzAxo&MI45|J}>h_Rn7S_Ug(G4nHb8Iep*D z)pc(#clTY&US41J^7Z|q7i5KA!NDK*3JY7%FDgo{cYOSWKI!R0AQyCRefMrlCDIpw z{~KeCKo##O*Z;pi9FX(>hsXbxSoiCIIleO1%Vs@&+G2NZ?x1h4Tv6MRoVt*o+#8W_ZVq@xqsT~{}}8bvHFN=?+}zVPSE(J8+?gwe`s!_V&N^baXu06S6{2SJ!X4 zdwOo|3K^kWVBpFwVPT7UL`2L~PDmJo^~AniZ{BRzEI(gyG~|Kd&>Oafj3ZzFd-pgX z=l>6n|Mh501pn{N&+qux_3LA{q^B=f6Cb~BMR@qW#Q_1o&-3!qobKvsIo`p+UCqHE ze6y=-`VT%nIe!NS-_eYWEHsRczHb&2^T<3p`l(4|4g9XLVyDyUH_#}ghRpmVZZ_+!h(oS0zbU7wst74gF5T#1|Sb_ zV1rv1Lqq#6CMM?HFsA?>T<&UP^G`QByWe{`IvyDSy}~dLkDVi+OHd68S~)5_eBMat z4SJ@h4;Yn~_mS$IJBn?2+W`6c|2x$a7(u?2)BA4&WBd*lAX|`0S((D6f&%5=v$Cdc zO-oz0Dkf&jypWJXGrhe}O?7oOn&{~0I?~1_Y!u}AH4YBf4|#drJO$o2ii&#R5Fh`< zD=DeSHz}#aHzA?SD=zMtTTD!uU1VgDQAo(6i++B0k2*PJPc}4+g$xi3-X}SL$AD1C z0U;a-u>cqlB1WO$c>w_z2oE}dI%;bZg>}TU9e@cR>FaxSFf?@RXJTSA#nRGjv7MdX zCr(b6Ho3X|v)$YKm)!vY`*wzeZ2mkl@?*8w*r{VvQu=kv&u>LGNTeGelH32h8(F{) z{Y_5qzYTowJGj7J&I>wo+xz$X{&VfxtUW0yYd*z1e_?R&FVnodE{t<_wi;x_)9jqlu1VlW@*1G|Eba{IpqmjymlyX603z0$z@A@}a} zzL1qQ_i$?JC!3?A_bdwv`eP>M`Qx3P9aL>?LkC-0CJX@Y_cb?({78?5CA8+s5-`Lq* z9j&Jqjd44;2l%{)wsuH&A$5b_RY;KiLx2$+k9QL6^7Iy!nm)9-uW9C2_|B3&>>mvTg z_}v}UO-n1J3&wLI!UB>FdO%Jf;&cbv+ClBX_pLQF0tQ0<|Jc|#@hcmft3NwCXG6ZY zea6S_BMo_j@JlV+n8ZCHt|EOtz!mb+#d-3S1~h-9%EsV@TI-|4RtrS zf(zc>k4^pk%bWrOU%LeczHtuROvOLfKJ;3Y4`yBtn@1>;`Iz(4DYOJ1K!ej%3zrAE&RMFjz+? z1~Pu!77L4vU!YR}&)?P!3Mw!K##n`iKL!QKrRC`824Xpg|zKk+{(q#Jm@i>79fs7g+@kK zmYbU1SY>9Gz1qSedll$oQ`74!&CG6m4VfNza_ge6Z-IGe=zW)%n8!Zx@x}h}@nxVF zesOWHy<=kDxJ5^o+lGgi8w3TtMjf8-wY4qVWNu#csgcp0HO9utYs}4ESHb>hjDy4X z?cCirwDtF2tQ-?Fwqt6l00YRDP%a15dmDU+{P16@(`)qUtdbHH`|RvB=TlO49gT`Q zzAYfYc!h_D*8~R#j`xS#+Qx$aqla2r#tyTvNE(IpyYUtlSI3x{Wlb_M$%THcV4J!5 z3S0=V#~K-HR@Gc|ADi?Okxf)AQz07nd6wEiKbk_4OkPQoe65U(O~+zmfBY&qSbLq9m+Jy2r`S=pwn^aq7?S^oWBgaOv`idh zX_-06!s5n6bMxE@CMNmgjE(P(H!`}fW?=ASrM`aI7Gt9q-Zf6?FCG*(3iAI*==w%kT3(xEX_-68-2C@3RaI(`JCwjE&Rh85?KJ z0bwqmI?>QDX}G?AY(HJyh%T51w1-{~b^$?y^zgsp*+^c6NvQxVvv1 z7Z|vFVS4(gJ-2T+I+K;foVk8oPW99V|0Q1g(B3t@sHlT(R@Q=3$;sP}L`MGgrJtYa zDmOR(IrjFM<85rNjIp&%8*giyIo{g(>Ub-w8<6>LVcfqv*}~%fG|2nY%*>0YnwXSK zF*15KMpyUcAPtQ-eJ)%m?{o3uvw<2K4~A)J-5IB=b90)3e%3-0ldCj;|HQ)L<~mEu zJjm~PTkY)fcG%hFZnw3~#`U!&rlu(quzm;GAdJ@bM(OLvEio~Qz(%FO)i z=lJ-;Ux$Qff8pijvDnEed77==jY+n)*RfA8d!n_~%}G{Pw_!(DFxArX{&Y*rM`~79 zMRTpJO6OWymCb|>4g6m;USGd-6l8#58XC`sT)y;txRz$|WL@3Ia}5pegNN^c_iuxT z3-&lTJUr^=_V_Pv??EQ6-ptJLX-(6iFo^^A3q~Y%VSkK-4iHWD@Q%fJ8 zBD;WqV*Ak0BHOUAC(sQ((189?-Q7KBgO$})tS6*S1HUgaHNCpb-24U+%FKqo@#ZLP z?Yt4%+8MwFzjgNZrU$&e|2`QOcEB?wW%;&8k2-K?hye!}In}uh{tNu}p}pf9j9>0g zp7hbl%3AqHYU=K9qoV%V91vi=!oxFswv*#E$o@B`+uP+#x3#@J19JB)8=J>7EiIqU z!km7-jZG=+eoE(C+mtS{wJn-&ZT)b%nc2OGMn(@N8W=o+Ed6+tj&|`F9i8Io`uatS zO^l1ySXeyXXk+u}JFF2L_wp*y4hni^hIK)k=;%`0n3xiqsHmqFSg(U^-~&y6|GTGv z16aq;!Ms0}<^@Ca^kQZh8>cP=uP!$?C;fl!8q7mhTUy>+XJvJBH)M%Jc6PVa?d|jb za&Wi<-Q(R0?(T(`Jv|FGe0&No!af0c-Xi_uCh-1jBO{u7WI}J5gSiD^$~|DqJ?I%9 zV7_vHimq-U<^~UD=<8o!ZDkd(&%?v?PppUPCnW4L&dQqb(XCqwOm;SR4k4XID5u)D z!H32h|AqS8d-bZheO}(&vsbTf`Y}HKmmQ&@`k(sxdM|QwOPlTFbYqs2WAG5C%X#k7=u=6##iWlym~4uy+tTgG7{*Oa$FyBFamIjFAd` ze&#opme;?B-14KHUCvJq4mm$MI_7+5Z+{)K2k9LWC+X`a!HysudWD=X!1G&eY@Y0} zwJioNl&m#1En8%0_!Kh0qv^W3PiN`rJ%QYD{DX_ji?cpHugt^3UVF#Il}DzgvI(#a zPDo2*<5E)IV2$o|WKz=0AgtRvVV?l>bPrC$P8jyPnY2&fBdixVF(7Zkty2;oHz>lH84YDong*8RYQ@HsA_8{he3AqOI zk!R2;zTR$aUB1rD?9~!u;}SJ}{l{2)cnrIPV%P=T-Ra~M^NXLK?WwS^6KCV%)_<0j zHLx)+`_q}ha;kM3JOlsB>D{!!EAalUXV2O><>f88n33`2PqDGT?+6Mq{?yCMf3d4; z+Cn$CbWp}ZSJ&%{U0iaZ3n*Cb;{14(i}SOU4vw!@*xSDX29zwvyc@DW-a^>hF2yr>d`mRUh37Z4l!8aBaf8u&jIIFN$% z`84>M85!k?X=$%uvr_`wy@#;h&Hv8M?mF=^_W2RdbNd8^`-Fqx2NVBeFHkVq64AOr zJ5U>7MQffQJ5aa{{QfvwU^?dgkQHta|8KRmee{i^<8xpDi@gHuI&Vhxr0Og_pYw@F`hp*4GJptii~^_1NlEuxKDuA>k<xQN{Sy*O zY$75ap2heNJH4wT_4J}>jOXqD$>*_v+b@Xo0QmhvyifK7JkeSL@FS=<_6$#f9r0ol zldQE^!`uYj#x}_Quq}AL-pcCPDs%H<%vtV^G&Ibb42<{~`h``NmUlk0w!Q)T;9%?- z(mjZC4VNY+&gx!J(1Fetq(fJ~m6c8Rymf1fR!Yi|pTokmH@mqxLq3n1Vq%gs%fcdMA#`NW z?`2{P&s+;#z;5iP`N7}+;qO5~#V0Xt|Lx~j1|44UZ|?37jyOBt+GB5j?Q<)ul!e$6 zJm1!q8~@7VkM zmpBFml{kci6j{TL*B~I^{%LpjJKtGbXN|-@{*N>?g0M#*lg za8`#F*5-_GevfHX)HAd2@aM+C!DadZ0Z%l2eea*aI^C~sZn?W4?_*w&xBwV{F+Yh2 z^MORj{s~wsNSJA15jWe+ED<*Rnc(Sctkc~(>F9XpoU?P`WmngG+8!SFwLLxWYkGMV zp7-!5_{+)Z);2~pANDmE-&|aFFVanRtVc0Je1{?^(nE|2LFAz%mg}M`g z_k|cBzya_+$N&610DMn-jj`u2koFmjhV9QR*d+o7a$t*?hqZ*9Vc2WXQC~l12Jq|$ zPtP2jYjXP}&MP_z`-DGyeKYoZd0Ef)@i{UwDr(KBt5=6N$~j*Zhb|iY9*Z^h81Q?{WPSbUiP*zGUPmWtw3b%H zNKMU%@tT^E(_w2rM_W5~v94~y63~2%^)oJCjvaaad}N<9XF_|PITNgM?pz2_uk+_a zdtA5>(v1l6K(|YmLVJLEUcMZrEF=mGiEtnk@<1qXAPnb-gaHRc)EV)R86t-2=p+u( z(MiGDVoGm4y=0srn$QVrhR|)Mp9~1NZ4n=T&m}eWp-WoYBZrif$7XSHdH)0ld(ZLm zQXd=^_W6Xw#K}D$K5Ql56Ii!%K^|dVDyN2PgC}U;tY^;#LiXPt^5DUr);T$57m||v z)WgFwzV-3Bx!J+t<~9e1dzjxo`Oew7=ua=NQrO6sVV_(n#{1_ux9fFEdV0BVPaVsj z>6MHGy-F|>cxv; zgTccC!OQ*5pAYVL?p#nGkjmM!!769Y1gV@p9n|;CnGj$=urjXUhYUVk;`=gkYPdFd`Qn9Q(xXRe;SV0{_bV*C zWSO1qd?6(z=8veToF9XNZhZya7S^^N?RRr4I^^b7`lqL73HH+zn*|4#ct%D($2#5X zWUS34VlN%HPKPx+TC-0AE`*S+PDI3G=mH9k*x6+*)YFR}4*nViJ|77h0l9rR_?>9j z#fzcCz^_9=Lx|3w4ri^k44|<{=J%Arw5Qlk0l&Y3i z$`Bo$^Z|N$>AfNMcf%foo<>G-)1eDF=bDy`5Mu8-p>KcpPIj$MeI%Z*;8+ z0^f%b-;4MkVIBhp0|R*Kd+uDYD)@P=u5QvH=p#=;7YQ5wd_CwHur6>{ANvDzfHxOC zJno!;o)BrWSCbr17yucf4=_aq)Ek6(12=b|`9pW$iZXocQBEALrIn7e!!j`TXY_zQ zP#2s<)*pI`rP%Wb-N$Xq`1r?B+1bz1^7CJ$6%;%R&B-Y;i;us9^FjlrxVs-85EQg^ zQexs9m76!aFmx`6@XTH**}@P0TEH|~SyiT8OT-seeN z1^^Cl{67HvI^gWtkWZkOJ7#B>ebLJ+A9Bb&*aJWEh>3aP858pqI)|rJwpCEjLjzym zd*|KU^3`o@au#Z8#&P#IYS#uu&`-&!VcOsp)>fQzb0^zfz52CzX66Nh^mI?H^z_s- zDJi+95)yL%h>XnpF);A<9uJTFtyqs+2b*5(tIOVm{dIdSEpPu~YkT)^d;5py9UY%& zxw<~q_w;;d;O+gu0O#sz;Y_bH?(Vmsv(H+Mv09bJR`7cT{wFy=3S0{($gPb3=O>ndkOEsuCB+2 z1P1ON9v{DG^o<+Jiy;3m!rmV_)vgV4(MRRf0BwLZzl5TqmZn#(EHX$+I-rq|pnoUw(@x<^D5MI|M@NXGg@YFb)3ca{%*R{-He=pTZie{g^-py%s*_gC!q zTco8GuYCG6$pAs5L+FJupLn12hcmRblht%}uV7za7VLj+3^Ope+7q@u-AqhkalS$9 zGF#h>Z(#3uHaPgc9i1PVnfW~8`t|1_*RK~_B_uq6TtNFF-21t@o){4j@Xh$RxTT}A zvsJp{yJ4NM|3^->Yl92me>v4}8=Oa*{)2D$;TvAv4U&`BT!@MJ`JeD`>)%5|V~>S~ z=KT>Jo%csR4gj1F#Md zJRUl_9oU!uyS;s$KE4&`5guL=o0#}Ijn4Cf4l)5c$Rzj~*fW@cy+Tot2i&m+Y2fEq z_$zFTm(jUB;P>8V&IDo&A*dh5e_&Ij8uSAT4GphhZ-4F_L&ICxFPw{SkY4L(V307; z(lUOxtLxSM9v<0$K*yj5eWn$@Q{aF-2<|B(U0Rhg3e0@_7`uP?7 z7#e!>=ZJ`Fhk}Ez?D6%@+~)3{{u%cFEU~sugq>dURP3wAnfuAAnwm+fmoF!PzvCg> z6R!t@LWpR*C!Qw0=ZWWk@_7v4^#GJs8U~R35B**6i1X*eH$oo|S>vXryZb$y-%}J2 z5%D4{GV)Dmc=($zoZlT06;®reJl@QU^d5p<8GPCh=*T>br@dt%R! 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�KM@918{HI)$WP<3&sSF^H8QapeD zCclEh(G@f_jQ{>m!Qvf!k1Mcp)vDF}GBS$0R8{q)&YiQDGBu5ox_Prm!q2Z$41K&} z0Ri3Mfqv=G(4pNC5ziIw#*Hf9OZuRabnlaLV&W&oJ9j4J;djd*b|;Q{TS?>zL{UR0 zj9j6Jn_H(Cc@DZ)Ixuip2DSm@`62k~=>LE3l9e?TiZ1dr69hOk50)pcMV|B=W~CMHf_PDvq7B_t4trPO}Y=^XvA`C8oe(wkVAMcHc9#Ke$AKMQ-3G7eG!CnTYUjXM1 zf%z_B)C35jFF?%IwOPW$qg4`fex!gG8Gru=;D{lqz`#cbLqnflx^w5116dvZEv5eMc-g{Rn=}mM z2M<&%^b0KhU^tcEG{1R!Q3EI z{Qtd9PHmVARz~LK$y%X#4j_*ZvUYHxXK(@cUmw55|Ga$wy8yL^b!0ulLhMgHH1v&H zSlGJ*p`q{g1O&X|adR8o3Xb5peY<FlkiZ^Zq?Y6W`7j|^4<#KRn#caNI=zJ5ldfwg-7|_SRQeVFmJ;D`aUErTK0LcMg;h*G)wdfo8L*{!1 zNiHD!3m15Rj3Iz8$}kt8h2#KP~sC_@F6YO`>5|}Avr+Y&8?Lba7hc|0?op(Es$q#6m)cK z+3)JwbJEA>;q~C)VTU%JJ|zj8}Rcy&8Ie#HLHo#)13VZ+BgJRb0& z4hQo<%QqMpRG=5IawF{b1)1jpk`sXOc^d%OpXY!D9>Do|{4e+cBo9DVfdk0j!R&zs z_$|F+(D$Mq9*_B4T?gsWYp@!*MqcpQ`N+sY!@GB%SYlogYK&f94GZg5G&4(}*3vRv zB_pG@gprX0vqBc@0l!y3{hQr47eBw?Aw9kGTGy`G9JRBH&~$O3D0_RiA`jNL+ugk% zHMTBYngY^#cMPU4oshgKLqswe4UrG^|4nUd6_1*DAJLzh1S)z@Tcgp<&e~Bcm$x z3|2zsk0duN$olKo%Sj>o2mkZ~U?V{P&kN#)^d1y^S8s^ zzH<$7i&VH)ouHsT+vMaYw=*-Jdr>Gay)f4dvjiRj+32K=jn7GU_mmSsL3N0K^((?I2kswhhlh{oB2I4r zU;k!i=2&1s!FVF#@CA*H(>17{uP0-3=%uSh+^)Q??qdOJYLkkK$H=-m$Hc^Ervd^V zpqHkZ0~qJAv1#B&-VJ!K=diG-V?+Fo)zq|>@y3m6dLyH1mK!%}xKV2_U}n}JW@*_h z4%r1=&tq)dz@V>Rvjw={aPeZ<<||jqN!fJuYQ=`DSIgIt0()SczJA39$VLN$%72y( z*RNM#e!#pSW>AS-;g=brupP)*qL?Q_`VrN%mX<9$t*zU4*x0mhv9W1ehj{{PkS~Po z(q)#I_$08f@a3J#$~SS<)o){}sz$xCvmc*!bPM z=2w7*fq_v~SNHU3b92Yb9v-omf`SS!-nr9_TKY%o!NHG?Mn=9o8z2AfQhfXv=5XErvjA+gSi zBMXEHcxN;;tfnUgI-FKtzj6mKyd7BHdgV&l)=QU4w?H;ux>UCL;>FU<7cP|IT=^E@ ze;H0IQ_zgiw{!pzfbh8%f`c8Ry^oxl*wKJg^ym_YbMubnRN@M*QDD_@D}QK{fh&E4LUK)uP|9oz~X2 zi`vevYb)&j_2@C!Xk*jJi&)5+h=?I_etCXJ$3*?Wz=ygA4<-w{y2fD-Jv;60UQBcR zxD)m!JF!wgKngj)_22Smzj$qnE3kUanzd3&N@_>1T{AcE_Krr6O_5nzT9*mzS;Mrn zCl})5UjYAa&Z0i&%DsEzH(>wUB_)k|V`g3y`sy=_ie?H+OF#c1pNf#DE3T^gm=F7( zoVO7I``;Gx_B4<$V*=huj4xoG4tOKKr-cCbl_c)}#6MmyZ~*js#e!_Te7TGkc;0Je z)p{E7NYwK`az-7H8{!O}$;mG~&>!FmzHy9=eTMgj&tgB3J+R}cNDhDv0WO&L8Rq>4 zye54IQr6@3MjYwQ%vu<&t-FB#?(O#W-5XI4v=+0-wj)j>R^v^JltlZpGBck2|l#{-vt~1$Y0ne*LD!Cv0&AzU39zPDjTmr>Uu@ zbK{1SVL(7K;@Pzp+1dT(xw(&xGculBhVQP296#z{UZcPEm0o!Gi>rtMTA{BFI2%PT z-2`%UAMRjgPCR;SlVBGlAvSGb5_i1Zi)G;^r)(;BGwMt9tu%u9i%(JtHjMCB`!v}bx z7ZdaJF!FKAh{MaHuI~`~>(Ddv#2_^Eg-uk{o11s;ymOC_AN5X5eB+&z^x6w^b&;!k zZWR?Ze8I=(!5-*pTIg2bob=QGj(^hj|B3@(3sf%P|4%yrn4h-;@cSfQd2igPKj-E3 zz&1MiNoZEq8}#ju71h^Ip*LWhbd4-kJIb4`!-*dnXfWYe(%_mH&0imhFpA*y0L&t1E!n zUdy>8B;@6EbPSGKTZbX;S7w@)_P~tP{h65q@b?GL-@QA87{HJ^`sCy=gM;7Ev7H?= zH^rQsdet!7@3gP)(<|uxH$ol%4dm@1j||XPcgEYh{}5(o?1J8>1GZ>@U+e`qkNtn( z0AT*V9{Im@R7ISBG~6XFl~ zh(90?`OZBt@r6Em4n)n&nl@gzK-vKFF~v>L`6Twq_#vOAWt*VAeJ^HxJm7M69$;{C zy1xmvKJfh;IGmgsq>;Zoj(S6fjEq;Ym>)`MYnv|X>YC1LYa0(KDS2%a7dIqeX_>uF zSNG;R6_rb?#l@9=rJ~yTt$y~4*R{9;t2S@m%8xyK_MJRwdDPxM;ao^a12F&CC?n&s zeo9LJg}ZnAF$cRJv$gurN8c-b`*sIrYd6zj?j{}Z!hsnWB6fB?dmJ76+2Zv^!NgObQ0ZUo*pfdjSz z`=lL!SVWBg;sX*lZ}y?jVMxr~eHgVqLzrpUx7x<0ncc~$>2P4+efT{OO%P-7Mm#eD z`xL~X2O*`telo7Ia?~v=Yvcs_A?S>a)7E1Tu}x}f*Vao)9#~3EO~3e*Ev~?~x&j-q zZ?B-DqK*RQd22a1q+?EZ2m0NfV|L!7iwOyRr($AykA{c$s09Rc?L+^M_^n$lsNb3I zt7k>ro!;EMmEOdpnf}I&Cgkm^b|S_ODJO;0_avrC+%L!i=D$A9#{&Nw4j}FSZNNYM zwQE%eV3S`*O^{1m+{>7(tg-yEvYGO#s?TK=6`w0Gzq_iYX094Dy{j4;K9Rk`@%j1h zT;YqHMXZqH`t_PE=g*fiUb|MqapOiKKkR);=y^HJ55~-pVObxa=cr45A>iRLOa(qe ztxqHN2fA+Vb3S*x!iUzkIWiQOcGxXFRrP zY8q_bz5CD#dU}>`^|N2RuJ3#WkoTizW8)J%aNy)V>~W=HXP1p>zCb*zNqt=ONEFV1Zda9&vH6!&6hoVly%(6SJ~Dq);f+ z8R!|xC@!8#D=wah!>ka$l$4j|$Pp+zICL>xyH>|xWYi>VVbLW8|9&6ldEwrkpvPzA zP#<$&#hpxVVJ* z_U}Kn=fVXqCG6vK5Ow;%!ehkx`Y}(l8+Cu32Ql+!zprlx^kch>yL%gEY_*|&mzN!xu$4=JqnV8g)^?OXH=V3H3s3ti9djeN12tBx9Uj8{2@ZWF%X-^>b zUqoPrho?FoW6ffUTiZe&Mn5c9K|S>P8i!G0#|=;$mgI_S*J+R<0vw$sd{ zWrwj*Bek(XJ@t+2b<~E}s&^V)t)ex)Ry{8_u2#|-U9F@u#L?hN1?}}K6*T%+Q4>H4 zY6GrbsUY<~84Dnf*n6}_6nT9G)aW0CeXowWo$5Dl_9$a+FXE7GWWNBorJmWuq>b0w zx=+~2>4~(v`y0#~ozP84_z3LJ>K7DzzDS{bK8pE+nCg1R(YZJP&mB!k# zjmpZbb+e^$%O(q>=1t~?%~WP4t#syA9n9u7olGXyoy;b7JgGO8<=%gEe4 z*%!oYXjqSXXktb@5`9EXh)uR)Z_pmh^de^wJ;&_wQS|Xos$h=a3C#2Y?ul!Kg~a*X z+|L?uag*Y{zAv|<_Gi_N8@0Q^gEmP?PZMftMr-=}CtHC3hJk^}r26`|==UAwM=W%e zrDf$-%txSgacN@2TqG9ENrV3r!FKMP9h0i+iS2xR!ax4@+i&0bqqcba-{1=Tym2Gd z@7&zd>-X+GwOK>s*0!@}Q)$sR!wMgs2RUzk^ytoG9!DouPVgu%xVDB^FeOmPJ z(OOy6ArDxKd>|=xJFPA1w_2MuY_c_O++<_Yyw%FOo5uX+LoQ?QC*p>|FVxHv-(IxK z8`rlfnli91{b*=e`B_;r^#!k}Uo+1xuUbw)uR2x%_ZlWamug1gTh;X9&QQU^>EqR^aAhw0*;OY z=-+=V;_CVgxPOJ2CGRnF@PkxH$gE;a3~@L)nK+S^MV!gaC5~fH(*4-8fXl^Ycon(N z7kZKuFzdt-dj+S|)xEE|e}A%haB!-ouWu5ye{YPC4`4wbGx`CmcKo6Hnb9ka{SZqy z%*~_NFI;e7I&?^nnvYN9$0bV^dw{>e=l{FDB+RAxWy=3 zbSi!QYV31Y1N$AAavtWY6FyMPya&KQ?e>>*iY(pRKW)stDa?b7= zOs0ZUK-K~<2%B_-it6RmqP`Opm?Zi1omr8o> zo0at9PE|~bj@4|s_H{hh9a=DFuwN$FePmyh_gjTX|9A2s0i*lK`QkxA@8yF-CKMtg zKgvf&&h3qgA{67|2=#mSh{Lb}zyoutckh0X397Z%B}i6=c3v*PGtA$ z9joj=d$j)fCB5orSI?C`ymq@eipJtnLAlNf9SZdcdm%ATH&j-yxUEMof>=zH=)Z4X)N6JJ|9g}mR? zovNyF>{0SsB{+DH9x>6KUvz&ntFLb>XHZZF_BJB%Pul;a{wMJ-x`_Yp8*1O=-T(UQ zlAmd5*?$uf+P_p@{@RM8M+4Sexl%yRz}RSNT7kN^8sNTmJMd3pf0Mmk^=3z#nyn5t zbu9L`TIKAc9$s@Uf9vn{bS~WMC2{?H%X4jw(q0{%^8RDT%KCJ5%Z5&!DjU|*E9yIO zA|D)(+oz+Gd0$&Qt^3H4oYA|m3|$jj?m$H8%{hJ^(_KfOc6 zjvaQTR8%%4TenyjZQf{^vu&$&0TrE1J2j{6o9$c{Gn~6Tro^No-!O9Cdbpj&t{y*M zxMRCR!FDS9;%%Gk3b(Aa&D*lh27KXI!nVt!Nm4g-K*u-zopo*Poaf_C!u4T0Vb)MZ zoXpDnC>I_+dI0-)T#bl$hW_Va+k5w(dQvE_f{KdX2BL>Ku&nGuaB1n3S3$wpjpXFl za_EI%c5`c^b8~BAC3Sy5KpXJi&gJP@iXNgU_VefMN&Hg_2#77(|KB76zVC}#vSiuM zbaY(53JEDMQ&70RMprjFLc|pL+jSP zr&hO`CIj7{5npOb16qql6a_Us+;E5B3eaDaI zfe&)#@lSGqj!yRdqeoME4<3x{+_x{NT~gAwMOfGq`royljm^1w=T6(QEn6&$HgC2p z+O)xfvSGbx)^-}3hV2~IuQqWQecH%o_-Pxb*(|rX8zC+iOi=S#5bIcsK2xz;kL;ke zYonueu3%wtt!88MsAFgMtY@IPUA}#jL&1(s_C+)_j%D29Ud_ta;)nF_m5tl9)DuR{ z#h>+Z?u}ndhIaRdgoaRmprbGf^haGIM({(*nJtpWm~KQGPz{JydFE#5u){(hpP z<6bHvqPj^%#ccc8v$0gzGlQDk6ARcE^6=~u_4mFn671Qx%iXJUpJjB9i9_9(pUV&t z;P#a8^?FYD`Mo3p178qkX6>VzngzW_j+8*Zm)?i|AJo$;f1;;XOzM7Mp91V>Lo$H< zjQhH}S>S}MzJmv&x@2Yj+hk;XS_A}K>gnig%QtPZC|tMBJb(QLi^2`-%(FLcG|lA@ z^c>>e?KZcK!{n2^LeiY{zSvKjSdGV4F7(*dG{kP$sUWq)d`CP}E~$Ms_1Mz~$}T$>inL z0^Q%v`FH#mllUj|e~ifgZ{y|N_0wYf|64@BcYRIRn`-&53=F)RrKOLsYHK^PUA>yj zX=~fO%gwD<(#QM$o-n@w+3t@a7%@&2LSDR4QtTD-3zrn0v{aVw^bsJ3cck8}xv6g*}j%;rFxo`?muBq_lCmxfOAmnufET zJ!?zW|83{y7W`qc{^z^K+P8S~KcMz^1q+K1n}Wh=fs-fQ1&xifG1scH_@A$h>XrGU-YIE3}g8EeJ^2ack?nt!qW|64@B zcYaOK|LeK9#6;EAuS#CJ6tLUQu0$c&|B+^L%o{bogr^7eGJ4dMihI-#6!oa8=5=4W zQuoTutslDdG4TH!y8jj7;qhcvPp@R?&>;%qc_sG`A1)?!|DzKpN(K)fzK3{!LXWm~ z27GVzd zrE|L^CA0BfdMiJFN~4faYLkda25_JGPx6HiL_{dPm23ka?}-R{y)QL#a&J=fxMWoL2!BAp00Z_iWZPEw{k_F#P@z$Se5S4I`?mnO$01h4-QVd$hEQ z2Tq)*f1;yP(x;`B)N}Z7Qt#ozDZQGSY26wcsgUF@#Q$LU}CItK#L$ zuao$P?Qfd5W|eW;hV7QE>(~s49Xz%K4Zj1ig3e%?f$jF>e#z`lsxl>$@)9|3_ljkW z$cSb>mJrSECpkb=G`CemB&QYny+ueUtNDv$HUsa-Eo9=o3`jcO|C$57ew_tg_>&7@ z8|1eM^5wTM(Z$tnrE)E&7IdoTJn7aa>J|80EFolsCnn$_lb=_cJg|QX`+n#oCO$lq zm^g4IJiJ!H);5M+PtS}&K|ympGc)@)_~|cR(s#ZBr2kJRD7Z)d#0hgvJF5ikAfJ{) zwh4m=)pGA2RG@UL9?a=EbSM|{1^=f`RX@R&x3d`aHShZy8FjrqbSMk&=a9I+kN97= zo?i89J-vq4;Da7b&1~R4y@$m8$&-157}@{i`t{lu7{B@W@Zr=RG9I9-o7qQ0V^_L# zseUG~PvW1ldX;g?S}M!FRZIrNHf~#D2fr=BClE+z>@6nL_7oEbq;rV|-AeA>&hArD$m@c> zhs~egOA7pdo>o#w7frg=~DBEnpz&tr*~;+s^pQ2OYbL)*q93pd`n!u-1t;Y?Oq3Tf6wvbnf-Kh4rNQ0T+dv+ z`V0QotTatqyV;^)C9@H+l*WMI=MN%|>?t46jTzkT?p;&t1LR{Ost_yaD6@kONHbrK58!|LwOg_+P!+jIw66dG6{p zW?4(O89wLWawD{5%LxtH5<+!P{=6K-@e2=72xV!K19GP1#Is*YiDo^L5Y4$S3f%rz zcEA_>lN><$3S=B`K}bIMBBbs|%%GW`J{YwKB>w#?XlVngcz6;U@m`y#2&GL-Ag@(` zJ-eBQE3rxXKwQUZ`;b2IkucAD>R}LN{WDeR$pf z$=?3{%;n29?~o51qM>mp1O78st~AXh@xN-78D;4*gL~XOAzgav6~rOwJVIGA2bj+z z7O?*X|F8vQzHoq|WbVXX@thHGz@V5&b~lObf9(T&@eLL@;P2R9;D8+He#$&2u(C$i zk=UoB3#`Jif`K8lMo=)lg~UI2g0vsNA-S!)1SzfDY?)1LEQw9RGI4E3FGn|CvhhgS zeL_cnn*guKkIR=W{{}z)#Y#t`d@;`fUw8nv zgb<~bmouk@l_{l_izmE>N6IpqR(${QpO-HGeeqFST!C+G1=g+KzC&@JZ^B{C&d(Ye z#m^NL6Wf%Ok~)->;=loM(CsM?4;{*W3e3MyRV{dS=urL;;sFoT)CwSlL+a`Uuhi9x zJ|gE%n43QU*547v#t(>7rwUPjQv%%A;#?E3-!_l^u&_RS-vxdsBh=M%iK9pJiL+-* z32JJm%HMwbg8$!Fm}al|-8fy0&HtIUTr_d^SRr9%@{lk!c|x2!2_8@^B$Oo5A!)Gv zvxtLx5et;cA%t0ziT%5?rj4#uzjt@(cze^S{l$qBRSyIOv)h=MQW}s8Xb~6xM;=(f zKk4T$*a6Vz^RYvA)Cexa6r=NcQdAF3I-F~IYSE{PT&lMDsI}{XB zI~5hvx|EdCyAB-4>QYwDgnrK;?R?-pXBa;IJ2kbU8N>rWsH>OFs;id}8XAQJxPZ{q zEGG2yYKUXUs)!>;N(qbuAl%$)fc+-e{q5j_4#L*9kvMSzF?_XrLR-5KuM0mPJ(~G` z^Jc5UB}|7fpD;_CI$ybiGTiTYI-|$`Evab3rlJfY=LHR@m%Ei|IPyo9I$|Y zQuhl9WwbIfhStF6|3mltS28k&)CdWsz?R4Y{?ox1q>wp-`FtX-ubt(0$iL*ss zUA>UBB|d9u6?{H-uJE&=VcBN`gNjdQ&lXQ3A2^|=mOgs;aN5Y>!->PXy77aGia{N# zR+;85TV|5|`*P#Vm8;BhScD=*xTF(ixWw*^@r%Sv3UK>Ri?F(UlH>Q9JGwuKxO%RE zxMA>sxOldQkdr7PeB7G;8TS9-dq2PXuUJ`An*;?jF=ycK_$RsGYYrgs&&!+CL{A@7 zO-D!SebV+1s3P+Kq;2peKKLa^@CW|?;D9vXpOkdi0GYskb{j8mGUA8fh#!Vk?x2ot zq}~zJ$It3NAjo|sVEsBKj&J2zSiFw!YXvrJ*nx4Mff=&0MbCiyE_wO1PC2=Z&OLiF z+V}0tf(?*Oasc=stMkBt+@AgWb6^jUwEzV#4;?B(FHJLi_&3n!gX0<+nuy!4tn6ABh z{hF6A2mY6vW-lXkKb?Ck@7|=3?4q&btfDdRSwv&svx>#OXBUnhwAVML#Sm~i2g z7-uXYC*U(8$gOMn+i$Dad|yxB;@$pVuYj1?Rm;753m(bKQ~H7X4jGw@HU))TvUg|b z_%nKWiY@)GatRxpeID{g{xHvb|H=jc50L&l>V2Z?7w}K&ezN`t_B`waQvWZ+3ch|lUr+Rh-p_2~<4Yp35C6dz z*!L}`p$Tr_;Yqp=z8w(~E}f;OrVy-jDa3B!yM(O7?HcMGqO$+j&&1;Y|2|fLo?b|F z&z|^NIl1CtIl0VEDXEM$#NztUvNoEORQ4%2xS7C~J_NS*o4s(M{I!Y-<-USK4*UVq zF34H}iB z)YPINkzYwfm)fDQw$o=4hxvtkI)DAeC}Y`@8>!o9TwD0|CVu1)zcW6Me=!_k2ar6# zPKpS4KqPvMRV3#97Phc=(n@g?@87@wZ`J?j&!36e*-!YnSt2QE;2k@AT9c5_f3yKe z{F6RBD{EvOiTf{k|Da0P@#r1MZ4ngALR~SL_gmnAh2wn85a+Yofb%8>hHzj%2);uQ z>IOo~SXdIe_UtM8C?ZlsP*G(OTejp7+qUP;F)-%ON{iXDR8Z`0Ds8;FiB{!mp#A!=(WL|$GwV)yj~#{EEQ z=Z+jH7?PFEXi-$m>VWQia{PGn$J3|V5W8!iMxS5*kt12?@5w@+Unc5*($E8#4D2UC z5_+Kj|G<0{@`e$x2O^L+3`b31SgY2N*ujg2S#xY$UfnPncoE)pq=CZe*TOm z0RghFU?Gl>+eY>ka&jg$GBCu{(9uO9b`TBUA+~~^KCY61;obl*c?BP*`1Xuw^IF|JRFPv^e?{xY;3aoWn@xXWn~M7 zBqdW@m6Wo3LPAQWs;l#el9B?VxVV5QDk?>;t^wGuCol$ZZ2x{T4v^giAOEF}PSc#8 zUNdp+S~qg{4ddF{6!h=pqMw)2M{)p(f3nvHJ^*3`QK%UTM*bkU^Uxu3&Q~NjAiVRS zN@$Dbk(eR9ODS`wuA~t=b~@Mo^tWqwH*LFF%PVtt8oGaS9{2OuC-MJx4j^BXHUNqL zO~C)v80pFWME{JeS(=e zZ{8MQV8|L~Vo9msXODj%%NI$A3EJhXSh=10FaIzu-r)c03M^Z;a+Q?S%`n)ZPxkD| z?2?ekXfrp@AE~IwCrV2Th(hdtQCyslBbm2v`h5O;^&4QnTTwA@;K-4hiQ~sxFh8Ri zIr|3q{bcP<{S@-|1ILc#1OIvR_&;(a1N}Zp=oJj@R95!yP*(QqAn~uNN@70(*bi$} z*NlFsb2fQS?@9`B=(SpPT)8J9++gGmlzylCOJf6 zClD8$1P4rPVhSBUcQyON=h<=S_vv~4J~J~zEC{LZ7jQp5K2DH+!n=2);DzGGp@nWLe}0oEymoE%vj ziF&5<{R9&eX%D0m^z<2T+1N9iSlH4l1Uc^w$O}dh{M?qwOP8)%j0^s+BIK|A&)C@& z4oXX>wC&lGHy|dK)~c?a-ItnL^r^BkZyx(3{>#epiR|p6&lfHfKbMoE^eZbDKZm}X zA$2~9{bR>S4yY%tTxloHook-c)-Fb$Z$S?@0Qk>Ezi(=`e0Pv9tGx(^pdor9-i!q~E_{m1RDcc+4c1WZWm6v-AGN^zCzP|BqSVyrKJmpWn?lt#l z)4qSdKg16l5Cr^3z!nH?Ie0L<`-ooR^wBf-2yg&#RzHOhkq&zN(+^kgY^HW;<=YcS zaEirzZ5RM~cg;EF!ehRS**ra19flkf<#{2)%m;(7mUEM3xJGK+p zJ{NXFY zInzF-tzD0LzcRwmumg3wH3WJB-|6TSV%|>ydIFPsl$3ni|Biou;6Du54{uXe32oQX zxjUwPDv>yPn#4c!|FvX-mBYRLr=PCJGw}q!;FGySaEL{J0td{_i+Jp3&VMN}I3M#F z9PxP*WALnvW8UYv*)JF;asIW;WB$bp;@Puj#PIMiIN%8}G}KS1Y9)T;WDT2E5{Z~r z5ec6>uq$FxQ6yoSpF8(6MxOqc*k5?Fsp&Ow<3<~N0g?lHVbeDfTwH~~G-V#!jEofU zz!#yV%>~XWgpg1L>K=O$e|SiUie88xDY)& z_h+rFMrN*D>6$!rsCYzKIUoLfN$0U+^@A5Kw0}UI59A?i~8Z1ks~ML;qNC9+K^*sl8ECMl8Mb*>`Isa zZjmb{n=~uDJAvSljGcoYFvlekODqT{UV{hzE?<35%Ns>-iAB!U*0vKf(=Ul9Po5A@ zpFaIl$nPINehkbH!FC@Y-oJZHq@`ES(eQ@P&=uchRuPSwlH&-PICda@ zdSU`8h5zAKO3EYR`t=t0^xg0UI*EPzDuCzwKXK2 zf87<>v4clILL#b4N-FQEv~*g(ynNm;#?w!caX%98f4BYfiR9$MkC@dxtgM{XhhCr_ zS=rbQIk~$X`}QSt95|5CdG1`@$f;9RbEi)?qR*!X_J85@;lp_YT3Tscii&>i3JTtB z*w5$xvG*2WRi$mzFm`u0O84O)-Jnu-cZ`nXIH_ZgV;3mW4Tlm`Ob~;V?rsne6BJCa zk^QfGAGd(wyz{-EbN!d^2Xj6191h3hVY}D5Yu)z){FCkX2KK$OXV3AjT(~Nbp|xn$ zNoLV^rSsfILTt4aL;i-%bn!|;sEyN z0n9KNFJ{)_Ag1bOG4u4v9j3Cf^5Y`R-?(vuDJdxhCzLa{Dhrr-%Y)tzm-T9dwy;q~ z%fAtNf_J*w{%^G;-CiEw8}a6!K%Z(14ATT(qaJuJ1+FWQ6D?p=RAOMuqZt{QD69x} ziHTidkPATEl!x4Z5o2zi!tnW#s0EOp5Zp9j!g-bhq@^$1Qc$=+_=g`5k}fS9lAy)E zde?;S(Ih&?bm{-@9oqhz{hL=nU;mf=n2Y;r@#3_%$W^`Gyt%yb^yyUA*C(G}X!|3W z#KbVn@eO{UrRANerzglVHulM$JlQXoIAF>Y|9m`#_UAl&{m5r4SEeEdR*F8K=(ort zqDL?g{R6f+=H?EB|J>=*k^i6WO?&?QWhY)QSn1C!TnP@K1s+)OV<4kH#iOQMw@qi& zHBa83yC{k=opzNmHVdKirx67FF@)D-3tQdPSQ+=<5sko8D{I>ZbGg8*8NmlCwqX0icUdOLV7E4OQ@4cj~ zd*ivFYyvimZQwuKS@H^`CkV|k zP`U74M|ppG_wIv+{G0bl`+NWIU4g-a$Be`I8mVj6*PoZ>&Wm}oXh|Y7eRd>cHXR&b7Q$#vI?M14kPrL>_n-5C&_{qam>B^- zK+nvdv2(lzZcAYb@-msCf=nhOJ(W2fbe%!&kr^%T%}8nZv)lqbA-Kbzk<;;G#8Gn- zRrX*mU(06!&*=yV@3c5g0qF^-zDRY&moH!7wXfkb<-Z;~_S9_&$%tA$KkgNLfJQa7 zSjNC0iJ3k<6Ezdc4cx|Dl-o@U7nV2b>ZZRyF5vOFaaW&8LjNZraRvCNJiul02ks~; zhU6z-A{s zL_Z&V^>gKC&Rly#_z%Omm+|pYOkrULY=8KD4Go`6BO`y*2v6jF&H*Rj19;^a8+&G9 z?#I1t+sdE;Ol_Ju^TN%kQ$2D_Ope3;lkFGePqXmKo-tE!W8U%;O<2HxATxKFKQnuY zFZ1(;P{ztS9)7ZA(y(Dx@oU%SFw2&wGxHb3Gc#t9A8-Y+zy(I#=rqIofPbET0K+bD zg0kUBtW)gb!B61n2Qo_dIpqk(@qC%FiULM^ia-9&)6DXw%v{Ar5bi?fH zP+LH*5k3GGae$<{A2UwTmAM!~eS}Rcz6s}qcWyP~{v}&yeE$3yUipGaOU`TL%N#Er zJH|ahTIOPwj7(^;GVfaD)T!w;$e-4)UR~K}VNud#W|j?pO?oqJ+Vz){CPlwSPTz}PKFgTH~tLuaN%0128+_xY&IO*BBS)JjTv8t!e0xzrvuiDPF%mpIN>l z6M69zX70RLX6no^W)k86E#v~o2Ow zEf?tvNJm7P0!~lx`t36&JF}QEn`rk+a-?OUn3#Kvm{?F8Y7q z)8m+{SF4-u->(O^yvF;yW^!^K;#{)y2ePt(*`lJpm@9ZH8M6g*6&24HLz|oh+@~uk zUdmKdx}2u0c(qhj#_hqdp=OK!cka;k-{jxC0>g$$$?5CchU@B{g5F1fxfcTH)diV4 zIzHL1u2D}OJ}iMA<}Uo#>}RH?0r~3c0@!{(oWs{_|C>1=0P%n?mKXd2pBz)ulldDq z^PiI#)Pi#OX>YsjIJ`6?!8p8(OBMbSVuB z_CGXJN$F~)g4DTtlHwb^J9g~a?ccm_+TZ(s;|j>j&se6Z=>x6LiDF${?`%CipB!51 z>Ry?%W}Pawv5BqSwJY+Tv9UjTT0Alg4gGS2_-~5?KH@)@a)Og5`{%4(n}Rw2X-(6n zovr}xb6Mm0hBgN?fKyN>^D6J%d)KvP z%hDT>lVnx^|4WxbTfpK!q8b0JuYZ!62y7GfHB2rtn!v0U_<@$D`6a9naKgpb9006; zivNl5A9V4$$x|;g<})LhX^1tZ&yIq>5y6;F4Q15f0~nhHFzFdZ&=Nmk9zJ{seysbr z2=nw@%?CXJ)fS&O)Gz_wag7tlS=Z~zIx>2E_ZPC#_UWtFWIWoni!>CuZ=O7P@gb+! zHvZ|^yu6ww1%<23pg~ul6$~T&&~xMg^T_uX;{S36G=c?^lGe$6`Y3DuZ``5nzs0|G z1<*&@LsN6Jy_QxWY`;$q`Tn#B|N8pG0Rqgy5nvvc2WDXivJ4D-b7&bE`NIAa{)LO| zKluYfpTHY=0Rga2xdGqoB}=02EnAlK4)a3Gr%(3;1MJQB95x`QU(L^cE!`Pi~bmbf{Ir%2lb%lBKE4V)*_G7A7)t=3ZwkW`qHU z!Hg#CJ=FpD-~d%TR)>w&L~c+U_|-v5|{;xl9`2zQ<(YqduPrLXUwOCFv{wI%`N@* z?_a_U8+HYGzzEa=!jKER@=#GRIE%*%g&z=_DJOqELsZneXv~-e8~&~Pru}{YH?F{l z5i*L}+BV_Z+9%7kwFT&L^TzDUW)2`8py#k>bJ*v0jsqwkK(zoZEx#-+EnoD&dZ8ZZ zgBTzHIRIbE0p41$Aoew8h2F5R@Ivm-6Ey(0oEh`HpUy(8KWm}S+o^LrUoKgB>ebPs zS!@-l6?@}&-$Ge=4nFZ-%#|Gq&9lOb^Q>Wg2{x|N__TS>)xB~L>GnZ>< zc;oyUfAp+-qpwxCKIH)N1@!fWwx7elp`kDN3E5N&m^0^8>EXkd9_`zAsn*QQCtqLR zJA2Zk0N8&&#QWzeFbk+*>eLgZW@er_rY3IL=2Jb&X3Y0#m^t6Oe&$@k6Z07!b-(?7 z^%eR3mX=BAD~vhPx#jbrfF%xwo^`EBJu{5Daf{VZbx`r3ve_UXK&LlIn56YP8`{mFMmO)EU%9NEtM^I73)I2O@ zuE!)c%{BIXtvcp-gSO<6dVPgsZw;jmFq)D_9u4TfJ7V5^zklMjH){ zz;Z1u0cO^Dvy0;ZE*{X+6Xpas4&ZnI{z9gvrf=5TwZV5oLn9h9GQybb>`3&x1=rxc z*!rNcu^;mNXUZ`b@QH{TGr{5S&y~r3%3uTO9^Nrxw8(09}W)~6XpJUW0-#3Zk ze#-luh5wKEANba?ARjAvgK_OF8JhtTQq?d#_(V<2m2{o!b5z{2Nzb z=uio14UMB$G&BOx*DiqXF2HPywm5+B-dQa(U=Dmj(H%F*drJ} zzsLr%{rdWT&;a;m3Gsia&dkgUvqN38O-`WOhUrO0eR2?^1zWFU9?a+xu7Q7TbIS1n=fJuKenz!iZ<{!dOSpXffpkqorie;(z^5=PMZICFYU{XA*Ea0!~Bp>3o95#sGS7@+&*_MmPt;gSs+4vaAINvO{IhWYI6w{ceq-}c!~l`ZY{c0Mko#M@40Q+89hX3VxM)#4;&0RhW?o@T z$QMDrKt=x)GeO0V8L8k4{g4lK0W(&{Dy&EmG++XTSA9*uL z(}mGdcYic!;GT$i^Zbimy?PA2pl~(;+4z==(-Tl_k@N(wUOfT+Z!ugYtgoIN}C_2|)?-8**d*8SVRTibtwuYCph{B^t4)cngdHN7y8 z((5Ds+wuciasXb78em|NjZMtkgan!e*4!JEoE(99-bd z)%$nvp0ip4(hYIzQ#}FI6&o5Jq7GQW=;=jbP5|`=QT{I+eFPD#A24?874SkBBQGER zZrr$i!JRt~Z=VzRwZZd^zhCd(Lx-uW?>nWc8c*X&J{HI1qu{A8PGWeX6>4-4>!a(X&!b@5FQG z$D|=7{U0B30M!A>4=_b6U;(YsG~^zq&J1PDXIz2~0Qm^hbBr#<9Pk%NOGMaL{IJt| z$m{wt@@n1>Wn`R^#3XE^Wu&YVr)YZCn(?d{W63>?qJ(Afxbap|BSu)oxwynVYHA`4 zK`pBzpjjlsIYf2roB~cyKy}6X`umu@l!w_MS7GlXFiRi?eM0e=8IZ_mX~j30n#N*o zP&8tLFz~>!#BSYW+H-*4I9|W@#~(dfSyM&DF#-0!L|xr8lkhL(fKUAZpAUIK57Y*| z;P-o`8ycQ0v9^wBB<#1e{q&gp|KEN)cL$mQ%n|2ldJQ_v@vi+;hvH#27BzWnjW%%;726H&8Bc&Dfs@o2;d zr}W;v)eOJ({nP$_-}V(yRbBe4s;Y0v#EAmTqxQs%`!8{Tcea+62l9e0$Pc-&eg#$s)l~>^}QKoJpu9tzF2{b0#`r$$N@r4A#s2Ld;mEW&)d?{ z4hd3{)`{X0R&inyN3V-Z9*vQZI2JD{X^Z*awh7>X>zg(O-y>|-)U?(Tke+~M6_l5k zLu*vZBqilDMn>1`Fc-8^LnG!X^$VMtrak-NhxGd0yOU9WiN@T;nAhklfL7QUIzatt z-}c?w{^`Hw6&N^h(4eJDokKCJ{{i}Za9)KF|7~-C7mIr>Eob-#PB_=jD`(@z$ojCb zD9iwD_WjBKr`*4b%awX_8VNS^X6aIyZ@Xswgmpf;U zU+LPlXEA%kCBI9TtsxU8*u<-=`(*O@o*B@|S{12_2Uf=a%o&uzY@?>)Yj zZw+6^uTELV_c8Wod|m$rzTSyf%6cc>U{26GVEw(&7x=&fAMpQAc!2OvJRr2~=mFO9 zU}Uu1&>!fIINt~SfWD)T_~%}Wo#KBic^zLyL0#}jTGlaD61HAa@>n8Pf&`YNqzxY1 zCxHVJ;iuya0q=?f2jZX`EM$AFDk@5m=c82u?9+33=)cTE??cqBkt0th$;g~Znl>$^ z*2E;GY3kI-w>x&kH*Mb@gIQw{n8zO3q^*6ibi@dkl)il@&HbA9Py2g)+gCtURo8IO zo`gD_$x*MW>Yjlbz!x}x@&O)L&d~Nb!}dF*!loBlS;fAKiHXEK&}Q3DGs2RR=#1_& zw^UTT(u5izpKL=znhg+GZe-*LJ+LeKf!KII7dD^e0S^DPlOKQ?fO%3Vw`^&or!b;sIs8XO^;xXC8l|phy`$0Z-MVjHl*VsjMNm1>fKaBiWM+tuz6ul2xbsBDrotuq#(za&6JjAK}VFu6c=Zr|0?a(j2TzUMvV$gz?#SFeV@%>=kK)G&BU6gn#ITVE+SP_kGKZj2xf|b_4zi_a2n<`-}si4aPYD-tbxX zMlu>WBZ52W$=UKu=Pw+sbE`wV$SyDWJnyF3M+L!lDinfO5H8>o9% zffMd1YkJ>P*7T_XFFaJ%@~P!%`4BHW;lWP;C)C47c*)ZxU%~$ku>TGiZv;;?B8Sj~ zd_fcS6LO2{1q!;}z`iT&zgv@{uCH(gA@cl+z`LTh?;Axe-&YEnJ`D;Qf~ShG`wFUF z4-_VP-IrB#Es&COz*!%J`)2Hu?Z@}Pzt0T`iOZPt8A^J7#P2bvg~nsnLUhxH4cDK} zn|GyLUOp&e)F@y04Sp#yGJdI&lHNE!NRTjdX6Wr*yW(-qKqT-Vjy)VRTw*atC=`AI z;oo@H*Svq)-|O4G0v++SZrySJ-MjOlX}$gk_8aiB zWX8t8${!ct+h9{#ZFox0Bzg#zJ)QQxQBfaA$Oz=sP zmiA2*F27Wq7oI35=a;r_T~x!Le@3&mAAUb(H$?;giHxD)rJEy1Iwkk&r9buCy<^)y z`PaMxBS(%IyLIb@>|ML^pkoQER#tXRgZ)oO3;_TBa}H2dbxv1TcS_gObIYAL(IIWd zjMKOL{1TA+6WafTggDI0EMUC7Q*b7R7uzE!#6Qac$N~5l85p<#*G{PYHRGQix8?wk zyxFsTi&RwX(mHnB5;9`Mk?ZIy$Rb-WT$~T!mCob2q$w!aq{z$LB+JVa{#hOX7huQt zvfu*+1^aZKvPZVEidQaThCIX!1$bPT~ui$|fl-0d&gB$MfG=1(N zr*I!R#v0@i9`bchVEI2((K%7e*Y>aFX?j19S97URQ1_@&RQIk?QWI1wsR8S%o_Cdi zcLf!Xn~Hq*as_4gQU#uSk%E$Io`RxVj-0%6y0nY~&io<#+kIHJI1A`_?1Txfg_!Z% zv~C^E2Q5VYGlAK$BNmS%F*o^gwWOpEd<5@gviY>6rG;z41VMtPW>CS_t?|G-+5QOF z{%H2kZrPIDg!9F3j2Ph%-?OKN@z=bE+TZKjy#i`#29tK}N_oC>XC7+o=PHzxTv8@Z zWOD%U?>#bFasYj=qT-weecIhW{mMc!kPqHwtwqt(mLBSR{xBbWi#0AZKV4Wl{Zf8WsgjrvS${L?~#p_%L6Z9&*u>@ zsCX9hCVG~@Zz$!F&!Fa0&R6%YP|@_esiNs$#nE6?qj*SmC4RQq=C*!EBUHLt*&IqNs?*^`6&BpdoMzkEeSA^ux(02c=+ zE4!tboBQ0@xic8`=8K3)10QQ^yXAiW{k6J?h}`#>nf>0^H~lfy`IVI1(r9)_>%KwX zTs=L{LIVSb9E$z1kOORuf6fm8w_tX-d)|NnyTdwl+7==qVV9_(;oHLgx5U5D{&Vn;t@A^WGm6)d**;Aaw*TKJW+sH!n*)_ z0r(;xKg(BA#^Sl>0q?YO6_wm_u(A}D+_MxF-I14YPY3=npW6-mOSAg2D-$7wV0CztH{*@$Z{LvA?-_a236d zV*f2$u7fk;!6V6Z-qJ%UsgpSqCL9g#+*w@aYu-cc@AYk7flm1P@kg7$ojWs;`-^_c z=exs~c7y-#ju~{qMR5S-1o1PRODV|u>8A?~yLMfKO$kO#`P?fV9XDu&1*q4bDW5Up z6lPF*z^=NdH}?z*IRJhDdWQV+5H}PU8alGMzh?Y@?gvobPea2#qife4p``nfk#S5$ z?XOMzgO^}a$@bfPwEwcQ$CGFw7XZ6YaRDuBb}1=2r+)AOxI7}e6Bj5cxnpjQdukiJ zKwOcor1W9&+|z-1eBZo)_cV4XVpnu$@lN}P1s{dw0D7KXva*gX?7wZIn3#2}tgLtC zs#W>8zE!|(5{u(an_{{dnsQt%7-;iu*c=hR~O*i4Y=K=H4EbcdN zjzT;i!(w^i!f@z^{Zcsm69))+KxqF>OwLtq+eY}OoL@Y%cW*N0BSt@#l=Q{l6I3)} z#7>`%9ouJsd`%pEtM8?$X*dOWrH1dnPiQ224~((+|C9qz4)M+XkX0RLBT zFJ6HCKmQ7|L32?vNE7x8eX131?j0ulgBS8tRqdc1aLj;yAj81GmGuKY(JzBo}>?3-t zr{`Wset@*}v3QOH6cruY^Z{Db7C+$u4*$(N@K0RO=4y?7jthhw0Dl1ffgAYB37E9Q z=dw!@7q^a;mUhiwu_F8Zh7IMYod|PyLO(tRzCYFeNZWhmwxpyWQC8MBh5P^x|Fln- z;1!RVttDHwL<0LUh#wM>15A9Yq7neV-y8gQwru$DU#;8E2L4tN`*lC}+_~$wZ`_#3 z7#j!Ol9O{xBAZCq=N9J&P)-oBd>(XUZw?+zWc8#wcb>;RdbWXZj~F4nxmTc>1AzCB z^Fv7gk3ON?AAaz=?dBHzY{!l>HH3d!g#Qm5z~%&|PxmTNQ?pC!*6q*GZryf<4ji~Y zf<7C~9ptdza)(cjTBK+C?Aa%gm%P%rV@Ei0-py;vmawKpi-I2V`Suy)3y_~c@xqsV z0Is&+NjCoret?h%zJ!17`M=`<(le3mmy#mi|5%)s*4ev+{go@Tn^v!mXe2vMKET$k zajfm%vL*Gsj?UR42?+tTK7Qa2;o>+zOw22R$Meqm{r8A>`}dRXFCKlrfkorTyT{AO z1f=%Vp#Y&ORpW!~V1QZ^;4FFG!1ge>F8P%C)EM+C1<;7>vEPw-!#uNS#(~x%P6aT*%`>lO}FY^G0^)KyT;s6f+sQIDh zZsd14xsN9 z6x>sgE4-VY9>MAaBO)T1jErc^^}O^*Q_~^c$fz0rRQH=S$u+ls|2<({yY58p@2@LE zh8&DU4zf-BBbSyudGg89zy3;MF+zUtrcGh6o6Q{X#~)$rbL0~&UVNsOVgcj`66ts5 z^IPkQT4Mgw$3kC#cGE7k9kz+Y+b(}Hh6Vveme zyK9&;nu53_9au>PW~lxZj{L+m;3jbiwFHz^ou0w-WdOarApr?tt-#2qW+dRNw{Y!f@2MF=c`Tj;m7w&D@as%;w^gU^5@1$kRt~}ncBMP~eIJSPb zcyUxM;l3@~FSP&O_?Zjxh?p%P<_H~u3v>j66ygP{4?-(kATI6@)veoj$#3y~ZU4+) z_X>;|qh}^3XP1KeG*5_sH^jPxf8qg0aDXFx0XNuw*Hp>_tXmiM^zh+S*#1n$$|@W2 zb;=v$_;dMu*N^s};{hQDc%g1cbp+>h7ne)*85zyJK|=gT!iP9tr=j71`2jAN3+Mv; zyV5MM{86J0UhmXtM+ox%SBV3L4m}h_T0+YIk^Y|^(?T9C1#?AmH*Jc4y?1Xq@Sg#! zWV5#N`|mG<1HzcVz&s{B{T`E_UxR+6+vrKi!pyFq$D>CdMlO)@oq`W~LZP1EOSo_A z`PN*}+8_85{<-I=CW!oB3iAF5=mkz&yg2d2AAgiLsjHvM7ZdZ0oi!`u{;pk7_L8Qj&DV9xV2`+<=0@`BG8QeSsZ1wCDc6E|R{@ zcaxM{{HwgYTLJKmGor};6aK0G?}U7|E$ZL4u=~#GoF3@spCg|gIg$a}pM`l`dB{;+ zdw@TeN_7Lw0BEHXBpyIskj=?2S>jusloW|sxXt(%&IO_z^{Hy~5TzLzxnM4!3wnXv z^0c)bGGX(tcJKbz<=(ybTx%Qu!~p`}-wpM$fFfw*FykT?5cb`C88*+2Z7`sG8MGv-rh^r-Vds}#K>$$(<0E!DZytlOz{xOp~4jfeY%P&RX zrPMm$HHGqfdU`>{zy2Eadey3kCwzWD7O;=}y?+|vpIaREX_t}l0+$f}ov~a{Q}Aku zf7Bb!myaI(tJSx8x3+)kuXzP<4qR7N)!%I8lCm{&5y?frNxr0@?mvy>?&i(`V1MzJ2#xgZ+<2{tx+o&i*&!-`MzM zG5r3fefzRuD|65zlg_xhmbQuePoIRY<`!`PhieY|EqCr`f46_a0pQYq<_n~24>0>&jOnNL%l(Sdv4M9 z!NFmTH20hGe-!_t9{{s~TypyLWAlEO$oKEtcki|S{r80p9eOZ|G&!XI75e`i{@qjb z^a2Y0{4>4rz=2%k$+Hmy$#FrK)Q^K4{DSCtdEbrKGOK2pMk^ur@N2` zKG=Wc{|NgYEcOZiwCHCF3a4^-Jm1_NJ#?mgn|Eycr~aB(VAwF8x`M*-7zG8df)Ds- z?YxqbBQ$Ha$O&*9K)W+?M=r@`W+V zh5kL{8NH!3X!ZfX3D5*my*u&6p+kAlBW8kEZhrymt)Hf|H0UcQ=iX~MIoEVKIXCz@ zt#bg}_uT$>*yn!sIR`ZJz^5EQnm=%B&YCru=%tFhMR~t3WBpU?bKeU&0J>q|T>zU; zIX}Ti4v>@c#lPv9LTPE|n66zXwCDc5CXl|-_ZmCabUt!^SqcgsIi#z_VtoM8#0hou z%^X0vLCOJAO#nWBG2vWM(YZ~XFvkJ#30OVBfdgmj2)0p z39CMMFc0=W2lKH0^SPOX{de!);S8;JV1PHM4P34qH_joE!?|!5>Hu0l$MqaF`wATH zKi&VH2RumsmnI=$9jB*vvSQ7e?3bFFXY`w#pdPlA4rdJ~{G@?r5$96&VyUEP5Ety{C+Sy~oi_P}%2 zuDA6+!hK_7BSYut2+z==v$NhX{{GpIQ8R-Ef@X>}W1KXF9Im+b@r(H?O9y^DF+DqfLt>D{4TvrN}`%S%>fQW9B>W(b`ttg{K|N|G{QaS18{W!3ya`e*49OsjZgS*WO4rf z{d@NOJlZ+jzj^Zp=jFU&Uc7jLx%u?k2Gj*_yo5e6Lr%{9V+_FIUAPN%z+z%f$z#XT zd`{BsxI$m!fbSj9XXpn1sBJE2iv#Gjq_(_7k(r@yHZU4|;^$PUpF?6_s!k$xz7mG=MO89T)fY$h@n47~t=>q-} z2XH2Q z`&X`1JdKRJ`Rv4rg1R4nyjDT^1BxHWfB2_7fS#X3=%NdjElYW(sd=G5nD=YO|9|HP zG~=J+0BC-5l$5;CN6l0Jsy9;mn|+g4V9+3G1$p_S;qd7{|o!*6%`#2JF>b! zvi(?J=744&zytGmT;a>wrg(W>dtOzQ!IYHb0f+Zt{|jCq-w(VVPemVamay*UoI_gQ z4jr}!3-f+^k@wpdMw)+$`$vzqjF~XOI-Yd0RF8x%0r+=eYk@*PK!Cafi&4Za?&;Cd zr7!60%=-Fz7UyK=xkb+r?w>w=iZe4FLmyC!pVdN(eB-T_mQRkXtSfSw&G@Isc+WJ{ z2a+&T=XOKGTVc`=4Ab%}G4W1=iAi8V%UYt42U_PSK|-yPQXqT} z%G0;T|9`^)oc-r^1%=baQc~8T9Xk#f{7v4l?H~H9UV))QwTux*Llf?iL-=pW0YYEE zmYtD7{y*}3EdEIw08IZw4)B1#>4Nz*)``oOohrY5`v!1W&73}+_C#0LFL)f_gI_BWViHqAn^GzFYYs;q1g=- z(^FqA*H_5-0p#yve&(%~cyH_RvuCf~&zu=lOz}W-Pk~1p{(u+q2GslSkb>C1+QK60 zHsW@~U7usW<#Yc$9`OAb1IWw!Aa{GVV)W=O4qx>qYJanD_6m$0Yq1D+j%;0~tgI7e zVzuM|7sLV%ST?9ZQ!dc;gEl~@4dOU};(#y30yO^z_8GNw2g)gC%$ehRW7#s_64-oj z$gxyiT?b&_0dc<*&JMKCr2f8+9d`ux=&|dv(D$dj-+>6q`;8i988d#oHG1Q05gR*^ z9+B)nt4k#ObBp5u^uJ&}SzuvJ&0TO#Eo)hhb%xigmRzpJ-&KHh;2)#V|pS_S@ z6tMFJNM9^2?vzY+o$?3&lcbUD>!vIAee~prs$+h5Vi~a*eK8^XJFZ z>FZxAA>Ut7@dWti?>XR~`2)={fV{jfIQw+@ph1g%`DSm}_7DD5ufT)}i++YSyg*jg zB?I_JtyqZv)_#B!n-k=;K|*~{OAcs`1KQ3BIs->e6sKdx^&xPKWsLmyV%(OgYO?fdA~7Zj$#(BHG0?WffpC#6x?z42gUy` zu>U^5zxPKD5cUQMGQ`B3lPoQ-KYaiGKJ5EF)C2Cb=jYKmnY8ZQxx?JLb&EYetGc=p zdog-$0?||Nk|e}F;h%Z}1R0pcm*nZ0_3)oypJ9Zd0bXB6@q(-@`B66b+@H=F@#9aR9v!Vu8$pf|^(V3ICj)fH>gElV?qahJw6temG`~fdF?PLk=dlyM>6^Cw!+(`4(4j-ej`H$9ABDyoKBo)x zVvfiIkPpxn2Q=3OIX|Eo|6IQ?=LgW|o?C*f&rZ83XBW>poPnOBUnvx?aB3GUs?#r%PYI;tC>mY<5( zvpq#rPb3bIk|Ov6gv>^TQ0-{$|*99`TsOy zi0d&HpeKs70&ZF6<`FeAGC_Hwq8^D#N+&Xq-^&;~))Uvp1OARb@*Mw+2indHvYKGz zV9rzy8S>rsZ~CTf|L|Y!3Uut)qlcp6?`{$jj)kZRp-0pa96)n3IS%-begGQ_aDIR= z7Wh;jB=iMTR9rC|z!Gt~4Q7McqaWBV-N3*aGbz09h=>F;-MXE6+NI0(3&4L!bL_t# zxj!NPspd~-VUVw^tn7mK@W2^@KIjoVS!iy4G9MbiEHyR4f7=|Osmaa?PnVEzO(LD% zB0W z#*Op9Iey;Y4(i+cZ+O5DTF!uM^bQvG?rk*vtKC5DZ}d%Hfi7Ko_mPtN!%ITK3HU#T z-ZMM&mpOmp2Yk)}pZEb^;DC>@0DOM|e1FGWW#vOD8XC44G{;|8_gDh71SOq1U1U0U z4rY4w3Vz?E%Yo=FUACQTiGS4nBSws{{3P~wrE|tnGw{u|usB(;V8QufBcs4Pva3S; zx6J`ujDXicALM~qn{MDbcjV^1pdls=Ve{f}&+Qcdqu)3?Ev*txJ!u77>j_9h#OVnL z_oOGFIzwaQBgVrc{*kDt73O+;j{h-Ztm7>#f+~uOpEb6fS7>5Q&S;%;f`{V*#oMAa4fSZ=KEOAI6y-b{RT4#}dE?`Q5sme%GPH zMW#oO5T<9(OYi}l3c7UJa=Lr>-E8d-{eIB>9766-h<^zQTlB&^K$GK!_YmOB06!N0 z3m0A}S-9|GiKeD6c!uHt$_ISe7vOk+_Li9c^f8Bh+VMNUcW_B|bxp1#Jpo~ybVDsy zOWc#5pr)o8y2BdGN4;`?)TqO8a&oq?;llhM^%6)(IOBS_XIE6z{>KL8(9nu!;^N*o z)6*OAPqPp3&vJt&5ObWrAu77dPei0!x36{swZG9feFYHz_YfD~$ol`y_;&=R3IDA* zfM(OQ)CSO5)p!o~uU8Jv1IeP<6>W2XJMKN4p~pLnyq6{BgIL0cKbF(E^NBYiB0)^| z?ia8wvH0)a{nQi8`8$tUKbZHi=L+F}@Lfq{Y^wWH?vG}PPnzVPH+AZ% zg1K`q6t7ux?Z)ic=ZmNxM@^0J&#nK=12jkEQ|!0=p74+FQU7;InmhMQ>7z&Ypc`yk zPe8aQJ;9wjcTsP=gIwcHX5m8rvN2;U<48kAc~QzIQtl6D?5lTKW4<)P!51>I?j?5+Wxj`0+>yWK6PjGs)STy2K_6{ zqk9DHdNy90)RF_>4?v&i1)JxsIl=LaNb%^aQE)rR(gV`Pj51pE~UQ0xnqbmj-zg2 z$Ks#j6!Z+G7#aEH)z`mlQ$zeY`jK^b{faR&yHGT4oB%nD);_@Bae%UNP_Edxfczf4 z^-Sp|=-$0+_W^?k4jnaa)C5r}QF&2WQRU&%!Jtp*!?KZBPMAwmB$95gmb!gW%`RhkX z{_v-Sn0=vy_%Zac*&qgR{Kx@9KcKlL0Q--blJ*JE0b~A8=;H$i(lE0#pE-E2gqb$& z!cEi=aF07fyCRGQu%k}zov)x^nJFQ$CCc3VRQcY$PZ)dq*O(Vr#B}L$mg(4${Qpbr zBK-I0;d7^3x2-`!`+tCH|A_yE_$U7#@6XO3ZN~rULbCr$mxh-9^ix#DFTY%`(9!XS z&c(A04&b=p?|Go*Oo5hu0M!LhOT?@L&y1^AGhe-Yc@ucQ$!ZBWJwbVSIdkL24cPut z=nD!MoLdMDuw^XGQBhR1hgR8xt^bW1=a}@%FIR82h4W9Jqh7`L?`bwcBjzn-JV0Hb zO&@^s30mre=`m0KT;Z_cjuB&|6t%`o7%_pb#W&WTqCH17* zkQ|`ce{BB=UHW$!2(O{NrucsXy#K@3(%GE7@ilbFkfGy7jT$E*Dk`BMDXE~QsHm>1 zqM~P@s%m7UsyfL)Ma5W0QPDtELPAqv*Sq(G;oZBB9MGlf!0w$p z_2B;cVPh9A8!KU(BO!5&X3w(tZ^;48aRBQVz(;UMM82(N|9+aUFPyJ?_;3kkRfgA) z{ihz{5BNtufP8-^!tnrc0Q9`c*t7lo5}wu66d}$pW~!=+ zS?yp|RS~eCj~>EYaNHTp5jo19F(&Mvc3}I7@wt%8b4WULDE7WkC-}Jwz-bA%K4t1r zrn*B?(ydpp{h2t!1K0U;AAoy~;{fvgWhR_28qeEvQcjm=u4S$|-(-o&h8b&T?3w-Z zY|Ggj=Ge{oV~)#=KW4a_{%-23|D(RW+;X{t17;7{+;e)*4Si` z+qHA~^1XkpSh0V{vSs_WEnd9$k9qU{`gz*4tt)kPf0-jAvufJ-@k^%3$Sj$mti0Gn zUVf&!=vdv+eR_{p8LdBel$dS8cySx_=+N2oG%LEL4{*!oPqS*n9|-Z!*?&jO&~v3cV)6X>(T|QCsmCn7I?M^LVb{KWkFeiih7Jt{ z2MBWjJ$exSFJt-K?$KlWnLd5?T^%^^5a#_@Li>9xmhg{UBYbGrbUJ5@@NZ^zvS8M% zvqd<2qy#ew5d%b5?B1PvYu&o2a?XikASewEz%Nj zIwHb8)fe8suZLc#y3x?^Ouo3dPb-Zuhx<==C8d*@Vq?$bNl#km&Y#U&ZM?={@7%3( zJy#xBd11}5H4)$2ejopx-FGR=?3Sg^KR!Rr!qOs1Z;xJ-)F!D5{nqvK>ASYC*MRQ_ zcn$b&fa}1O1MLSd9c(#h{-9k0W)9dmzFb&?E=rOTYbDXZuIfqu)*JN!-kUqfBbRw)aK0>Pi@*161Z_=i2tv@2K%gE zf5G$n@6S7}SaH^J=FEVuJ`J- z^ZbAThtT_TBx>~NW2pbzqJ}O!n}}u#>+AdGq8}%J#*8zC3l;>ItXz4eY~8wu@(ml} zDi0jUzP)Z;^hXY$+Q8>I0q!}{4u7g2ZuSk@@&)i*7R4Ci;;zVfc&5{5nKI=>fpBhs zJK}wJ#0fO(lV&ChXDCt60;eZN?k{o2j;k0>_!63-SL}QOnn^&5G(@!1Yy#2~a6QVe zUOhp)algsJ;zBXb??fFS0Q#Y3O#o?sXjUl40hA+9k~^I_ZoGfG%%WKyT0bdUng3~i zcCq#1g!NwQZ)`lZv3AqhP0xQn`}>Rar`I>E3|RSep7*?3Gk3Fl8a5i`BX^BV>AR(G z%z*6!;s@=p+gbo8b3_?z^^Z#RT{ztzq#p;6x4jil?C8aP$Q`2OTiOG^5XV3n5 z)53+@b}wIk!1|}3oV>Pf4LEb)z~yVUw()UJP8mt|_L)gXk7lG`@P6w4{n;4@4&-MY zI#i5RnsM-8S^9wkWhs02mL~r7S4r%)ZKY8gH&$Hx@yDv*<;!mcn41^*n3$wFo12Fp zG%yJGSyA@D_i{>qF3{Q+Gg{m&T}<2|f$-lJ2aF$Y8>_GHSF(LOo%b6H9Mc(8DX{+; zi2w6(_RpD8X=&>SXmfFPrAKk!z5!1uzb7Jcni(}J1h&5luX~6;dk=r^K2|kjdE^l@ zcTO&A|GRX&%=GAXh3VPj3e&Y~DAS>X@59cWw;=}De|5-^BbfVfG)7ePIP|}E$N>n? z8KAQSCrvtm8o+7902hjvEeoX>06xI=imh9dt1K<^?)>sgEM|%efK?Cp41RyZ0mKFW zlmlAV3rIIadIHJ~;QS==@kvj>>IdoU6q=z%96-Gh!djvrW865K_@zrvm%o1f2v`?t z2MFtA=UZwCxY-1xC8)2j#q5NejmF03aCVOm{0SlcNf#uX`RfOt#1FoPkON37AU`1} zeVBrEtmJQ+r*-!!Uz=_}y?Ckb(ueEMtbe`f(k5nG=r(q33Ejf{eE#Rx%T6wPI@4$7 zLqiwC+tP=m^ZIY^pTMp^`zQ6<);q56_P%jr_KiuGJ0h2>YN?v7en35b;`WIl%D*am zORSJMJaF2;UwRnySkzgqvsnlE4q6>0Ix2M;(?zUf|BeIOYY0MGApf3hy(W*RHP6_1 z{<;}6)^A_Dc;}HdYph*={na~Q%a(J&fBh8^i34`hY;B7R+}-b#`}x(~_V<5s-^=Uq zT?dC+47aMedF06bDl4m+ietxW%8nhYE;)AWKDUbTxWLk~CTHKi$LU+P)Z@Utw{c6B zybZUocot%6df!(^C*M*|_UfPF(L|d1!106r%q?F&f)RNCK;J4@3wBe z0Uo%;Y}tCF>GuuAOFq2GT7?I9rnJ&-(bVvM8et`J+XlC||B1T>HLTS(LJA(T4 z*?(#1;3HvUMjyK_E@p@NA)QU?1`VJWG=P3MC#2a2Sh3=2*$+QN-1zOcSe$#GeDm0` zqPv?nCspEHAe=o$=aTwn33-6HfLoj&!0m1O1LOy^_60uA4-mdvF5#IeHo(6f<^J$9 z?+^V1cIf#Nl;!1Bz~_I)&L?Q8Dd4mOb#-+gXA{)d-)CZDi(ad!1ZGkE&*2~1AkqZ=-JjbKLyXRFqF;iVZuJ)YEW%#L#_1qLi&=i%r~39?bQb`((w*74_@S zu7CCW#oym<4Bq&5W5~vL>(8%$x%}kvCo{ZfJTP!ExTRpFP&o9@p^5#s^}pU{OP@&W zQR4f>(eP zPYCywz<;@w)&0_=NADF|S=}kLw7d<>v#ZF;>UPob<97;eY-%$0?tPfFY18BARjZy{ zGBj+PMDBpHKbpplcssz;&~y@$4R;)Pj8FanvOUp(vfn3?1Jt#hWhw6 zZ@&I|-@aI!r*|Dbd;)BL3eNsWWu{NR42=6ViHHPZ4|*>m;$73Rqepqij!ropJK83T zh*-ynh#U;>)$926(WBkKCzv0~^UPP(5Zo2#xz_iTv}qC%KhAWQa%TEV3mDbO7ntSi z(wL=dGMF)v&Lsm!TBi>iZJjg{IRMdd$FECE*j`tXcZye6^+?gv^~s!M8~`2A=>n?% zFJ2s4x@y(6vLAnpD*yfW_{#0ulW$sE7vJ5pC+n7}X%K1=Zpgn=zo2kl2W}kq=KluPS4;6jW6BOORQ%5lyBd@4ZolYdDFx?F)`tM5W+v52QYEssqFRZP7yG zY8U!1tex#M`@sysj9N1nv+7AUlPZi37-bl2H@KqrtDd*YN|l3>b0mKgH4$ApR(I^w zQOcvV`;X~A9*Tnhr*Z?md-dwABqPI{ZD25W-Sp`{{e>KzY(VB`QNABU~ zVOI&TUv%Wi?Lu03TzvHC-C}F&yQMZZ_sSd`s`HK>t;W!rdskMiy5(*|2R$$?>br^7)%7j@;fF}%w6C&f zP|~^I`}e1!29VY?eR@R0xN*VNJl?fRC8hJZ-Mb&YBqFjSKtyEsDG`yqL0D%*ME0G< z_jvr*Y3ygabU73P{p1Z$aiFh@ZFfPn%<0r(FsP} zFpv?~4}3pf-LGMsidU_ug8OZ8dDl`oo-6u^-7}2zebdcN0yAbz56YQ0??NHP0jpPs zm91YNja)!H>jzj_72G*`wD8W7C7~q>3O49#)?e7+3%{}k~Neu@1gtgcI1YGz3s z9g#kI&*&t{LsHplHfm*tj)qlcu4Z> z`TM1Ib~R;AP7ljnTxzRaUF&YTxICoiQvUku_T|;9ZwL$xbL`~gl5M1<60Ijph_@O$ zCf0gbzwlk6GjkMWgBg)-eu%%_p7!hKT0Llx``tl#EJRRLDr9ADk_ocJ~UJm2?Hg%WuX1YoEFg>JvnVzzK zOm78$rmvDO(~sxP^ilF+dds`N?-!vT4(T3g^$ekiT?kNWtpW zSBuxLk1Qh&*s>-0CekIh?CeVJZrBi0sjKUrEh%Y;J718WU-bUTlM2)kX$FaK(QE?J65P2{0{@^8K37!r zgbBX*v&icqw+FvJY0jK0sH-Le_i?cO(byxQ#kh1^Ud}&xU{ANm;ij9C)V$>jc{Xyn z(pJ)0Vu!>tdBbf7G<;j(V|AsYZ)-RYv4_5hsEDk{AdzAJt91gZ z*(*p(D=wHkdCBkd=50B=a;1%6{rXd(8#i80*}65a@XtRh%XaU+f8)rJy3*svpWJY9 zX{hw^d2Gt0_ey~O64-yNyQPN@-+}Lc zx5U==L8+5dUAc$H)0;j%&+hp7)!*~?ufON#_pHjz?NRoD1Jz;c)>R0MjdN^eWs{Ff zN`Aoq*jUS9{Uhx}5%Dl8J}&eGb1v{ zju9D-HP)8tAa2iy@(?@xf84zVSe5J6HN3D3yT!mnK%^T43&jppY(T^UJ5WJD5XA%$ z6+~1pKmqCQuC-V}cXxL_-?$fYwtJs_&ilUK|Hb?LT-Uvy1&hsQdpX9ObIdX4t>oIR zw$;0N-~NEABS!*@Pz&UoJsXsH=~6__wQKPOckiYaKYNy4{_r8YOjFY{i|h|wwdxW4 zbYzd94R(p!qhI(RalmkG@JCMYhi7P56Zrl4hbBP!e{0viM9;t>6Q2B9adFpl#B=Nt zcI9|_CUz7TXJ8)@@e^bM_iW_jIz%rJj~_QBl`nkXmd#=LPOzR%b= zn!0f#947?d7fu~G7+ke*j(x=V$uEM0Zy!xp{2-hu{Ysi6ZX?bXwHD=wzYxz9eJ+-b zlP6>)oU_bgMaB{f{*2|8D{=&%2o|q?yt;`0!P1-s_ZM*H+?$hz8X$V9DSwjWQ^~9i zuQrtIcHCWa#N|kf_IK@0EqASs<3EnK9shQ`^_cUqmg66ex9WV<={Vzjw)^bovt3%w zS{;X+4mIzz-C3-1PbFgI$(1&|YP{!1%8%5biPARGR?;MB3usH>8kh|C@qfv?_TM>e z7&mt8IMn$HC)CwXk$$|*>C^AsE?x|bxN(C{H#96n6TLF`?%j&4yLU^n?%%HjhU*Zg zoAT}KS_|L2X)AvHy0yg4t{DNV0U}mS{(}dVdFJL7ITjWb+2DZ82M@|{N;8q`b5J)x zBW%nB<_q22x+~n>`)WKr`)fTtdu!a>I!fQaZ-zEd8*%=8@yGS+nJ)wc;-4;C#?Aj9 z@$p4Im^(Y{@ysbfPvvxSBxOh~|M3_09jKWzKR1hrco!%r1TxpH3&F0gQ0QhsP5louhRMp^nI=7R%F5c|1@@Pg;GY1W~VlHLpj zg;18HWB@}sWo}x{ELeZwZpymp_ zr^FRpDW&z^l=}8?N<$-wI(RU?>&TIaMlG$da?%$-E|7cudP45~`^;i%>zoSc1I6m< zpV`8~&#=Rv@W2c1Zh^n&fWPX4f98RItqTls0m=V~{~uZ){yQHE#l?TtN=SUg?jvX1 z{o#~!>{xJvy?qk(^Jfx#!-;)gzs9v&S%uf{+U1#pnjdpk&gdb0Cb2&mb$|Tcy#eK> zrm@gt3HC#AhVBPHa3H990?!M-se5%5RkmCq`O_hlC+Dd_^tIHNWSd=rzWDXsgf12nM z(cE>f)|GE{*jl^aWq<48uZP>0@G=;|Nr14oH${^ zL}f`y6|HUC^o$Q5e(?6pna_y%u?e?siYa-DH{HmhDdc9&rf-OIP>@z;T zD9gEX!^wFve@2+&sx|Stt1auGGaWClBpA#1^{t!2Bo_*B(`3~6S>kRGM2S1x|Nf1*? zDiC*&`O>|;E1Uo7gZ<~vyQuQw66%9<45fC^nVPlo4YhE!3#GKdhuXFyjM}$9p34Ed zcgI7cjG=UNLVGV?4rxJepz8AF*dp`@QATkfr(~91x)fQkWy>elnl(?kJOI7(R~|5o z`M*Agu|NF$k8*(zWT!|P<|a`4Ta#J-3jBFg1Iw5{{+xXDXh=2br^35HUA-EPJ5hp~ zum?N^`F{MeWuK7qe}-=P1^W)aUn+4^B?srpCtDmAAqbsHQvHYvPSY!Q1U(JcIOZG-4b ziFye;$tI~cGR;!2Wg10o#p+i)U0E^TVt&qCv$^b5R;vo6ZKTWBy;)bS{$X4F&W}6m zw}04Pr|zU)y~SZmh5B3d%H41G)E;uwY&z+5vQ6)kUdQ<_=R41QKGS}}=|tncH~Z=~ zKG|5d|3`=IWWKh|K-R5O7ScvJdlAH&qhCw zWo=!{vbC+vK(7xxP>;O7Hs9R57CAt54&p!O$&(6(on39phY!sOZf>0k0RerC@Ng~9Q((PtuAVCfZW;D zPPH_YQa{{dsBQaPsa0Zb)Oxi5YR9e!_>$tO0~(3co;`RTh@j4&_oJ>~52UVLi|hp@ zw_Lp%S9R-Fa`A%)oFZ^R@r4WFd1`7OndDv(+{uT!o#2{a{IB>YpATdI_vgRT20y@y zNbU%GiM~DATkckmnx_&wd1E(k_RcjjN*pvYia`w@i8?=&a8=u{djN>mhrHpBgWg`E zyu2Ih#*LW%+qc8Gai9Eq78bD-G|!GH<6i`ip7tbQ-W8?T<>qq|q@GH%q3LC-zu#7b zSY3U{^-%NvFB}&ZZ`PT|fGOuLo#cafDS3X@; zz4+1M3O-A|lBJKAmIyu*ER(R6s05c(so1NOtG-n&SABzHr&^+Fqgu4ddUM&1mpiKV z+3l-6Y=5}%)TdKz zsMA){WN8b*1e4(({C`XvB%WPmad8!`En9T&9Xj;jy{@in$fZk3X}4|_b4*NXnfLG4 z&=JEKW@eSFyLZbuhK6O}gL32oWx#$Z=l=b2TqsjRfBm{4#mT9e?&8wIMh&0s=-8hB z;zbMa-vAz{&wl!}mStyGmx9_p7V$mO-@iX5CWcChiyO=c3+pZL_UEcwJPb8s3_x`m{|G`adC!^j07WEQ9AvR z(8ZE9qCvSb>!KKn$jKEHLb&G-4hR5N{c!woe&>RJ#{)tC;0=QDp`XRo47j)-#y@!~ zCk2o7Z-M$q@-9{ht*3E?8>(>)oZrw_$x__Tl zg4&?az#t-b-#(8F1%*!xva^?X{YZ`fU*&<{bphgAMtyCT!`fSe_)@yhVGAjp`JNJ^SgB`1o)4@j)53>E8+$h zFS5fPMXMiUshlr-TbMrn-1uPHX__DH6wMR4fE{fc?G{airbUyc?V<_Olxh6<=S8ktA_ zf8b;MxN+kZ#Ko1k*gtsC!bwNxTgb(WiFD-mEOYa^w8xKYQxVJ4Y;EfqHa2zaM~`Ya zgab$(fEha3Cx)l8R4Er0DVSeVL)5U3ou#wB#T+AYLHS3q;W@y6dzU)=cSNuZ*#4fU*d8F5O^JXY|u+iwn#!(_7@0m2({U4(y*aYw#RSw}Z znjEL}P%&q_-S*NWACJ`Ox}I)^$FJjz`K1(|+9~!@tZ~gV;RC71yXGHMm z@ccyP?@c>H3!v$N2aeM|(st9#Pz9W!$tT4{5Sp}I=`&2u>46iwNpkK z8uuNwwZDWI7;v$l4wHKd`gRHL-ZdwF{o0=P^=li$$*GAAtvq6_s?Wg$tuz3J4^*C@E!n zZrYR=w0(PFzd^xE^S2wOuLnA6zRW&pN{Q=x7;s-wPLmw;vzPn(il2Rb* z0RIemc^`0rH}-D$Vz-8WnyhRfxL`;F!0U7=sQ@~(K)fG_x;X&2^MlvkD`~|FZ{RNg zp1<(Sr622)oqMhsrQ7}t~(#6ds7$RmOOu_qt*}@NmN(3wg zYJ{E$b)kRIjrzYy#7d-emHDcyB}Pk+8ppxX(aeFWJtnDHghX^v|C? zaiO98$w5IqtRFwxG8`Q0Goa-$jg2cAPoFj@MgY~4C` zxQZ$#N=1niuPDKKCu6`;llEr{ONTOKC&=H%&B*d$XjZx zfGtH^M*4@p4!;dGYNahTS?D!2Q{)XbeT^M8LBNKZFaEZB>t6TTTQ}p%pFZN0nHbZH zPo50U*|_l=i{t|2egX3QWAD)K9PknRMPk1#!9VJL8{7x>u?YVU1K7u#bK*pBwXtyo z@E?Xl{5!})OcSZYhr?<~>?a<6g8kt$jDJ;C_iQt>aO4UR6uHxbXT*I^9-76^8NBZz zX6tN?n{s1BsJN+U#)jt`OZPeKZ`A&x)vfP-VerC_3xm2}b$btg)a=+{zoSvbMzvhp zN+uUxezuIIOuD3nBwhTzc&ezWXqvEzFl(*F+5*vsq7|Z#MVlq9CA(y;Wt-PNTwA*G z-pY&>hAUF0U6>X+N_UjsNZpaXz_brCcuyR6VA&nd?|6EUd|>R^v4Nw`;QLOG^x-+p zGlYAB{XdQmHy0o|!4aB0Z3nbLRj`Q+vVt|Vb?6YTfmdMef46#I*`h_u52~sjzOiS| zoi`^=d)6s1|ws`8xoo33XbSntIVl?hE3mGGkv z^#Q}MZ#N`;)265_4yXMub@{MLPh`7ZoOXCu>ZyQ$f_78Wv}%3*xHkB55$9r1=SFe0 zD}DW-e)#g5w{G<=l9Tgg?${Akbnzmi@A~yrXyHlNpPztwIIjQn>6q?g$D&*I?v1G0 zyg3X#$sph_0N4uz_5#yMJ|ZI%fSSXPiMpczUn@tCEe{%7qTuSc;exB*MXz4{E`Fte zed?kWcAOc@Z3`zYx2+w!!nU2Z%C?`j@+C!E0WMhff}-)0gV+2v1GEKCI%%^k>L)C+ zDp@OQpL_g-Ux9@Mqx{K}jLNH56N>ij^U5SUM=;l$hTX!%^TW;ge~bOxIly83i;20E zi-`CQN=p97)zuBIH!>pFkKq297#YQLXR6fI{fgk@hd1dD?ElIENWU;?-8%OS=on1` z0uJ<%BQ1W89%bP(n)bq%U%@J6{Pjs8Q!bAUlYStRyV-Vg%^t_SZRpqcYJJh_KloXAv#19Spfj+U>lGmV z0kWU$zrn}cnKS3^R8-t!uw%zno1;hVea@YWL~p;CWog-vir#&~=g*x{-rjxT(Bxx* z{nW(70d{0$PqwFLE9&zaV6>7BEic){r9IByzmI5miHP}(^mJ}KN9^w{^7ZX5adPT{ zhTom#?A*r;2pCKQKQP#ADkC$K%Eie~OB*PPit5it?T}?_+nQ-<*@)O*hupt93HoBt zsZ#}CHf`cK2nwdStX)g@RZ_}|+`Kt20=N%WRb__&_v8dCE3-o6lHI@ zU#rM4xmFo&Y*T4^A*6J(T2!Whuy=}pkVmqFq&MQQ4}JUgs2o4P8nWpApFYUWn!dg+ zoL0=T*C8*cprWHQyI;IW>Ark9x$pMv6z<>kJ9m=scRvzX528+-@M*)n-Obmor48J< z!vZeS!4YZD>{FoGC*Z{5`=Y@KvCzn)J9qDnsDd_ty?+6yu>+vV2SPXS#?u=+#siV7 zM^;KohS#Ik&c)u280hBsoi$ybN2s$U0&^-}9JRe9w}n@;zfsSol0|^y23g zBl(}#k68Mmk+$?{4UONbjJEJm0d0mkn>OAki8k(T)ZF=2(W^vjo`F<-!fZkSen z`gC~S_U-QQK7E3R!4WZ^=>23TDvA3f_vglcqUYC$i1<^)`>UrH+<5oyP~Ly%P8>J( zU%s5!O}srKBF^w6apOP90r2yj(Fgd9n&Joi{Z2`>OoX-$85~d5^ z5l&uncTMtIykB*ESB)L4DPNz5t34PfiA)PrcE{$2wpVy>6 zd{|9~Zzt8qrz<%&b|4kFXLC4IR(g7WUSeWTK~PX<9(4Td=g*rnU%Y6|z|YJG3F*yb zu?BN;a|a7DGY1Ok^u7XcO1_6jch;LX9h`>`TbLFWjZEMleTM3oD_6>V4j<0{C@05y zFDRJmCN0hKQ&!FjNB$qVabr%HswxNCAB*5WP)?4Cd?1?*Z&Jbg_YI|9-pv(3{;k!4 zKiWz>f3*4A1vQ;I=utRlrfn3B_UPy6@sEASPk0f`$LAD(^k^hxit^Ul+P>jp0DM4R z_CTj^hd$ScUAfiZfI@iN*wnjsNu9d7Ni8>TG6u}d82Ee71Q*b``R|o0q3C0U_3G;< zcHg*>F<@kriClq=`j`nGV1N(klz~AKb^3G^^t%X3ODm#RO)U&}XZbOSKCo()2kzAL z1ZF(31I!27LS&JIM11GkwIPiH0v{QR7Cpy4foI{17FtK~EqD#KhdwrcZwo zI&tFT!13d)0>+Gf=!Y3AucfMHiJRXlWyxBJa#r6LK`qfl zx3FjcuhcUvEo+mEjH*JMQj4(f|6KL%lrzvy@S^2k`Nd z86P7Yv!7F@SfhOS2|YU<*5i`KxVYMm!osG$`ugUO-xwOvDx4P24-2}(W8s$J8>eRURyi8@!GYVJ|m+XU?dYc0r?&_ z_2Wko6%$iQB`23tv9YDp=g$Rg_wF$(w6)_4H*Y36!B2*OfIIHK`-wa{kRu`zhuv8T zl#q~D>B5CCW3XEoy9KO5XU~2dK4ZqC&}q{ihD@3AFqn6ex&OrRX1?Rbn0SpDY3w~i93BdDBHv&sSx|o zipcDNmR3OSo;}|)Hf(T0{p5gqN<72`rNtJkyEBT37I(YNzjnL`jOZ8X-|XH9%^Oo za#YDvex#fw`A{-@jipGg@Pjo4Yb@6kLdz@Qzs=8Hd}DFSqAQEy7G7K!&3B$Ja^9JF z5wlOv4x6PrD~#_PU)1u;%i{?TkbFSQR4i4}T#_MXA;uDaAf5~Sm+gAFtNyz4^$uG< z+a9ktuihk9QXeaa)kn{w_r){gd%{w~y1nDQyImqaciH&abli5m(|+9HMALTb9o3tk zs+FlcR4J4)mCBlTWnL`l75v*gfM^55^}q?*r(gX6Jt%_vXd+Mth$cWhga3zp0!tPw z;6JFMa`?usU3cGUX?+d9a)rUUcds5Xy(81vxrYtxvyktT+B_pGi^^d#2lA7W`ndHu z;(6AiM-9;a8#BQb+3xN=dFb000RKg7c3%mwUlH8ac6*ei zzj;&3c6Dvc^7U=W^7n7a{Q0vf{lka4RLuA$fg^A#Q=lo*EiEgt7o;-k`t>s36DJBj zDk(8-moJZVSiL&wi=-seQ$Zoi2OQv!6H0hMMI|d#Nr{uBt6P+hxL==?)X~?_Ko(B^ z<71$!Yp|oFm}*LlqH5hgP@MZ#gD>~LsNE>=iq1RcG4c3=1GqDP?h_{1gsoov1M@&( z9MZo(b}YWYz#z5k=1n^MwyBlp&nJ{^+ZK_He4XU%T+L5aH9T|o?zmEI?UV+6eR}(? zTg+ZVL&gARj4Fuc2QM++PbfWdgkGnqnbzv*S@AFK%NRhu(T$y)js1Rpg>8tZReSbC zj;H)_-!?-3&m zy?A(T{TwUvR(!W|8yeIai_%X*+l@tdh(i z-?@`qf}EfjHA~^yvk?W_+99Qf4h2>1+7(cI{CIdRG=#xx*OCU&%jm`LTA`{Ml&7v9 zTdl61+_rr?tAFRt%)V{g(rYnOfjg3IBBxG$95QLr6a21L0V8?HtmRX0(IdChj(kxs zP`BQkt@1=QTj7yHj^qQ$91#nVTp<&otVK5#rOq*!8$I{@+=zK+=Y`KXJ12bB=~-b@ zwWkJ;IWoqF=OB;gD9usc)3l}qFS@WOdd1Zh@#xbhi<*e0ikgYi*O;zhp%%#BX1%TY z>Zhx1w!XGKzHz<-^bGnScAgFv78FuhIaz~rHoZSOHM-v~!N31oglqrnfVX{j-Hf|W zI%>D?d$qq|`}6Hp>#a7FlHTCl%X4FX@3# z`JX@6Bj2y2-@8|wYGhOl4k$@5GAc@drk`eGQ^~Nmucp6!Tb*oeU6FtpfH-_#f{95n z_5qcECrW_-(s=MjFm%7~`}SqoL-(^=zC7MRSUB5upQ(QgaDUe5r=fdg^5IXUgSLafWo$mAXdlR-r%MGyMM_)#vQU#J&8F9&aYyE&lcsMU4A?qJi-7dvY< zfCI#h#WNINp=yjhfGX4aDP;zegZdUNpJQTbtE0|kkR{Uxa5%g~oAhc*Wdzq{zQ zU3)H;;O5@B(^zO~+E()HSubY&sWLaW!7|M1l(@Qf6k@**{C$<_z%(5g1_zWRd75Qpw=YGPx&#M(H;@=7g zB)k_9Np@VbCh489F!BA-p#^X}Wo0wGq@*~J`}Y@RT3a_(M@M(|G&KB+*a|hjvU0E@ zHg+(}-hO~%e4ENQy-AfmxI^WdJ)pkn*tDFGewDp+>a%bf&D>+s%(vlUG6Cs|N?|Pd zUtVEZS%P`u`x`occ!-C*#Doh72N3TN!8+pTUpzwO`$FK0^H1BfDKhKh$CAeO_J96$ z2M7D8t}e1auLXzrgG!MTd{9%9L+#zmr1<$g8pe*bj6fX6eu8V5 zS-uXO6CM~o!=D@eiut#I`zwg|9*Kk_&O5z`X(qUx~7voWty5laR-8Tu7*Z%nTAGmQ-j63LP?mt)JC z$}y#kq!?>&uT5Khb9D;;W&Ze?dNaeuYmN6CePXoF*pp-Z#+@AJKl<2c-%&?L`S2Wq z4~Jm?&@T=+Kyx3VIl^n2&a@EJ2T?>D5V$FjAb49aiS+wc-Cf1n_-JFvxi{w;&0Wmf z?L6#yzW9FW^$zy#4UZ1*Pe_O#OiN9pl9E%Xz?fj_OYk?!*5@^4`u#p-;9@}OIO_Ch z*dA!y`gCihqPb!||26)k5vND^fCGm7K!1n*;ja-6AU*-|JdF8)t+Z?086pWNgk+BJ ze=`RxoHuWg#!%jW&;Iyv*U*a>nJf~^t*kmQZ{J(=@#A3O_wW7a*Y_3$26mT(gmjns z`E?h4`__e6-yUrmOINhuTf zX27?H{@p+DkDY!+G2npcJ9mm=@l3$?Am=YmMcjw4xFj990QUcsVc$K9Q_CcM9#u)~LoIj>(gRV5^JceniOJFYwH>jx{7 zll!xsoO&?R(9O7Vr8m>)_F(?K8&s*uH43J*KA#ijjhCdXGS{teOcs*}p^GZSu_P2C zShC#tAu>ns2mXh1fWPtp`JDIwhJ1rVvjOlPaQz3wM<^olJ$25UchS7OFT;d{-X~!< z5mKzm_S)KRkO7JRgcC@5(1!hb4cMVsiIWe%a5^|3wwa$ls6j*|5gZ7pwQQMB zC*~7lki(;n8Rmdt+}{KjkUXE{`-CHIxbyN_;hu1}TFf*cR!5*;A50xT?u+x2($@B& z^z{SLp9ts_68e}nXU^LQoG5bm`5oi1D=k$`?MK$`-TvjQtZEQy(aZhj_mPK3dST~pF!^^EUtAos^vcq><=2+SExx)qap}#a3{`X0B3&EZdP4`p7R!&8 z9k$~jzC>~r+}&}ZfTwAb*Il0`zfb#s&PR@ITB0L=!ko^X2*kw$W~KIbb*sn2vn{{~LTlW5ZvojmAQ(iVVd5Tx;vDBJ}6UDR}p;JJ-pnJ?G=cmR!u;=E4JzW%alT z`amP=&Ye1Ve(Q4X-D@hmZ`xLBX4Y9|YT8#~YB~V1sEd3(>)yR8=k=FRNai2L@dS0|F#KaBrj z4j??>C?u5ZEG(QJc<4|u^o6FfgoLiPrlx^^8oB|J$VW$AU4K*y2Pb;yp z$;S9|aZ6NGSw~7rc^8LM*282L_IrCXx(*x&%!V(K0}kk2yEdGlT$**9~&~F)5r{20*lVW(QHtn`y4a?Z5 zAX6DEAbN|l@;GW?hS z>;X44P$?-TxEmp>J+yjQ8!MX3l&bK4r?&kO>nWL!WpE>_0@#PxdH1a;L2}4V`W#m@wO9blmjo z(_<%Jni4tT+=SrKr$_sZ(H-MAY1q#lg>;EUvP0jqP&rOw&o+v@^d!lYOlV&T6fdIu+H$^ojOCuyA78d zuGQ<t1+| zaKoRq0g?xh9^nz1111M=Ko8L5&JmN@A+lTSf6>QOUf!u&WMp=n+q(7YGfmC+9y&Vl z3HtiwjJtR1v3IK#-khEyd;30Udz~C}bFy!@BJRS4qS*823*!t73S%!_Dvr8xr9Arj zwVJp)x9ihPO`6#j=AGEx(}$V90bsvB*TkeF%fzG+*sn|n#!2m;0?cD?S8*KTJ~>?M zldl8gX}~}I7yi@D%}bfk{L;Yz@u>Sr?Ek8!#(9mn56wUE{o1uD!?^$b$*lvR3EHn* z8UJPddQOzNc}<3oPg_-feqTFw&v#)DJ=sOiJ-xkD7k1J&6&LrFg@(4XtgLDycJ0cA zXE-xz%a+{ety}ZRGg3`0CvwxK?8uFqvLjSgGb7f?vtyOSSqV7d3L98oWwluk6sxiyXOJ@|ExQZ`+SH8dT_7@ryaeBdgu;Cxc?x!9(y^{XUuqm9jFd5zjA>nc!8K0 z6YD;IKCM$!)Q35Jx>M+k8Lz^oO?v{3|1nqNAC3P0$dRPSZ;pPe`Hy)ktz(5Pwy>s| zj)%Ju%jgR|`7gsi`5nr+LH1>WmEY26IeI`p^?-=NKC|FpP~Wi003Y`{?^6;1J!9 zyD59qz9Xi9*- zercMEOEY{0-Sss!gRR5+=*bh>e@kQIU=5qyUF6}>g50Mf9NwW&c!t81mDyx=APjxR zFa?F7qo~Nj=f53!R*0M!GekiYUPGm9->rv>KJGRxGu!1-dO|HYZ-a77hPZ50`dXQY zbV<1|mK?V?Ky6ysC3*Ss_t>4~mP)>^tQ^k9t})CL5$p%0%gXwL3xY@= zu~J8e(Q)b&qeoLSv1`MIpyv7WU5m$#{m7a;`BTc&sh{F!%y5pMHS1IK%$Xm;X3Y42 z{{P3Q*|R@I&7JFv{@oYsDE^*-_sW@aw}i5B#2tP*q>UlA_%%+66WnW zR7gk>_nwYFrvm+4-(yEd8$3@$d#Hl$P(Y=o(g%0%{*gO-wtXb<4-RmQoj30-cCLR& zg6F5!z#yduGd~%lN1OW)jvF(EoA3LM7(w0IsNhw> zR7DfT9D;q#mzwojZ?#)bzCPJ{*20ywfkyiN_Ujh!=#B9otvA|#49=u;lY*z| zPm3Tkf+XgT)f?+ien--iTyuL3Q`%G}ThUx8U)e&rK+!@mU&d56d(B-D)}qUc;wPS& z7z7R=JV5dRZ|)hc4M;sOqzU?Ry@F(ZP@1-rwhI3wTEPFuk6F{F&)y*~zt2Ej-OyT7 z)7c9R>S$fv;u!4GO1^QUGTp?a9$ua%HavI8_iGa_UoORa1>7@)|AI&Z{i4WgS1Mwl z6(&7;)JeCo?qk2S8O(a|oXUD=Ihb+pUN1a%ZS*^L>(J{jOSpZzC?1DkpL>2`pY#Dp ze*aJSN8BfM0CxNq2V&0ei<%nyji6v6{Ju%Q@Sn`Z|KD)HFAX5c0Xu=*wr|gl!LAXu zw|84Hi`83OT}?IMj{C;eR?OEoP}TYQ{iRV+T{#X8O-UzD7Lqvuc!+bNw{0tk*|DP_ zT3tOadh_NS%n@e%8T(4CK=6W(j7++>l46GcCe?zF-D>5r$M)4HYH2rxoxIib;o!^a zTiZQ~G*rTK<>g|tMCD>LB^07Fkn}N;Q>+6ub7Ygkvo0*^H73!g8L!t8>8O2^IdiQ2bOSfXRFtmtYuBOFfnZ0+3|rR2=4#pj5syIXYA>*{=8>+gQuOH7K(elf>8^E zi`*1RSG8DQu>1M$$`fx-wCFqQ_g!+nG)6wd~$~J zdiM$ECI$=L7EYHnlgrs)wW(y=i|tiAtany#f3c%#%d@TJ8y{~fL7k8%dRL51a)J>$ zBfT+WNOA$f0Ymk`a7~~Ge}E49mt=-`9qlBK7|&*&B|JjhJIwzlIe^^HBZIl#6YJOO zneE$W?R@-rP&jhyDEMR|_4M-NFJ3H#240OkpeD`Gu$o{$3fPbS4gb;LfT)WXi{ozH zs!p~rZ)U!D(UtMmzCY9cEtPF!O=Va<97N3TNxgl$B^f^_*3hsd`qr(&SVQ0+JAnxP zNew`-p9(#Y%<})v0o?i@*iXiLQCF{)_#QuA@DW&hvuYK$-}fKkpVR}SC+L9h$1}wT z+?Me6Z4=Aaw>>X8xu+y2r@u5WufLeX=_|l|VfN?G9i-sHkhQfWU`@ z#f$9|=FWYcICJLfq^VQi;yBUg&i#xU+LbZf9RF4YL!>q(xpFR=FIty+~IpH`gF<4%AuSiN8)k+5WV90b9RG+ z1E<-^DZS;*o1`Y}@2yl)a${ou8SX}NiW|m1?n#Ry`2hNaVR#*@q2XJDUVq}yzI4N{ zi2L5Uy52>#P1q0KoJobzL%W^BT-qPFJ!m@i;#k>&D+}WAci($B=KmcBkgpM)ZKB@9 zK*9r4^ri&Ewo9NEfM=m-m(A|lBlbsIPB@-yKl<)itETvq`eruaU;IA_f7<+6 zvli@8R6KZj+cqQHLx;ZlYH2YL`%5AaV zB>w-xe=_h-a(jY*E(eet0Eg57zwlq0h+QJ#`ub&F2M!c{kda|R*GqCDbwAPl|2Y3t z2M{j+d_hU@2PeITN61N5mH|&wSqyd-q<;O{n(po0&i423Wc~cvnf~rwD}Ikg;Jqqd zO|2~T;KAxt;66oHw=wnn`F1)^>gm&s_*zZ!!Gq;->gxH(1K5GUzK@g?<2&*XH+YY5 z|4>n=renSqAp822e%3jyj>r@H^PKkX^0}wH`<0IHu_ww4uRn)R*M98u zr#_P=y-XN8_6>9N=r`=KV_(6$_c~?942Q(IbDf~&IYV3j5-?MO^27}Vn46C0o!7hwOv-Jh`Q3JT(uCedQs;W^vuQo&Dn-x<8|1v3g?u*=|60zY=6E?GkIfcJ3=7dpm}oypqT zp)E4fA9A5NeqOM^Cf31$j$MevlGTm-L24*=F1tT4A+Y<6_uE!ON5eXqyD}_pJVZ?V zSJ)r^I%ajby1;nd@%}^?kTI6YRDZIyZ2!ysb;n*GZ`E};-Hp2e2lO5Fsf&&mDa`Uv z+OKrF_gn36KrK)%dqXyBrq0Z8;NJts6ZODP(nBD<#1Y3v_>4R;(s#tk5xzXy!2T&5 z-68z{i37MgKf(XPco{R<>`hjiO837y(4hU{RG0p@i`0cL`u%#&XS%URwEd{#(I)H- ztl9cvYZ=)cwD8KpxL+Dr0i-8@=jel@U+`|>HJGV5b3fl&KE<(9#!maM zasaubOI}!5No&J~bLRW@*?xvmGZJ%jk-EBt|G+=t0OSGSf;@tKF8?m?_>W5E&%0c|kc>au{o0;|}+%{}S?p_yt<3?HJjT?or(D1nV zKk%P~`2P?5qZdec;7>UKVn6DEa(sVPh@M`B$L`%lA2H*@#r!|xf2a=lIW-nuowJb$h;;DjdU*^Uj4T5E;$6lN|`oi~C{ zU<6H4hDJMNFlyxOZ<8lGq|Ke{k;TX7#pdI4PvzqyTK=b)1q)nafL(Y39K&^WBMbZb z{!X78W`#cJnr)axs74K+*^T+bDk&+?^eI!EBWBKY!FeAscB~D_1+e=kwitgDU64Fc z^H*W^pd5bv63ikLVNRhC^9cEvPtNQP*YxK+!YnTHSim1gDOieT=? zAT)#ys;QxgDlI7;OiWMg`xgAI)567~@!+F_MI@#ZTnyv<-#rNjjMW|MKkn2xKg~;bYAIn>AlhGJ8yq}Q1_K?kH!;?rY&Y$%H?j!}f(OXl{~vh(Y5?BzlS3u$No8)Z+E}{B zc5nT0$CDlBKA#`BvA-tj#*GqiKp|>{B4_}`LP2L~2g&KVy8l0O05=DK|0kIn^MQL?;2t}Hh7L5tm&=yL5kB}J zFQ5Kp<3`RmHMI=a4I8ptWo5JA70k!1U@7$eN{CjC={h>y>`Rvhu*Zx-9H&^u#)Ise zH+z}z1f*$e*C!n~P!YL)eXg&lXu5}xP}+}GtLPpgBALM(H&#VyXtX3~YxjZ!sPx;n zDd?Y6=8YQzX{YsCJv22_OgE_5>|M3!(6R~RmJRXuh!H%amMxz#ZS&3r3-oUg|gOBff^6c4PW2aC5gi*$j?0|rZw%*>~MiPh7G)V5(?u0j} z1-Pn(zEMoY#j*PldrP4c#7&y?CTQZs=b`-kuIZwp!Rgz#rxwHR(QRQ-hZ&_>;J+5T zH>!dEQp_giVQw)4JV53XnV3stbZp%kTOx`bXs8o#7oS(^uY-HPyfB;Sjr&6VaDR^< z9sA7~Lc(4QAt87A>Sf=Pm&|jE6PNI0TE2{F&u_@aZ`;cKjeEL#xc2}P9j2nZf=cJ4 z4|qpceo#Z54iCep*zBt^a#KMetZ}CVx^6wGdEgnEZO!5 zJA5DSuGM&|(Rldz;TFvGw`o4pY}swOyKbZL#uDl4(wTE~=0tLRL&G`1Z#+P9fnOZp z&2#)02WaDY3S2<63FQ6c;CCST`_IK!`4cfGT!4DNa=-2VhU4!}w4eT@*JI#nFmTo5 z3U%%0HR`fQ~&xlxddYQ}e{mYF6 z)df$9%8PCu!#ig3e>Mjov0qU{WS#cLjpr@)?tS(3*s;h6ZSBGc9i4(O+@ZtefL}EL zw+=uKz>WU||0E9}9Dw|v9HI%(vHKGIPR;{WgXCmO&o{<*aPJpaIdIk=$~8bfU$`tWYR8gM_9^Aqe3 zpFe5%m|0S?*y=qnbbKD2=0@x?V2A|u)H$Y|vNKHWcgYXC>Vtz2`vxo@O zM?s+=9PvMH*RHxGP0iLcUELluu?X(D7$&hCd(C>87cR7+7O0Qkxw9ffMke1^SUAgP z_39iS0fGEL35hDqAh*V7X!OOOJUN(j?%V+V+O>Y>)vMhsJ-v>oLx)PYF7Z5(upNCdcB#}+Wu;{lCxbH>5Eam4``xzH;H5#8&^4hH#6;3VAJYH- zMh6_m|L@O{r$+ifxAVcB-r<-5PL;kZouy)`Qn<-{Q`uIFt(DOJs?_hRR}$>Y-;~c; zb$(UKNKMrL8ran}>>C_d}3|W4B`E!9?0++-$h-*xqKY7uA7XJ(9&Rw)eL4m}6lUD~1y7`|t z!H(3{E)LVy&ijS`d?c9v$N?gT@jrz9Kji?4*RGXeezy)D!FG6r`U>AWQdzHTDaPYR zRN_73{#e7C9f;lazVB>$7`zxihK#*Q^=p z`43~CJb%MKiT!VZ|5u3d*695a?Em!;4j}cwTYQfLc!KL2z|VkZI2k)c81B;2Il&t? zl)=DK8+Y(vOR}~$$@}{`*RNBV$OGVGq%y8w?@d2_x-I#@fhJrYULCY%O<9njU}ezi z)ir@DS2p1D4h#(Uhws?Y8>6Y&m8h-Vfy?Y#(vBamPuaV->c^%{@n%v|HpfNQo|RfK zUmlmk{O$cPJUlRGj^$f8Ws0oQ?AeEP7cVw^vS^XbkNNXIMNFCUEphDFuSwIV|6pL} zMi%A?3yqB#RnE@2O`)Ns9dvqmPiAIWUq(jB0E3a+@8!kl-mt+l13PrRr%!)_z5D){ zBMzbCiQT3?xEsI=yLTgU4GiLYuU_?s$IlDL8yw&VE(pYKf?%9P_uH8=$|UGGB{OaS$OK&4F*I8^t^O?*>dsa#p<=!*QWi? z;GfF{#4`ZyAo&1%J)y`Y65t_D7r!ZK@;nM3%lQ9#RyXoG&eZp z7bJa!|H0f_fK|1wUBlaM#RdUs5b2Z|4dkj(sAa;OYpWJ&cIlCl@?>t`$-%&mX zzFmBm`BnJ!7xF9=`a3zm)G1S@t`-tfG+e))`2J^3AgqIKZYfSlFz`=K2x5Q`Gt9f0op$MS$9M~aC5AH#nhczcVUKO4?}{)|e$XGbMiU!$TgSPn*+pX-b^Ki3d> z_H6kW{-G069*-Gb;{WM~;D5{pjKu)o@n4PS8>3I3ZVuAdZ*)^uE+&2a-*f-*_#bBj zkoPA($J`(B{SS!mhZf+^_$P6|6XXOW4?vy}Px^$!2B4;x_yNZr8lu=8$nuntDG5?h zsf*mXvpr5vuP5cmkwMf0M)NE!seE(u(YzBUhSK!(`rZXZ^bYL;KJ8b33ei|R& za3~iS6|`c-u)m~aXVCifEn({F4Kdrd*P?$=f%lh(U?%CMy!=;l85wJB8JT@7^BMk@ z4WBr1?%WwO_*TxJPhx`uCd-yxdceqd*J<|bR{>L|*hkKs>72A^(KpoVd~&dVoChv* z^ca1!!9AAA%j*%vEokzY-~86tt;v4i`=G@=(Y-UZG_}8>rD3G2vx{nN zX{BmwYALd7o066?>J{QOaNqHM$FYaU>d?#ozw-a%V*z?hjT+))!^z$=jb{2Y9AXH@ zj8NFZgA2pR{11G(C&4%CVb&Ot(_)#c%w_WBEms%f6v>*)7KoXOXLBFtPF%QuVJN*v zup9ljAN4_cPJn!X)CLi6_#*D0a|YtiC-X|~BqtzoyTAp347uxaMc@jq-27;B{m$n* zoA4E`LM5h`f*34T5!2Q}~fj(Cxsaqmu#{&M~C0Utupm3=qJp&wH0$ zmHp5{!G$stXH1;=?_vP5+rLd#cDI?Dn$fzIS&Z-OhG2{^oTm>eBg+PxqmWnv z|KDSO_-lMNvFe~g~s82(8=Vax{5@edpD9`(GBf`X}EL`Cy_)~>A#)6i&& z-m|A8$tj1 z)8;EAR1+X2Rf3sXh2BEeA@tEgDyfkyj%6_xjAHg3GMRzzgO#0eArv0m)t$@3S? zn#H%0iAh0feB1i<@%7^3 z(WQKRv|N79&@4eNTDqtZEmcG=AbI6R=<#aXCbAlxN?K~78OLBQ969s+!qDj#CIro} z_$_eOxd}mY&(02+dU~q=5{o4f#vhHV-_hQ+2c-w~WS3?S)zsCDHq97Z(;{9-{(7h|8(D}`=AGr_#x2ddEw?o4)o*OB-C(6srFc=Xbk#YTeBR?cNwf9m z1fWjf_B|&+T>$oj^a#)=^u=fUPBEI|2X0W(Cm=S!gJ7C)g)TB)94>rOI15}MW$O3U zNnN06*E7r`J=)r!bVs=yvxIp(<~+&h2PX1bz|SKeU_Z;Au+(g6EM}n-7hGJB%XEvW zp6xta3i|=}m&{w3jTcEQQl7eC>f*n`|CsOJ58r=%>(-}^y1Ee|m~#o@o3^3LUK)zpwIzSEPgPUW|oM{c+zrXq0rcI^q#l(p3Pk2wT z|5x~jFaLM=C-DFs|Ck>}e}J9?U{~mS41hkKJ!}a%hzSzj^7AKtMqR)|POc)mj1Y106q=M&M)HOYgqyhV^VY5YH@0nitPlO7)MxGV? z{A)L?U8{FkQ`7Q}rsj?JY;5LlFzfq%=1eQE#f$HTEnfUMj+^@R1o z;eS8(!s3uwm#5NZp8qXqra3Twc0%CnvonI{fUk4T**PJzO=kzqHk%zZ!FYl%_&3vU zd)#jM6!EDmC^e`*F)wileZP^6+>DXfSh zZ2#TYNFG3T2&NyL?lSHafuU>k3S;6}HWdQ~Tg@OC_6$b3zUkDAYLfC;q5(E4m|BuZB5d4qXfU#QO zv7==EzlNUUTbg%B{qQI|Icjz=?M_u z-++0cHq02b2kzco?*yMout<9TzvKT;Y`_oy|Ht_M7cl_AKWYGU{F6BWa>m5~bo`Tg z0G|K8X8?XN;m}95F!H ziWMV4D_3>|2?^B(i;5OQ2s8sHm4-S-vZ`u%y0&(Mzm87ov%Pzr%(S$wi1F}9{T+<| zllPE0uW8d}&YUu34r;{n7fzVK!Zv9VufWu)qSCWw$tx{ernZfTM@yHNS7#qDuigQ2 zczAU7adPh1&BnHQD=VwgMkc0Jatjx(6qq-Qg=NYVeAfJ#i>GnV5}YnOLv5z!W?S8(y1wF&2O%FCTth>Li0h%QZVt2*rpjTAQZpU8B z{)oMk(~h$awTy4fTFjT26q$C-VxPtLXElH+_$4nOu+~6P@tBpS=ChA`_C)yU=@t9w z>lYyhDDWSTe^Lh^_{SOF3;d}LP#i&O0XVed$0}*3j2psEo3zo+nfBAn&EWgb_C}bQ zwqP%)3a11)Kq-2FbpKCc0D2toBM%_Gz#n-)*^hc4*%4>~_ef{Np+oK7nwpg#rKD2F zd_TeeU)g}4^Z(y#06%hodptbR_j!0?9w7%HH9*oQ9K%1!1KuI-C$#|L|B(yO_X;p; z91lIQ*cX^1e#6I?>>wnR<1Q&#>L)8(9lUyVWBBUT?Xhdu_QkGSHyR}`Plbz#QDN}^ zA$)uzfqZb`W@uGhQjEL|+y?7Xm7>+_`Rk;kKsJI;4nEnZtR8r?OjMvX4@N8Rt= z&i;(Y_usiEHh|!N?vc4cWLAjzBy%{Y31N3(AImGxzOC;5^m?w;r|76%YJ@V!4U(F-%g-gFyaNNm6~ zdMxl8@&U590RFM`4R=&33H}Y=7?NX_q*M_ny)pxH$LCk z4jr;4@rUBIeE0b(Iqz{+vOi!ax`pM-PqTV5X)|3}vTn)ldEE1aCQO;|OFc0*Mn-l` z85wPpO`EPh*tyfiO;79JN5$gd)rd6-E4r z1!%{PR)ia$s3-UjKWo|t-`|gGXZV>j^$};yDkGp35)FOd81R9|g10{woc%v+K`%Os~E_9%`*d3;)*NnQ~_nsfL{l>6AcKuZz@D@IsUjIj2Pqe@Z{>iz= z%NtFwPYxc(keLAN5XQX%w&{BUKg9s!a)3mV8@$AO$7p~_W@ygRoMndDoR4(u zpPdu>JNAFrfElJU0+}u{MMFm`NBOC8srpN`O7)lO)haJH*2_GRtrfl}TCw(#Vuj8t z-MXXijy4>7b*y&(!~K;S)*40d#fiw*ec&%i-2c6=_ESFpcX0k!??GPSL-4=w_`*=a z8OjY#FkuVfWH~Fj92J|5rCT3ut>5!%Py2z7M*Su(Ce+!lXJH4BB5h3wnrE zWM*)l!MtFwb~@8-fHrCZgm>h}+yG{Xy{7D&>N^Fo0I3aP#@81%&3*py`Ju8`W%Ef5 zVE2pNZHD%Sedq-aA8|f1y3b+XkoHIIK8^R9UCJ+&Tcn>z*9bfiD1%O6A-gSmKI?{PjzOJ}@=kEinlzz)2@!A@};eXkgC0Nww?o)G&$ zVgRyt@Qm~hxVfVradAbxYX$KBaN&5N}=7>j;-w)H2 zlsbc?rK|nL#0ml>C38Z6{ZO0;MaBGN4UOtFDh2h6^I+}XM5t!lf-BhhM(+no6;Z>`J~vlhz|y)2r_b(Sj*yZ2HgCUr(uTx z^}$1v{W;CK(iCjhRI5MR+_vl8uAaT0_YUlJ+&iHCS-W?O{g#e(uhun)KNc@tac@N) zmmOCo#~qGTwp(mTEY>UutT$PcSpT)MYo#~1jTbm(Q3xa;cXd*k@+-CKbCzbNGJ;nI*JM@mC+koy;-?ncKy=>dlQ z$OW+LTON+#kO=U2ML>rq61?3}w{A5@oIl@0GdbBzGda~3cE+TYcHw+W#I2i6;ZL5l zgn#(Z6%KyDC}@5{%ey=A?c0`wTeoU{$^**K52#K8=RoSEOTFM8>W?I`zlKIR=KWIs zfd9W?1Kt3$bQ^#e;1O{D82Ipud`0Wvq@)TL$`rIj2uk zS(y1tJ$!f++#!Ro3q4`$*Ea-6N)`hD*&(Y|Wx@tzgv!Zf#ctS8oT8yo>%V<_+QZG8 z?T@WrZzjRRgIVDJ&R?X*G?RO#&@7o*>T|cuH3477`-K(@BN@yY!snlxABGxVF!Fu+ znT2{Ebo5e`pD34Xf4{x%fXjjQ;~vL*4!a*7(09=r(6HC+l6fjyx5AdcfcFMp2FFE? zIPhkN&pbLa;D0OT|1@_@xB7Kd@0a>t6E7flAoV2a#Txm4`uWhvcPD&6*GThqa> z2m8+XpBudtbcwR?wiq#XHtsikcc63UBdz)kH#d~aT#(5VJ}Z*JbBZU4{WwP)%TbnS zmZQv3EXP=*IZkrKahq@_aGc>t0xxg^!)b>2d1mtx7g;PyTXADWiQGfEI+d3yE$VO8 z+cv%5)S~=WxpDQ&)m0*oL<+d=xYIanIFh*Ta3yfw=8WUG#Sy#w#`0L;KY_)Z#hpoy z=^EoEM*aCB^JS*Ynes1lfY|Ss(p6A6czMf~M{hye?4he$NRFqDPQK6Hz4?K_eK2-- zLcxtsv$CoW$Gk24dQ~Xue#8cZAs3+60ZyN;h`{T#`}bRD*p-QJbnK3@x9^O7`JyA{ z&h7T_GpC!vP9ARvGdFLh-MZBl1`eH2ye9-2L7`z`;INOT;=vo3;^^3(U}HnaKY9XV zF+c^`4+QqR(l1^dO2o`PZQs7;ZyPog!oMeyL-+lE5(AJ};OBlInFAvIy%)HCSiL&O z3A4lC46O4uFlY%dG;9krG;Brft-(t}qw1Twdg-@q+Y0@Rj7o#B?-YRjXAf=d!q1oy zCU$@x7r>Sf|NlGwkq12F;D~}OR{&<&w0yz$`gkD7aY zZqT3Z`W-+&{IAYG%n!)!FqzdMSH$BXga=T>QY2}$)#`NRTgq7~cT}=A-QQH8^&afDPevU&FLfGsJlRpV_2Jf*XnDUam_OWq2*6~z*dBr0T{$yCZb zmnoNeDpf4{P&5bizGTij-!adAlRbuDA9ep2wi|45?049cms>9PVAf~8woG-I!2*#5 z|APPd^X4s35f|TbR9V^FZtK?1E_?Q*`snDCc<$Mg2kaLHV6QI(xDUB=rz!NwleX|z zuR0>1JZX)(eY*kqdqpU60XqJH{c!N-(D0tnj~{zNp!pXO7B&*==RW{F@9ww<_uEnL zs}D0dRYwCy5bf!+&JgU<20 zcIbr;f>UfH-q^S&L|3=L8JgW>-uDmK{}UVVFL?lB0J8gM53GF!zPOguTUn)H6E64jpO(?_g`n)2FRD&<25qNjrGX+o46;8g=DLt;?oOIj>On1J7XG z3sN%}^Zx|@@c++wcoN*!tSOE}Ee|un{on|tieLls%*-gv2M#4@YxjaTpe0yZx*}LY zq6oQv4h_5_qz4dz7$8AOsU!?shA)(qoX@UVb7hl+gvOj{)Ba=pPXDP$g4C8~u+89Q zSjDi3Z#Uls?8du0~uPIqQFJ2%6)4M_i6}AO3Qr%gEti$Ntaz+w?!`w?dP=MeD6r z^X3aWCZ8($J&$GKPH&}jc*`{TB^M&V2 z{~Z7Gv#|;6l#|o9P*t^gy>n*(Fi_wNzviQ>TLA2r2Vc2T9|~UV&<`KFL*3m6B7J-Y zqk-p`r%zkJ!&^nLA9nnB84X%nVNaj7g*rL)1^N08hlYn!(a_~i2n!obLQJ3V;6W?! zUrU1z4}&cTfsF|A@}^^-Xnw{Mjj*`5;k1B&o@9IbmS`)hO41iV->?+3fHjGhmd&Xb zFZN_!xiX4fnGxjvT>)BJ)t|9jNAUkaTs--2*#J_nA3MbFi-{$>Km*7Zdpek7ZjZ;z zZVIpo{xJ$%MpP1Z`cm!e`t!bg>8*~A?rY7<>+7nl>~AkB>T7^b2(blmwzdrp&=Gvb z!4dN_{!s&nevIdxR;?-s)6!~6K6tP%*TiHr|I8UG=fnvLOZKA)Tel8Hty|X_CMj7P zCLvLV-hW;MY!dyz2E;2U6huL*@SU>qx3lu{S2u}^Ys{NGdES2txBscnnKWn8yxA*f zOY&~!HBvB9cnyy0lpP;-H12q}qZaym)g;H4yepN(b`$aGEzUT}$5J`!A2wI%yTJE* zo#;LnaBldt-)ZW=H>1Iw4q9F7Unb_733OQ(Bo*-{#|Z(mcw>C?R#*u~Ez`#Zotw7{tByLYJ~=nvOJ z$Dp^qet2}`J2fykK}QE$TZfv`(|e1&ygHE6R3o2AddkTeL+XF@{2%@wyM)n?@!TgV zsT_ZG^}0A+-EQpnjN}|WN@eWZMPkBPXB|a9eLVih@Jy~_c;5%?k3VD2 zlk0dp02}a|{%>wG4$TMIDLx_+aE4D`srv1cJ*ql!mQft676~)rav6MnQ)Ub3p1k*`W~bX_#B`g zLmxxRz}tY@-zNdUGypMRRx$kqA0Qb4b?K8M+ zxkzpwPkcRz^~VnL4d!T(dm?FwJu0kxtQzcs?3!(ZY@4mTtr~W`-%-YWgF6PZ5pS1i zFFUt*&0@`&>@)es>jbeZS;D$ST3X9QMdj+V?c3dbQR|}_7*v2qqnd`Dy|BlR+X?oA zyuF9${v7dnVpP;H^g4T!UcPLJvb3y%_E%*X_T$6g&qEQ*hr#bh#l})e(B@8u-Y4{W zds5%LX#r<<4Gn%g92~n*(CUn!(ME_aXbSWJp`9|Claw@+85Go$@Zm#q2=@HJ!BH4` z=1e(qfO?SIx5b}1(+$qSzF5TlQKo=3$tFAbR#sFIc9m-)BdPA18cu>i=Z^V**oN$2Ny(HKVq#9GR<5*EW@S}dFn#(T_5uI5F#UHwcfzCzlNopzgd{X1 z4s0^r^m5muT}ei+M%|};P7NA+8TTDAbB~ z*L`o#0WYJW!(N9+^j!4&HSM>yDL!9UEq+%#SKL-SP0CIxP3(?X>PoAXiR>2aF@M1S zc@*;li2)cvchY1YjT5+8IuPJcXT zmtsd*Mp#lu{g2YIuji#lX?ti>+uXKMYA$M&lCu(};HW@JeUus&ekI(!;?ar*p1VBd zztgh-d+sB{$c0CRrf>hH#;XecetmycRD6Ic07-G-fHP- z*|hfV+QemtmOWgex#ZY<>G{f&7$>oe|2~DexWx9XUTt_qP3@txj!q2rd#fXjjq4(z z1ie=c4Y$_oBD#O zQ~#^*KY8wC20j@+Wkr2O%WYS-xf#AUEI;Xca^zg#xe?^WBm2MZ@84pt(JuK+x|;hD zU#ZY5(He!1Yg^R6X!PuI*B;XOraPqbRd-<9=j~leuap}k?@Ja5-4;rdu#rd|!@q!) z03H8h@jviL_VzsKhw$MMKfND$!Fas?**)w4{QkEYMl%BR@9CG>ezk3WAN;<}BhI7C zFVU~ZBgUiKKFGe~p4+`vvzKP|QkGI_h&jlg;f}c=KT-qKxT}%3@AbaoGcIS!FMD6E zwDPs8yc2$>;Zf3~jwfkP`tPOQ8@(EHl`;#(Tu_iPb;$n^rSGjzX}N1r8m=1DMwgA$ z+Rtl;q~1z(2|X67M_gZwx?L9Qb=D+Y<2i0~#Es#7{1tJ1^a|S*Nr#*cRXq%Q*y5S) z*_l$A(wA19)*n$2(c_fl)M@Q+-J)!x9L1>5cw^DVMfx)YXRLx=FvA%BXHK6!bB%z2 z(!q7>PCwMra`XqMZ>)()3%K>$<9_9P%KNFn;&z;td_)<}Lr!a$zCe>b%Hnlal~5k~mH(GN^^bL-B8 z7I0xn2~|>5G+F{)y~3E7-YnG9qM`ZcV`^Ib?eO6u&jSZ4f)5>P!q3!`aPs6pgrQ-# z|L)xlzFJx}p6K~GgJW|n-XFvL`0H3)@cZ>wc>uw`oLsiwjvZCesPUy+SPbL?|M|dw z9^(H3#Qqh~Gi$-$X%KwifA*`Zx_YoUIJk>;{d(PN%ppExV~ZmE0i^#AI}m3tES%+r znPBV-w?wO|_9dvRkH!O z*|x1Z5kDj1i?FLoN_RDcgtXxQ7ma#aw%Hwe)a7WKsh=rz z*6%EJ+~XMPdAfrt?>2S`J{4)=e#+M*{8p?(_T%cF4PQ16ZFbWb)o|H5u*FfMd)@2x zO_C2JO9gHUWC&RcC5hXJ6a1$W|4;DGVZjkY$3Nl0gzrX`Lot)N@gwqdr__oMFDKTUet_B8cr&x5oF^c>() z)FsNCW=@%em{5lU4pRm`29%bE7NzE_Hn{fP+D?h567?(YtSDl&Vohbb%oNLVl_eUr zd=lsX?(YfyN!*X#ehljk)|g%Ica^~Jw-BG7S)18k(_S-J-CjMEQJvA}n(o?t+3Rw> z>}6T6rP@m^7bz~Kh;@ZKU5MP z-W`cL;M>)!(;qHh9z}dV!9M1LGF|25N(0c}hc-YLX8ML>NxrY3Kq1bjLa+xE4D18< z6b&aFF+jMeXft{M)iFv+)ydno)nw@EHm2_1-{h&M7k_8#*5|tw6bzQkne#v11Nx8I z4d_iVuV5CH*&?%lqw&TkdmrshA~kPQA5+TM6PkCf2S(x7N7lSqGqCc7U?2Z0fdMgl z$>CLwa-)ha>!|h4%0ufvD|N4avj#J)66NT3XYyU;OZC|4#=m z{>(!&1BJ|ml9a9~Wn;!JPvx3QE;Kwch)xguIlbGEyoS}y`iUGoT_;n__i%N zC%U()scNXVuXmJijS#MZl8Ta%sI;g7#~{Zp8%LW~9b27p%mD<#2Dr^RIwu%C{cOY6 zh9%~1<`s5fb~R69pVmB!e^&Q0r@v=?1Z)1M9^2B2;w-lDEXU!}~$%_-wxW6H?S zh}z}0YjD%YO`WpOW$OiP1dCWNvnHe0AF=e}(g^tf7*fX{!~9sjPp*vCjL`~D6|!&o z-E4A7cIrs3NbYUuY8dJm=oqf+tQ$%wPw2HzwC_6p`FIikDgL)i+nG)+mR-DQ(y~c^ z*!|&QVd2wNR5ZG+qw~t^!vFbf&cGLtaj1f@l^NRSBpAWT0kWl$kkPnwvXR3jOZFprG!oj~`p&G1CiPjuPw) z7l*)JL|a?eq<;9&lI!W&nI9C?mGA4@k?r8n5)Zx4U^BB4Z(!dY*mnb;?$>qenta8@ z`@yp{8mXt(>5iEk;#=R!%BF#%GxfcsWHPD!{fztZkN=$wATvYc{hq3-CE?%?NIZ3_ zE9dg%;R0)G3Yr8|(f#{$|KA90*KYiO7#jJm1vK6e5M7|Q+S;Ka8m&9R#-`ydGyonf zUmi*DkD1_fV80{~aX+*{I%8E;2O)+_MFaa`;^M=>LPCQfLPGt)!oq!s`}-qAM24^< zJRBn}-4&yt&%A7MPgYIP;Hh4Hesw09p(C-tuWSI__v`%Ty5h`=_!|y4 z8eOAYJMyaX273B>=tOBlgF{qL7j(^Tp#JCX&pRw%SvCrt6G|pA0I376 zy0j{l;NS9_WyOPt2Q}|g-`9Oe`%wQr{e9EBtaqKSvtAE8%X~IUdI7f*Zc&#aFHxt1 zPE-5c_YZD&+}^eR)%ph1IEq;>u_j^GCv2hRLK^c`=4kdC@cqC)9ru{$!9D#jTQf(I z-rjMS(OgGIDnB5ADnzbNe0gbB))2IO z`}6$#x=_b!jzZ7RA9DddyLab@V#YV&@#Ch9FJC(HgM)jDLPC1-JUluwU%zgKR!>#v zp+j`+2V(~)2;4xh8)b2r>q~j^q#^Cai-wf@_v=s>tU#T=#22r70Q;`!>3vmJZu+)j zMUT&tB~*}rz;FO?@Kr^n=#`XI(i<6>khqsz5dmz zRjKz`StDLz2gq4Us?Z-if6xZ#M2~MUSyPjW+pu9YOiHRB+&`UxA|mZUA|fr=`EAC| zZ(F#ORClzL)L^W%bZ`9X)vZaYs;wznTAkTOMtztY>hL~sBH2z~|Ba54(*6Z==l<8^ z{}X0Tn8hf>C?l&SYpi-!^&|S>Il7Lz-I(d=)&8X2v-!j39{D%&eW-DbNZ3nK3QlV% zg)eIe{)c58)z#`=-=ZNEQ6lcfQoV{=V;AgUv03Ym|yhjT>?daO}Bef30P+ z_2weX0r(Rez;~7}ZvV^uCFkAGS3Zt;TltO2WJg_zxI!8G7!U1r-rJ-0My*NizFZl{Wsc-or)LGvHJcm4c!@D;x%F~7_DS83 z;QmJqAOW@@0doNfYOmD_Zw1_H@<{XO%&X5EXzgho?&$9ru4t?%iq?OYm(P2zppg1eQ8698_KeTO2B0QB-rxV~ z{-4AEp}M;Dv8V&2qUMke9^cIK=LewkH(2cFH&~52MLS{uq6^kb4mhcZJ?Lm_8^vn_ z>8Nx1Vwdq5W&oeCvBiE870vZgRICO+SbNIu-GhkXM^ks~7>-`Qz9&dRqQy^GxXw>h zvLb<&}hlnySFdN$f(KXz=4Pxy1Gxc zDJW>qoHp%W=D7c3@%%shpb4`l%w-T@kQUx1d`$7Q;(OHxs#(B%qxyUG#tpAkn&n=~ zHw!%%ZWDeZJV<(7s$bP8Rado9MQ6n!+0XKQVy`4xMIMS)2;ULOXMdav0(*}uD_wq$T{Xb?h=6#e@pT!W2` zjdZ?|l#G-Sw-C2J8wZ=Vy|#NRunQE1&;7dW)Ut@ZPxlsIaKBLbJob65TcKN{ce!_? zM~O$%w~}w|F2ydrp9((>zs`A0-AlYTdNKIo&=J=oy*u9SXiHafJ zW&|!ezbFEEe+=?|f_r-H4>LS*EAOsM!K`178pv~Y?#*#@?8qRq_%=3mH28Wy%mfp^ z?~B;qS5L11kMjv92<^m)(#X@N%Ok+`OJ;(>89+D!ih^)Y{60B||4UFapdZ+QqEH;# z$&)2`z6^Gx(sSp|+OLX=O|CpVJ)VmeQ9jF-Q2{(WRDgiM5W)W^S=ro=>({3{ZQ7LO zj9ECxwQDo5^P58O{VUv$f9_vwz)!V6k_TW8Ak7OifZ*k?iv*uYtdUV0InW{N$iVDV zVQ6Sybz$L93v57pR~OZR(++K*`m(a&f{2J-vbW^Cb7%e|HZ~IXXSgdURDjp7HF?jT z{wxE7kxb0_CvM-~9igPu>@Ou%<&9l`FAj-&8o_} zU+lQpa~T_%M7ig3C2~*YiUjWo7Ge**MD)36lj>*nf&FfVlnG{hO#Mwqk9r*)-u-3w zfc#5^b{-qPa_*}nN^468J9|1u2|pmo*NZEPhojS?2b_YPx@{e8TMynnShes7u!{cv?{fj9?oaB4 zunUOglWu*v)#MrL*-7k3S8vzIFgVkD2+ue^uO9kO=`>Hg9?r^{PE6E2ulODzm@1p?EN%ETQ*H%9oCtK{s2lJDHFD zC@0SSoYy(kIrp(hv8b!v=iAF0ns$ ze*(}G^vB$Pz@9zT=!Z5XfBe{y7aH0N9iYLQ%F2gv%-*n`5@*uF#uhju^A|DZpg z^qiA3iPZZ-)YO_V*V~i1Z{J|nzJ2|v-~)=@vZV>!;T1koQu*$}!s%ZH1d?5ag;U(c z#nU~pBZ%jV0_Eh&f@EdO1H{EkeT0Pzu@6ubjyS?YO)cZz#*LqkV*f{si|aqm0rEew zACu-yS}GO4Tb`uF){7Idi<*mlzE*P|i-EheyQ8n- zUQw@OUynYaJsGj~yD@as;aKn5dy4gJ7ddj4o3kfDOOpl;tWah%=12}pju^hnd49ayoA2G=DJ~0`y-!4c!p~*^Fg=-*M1q_^UJ<*}{TB}~_rc~b46UZTs{^l{qbWX_P^NYXx{u}Jy{XY7Bq7Otf*1ug} zs`**7dYALA`t45J>vUXn8Z3M*S{~CLw>c#^bp&N2=gbT3AwB=w0k@kp-fNUCzq~vE zoIj5_cXOJtu4CQ0glCDsgo%ITjAL4~h-t67`oTK~55Dp_e?A@DoWq5WA5*!&K4#y# z6HHB;vAbW5-Iqd$z32L9Y32E1E*LYx^td1PpagZj3fi$_RkWi=D}nc7I_|L}NXI_v zeRS+2{|_YoAH95n{m>I9==euIP#SpPK&9WFJvH82wluh}TGi^lY+1j@oH-PTrzme$ zR?3f?n*t{W6)Yt+?6Z1x)7K3fiqQYccHgxt&tu1qJl9Q|vK{2)((F;cBWJw7|JD7! z!~j3r0J0y1S^%jDkeNV7%-xgTz(;ZMG#7RC{19lFC%k>zmg(u)odY|NL!(5&2@kE8S}Zu`3mxA5QtE`i_2%{5*Qk``*B*cc;46-dbC|==9=*X$PnK zW9L3_$*CnYVgpDnfIXII`J3{YR<2eJuJNuN*;UyC-F@BkdL603bP|n5Qq4+#E!r8O&_Qvds-!9Ve?_2KM7}*@zOlzPu z1=a?(dX{^3ea`#b_b~2ZzqyZjm%&Ga#`SmCm&`pqHx!zk-$>lQ(0pMydVbMt*7SaV zJm&agh3*NZt$z(&!cSXk4BQP`4|*MJ*K^lv)pgZvI^lkz@tXg&X43b674fR|S@^To ztG-tox4hd@f?42r#7>{s_p@8GsciR@KYY}qaH}t10?(Wg!AXeu74cS0r4|7u8PDSR5aOt!rpS&ojZ*_&^7;v z{U6Nv#)1zt(*r!9q3Y^QvETzn?B9{3qtg<*V@F*WxO{w|{r6QwB-xSZ2;h7I_Q`P+ z6ijjk?|=*JfD3qqo$wk?yfd_dzO7uDq|8#AD#wL2bj^yj#LcaCEU$+_98dNSl0lf~?>*)9& zKl2YCPBgg5oS%KLeMfLgaBoIY#$Z`h*+^AY6;)DPLU@Kp{AvCJFWp{tTfVexQo5p) zhg|?)*bfhIg!)21F!O@jg{qhFFX{e2qB){DtRal}{}$IG*Y;Owuez*5t-FspA8p?9 zcuU37b4#P3(cw1vHUD_@_TwSL?aywI1_7 z)^jrOWbYxrLtWag+D*G0chwj<8dV+ta=hBq)wGt(0IhklCL90W$;_9T>AoL)U#A(@ zFm7SsV&MN3-`BjUQyDZB6}7E&bsxK(JeineVL|rlx|7V!TOyAhtqIW4DH_8*cqj77 z@!GvR4>3R4^{I}vv}{SVvg$v z(gq?kL+uKd9T&f-IQ>6*K%LvPJ-We|Lxds zp0m%)4qj}rIFk7aa}4qQ@cZ$o`6sM;0zJa_&?9%=(|pwDXpc#tNk3)>`;B~zI=4G- zuV43eU8(p3@eH!_Bk@2YeWl&X+0!VCpIz71v?8aF@a>xaXC*JG}}De~B{YUJ@nB<2Uc^1PArk71wq+>k?uD&X@Q zQ>?AKGwjR3_f1-w zALJC|?ld;D*?PMAJn#w@;BA`2B4 z?qJ-_cpjWHt}M4$60x5WzvcCoqI<#jT75Hpdy49dhI;yY=r#8KUc~I}om5qI6_uEt zIN}=WI*9sO&%pAOSU`MSN}|9up$DP){;K3Dl9 z`U$=MA5;@WYJZK+dCrYb`z7gk=|t>C!bzHRkZn5u7l2!Y^yYa2`{I=gpJe zFDq;INJGQNXVc)iMF%a&Sq%)cNmZuMNXs@s#Fzuz16 zeXofVDX-stqkLck2>$&SE~NYs`v)NQ4?ygXzBm;oBSS@jH#>au=H4JJt-1jG42VC9 z!%5!&y#@TN`REIzk{#Z^8w30;{FA)kXZ(}L;0qvjfbf43O(5`s)A<3BBcwqGAn%Kq zSh0tUOoi{-wH2@fg8?F7X2bZiqJkKCWc{qgqT2_T2u|BQcP3%;TTKz^?c zKmX^$TwLbsmoHaaGHu#_9rquZ|Cz}(bLE273p5yaGMcfNuzY5|&K!qccEZlLJ4+vj zK5h-l3F;|rEFBye92gxL9-;exdX2rVp32V49u0^N7=GsVtnc)T)18u+B+G!4aKy>M z(+*Dyo_%a~@ca|=g9-14;!VYDTX)-L*BIAMQnM?sD<3AhAkB@RiKt zdk3lr_6Z+2*#QDazc2Cs;0gC7_&?)xrs4tZK@Bk9;9cR}_@&@W{p;k{^*2Ip)E#p? zR;6U8lt0~gdJz6T-zFZP=rixkybz`fOwsJu*b|T+C86d|c6k%w`}5S_saNg&vbPnz zkzU|`;F#aBK79}U_N|Uv>(KuzBy#~|{udk{gy(~tc<_8AvYWGiVco}S&7{t>f05*( zwbK^;`Tr9TfKCxvus~W@OzhOHb?aPQk(+v{s@1!JL*GMFv)FU{_99QjxHtt~IKV&Q z`mMr@a5K0!d(&-fM$+%x836}(e~gJqdx*Y%ozJ#y72x5mLY!Xfv3YYnw5u9jP`7iJ zlWX@~x2_Mo_`{)?f1zn28xX+ANCmO8Q^BbFhhiTl z96Y&E%F0xvre=SLwss?8i!$(w6~~yGmO+cRg!CRTFOc~G+Pd^O;BV#u|6&7vQ3v>C z3_$Qd?tnWa9^5|3@A&yMKdxMv>ntQx=qfH=1l;F3BA@?^93Fl@(S_vpz(4W-U%>^2 z7%09kW&Zf;cHY14X zG*cvWc`IM|y=e7{_v((%jUUJ>&KoKzD;X&$EEr8pO&swJ^Bs8Z`n>b(i?fXzE^o*K zho>LO*Z+*$Ys?0~4tRj$iza+dI9VQh0jn>q&XhbanZ|Cy9!+>a=sEwP@47t%|KxKO zuPbJnIG9w}h1%78%KTLSwcu+b=KmVV9)P)rc@6ZtD_2}vkpg?>fqK8^j8iiL7%dqi z*{-t1bKT%d0%u1u!9Vu-ljWbu7jF5mrAE(1ukEnM;htkY$NEtJ>%gpFJ?RCISpb&n zEYYmitTFhvjUo5TZ!J&6X9X`mw)`oJHjCNPwM#YU^34_cGv80XvndlNO_Ao{P|%l@ zICE82&JpwU72Zn9onKb3uK9xc73S|MT);W?6?Wkp4)$#-Q4?qYzjp`pHU^-(L4l8F zH1hcIKI99n$meUnqTlVddUdtys#UelqM~)EYd3rm7H;#9m+$e%+-umbT_aIOMx*i2 z$xJ?XZZwT>Y=B=Q*3gim0q4-cp#p*bK*awcz<(IFTu33}sA|{qbY5*h-fZa%dJxFrk z=TE*5p=@I=u50Scm+#`5H}4N~9KYl5zu;!d;welF5)6t<)Rq`A?Ps!MxyTZXS@n3O zXG(dtfws*a=^kAfRT=%QU9BTyTE5MQ*{iF*`|R+zaO#8ihf&LSmP0DGDqV{%FREcU z&rmYoVtyX@0@J3So)I_AczWdIW0QkLFN&s~w?AM1%;#CFYq)Edf4qNhcw%^eSVGt! z(FA_u@uth_gH;RR2gjaH%wPHb@iu|@c2XzAAv_@8IY7Q&iO!xE@%w}a!a}^0v-C!@lup!qX<}5bI{l;nit8*QgY<63oP$Pr!uZ6MPW&)7UPv z#bGx;na_qVjoXGh8T?*x{C50l>tC)f)A*oKtL328wAW>CyPm6FJHfuvTctAL2g2#Z z?<1~?X1vN6g%i2#`m$Kqfmm?-I>Gnf_}%v}p7xis;ZTvDD9Ozur@u<}^m$p?5AS7V z%AspL;=sex`iYOP-a$;P)=5&b8uhFSM``IYXL%)(OgiH)t?1#>$dii$0sn>V-mX=}HK?BCxN zb>c*C9C)=*Ya9grhoB8QLijdPj~t<*u?rha{6BQ@#^WD4yCac0IvvgzYbt~CU2-CE_Nq*MeQu~d=^;3VRENQRC; z)Kwmy*ZLeBCJHPp8yFzu^U;YRa=ltjaC@s=q)M32Be2&?D(e*{Kf_DW|Ej%si zKPPc33Z2U#;d{aZl6NJ$ zC2b_z1aAm6uw7v<1&?UvWRuA;la5ac1OG;@*&DNZvbX!f`$gM3-*=sFyxw#^ba~i* z`Q7E_V-Jqi%Ac3dLd~D9tu-F!|L&e(ef%NsAHzMlr@w~&AK?X8vr)@E{_%L}jer|f z&*Puhzesw~WEW}Iaw*_a+X1Ho&8kmTt7UCu3z;pLW9Ock8w|S`&3&Ca3HyF&;POc) z9DVq^W@x<8D8c*dus7MX|I7ZS9iMkJZ2q{pcKw_6r3Cxn^Nd+`Z5bW+i!U#ZSaNwu z1mktac<3hhGaY7nylmGpvn6YnXwKuECp2-&#D7~K=4D*Ea;LcTp);$bpF9*6%5cWK z-N)Ipd)_lJ)PG=Ns=_PRT$!JFc(S0Co%cmnw%A2Usr(E0Z^5Tqi<&|cc*NT~ zqB9B{-ihbV^`~FHJd}3s+z7O?N1=&9--iYF9o_%OA#Z@LUQg741C8O>BZmJkg{EKy zxB=)f0GS0Cp974K0shJc{7WA2iPsjN` zxJTSiYWV2wr-8q-I2tkb5V^aU0y5QXsHKi2;HC0PGAp2TkHEa?592B5ig$5K2 zZAs)(>!mT^#w@|iYP(mOR}bp;gJm^kqolu1*YYebrION;M!mzl|3CJw11!ob?J}T< zih_uu^dg}2-i!1OqJRpB4Wx(^dv91{?T{Ldx2Pd1zE zCcDWd%RfB1`G%QsnD5^EzW1%?j9m%7GV0~)HEeXr^WE39@9w^4uWMcnYtF2xR@$qS zkMsEzZ1xHKu74%-|LS#GCvf(X+#&SIOqZF_%x=taLOX@iEiYP@f|uhHa`Lqok}ot} zOu5*0^6klPV1M?mf3m*I{HA$>+F7-7o_##2Y`fXs3LFtgU2@GQhWyeN9|E=c=S+-uKGN#r_5c z)z2+08z%GtaB;yK(DlySo75i+f=hS!jg-__yo}73WWt+gZvN#xYUq#@bka=`d@wi3BMopOCR4ZUtR;O-?jo^j1}+OH(CmOfnxYk`M?`YBRas&t|iLE zq{<(<;vVoqeKC(df`WbV-shmS7CR{ zuFCVNXU(CPhw8se{I2Cx!l}-~v4{J&MQ$6~7_hO|;Y+<{_Vc{s+Rv4=_~hcO z#gKi}M&?7mRc;I3)_OVaa>tYOCtU%V0o{J@{JPG^p6`GiP!D{cT*Ug5VB;qvmimtS zDEE8zL+mN62UwHl9-jM-eINTPE;p|09IH5X&eNQ?Y_=f${$Jqt`RTrxK80?E#JpK5 z=Irxb_ee@S^HW#t41=#7ZerYvYx6T@qN9>9f^+BqtAIaGd{E+DF0s`l) zxw#!>*w~a1_xoG_)=$^+ukt(b0Z<31I8Se$o!|z+6S6+CF^V@7OXaW2ms&lrYB&&f zp!;Ur&4Ivofuj+r5#!Nm(PJ-?UkpEtc{p(T+3B7wzFRt$U0>Fsc}26y_@;6D%Dbz2 zHa>D3aeB1ri``wjFP1kg#|*C;4XU11?GQR5^of2OeJagnnpliD#MYzXzmr%#@!La) zFaK+0`(M6qBk;sPow;zo? z+PnAl-k~iowhXU%w5AL8e;s-k3%Cz(Cky%rriz>qA+lefbwjIiN63y2)I|116-M=C z*JKZ*R;KpjaqsQq+a16SuN6Hjnh6^!LExl78ZeOa!0%Tmc~-IjSm62S5lZ9T$@`So ziT5u#ULEa650@{ii(b9@ON_&seqighy|l2X zf1#sO9*7(k=Zhn_zC==}L{=Mn3=E-hVn7j{K}zJ7HS`tf7z?AjuiE^U9U zrq&O?U=&;#1Hb}l#hG6PZjcHh|KJ0tPW1Gwh(a8I)B_Vfo^Nr0Kf(_{{GZeUWI^_G zarWoI-_OI@pEKd-6QBQ^{Npi+_lJX5ARe^~X&X1TL(colfnQt=e4#4HKG6dez!~|t zd-rIDn_EvD@Qp&Xwabx9$UL!Vk^d$>zI_IqoR-{-jDM+m-(S=>v^2E9m|V=G!(%PK zLH_8{!%IWKSy8HcPq#tuo?f%9ON#d z^P)?l*+LUXai_#l+$e8oooSWNVEg{2+z=P*Ts{jqnT@;6 z%x*ngx9(#i@Til$y~h$x{OtY65RXRgj!qzCUDn!nVhi=mYro6Sn?8$o!9W z0QomxbpT@7>4+hu-Ga`z#Ko2L9UI%*3&8F6wX!PlN1xDBa0)#_e%}`y8@F|Iih!e0 z5(xg=Fr0~D`ua^#nwp()78cz}4i4=}s8K}SaV2t*RoTP_0B11a84X1)V8ZVIS`YA3 z`Tr$f;2(T|pNj*)FPNwagnU1MyuK7zvUqWJ0Q{RLuyyb_{R!?D*NJ@p3t)>y zgFguVeRI0Idr#r6T_ceHFIABL8jR|%I-mr62H9J-^e5Qcw*n)y2E0UxyZHGpt>or* zR+v9uea4h2f0p~k%I6MAb zdH?ahBX$Jg0~g#On4x}5qul(Wd4t29wcY4h8uSYC?!y`0wKrmK2jstN|LgsIu=@v{ zpF0n%da$Zp?}}axa7*&}4)MJcJ}#Vwm|qso^a6)R4viOLF7!p_MGm2df3#zuV{D>_ zzrA`mt|YGi+S_Yg7I)1nc~5X>DqNN?(Z8)<1Kyv{ruWQhO&^-p>)q2YS3Id0De575 zL3pLGJHIBsG4R*^4&UcrtrK~jnUQghv7GG6Z5HNdZf{usHUa)Ou(rQ|cW)eV`XR*l zyP~bFo5FN;E0N2~o{;q)dRBhuT_KNuEbl*k{8j#mUqE64-*mtY#0u^Kw;#AepTQN> zoQdA~9AE;aKYiMnaO+lUI5+_xgJK0Vd9V^y}g5&w-7ejhtP6grQ+^ z%<9!;@#u*HA4&O3)C31fOJ||pH!li33vu8SNr8V4%<`cE$axuLp2+{_uY=eErTh1f zXM1?`rywpGfw;&yaq&=RUfu(SoSaqyvuFQhbACU!$7tzkr->?xYAv-}>SeLh;^wOJ ztK!!^UDxUu>NvV4V9k)_L(3scKg*F74^|9V-d)~lc*C$k`Mh!&a$>2F;Rx9Bp>uc4 zjYrJ9#PF(N?XnxonpfRk)v?xhZJ(pBsC!bc%I3OF+a^Ef!L84> zjY9SZaQ1iaj@aFWcwaZp{XUmqmwwxaww=b;jq4Q8C>9GG7D!!mbkRG}Q=%DyCj>Ke zZ|GJ4pS$Zx>XX6Ts@&1GzP9oHk^ZsP{?<`o_YDPR1@`TW*w?0hQ@u>)s&t9oUA@mM z{8luternrl8)VxC+^|;k`sAw~Red7qB6)bR#o{%Ba)PRK)9I%F+R_4DVEy5i#D^3>G+{HUnj zWIw--@MFgs{J=qcO+zCe+N->% zecQ^x3smm!-%$uWr1#sm*Tt+@QT4{svOdYlsWTO_Pih@L;Os9a=RWWSYcYrpAo5=Z zy?~rR|2sRo&gc629}kL(e!q&F+f|vJU27H{-9PZP{j)XwE4;?W$0lTGW$3ibbJ=&R zeO5o)cz0t7>RCp>k2Ss_V8h7TfVE?*9<3g-@LkrWcTKlJ>6}tI*M9Ezi06fU9pMXh zFGx~6tyrvkQMbnQs%Zo4h*ogmcCEa=vcvq6d6Ui=oodAsilx8_%Yn@h4;vr`zCbK= zKF{}lTSErl*=8J2`{47bS2?3ny6EttbYk}}Ik_ZV_=Iqp*lDqxjSn|AT#dcj8($PZ z{JHJ(c<)f}c;`UpXjyaF@ay8&1D6vncdhbYSu1x#u1x!$b`AP}n@|tbxiQqS+cDCy z*FMC)0Xc<4)qScrq#UF?MD;|?S$SChZU2yc!2%B8Np9G0X?fw^+O=`${cFwg@*2y; zeof!KdpyO>tv}Art~EkmzY?`<84nQ$_%-rRWS)$Nkb4pzcz`PzBp!%ZLON=OO47gk zuK5!%g1dm9H3}@PVT_*6&hduI%8@eQ2xfs-cj(2kWksk7$oB__jz2KM3AV?-r2~E>|KI>l zhfc{xtxq;;BMP8v>I$!4Z~y%2Rd-uXPG5I%@jz#Oet%;^LQm!M=iR80>?D}t(4m9i zD*r<2oq#DmUIzdFvyac0S`2a}x}bE=p5a_Kw_fB2TOa7?BzX!8pD^O!uwKN>y!fvm z&%gCMko>cXx{8tUI^!KHcCNVLaMmFfetg5GkWJ%m!ER$)g13wzzCE(;@%mnH)wG*j zGpSQNrBKYgjX7>Y{h;d)eO5cERxW;6 zJa6vyx$#pxro6=aBWXNnUf~*r5slA@M!iimYVM<<177{R{s5`@L;pb>;(o=(=S=GC zZ`*gdKXHfuvUPCt)6JdW;%)_>S0nNijo>+ILGH3n>5Ni|z#)M&;QA#C9TQ50{AZv( zKUMXD>PPenw)wyJ@6Y>`H`3VIIF9pwtg@|gB(WlK&^OJu8+-{Zn)fxUF={QJTDG9) zx6|>3W3Nl3OF!_#dyp6W2n?iPB@d;eq86gIe6oB>!1nr0e*aI`Q-+&cVZDZi$4N`e zyMcCgnZVrb&hYRU2FLEmdr!~7#Ely}qtU-ZVu61r|B&UMv;QYz17FYoZ#v)}!NbJx zh5qnG3;=bnxyjqM)tBM?Z_3OZ=x=Bk9UUGXCkSl)z}RbTX&EPYqQDmJkH2@X>)F12 zjkiop%5JHtl|8kzYz$korZvjRsRg*=P4EL6zztf9bGHh;2;~Hi0QE+dVHOru;P&|x zjNE$waP)~UK=i*=tCbH8X6mF zXc%tE&K~##j-@Uv zZ#?Q9;yt=8blVtuSO?ent?RbBZPjdW$)HB&m`owPCvY)f2T-<9BFL5W$S>TzFnP(L zB{_KgBhLI1G9-^k762C@mGsdf_7_gxhn~7Ov$rwD&)muwPwzSH4W5re&FxFr0Z|lJ z+9>z~k-yakAo9=XI_tIU0l5!`XN~Jt-?Z&;edOBj{?xq}xrtW0dv^7RQ`8_PS)+MD z^OMwZsRHhO-0#@;vM2B#;eQW)<8&b(p;X?(yzdPz8x2-+asNw2V&ga2Y@Hoo3wRn$NOEoTGDpysz-ftO@f5uW(6i7G170uig*2i+ zp$4&mqKO#bR~_(6egJgD-}wTBHx%_h+4q+$$qK|AB=6eQSoZ8$cV}_&AmRV`F&;2^ zJlNDU+C(@Go;@2wujCitsf=ZMdk^KH-lt&8mYyO{&%QF$Ih2EMs0j4{`7SO!>8nopIW?=gR9bKWKc{PmsA&o+%9 z{yhxac?3FO0Js>PmbWdN^e^gHLw4Td+zzAlpp7Ksn?1nb#Slg8gLymWC6XR`ocnKQ zdCwv;9x>Bv=4;>vW=Qx*7NF0rNa~pMN1;Q)1*o~pVDx58fDU+t{9pt|qQu zKSg(o=IEW)tFgLj)w1sH`i_kcHn!Q`wQDfBVp5@YTJ0mzPo&0Y{?7St=k1yoPtJZK z|BH?+dXLyfGV*`#OfH*L91K3#elPJ}Kl&j?fZH`1UlBj#m+jYg$LFHbrjg#N6eZpD_Rc1wX&XUf`WQ zn_r)k)7(W;a`!o1-6!G8mlq>v-I)N}KNi@0uM7=8J(HKu|5x&ly4S2wb@if`c>ik) zi@GR`m!_tl5yP(n2W~0waq=KX>9@g)jhcewU`@@Bsr&Xd6$5MVGw_393kVw)RV5ZtiH}^XL8HXU}#Ay1KRp;(NVva~p{Re{RywonMl-Zzp`cJveLI z;0rXpgI$s0?cIoaqSh2o&o1OMx?f&qf_nu9H&-cmTVy zz%iZffsTTL+ch~j4CYRo_Ord)zsFktw>|?sC{sB^IAj*am2!9~M5(<`R+7MCq*jm{ZY z$sd*fuyFgr6goG$Xc{Mc4$%X@K0gpfYyk8$rYt?aw9@pVSsmh!4J&Wi)LC4$s8T(p zTDWk>!o(@=Qz99?7^7LXv&15Hk-)hRH9PycQ&97hOzQrKFQai*qjanP*0!@zXZ!BG zyEphG`^n&~^jp0LVh?oMhS)Zl1ei9h47TZXfb6e-vA%m#*rq;=e&WT$dRkg~$+>f7O*lENU4S!lMp^lBh`xRy`q&#@>giR6 zpe_ZNG#OuQ`+uW99E9dH}<00F?hBC#~s%WY+WfjytVCr}MOuurfFN;5Nua}yJX;vYZm3Gwl1^G6M^ z|B4kYp$-ncueWR&jfW1vdHekEGZtw^8CAqnE#~=?! zc*DNM1OAyG@GTZN!7m1T#t%6^uMY1jMF+)0`aAXgmz`Ud4Zpu_&Eqw_$aDAD-M4FBc57Lq z_C@VavL|Fe5T71-fG{H8#19}k;M@3S7eFTv+aP?-HkLTiL!!BgCzOh{Ap6!gZCbZH z^61_hu&>kSxliYjXGc5sKiS{zddsyz@1$<&Jnwk~e<$*{=l~-BgmXlCzjT5Aclwpa z7mO=SE*V#7ozW^1J|LVn-DCQzsUA~bGHqjeLvk1Mcg%mgVAp~Kwq0xqtUFlaNxv`r zlh=Gl_;OLp*y#1ltMkz7L%oON4)>zJzs)+>y1_WmsBT5DRm&Rm`mPII-{TnK*t_Wk zWIw`rXj6z&D{3Dym!DkzRDF~B9uWhPW%Kyw3I5(|#Yj&-OCCL@791R_o%#899TyYx zeJmrB3arXPU{PgGoc+MOAlIMz{oibV*!>0QZ>s`cZPPm^rykh7qv>8=qscBVB%jy= z`DsI+a6_=3UKQ$yi;zptginx$*kWD;us0#6wb{TPEx2{7E7#AjGxO1-&g6UdIwQ}V zX?+Zwfm`t9Z)<8+KDMxE3SP6O>y?YkU>s@|h#i2wfG;@zhu`nm(FJ>;6}~`g>h|sJ z$Y=DVZQtIT0Bmpk{d)8`{%afHYi%&G{fVx6gxdZmz~w`)bM*({05)W1_7C**{IdKb zeo>#6Hd=7wMqlEF4K2}#p9agxk^X?pQ1tzhn&63{qEd{wO6Dmsu?J290y}iLxJ>3T zFwFnG*Y?l+Ji6I*b7n1`B?C+EEZFwN|SS;O*s%iAq(Su`44HTbN4 zR=tdOKW`fS7W$Xy{S6~HIT)c6SE3Joq6>)p6C53hBa-=Q->FwFyKdRMGhkQm)u^k3 zz!e+}diSh9C?%-xVf@41%Mq8m4*MT&cew0O%e8|m74jc35f}UwF@cGGALszS?R@Vw zj%$3>IHBARC*uaOf4_jFDKtD7Xhk(IeOw>*CS^jM4^-dejrQ zAP3ipUZb`cd;3Pz5|sTo4lwcfCm5pS{P)Fx-=Bqg=REZD=b{E8H_qO^ssP;Qwa8O~ z69|kz|M)`!0^Nvb)`34E|MKOoH&#~F&%fe-k($77gW&r-1HRXF5s`=;LPDpl`1#j~ z&z&nxYBPUneg0RUr)8#Pow8_(3Ke%J&=9}pXX_}!XkmsUIbxpZ8K zy3!w(88(_$nl_$anLnCWkvIJA!@HsI)bM`rh4lD5_35y?X!jX-Vu>^^@B>^C8zglC z|HL2o8Uu)2w0%*E+#$Jag`)~NGDl^y$=Q$Z_8Q;qC9(Y(JsG2CY?%>7??(R;HAoQ@ z7uYYZ6C(^d?bZDK^V87p`%(6m>?ajJl`0i~l`{3G>cx72dS&Lp<~7TMm)EZfUe#h3 zY}dXnbX~7gxYMBHQ^#6|YYs_Phpg`FZq(f=XC${`9{aq%6M-0(iBK&zbZ3$n4os&;tpV!L4^!Tl>>vbMuBEYwMOUd;89p_{<3S8^8%}dX5}C zI7P|{9uc^R8snXu8lZb>L*c{YGqcez^tW8VPj$do`#;l9S~}Msbw0o}$O8^%PBiKh zbFN%zt%{HD>u6{g8zfvn@b^jnl8iC%fejB0ed(yG8mR^+Qr3wREn(m)#k{3X)ClAJ z|5hU$f}SDt54=CIWQniiqD9-Zxwwp2r%jvpd#~qT=JS|oSZM@kBq&l8T?Q=%2No-q zL%<60XWzx1h~D0Jv$oE91H6w2#Q(y%c5%IvI4qHmSbwp?356o)hJ5gUWU%aDd5e7C zL|pJ|eDK9IFNW6|XEe(91?_8pl>Deay)=EawzU=<4P9S4Iy=T|8fwO}OR|R}QzQFt zzr5XZ@ZrIB-4nW{6Y&6&A8`HWIYJ`;z$A|o-6NVNc~CM#=CDkr_(Ab>_MPkrB>zD0 zyoh~|xJ4}VKsfd=z!(W0Zx2;JN7SHbOm^Kub!gz<%y6`#!qE`;&)Q(11vl;f)Vq74ZQ0F zE33w2d;2!n0$pj)1MfF)9!mA_7)^F}9|qpw&|6p60i5%FaZXNMvB)zZ*4P?jWY`p` ztXv%~Emi(PN~)CfA$>0-^zH`ohnIMHlTNd-#U7nK`_&O9rsxyw?1>l2>+0%deg+0L zk4;SKFlz5Q2205Q8DL{fk9!YwRI(Ui>i|n_MbuqDsZ9+mPm-F$hL65KGWbyldQRhGhP!>@nX(Va1 zY1C*|(i+ivQC3qf;LHz3jPDiB_i$!!=2(eC61lLkOAIa=R3pY-Yk1MH1~mYcN~e^H z7ad%b`87uPVj?aGKSAk~a>1FfvmGxoU-T8%7LNcMc${DZ4-XE1>F%1q85~b9NFN42 zP~Z9B^Igt2oEtfJa;Cxth@f?+drkSFPT(JMghckIY?>O$zl}dde4luR^dad?x&57AV5O&@r^vyfZo$Q6yMd2y>u&U;T@@A%e+sNy zGX6mae9QSm{^1L}$2pfBp{ZH=1{k=+9{`3&E9&<O#3-A< z1=0!Z`EKCZl0kTZTB@r@tH5hqaQk-qYa5%gZ~Xw@&V6zXK_B!ZF|m|mOP1W(ARyqa z$<3|LGIi=-hSTqN-b#P}aZEHUv;wpe6lsbsMV+z=I>3W=742DIWCc$1o)#-~P$*mJ zgi?{-dA(1|Z!T}Nzh~dR`tIts`*mUwO+`%ko)f^@{j**4g&*= z-2C~kl0Z6ER){d%|c`p~1-CvsJ!jrS67t?*Uh&vJLyk0+0zg+FH`Uf?C^+Ir>7DE@5=zOo6Zg9pROn&67nxilrSdud5e7zt1T_!H2-9NeqBLFcA~@sr*Afe2WJVJwP6l z+JNVfDPUoJAmfFqYB8_@tDpxOVH0$up$4AR93nS;XOsQ zMMJ&)y<>!@neYsJ!x<)AUa8rsqXDl2`j0(6*1qE0ib~|_5}`w)8N8;)BCZfaai@8W z&x@vUrMyP1z^esb3lb#{NM@n-Fi-B3T%pJbk$m{^dEBSCOXaS}H{0B^?%V!s`^d?c zC&x~|JT(R!pE2hE=MnVj4XHg)>y^4I)v9n`p;^~ow|&|3Wj*#0_Wcg89D40v+ILuo zTQ?azGpJPYQ~LlO)Dl&H^)l7l>S?N{)PhxZs$5ZAr?f-TSkgv7L_m`C>in7OHhs#J z=}TBy#dJA1OwpI>=*G{#|Hz_6x8aw+fey$N~Ye` z(8zfJE@6KioxDH;g95@OLihwpz8}2e*}ymY5TvB^5%mloL!h%D`&knMd?Se4X8_wU z3;2iS$p;TMW?Z}0p5yD=nTwGLUZ515`%#D!l6W79_2a#S+h;=lgONiZ*nq(F_IDK! z*ssq8^oHrvdH>9{{Zs$;G#Um9JB5!{gjR_nM=_>Rr?H!{Y{q^`Z^KEa zbLe&pbQ>Tw!{E{wCAI*m8GsIGAie-N^x}B-@FX7&I^6v9-P5l0^7O&Rj>a*<<<|p$ zjNpvc)z*C}FDw7@KJ)$P)2OHYK97CcS6y6L1AD$u{D^pg#1V-P;2X|cxO+i5VtJ%y zFnYSjjD#fz7U!YQAY1x`be_x^nL^=H!ui1K%$K|%_1W&BUC*(IW5a$aeqVw!LcRo~ z28`WHx;K9GrOzlhz6T8+8TJ`IG3+)CH0@jwvZ8~?z0(_~0mm4}-nCI{J8Z(N8Iei4~V9w-ov*0NMTL|4gM&rt;64C8){H zZeYdDy$<*kJNF3)T)rkK7~zkYAc+kU9q?nh|J&oQalwgxAn1Z`*9Uxj$-uo$f1#;S z5@Wfn0X^^?zzOa_?Z80Fnl;0~{TWR)G9G&)FW()!WXWgr5`DOYxVR#GYmMBQ>Qa+1LFC-R?%7+&T#9YFefA=eqm2WA2zB6}NF? zRrJc0|g+xdEo$N#Y;lpGTtdJWbA}@Cc3Z7koITUAMktKNSfBx$JiQj;ZW(s9K zg@aazRu(YD-X=N?!j7sgZ+Eu#e^=fdfQ8}gZN%V+lG3s{GP{)@hdQ`N4)bvZ8g6;pEL>|um zOumzRh1Pein~%Rd(f2ehVB~%2`|+GlIpZlMDPxhjkz?TG8`&JZd0<6=RUh(sy>>6` zdmN)3dpE~z?%(ou%YbvNbMKm04#fVi(+$+CQhKCXp?FIrN8yB0g!Fd#o8oJwc8eH` zuI7^BR-D2x^*8tR{*fz5a?_l%XLG46T&QKi$!WWum)C2jfWVmxf`ZS8><0=9=O8Zl zvpQhH2KaXVLkAGu@D*1m1$nUSP!08pS4M_)z{71yShcb($;PHT$=tmEy`kX=VU$m(FIyZMV&ivRTvu3@5+`l6J|2Nq`Fm-AKi3NO>`(Se216yD}J$>Zi>C>Z+ z&zco`YSyg8OA8ic0LQEFDewgd=WnQbT(ih+w4c5>od7-9O@&fV?-=En2 zKb-$xuS5q-obLqRAG(BadXhnW1K24O&jAzchjSlS;QnVlN1o#Dk|l2s3klsuEX7N0 z{(L=_DO3LE>wJHg{RZqHnmIJ{C;}7-8VQ=EG>SBq3zjX|t>Uc`XmHw~0Q~{2z|`nM z4^a>J?|SVY*mq3S3|`RwBz;UeAGUkc;zNrwP6wZEdz|>VD?Ts2zo4>UxVpBQaQTfB zUZBj}%(2L~;06e}(X+#MN2}$f<+YIcYKyDOnrv>^v@OFGHTsR(=e6sRbF2{a5i1l$ ztWV;kM84!{$y{J|r_bCoGfwT2TFH?YM>_o9KkiR0PaUaks~vA>Zx}CWC>eWO@^yyGwKEBeEz$85YSw1*p2FU?L9)#V$pZNZe zcliAw@B<=!X3u_eiiIWN+`M@ymm%*rI5_g|^6(Vh=iw>w<>vlypP&E3Q*i6PSh}=4 z5*U3LCDFhZh%q<+6m4Ww@lsp6Gz@+KiTT6-&;GFvAo4$<1Bh+^74w(G2FM`qhrDMJ z`Jd1McueAb1lRwRkkDfnKE4C`3l=QrV_*>c1D580`y*%4%%*Tt7E{D1YIF*8=4_^H z-mcA(`ZgbbB4QGEd!3R-0qgJ-)z@iLN4@~g&mt9;|dLisWn}4Ez z_p6LoeJKSgLnQC_F6Z3{IEIEEL_X;C@%QQ2bknKP_@Z&G;U%Lw+uL>>z!>TWPgk$) zZQFL>47Q=4uaWfg0e}1>usd=WA77k4d++SnxqIivyFYfXyPI&gE2$)Du%^9cw0E#~ zysN)!yu7)5>`l>|QE&l{qK|jTIox^B{nh3n&nVB~t#7R!}Zn)H$oine}IH&Q+^qN_N zgReuEtH0}zXQ0<8t^*GD9XeO~u535IZPBQ8S*rp$y*$ptoXNo5ertB!tn~b=^KHQH z>Md_79|nHM*ucoZSW|D)Sk~vP(SV$Qp(C+J2VBBj1~x}-9`cCv81|0x9^3wU+qmaz z&taF>F8%QRJD0y${#n~s`@Pahr69Q-@>j&yN$wIf5?;+I!>c;=XE?k6ly&$Of0Uk% zj-H>1NpLAUyP+i~=b8;%T-$c?@SH!#!xQ4m$@$KYn>!QS)&(J&n&puuCZFLSHv!kP zHG0L0M$!`qAG-u{{}G(`MTm`+21|&SKIP-fd&tI?8MtUs;R{jG$}ka;`X~vBF7Sf( zJmcr@2G(%1Q;UF1KIyl2M`|s{etirvO`cm0DX`PO!YkEETgvb@LaH(Ki^4q=1jFY z6v|&-zyFHm|Kr|*n84KeQ#q#!O_imSqSKkCHf=St8S~DCYZu<&_2!KRo^}@e^HfqR zG=TwtT440tB%yyGUE!oc(W)D(YPLSy+ITqdaEni%PwSz_hnlzF+upe8`ldR~HJvnXb{nqy&t}whccZ-~EAc`1 zx^CrxkOQrV_jQ8Hc%Zenbp+U5qwRz3V@1tHBauasgO^e+^|?iE?s5!w>~(+XKIr+< zbHw}A*0JqT+sDDWDbS=EgfEU;d^V1etFg$s}QEL`Y!3psjn#zyMs)IjE&&;!(oe%!vd8#Z*s zfD7am>h&OBpF*XjOHdD60>1lVKQ^}XyEA7dK7?F9;pE5*;N~t4=I5_^&c#(7#LwS= zm_f@;LBY;*7$*b-KA(g=e`V34Vqg4Q)aq5dP*A9kR#k0FfXu(w(HThB)$PwRFzC-R zHSJ3`G3f%QSW~pRdc_M8*C+Nnuzd*c7UAT6inBf#_+epMTKQ4Bx`nUx^a`W2vb_0dwF?pu4ZN3qC8`UE(aam zf4z?5j}z2C@RJb}n94krbt?Z`8he^ssND;ls2SWs6EV|! z=4;WzqS?R-Dn{)!j94;r+sCGrZ7W>_!tpa7ECQ zTVZ@{LI<>N^xxR)f%xLq(5<6ep1Tij0Dp(wW81c6_m(vPgY)yIC!3ltzq#BMQyA0# zsrl1zXMg8tdw=_AS!>zwoANgUH`8x+gR`?0eLT%;!q&8JirCcS{&EX+fY%6~A4MN; zpTjeUTI4iRbx!C!QS??kC223=jyV5v9%&xM8O$?&w|?F~a8-WsqX|Ei=!_ZCTC-=H zSk9TV&XI*>_ujd4uixb7j|oH1UYw<6YXWNjQ2R@2{)UpAoqK_g**Z}RWNG;sJ%csz z==%W=Mmh3s1&AePLl>mpXJ&r?Xztw1Ckq$mLl=~SpQPpFL4b+y)dv$a)5!59Yk_~L~nc>@c)4WKEdq`j^q4j z#1mdGUHS?2i&am6b97ut$lqb!ydCNc3`YD@rU-+S`Db~Ve{o{}pT9tvL19FWke{-I zqC_K0V?=U<`2SYg)wCDT#}_coYg!EOwz3sYDSU+7S6be%Y_R#>rq$x6MHA`;>vS&a zRI7fcT2AVQ(O(cwWdB?55a(Xbck1WVD=fdaY+n0lZP(^!n+LXqZ5!PdzHQV!*nPn1 znNyE_z?u$Qf7{mGVY@r;r`+!&et$(%#c*AF-Ec`$$xv*0Y~P)XJDppjw>BZyS7q|d zq}nFLy526-zGY*?#vYfKE`#Xh?OOL_-6zBulZ||g9;?dV)HU-|J05&F_0J zuc3FYuDxl%=m17udoFr>3(#AGp1C&QYPJv@j~Hv~3X*?&%+8*2pNT2qG2(bZA|e&Q z4{LlXC)b1CoI&txbi>wfe*@b-Sy#6=!_;&n&&ujc&a!2rU(fuu4D<_Q)V((~tx5zB zAME;4d~Oc>b7HHLx}=;y^yfa2m(K^r&&M$EbVeZ_0PcpxpR!gfPR7IuilC*TBGbiT}Z3c#Ai>FSNm`J%7CtrAJC1bOUuu&7PapSii7twhyy!TlZ{j?V1N`@@+2Jyf!&(a!+%!<`Kyi zlIsLD1r692vy0Ntr2n76cmFl}aSDYpg`1w9SDBev!({GUD_d4pXAcgJBj;shpM@A1 z72%w32gYC@!4?HJ)?n_bQv-#!Z}(M%hW6DaB=pt3ecM+N9^PGoI=Z}T*V@uhV-t%U z`!mSf6CR$_7cw#x;i{_5IGekXC+L5vpwJbgsMreszBN@}zcb6stS@`Tihjs@PnwBI zd%BrfV;VS&-vf(339|nNvLB_PQ3U@yI|RLj=us#FzER~XGqW1-o_vl3-fgTQbZPY! zdHL6yd3a9fPoHirI%SFs6NU0Wi|6-i*8LBE4|v9D7-^VkxM+kZq7+qH1zIy2O&SL} zW4dj8&U_Cvk7?$BZ?_4x0==#Qu0z1t81V@77zXBU&#F7C+7P>|UwVFNC5aKBcOZuM z0B@T7Dfy4um$WNa+*#4M;mL;XEumY6c1G?T-yOaC%dVJRUv|CO^~K|*$FN(NTmSy3 z{e2f>FZTE*`F01T2XzN#26kUgyxis;;a!i~-V(V7a{1`#MK6zLsZo&8r)5uBF_Az`BPw@TvXYhvR%wnY~>=wMe3|vtUR<+XeX^LNHDA6Jtytayp;d)?!RYXVk zeti76E9=6A)+A5Qx>u&A<-pA#GTfYKZr=Oe(sBU&VBPO@bXp+$O=$)OjcJC4O&Mlp zEor8vO(}*3b$I>rTW#&Kcx~;H1Vh7$IOO-k6&3S<$B`X@=b|C=Z&t5vA+d?Ks67Hk zK_7U=`oPWEn+p7H)FCw!4j+=g{2n}L-=p{VHn_|X-%Li0Pj)2gm|^o*K=x}e8sY0y zU6PWD+BARuQN3x?ti^j zF-QR~*JsoW5*;uK9WVwS?jgrVjys->Q1O`d1wXb#aI)B*y zF*xg@tzu7(R{>pu5L+FNHry!^HozHi+9({4NGY( zsL}G%Du(PAsyqis|t;`}Xr{a3*MC{9FgaNgm={S{B2 z4%R0p4|YQS2Y{=EzOC_YV1YIP6SNAo^&f(Rdor(I??~Lcw*?p+jfsdQWZ2vH=C5Br zSO7he2ON+b>V(*-r_udqo!UK3x7Ncbv&<-gN(+x55K%H6t+BUh(d65 zQ~{f;DHc9SJTQ;rfhClP{sZ8Ljpq=|-tF5*ayD=7Nd;FoY63nH>|*et#>4JUM{Upt z#1YD&Pd>%#>(|EW=+u+E=9wi+Lf0}f?$M&9T?W~gXQfak6`s$pUHd=ueP3+>3MWN~ zMwmvC)D3bNaky$aYhE?lYZPvE)+!HizeZpS4mdw?9&vi?G`RM`+AiCBw%`?9*-Gm7 zNPM5z{4RkmeV(D7!{pp2c0U>9{NEk3`^(m-ts^esF8wx7ZQ70R7}ptGH>fbUYFNDN z`(@?&cl1jY?kIeexi9lU<*~{~bwBkSwHs>j%BPi|DIHY0qqt4+tlTEKJ(4z(j!X2G zSn$j9tFmyiaMMkt`+rsMiN*z5wy9GWNX?ohr^Uo%xMJQshs{Dl`%kH=-n*}-_cnIj zx~dNsF7#A~g!DJ2r1ZC!lnnMZH;?x9^o)0*ceA;!Zv0bL)^L7USYO($TivM#4|Zpu zXRvV7rv6G#&*4h+4VOAQcYa*At~C#QJL$mJLkzGYT2Zkm1ekr#MMQFtTgXNJKMyqp z1>x}Xam_()|3egHdP4qzQy8~-^B~3$IL3!kyu609ckUc50zYRFc)PNJA({w(AWT`g z1o@17_z$J=!18+wUh}s`Ms=}zdUb)|9Pj~epWXE7+m=!&7GgN}=Tc?=4^;z*3eeNc zrCC73O(Q}lLZ`;3&$nK~LF1T#r@=joLl)6CXKnKAZ`#+d@mU*F>r;)s0CNKW8G)=NSC`~U-j>W&zN4I>dQCMJ7)H;O4k+E0-zxu|w3GC1acgnM#fFQQ z3n&R_vI(*+W|+nBKgZ?rdoCV&+C=-Zz4gHSZ%R)eXe}ujn!o{WXc(_Sk8RPrcf;8M0sYw*F7)N?+SOZ%UZL9U+lL$W z?Hl>LYu9iEaKDP72XgK0Tiyd#GakHU$ng~hE)mHI1a@yAYH*NW%nAdZA7Tc1FLD0A zf-eA!;u`exHASN?0r+5T@$1%gB!iDU9kIy*#3xE1|M^~C{pr9nO)xQO0xxkDY>Mh6 zQ`35k#wb0#YF|agcl-GGzF*16=%F!XimBL?DY7gS3bnTP_uN4L?6aoR%!D3br{krQ zSfIGTOwvqpv$})&aea6FJEnU~!oz*R?HuULsgNLu*DZpvSImmg) z^||Y?%X1f8U53EnHRKlRHsTWMGUWK&alrPeZMX4V<0k%-{JHeI>EFydGA~KsJAo9j zvtn;i9~g-mo&d!IinrvwMgG3$Ky> zfq7G>&KG55l+<8i(lupav9@Dnb@AZjJbX<@=h>URdp{x;*!n3bsPA)h^gwM=(g5kF zFVD;z%8!p9%zFH|Hyb$MA9n5PF2k8z54mgxpGG5ib?U&cQw@%dVsPo?p_ecnxME3& z<-IX9ta=F_ARJg?z!S;~NB?~katg1}=YxFVN5l||Ujh>l`H6DimRH1pUnqg#^V-`t zXKdWqk-ue2Z|>&Jed)jzOEx!ejn~m>LM)*LpW76I_yJ=4ue^A9FIdf;>!vw#rl|-6 zgVbzltna_S9Z3%X(F5~nI2jf($j+6UYXsckwGt~NcByVuy`bx<`_O2Qaipn_X$r74 z3RYZM@yY6@RRijY+gILS*@gaso>hLUdR9MP-Rtnwp&vbbeKwD6I?eB!H-nSB0+{{@ z!1xX3*ufDfxJ&Sn_%87~GTUV?$!(E4CbeFAo4A$u24M4A@+1m^+UOl;O&Nd zS0sUdICagM7RY`pdXd}To0)Yd=<0SwXld0y(A3QFk(LewPU=w$Hnt6FEG!1X%*uuO>bdlh z&JLXrz5RNx^^fQ$>K)U2uX{o--QYXJ>}8iM3oWlKFElxClBsi2CsA>~VmO~0-)$BL zmJb1zVuT!=twbTv|2Y+pOBUb+7_Dpai<17#u-G9v(d(z{i)j zc5QQ(l~wI~ob~7_ER2QxV-z4(kWYMqFw_A6gE%V~wLQQc%Yh#F2z>reDHaxus2ggH zGcsy;uBTT5PSUsoN=o-Ph>0CE=H|8oPq!uyGc*6RDgPN4+ka!u|3Cc=dPe%$GZxI? zX5?iQnJF|=ZieU#^_en^hO7!96byMUG#H3W-k_|R!ANFz_PghrKCf>wLF{0tLTbygcLT`mV9Q=v^V)-s;* zE9E_9tz_MV4TbIIC^G9aN;0a<5uYPJckx__x%_hlXU(6*Mn_La|J%L|_1(WT12d2> z>RxY1E-_ds7Ja0F4cAxzM0@p5ziTMMcD>7U~mw0dBt!?ihCKjZAP2SnY2YeL_+DB%0{;EeQ!+BXK8lLh_o=I&^IW?0F!)kE zjpgO7mhke5&tPDn*7W_-g8mhsr=_7o1;KRG5;38gXddbcIcV5uc<4B%2r>yWNpVVY zstPF!>4|BG8HsC%8!lE{thqpVfjGkg1`axAIu_a)v@mVSo#Y;@=z(Hy0(`OVEu3lACyn9Dk+5d^E>I>xFqhIRi zB)-wtPm4D&$VxCU$V~%}VXlM2r;l!K^<_JDv{mlg*M)d~TaLT?XXNxt-NcSIv0 zNLe`sT;Z?oD=EFWE-(M|oSfXfBhu1Wc1lQ`*divjccZANr@e&4CTm&QHHOmC=F-B# zvaBpD)cF2n*>EIg0KI^-d>RAe^jWi+XR|QPVPcuZG>duK^l8&6w3CC6?SE~#NWK8Y z(Tocz6wbv{riiP~pRa8uENpEjD!P7?q~vB#8JX=nWo7s8my`23DkpdPq`drj=!Gj6 z6cuk=Qc$>kLrv}eLo>7I!43}55iTxoqrALQUV3{chq}7PKD4zBy>4dq@U*)6)gy9p zXAa8Ao!TQS>*Fmgy>FAaxR;%Xh@+*D&}st#0W(#8{-xsV>{6U_=5WnqV3<_(+W%{N zg8F^`f6f5W1&9;OBKTus?CcVdNp*c;VM7ZMk>x8zMQ!cH#n-KukZ^L8lyr5LlyrBM zl=N_uknr@Bm*279z~JZ!8=G@y92_p6+pyu<$u(;(?6*}sruB~lt zprmA^BqE|B%E>7%z{Mby@%RR8#*Ubq1(2K%D{V z3{Yo)Is?=hpw0kw2BI_h4fI0)z8KBMpbq1(2K%D{V3{Yo)Is?=hpw0kw2BU-((vN5zkH#^pqtAoHy)zJcrEJdpiI0Izu@1@Mb|qS)Surl16-<0 zY3$z?(KwZj&}^#s@}~~+9msbf---G|oq_*!2DsF;Xq-w0X*Ok+(QHacqS+K5|K|?! z9q?VqcOu`7`a_+8|6~S;uAJE0fA+rq>EF+)n9$?@$-PnkEp-MaY?8#}KUZh}bZ?X2 z$#*Inr7jS42L7WNn6S5hZh!q1o~!usAKe@EA5&+5y1%J=U^34Db$?Ih)urAWb$?U$ zz+|2Q>i(Y0t4qB%>i(wgfyq1r)crk~SC@Kk)csA}1Cx0MsQY^|uP*i8sQa6`2PX3j zQ1|y_UR~4@~A6pziOqTqwa6&9+=ECK;7Syd3CAxM%~}kJusPPfV#gY z^XgLXjk>?7dtfrp0Cj&)=GCR%8+CtE_rPSH0qXvq%&SYiH|qYT?t#fX1JwOJnOB#3 zZ`A!w-2;<(2B`acGOsT6-l+SVx(6on3{dy?WL{nBy;1i!bq`GD8KCa($-KJMd!z1e z>K>TPGeF(nlX-Qi_eR~{)IBhnXMnoDC-dr3?~S^@se52D&j59QPv+I7-Wzp)Q}@7R zo&oCqp3JLDy*KLqrtX2sJOkAIJ(*XRdT-SIP2B^Nc?PKador&s_1>uao4N-k^9)e; z_hepO>b+6-H+2t8<{6;w@5#Kn)O(}uZ|WYH%rijU-;;TDsrN?R-_$)YnP-5yzbEtR zQtyqrzo~m*GS2{Ye^2JsrQRELe^d9sWS#-){+`UMOT9Ph{-*AM$vgwp{XLmimwIpi z*WT5~$aPisyS5>Y1Bnyt5GSPCP#4({oZa=>amYu#J2UofydT4U*a_Ws@yD)h;@FFK z4GI0SLqM%iD_Ybdl}d2>1xB+jy;A!*Z`S`L(B-kK4j= z#*Q4g9%oJ}D;I&c?7GwJxc>q743NC$UNYuqCBoraN9Mp>kQ1M8GRN!Mcdpz*Bl8z3Qfhn2~POSSIbxL973eB}wP?WJ1h zm6{hhec_d;zaJOv=i)1sGtHLw#nL{q+2YoC^?sX%1IlmZNgAFstB&$p`Q5DCO%u2B zTlt-aC(Wv({8oN9D|gext^8Jgr{PJn>L|aJ-_6S1G;u4xmEUQ2(yThlZ{>HhayL!f z%5UX&8lE((j`Cai-K^YA6SwkP`JIL*&8nmPR(>}tchkhJ{8oOa;YqXVD8H58&C1<0 zaVx)--)VT#tUAhX<#)4kH%;8iZ{>Fyo;0hD@>}`ctlUi#xAI&0orWjPs-yf?em5(3 z)5NX(R(_}9NwexGzm?z3%H1?^E5DWBX?W7CI?8Y5ce8RgP29?F<#!sMG^>vCTlw9r z+)Wd=@>}_xh9}Lcqx@EWH!FA3#I5{Rey8C{v+5|nmEX`F%kC79#Uq0WbM|KyrE|3xD&}{=Vzy{{_E8^b0fbt>-R{ z-@(N1UfNuclm5QzN@jRDcl~|tG8X#o(nX-PYp*%-)bnP7zjYhy`m$PFSc@=r=Afbo zR6}6n+qapCr(ZCWU;j??a2&BR<_%llSuHKSt_aK_(AwQ&CcpPmbF9btJM)P3ru^M= z@#`B!pcVoHpLx_we(Txz=WxW>|LI3+rK9bNz&r%jZP;j^t4wl!thVNTt*<}+uXgTc ztSX8?C<1)GW}hLy);zA(KlYR(k31flmg*}476CpVpM2`a@y_4)d}`;(2en#?KwSu2 z*S5nPy@=2Ls?DFT_4SLh=7ycO*Oi+ZDFQ^mey06e^SE07;J1n*kOcwdcNX;2Hg*J* z-?3Ln3{oJ++M;0p)k>71H>!AfWutf}YyOj)3wz z_6liySrAZuXF*SGV@E*w9eaf|zAOkRzq6pHwy`6i{Eods8ebL!l;2s<5Kw++K~HUCM?m=v zS=n}ZvLK-R&VruW#*TpUJN61`d|41serG{X zZDU7Z;=;>j@+(i6$;Tdx>&2KZyb?Qn8eilHjD7iOGx_9&xVX)+_Kk~%v8gBmSrEAK z_T6UUyDvJ9$1smG28=hx#Mo36fvgC;W!If{uC!~Fi?hbA$zP)*W8l2am{e*au;tyx zv_flkb`@SNYiQ7BHrXTJZ^7%|@VXAK7OROjuzBK2_rlq-$m5XN2FVkU5dYKxm`ACPbGphsK{aPtmK#e@G4?x_(UZ zzSgmx_-py}+n2+?-(R)&mBv__u|h+N7;lz5YYu3DK3f zLu{x^5!BK_$)_#8y$bzTtzP=o|TA?;FR_lf@fNv zq8EyPJ+ahdBk|QbhQ?pZU$T8U{3rXYbsmD}a>O4JPp#aN?Tz#;eKoR=Q2gtOCB!B~ zm-vLnFPNr1*5f}UhY($fImD)(_@$pT$K~*!CXOsRA$W%N6N*pTSk6Kumb5n1578C< zP#cLW&5^z-X>>&=MBmr-F%w^vj=oK$cK;f6DgL#keDAzB*8ft*Tgg{U_@s%2Jn{AX z_m%SVuZ8H+j`|ee$5Os`-oyNd#N+GoeVQ@m+tkXHx|CXa^|m|jjs071Y-#P51D~ae zMaGsNv;V$UKOwsGMSV&wzxB2|?_vH!;_-E*uUccwx2ctv_^hQ@Z@c(!$q4f{@C*M{P(r|hqi~tAESSXNpj(Fu`$MWv8i>WZY2@BWLxNU+Bfq1 zZ}d$`#<^7I{Qg)aU!gcu(-+^;Uh7ExYC8PfLHzl8{`+dS^b0+Bw;cW@2I-4O zVky^?N46)gRXe9HrCNK|>y7j+u{E*}`ld)v8hLwHnHI& zF-bnPjy^VgUu$gDJ$^h0ez zFr;rvs7+dZ-?xw1V)nkxVtQ<&E~S=Uz3q+kEipB+5BjD^UrT*--qZW9CzcSK5M5#s z8h8xjv=vd zTuD43V=SbPG%@=+5|4jvDc^_suP4_Kn-E>%5E_3dcJ=rV$st5nVh^#YCw}QC%`t5( z{@9Y3(vC6FUn$2lxcfRQ1($mB^s!3wzTSQsWmhZaN@Gi6Ni)VoccmO_;hAh-DY(>| zXA-Yk=k@l}D7#wmW^Hfe_oM8WuNwIMt5E;c55}42ZGd5L5n) zi!lo48N1Y+c`-lc`fHFe$gg|Cb6zJNf`f2@euM|faS&7ftUbpoYsHw!dGejPv%c?v zFy(Q`LC7Jk2Uo82Pi;RbHu|}+w)Sly+oMv5FW9G=5PeI=A3D1cG z+o?x=+R>hKCjF69!hx9bPjX-8Jl2jid@qFDW^T-J3Nj5j1Ni`iN1pRK+sO~=b3WiW zpdZcy^vkhD4lsY>9s~a+f5t_QlB>df&g)Wpa-4N$uFU&E$cG?2eh|XzY^NT_0qtnd zF+_j#EBTXa>^Fw|SqFdYGfvKXyw{P#9M7EUSX)w_^=8h$1^FEak38pfwo{M#w4*)! zNd7Vh$Q&3${;UJ*!TXb6f61TsIC7ffp82u%tov_6_^g{4`1w;WnLqL593c6}RDa1o z$$!beTK#_$LJZQ>pZSwl%%3>NRDYlU9G{%0c+cXv<@ilA{>cd$|GYO=8vp+M$NbrE z4E$#toR7IS@$=`r&H0=2IOlVY0gmmo^FQw!GXE1#=I`JC$h+9`XMDU*^L|Cn$`~LA zSW~$Na-Js+oDX>9IkE9;FLNLLaO@}V|FP7c>oC^ep95Jd){GqB8o*!w5d-GQd^y+g ze#dk2gBWqXNUs0rPsTp`VSh2@?~eiD0Ba`e0P%icun%AKF1L4{oLta za_2F&{27yvf!q_A3%S6&$Oo?Fd?7Xzwo#8Y=X!Fn++)q3aZ$)sj#py9JeUvj5-gaf zFTBonsX6T`ao%}%e`Bn_#77=bk~k0x=ID#mT5@LIGS|_E_)H#S&R=3>{1oOxVQv)W zN^xiIuhVdA8`_AEYRA{=^G|-}%D&Tl{^`fgoG9dkFYc=Q>+3J}o_%w?Fb?uyIfZ?) z?`t5T7%Y}kvW8~!FE#s40`GpjmrWxp9V-JLwMI~2F(D@ zd+~P<-c$Z*%}-wCHx1m2SNPtE^SZ{n;;!F;h8FmnF{jOObJ84zx8vr7nSke$X40I3 z+I3c^4Kca@I?W-xGp5t5gKZ8v_nRqm5c&@ynu9o-u+F>S^B_DPH18`NjTtl7!$$hZ zLvz|3wtY__hU4&c(%KebaT4bH3+{>5_b2;>x=Zw`oi9Szw8MvGngNuNHz1RE_Z2EA8d7ZG?Pa$ujcNCG$ zK!=FTzt)Pr+DyB-)x6Wb>%jX~Qwg7L^G5g|Ecs(a*o)xBGly<6Hlp+ZUKCfCB7B?z zK4*Z{jF*)$3$djQv72v`L@pPsMv)0AQ;V2Mx>{4K4UA%`C=McYt6@7(%}rX;#bY5D#mHM zvwvEOuatB+Z!wpg<>+Vk-B-yi9@LI1fo3zeoW#&R)pgz zS$EoRFnp|Gv zhxZ(XXK>YVTVlT)e7oA*gxP1E`9Y~)H-^a18TdYhBYWcL<@h;j^C8;4I6pT7pLOPk zB|oFKLYy!2@bA1FMP!^?llBh3J1u@cD%o?SoI(VxHh<#BjF_3bAF%(ad7OI4hO`)` z)>#eV6h{rKLlzO?`Wdym0mjTPI-WVSS7M!^b0~f}6+OE#&Z{C3M`Cn+iAAVTb6F80o;VEEE%ukx_7#m4pOgEtK^1PF? z+#|!t>q@a)Z)5pM)mS#!SbiED%emrqbF;bHtT#V{?E%XR#+*Yg93jldM~rtvjtq{2 z6BTkhSL}tqYvAv{Gx%HE;?Hp(wg4{1M>nwxA4d=yqvPG=0bApGXSn6yV=tIPOgT?* z2I7ifJ_9f>uDYps3;6gj)+=sa`-sJ4Y#vSz*Tm`dRzq;=MitMX!W;)~tuSTz$9az{ zx%78}rN(JJ?gRf|c3=cuWu9NaX|m7F$DEf>R~Tb&YWa-4k9@-Xh50$`XJ;LUZ2I=k zs^VXnU0`W>3=UVE2@FQ~S?FG7ejl^Q3)YvLALq}Rt{zo6y7v58{Md-YaE|V7pk)znp6>H`jW3veK+B$AmBB*jq~+`yZR9 z%gO4Le7(*53hu6D%s5{hvAp#?8FSX0E8b<^YJL^y{jc?PuB=e+8j&FQ}abS3Zn6=PLB|l5Ia*HZHY)vef>IWP2%=4dDOVyu7+g{Hk`p{{*nQ z3#;&ZFdkpFJ{MxO3CP?8pRd^Vhj34x22$Hl^C9dN`WK9G*6J?UTm_r|E7`O`dmrX8 zUzqP@Sd%i}SG|0`1f^EI+}iu2)}$&QvG&F-AM)Lntt~v}fj#fyr?5Im&p~7U$d0qW zH8-@(B9>w7ou=&`VFJ~ZwPuBT;M#M=e+1VywcOCcZH2SzLkz@dq1_|U*^Ib)aEI!~ zad!4R{nTJI2v5G&0`bHPE}KFg#*9I?d~h z>CN|!_vQu%OnZA>BAE*t@sy-eM1Yw802d0KMi1bNg)H_VNx?vV0kC_6&9P7ih2( z^HaHHcog5WLilL76+<2kx$$CeZeL-@wC_ZK8{DPtq0!+UUg^N@2TJ+zuI|Cnk?z5J zVA*Eg?5^$aEgUEg4vh>KMi?Y>yv=NK9`b{u-F=1bQGDGQuG?UAeaZE{y#x8eJ-NQo zd?D90u)mA(qvspE<~{hX)lebFFcHC9yq2ND-d@7p28?cW*N1xs59HAkKyPd7#8|TN z7Q6ZoRiPZ(b`ArZ@9=@HkzCJk?|blwuGjXlgLNkq<)-0cukB_hCLr6C>nRW>-|SZN z%M04XZWpOWmnFO0VBg?SJ43t;)8B#??Ec%@+c15gC2tu6ti9mdv2wFUBvW_S5J@-E zQ?Mllcw3#FEn9JDuxQ%1cbIoL3wx1avx|1jVb+hm+ShBT(B6S(5ap}Ip#m#Q_3fBy zY(xWv10dXJe_;TCZQstFE0dFJFhQ`i=)}f$Uekfz{0OMj*1m%aJ>RIO(7U(C8tug0 z*ESvK=>x5)eJdV6lrEttNT{uS7w+Zr9CVXPY`M+soZkZS_jPp_`q0?XhFiouGc@vg zJ8s>j9tH;cdiyOEIy!JG#MQhVU%dmvMbK~%4Bp;pZg=V~$;tYau-2f!P{dwC+~)*a4{Hrw#uEzqaG zTS{w%vj1)H!THyT#N}3xyj${#R_omJBkr)bU=6+tRo)8UZhztSMr_-M=w@uB?cn8@ zeR@~ktB(OqdBVrIcjD!qy^Prv(->Az+{e20j6`-NqU&F?X-zW2ZP4XPo6%Ee06Ok| z=eN88G32rP83%R++~IRL?}H8f5vju#&uusoar$}#Ufr+XwB``b-D-M%tN80i^>WC# zfcqo;aVPF}d|VaG_tyn~Q#M|=BlO!>p})@~_8H{DT@LTAhfvQccsT_0r)*Tz`a(sm z-O%q`p!+Cvi9DGyv7ptUb<(!FXMTKs!6z6GqPJtPnBOxq?goN=Sm)e}e24JL+nl+l zd=UOwu_LLhSO?no13g!V`98)G0lnQ1G*2Wv&d#nz++-VZo`wda=GvEt&&R68({w!H z)0L#wDo^VWyzm){Q`2L`W5-XNnm#pktfOPs*wn$3$7d!^&Y*32?qu8P!&pddM%A6)4(pfenaplq z=iJgGRvd+aTrUg31ko8rB$pa&ax0I_-CZG$t1xjA0^?ugmk+9z{=Z?yd(53cgc zi@wZ$uPXZ`KBdJZR)Lv4uP=MvZavQ}Kr_3(T3mCv%Aga=*vuVVQ|f@e`?~He3>7#t zxavBXN>|6RFzFvHGYtje!k2b-{VdDj%n@xYr@W)=o~yLs?4`Q4?0je0`P?EubMSAr z{@ZzH6cl#=s5bQFE&MZggVh^$Ncil={;xMbxDT5uj(>E9jRQMdu%pP_>2CP79bvS0 z4dk#ST!kxKxqADPXII;4V&Tr@xpKqYp5zDRYo*=D5ABumKIBKMOIcyh!K{*dk<3XA zR!9msGKumI#%^wY>_VH{@!0JN9yUAWdkAUoli0o{&8tHKFp$hwOvsqo6oN0XZzJ`l6xY0Nc4O4D)aNTmzUyRy)dqK z8*|6?SCraY7Phw<^YDGrzG^``zvJ}e$Jk!^J5Kx_$oW5Hd*$ze@H-4Qe_Yznx%j>D z#m`pG{;{+_w4fc3n)QDo?I<9`uRA{%+x4f?{_ui!`v2l5rTrrd+W9O!|7X(v(FN`H pdGx2Q@aB;7PRE-=_){6D~vmH+?% diff --git a/res/panitent.rc b/res/panitent.rc index 43a4a60..2fdefc6 100644 --- a/res/panitent.rc +++ b/res/panitent.rc @@ -1,148 +1,226 @@ +// Generated by ResEdit 1.6.6 +// Copyright (C) 2006-2015 +// http://www.resedit.net + +#include +#include +#include #include "../src/resource.h" -#include - -IDI_ICON ICON "panitent.ico" -IDI_LOG ICON "log.ico" -#ifdef __MINGW32__ -1 RT_MANIFEST "panitent.exe.manifest" -#endif - -IDC_PENCIL CURSOR "pencil.cur" -IDC_BRUSH CURSOR "brush.cur" -IDC_BUCKET CURSOR "bucket.cur" -IDC_PICKER CURSOR "picker.cur" - -IDB_TOOLS BITMAP "tool32bpp.bmp" -IDB_TOOLS24 BITMAP "tool32bpp@24.bmp" -IDB_CLOSEBTN BITMAP "close.bmp" -IDB_FLOATINGGLYPHS BITMAP "floatingglyphs.bmp" -IDB_LAYERED BITMAP "layered.bmp" -IDB_LAYERED_PREMUL BITMAP "layered_premul.bmp" -IDB_DOCK_SUGGEST BITMAP "docksuggest.bmp" -IDB_DOCK_ICONS BITMAP "dockicons.bmp" -IDB_DOCK_BTNFRAME BITMAP "dockbtnframe.bmp" - -2 VERSIONINFO -FILEVERSION 1,0,0,0 -PRODUCTVERSION 1,0,0,0 -FILEOS VOS_NT -FILETYPE VFT_APP -FILEFLAGS VS_FF_PRERELEASE -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "000004b0" - BEGIN - VALUE "CompanyName", "Aragajaga" - VALUE "FileDescription", "Panit.ent Graphics Editor" - VALUE "FileVersion", "1.0.0.0" - VALUE "InternalName", "panitent" - VALUE "LegalCopyright", "Copyright 2017-2021 Aragajaga" - VALUE "OriginalFilename", "panitent.exe" - VALUE "ProductName", "Panit.ent" - VALUE "ProductVersion", "1.0.0.0" - END - END - - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x00, 1200 - END -END + + + +// +// Bitmap resources +// +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDB_LAYERED BITMAP "layered.bmp" + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDB_LAYERED_PREMUL BITMAP "layered_premul.bmp" + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDB_CLOSEBTN BITMAP "close.bmp" + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDB_DOCK_BTNFRAME BITMAP "dockbtnframe.bmp" + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDB_DOCK_ICONS BITMAP "dockicons.bmp" + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDB_DOCK_SUGGEST BITMAP "docksuggest.bmp" + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDB_FLOATINGGLYPHS BITMAP "floatingglyphs.bmp" + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDB_TOOLS BITMAP "tool32bpp.bmp" + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDB_TOOLS24 BITMAP "tool32bpp@24.bmp" + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDB_DOCKHOSTBG BITMAP "dockhostbg.bmp" + + +// +// Dialog resources +// +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_ABOUTBOX DIALOGEX 0, 0, 170, 62 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +STYLE DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU CAPTION "About Panit.ent" -FONT 8, "MS Shell Dlg" -BEGIN - ICON IDI_ICON, -1, 14, 14, 21, 20 - LTEXT "Panit.ent, version 1.0", -1, 42, 14, 114, 8, SS_NOPREFIX - LTEXT "(C) 2017-2021 Aragajaga", -1, 42, 26, 114, 8 - DEFPUSHBUTTON "OK", IDOK, 113, 41, 50, 14, WS_GROUP -END +FONT 8, "MS Shell Dlg", 0, 0, 1 +{ + ICON IDI_ICON, -1, 14, 14, 21, 20, SS_ICON, WS_EX_LEFT + LTEXT "Panit.ent, version 1.0", -1, 42, 14, 114, 8, SS_LEFT | SS_NOPREFIX, WS_EX_LEFT + LTEXT "(C) 2017-2021 Aragajaga", -1, 42, 26, 114, 8, SS_LEFT, WS_EX_LEFT + DEFPUSHBUTTON "OK", IDOK, 113, 41, 50, 14, WS_GROUP, WS_EX_LEFT +} -IDD_VIEWPORTSETTINGS DIALOGEX 0, 0, 309, 176 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Viewport settings" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - DEFPUSHBUTTON "OK",IDOK,198,155,50,14 - PUSHBUTTON "Cancel",IDCANCEL,252,155,50,14 - CONTROL "Double buffering",IDC_DOUBLEBUFFER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,69,10 - CONTROL "Exclude canvas area when painting background",IDC_BKGNDEXCLUDECANVAS, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,20,168,10 - CONTROL "Fill two rectangles",IDC_BKGNDFILLRECT,"Button",BS_AUTORADIOBUTTON,17,33,73,10 - CONTROL "Use GDI regions",IDC_BKGNDREGION,"Button",BS_AUTORADIOBUTTON,17,44,67,10 - CONTROL "Show debug info",IDC_SHOWDEBUG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,61,69,10 -END -IDD_PALETTESETTINGS DIALOGEX 0, 0, 309, 176 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Palette settings" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - DEFPUSHBUTTON "OK",IDOK,198,155,50,14 - PUSHBUTTON "Cancel",IDCANCEL,252,155,50,14 - LTEXT "Swatch size:",-1,7,10,41,8 - EDITTEXT IDC_SWATCHSIZEEDIT,52,7,40,13,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER - CONTROL "",IDC_SWATCHSIZESPIN,"msctls_updown32",UDS_ARROWKEYS,92,6,12,14 - LTEXT "Checker:",-1,7,27,32,8 - EDITTEXT IDC_CHECKERSIZEEDIT,52,26,40,13,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER - CONTROL "",IDC_CHECKERSIZESPIN,"msctls_updown32",UDS_ARROWKEYS,92,25,12,14 - CONTROL "",IDC_CHECKERCOLOR1,"Win32Class_SwatchControl2",WS_BORDER | WS_TABSTOP,51,44,17,16 - CONTROL "",IDC_CHECKERCOLOR2,"Win32Class_SwatchControl2",WS_BORDER | WS_TABSTOP,73,44,18,16 -END -IDD_TOOLBOXSETTINGS DIALOGEX 0, 0, 309, 176 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Toolbox settings" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - DEFPUSHBUTTON "OK",IDOK,198,155,50,14 - PUSHBUTTON "Cancel",IDCANCEL,252,155,50,14 - LTEXT "Icon theme:",-1,7,10,41,8 - COMBOBOX IDC_TOOLBARICONTHEME,47,7,132,30,CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP -END +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_ACTIVITYSTUB DIALOG 0, 0, 217, 52 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SETFOREGROUND | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +CAPTION "ActivityStub" +FONT 8, "Ms Shell Dlg" +{ + LTEXT "Static", IDS_ACTIVITYTEXT, 35, 10, 175, 35, SS_LEFT, WS_EX_LEFT + ICON IDI_ICON, -1, 5, 5, 21, 20, SS_ICON, WS_EX_LEFT +} + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_BRUSHPROP DIALOGEX 0, 0, 309, 176 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +STYLE DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU CAPTION "Brush Properties" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - DEFPUSHBUTTON "OK",IDOK,198,155,50,14 - PUSHBUTTON "Cancel",IDCANCEL,252,155,50,14 - CONTROL "",IDC_BRUSHSIZE,"msctls_trackbar32",TBS_NOTICKS | WS_TABSTOP,183,53,119,15 - LISTBOX IDC_BRUSHLIST,7,7,147,162,LBS_SORT | LBS_NOINTEGRALHEIGHT | LBS_OWNERDRAWFIXED | WS_VSCROLL | WS_TABSTOP - LTEXT "Size:",-1,165,56,16,8 - CONTROL "",IDC_BRUSHPREVIEW,"Static",WS_BORDER | SS_BITMAP,165,7,137,41 -END +FONT 8, "MS Shell Dlg", 400, 0, 1 +{ + DEFPUSHBUTTON "OK", IDOK, 198, 155, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Cancel", IDCANCEL, 252, 155, 50, 14, 0, WS_EX_LEFT + CONTROL "", IDC_BRUSHSIZE, TRACKBAR_CLASS, WS_TABSTOP | TBS_NOTICKS, 183, 53, 119, 15, WS_EX_LEFT + LISTBOX IDC_BRUSHLIST, 7, 7, 147, 162, WS_TABSTOP | WS_VSCROLL | LBS_NOINTEGRALHEIGHT | LBS_OWNERDRAWFIXED | LBS_SORT | LBS_NOTIFY, WS_EX_LEFT + LTEXT "Size:", -1, 165, 56, 16, 8, SS_LEFT, WS_EX_LEFT + CONTROL "", IDC_BRUSHPREVIEW, WC_STATIC, WS_BORDER | SS_BITMAP, 165, 7, 137, 41, WS_EX_LEFT +} + -IDD_NEWDOCUMENT DIALOGEX 0, 0, 173, 139 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "New Document" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - DEFPUSHBUTTON "OK",IDOK,109,115,57,17 - LTEXT "Preset",-1,7,9,22,8,0,WS_EX_RIGHT - COMBOBOX IDC_DOCUMENTPRESET,34,7,132,30,CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP - LTEXT "Width",-1,38,37,20,8,0,WS_EX_RIGHT - EDITTEXT IDC_DOCUMENTWIDTH,62,34,63,14,ES_AUTOHSCROLL - LTEXT "Height",-1,36,55,22,8,0,WS_EX_RIGHT - EDITTEXT IDC_DOCUMENTHEIGHT,62,52,63,14,ES_AUTOHSCROLL - PUSHBUTTON "",IDC_WHLOCK,131,44,15,14,BS_BITMAP - PUSHBUTTON "",IDC_WHSWAP,151,44,15,14,BS_BITMAP - LTEXT "Background",-1,20,80,38,8,0,WS_EX_RIGHT - COMBOBOX IDC_DOCUMENTBKGND,62,78,88,30,CBS_DROPDOWNLIST | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS | CBS_CDCLRSEL_SWATCHBIAS | WS_VSCROLL | WS_TABSTOP - LTEXT "Est. RAM:",IDC_DOCUMENTRAMLABEL,25,100,33,8,0,WS_EX_RIGHT - LTEXT "0 Kb",IDC_DOCUMENTRAM,62,100,15,8 - PUSHBUTTON "",IDC_WHNOLIMIT,7,118,15,14,BS_BITMAP -END +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DOCKINSPECTOR DIALOGEX 0, 0, 473, 297 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +STYLE DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU CAPTION "Dialog" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - CONTROL "",IDC_TREE1,"SysTreeView32", WS_BORDER | WS_TABSTOP | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT,7,7,171,283 - CONTROL "",IDC_LIST1,"SysListView32",LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,185,7,281,283 -END +FONT 8, "MS Shell Dlg", 400, 0, 1 +{ + CONTROL "", IDC_TREE1, WC_TREEVIEW, WS_TABSTOP | WS_BORDER | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT, 7, 7, 171, 283, WS_EX_LEFT + CONTROL "", IDC_LIST1, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_ICON, 185, 7, 281, 283, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_NEWDOCUMENT DIALOGEX 0, 0, 173, 139 +STYLE DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU +CAPTION "New Document" +FONT 8, "MS Shell Dlg", 400, 0, 1 +{ + DEFPUSHBUTTON "OK", IDOK, 109, 115, 57, 17, 0, WS_EX_LEFT + LTEXT "Preset", -1, 7, 9, 22, 8, SS_LEFT, WS_EX_RIGHT + COMBOBOX IDC_DOCUMENTPRESET, 34, 7, 132, 30, WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT + LTEXT "Width", -1, 38, 37, 20, 8, SS_LEFT, WS_EX_RIGHT + EDITTEXT IDC_DOCUMENTWIDTH, 62, 34, 63, 14, ES_AUTOHSCROLL, WS_EX_LEFT + LTEXT "Height", -1, 36, 55, 22, 8, SS_LEFT, WS_EX_RIGHT + EDITTEXT IDC_DOCUMENTHEIGHT, 62, 52, 63, 14, ES_AUTOHSCROLL, WS_EX_LEFT + PUSHBUTTON "", IDC_WHLOCK, 131, 44, 15, 14, BS_BITMAP, WS_EX_LEFT + PUSHBUTTON "", IDC_WHSWAP, 151, 44, 15, 14, BS_BITMAP, WS_EX_LEFT + LTEXT "Background", -1, 20, 80, 38, 8, SS_LEFT, WS_EX_RIGHT + COMBOBOX IDC_DOCUMENTBKGND, 62, 78, 88, 30, WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST | CBS_HASSTRINGS | CBS_OWNERDRAWFIXED | 0x00000080, WS_EX_LEFT + LTEXT "Est. RAM:", IDC_DOCUMENTRAMLABEL, 25, 100, 33, 8, SS_LEFT, WS_EX_RIGHT + LTEXT "0 Kb", IDC_DOCUMENTRAM, 62, 100, 15, 8, SS_LEFT, WS_EX_LEFT + PUSHBUTTON "", IDC_WHNOLIMIT, 7, 118, 15, 14, BS_BITMAP, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_PALETTESETTINGS DIALOGEX 0, 0, 309, 176 +STYLE DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU +CAPTION "Palette settings" +FONT 8, "MS Shell Dlg", 400, 0, 1 +{ + DEFPUSHBUTTON "OK", IDOK, 198, 155, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Cancel", IDCANCEL, 252, 155, 50, 14, 0, WS_EX_LEFT + LTEXT "Swatch size:", -1, 7, 10, 41, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_SWATCHSIZEEDIT, 52, 7, 40, 13, ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER, WS_EX_LEFT + CONTROL "", IDC_SWATCHSIZESPIN, UPDOWN_CLASS, UDS_ARROWKEYS, 92, 6, 12, 14, WS_EX_LEFT + LTEXT "Checker:", -1, 7, 27, 32, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_CHECKERSIZEEDIT, 52, 26, 40, 13, ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER, WS_EX_LEFT + CONTROL "", IDC_CHECKERSIZESPIN, UPDOWN_CLASS, UDS_ARROWKEYS, 92, 25, 12, 14, WS_EX_LEFT + CONTROL "", IDC_CHECKERCOLOR1, "Win32Class_SwatchControl2", 0x50810000, 51, 44, 17, 16, 0x00000000 + CONTROL "", IDC_CHECKERCOLOR2, "Win32Class_SwatchControl2", 0x50810000, 73, 44, 18, 16, 0x00000000 +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_PROPERTYGRIDDLG DIALOG 0, 0, 320, 240 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +CAPTION "Property Grid" +FONT 8, "Ms Shell Dlg" +{ + CONTROL "", IDC_PROPERTYGRID, "PropGridCtl", 0x50020000, 5, 5, 310, 230, 0x00000000 +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_TOOLBOXSETTINGS DIALOGEX 0, 0, 309, 176 +STYLE DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU +CAPTION "Toolbox settings" +FONT 8, "MS Shell Dlg", 400, 0, 1 +{ + DEFPUSHBUTTON "OK", IDOK, 198, 155, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Cancel", IDCANCEL, 252, 155, 50, 14, 0, WS_EX_LEFT + LTEXT "Icon theme:", -1, 7, 10, 41, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_TOOLBARICONTHEME, 47, 7, 132, 30, WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_VIEWPORTSETTINGS DIALOGEX 0, 0, 309, 176 +STYLE DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU +CAPTION "Viewport settings" +FONT 8, "MS Shell Dlg", 400, 0, 1 +{ + DEFPUSHBUTTON "OK", IDOK, 198, 155, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Cancel", IDCANCEL, 252, 155, 50, 14, 0, WS_EX_LEFT + AUTOCHECKBOX "Double buffering", IDC_DOUBLEBUFFER, 7, 7, 69, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Exclude canvas area when painting background", IDC_BKGNDEXCLUDECANVAS, 7, 20, 168, 10, 0, WS_EX_LEFT + AUTORADIOBUTTON "Fill two rectangles", IDC_BKGNDFILLRECT, 17, 33, 73, 10, 0, WS_EX_LEFT + AUTORADIOBUTTON "Use GDI regions", IDC_BKGNDREGION, 17, 44, 67, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Show debug info", IDC_SHOWDEBUG, 7, 61, 69, 10, 0, WS_EX_LEFT +} + + + +// +// Cursor resources +// +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDC_BUCKET CURSOR "bucket.cur" + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDC_PENCIL CURSOR "pencil.cur" + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDC_BRUSH CURSOR "brush.cur" + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDC_PICKER CURSOR "picker.cur" + + + +// +// Icon resources +// +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDI_ICON ICON "panitent.ico" + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDI_LOG ICON "log.ico" diff --git a/res/resource.h b/res/resource.h index ef2f283..722a070 100644 --- a/res/resource.h +++ b/res/resource.h @@ -1,55 +1,52 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by panitent.rc -// -#define VS_VERSION_INFO 1 -#define RT_MANIFEST 24 -#define IDD_ABOUTBOX 100 -#define CBS_CDCLRSEL_SWATCHBIAS 128 -#define IDD_VIEWPORTSETTINGS 129 -#define IDD_PALETTESETTINGS 130 -#define IDD_TOOLBOXSETTINGS 131 -#define IDD_BRUSHPROP 132 -#define IDD_NEWDOCUMENT 133 -#define IDC_DOUBLEBUFFER 1000 -#define IDC_BKGNDEXCLUDECANVAS 1001 -#define IDC_TOOLBARICONTHEME 1001 -#define IDC_BKGNDFILLRECT 1002 -#define IDC_BKGNDREGION 1003 -#define IDC_SHOWDEBUG 1004 -#define IDC_SWATCHSIZEEDIT 1005 -#define IDC_SWATCHSIZESPIN 1006 -#define IDC_CHECKERSIZEEDIT 1007 -#define IDC_CHECKERSIZESPIN 1008 -#define IDC_CHECKERCOLOR1 1009 -#define IDC_CHECKERCOLOR2 1010 -#define IDC_BRUSHSIZE 1011 -#define IDC_BRUSHLIST 1012 -#define IDC_BRUSHPREVIEW 1013 -#define IDC_DOCUMENTPRESET 1014 -#define IDC_DOCUMENTWIDTH 1015 -#define IDC_DOCUMENTHEIGHT 1016 -#define IDC_WHLOCK 1017 -#define IDC_WHSWAP 1018 -#define IDC_DOCUMENTBKGND 1019 -#define IDC_DOCUMENTRAMLABEL 1020 -#define IDC_DOCUMENTRAM 1021 -#define IDC_WHNOLIMIT 1022 -#define IDB_FLOATINGGLYPHS 1024 -#define IDI_ICON 1101 -#define IDI_LOG 1102 -#define IDB_TOOLS 1201 -#define IDB_TOOLS24 1202 -#define IDB_CLOSEBTN 1203 -#define IDC_STATIC -1 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1000 -#define _APS_NEXT_SYMED_VALUE 101 -#endif +#ifndef IDC_STATIC +#define IDC_STATIC (-1) #endif + +#define IDD_ABOUTBOX 100 +#define IDD_VIEWPORTSETTINGS 129 +#define IDD_PALETTESETTINGS 130 +#define IDD_TOOLBOXSETTINGS 131 +#define IDD_BRUSHPROP 132 +#define IDD_NEWDOCUMENT 133 +#define IDD_DOCKINSPECTOR 134 +#define IDD_ACTIVITYSTUB 135 +#define IDD_PROPERTYGRIDDLG 136 +#define IDC_DOUBLEBUFFER 1000 +#define IDC_BKGNDEXCLUDECANVAS 1001 +#define IDC_TOOLBARICONTHEME 1001 +#define IDC_BKGNDFILLRECT 1002 +#define IDC_BKGNDREGION 1003 +#define IDC_SHOWDEBUG 1004 +#define IDC_SWATCHSIZEEDIT 1005 +#define IDC_SWATCHSIZESPIN 1006 +#define IDC_CHECKERSIZEEDIT 1007 +#define IDC_CHECKERSIZESPIN 1008 +#define IDC_CHECKERCOLOR1 1009 +#define IDC_CHECKERCOLOR2 1010 +#define IDC_BRUSHSIZE 1011 +#define IDC_BRUSHLIST 1012 +#define IDC_BRUSHPREVIEW 1013 +#define IDC_DOCUMENTPRESET 1014 +#define IDC_DOCUMENTWIDTH 1015 +#define IDC_DOCUMENTHEIGHT 1016 +#define IDC_WHLOCK 1017 +#define IDC_WHSWAP 1018 +#define IDC_DOCUMENTBKGND 1019 +#define IDC_DOCUMENTRAMLABEL 1020 +#define IDC_DOCUMENTRAM 1021 +#define IDC_WHNOLIMIT 1022 +#define IDC_TREE1 1023 +#define IDB_FLOATINGGLYPHS 1024 +#define IDC_LIST1 1024 +#define IDB_DOCK_ICONS 1025 +#define IDS_ACTIVITYTEXT 1025 +#define IDB_DOCK_BTNFRAME 1026 +#define IDC_PROPERTYGRID 1026 +#define IDB_DOCK_SUGGEST 1027 +#define IDI_ICON 1101 +#define IDI_LOG 1102 +#define IDB_TOOLS 1201 +#define IDB_TOOLS24 1202 +#define IDB_CLOSEBTN 1203 +#define IDC_PICKER 1601 +#define IDC_BRUSH 1602 diff --git a/res/wisp.bmp b/res/wisp.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6fe7b09f3df1885f29cc34ab36194a657847308b GIT binary patch literal 65590 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 literal 0 HcmV?d00001 diff --git a/src/aboutbox.c b/src/aboutbox.c index 7fa5c73..a6229da 100644 --- a/src/aboutbox.c +++ b/src/aboutbox.c @@ -3,21 +3,26 @@ #include "aboutbox.h" #include "resource.h" -INT_PTR CALLBACK AboutDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, - LPARAM lParam) +#include "panitent.h" +#include "sharing/activitysharingmanager.h" + +INT_PTR CALLBACK AboutDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: -#ifdef HAS_DISCORDSDK - Discord_SetActivityStatus(g_panitent.discord, L"Seeking About dialog 👀"); -#endif /* HAS_DISCORDSDK */ + { + Panitent_SetActivityStatus(Panitent_GetApp(), L"Seeking About dialog 👀"); + } return TRUE; + break; + case WM_COMMAND: EndDialog(hwndDlg, wParam); return TRUE; + break; } return FALSE; diff --git a/src/bresenham.c b/src/bresenham.c index 50bb40b..f1cb5a8 100644 --- a/src/bresenham.c +++ b/src/bresenham.c @@ -2,131 +2,130 @@ primitives_context_t g_bresenham_primitives; -void bresenham_circle_plot(Plotter p, int xc, int yc, int x, - int y) +void bresenham_circle_plot(Plotter p, int xc, int yc, int x, int y) { - p.fn(p.userData, xc + x, yc + y, 1.f); - p.fn(p.userData, xc - x, yc + y, 1.f); - p.fn(p.userData, xc + x, yc - y, 1.f); - p.fn(p.userData, xc - x, yc - y, 1.f); - p.fn(p.userData, xc + y, yc + x, 1.f); - p.fn(p.userData, xc - y, yc + x, 1.f); - p.fn(p.userData, xc + y, yc - x, 1.f); - p.fn(p.userData, xc - y, yc - x, 1.f); + p.fn(p.userData, xc + x, yc + y, 1.f); + p.fn(p.userData, xc - x, yc + y, 1.f); + p.fn(p.userData, xc + x, yc - y, 1.f); + p.fn(p.userData, xc - x, yc - y, 1.f); + p.fn(p.userData, xc + y, yc + x, 1.f); + p.fn(p.userData, xc - y, yc + x, 1.f); + p.fn(p.userData, xc + y, yc - x, 1.f); + p.fn(p.userData, xc - y, yc - x, 1.f); } -static void bresenham_circle_fill(Plotter p, int xc, int yc, - int x, int y) +static void bresenham_circle_fill(Plotter p, int xc, int yc, int x, int y) { - bresenham_line(p, xc - x, yc + y, xc + x, yc + y); - bresenham_line(p, xc - x, yc - y, xc + x, yc - y); - bresenham_line(p, xc - y, yc + x, xc + y, yc + x); - bresenham_line(p, xc - y, yc - x, xc + y, yc - x); + bresenham_line(p, xc - x, yc + y, xc + x, yc + y); + bresenham_line(p, xc - x, yc - y, xc + x, yc - y); + bresenham_line(p, xc - y, yc + x, xc + y, yc + x); + bresenham_line(p, xc - y, yc - x, xc + y, yc - x); } void bresenham_circle(Plotter p, int cx, int cy, int radius) { - int x = 0; - int y = radius; - int d = 3 - 2 * radius; - bresenham_circle_plot(p, cx, cy, x, y); - - while (y >= x) { - x++; - - if (d > 0) { - y--; - d = d + 4 * (x - y) + 10; - } else { - d = d + 4 * x + 6; - } + int x = 0; + int y = radius; + int d = 3 - 2 * radius; bresenham_circle_plot(p, cx, cy, x, y); - } + + while (y >= x) { + x++; + + if (d > 0) { + y--; + d = d + 4 * (x - y) + 10; + } + else { + d = d + 4 * x + 6; + } + bresenham_circle_plot(p, cx, cy, x, y); + } } -void bresenham_filled_circle(Plotter p, int cx, int cy, - int radius) +void bresenham_filled_circle(Plotter p, int cx, int cy, int radius) { - int x = 0; - int y = radius; - int d = 3 - 2 * radius; - bresenham_circle_fill(p, cx, cy, x, y); - - while (y >= x) { - x++; - - if (d > 0) { - y--; - d = d + 4 * (x - y) + 10; - } else { - d = d + 4 * x + 6; - } + int x = 0; + int y = radius; + int d = 3 - 2 * radius; bresenham_circle_fill(p, cx, cy, x, y); - } + + while (y >= x) { + x++; + + if (d > 0) { + y--; + d = d + 4 * (x - y) + 10; + } + else { + d = d + 4 * x + 6; + } + bresenham_circle_fill(p, cx, cy, x, y); + } } int sign(int x) { - if (x > 0) - return 1; - else if (x < 0) - return -1; - else - return 0; + if (x > 0) + return 1; + else if (x < 0) + return -1; + else + return 0; } static inline void bresenham_line_rc(Plotter p, rect_t rc) { - bresenham_line(p, rc.x0, rc.y0, rc.x1, rc.y1); + bresenham_line(p, rc.x0, rc.y0, rc.x1, rc.y1); } void bresenham_line(Plotter p, int x0, int y0, int x1, int y1) { - int x = x0; - int y = y0; - int dx = abs(x1 - x0); - int dy = abs(y1 - y0); - int s1 = sign(x1 - x0); - int s2 = sign(y1 - y0); - int swap = 0; - int temp; - int p_; - int i; - - p.fn(p.userData, x0, y0, 1.f); - - if (dy > dx) { - temp = dx; - dx = dy; - dy = temp; - swap = 1; - } - - p_ = 2 * dy - dx; - - for (i = 0; i < dx; i++) - { - p.fn(p.userData, x, y, 1.f); + int x = x0; + int y = y0; + int dx = abs(x1 - x0); + int dy = abs(y1 - y0); + int s1 = sign(x1 - x0); + int s2 = sign(y1 - y0); + int swap = 0; + int temp; + int p_; + int i; + + p.fn(p.userData, x0, y0, 1.f); + + if (dy > dx) { + temp = dx; + dx = dy; + dy = temp; + swap = 1; + } - while (p_ >= 0) { - p_ = p_ - 2 * dx; - if (swap) - x += s1; - else - y += s2; + p_ = 2 * dy - dx; + + for (i = 0; i < dx; i++) + { + p.fn(p.userData, x, y, 1.f); + + while (p_ >= 0) { + p_ = p_ - 2 * dx; + if (swap) + x += s1; + else + y += s2; + } + p_ = p_ + 2 * dy; + if (swap) + y += s2; + else + x += s1; } - p_ = p_ + 2 * dy; - if (swap) - y += s2; - else - x += s1; - } - p.fn(p.userData, x, y, 1.f); + p.fn(p.userData, x, y, 1.f); } void bresenham_init() { - g_bresenham_primitives.line = bresenham_line; - g_bresenham_primitives.circle = bresenham_circle; + g_bresenham_primitives.line = bresenham_line; + g_bresenham_primitives.circle = bresenham_circle; } diff --git a/src/brush.c b/src/brush.c index 0339f2e..6303a91 100644 --- a/src/brush.c +++ b/src/brush.c @@ -7,9 +7,9 @@ #include "primitives_context.h" typedef struct _Brush { - Canvas* tex; - float distance; - BrushBuilder* builder; + Canvas* tex; + float distance; + BrushBuilder* builder; } Brush; BrushBuilder g_brushList[80]; @@ -24,196 +24,197 @@ Brush* TextureBrushBuilder_Build(BrushBuilder* builder, int size); BrushBuilder GetPointBrushBuilder() { - BrushBuilder builder = {0}; - builder.fn = PointBrushBuilder_Build; - return builder; + BrushBuilder builder = { 0 }; + builder.fn = PointBrushBuilder_Build; + return builder; } BrushBuilder GetCircleBrushBuilder() { - BrushBuilder builder = {0}; - builder.fn = CircleBrushBuilder_Build; - return builder; + BrushBuilder builder = { 0 }; + builder.fn = CircleBrushBuilder_Build; + return builder; } BrushBuilder GetSquareBrushBuilder() { - BrushBuilder builder = {0}; - builder.fn = SquareBrushBuilder_Build; - return builder; + BrushBuilder builder = { 0 }; + builder.fn = SquareBrushBuilder_Build; + return builder; } BrushBuilder GetTextureBrushBuilder(Canvas* tex) { - BrushBuilder builder = {0}; - builder.fn = TextureBrushBuilder_Build; - builder.userData = tex; - return builder; + BrushBuilder builder = { 0 }; + builder.fn = TextureBrushBuilder_Build; + builder.userData = tex; + return builder; } Brush* BrushBuilder_Build(BrushBuilder* builder, int size) { - return (*builder->fn)(builder, size); + return (*builder->fn)(builder, size); } void InitializeBrushList() { - g_brushList[g_brushListLen++] = GetPointBrushBuilder(); - g_brushList[g_brushListLen++] = GetCircleBrushBuilder(); - g_brushList[g_brushListLen++] = GetSquareBrushBuilder(); + g_brushList[g_brushListLen++] = GetPointBrushBuilder(); + g_brushList[g_brushListLen++] = GetCircleBrushBuilder(); + g_brushList[g_brushListLen++] = GetSquareBrushBuilder(); } BrushBuilder* Brush_GetBuilder(Brush* brush) { - return brush->builder; + return brush->builder; } void Brush_SetBuilder(Brush* brush, BrushBuilder* builder) { - brush->builder = builder; + brush->builder = builder; } float euclidean_distance(float px, float py) { - return 1.f - min(sqrtf(powf(px, 2.f) + powf(py, 2.f)), 1.f); + return 1.f - min(sqrtf(powf(px, 2.f) + powf(py, 2.f)), 1.f); } Brush* Brush_Create(Canvas* tex) { - assert(tex); - if (!tex) - return NULL; - - Brush *brush = calloc(1, sizeof(Brush)); - if (brush) - { - brush->distance = 0.25; - brush->tex = tex; - } - - return brush; + assert(tex); + if (!tex) + return NULL; + + Brush* brush = (Brush*)malloc(sizeof(Brush)); + memset(brush, 0, sizeof(Brush)); + if (brush) + { + brush->distance = 0.25; + brush->tex = tex; + } + + return brush; } Brush* TextureBrushBuilder_Build(BrushBuilder* builder, int size) { - UNREFERENCED_PARAMETER(size); + UNREFERENCED_PARAMETER(size); - Canvas* tex = (Canvas*)builder->userData; - Brush* brush = Brush_Create(tex); - Brush_SetBuilder(brush, builder); - return brush; + Canvas* tex = (Canvas*)builder->userData; + Brush* brush = Brush_Create(tex); + Brush_SetBuilder(brush, builder); + return brush; } Brush* PointBrushBuilder_Build(BrushBuilder* builder, int size) { - Canvas* tex = Canvas_Create(size, size); - uint32_t *buffer = tex->buffer; - for (int y = 0; y < tex->height; y++) - { - for (int x = 0; x < tex->width; x++) + Canvas* tex = Canvas_Create(size, size); + uint32_t* buffer = tex->buffer; + for (int y = 0; y < tex->height; y++) { - float value = euclidean_distance( - (x / (float)tex->width - 0.5f) * 2, - (y / (float)tex->height - 0.5f) * 2) * 255.f; + for (int x = 0; x < tex->width; x++) + { + float value = euclidean_distance( + (x / (float)tex->width - 0.5f) * 2, + (y / (float)tex->height - 0.5f) * 2) * 255.f; - uint8_t byte = (uint8_t)(round(value)); + uint8_t byte = (uint8_t)(round(value)); - buffer[x + tex->width * y] = byte << 24; + buffer[x + tex->width * y] = byte << 24; + } } - } - Brush* brush = Brush_Create(tex); - Brush_SetBuilder(brush, builder); - return brush; + Brush* brush = Brush_Create(tex); + Brush_SetBuilder(brush, builder); + return brush; } Brush* CircleBrushBuilder_Build(BrushBuilder* builder, int size) { - Canvas* tex = Canvas_Create(size, size); - draw_filled_circle_color(tex, size/2, size/2, size/2, 0xFF000000); + Canvas* tex = Canvas_Create(size, size); + draw_filled_circle_color(tex, size / 2, size / 2, size / 2, 0xFF000000); - Brush* brush = Brush_Create(tex); - Brush_SetBuilder(brush, builder); - return brush; + Brush* brush = Brush_Create(tex); + Brush_SetBuilder(brush, builder); + return brush; } Brush* SquareBrushBuilder_Build(BrushBuilder* builder, int size) { - Canvas* tex = Canvas_Create(size, size); - Canvas_FillSolid(tex, 0xFF000000); + Canvas* tex = Canvas_Create(size, size); + Canvas_FillSolid(tex, 0xFF000000); - Brush* brush = Brush_Create(tex); - Brush_SetBuilder(brush, builder); - return brush; + Brush* brush = Brush_Create(tex); + Brush_SetBuilder(brush, builder); + return brush; } void Brush_Draw(Brush* brush, int x, int y, Canvas* target, uint32_t color) { - Canvas_ColorStencil(target, x - brush->tex->width/2, y - brush->tex->height/2, - brush->tex, color); + Canvas_ColorStencil(target, x - brush->tex->width / 2, y - brush->tex->height / 2, + brush->tex, color); } static int sign(int x) { - if (x > 0) - return 1; - else if (x < 0) - return -1; - else - return 0; + if (x > 0) + return 1; + else if (x < 0) + return -1; + else + return 0; } void Brush_DrawTo(Brush* brush, int x0, int y0, int x1, int y1, Canvas* target, uint32_t color) { - int dx = abs(x1 - x0); - int dy = abs(y1 - y0); - int signx = sign(x1 - x0); - int signy = sign(y1 - y0); - - if (dy > dx) - { - float slope = dx/(float)dy; + int dx = abs(x1 - x0); + int dy = abs(y1 - y0); + int signx = sign(x1 - x0); + int signy = sign(y1 - y0); - for (int i = 0; i < dy; i++) + if (dy > dx) { - Brush_Draw(brush, x0 + (int)roundf((float)i * slope * (float)signx), y0 + i * signy, target, color); + float slope = dx / (float)dy; + + for (int i = 0; i < dy; i++) + { + Brush_Draw(brush, x0 + (int)roundf((float)i * slope * (float)signx), y0 + i * signy, target, color); + } } - } - else { - float slope = dy/(float)dx; + else { + float slope = dy / (float)dx; - for (int i = 0; i < dx; i++) - { - Brush_Draw(brush, x0 + i * signx, y0 + (int)roundf((float)i * slope * (float)signy), target, color); + for (int i = 0; i < dx; i++) + { + Brush_Draw(brush, x0 + i * signx, y0 + (int)roundf((float)i * slope * (float)signy), target, color); + } } - } } void Brush_Delete(Brush* brush) { - assert(brush); - if (!brush) - return; + assert(brush); + if (!brush) + return; - Canvas_Delete(brush->tex); - free(brush); + Canvas_Delete(brush->tex); + free(brush); } Canvas* Brush_GetTexture(Brush* brush) { - assert(brush); - if (!brush) - return NULL; + assert(brush); + if (!brush) + return NULL; - return brush->tex; + return brush->tex; } inline static int BezierGetPt(int n1, int n2, float perc) { - int diff = n2 - n1; - return n1 + (int)((float)diff * perc); + int diff = n2 - n1; + return n1 + (int)((float)diff * perc); } void Brush_BezierCurve(Brush* brush, Canvas* canvas, @@ -223,25 +224,25 @@ void Brush_BezierCurve(Brush* brush, Canvas* canvas, int x3, int y3, uint32_t color) { - for (float i = 0; i < 1.f; i += 0.01f) - { - int xa = BezierGetPt(x0, x1, i); - int ya = BezierGetPt(y0, y1, i); - int xb = BezierGetPt(x1, x2, i); - int yb = BezierGetPt(y1, y2, i); - int xc = BezierGetPt(x2, x3, i); - int yc = BezierGetPt(y2, y3, i); - - int xm = BezierGetPt(xa, xb, i); - int ym = BezierGetPt(ya, yb, i); - int xn = BezierGetPt(xb, xc, i); - int yn = BezierGetPt(yb, yc, i); - - int x = BezierGetPt(xm, xn, i); - int y = BezierGetPt(ym, yn, i); - - Brush_Draw(brush, x, y, canvas, color); - } + for (float i = 0; i < 1.f; i += 0.01f) + { + int xa = BezierGetPt(x0, x1, i); + int ya = BezierGetPt(y0, y1, i); + int xb = BezierGetPt(x1, x2, i); + int yb = BezierGetPt(y1, y2, i); + int xc = BezierGetPt(x2, x3, i); + int yc = BezierGetPt(y2, y3, i); + + int xm = BezierGetPt(xa, xb, i); + int ym = BezierGetPt(ya, yb, i); + int xn = BezierGetPt(xb, xc, i); + int yn = BezierGetPt(yb, yc, i); + + int x = BezierGetPt(xm, xn, i); + int y = BezierGetPt(ym, yn, i); + + Brush_Draw(brush, x, y, canvas, color); + } } void Brush_BezierCurve2(Brush* brush, Canvas* canvas, @@ -251,33 +252,33 @@ void Brush_BezierCurve2(Brush* brush, Canvas* canvas, int x3, int y3, uint32_t color) { - for (float u = 0.f; u <= 1.f; u += 0.01f) - { - float xu = - powf(1.f - u, 3.f) * (float)x0 + 3.f * u * - powf(1.f - u, 2.f) * (float)x1 + 3.f * - powf(u, 2.f) * (1.f - u) * (float)x2 + - powf(u, 3.f) * (float)x3; - - float yu = - powf(1.f - u, 3.f) * (float)y0 + 3.f * u * - powf(1.f - u, 2.f) * (float)y1 + 3.f * - powf(u, 2.f) * (1.f - u) * (float)y2 + - powf(u, 3.f) * (float)y3; - - Brush_Draw(brush, (int)xu, (int)yu, canvas, color); - } + for (float u = 0.f; u <= 1.f; u += 0.01f) + { + float xu = + powf(1.f - u, 3.f) * (float)x0 + 3.f * u * + powf(1.f - u, 2.f) * (float)x1 + 3.f * + powf(u, 2.f) * (1.f - u) * (float)x2 + + powf(u, 3.f) * (float)x3; + + float yu = + powf(1.f - u, 3.f) * (float)y0 + 3.f * u * + powf(1.f - u, 2.f) * (float)y1 + 3.f * + powf(u, 2.f) * (1.f - u) * (float)y2 + + powf(u, 3.f) * (float)y3; + + Brush_Draw(brush, (int)xu, (int)yu, canvas, color); + } } void Brush_SetSize(Brush** brush, int size) { - BrushBuilder* builder = Brush_GetBuilder(*brush); - assert(builder); - if (!builder) - return; + BrushBuilder* builder = Brush_GetBuilder(*brush); + assert(builder); + if (!builder) + return; - Brush* newBrush = BrushBuilder_Build(builder, size); - Brush_Delete(*brush); + Brush* newBrush = BrushBuilder_Build(builder, size); + Brush_Delete(*brush); - *brush = newBrush; + *brush = newBrush; } diff --git a/src/brush.h b/src/brush.h index 53cabf3..5fe6cc5 100644 --- a/src/brush.h +++ b/src/brush.h @@ -12,22 +12,15 @@ typedef struct _BrushBuilder { Brush* Brush_Create(Canvas* tex); void Brush_Draw(Brush* brush, int x, int y, Canvas* target, uint32_t color); -void Brush_DrawTo(Brush* brush, int x0, int y0, int x1, int y2, Canvas* target, - uint32_t color); -Brush* TextureBrushBuilder(void* userData, int size); -Brush* CircleBrushBuilder(void* userData, int size); -Brush* SquareBrushBuilder(void* userData, int size); -Brush* PointBrushBuilder(void* userData, int size); +void Brush_DrawTo(Brush* brush, int x0, int y0, int x1, int y2, Canvas* target, uint32_t color); void Brush_Delete(Brush* brush); Canvas* Brush_GetTexture(Brush* brush); void InitializeBrushList(); BrushBuilder* Brush_GetBuilder(Brush* brush); Brush* BrushBuilder_Build(BrushBuilder* builder, int size); -void Brush_BezierCurve(Brush* brush, Canvas* canvas, int x0, int y0, int x1, - int y1, int x2, int y2, int x3, int y3, uint32_t color); -void Brush_BezierCurve2(Brush* brush, Canvas* canvas, int x0, int y0, int x1, - int y1, int x2, int y2, int x3, int y3, uint32_t color); +void Brush_BezierCurve(Brush* brush, Canvas* canvas, int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3, uint32_t color); +void Brush_BezierCurve2(Brush* brush, Canvas* canvas, int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3, uint32_t color); void Brush_SetSize(Brush** brush, int size); extern BrushBuilder g_brushList[80]; diff --git a/src/canvas.c b/src/canvas.c index b483439..2a30d43 100644 --- a/src/canvas.c +++ b/src/canvas.c @@ -57,7 +57,7 @@ void* Canvas_BufferAlloc(Canvas* canvas) size_t buffer_size = (size_t)canvas->width * (size_t)abs(canvas->height) * (size_t)canvas->color_depth; canvas->buffer_size = buffer_size; - canvas->buffer = calloc(buffer_size, sizeof(uint8_t)); + canvas->buffer = malloc(buffer_size * sizeof(uint8_t)); return canvas->buffer; } @@ -227,7 +227,7 @@ void Canvas_ColorStencilBits(Canvas* canvas, void* bits, int x, int y, int width uint32_t* pTarget = (uint32_t*)canvas->buffer; /* Get starting pixel pointer */ - pTarget += (size_t)max(0, y) * (size_t)canvas->width + (size_t)max(0, x); + pTarget += (size_t)max(0, y) * (size_t)canvas->width + (size_t)max(0, x); /* Get intersection */ Rect rectCanvas = { 0 }; @@ -292,12 +292,13 @@ void Canvas_ColorStencilBits(Canvas* canvas, void* bits, int x, int y, int width Canvas* Canvas_Clone(Canvas* canvas) { - Canvas* clone = calloc(1, sizeof(Canvas)); + Canvas* clone = (Canvas*)malloc(sizeof(Canvas)); + memset(clone, 0, sizeof(Canvas)); if (!clone) return NULL; memcpy(clone, canvas, sizeof(Canvas)); - clone->buffer = calloc(1, canvas->buffer_size); + clone->buffer = (uint8_t*)malloc(canvas->buffer_size); if (!clone->buffer) { free(clone); @@ -321,7 +322,8 @@ Canvas* Canvas_Substitute(Canvas* canvas, RECT *rc) int subWidth = rc->right - rc->left; int subHeight = rc->bottom - rc->top; - Canvas* sub = calloc(1, sizeof(Canvas)); + Canvas* sub = (Canvas*)malloc(sizeof(Canvas)); + memset(sub, 0, sizeof(Canvas)); if (!sub) return NULL; @@ -329,7 +331,7 @@ Canvas* Canvas_Substitute(Canvas* canvas, RECT *rc) sub->height = subHeight; sub->buffer_size = (size_t)subWidth * (size_t)subHeight * 4; - sub->buffer = calloc(1, sub->buffer_size); + sub->buffer = (uint8_t*)malloc(sub->buffer_size); size_t origStart = ((size_t)rc->top * (size_t)canvas->width + (size_t)rc->left) * 4; size_t originStride = (size_t)canvas->width * 4; @@ -349,7 +351,7 @@ Canvas* Canvas_Substitute(Canvas* canvas, RECT *rc) Canvas* Canvas_Create(int width, int height) { - Canvas *canvas = calloc(1, sizeof(Canvas)); + Canvas *canvas = (Canvas*)malloc(sizeof(Canvas)); if (!canvas) return NULL; diff --git a/src/color_context.c b/src/color_context.c index a3c5db9..4afa070 100644 --- a/src/color_context.c +++ b/src/color_context.c @@ -18,10 +18,12 @@ typedef struct tlist$$##T { \ \ tlist$$##T_t* tlist$$##T_new(int (*cmp)(T, T)) \ { \ - tlist$$##T_t* l = calloc(1, sizeof(tlist$$##T_t)); \ + tlist$$##T_t* l = malloc(sizeof(tlist$$##T_t)); \ + memset(l, 0, sizeof(tlist$$##T_t)); \ l->capacity = 0; \ l->length = 0; \ - l->data = calloc(16, sizeof(T)); \ + l->data = malloc(16 * sizeof(T)); \ + memset(l->data, 0, 16 * sizeof(T)); \ l->cmp = cmp; \ \ return l; \ diff --git a/src/controllibrary.c b/src/controllibrary.c index 72bfa49..02d071b 100644 --- a/src/controllibrary.c +++ b/src/controllibrary.c @@ -21,7 +21,8 @@ LRESULT CALLBACK ControlLibrary_UserProc(ControlLibrary*, HWND hWnd, UINT, WPARA ControlLibrary* ControlLibrary_Create(struct Application* app) { - ControlLibrary* window = calloc(1, sizeof(ControlLibrary)); + ControlLibrary* window = (ControlLibrary*)malloc(sizeof(ControlLibrary)); + memset(window, 0, sizeof(ControlLibrary)); if (window) { diff --git a/src/crefptr.c b/src/crefptr.c index 71bc8ce..2973f8b 100644 --- a/src/crefptr.c +++ b/src/crefptr.c @@ -31,6 +31,7 @@ void crefptr_deref(crefptr_t* ptr) crefptr_t* crefptr_new(void* ptr, void (*dtor)(void* ptr)) { crefptr_t* s = malloc(sizeof(crefptr_t)); + memset(s, 0, sizeof(crefptr_t)); s->refCount = 1; s->data = ptr; s->dtor = dtor; diff --git a/src/dockhost.c b/src/dockhost.c index 2a98df4..13090c2 100644 --- a/src/dockhost.c +++ b/src/dockhost.c @@ -9,13 +9,14 @@ #include #include -#include "stack.h" -#include "tree.h" +#include "util/stack.h" +#include "util/tree.h" #include "dockhost.h" #include "panitent.h" #include "resource.h" #include "floatingwindowcontainer.h" #include "dockinspectordialog.h" +#include "toolwndframe.h" static const WCHAR szClassName[] = L"__DockHostWindow"; @@ -54,20 +55,8 @@ BOOL DockData_GetClientRect(DockData* pDockData, RECT* rc) return TRUE; } -DockData* DockData_Create(); void DockData_Init(DockData* pDockData); -DockData* DockData_Create() -{ - DockData* pDockData = (DockData*)calloc(1, sizeof(DockData)); - if (pDockData) - { - DockData_Init(pDockData); - } - - return pDockData; -} - void DockData_Init(DockData* pDockData) { pDockData->iGripPos = 64; @@ -230,24 +219,20 @@ void DockNode_Paint(TreeNode* pNodeParent, HDC hdc, HBRUSH hCaptionBrush) /* ================================================================================ */ - /* Draw close button */ - HDC hdcCloseBtn = CreateCompatibleDC(hdc); - HBITMAP hbmCloseBtn = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_CLOSEBTN)); - HBITMAP hOldBm = SelectObject(hdcCloseBtn, hbmCloseBtn); - /* + Draw close button + Calculate right-align of title bar buttons sex rc->left is the offset of current dock area [ RectWidth ]* *[ <- ] Button width */ - - BitBlt(hdc, rc->left + Win32_Rect_GetWidth(rc) - WINDOWBUTTONSIZE - iBorderWidth, rc->top + iBorderWidth, WINDOWBUTTONSIZE, WINDOWBUTTONSIZE, hdcCloseBtn, 0, 0, SRCCOPY); - SelectObject(hdcCloseBtn, hOldBm); - DeleteObject(hbmCloseBtn); - DeleteDC(hdcCloseBtn); + CaptionButton button = { 0 }; + button.glyph = GLYPH_CLOSE; + button.size = (SIZE){ WINDOWBUTTONSIZE, WINDOWBUTTONSIZE }; + DrawCaptionButton(&button, hdc, rc->left + Win32_Rect_GetWidth(rc) - WINDOWBUTTONSIZE - iBorderWidth, rc->top + iBorderWidth); /* ================================================================================ */ @@ -388,8 +373,6 @@ void DockHostWindow_Rearrange(DockHostWindow* pDockHostWindow) DockInspectorDialog_Update(pDockHostWindow->m_pDockInspectorDialog, pDockHostWindow->pRoot_); } -WCHAR szSampleText[] = L"SampleText"; - TreeNode* DockNode_FindParent(TreeNode* root, TreeNode* node) { if (!root || !node) @@ -533,6 +516,8 @@ void DockHostWindow_OnDestroy(DockHostWindow* pDockHostWindow) DeleteObject(pDockHostWindow->hCaptionBrush_); } +#define DOCKHOSTBGMARGIN 16 + void DockHostWindow_OnPaint(DockHostWindow* pDockHostWindow) { HWND hWnd = Window_GetHWND((Window*)pDockHostWindow); @@ -543,6 +528,36 @@ void DockHostWindow_OnPaint(DockHostWindow* pDockHostWindow) if (pDockHostWindow->pRoot_) { DockNode_Paint(pDockHostWindow->pRoot_, hdc, pDockHostWindow->hCaptionBrush_); } + else { + HDC hdcLogo = CreateCompatibleDC(hdc); + HBITMAP hBitmap = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_DOCKHOSTBG)); + + BITMAP bm; + GetObject(hBitmap, sizeof(BITMAP), &bm); + int width = bm.bmWidth; + int height = bm.bmHeight; + + HBITMAP hOldBitmap = SelectObject(hdcLogo, hBitmap); + + BLENDFUNCTION blendFunc; + blendFunc.BlendOp = AC_SRC_OVER; + blendFunc.BlendFlags = 0; + blendFunc.SourceConstantAlpha = 0xFF; + blendFunc.AlphaFormat = AC_SRC_ALPHA; + + RECT rcClient; + Window_GetClientRect(pDockHostWindow, &rcClient); + + int clientWidth = rcClient.right - rcClient.left; + int clientHeight = rcClient.bottom - rcClient.top; + + int x = clientWidth - width - DOCKHOSTBGMARGIN; + int y = clientHeight - height - DOCKHOSTBGMARGIN; + AlphaBlend(hdc, x, y, width, height, hdcLogo, 0, 0, width, height, blendFunc); + + SelectObject(hdcLogo, hOldBitmap); + DeleteDC(hdcLogo); + } EndPaint(hWnd, &ps); } @@ -777,7 +792,8 @@ void DockHostWindow_Init(DockHostWindow* pDockHostWindow, struct Application* ap DockHostWindow* DockHostWindow_Create(struct Application* app) { - DockHostWindow* pDockHostWindow = (DockHostWindow*)calloc(1, sizeof(DockHostWindow)); + DockHostWindow* pDockHostWindow = (DockHostWindow*)malloc(sizeof(DockHostWindow)); + memset(pDockHostWindow, 0, sizeof(DockHostWindow)); if (pDockHostWindow) { @@ -815,3 +831,31 @@ void DockData_PinWindow(DockHostWindow* pDockHostWindow, DockData* pDockData, Wi pDockData->bShowCaption = TRUE; pDockData->hWnd = hWnd; } + +DockData* DockData_Create(int iGripPos, DWORD dwStyle, BOOL bShowCaption) +{ + DockData* pDockData = (DockData*)malloc(sizeof(DockData)); + + if (pDockData) + { + memset(pDockData, 0, sizeof(DockData)); + + pDockData->dwStyle = dwStyle; + pDockData->iGripPos = iGripPos; + pDockData->bShowCaption = bShowCaption; + + return pDockData; + } + + return NULL; +} + +TreeNode* DockNode_Create(int iGripPos, DWORD dwStyle, BOOL bShowCaption) +{ + TreeNode* pTreeNode = BinaryTree_AllocEmptyNode(); + DockData* pDockData = DockData_Create(iGripPos, dwStyle, bShowCaption); + + pTreeNode->data = pDockData; + + return pTreeNode; +} diff --git a/src/dockhost.h b/src/dockhost.h index 565d8e7..2c54191 100644 --- a/src/dockhost.h +++ b/src/dockhost.h @@ -2,7 +2,7 @@ #include "precomp.h" -#include "tree.h" +#include "util/tree.h" #define DGA_START 0 #define DGA_END 1 @@ -44,3 +44,5 @@ extern TreeNode* g_pRoot; DockHostWindow* DockHostWindow_Create(struct Application* app); void DockData_PinWindow(DockHostWindow* pDockHostWindow, DockData* pDockData, Window* window); +DockData* DockData_Create(int iGripPos, DWORD dwStyle, BOOL bShowCaption); +TreeNode* DockNode_Create(int iGripPos, DWORD dwStyle, BOOL bShowCaption); diff --git a/src/dockinspectordialog.c b/src/dockinspectordialog.c index 2d8e4cc..5ce9266 100644 --- a/src/dockinspectordialog.c +++ b/src/dockinspectordialog.c @@ -1,13 +1,13 @@ #include "precomp.h" -#include "dockinspectordialog.h" +#include "DockInspectorDialog.h" #include "panitent.h" #include "resource.h" #include "win32/util.h" #include "dockhost.h" -#include "tree.h" -#include "queue.h" +#include "util/tree.h" +#include "util/queue.h" DockInspectorDialog* DockInspectorDialog_Create(); void DockInspectorDialog_Init(DockInspectorDialog* pDockInspectorDialog, Application *pApp); @@ -16,13 +16,15 @@ INT_PTR DockInspectorDialog_DlgUserProc(DockInspectorDialog* pDockInspectorDialo void DockInspectorDialog_OnInitDialog(DockInspectorDialog* pDockInspectorDialog); void DockInspectorDialog_OnOK(DockInspectorDialog* pDockInspectorDialog); void DockInspectorDialog_OnCancel(DockInspectorDialog* pDockInspectorDialog); +void DockInspectorDialog_OnNCPaint(DockInspectorDialog* pDockInspectorDialog, HRGN hRegion); DockInspectorDialog* DockInspectorDialog_Create() { - DockInspectorDialog* pDockInspectorDialog = (DockInspectorDialog*)calloc(1, sizeof(DockInspectorDialog)); - + DockInspectorDialog* pDockInspectorDialog = (DockInspectorDialog*)malloc(sizeof(DockInspectorDialog)); + if (pDockInspectorDialog) { + memset(pDockInspectorDialog, 0, sizeof(DockInspectorDialog)); DockInspectorDialog_Init(pDockInspectorDialog, (Application*)Panitent_GetApp()); } @@ -56,7 +58,8 @@ void DockInspectorDialog_Update(DockInspectorDialog* pDockInspectorDialog, TreeN Queue* pQueue = CreateQueue(); - TreeViewNodePair* pPair = (TreeViewNodePair*)calloc(1, sizeof(TreeViewNodePair)); + TreeViewNodePair* pPair = (TreeViewNodePair*)malloc(sizeof(TreeViewNodePair)); + memset(pPair, 0, sizeof(TreeViewNodePair)); pPair->pTreeNode = pTreeRoot; pPair->hti = TVI_ROOT; Queue_Enqueue(pQueue, pPair); @@ -86,7 +89,8 @@ void DockInspectorDialog_Update(DockInspectorDialog* pDockInspectorDialog, TreeN if (pCurrentNode->node1) { - TreeViewNodePair* pPair = (TreeViewNodePair*)calloc(1, sizeof(TreeViewNodePair)); + TreeViewNodePair* pPair = (TreeViewNodePair*)malloc(sizeof(TreeViewNodePair)); + memset(pPair, 0, sizeof(TreeViewNodePair)); pPair->pTreeNode = pCurrentNode->node1; pPair->hti = hItem; Queue_Enqueue(pQueue, pPair); @@ -94,7 +98,8 @@ void DockInspectorDialog_Update(DockInspectorDialog* pDockInspectorDialog, TreeN if (pCurrentNode->node2) { - TreeViewNodePair* pPair = (TreeViewNodePair*)calloc(1, sizeof(TreeViewNodePair)); + TreeViewNodePair* pPair = (TreeViewNodePair*)malloc(sizeof(TreeViewNodePair)); + memset(pPair, 0, sizeof(TreeViewNodePair)); pPair->pTreeNode = pCurrentNode->node2; pPair->hti = hItem; Queue_Enqueue(pQueue, pPair); @@ -123,10 +128,38 @@ void DockInspectorDialog_OnCancel(DockInspectorDialog* pDockInspectorDialog) EndDialog(Window_GetHWND((Window*)pDockInspectorDialog), 0); } +void DockInspectorDialog_OnNCPaint(DockInspectorDialog* pDockInspectorDialog, HRGN hRegion) +{ + HWND hWnd = Window_GetHWND((Window*)pDockInspectorDialog); + HDC hdc = GetWindowDC(hWnd); + + DWORD dwStyle = GetWindowStyle(hWnd); + + RECT rc = { 0 }; + GetWindowRect(hWnd, &rc); + rc.right -= rc.left; + rc.bottom -= rc.top; + rc.top = rc.left = 0; + + SetDCBrushColor(hdc, Win32_HexToCOLORREF(L"#9185be")); + SetDCPenColor(hdc, Win32_HexToCOLORREF(L"#6d648e")); + SelectObject(hdc, GetStockObject(DC_BRUSH)); + SelectObject(hdc, GetStockObject(DC_PEN)); + Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom); +} + INT_PTR DockInspectorDialog_DlgUserProc(DockInspectorDialog* pDockInspectorDialog, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { + case WM_NCPAINT: + DockInspectorDialog_OnNCPaint(pDockInspectorDialog, (HRGN)wParam); + return 0; + break; + + case WM_NCHITTEST: + return 0; + break; } return Dialog_DefaultDialogProc(pDockInspectorDialog, message, wParam, lParam); diff --git a/src/document.c b/src/document.c index 6fc06bf..b959087 100644 --- a/src/document.c +++ b/src/document.c @@ -30,7 +30,8 @@ void Document_Init(Document* pDocument) Document* Document_Create() { - Document* pDocument = calloc(1, sizeof(Document)); + Document* pDocument = (Document*)malloc(sizeof(Document)); + memset(pDocument, 0, sizeof(Document)); Document_Init(pDocument); @@ -70,14 +71,16 @@ void Document_OpenFile(LPWSTR pszPath) return; } - pDocument->history = calloc(1, sizeof(History)); + pDocument->history = (History*)malloc(sizeof(History)); + memset(pDocument->history, 0, sizeof(History)); if (!pDocument->history) { Document_Destroy(pDocument); return; } - HistoryRecord* initialRecord = calloc(1, sizeof(HistoryRecord)); + HistoryRecord* initialRecord = (HistoryRecord*)malloc(sizeof(HistoryRecord)); + memset(initialRecord, 0, sizeof(HistoryRecord)); pDocument->history->peak = initialRecord; /* Create and set canvas */ @@ -187,14 +190,16 @@ Document* Document_New(int width, int height) return NULL; } - pDocument->history = calloc(1, sizeof(History)); + pDocument->history = (History*)malloc(sizeof(History)); + memset(pDocument->history, 0, sizeof(History)); if (!pDocument->history) { Document_Destroy(pDocument); return NULL; } - HistoryRecord* initialRecord = calloc(1, sizeof(HistoryRecord)); + HistoryRecord* initialRecord = (HistoryRecord*)malloc(sizeof(HistoryRecord)); + memset(initialRecord, 0, sizeof(HistoryRecord)); pDocument->history->peak = initialRecord; Canvas* pCanvas = Canvas_Create(width, height); diff --git a/src/experimental/bubble.c b/src/experimental/bubble.c new file mode 100644 index 0000000..1e5f700 --- /dev/null +++ b/src/experimental/bubble.c @@ -0,0 +1,126 @@ +#include "../precomp.h" + +#include "bubble.h" +#include "../resource.h" + +#include "../win32/util.h" + +HWND g_hWndBubble; +POINT g_localCursorPos; +RECT g_rcWindow; +RECT g_rcClient; +RECT g_rcTemp; + +static const WCHAR szClassName[] = L"__BubbleClass"; + +LRESULT CALLBACK Bubble_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_CREATE: + break; + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hWnd, &ps); + + POINT ptCursor; + GetCursorPos(&ptCursor); + + WCHAR szString[1024] = L""; + swprintf_s(szString, 1024, + L"MouseX: = %d\n" + L"MouseY: = %d\n\n" + + L"Local MouseX: = %d\n" + L"Local MouseY: = %d\n\n" + + L"rcWindow.left = %d\n" + L"rcWindow.top = %d\n" + L"rcWindow.right = %d\n" + L"rcWindow.bottom = %d\n\n" + + L"rcClient.left = %d\n" + L"rcClient.top = %d\n" + L"rcClient.right = %d\n" + L"rcClient.bottom = %d\n\n" + + + L"rcTemp.left = %d\n" + L"rcTemp.top = %d\n" + L"rcTemp.right = %d\n" + L"rcTemp.bottom = %d\n", + ptCursor.x, ptCursor.y, + g_localCursorPos.x, g_localCursorPos.y, + g_rcWindow.left, g_rcWindow.top, g_rcWindow.right, g_rcWindow.bottom, + g_rcClient.left, g_rcClient.top, g_rcClient.right, g_rcClient.bottom, + g_rcTemp.left, g_rcTemp.top, g_rcTemp.right, g_rcTemp.bottom); + + RECT rcClient; + GetClientRect(hWnd, &rcClient); + + HBRUSH hBrush = CreateSolidBrush(RGB(0xFF, 0xFF, 0xCC)); + FillRect(hdc, &rcClient, hBrush); + DeleteObject(hBrush); + + SetBkMode(hdc, TRANSPARENT); + + HFONT hFont; + { + NONCLIENTMETRICS ncm = { 0 }; + ncm.cbSize = sizeof(NONCLIENTMETRICS); + SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0); + + wcscpy_s(ncm.lfCaptionFont.lfFaceName, sizeof(ncm.lfCaptionFont.lfFaceName) / sizeof(*ncm.lfCaptionFont.lfFaceName), L"Courier New"); + + hFont = CreateFontIndirect(&ncm.lfCaptionFont); + } + + HFONT hOldFont = SelectObject(hdc, hFont); + + rcClient.left += 4; + rcClient.top += 4; + rcClient.right -= 4; + rcClient.bottom -= 4; + DrawTextEx(hdc, szString, wcslen(szString), &rcClient, 0, NULL); + + SelectObject(hdc, hOldFont); + DeleteObject(hFont); + + EndPaint(hWnd, &ps); + } + break; + + case WM_DESTROY: + break; + } + + return DefWindowProc(hWnd, message, wParam, lParam); +} + +BOOL Bubble_RegisterClass(HINSTANCE hInstance) +{ + WNDCLASSEX wcex = {0}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = (WNDPROC)Bubble_WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON)); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wcex.lpszClassName = szClassName; + wcex.lpszMenuName = NULL; + wcex.hIcon = NULL; + + return RegisterClassEx(&wcex); +} + +HWND Bubble_CreateWindow(HINSTANCE hInstance) +{ + g_hWndBubble = CreateWindowEx(WS_EX_TOPMOST, szClassName, L"Bubble", WS_POPUP | WS_VISIBLE | WS_BORDER, 100, 100, 200, 400, NULL, NULL, hInstance, NULL); + + return g_hWndBubble; +} diff --git a/src/experimental/bubble.h b/src/experimental/bubble.h new file mode 100644 index 0000000..130ed04 --- /dev/null +++ b/src/experimental/bubble.h @@ -0,0 +1,11 @@ +#pragma once + +extern HWND g_hWndBubble; +extern POINT g_localCursorPos; + +extern RECT g_rcWindow; +extern RECT g_rcClient; +extern RECT g_rcTemp; + +BOOL Bubble_RegisterClass(HINSTANCE hInstance); +HWND Bubble_CreateWindow(HINSTANCE hInstance); diff --git a/src/experimental/captionglyphs.c b/src/experimental/captionglyphs.c new file mode 100644 index 0000000..e69de29 diff --git a/src/experimental/captionglyphs.h b/src/experimental/captionglyphs.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/src/experimental/captionglyphs.h @@ -0,0 +1 @@ +#pragma once diff --git a/src/experimental/dockhostcomposite.c b/src/experimental/dockhostcomposite.c new file mode 100644 index 0000000..a5455d6 --- /dev/null +++ b/src/experimental/dockhostcomposite.c @@ -0,0 +1,252 @@ +#include "../precomp.h" + +#include "dockhostcomposite.h" +#include "../util/list.h" +#include "../util/assert.h" +#include "../win32/util.h" + +/* + * [PRIVATE] + */ + +/* + * [PUBLIC] + */ +typedef struct DockHostComposite DockHostComposite; +struct DockHostComposite { + Window* pWndHost; + List* pDockSiteLeft; + List* pDockSiteRight; +}; + +void DockHostComposite_Init(DockHostComposite* pDockHostComposite); + +DockHostComposite* DockHostComposite_Create() +{ + DockHostComposite* pDockHostComposite = (DockHostComposite*)malloc(sizeof(DockHostComposite)); + + if (pDockHostComposite) + { + DockHostComposite_Init(pDockHostComposite); + } + + return pDockHostComposite; +} + +void DockHostComposite_Init(DockHostComposite* pDockHostComposite) +{ + memset(pDockHostComposite, 0, sizeof(DockHostComposite)); + + pDockHostComposite->pDockSiteLeft = List_Create(sizeof(DockWindowInfo)); + pDockHostComposite->pDockSiteRight = List_Create(sizeof(DockWindowInfo)); +} + +void DockHostComposite_SetWindow(DockHostComposite* pDockHostComposite, Window* pWindow) +{ + pDockHostComposite->pWndHost = pWindow; +} + +Window* DockHostComposite_GetWindow(DockHostComposite* pDockHostComposite) +{ + return pDockHostComposite->pWndHost; +} + +void DockHostComposite_Dock(DockHostComposite* pDockHostComposite, DockWindowInfo* pWindowInfo, int nSide) +{ + List* pList = NULL; + + switch (nSide) + { + case 1: + pList = pDockHostComposite->pDockSiteLeft; + break; + case 3: + pList = pDockHostComposite->pDockSiteRight; + break; + } + + if (pList) + { + List_InsertBack(pList, pWindowInfo); + } +} + +LRESULT DockHostComposite_WndProc(DockHostComposite* pDockHostComposite, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, PBOOL pbProcessed) +{ + switch (message) + { + case WM_CREATE: + { + return TRUE; + } + break; + + case WM_SIZE: + { + RECT rcClient; + GetClientRect(hWnd, &rcClient); + rcClient.top += 48; + + // InflateRect(&rcClient, -50, -50); + + { + int listLength = List_GetLength(pDockHostComposite->pDockSiteLeft); + if (listLength) + { + int dockHeight = (RECTHEIGHT(&rcClient) - 24) / listLength; + for (size_t i = 0; i < listLength; ++i) + { + DockWindowInfo* pdwi = List_Get(pDockHostComposite->pDockSiteLeft, i); + SetWindowPos(pdwi->hWnd, NULL, rcClient.left + 24, rcClient.top + dockHeight * i, 200, dockHeight, 0); + } + } + } + + { + int listLength = List_GetLength(pDockHostComposite->pDockSiteRight); + if (listLength) + { + int dockHeight = (RECTHEIGHT(&rcClient) - 24) / listLength; + for (size_t i = 0; i < listLength; ++i) + { + DockWindowInfo* pdwi = List_Get(pDockHostComposite->pDockSiteRight, i); + SetWindowPos(pdwi->hWnd, NULL, rcClient.right - 200 - 24, rcClient.top + dockHeight * i, 200, dockHeight, 0); + } + } + } + *pbProcessed = TRUE; + return 0; + } + break; + + case WM_LBUTTONDOWN: + + break; + + case WM_ERASEBKGND: + *pbProcessed = TRUE; + return TRUE; + break; + } + + *pbProcessed = FALSE; + return 0; +} + +void DockHostComposite_Draw(DockHostComposite* pDockHostComposite, HDC hdc) +{ + HWND hWnd = Window_GetHWND(pDockHostComposite->pWndHost); + + RECT rcClient; + GetClientRect(hWnd, &rcClient); + + /* Adjust client area border */ + rcClient.top += 48; + // InflateRect(&rcClient, -50, -50); + + /* Background */ + HBRUSH hBackgroundBrush = CreateSolidBrush(Win32_HexToCOLORREF(L"#48425f")); + FillRect(hdc, &rcClient, hBackgroundBrush); + DeleteObject(hBackgroundBrush); + + HRGN hRegion = CreateRectRgnIndirect(&rcClient); + ExtSelectClipRgn(hdc, hRegion, RGN_AND); + + /* Draw frame */ + SelectObject(hdc, GetStockObject(NULL_PEN)); + SelectObject(hdc, GetStockObject(DC_BRUSH)); + SetDCBrushColor(hdc, Win32_HexToCOLORREF(L"#6d648e")); + + /* Bottom site background */ + Rectangle(hdc, rcClient.left, rcClient.bottom - 24, rcClient.right, rcClient.bottom); + /* Left site background */ + Rectangle(hdc, rcClient.left, rcClient.top, rcClient.left + 24, rcClient.bottom); + /* Right site background */ + Rectangle(hdc, rcClient.right - 24, rcClient.top, rcClient.right, rcClient.bottom); + + /* Pins */ + SetDCPenColor(hdc, Win32_HexToCOLORREF(L"#48425f")); + SetDCBrushColor(hdc, Win32_HexToCOLORREF(L"#9185be")); + SelectObject(hdc, GetStockObject(DC_PEN)); + + int pinHeight = 100; + + HFONT hFont = GetCurrentObject(hdc, OBJ_FONT); + LOGFONT lf; + GetObject(hFont, sizeof(lf), &lf); + wcscpy_s(lf.lfFaceName, sizeof(lf.lfFaceName) / sizeof(lf.lfFaceName[0]), L"Montserrat"); + lf.lfEscapement = 2700; + // lf.lfOrientation = 2700; + HFONT hNewFont = CreateFontIndirect(&lf); + + HFONT hOldFont = SelectObject(hdc, hNewFont); + + { + BeginPath(hdc); + MoveToEx(hdc, rcClient.left + 24 - 1, rcClient.top, NULL); + LineTo(hdc, rcClient.left + 24 - 1, rcClient.bottom - 24); + EndPath(hdc); + StrokePath(hdc); + + int listLength = List_GetLength(pDockHostComposite->pDockSiteLeft); + for (size_t i = 0; i < listLength; ++i) + { + BeginPath(hdc); + MoveToEx(hdc, rcClient.left + 24, rcClient.top + 2 + pinHeight * i, NULL); + LineTo(hdc, rcClient.left + 4, rcClient.top + 2 + pinHeight * i); + LineTo(hdc, rcClient.left + 4, rcClient.top + pinHeight - 2 + pinHeight * i); + LineTo(hdc, rcClient.left + 24, rcClient.top + pinHeight - 2 + pinHeight * i); + EndPath(hdc); + StrokeAndFillPath(hdc); + + DockWindowInfo* pdwi = List_Get(pDockHostComposite->pDockSiteLeft, i); + if (pdwi) + { + int len = GetWindowTextLength(pdwi->hWnd); + PWSTR szText = (PWSTR)malloc((len + 1) * sizeof(WCHAR)); + GetWindowText(pdwi->hWnd, szText, len + 1); + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, RGB(0xFF, 0xFF, 0xFF)); + TextOut(hdc, rcClient.left + 24, rcClient.top + 8 + pinHeight * i, szText, len); + free(szText); + } + } + } + + { + BeginPath(hdc); + MoveToEx(hdc, rcClient.right - 24, rcClient.top, NULL); + LineTo(hdc, rcClient.right - 24, rcClient.bottom - 24); + EndPath(hdc); + StrokePath(hdc); + + int listLength = List_GetLength(pDockHostComposite->pDockSiteRight); + for (size_t i = 0; i < listLength; ++i) + { + BeginPath(hdc); + MoveToEx(hdc, rcClient.right - 24 - 1, rcClient.top + 2 + pinHeight * i, NULL); + LineTo(hdc, rcClient.right - 24 + 20 - 1, rcClient.top + 2 + pinHeight * i); + LineTo(hdc, rcClient.right - 24 + 20 - 1, rcClient.top + pinHeight - 2 + pinHeight * i); + LineTo(hdc, rcClient.right - 24 - 1, rcClient.top + pinHeight - 2 + pinHeight * i); + EndPath(hdc); + StrokeAndFillPath(hdc); + + DockWindowInfo* pdwi = List_Get(pDockHostComposite->pDockSiteRight, i); + if (pdwi) + { + int len = GetWindowTextLength(pdwi->hWnd); + PWSTR szText = (PWSTR)malloc((len + 1) * sizeof(WCHAR)); + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, RGB(0xFF, 0xFF, 0xFF)); + GetWindowText(pdwi->hWnd, szText, len + 1); + TextOut(hdc, rcClient.right - 1 - 4, rcClient.top + 8 + pinHeight * i, szText, len); + free(szText); + } + } + } + + if (hRegion) + { + DeleteObject(hRegion); + } +} diff --git a/src/experimental/dockhostcomposite.h b/src/experimental/dockhostcomposite.h new file mode 100644 index 0000000..d058c9a --- /dev/null +++ b/src/experimental/dockhostcomposite.h @@ -0,0 +1,17 @@ +#pragma once + +typedef struct Window Window; +typedef struct DockHostComposite DockHostComposite; + +typedef struct DockWindowInfo DockWindowInfo; +struct DockWindowInfo { + HWND hWnd; +}; + +DockHostComposite* DockHostComposite_Create(); +void DockHostComposite_Init(DockHostComposite* pDockHostComposite); +void DockHostComposite_SetWindow(DockHostComposite* pDockHostComposite, Window* pWindow); +Window* DockHostComposite_GetWindow(DockHostComposite* pDockHostComposite); +LRESULT DockHostComposite_WndProc(DockHostComposite* pDockHostComposite, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, PBOOL pbProcessed); +void DockHostComposite_Dock(DockHostComposite* pDockHostComposite, DockWindowInfo* pWindowInfo, int nSide); +void DockHostComposite_Draw(DockHostComposite* pDockHostComposite, HDC hdc); \ No newline at end of file diff --git a/src/experimental/docklib.h b/src/experimental/docklib.h new file mode 100644 index 0000000..7fc3d39 --- /dev/null +++ b/src/experimental/docklib.h @@ -0,0 +1,5 @@ +#pragma once + +#include "dockwindow.h" +#include "dockpanel.h" +#include "bubble.h" diff --git a/src/experimental/dockpanel.c b/src/experimental/dockpanel.c new file mode 100644 index 0000000..e65ca10 --- /dev/null +++ b/src/experimental/dockpanel.c @@ -0,0 +1,871 @@ +#include "../precomp.h" + +#include "DockPanel.h" + +#include "../resource.h" +#include +#include "../win32/util.h" +#include "../util/assert.h" +#include "../toolwndframe.h" + +#include "../util/list.h" + +#include "bubble.h" + +static const WCHAR szClassName[] = L"Experimental::DockPanel"; + +#define CAPTIONHEIGHT 24 +#define BORDERSIZE 32 + +#define DCX_USESTYLE 0x10000 + + +#define GLYPH_SIZE 8 + +enum { + CB_NORMAL, + CB_HOVER, + CB_DOWN, + CB_DISABLED +}; + +typedef struct SysConfig SysConfig; +struct SysConfig { + int smCXPaddedBorder; + int smCXSizeFrame; + int smCYSizeFrame; + int smCXEdge; + int smCYEdge; + int smCXSmIcon; + int smCYSmIcon; + int smCYCaption; +}; + +SysConfig g_sysConfig = { 0 }; + +void UpdateSysConfig() +{ + g_sysConfig.smCXPaddedBorder = GetSystemMetrics(SM_CXPADDEDBORDER); + g_sysConfig.smCXSizeFrame = GetSystemMetrics(SM_CXSIZEFRAME); + g_sysConfig.smCYSizeFrame = GetSystemMetrics(SM_CYSIZEFRAME); + g_sysConfig.smCXEdge = GetSystemMetrics(SM_CXEDGE); + g_sysConfig.smCYEdge = GetSystemMetrics(SM_CYEDGE); + g_sysConfig.smCXSmIcon = GetSystemMetrics(SM_CXSMICON); + g_sysConfig.smCYSmIcon = GetSystemMetrics(SM_CYSMICON); + g_sysConfig.smCYCaption = GetSystemMetrics(SM_CYCAPTION); +} + +typedef struct CaptionGlyphs CaptionGlyphs; +struct CaptionGlyphs { + HDC hdc; + HBITMAP hBitmap; + SIZE size; + HBITMAP hPrevBitmap; +}; + +typedef struct CaptionButtonInfo CaptionButtonInfo; +struct CaptionButtonInfo { + RECT rc; + int iGlyph; + int iState; + int ht; +}; + +const CaptionGlyphs* GetCaptionGlyphs() +{ + static CaptionGlyphs s_glyphs = { 0 }; + + if (!s_glyphs.hBitmap) + { + s_glyphs.hBitmap = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_FLOATINGGLYPHS)); + + HDC hdcScreen = GetDC(NULL); + s_glyphs.hdc = CreateCompatibleDC(hdcScreen); + ReleaseDC(NULL, hdcScreen); + s_glyphs.hPrevBitmap = SelectObject(s_glyphs.hdc, s_glyphs.hBitmap); + + BITMAP bm; + GetObject(s_glyphs.hBitmap, sizeof(bm), &bm); + s_glyphs.size.cx = bm.bmWidth; + s_glyphs.size.cy = bm.bmHeight; + } + + return &s_glyphs; +} + +void DestroyCaptionGlyphs() +{ + CaptionGlyphs* pCaptionGlyphs = GetCaptionGlyphs(); + + SelectObject(pCaptionGlyphs->hdc, pCaptionGlyphs->hPrevBitmap); + DeleteObject(pCaptionGlyphs->hBitmap); + DeleteDC(pCaptionGlyphs->hdc); + + memset(pCaptionGlyphs, 0, sizeof(*pCaptionGlyphs)); +} + +void DrawCaptionGlyph2(HDC hdc, int x, int y, int iGlyph) +{ + CaptionGlyphs* pGlyphs = GetCaptionGlyphs(); + TransparentBlt(hdc, + x, y, + pGlyphs->size.cy, pGlyphs->size.cy, + pGlyphs->hdc, iGlyph * pGlyphs->size.cy, 0, + pGlyphs->size.cy, pGlyphs->size.cy, COLORREF_MAGENTA); +} + +void DrawCaptionButton2(CaptionButton* pCaptionButton, HDC hdc, PRECT rcButton, int state) +{ + if (state == CB_HOVER || state == CB_DOWN) + { + SelectObject(hdc, GetStockObject(NULL_PEN)); + SelectObject(hdc, GetStockObject(DC_BRUSH)); + + COLORREF color = Win32_HexToCOLORREF(state == CB_HOVER ? L"ada4ce" : L"c8c2df"); + SetDCBrushColor(hdc, color); + Rectangle(hdc, rcButton->left, rcButton->top, rcButton->right, rcButton->bottom); + } + + const CaptionGlyphs* pCaptionGlyphs = GetCaptionGlyphs(); + DrawCaptionGlyph2(hdc, + rcButton->left + (rcButton->right - rcButton->left - pCaptionGlyphs->size.cy) / 2, + rcButton->top + (rcButton->bottom - rcButton->top - pCaptionGlyphs->size.cy) / 2, + pCaptionButton->glyph); +} + +void DrawCaptionButton3(HDC hdc, CaptionButtonInfo* pButton) +{ + PRECT prc = &pButton->rc; + + if (pButton->iState == CB_HOVER || pButton->iState == CB_DOWN) + { + SelectObject(hdc, GetStockObject(NULL_PEN)); + SelectObject(hdc, GetStockObject(DC_BRUSH)); + + COLORREF color = Win32_HexToCOLORREF(pButton->iState == CB_HOVER ? L"ada4ce" : L"c8c2df"); + SetDCBrushColor(hdc, color); + Rectangle(hdc, prc->left, prc->top, prc->right, prc->bottom); + } + + const CaptionGlyphs* pCaptionGlyphs = GetCaptionGlyphs(); + DrawCaptionGlyph2(hdc, + prc->left + (prc->right - prc->left - pCaptionGlyphs->size.cy) / 2, + prc->top + (prc->bottom - prc->top - pCaptionGlyphs->size.cy) / 2, + pButton->iGlyph); +} + +List* g_pList = NULL; + +void UpdateCaptionButtonPositions(List* pList, PRECT prcWindow, int btnWidth, int top, int bottom, DWORD dwStyle) +{ + int xOffset = 0; + /* Iterate over caption buttons list */ + for (size_t i = 0; i < List_GetLength(pList); ++i) + { + xOffset += btnWidth; + } + xOffset += (dwStyle & WS_MAXIMIZE) ? 10 : 4; + + for (size_t i = 0; i < List_GetLength(pList); ++i) + { + CaptionButtonInfo* pButton = List_Get(pList, i); + + pButton->rc.left = prcWindow->right - xOffset; + xOffset -= btnWidth; + pButton->rc.top = top; + pButton->rc.right = prcWindow->right - xOffset; + pButton->rc.bottom = bottom; + } +} + +void UpdateCaptionButtonRects(HWND hWnd) +{ + RECT rcWindow; + GetWindowRect(hWnd, &rcWindow); + MapWindowRect(HWND_DESKTOP, hWnd, &rcWindow); + /* Remove offsets to normalize window rect upper left corner to 0 */ + OffsetRect(&rcWindow, -rcWindow.left, -rcWindow.top); + + DWORD dwStyle = GetWindowStyle(hWnd); + + int btnWidth = 45; + int btnHeight = 30; + + DWORD dwExStyle = GetWindowExStyle(hWnd); + if (dwExStyle & WS_EX_TOOLWINDOW) + { + btnWidth = 17; + UpdateCaptionButtonPositions(g_pList, &rcWindow, btnWidth, 7, 24, dwStyle); + } + else { + if (dwStyle & WS_MAXIMIZE) + { + UpdateCaptionButtonPositions(g_pList, &rcWindow, btnWidth, 8, 31, dwStyle); + } + else { + UpdateCaptionButtonPositions(g_pList, &rcWindow, btnWidth, 1, 31, dwStyle); + } + } +} + +void OffsetRectClientToWindow(HWND hWnd, PRECT prc) +{ + RECT rcWindow; + GetWindowRect(hWnd, &rcWindow); + MapWindowRect(HWND_DESKTOP, hWnd, &rcWindow); + + OffsetRect(prc, rcWindow.left, rcWindow.top); +} + +BOOL g_fDrawLock; +BOOL g_fEnableStyle = TRUE; + +int GetCaptionButtonIdByPoint(List* pList, POINT pt) +{ + for (size_t i = 0; i < List_GetLength(g_pList); ++i) + { + CaptionButtonInfo* pButton = List_Get(pList, i); + + if (PtInRect(&pButton->rc, pt)) + { + return i; + } + } + + return -1; +} + +LRESULT StylingProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, PBOOL bProcessed) +{ + switch (message) + { + case WM_NCMOUSEMOVE: + { + RECT rcWindow; + GetWindowRect(hWnd, &rcWindow); + + /* Window-relative cursor position */ + POINT pt = { 0 }; + pt.x = GET_X_LPARAM(lParam) - rcWindow.left; + pt.y = GET_Y_LPARAM(lParam) - rcWindow.top; + + /* Transform window to it's client coordinates */ + MapWindowRect(HWND_DESKTOP, hWnd, &rcWindow); + + /* Remove offsets to normalize window rect upper left corner to 0 */ + OffsetRect(&rcWindow, -rcWindow.left, -rcWindow.top); + + /****************** + * Implementation * + ******************/ + + /* Update caption button rects */ + UpdateCaptionButtonRects(hWnd); + + /* Reset all to normal state */ + for (size_t i = 0; i < List_GetLength(g_pList); ++i) + { + CaptionButtonInfo* pButton = List_Get(g_pList, i); + pButton->iState = CB_NORMAL; + } + + /* Set hover state if cursor above the button */ + int btnId; + if ((btnId = GetCaptionButtonIdByPoint(g_pList, pt)) != -1) + { + CaptionButtonInfo* pButton = List_Get(g_pList, btnId); + + pButton->iState = CB_HOVER; + RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE); + *bProcessed = TRUE; + break; + } + } + break; + + case WM_NCLBUTTONUP: + + /* WM_NCLBUTTONUP does nothing with caption buttons by default + * Maybe it's because of internal checks that trying to + * ensure that released button is in specific system button rectangle. + * By the way we can't deal with system style caption button rectangles. + * + * So we handle it by ourselves by posting corresponding + * WM_SYSCOMMAND messages + */ + + switch (wParam) + { + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + { + DWORD dwStyle = GetWindowStyle(hWnd); + int nCommand = 0; + + switch (wParam) + { + case HTMINBUTTON: + /* Restore window if already minimized */ + nCommand = (dwStyle & WS_MINIMIZE) ? SC_RESTORE : SC_MINIMIZE; + break; + case HTMAXBUTTON: + /* Restore window if already maximized */ + nCommand = (dwStyle & WS_MAXIMIZE) ? SC_RESTORE : SC_MAXIMIZE; + break; + case HTCLOSE: + nCommand = SC_CLOSE; + break; + } + + RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE); + /* lParam is a cursor position, so pass it through */ + DefWindowProc(hWnd, WM_SYSCOMMAND, (WPARAM)nCommand, lParam); + + *bProcessed = TRUE; + return 0; + } + break; + } + + break; + + case WM_NCLBUTTONDOWN: + { + RECT rcWindow; + GetWindowRect(hWnd, &rcWindow); + + /* Window-relative cursor position */ + POINT pt = { 0 }; + pt.x = GET_X_LPARAM(lParam) - rcWindow.left; + pt.y = GET_Y_LPARAM(lParam) - rcWindow.top; + + /* Transform window to it's client coordinates */ + MapWindowRect(HWND_DESKTOP, hWnd, &rcWindow); + + /* Remove offsets to normalize window rect upper left corner to 0 */ + OffsetRect(&rcWindow, -rcWindow.left, -rcWindow.top); + + /****************** + * Implementation * + ******************/ + + int btnId; + if ((btnId = GetCaptionButtonIdByPoint(g_pList, pt)) != -1) + { + CaptionButtonInfo* pButton = List_Get(g_pList, btnId); + pButton->iState = CB_DOWN; + RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE); + *bProcessed = TRUE; + return 0; + } + } + break; + + case WM_NCHITTEST: + { + RECT rcWindow; + GetWindowRect(hWnd, &rcWindow); + + RECT rcClient; + GetClientRect(hWnd, &rcClient); + + /* Window-relative cursor position */ + POINT pt = { 0 }; + pt.x = GET_X_LPARAM(lParam) - rcWindow.left; + pt.y = GET_Y_LPARAM(lParam) - rcWindow.top; + + /* Transform window to it's client coordinates */ + MapWindowRect(HWND_DESKTOP, hWnd, &rcWindow); + + /* Translate client rect to window coordinates */ + OffsetRect(&rcClient, -rcWindow.left, -rcWindow.top); + + /* Remove offsets to normalize window rect upper left corner to 0 */ + OffsetRect(&rcWindow, -rcWindow.left, -rcWindow.top); + + /****************** + * Implementation * + ******************/ + + UpdateCaptionButtonRects(hWnd); + + int ht = HTNOWHERE; + + if (PtInRect(&rcClient, pt)) + { + ht = HTCLIENT; + } + + /* Iterate over caption button rects and check that cursor in these */ + for (size_t i = 0; i < List_GetLength(g_pList); ++i) + { + CaptionButtonInfo* pButton = List_Get(g_pList, i); + if (PtInRect(&pButton->rc, pt)) + { + ht = pButton->ht; + break; + } + } + + int smCYSizeFrame = g_sysConfig.smCYSizeFrame; + int smCXSizeFrame = g_sysConfig.smCXSizeFrame; + int smCXPaddedBorder = g_sysConfig.smCXPaddedBorder; + + + if (ht) + { + /* Empty check for that ht is already defined by caption button */ + } + + /* Window icon (system menu) */ + else if (PtIn(pt, + smCYSizeFrame + smCXPaddedBorder, + smCXSizeFrame + smCXPaddedBorder, + smCYSizeFrame + smCXPaddedBorder + g_sysConfig.smCXSmIcon, + smCXSizeFrame + smCXPaddedBorder + g_sysConfig.smCYSmIcon)) + { + ht = HTSYSMENU; + } + + /* Caption */ + else if (PtIn(pt, + smCYSizeFrame + smCXPaddedBorder, + smCXSizeFrame + smCXPaddedBorder, + rcWindow.right - (smCYSizeFrame + smCXPaddedBorder), + smCXSizeFrame + smCXPaddedBorder + g_sysConfig.smCYCaption)) + { + ht = HTCAPTION; + } + + /* Left sizing border */ + else if (PtIn(pt, + 0, + 12, + smCYSizeFrame + smCXPaddedBorder, + rcWindow.bottom - 12)) + { + ht = HTLEFT; + } + + /* Top sizing border */ + else if (PtIn(pt, + 12, + 0, + rcWindow.right - 12, + smCXSizeFrame + smCXPaddedBorder)) + { + ht = HTTOP; + } + + /* Right sizing border */ + else if (PtIn(pt, + rcWindow.right - (smCYSizeFrame + smCXPaddedBorder), + 12, + rcWindow.right, + rcWindow.bottom - 12)) + { + ht = HTRIGHT; + } + + /* Bottom sizing border */ + else if (PtIn(pt, + 12, + rcWindow.bottom - (smCXSizeFrame + smCXPaddedBorder), + rcWindow.right - 12, + rcWindow.bottom)) + { + ht = HTBOTTOM; + } + + /* Top-left sizing border */ + else if (PtIn(pt, + 0, + 0, + 12, + 12)) + { + ht = HTTOPLEFT; + } + + /* Top-right sizing border */ + else if (PtIn(pt, + rcWindow.right - 12, + 0, + rcWindow.right, + 12)) + { + ht = HTTOPRIGHT; + } + + /* Bottom-left sizing border */ + else if (PtIn(pt, + 0, + rcWindow.bottom - 12, + 12, + rcWindow.bottom)) + { + ht = HTBOTTOMLEFT; + } + + /* Bottom-right sizing border */ + else if (PtIn(pt, + rcWindow.right - 12, + rcWindow.bottom - 12, + rcWindow.right, + rcWindow.bottom)) + { + ht = HTBOTTOMRIGHT; + } + + if (ht) + { + *bProcessed = TRUE; + } + + return ht; + } + break; + + case WM_NCCREATE: + { + LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam; + + UpdateSysConfig(); + + g_pList = List_Create(sizeof(CaptionButtonInfo)); + + CaptionButtonInfo btn = {0}; + btn.iState = CB_NORMAL; + btn.ht = HTNOWHERE; + + btn.iGlyph = GLYPH_HELP; + List_InsertBack(g_pList, &btn); + + btn.iGlyph = GLYPH_MORE; + List_InsertBack(g_pList, &btn); + + btn.iGlyph = GLYPH_PIN; + List_InsertBack(g_pList, &btn); + + btn.iGlyph = GLYPH_MINIMIZE; + btn.ht = HTMINBUTTON; + List_InsertBack(g_pList, &btn); + + btn.iGlyph = GLYPH_MAXIMIZE; + btn.ht = HTMAXBUTTON; + List_InsertBack(g_pList, &btn); + + btn.iGlyph = GLYPH_CLOSE; + btn.ht = HTCLOSE; + List_InsertBack(g_pList, &btn); + + SetWindowTheme(hWnd, L"", L""); + + *bProcessed = TRUE; + return DefWindowProc(hWnd, WM_NCCREATE, wParam, lParam); + } + break; + + case WM_NCCALCSIZE: + { + BOOL bProcess = (BOOL)wParam; + DWORD dwStyle = GetWindowStyle(hWnd); + + RECT* rc = NULL; + if (bProcess) + { + NCCALCSIZE_PARAMS* params = (LPNCCALCSIZE_PARAMS)lParam; + + /* params->rgrc[0] is the new rectangle + * params->rgrc[1] is the old rectangle + * params->rgrc[2] is the client rectangle + * + * https://stackoverflow.com/a/2135120 + */ + rc = ¶ms->rgrc[0]; + } + else { + rc = (PRECT)lParam; + } + + if (dwStyle & WS_THICKFRAME) + { + InflateRect(&rc[0], -(g_sysConfig.smCYSizeFrame + g_sysConfig.smCXPaddedBorder), -(g_sysConfig.smCXSizeFrame + g_sysConfig.smCXPaddedBorder)); + rc[0].top -= g_sysConfig.smCXPaddedBorder; + } + else { + // InflateRect(&rc[0], -1, -1); + } + + if (dwStyle & WS_CAPTION) + { + rc->top += g_sysConfig.smCYCaption + g_sysConfig.smCXSizeFrame; + if (dwStyle & WS_THICKFRAME) + { + // rc->top += smCXSizeFrame; + } + } + + LRESULT lResult = 0; + if (bProcess) + { + lResult = 0; + } + + *bProcessed = TRUE; + return lResult; + } + break; + + case WM_NCPAINT: + { + RECT rcClient; + GetClientRect(hWnd, &rcClient); + + RECT rcWindow; + GetWindowRect(hWnd, &rcWindow); + + POINT ptDisplace = { + .x = rcWindow.left, + .y = rcWindow.right + }; + + MapWindowRect(HWND_DESKTOP, hWnd, &rcWindow); + OffsetRect(&rcClient, -rcWindow.left, -rcWindow.top); + OffsetRect(&rcWindow, -rcWindow.left, -rcWindow.top); + + UpdateCaptionButtonRects(hWnd); + + + DWORD dwFlags = DCX_WINDOW; + /* Note the use of undocumented flags below. GetDCEx doesn't work without these + * GetDCEx returns NULL if there is no DCX_USESTYLE, and sometimes, if no DCX_LOCKWINDOWUPDATE flags + */ + dwFlags |= DCX_LOCKWINDOWUPDATE; + dwFlags |= DCX_USESTYLE; + + /* + if (hrgn) + { + dwFlags |= DCX_INTERSECTRGN; + } + */ + + HRGN hrgn = (HRGN)wParam; + HDC hdc = GetDCEx(hWnd, NULL, dwFlags); + + /* + * Undocumented WM_NCPAINT's (HRGN)wParam == 1(NULLREGION) issue + * https://stackoverflow.com/a/61901103 + * https://stackoverflow.com/a/50135792 + */ + HRGN hrgnTemp = NULL; + + if (!hrgn || hrgn == NULLREGION) + { + /* Exclude client area from hdc */ + ExcludeClipRect(hdc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); + } + else { + // MessageBox(NULL, L"Custom region", L"Info", MB_OK | MB_ICONINFORMATION); + + hrgnTemp = CreateRectRgn( + rcClient.left + ptDisplace.x, + rcClient.top + ptDisplace.y, + rcClient.right + ptDisplace.x, + rcClient.bottom + ptDisplace.y + ); + + if (CombineRgn(hrgnTemp, hrgn, hrgnTemp, RGN_DIFF) == NULLREGION) + { + /* Nothing to paint */ + } + + OffsetRgn(hrgnTemp, -ptDisplace.x, -ptDisplace.y); + ExtSelectClipRgn(hdc, hrgnTemp, RGN_AND); + } + + if (hdc) + { + HDC hdcDoubleBuffer = CreateCompatibleDC(hdc); + HBITMAP hbmDoubleBuffer = CreateCompatibleBitmap(hdc, rcWindow.right, rcWindow.bottom); + HBITMAP hbmDBOld = SelectObject(hdcDoubleBuffer, hbmDoubleBuffer); + + SelectObject(hdcDoubleBuffer, GetStockObject(DC_BRUSH)); + SelectObject(hdcDoubleBuffer, GetStockObject(DC_PEN)); + + /* Draw frame */ + SetDCBrushColor(hdcDoubleBuffer, Win32_HexToCOLORREF(L"#9185be")); + SetDCPenColor(hdcDoubleBuffer, Win32_HexToCOLORREF(L"#6d648e")); + + DWORD dwStyle = GetWindowStyle(hWnd); + + if (!(dwStyle & WS_THICKFRAME)) + { + SetDCPenColor(hdcDoubleBuffer, Win32_HexToCOLORREF(L"#9185be")); + // SelectObject(hdcDoubleBuffer, GetStockObject(NULL_PEN)); + } + + Rectangle(hdcDoubleBuffer, rcWindow.left, rcWindow.top, rcWindow.right, rcWindow.bottom); + + /* Draw caption */ + + if (dwStyle & WS_CAPTION) + { + int smCXSmIcon = g_sysConfig.smCXSmIcon; + int smCYSmIcon = g_sysConfig.smCYSmIcon; + int smCXEdge = g_sysConfig.smCXEdge; + int smCYEdge = g_sysConfig.smCYEdge; + + /* Draw sysmenu icon */ + HICON hIcon = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, smCXSmIcon, smCYSmIcon, 0); + int iconX = g_sysConfig.smCYSizeFrame + g_sysConfig.smCXPaddedBorder; + int iconY = g_sysConfig.smCXSizeFrame + g_sysConfig.smCXPaddedBorder; + if (dwStyle & WS_MAXIMIZE) + { + iconX += smCXEdge; + iconY += smCYEdge; + } + DrawIconEx(hdcDoubleBuffer, iconX, iconY, hIcon, smCXSmIcon, smCYSmIcon, 0, NULL, DI_NORMAL); + DeleteObject(hIcon); + + /* Draw title */ + PCWSTR pszTitle = L"Default Caption"; + WCHAR szTitleBuf[MAX_PATH]; + int chLen = GetWindowText(hWnd, szTitleBuf, MAX_PATH); + if (chLen) + { + pszTitle = szTitleBuf; + } + + HFONT guiFont = Win32_GetUIFont(); + LOGFONT lf; + GetObject(guiFont, sizeof(lf), &lf); + lf.lfWeight = FW_BOLD; + wcscpy_s(lf.lfFaceName, sizeof(lf.lfFaceName) / sizeof(*lf.lfFaceName), L"Montserrat"); + HFONT hFont = CreateFontIndirect(&lf); + + HFONT hOldFont = SelectObject(hdcDoubleBuffer, hFont); + SetBkMode(hdcDoubleBuffer, TRANSPARENT); + SetTextColor(hdcDoubleBuffer, COLORREF_WHITE); + RECT rcTitle; + rcTitle.left = smCXSmIcon + smCXEdge * 6; + rcTitle.top = smCYEdge * 2; + if (dwStyle & WS_MAXIMIZE) + { + rcTitle.top += smCYEdge * 3; + } + rcTitle.right = rcWindow.right - smCXEdge; + rcTitle.bottom = smCYEdge * 2 + GetSystemMetrics(SM_CYCAPTION); + DrawText(hdcDoubleBuffer, pszTitle, wcslen(pszTitle), &rcTitle, DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_VCENTER); + + SelectObject(hdcDoubleBuffer, hOldFont); + DeleteObject(hFont); + + UpdateCaptionButtonRects(hWnd); + for (size_t i = 0; i < List_GetLength(g_pList); ++i) + { + CaptionButtonInfo* pButton = List_Get(g_pList, i); + DrawCaptionButton3(hdcDoubleBuffer, pButton); + } + + BitBlt(hdc, 0, 0, rcWindow.right, rcWindow.bottom, hdcDoubleBuffer, 0, 0, SRCCOPY); + + SelectObject(hdcDoubleBuffer, hbmDBOld); + DeleteObject(hbmDoubleBuffer); + DeleteDC(hdcDoubleBuffer); + } + + ReleaseDC(hWnd, hdc); + } + + if (hrgnTemp) + { + DeleteObject(hrgnTemp); + } + + *bProcessed = TRUE; + /* An application returns zero if it processes this message */ + return 0; + } + break; + + case WM_NCACTIVATE: + *bProcessed = TRUE; + break; + } +} + +#define IDM_STYLING 101 + +LRESULT CALLBACK DockPanel_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_CREATE: + { + HWND hCheck = CreateWindow(WC_BUTTON, L"Enable styling", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 10, 10, 200, 30, hWnd, (HMENU)IDM_STYLING, GetModuleHandle(NULL), NULL); + Win32_ApplyUIFont(hCheck); + } + break; + + case WM_COMMAND: + { + if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDM_STYLING) + { + g_fEnableStyle = Button_GetCheck((HWND)lParam); + RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE); + + if (!g_fEnableStyle) + { + SetWindowTheme(hWnd, L"Explorer", NULL); + } + else { + SetWindowTheme(hWnd, L"", L""); + } + } + } + break; + + case WM_DESTROY: + break; + } + + if (g_fEnableStyle) + { + BOOL bProcessed = FALSE; + LRESULT lResult = StylingProc(hWnd, message, wParam, lParam, &bProcessed); + + if (bProcessed) + { + return lResult; + } + } + + return DefWindowProc(hWnd, message, wParam, lParam); +} + +BOOL DockPanel_RegisterClass(HINSTANCE hInstance) +{ + WNDCLASSEX wcex = { 0 }; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = (WNDPROC)DockPanel_WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON)); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wcex.lpszClassName = szClassName; + wcex.lpszMenuName = NULL; + wcex.hIconSm = NULL; + + return RegisterClassEx(&wcex); +} + +HWND DockPanel_CreateWindow(HINSTANCE hInstance) +{ + return CreateWindow(szClassName, L"PanitentMDIChild", WS_CAPTION, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); +} diff --git a/src/experimental/dockpanel.h b/src/experimental/dockpanel.h new file mode 100644 index 0000000..f1af016 --- /dev/null +++ b/src/experimental/dockpanel.h @@ -0,0 +1,4 @@ +#pragma once + +BOOL DockPanel_RegisterClass(HINSTANCE hInstance); +HWND DockPanel_CreateWindow(HINSTANCE hInstance); diff --git a/src/experimental/dockwindow.c b/src/experimental/dockwindow.c new file mode 100644 index 0000000..0780770 --- /dev/null +++ b/src/experimental/dockwindow.c @@ -0,0 +1,145 @@ +#include "../precomp.h" + +#include "dockwindow.h" +#include "dockpanel.h" + +#include "../resource.h" + +#include "../win32/window.h" + +#include "dockhostcomposite.h" +#include "../util/assert.h" + +static const WCHAR szClassName[] = L"Experimental::DockWindow"; + +typedef struct DockHostWindow2 DockHostWindow2; +struct DockHostWindow2 { + Window base; + DockHostComposite* pDockHostComposite; +}; + +DockHostWindow2* DockHostWindow2_Create(Application* pApplication); +void DockHostWindow2_Init(DockHostWindow2* pDockHostWindow2, Application* pApplication); +void DockHostWindow2_PreRegister(LPWNDCLASSEX pwcex); +void DockHostWindow2_PreCreate(LPCREATESTRUCT lpcs); +void DockHostWindow2_OnCreate(DockHostWindow2* pDockHostWindow2, LPCREATESTRUCT lpcs); +void DockHostWindow2_OnPaint(DockHostWindow2* pDockHostWindow2); +LRESULT DockHostWindow2_UserProc(DockHostWindow2* pDockHostWindow2, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + +DockHostWindow2* DockHostWindow2_Create(Application* pApplication) +{ + DockHostWindow2* pDockHostWindow2 = (DockHostWindow2*)malloc(sizeof(DockHostWindow2)); + + if (pDockHostWindow2) + { + DockHostWindow2_Init(pDockHostWindow2, pApplication); + } + + return pDockHostWindow2; +} + +void DockHostWindow2_Init(DockHostWindow2* pDockHostWindow2, Application* pApplication) +{ + memset(pDockHostWindow2, 0, sizeof(DockHostWindow2)); + + Window_Init(&pDockHostWindow2->base, pApplication); + + _WindowInitHelper_SetPreCreateRoutine(pDockHostWindow2, (FnWindowPreCreate)DockHostWindow2_PreCreate); + _WindowInitHelper_SetPreRegisterRoutine(pDockHostWindow2, (FnWindowPreRegister)DockHostWindow2_PreRegister); + _WindowInitHelper_SetUserProcRoutine(pDockHostWindow2, (FnWindowUserProc)DockHostWindow2_UserProc); + + pDockHostWindow2->base.OnCreate = (FnWindowOnCreate)DockHostWindow2_OnCreate; + pDockHostWindow2->base.OnPaint = (FnWindowOnPaint)DockHostWindow2_OnPaint; + + pDockHostWindow2->pDockHostComposite = DockHostComposite_Create(); +} + +void DockHostWindow2_PreRegister(LPWNDCLASSEX lpwcex) +{ + lpwcex->style = CS_HREDRAW | CS_VREDRAW; + lpwcex->hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON)); + lpwcex->hCursor = LoadCursor(NULL, IDC_ARROW); + lpwcex->hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + lpwcex->lpszClassName = szClassName; +} + +void DockHostWindow2_PreCreate(LPCREATESTRUCT lpcs) +{ + lpcs->dwExStyle = 0; + lpcs->lpszClass = szClassName; + lpcs->lpszName = L"Panit.ent Alpha 0.4.2"; + lpcs->style = WS_OVERLAPPEDWINDOW | WS_VISIBLE; + lpcs->x = CW_USEDEFAULT; + lpcs->y = CW_USEDEFAULT; + lpcs->cx = 300; + lpcs->cy = 200; +} + +void DockHostWindow2_OnCreate(DockHostWindow2* pDockHostWindow2, LPCREATESTRUCT lpcs) +{ + DockHostComposite_SetWindow(pDockHostWindow2->pDockHostComposite, pDockHostWindow2); + + DockWindowInfo dwi = { 0 }; + + HWND hDockPanel1 = CreateWindowEx(WS_EX_TOOLWINDOW, L"Experimental::DockPanel", L"Toolbox", WS_OVERLAPPED | WS_CAPTION | WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 320, 240, Window_GetHWND(pDockHostWindow2), NULL, GetModuleHandle(NULL), NULL); + ASSERT(hDockPanel1); + dwi.hWnd = hDockPanel1; + DockHostComposite_Dock(pDockHostWindow2->pDockHostComposite, &dwi, 1); + + HWND hDockPanel2 = CreateWindowEx(WS_EX_TOOLWINDOW, L"Experimental::DockPanel", L"GLWindow", WS_OVERLAPPED | WS_CAPTION | WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 320, 240, Window_GetHWND(pDockHostWindow2), NULL, GetModuleHandle(NULL), NULL); + ASSERT(hDockPanel2); + dwi.hWnd = hDockPanel2; + DockHostComposite_Dock(pDockHostWindow2->pDockHostComposite, &dwi, 1); + + HWND hDockPanel3 = CreateWindowEx(WS_EX_TOOLWINDOW, L"Experimental::DockPanel", L"Info", WS_OVERLAPPED | WS_CAPTION | WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 320, 240, Window_GetHWND(pDockHostWindow2), NULL, GetModuleHandle(NULL), NULL); + ASSERT(hDockPanel3); + dwi.hWnd = hDockPanel3; + DockHostComposite_Dock(pDockHostWindow2->pDockHostComposite, &dwi, 3); + + HWND hDockPanel4 = CreateWindowEx(WS_EX_TOOLWINDOW, L"Experimental::DockPanel", L"Palette", WS_OVERLAPPED | WS_CAPTION | WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 320, 240, Window_GetHWND(pDockHostWindow2), NULL, GetModuleHandle(NULL), NULL); + ASSERT(hDockPanel4); + dwi.hWnd = hDockPanel4; + DockHostComposite_Dock(pDockHostWindow2->pDockHostComposite, &dwi, 3); + + HWND hDockPanel5 = CreateWindowEx(WS_EX_TOOLWINDOW, L"Experimental::DockPanel", L"Layers", WS_OVERLAPPED | WS_CAPTION | WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 320, 240, Window_GetHWND(pDockHostWindow2), NULL, GetModuleHandle(NULL), NULL); + ASSERT(hDockPanel5); + dwi.hWnd = hDockPanel5; + DockHostComposite_Dock(pDockHostWindow2->pDockHostComposite, &dwi, 3); + + return TRUE; +} + +void DockHostWindow2_OnPaint(DockHostWindow2* pDockHostWindow2) +{ + HWND hWnd = Window_GetHWND(pDockHostWindow2); + RECT rcClient; + Window_GetClientRect(pDockHostWindow2, &rcClient); + + PAINTSTRUCT ps = { 0 }; + HDC hdc = BeginPaint(hWnd, &ps); + + Rectangle(hdc, 0, 0, rcClient.right, 48); + + DockHostComposite_Draw(pDockHostWindow2->pDockHostComposite, hdc); + + EndPaint(hWnd, &ps); +} + +LRESULT DockHostWindow2_UserProc(DockHostWindow2* pDockHostWindow2, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + DockHostComposite* pDockHostComposite = pDockHostWindow2->pDockHostComposite; + + LRESULT lResult = 0; + BOOL bProcessed = FALSE; + if (pDockHostComposite) + { + lResult = DockHostComposite_WndProc(pDockHostComposite, hWnd, message, wParam, lParam, &bProcessed); + } + + if (bProcessed) + { + return lResult; + } + + return Window_UserProcDefault(pDockHostWindow2, hWnd, message, wParam, lParam); +} diff --git a/src/experimental/dockwindow.h b/src/experimental/dockwindow.h new file mode 100644 index 0000000..7854314 --- /dev/null +++ b/src/experimental/dockwindow.h @@ -0,0 +1,6 @@ +#pragma once + +typedef struct Application Application; +typedef struct DockHostWindow2 DockHostWindow2; + +DockHostWindow2* DockHostWindow2_Create(Application* pApplication); diff --git a/src/flexible.c b/src/flexible.c index 6ee5b2f..60c8cdd 100644 --- a/src/flexible.c +++ b/src/flexible.c @@ -34,7 +34,8 @@ static inline LAYOUTBOXCONTENT LayoutBoxContentFromGroup(GROUPBOX *pGroupBox) void CreateLayoutBox(PLAYOUTBOX* pLayoutBox, HWND hWnd) { - *pLayoutBox = (PLAYOUTBOX) malloc(sizeof(LAYOUTBOX)); + *pLayoutBox = (PLAYOUTBOX)malloc(sizeof(LAYOUTBOX)); + memset(pLayoutBox, 0, sizeof(LAYOUTBOX)); if (!*pLayoutBox) return; @@ -217,7 +218,8 @@ PGROUPBOXCAPTIONSTYLE GroupBox_GetGlobalStyle() void CreateGroupBox(PGROUPBOX *pGroupBox) { - *pGroupBox = (PGROUPBOX) malloc(sizeof(GROUPBOX)); + *pGroupBox = (PGROUPBOX)malloc(sizeof(GROUPBOX)); + memset(pGroupBox, 0, sizeof(GROUPBOX)); if (!*pGroupBox) return; @@ -601,7 +603,7 @@ void DrawGroupBox(HDC hDC, GROUPBOX *pGroupBox) SetBkMode(hDC, TRANSPARENT); SetTextColor(hDC, pCaptionStyle->dwTextColor); - /* Temporary buffer for overflow ellipsis */ + /* Temporary pszBuffer for overflow ellipsis */ WCHAR szCaption[80] = { 0 }; StringCchCopy(szCaption, 80, pGroupBox->lpszCaption); diff --git a/src/floatingwindowcontainer.c b/src/floatingwindowcontainer.c index ea7171e..ef2b2c5 100644 --- a/src/floatingwindowcontainer.c +++ b/src/floatingwindowcontainer.c @@ -5,6 +5,7 @@ #include "FloatingWindowContainer.h" #include "resource.h" #include "panitent.h" +#include "toolwndframe.h" static const WCHAR szClassName[] = L"__FloatingWindowContainer"; @@ -92,56 +93,11 @@ LRESULT FloatingWindowContainer_OnNCCalcSize(FloatingWindowContainer* pFloatingW return 0; } -enum { - GLYPH_MORE = 0, - GLYPH_PIN, - GLYPH_MINIMIZE, - GLYPH_MAXIMIZE, - GLYPH_CLOSE -}; - -typedef struct CaptionButton CaptionButton; - -struct CaptionButton { - SIZE size; - int glyph; -}; - LRESULT CaptionRightContainerHitTest() { } -void DrawCaptionGlyph(HDC hdc, int x, int y, int iGlyph) -{ - HBITMAP hbmGlyphs = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_FLOATINGGLYPHS)); - HDC hdcGlyphs = CreateCompatibleDC(hdc); - HBITMAP hPrevBm = SelectObject(hdcGlyphs, hbmGlyphs); - - TransparentBlt(hdc, x, y, 8, 8, hdcGlyphs, iGlyph * 8, 0, 8, 8, RGB(255, 0, 255)); - - SelectObject(hdcGlyphs, hPrevBm); - DeleteObject(hbmGlyphs); -} - -void DrawCaptionButton(CaptionButton* pCaptionButton, HDC hdc, int x, int y) -{ - RECT rcButton = { 0 }; - rcButton.left = x; - rcButton.top = y; - rcButton.right = x + pCaptionButton->size.cx; - rcButton.bottom = y + pCaptionButton->size.cy; - - SetDCPenColor(hdc, RGB(0xFF, 0xFF, 0xFF)); - SetDCBrushColor(hdc, Win32_HexToCOLORREF(L"#9185be")); - SelectObject(hdc, GetStockObject(DC_PEN)); - SelectObject(hdc, GetStockObject(DC_BRUSH)); - - Rectangle(hdc, rcButton.left, rcButton.top, rcButton.right, rcButton.bottom); - - DrawCaptionGlyph(hdc, rcButton.left + (rcButton.right - rcButton.left - 8) / 2, rcButton.top + (rcButton.bottom - rcButton.top - 8) / 2, pCaptionButton->glyph); -} - CaptionButton g_captionButtons[] = { { {14, 14}, @@ -285,20 +241,21 @@ LRESULT FloatingWindowContainer_OnNCHitTest(FloatingWindowContainer* pFloatingWi } } - return HTCLIENT; } FloatingWindowContainer* FloatingWindowContainer_Create(struct Application* app) { - FloatingWindowContainer* window = calloc(1, sizeof(FloatingWindowContainer)); + FloatingWindowContainer* pFloatingWindowContainer = (FloatingWindowContainer*)malloc(sizeof(FloatingWindowContainer)); - if (window) + if (pFloatingWindowContainer) { - FloatingWindowContainer_Init(window, app); + memset(pFloatingWindowContainer, 0, sizeof(FloatingWindowContainer)); + + FloatingWindowContainer_Init(pFloatingWindowContainer, app); } - return window; + return pFloatingWindowContainer; } void FloatingWindowContainer_Init(FloatingWindowContainer* window, struct Application* app) @@ -423,7 +380,29 @@ LRESULT FloatingWindowContainer_OnNCMouseMove(FloatingWindowContainer* pFloating const int xStart = pFloatingWindowContainer->ptCaptionUnpinStartingPoint.x; const int yStart = pFloatingWindowContainer->ptCaptionUnpinStartingPoint.y; - if (sqrt(pow(x - xStart, 2) + pow(y - yStart, 2)) >= 20) + HWND hWnd = Window_GetHWND(pFloatingWindowContainer); + + HDC hdcDesktop = GetDC(NULL); + HDC hdcScreenshot = CreateCompatibleDC(hdcDesktop); + + RECT rcWindow; + GetWindowRect(hWnd, &rcWindow); + int width = Win32_Rect_GetWidth(&rcWindow); + int height = Win32_Rect_GetHeight(&rcWindow); + HBITMAP hbmScreenshot = CreateCompatibleBitmap(hdcDesktop, width, height); + + SelectObject(hdcScreenshot, hbmScreenshot); + BOOL bResult = PrintWindow(hWnd, hdcScreenshot, 0); + + POINT pt; + pt.x = x; + pt.y = y; + MapWindowPoints(NULL, hWnd, &pt, 1); + + BitBlt(hdcDesktop, x - pt.x, y + pt.y, width, height, hdcScreenshot, 0, 0, SRCCOPY); + + + if (sqrt(pow(x - xStart, 2) + pow(y - yStart, 2)) >= 100) { // ReleaseCapture(); FloatingWindowContainer_OnUnpinCommand(pFloatingWindowContainer); @@ -440,7 +419,7 @@ LRESULT FloatingWindowContainer_OnNCLButtonUp(FloatingWindowContainer* pFloating { if (pFloatingWindowContainer->fCaptionUnpinStarted) { - // ReleaseCapture(); + ReleaseCapture(); pFloatingWindowContainer->fCaptionUnpinStarted = FALSE; } diff --git a/src/glwindow.c b/src/glwindow.c index 6546aff..5afbe8e 100644 --- a/src/glwindow.c +++ b/src/glwindow.c @@ -26,28 +26,29 @@ LRESULT CALLBACK GLWindow_UserProc(GLWindow*, HWND hWnd, UINT, WPARAM, LPARAM); GLWindow* GLWindow_Create(struct Application* app) { - GLWindow* window = calloc(1, sizeof(GLWindow)); + GLWindow* pGLWindow = (GLWindow*)malloc(sizeof(GLWindow)); + memset(pGLWindow, 0, sizeof(GLWindow)); - if (window) + if (pGLWindow) { - GLWindow_Init(window, app); + GLWindow_Init(pGLWindow, app); } - return window; + return pGLWindow; } -void GLWindow_Init(GLWindow* window, struct Application* app) +void GLWindow_Init(GLWindow* pGLWindow, struct Application* app) { - Window_Init(&window->base, app); + Window_Init(&pGLWindow->base, app); - window->base.szClassName = szClassName; + pGLWindow->base.szClassName = szClassName; - window->base.OnCreate = (FnWindowOnCreate)GLWindow_OnCreate; - window->base.OnDestroy = (FnWindowOnDestroy)GLWindow_OnDestroy; - window->base.OnPaint = (FnWindowOnPaint)GLWindow_OnPaint; - window->base.PreRegister = (FnWindowPreRegister)GLWindow_PreRegister; - window->base.PreCreate = (FnWindowPreCreate)GLWindow_PreCreate; - window->base.UserProc = (FnWindowUserProc)GLWindow_UserProc; + pGLWindow->base.OnCreate = (FnWindowOnCreate)GLWindow_OnCreate; + pGLWindow->base.OnDestroy = (FnWindowOnDestroy)GLWindow_OnDestroy; + pGLWindow->base.OnPaint = (FnWindowOnPaint)GLWindow_OnPaint; + pGLWindow->base.PreRegister = (FnWindowPreRegister)GLWindow_PreRegister; + pGLWindow->base.PreCreate = (FnWindowPreCreate)GLWindow_PreCreate; + pGLWindow->base.UserProc = (FnWindowUserProc)GLWindow_UserProc; } void GLWindow_PreRegister(LPWNDCLASSEX lpwcex) @@ -58,9 +59,9 @@ void GLWindow_PreRegister(LPWNDCLASSEX lpwcex) lpwcex->lpszClassName = szClassName; } -BOOL GLWindow_OnCreate(GLWindow* window, LPCREATESTRUCT lpcs) +BOOL GLWindow_OnCreate(GLWindow* pGLWindow, LPCREATESTRUCT lpcs) { - UNREFERENCED_PARAMETER(window); + UNREFERENCED_PARAMETER(pGLWindow); UNREFERENCED_PARAMETER(lpcs); PIXELFORMATDESCRIPTOR pfd; @@ -91,7 +92,7 @@ BOOL GLWindow_OnCreate(GLWindow* window, LPCREATESTRUCT lpcs) pfd.dwVisibleMask = 0; pfd.dwDamageMask = 0; - HDC ourWindowHandleToDeviceContext = GetDC(window->base.hWnd); + HDC ourWindowHandleToDeviceContext = GetDC(pGLWindow->base.hWnd); int letWindowsChooseThisPixelFormat; letWindowsChooseThisPixelFormat = ChoosePixelFormat(ourWindowHandleToDeviceContext, &pfd); diff --git a/src/history.c b/src/history.c index e80dc1b..2aa38ff 100644 --- a/src/history.c +++ b/src/history.c @@ -14,137 +14,148 @@ Canvas* g_historyTempSavedState; void History_Undo(Document* document) { - History *history = Document_GetHistory(document); - Canvas *canvas = Document_GetCanvas(document); + History* history = Document_GetHistory(document); + Canvas* canvas = Document_GetCanvas(document); - Canvas *mask = history->peak->differential; - RECT *rc = &history->peak->rc; + Canvas* mask = history->peak->differential; + RECT* rc = &history->peak->rc; - uint32_t *pDest = canvas->buffer; - uint32_t *pOrig = mask->buffer; - pDest += rc->top * canvas->width + rc->left; + uint32_t* pDest = canvas->buffer; + uint32_t* pOrig = mask->buffer; + pDest += rc->top * canvas->width + rc->left; - for (int i = 0; i < mask->height; i++) { - for (int j = 0; j < mask->width; j++) { - *pDest++ ^= *pOrig++; - } + for (int i = 0; i < mask->height; i++) { + for (int j = 0; j < mask->width; j++) { + *pDest++ ^= *pOrig++; + } - pDest += canvas->width - mask->width; - } + pDest += canvas->width - mask->width; + } - history->peak = history->peak->previous; - Window_Invalidate((Window *)Panitent_GetActiveViewport()); + history->peak = history->peak->previous; + Window_Invalidate((Window*)Panitent_GetActiveViewport()); } void History_Redo(Document* document) { - History *history = Document_GetHistory(document); - Canvas *canvas = Document_GetCanvas(document); + History* history = Document_GetHistory(document); + Canvas* canvas = Document_GetCanvas(document); - Canvas *mask = history->peak->next->differential; - RECT *rc = &history->peak->next->rc; + Canvas* mask = history->peak->next->differential; + RECT* rc = &history->peak->next->rc; - uint32_t *pDest = canvas->buffer; - uint32_t *pOrig = mask->buffer; - pDest += rc->top * canvas->width + rc->left; + uint32_t* pDest = canvas->buffer; + uint32_t* pOrig = mask->buffer; + pDest += rc->top * canvas->width + rc->left; - for (int i = 0; i < mask->height; i++) { - for (int j = 0; j < mask->width; j++) { - *pDest++ ^= *pOrig++; - } + for (int i = 0; i < mask->height; i++) { + for (int j = 0; j < mask->width; j++) { + *pDest++ ^= *pOrig++; + } - pDest += canvas->width - mask->width; - } + pDest += canvas->width - mask->width; + } - history->peak = history->peak->next; - Window_Invalidate((Window *)Panitent_GetActiveViewport()); + history->peak = history->peak->next; + Window_Invalidate((Window*)Panitent_GetActiveViewport()); } void History_PushRecord(Document* document, HistoryRecord record) { - History *history = Document_GetHistory(document); + History* history = Document_GetHistory(document); - HistoryRecord *sharedRecord = calloc(1, sizeof(HistoryRecord)); - if (sharedRecord) - { - memcpy(sharedRecord, &record, sizeof(HistoryRecord)); + HistoryRecord* sharedRecord = (HistoryRecord*)malloc(sizeof(HistoryRecord)); + memset(sharedRecord, 0, sizeof(HistoryRecord)); + if (sharedRecord) + { + memcpy(sharedRecord, &record, sizeof(HistoryRecord)); - sharedRecord->previous = history->peak; - history->peak->next = sharedRecord; - history->peak = sharedRecord; - } + sharedRecord->previous = history->peak; + history->peak->next = sharedRecord; + history->peak = sharedRecord; + } } void History_DestroyFollowingRedoes(HistoryRecord* record) { - if (record != NULL) - { - Canvas_Delete(record->differential); + if (record != NULL) + { + Canvas_Delete(record->differential); - History_DestroyFollowingRedoes(record->next); - free(record); - } + History_DestroyFollowingRedoes(record->next); + free(record); + } } void History_StartDifferentiation(Document* document) { - Canvas *canvas = Document_GetCanvas(document); - History *history = Document_GetHistory(document); + Canvas* canvas = Document_GetCanvas(document); + History* history = Document_GetHistory(document); - History_DestroyFollowingRedoes(history->peak->next); + History_DestroyFollowingRedoes(history->peak->next); - Canvas *savedState = Canvas_Clone(canvas); - assert(savedState); + Canvas* savedState = Canvas_Clone(canvas); + assert(savedState); - g_historyTempSavedState = savedState; + g_historyTempSavedState = savedState; } void History_FinalizeDifferentiation(Document* document) { - Canvas *current = Document_GetCanvas(document); - Canvas *saved = g_historyTempSavedState; + Canvas* current = Document_GetCanvas(document); + Canvas* saved = g_historyTempSavedState; - for (size_t i = 0; i < current->buffer_size; i++) - { - ((unsigned char*)saved->buffer)[i] ^= ((unsigned char*)current->buffer)[i]; - } - g_historyTempSavedState = NULL; - - - uint32_t *imageBuffer = saved->buffer; - - RECT rc = {current->width, current->height, 0, 0}; - - BOOL topSet = FALSE; - for (int y = 0; y < current->height; y++) { - for (int x = 0; x < current->width; x++) { - if (imageBuffer[y * current->width + x] != 0) { - if (!topSet) { - rc.top = y; - topSet = TRUE; + for (size_t i = 0; i < current->buffer_size; i++) + { + ((unsigned char*)saved->buffer)[i] ^= ((unsigned char*)current->buffer)[i]; + } + g_historyTempSavedState = NULL; + + + uint32_t* imageBuffer = saved->buffer; + + RECT rc = { current->width, current->height, 0, 0 }; + + BOOL topSet = FALSE; + for (int y = 0; y < current->height; y++) + { + for (int x = 0; x < current->width; x++) + { + if (imageBuffer[y * current->width + x] != 0) + { + if (!topSet) + { + rc.top = y; + topSet = TRUE; + } + + if (x < rc.left) + { + rc.left = x; + } + + if (x > rc.right) + { + rc.right = x; + } + + if (y > rc.bottom) + { + rc.bottom = y; + } + } } - - if (x < rc.left) - rc.left = x; - - if (x > rc.right) - rc.right = x; - - if (y > rc.bottom) - rc.bottom = y; - } } - } - rc.right++; - rc.bottom++; + rc.right++; + rc.bottom++; - Canvas* cropped = Canvas_Substitute(saved, &rc); - Canvas_Delete(saved); + Canvas* cropped = Canvas_Substitute(saved, &rc); + Canvas_Delete(saved); - HistoryRecord record = {0}; - record.differential = cropped; - record.rc = rc; + HistoryRecord record = { 0 }; + record.differential = cropped; + record.rc = rc; - History_PushRecord(document, record); + History_PushRecord(document, record); } diff --git a/src/layeredwindow.c b/src/layeredwindow.c index fcee5e6..5d7e636 100644 --- a/src/layeredwindow.c +++ b/src/layeredwindow.c @@ -313,12 +313,13 @@ void LayeredWindow_Init(LayeredWindow* pLayeredWindow, struct Application* app) LayeredWindow* LayeredWindow_Create(struct Application* app) { - LayeredWindow* window = calloc(1, sizeof(LayeredWindow)); + LayeredWindow* pLayeredWindow = (LayeredWindow*)malloc(sizeof(LayeredWindow)); + memset(pLayeredWindow, 0, sizeof(LayeredWindow)); - if (window) + if (pLayeredWindow) { - LayeredWindow_Init(window, app); + LayeredWindow_Init(pLayeredWindow, app); } - return window; + return pLayeredWindow; } diff --git a/src/layerswindow.c b/src/layerswindow.c index b47e410..c6e4e69 100644 --- a/src/layerswindow.c +++ b/src/layerswindow.c @@ -26,7 +26,8 @@ LRESULT LayersWindow_UserProc(LayersWindow* pLayersWindow, HWND hWnd, UINT messa LayersWindow* LayersWindow_Create(Application* pApp) { - LayersWindow* pLayersWindow = (LayersWindow *)calloc(1, sizeof(LayersWindow)); + LayersWindow* pLayersWindow = (LayersWindow *)malloc(sizeof(LayersWindow)); + memset(pLayersWindow, 0, sizeof(LayersWindow)); if (pLayersWindow) { diff --git a/src/log.c b/src/log.c index 3d451c4..b4bc667 100644 --- a/src/log.c +++ b/src/log.c @@ -1,6 +1,7 @@ #include "precomp.h" #include "log.h" #include "util.h" +#include "panitent.h" typedef struct _tagLOGEVENTOBSERVER LOGEVENTOBSERVER, *LPLOGEVENTOBSERVER; @@ -24,27 +25,84 @@ void LogMessage(int, LPWSTR, LPWSTR); LPLOGGER GetLogger(); void Logger_PushMessage(LPLOGGER, LPLOGENTRY); -void LogMessage(int iType, LPWSTR szModule, LPWSTR szMessage) +void LogMessage(int nLevel, PCWSTR pszModule, PCWSTR pszMessage) { LOGENTRY logEntry = { 0 }; GetSystemTime(&logEntry.timestamp); - logEntry.iType = iType; - StringCchCopy(logEntry.szModule, 80, szModule); - StringCchCopy(logEntry.szMessage, 80, szMessage); + logEntry.iType = nLevel; + + int len = wcslen(pszModule); + size_t size = (len + 1) * sizeof(WCHAR); + logEntry.pszModule = (PWSTR)malloc(size); + if (logEntry.pszModule) + { + memset(logEntry.pszModule, 0, size); + wcscpy_s(logEntry.pszModule, len + 1, pszModule); + } + else { + Panitent_RaiseException(L"Memory allocation fault"); + return; + } + + len = wcslen(pszMessage); + size = (len + 1) * sizeof(WCHAR); + logEntry.pszMessage = (PWSTR)malloc(size); + if (logEntry.pszMessage) + { + memset(logEntry.pszMessage, 0, size); + wcscpy_s(logEntry.pszMessage, len + 1, pszMessage); + } + else { + Panitent_RaiseException(L"Memory allocation fault"); + return; + } LPLOGGER lpLogger = GetLogger(); Logger_PushMessage(lpLogger, &logEntry); } +void LogMessageF(int nLevel, PCWSTR pszModule, PCWSTR pszFormat, ...) +{ + va_list argp, argp2; + va_start(argp, pszFormat); + va_copy(argp2, argp); + + int len = vswprintf(NULL, 0, pszFormat, argp2); + size_t size = (len + 1) * sizeof(WCHAR); + PWSTR pszString = (PWSTR)malloc(size); + if (pszString) + { + memset(pszString, 0, size); + vswprintf_s(pszString, len + 1, pszFormat, argp); + + LogMessage(nLevel, pszModule, pszString); + + free(pszString); + } + else { + Panitent_RaiseException(L"Memory allocation fault"); + } + + va_end(argp); + va_end(argp2); +} + LPLOGGER GetLogger() { static LPLOGGER s_lpLogger = NULL; if (!s_lpLogger) { - s_lpLogger = calloc(1, sizeof(LOGGER)); - pntvector_init(LPLOGENTRY)(&s_lpLogger->entries); - pntvector_init(LPLOGEVENTOBSERVER)(&s_lpLogger->observers); + s_lpLogger = (LPLOGGER)malloc(sizeof(LOGGER)); + if (s_lpLogger) + { + memset(s_lpLogger, 0, sizeof(LOGGER)); + pntvector_init(LPLOGENTRY)(&s_lpLogger->entries); + pntvector_init(LPLOGEVENTOBSERVER)(&s_lpLogger->observers); + } + else { + Panitent_RaiseException(L"Memory allocation fault"); + } } return s_lpLogger; @@ -55,33 +113,51 @@ int LogRegisterObserver(LogObserverCallback callback, LPVOID userData) LPLOGGER lpLogger = GetLogger(); assert(lpLogger); - LPLOGEVENTOBSERVER lpLogObserver = - (LPLOGEVENTOBSERVER) calloc(1, sizeof(LOGEVENTOBSERVER)); - lpLogObserver->callback = callback; - lpLogObserver->userData = userData; - lpLogObserver->id = lpLogger->obsIDCounter; + LPLOGEVENTOBSERVER pObserver = (LPLOGEVENTOBSERVER)malloc(sizeof(LOGEVENTOBSERVER)); + if (pObserver) + { + memset(pObserver, 0, sizeof(LOGEVENTOBSERVER)); - pntvector_add(LPLOGEVENTOBSERVER)(&lpLogger->observers, lpLogObserver); + pObserver->callback = callback; + pObserver->userData = userData; + pObserver->id = lpLogger->obsIDCounter; - return lpLogger->obsIDCounter++; -} + pntvector_add(LPLOGEVENTOBSERVER)(&lpLogger->observers, pObserver); -void Logger_PushMessage(LPLOGGER lpLogger, LPLOGENTRY lpEntry) -{ - LPLOGENTRY pMemEntry = calloc(1, sizeof(LOGENTRY)); + return lpLogger->obsIDCounter++; + } + else { + Panitent_RaiseException(L"Memory allocation fault"); + } - memcpy(pMemEntry, lpEntry, sizeof(LOGENTRY)); - pntvector_add(LPLOGENTRY)(&lpLogger->entries, pMemEntry); + return 0; +} - LOGEVENT logEvent; - logEvent.iType = LOGALTEREVENT_ADD; - logEvent.extra = (int) pntvector_size(LPLOGENTRY)(&lpLogger->entries); +void Logger_BroadcastEvent(LPLOGGER pLogger, int iType, int extra, void* pData) +{ + LOGEVENT logEvent; + logEvent.iType = iType; + logEvent.userData = pData; + logEvent.extra = extra; + + for (size_t i = 0; i < pntvector_size(LPLOGEVENTOBSERVER)(&pLogger->observers); ++i) + { + logEvent.userData = pntvector_at(LPLOGEVENTOBSERVER)(&pLogger->observers, i)->userData; + LogObserverCallback callback = pntvector_at(LPLOGEVENTOBSERVER)(&pLogger->observers, i)->callback; + (*callback)(&logEvent); + } +} - for (size_t i = 0; i < pntvector_size(LPLOGEVENTOBSERVER)(&lpLogger->observers); ++i) { - logEvent.userData = pntvector_at(LPLOGEVENTOBSERVER)(&lpLogger->observers, i)->userData; - LogObserverCallback callback = pntvector_at(LPLOGEVENTOBSERVER)(&lpLogger->observers, i)->callback; - (*callback)(&logEvent); +void Logger_PushMessage(LPLOGGER lpLogger, LPLOGENTRY lpEntry) +{ + LPLOGENTRY pSharedLogEntry = (LPLOGENTRY)malloc(sizeof(LOGENTRY)); + if (pSharedLogEntry) + { + memcpy(pSharedLogEntry, lpEntry, sizeof(LOGENTRY)); + pntvector_add(LPLOGENTRY)(&lpLogger->entries, pSharedLogEntry); } + + Logger_BroadcastEvent(lpLogger, LOGALTEREVENT_ADD, (int)pntvector_size(LPLOGENTRY)(&lpLogger->entries), NULL); } void LogUnregisterObserver(int nID) @@ -104,8 +180,10 @@ LPLOGENTRY RetrieveLogEntry(int i) { LPLOGGER logger = GetLogger(); - if (i < 0 || i >= (int) pntvector_size(LPLOGENTRY)(&logger->entries)) - return NULL; + if (i < 0 || i >= (int)pntvector_size(LPLOGENTRY)(&logger->entries)) + { + return NULL; + } return pntvector_at(LPLOGENTRY)(&logger->entries, i); } @@ -117,3 +195,33 @@ int LogGetSize() return (int) pntvector_size(LPLOGENTRY)(&logger->entries); } + +void LogEntry_Destroy(LPLOGENTRY pLogEntry) +{ + pLogEntry->iType = 0; + memset(&pLogEntry->timestamp, 0, sizeof(SYSTEMTIME)); + free(pLogEntry->pszModule); + free(pLogEntry->pszMessage); +} + +void ClearLog() +{ + LPLOGGER pLogger = GetLogger(); + assert(pLogger); + + Logger_BroadcastEvent(pLogger, LOGALTEREVENT_CLEAR, 0, NULL); + + int nLogEntries = LogGetSize(); + for (unsigned int i = 0; i < nLogEntries; ++i) + { + LPLOGENTRY pSharedLogEntry = RetrieveLogEntry(i); + LogEntry_Destroy(pSharedLogEntry); + free(pSharedLogEntry); + } + + int nVecSize = pntvector_size(LPLOGENTRY)(&pLogger->entries); + for (int i = 0; i < nVecSize; ++i) + { + pntvector_remove(LPLOGENTRY)(&pLogger->entries, 0); + } +} diff --git a/src/log.h b/src/log.h index a374983..2b9dd21 100644 --- a/src/log.h +++ b/src/log.h @@ -12,13 +12,14 @@ typedef void (*LogObserverCallback)(LPLOGEVENT); typedef struct _tagLOGENTRY { SYSTEMTIME timestamp; int iType; - WCHAR szModule[80]; - WCHAR szMessage[80]; + PWSTR pszModule; + PWSTR pszMessage; } LOGENTRY, *LPLOGENTRY; enum { LOGALTEREVENT_ADD = 1, LOGALTEREVENT_DELETE, + LOGALTEREVENT_CLEAR }; enum { @@ -28,7 +29,9 @@ enum { LOGENTRY_TYPE_ERROR }; -void LogMessage(int, LPWSTR, LPWSTR); +void ClearLog(); +void LogMessage(int nLevel, PCWSTR pszModule, PCWSTR pszMessage); +void LogMessageF(int nLevel, PCWSTR pszModule, PCWSTR pszFormat, ...); int LogRegisterObserver(LogObserverCallback, LPVOID); void LogUnregisterObserver(int); int LogGetSize(); diff --git a/src/log_window.c b/src/log_window.c index b8fbcc7..ae21020 100644 --- a/src/log_window.c +++ b/src/log_window.c @@ -1,4 +1,5 @@ #include "precomp.h" + #include #include @@ -9,289 +10,341 @@ #define IDC_LOGVIEW 1701 typedef struct _tagLOGWINDOWDATA { - HWND hList; - int nIDLogObserver; -} LOGWINDOWDATA, *LPLOGWINDOWDATA; + HWND hList; + int nIDLogObserver; +} LOGWINDOWDATA, * LPLOGWINDOWDATA; + +static const WCHAR szClassName[] = L"Win32Class_LogWindow"; + +/* Private forward declarations */ +LogWindow* LogWindow_Create(Application* pApplication); +void LogWindow_Init(LogWindow* pLogWindow, Application* pApplication); -LRESULT CALLBACK LogWindow_WndProc(HWND, UINT, WPARAM, LPARAM); -BOOL LogWindow_OnCreate(HWND, LPCREATESTRUCT); -void LogWindow_OnSize(HWND, UINT, int, int); -LRESULT LogWindow_OnNotify(HWND, int, NMHDR *); -void LogWindow_OnDestroy(HWND); -void PopupError(DWORD); +void LogWindow_PreRegister(LPWNDCLASSEX lpwcex); +void LogWindow_PreCreate(LPCREATESTRUCT lpcs); -#define LOGWINDOW_WIDTH 360 -#define LOGWINDOW_HEIGHT 480 +BOOL LogWindow_OnCreate(LogWindow* pLogWindow, LPCREATESTRUCT lpcs); +void LogWindow_PostCreate(LogWindow* pLogWindow); +void LogWindow_OnSize(LogWindow* pLogWindow, UINT state, int x, int y); +LRESULT LogWindow_OnNotify(LogWindow* pLogWindow, int idCtrl, NMHDR* pnm); +void LogWindow_OnPaint(LogWindow* pLogWindow); +void LogWindow_OnDestroy(LogWindow* pLogWindow); +LRESULT LogWindow_UserProc(LogWindow* pLogWindow, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); -const WCHAR szLogWindowClassName[] = L"Win32Class_LogWindow"; +void LogWindow_LogEventObserverCallback(LPLOGEVENT lEvent); -BOOL LogWindow_Register(HINSTANCE hInstance) +LogWindow* LogWindow_Create(Application* pApplication) { - WNDCLASSEX wcex = { 0 }; - wcex.cbSize = sizeof(wcex); - wcex.style = CS_VREDRAW | CS_HREDRAW; - wcex.lpfnWndProc = (WNDPROC) LogWindow_WndProc; - wcex.cbWndExtra = sizeof(LPLOGWINDOWDATA); - wcex.hInstance = hInstance; - wcex.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_LOG)); - wcex.hCursor = LoadCursor(NULL, IDC_ARROW); - wcex.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); - wcex.lpszClassName = szLogWindowClassName; - - return RegisterClassEx(&wcex); + LogWindow* pLogWindow = (LogWindow*)malloc(sizeof(LogWindow)); + + if (pLogWindow) + { + memset(pLogWindow, 0, sizeof(LogWindow)); + LogWindow_Init(pLogWindow, pApplication); + } } -void LogWindow_LogEventObserverCallback(LPLOGEVENT lEvent) +void LogWindow_Init(LogWindow* pLogWindow, Application* pApplication) { - switch (lEvent->iType) - { - case LOGALTEREVENT_ADD: - { - LPLOGWINDOWDATA wndData = (LPLOGWINDOWDATA) lEvent->userData; - assert(wndData); - - ListView_SetItemCount(wndData->hList, lEvent->extra); - } - break; - } + Window_Init(&pLogWindow->base, pApplication); + + pLogWindow->base.OnCreate = (FnWindowOnCreate)LogWindow_OnCreate; + pLogWindow->base.OnSize = (FnWindowOnSize)LogWindow_OnSize; + pLogWindow->base.PostCreate = (FnWindowPostCreate)LogWindow_PostCreate; + pLogWindow->base.OnDestroy = (FnWindowOnDestroy)LogWindow_OnDestroy; + pLogWindow->base.OnPaint = (FnWindowOnPaint)LogWindow_OnPaint; + + pLogWindow->base.PreRegister = (FnWindowPreRegister)LogWindow_PreRegister; + pLogWindow->base.PreCreate = (FnWindowPreCreate)LogWindow_PreCreate; + + pLogWindow->base.UserProc = (FnWindowUserProc)LogWindow_UserProc; } -HWND LogWindow_Create(HWND hParent) +void LogWindow_PreRegister(LPWNDCLASSEX lpwcex) { - RECT rc = { 0 }; - rc.right = LOGWINDOW_WIDTH; - rc.bottom = LOGWINDOW_HEIGHT; - AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW, FALSE, 0); - - HWND hLogWindow = CreateWindowEx(0, szLogWindowClassName, - L"Log", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, - rc.right - rc.left, - rc.bottom - rc.top, - hParent, - NULL, - GetModuleHandle(NULL), NULL); - assert(hLogWindow); - - ShowWindow(hLogWindow, SW_SHOWNORMAL); - UpdateWindow(hLogWindow); - - return hLogWindow; + lpwcex->style = CS_HREDRAW | CS_VREDRAW; + lpwcex->hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_LOG)); + lpwcex->hCursor = LoadCursor(NULL, IDC_ARROW); + lpwcex->hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + lpwcex->lpszClassName = szClassName; } -LRESULT CALLBACK LogWindow_WndProc(HWND hWnd, UINT message, WPARAM wParam, - LPARAM lParam) +void LogWindow_PreCreate(LPCREATESTRUCT lpcs) { - switch (message) - { - HANDLE_MSG(hWnd, WM_CREATE, LogWindow_OnCreate); - HANDLE_MSG(hWnd, WM_SIZE, LogWindow_OnSize); - HANDLE_MSG(hWnd, WM_DESTROY, LogWindow_OnDestroy); - HANDLE_MSG(hWnd, WM_NOTIFY, LogWindow_OnNotify); - } - - return DefWindowProc(hWnd, message, wParam, lParam); + lpcs->dwExStyle = 0; + lpcs->lpszClass = szClassName; + lpcs->lpszName = L"Log"; + lpcs->style = WS_OVERLAPPEDWINDOW; + lpcs->x = CW_USEDEFAULT; + lpcs->y = CW_USEDEFAULT; + lpcs->cx = 640; + lpcs->cy = 480; } -BOOL LogWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpcs) +BOOL LogWindow_OnCreate(LogWindow* pLogWindow, LPCREATESTRUCT lpcs) { - UNREFERENCED_PARAMETER(lpcs); - LPLOGWINDOWDATA wndData = (LPLOGWINDOWDATA) calloc(1, sizeof(LOGWINDOWDATA)); - assert(wndData); - SetWindowLongPtr(hWnd, 0, (LONG_PTR) wndData); +} - DWORD dwError; +HIMAGELIST g_hImageList = NULL; - HWND hList = CreateWindowEx(0, WC_LISTVIEW, L"", - WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_OWNERDATA, - 0, 0, 0, 0, - hWnd, (HMENU) IDC_LOGVIEW, GetModuleHandle(NULL), NULL); - dwError = GetLastError(); +#define numButtons 3 - if (!hList) { - PopupError(dwError); - assert(hList); - } +#define IDM_NEW 1433 +#define IDM_OPEN 1434 +#define IDM_SAVE 1435 - ListView_SetExtendedListViewStyle(hList, - LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | - LVS_EX_HEADERDRAGDROP | LVS_EX_JUSTIFYCOLUMNS); - SetWindowTheme(hList, L"Explorer", NULL); +HWND CreateSimpleToolbar(HWND hWndParent) +{ + const int ImageListID = 0; + const int bitmapSize = 16; + + const DWORD buttonStyles = BTNS_AUTOSIZE; - WCHAR szText[256]; - LVCOLUMN lvc; - int iCol = 0; + // Create the toolbar + HWND hWndToolbar = CreateWindowEx(0, TOOLBARCLASSNAME, NULL, WS_CHILD | TBSTYLE_WRAPABLE, 0, 0, 0, 0, hWndParent, NULL, GetModuleHandle(NULL), NULL); - lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + if (!hWndToolbar) + { + return NULL; + } - lvc.pszText = szText; - lvc.fmt = LVCFMT_LEFT; + // Create the image list. + g_hImageList = ImageList_Create(bitmapSize, bitmapSize, ILC_COLOR16 | ILC_MASK, numButtons, 0); - lvc.cx = 100; - lvc.iSubItem = iCol; - StringCchCopy(szText, 256, L"Time"); - ListView_InsertColumn(hList, iCol++, &lvc); + // Set the image list. + SendMessage(hWndToolbar, TB_SETIMAGELIST, (WPARAM)ImageListID, (LPARAM)g_hImageList); - lvc.cx = 70; - lvc.iSubItem = iCol; - StringCchCopy(szText, 256, L"Type"); - ListView_InsertColumn(hList, iCol++, &lvc); + // Load the button images. + SendMessage(hWndToolbar, TB_LOADIMAGES, (WPARAM)IDB_STD_SMALL_COLOR, (LPARAM)HINST_COMMCTRL); - lvc.iSubItem = iCol; - StringCchCopy(szText, 256, L"Module"); - ListView_InsertColumn(hList, iCol++, &lvc); + // Initialize button info + // IDM_NEW, IDM_OPEN, and IDM_SAVE are application-defined command constants - lvc.cx = 200; - lvc.iSubItem = iCol; - StringCchCopy(szText, 256, L"Message"); - ListView_InsertColumn(hList, iCol++, &lvc); - wndData->hList = hList; + TBBUTTON tbButtons[numButtons] = + { + { MAKELONG(STD_FILENEW, ImageListID), IDM_NEW, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)L"New" }, + { MAKELONG(STD_FILEOPEN, ImageListID), IDM_OPEN, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)L"Open"}, + { MAKELONG(STD_FILESAVE, ImageListID), IDM_SAVE, 0, buttonStyles, {0}, 0, (INT_PTR)L"Save"} + }; - wndData->nIDLogObserver = - LogRegisterObserver(LogWindow_LogEventObserverCallback, (LPVOID)wndData); + // Add buttons. + SendMessage(hWndToolbar, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0); + SendMessage(hWndToolbar, TB_ADDBUTTONS, (WPARAM)numButtons, (LPARAM)&tbButtons); - int nEntries = LogGetSize(); - ListView_SetItemCount(hList, nEntries); + // Resize the toolbar, and the show it. + SendMessage(hWndToolbar, TB_AUTOSIZE, 0, 0); + ShowWindow(hWndToolbar, TRUE); - return TRUE; + return hWndToolbar; } -void LogWindow_OnSize(HWND hWnd, UINT state, int cx, int cy) +void LogWindow_PostCreate(LogWindow* pLogWindow) { - UNREFERENCED_PARAMETER(state); - UNREFERENCED_PARAMETER(cx); - UNREFERENCED_PARAMETER(cy); + HWND hWnd = Window_GetHWND((Window*)pLogWindow); + + HWND hToolbar = CreateSimpleToolbar(hWnd); + pLogWindow->hToolbar = hToolbar; - LPLOGWINDOWDATA wndData = (LPLOGWINDOWDATA) GetWindowLongPtr(hWnd, 0); - assert(wndData); + DWORD dwError; - RECT rcClient = { 0 }; - GetClientRect(hWnd, &rcClient); + HWND hList = CreateWindowEx(0, WC_LISTVIEW, L"", WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_OWNERDATA, 0, 0, 0, 0, hWnd, (HMENU)IDC_LOGVIEW, GetModuleHandle(NULL), NULL); + dwError = GetLastError(); + + if (!hList) { + assert(hList); + } - SetWindowPos(wndData->hList, NULL, 0, 0, - rcClient.right - rcClient.left, - rcClient.bottom - rcClient.top, - SWP_NOMOVE); + ListView_SetExtendedListViewStyle(hList, LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_JUSTIFYCOLUMNS); + SetWindowTheme(hList, L"Explorer", NULL); + + WCHAR szText[256]; + LVCOLUMN lvc; + int iCol = 0; + + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + + lvc.pszText = szText; + lvc.fmt = LVCFMT_LEFT; + + lvc.cx = 100; + lvc.iSubItem = iCol; + StringCchCopy(szText, 256, L"Time"); + ListView_InsertColumn(hList, iCol++, &lvc); + + lvc.cx = 70; + lvc.iSubItem = iCol; + StringCchCopy(szText, 256, L"Type"); + ListView_InsertColumn(hList, iCol++, &lvc); + + lvc.iSubItem = iCol; + StringCchCopy(szText, 256, L"Module"); + ListView_InsertColumn(hList, iCol++, &lvc); + + lvc.cx = 200; + lvc.iSubItem = iCol; + StringCchCopy(szText, 256, L"Message"); + ListView_InsertColumn(hList, iCol++, &lvc); + + pLogWindow->hList = hList; + + pLogWindow->nIDLogObserver = LogRegisterObserver(LogWindow_LogEventObserverCallback, (LPVOID)pLogWindow); + + int nEntries = LogGetSize(); + ListView_SetItemCount(hList, nEntries); } -void PopupError(DWORD dwError) +void LogWindow_OnSize(LogWindow* pLogWindow, UINT state, int x, int y) { - DWORD dwFormatError; - WCHAR errMsg[256] = { 0 }; - const DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; - dwFormatError = FormatMessage(dwFlags, 0, dwError, 0, errMsg, 256, NULL); - if (!dwFormatError) { - /* Ooops, we got a serious problem */ - StringCchPrintf(errMsg, 256, L"Unbelievable. Can't format " - L"error message in human-readable format. " - L"FormatMessage error code: %d\r\n\r\n" - L"Originally reported error code: %d\r\n"); - } - - MessageBox(NULL, errMsg, NULL, MB_OK | MB_ICONERROR); + RECT rcClient = { 0 }; + Window_GetClientRect(pLogWindow, &rcClient); + + RECT rcToolbar = { 0 }; + GetWindowRect(pLogWindow->hToolbar, &rcToolbar); + + int toolbarHeight = rcToolbar.bottom - rcToolbar.top; + + SetWindowPos(pLogWindow->hToolbar, NULL, 0, 0, rcClient.right - rcClient.left, toolbarHeight, 0); + SetWindowPos(pLogWindow->hList, NULL, 0, toolbarHeight, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top - toolbarHeight, 0); } -LRESULT LogWindow_OnNotify(HWND hwnd, int idCtrl, NMHDR *pnm) +LRESULT LogWindow_OnNotify(LogWindow* pLogWindow, int idCtrl, NMHDR* pnm) { - UNREFERENCED_PARAMETER(hwnd); - - if (idCtrl == IDC_LOGVIEW) { - switch (pnm->code) + if (idCtrl == IDC_LOGVIEW) { - case LVN_GETDISPINFO: + switch (pnm->code) { - LOGENTRY logEntry; - NMLVDISPINFO *plvdi = (NMLVDISPINFO *) pnm; /* ??? */ + case LVN_GETDISPINFO: + { + LOGENTRY logEntry; + NMLVDISPINFO* plvdi = (NMLVDISPINFO*)pnm; /* ??? */ - if (plvdi->item.iItem == -1) { - OutputDebugString(L"LVOWNER: Request for -1 item?\n"); - DebugBreak(); - } + if (plvdi->item.iItem == -1) + { + OutputDebugString(L"LVOWNER: Request for -1 item?\n"); + DebugBreak(); + } - LPLOGENTRY pEntry; - pEntry = RetrieveLogEntry(plvdi->item.iItem); - if (!pEntry) { - assert(FALSE); - } + LPLOGENTRY pEntry; + pEntry = RetrieveLogEntry(plvdi->item.iItem); + if (!pEntry) + { + assert(FALSE); + } - memcpy(&logEntry, pEntry, sizeof(LOGENTRY)); + memcpy(&logEntry, pEntry, sizeof(LOGENTRY)); - if (plvdi->item.mask & LVIF_TEXT) { - switch (plvdi->item.iSubItem) { - case 0: + if (plvdi->item.mask & LVIF_TEXT) + { + switch (plvdi->item.iSubItem) + { + case 0: { - DWORD dwError; - int cchCount; - WCHAR szTimeString[256] = { 0 }; - - cchCount = GetTimeFormatEx(LOCALE_NAME_INVARIANT, - TIME_FORCE24HOURFORMAT, - &logEntry.timestamp, - NULL, - szTimeString, - 256); - - if (!cchCount) { - dwError = GetLastError(); - assert(FALSE); - PopupError(dwError); - } - - StringCchCopy(plvdi->item.pszText, 80, szTimeString); + DWORD dwError; + int cchCount; + WCHAR szTimeString[256] = { 0 }; + + cchCount = GetTimeFormatEx(LOCALE_NAME_INVARIANT, + TIME_FORCE24HOURFORMAT, + &logEntry.timestamp, + NULL, + szTimeString, + 256); + + if (!cchCount) + { + dwError = GetLastError(); + assert(FALSE); + } + + StringCchCopy(plvdi->item.pszText, 80, szTimeString); } break; - case 1: + case 1: { - switch (logEntry.iType) { - case LOGENTRY_TYPE_DEBUG: - StringCchCopy(plvdi->item.pszText, 80, L"Debug"); - break; - case LOGENTRY_TYPE_INFO: - StringCchCopy(plvdi->item.pszText, 80, L"Info"); - break; + switch (logEntry.iType) + { + case LOGENTRY_TYPE_DEBUG: + StringCchCopy(plvdi->item.pszText, 80, L"Debug"); + break; + + case LOGENTRY_TYPE_INFO: + StringCchCopy(plvdi->item.pszText, 80, L"Info"); + break; + case LOGENTRY_TYPE_WARNING: - StringCchCopy(plvdi->item.pszText, 80, L"Warning"); - break; + StringCchCopy(plvdi->item.pszText, 80, L"Warning"); + break; + case LOGENTRY_TYPE_ERROR: - StringCchCopy(plvdi->item.pszText, 80, L"Error"); - break; - } + StringCchCopy(plvdi->item.pszText, 80, L"Error"); + break; + } } break; - case 2: - StringCchCopy(plvdi->item.pszText, 80, logEntry.szModule); - break; + case 2: + StringCchCopy(plvdi->item.pszText, 80, logEntry.pszModule); + break; - case 3: - StringCchCopy(plvdi->item.pszText, 80, logEntry.szMessage); - break; + case 3: + StringCchCopy(plvdi->item.pszText, 80, logEntry.pszMessage); + break; - default: - break; + default: + break; + } } - } - return FALSE; + return FALSE; } break; - case LVN_ODCACHEHINT: - // NMLVCACHEHINT *pCacheHint = (NMLVCACHEHINT *) pnm; - break; + case LVN_ODCACHEHINT: + // NMLVCACHEHINT *pCacheHint = (NMLVCACHEHINT *) pnm; + break; + } } - } - return FALSE; + return FALSE; +} + +void LogWindow_OnPaint(LogWindow* pLogWindow) +{ + +} + +void LogWindow_OnDestroy(LogWindow* pLogWindow) +{ + LogUnregisterObserver(pLogWindow->nIDLogObserver); } -void LogWindow_OnDestroy(HWND hWnd) +LRESULT LogWindow_UserProc(LogWindow* pLogWindow, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { - LPLOGWINDOWDATA wndData = (LPLOGWINDOWDATA) GetWindowLongPtr(hWnd, 0); - assert(wndData); + switch (message) + { + case WM_NOTIFY: + { + return LogWindow_OnNotify(pLogWindow, (int)(wParam), (NMHDR*)(lParam)); + } + break; + } + + return Window_UserProcDefault(pLogWindow, hWnd, message, wParam, lParam); +} - LogUnregisterObserver(wndData->nIDLogObserver); +void LogWindow_LogEventObserverCallback(LPLOGEVENT lEvent) +{ + switch (lEvent->iType) + { + case LOGALTEREVENT_ADD: + { + LogWindow* pLogWindow = (LogWindow*)lEvent->userData; + assert(pLogWindow); - free(wndData); + ListView_SetItemCount(pLogWindow->hList, lEvent->extra); + } + break; + } } diff --git a/src/log_window.h b/src/log_window.h index 0f955c5..42bdb15 100644 --- a/src/log_window.h +++ b/src/log_window.h @@ -1,7 +1,18 @@ #ifndef PANITENT_LOG_WINDOW_H #define PANITENT_LOG_WINDOW_H -BOOL LogWindow_Register(HINSTANCE); -HWND LogWindow_Create(HWND); +#include"win32/window.h" + +typedef struct LogWindow LogWindow; + +struct LogWindow { + Window base; + + HWND hToolbar; + HWND hList; + int nIDLogObserver; +}; + +LogWindow* LogWindow_Create(Application* pApplication); #endif /* PANITENT_LOG_WINDOW_H */ diff --git a/src/new.c b/src/new.c index 4521361..b18dd94 100644 --- a/src/new.c +++ b/src/new.c @@ -18,6 +18,7 @@ #include "panitent.h" #include "checker.h" #include "util.h" +#include "sharing/activitysharingmanager.h" #include "win32/util.h" @@ -82,7 +83,7 @@ LRESULT CALLBACK NewFileDialogWndProc(HWND hwnd, iWidth = StrToInt(szWidth); iHeight = StrToInt(szHeight); - printf("[NewFile] width: %d, height: %d\n", iWidth, iHeight); + printf("[NewFile] width: %d, AVLNode_Height: %d\n", iWidth, iHeight); DestroyWindow(hwnd); Document* pDocument = Document_New(iWidth, iHeight); @@ -273,6 +274,7 @@ INT_PTR CALLBACK NewDocumentDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, case WM_INITDIALOG: { NewDocumentDlgData* data = (NewDocumentDlgData*)malloc(sizeof(NewDocumentDlgData)); + memset(data, 0, sizeof(NewDocumentDlgData)); if (!data) { return TRUE; @@ -554,24 +556,22 @@ INT_PTR CALLBACK NewDocumentDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, void NewFileDialog(HWND hwnd) { -#ifdef HAS_DISCORDSDK - Discord_SetActivityStatus(g_panitent.discord, L"Creating a new document"); -#endif /* HAS_DISCORDSDK */ + Panitent_SetActivityStatus(Panitent_GetApp(), L"Creating a new document"); #ifdef LEGACYDIALOGNEW RegisterNewFileDialog(); RECT rc = { 0 }; - rc.right = 210; + rc.pRight = 210; rc.bottom = 170; AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); CreateWindow(L"NewFileDialogClass", L"New", WS_VISIBLE | WS_OVERLAPPEDWINDOW, - (GetSystemMetrics(SM_CXSCREEN) - rc.right - rc.left) / 2, + (GetSystemMetrics(SM_CXSCREEN) - rc.pRight - rc.pLeft) / 2, (GetSystemMetrics(SM_CYSCREEN) - rc.bottom - rc.top) / 2, - rc.right - rc.left, + rc.pRight - rc.pLeft, rc.bottom - rc.top, hwnd, NULL, diff --git a/src/option_bar.c b/src/option_bar.c index eaf8242..4d06f96 100644 --- a/src/option_bar.c +++ b/src/option_bar.c @@ -62,8 +62,7 @@ void BrushSel_OnPaint(HWND hwnd) uint32_t* buffer = NULL; HDC hSampleDC = CreateCompatibleDC(hdc); - HBITMAP hBitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, - (LPVOID*)&buffer, NULL, 0); + HBITMAP hBitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (LPVOID*)&buffer, NULL, 0); assert(hBitmap != NULL); assert(buffer != NULL); @@ -357,6 +356,7 @@ INT_PTR CALLBACK BrushProp_DlgProc(HWND hwndDlg, UINT message, WPARAM wParam, case WM_INITDIALOG: { BrushDlgData* data = malloc(sizeof(BrushDlgData)); + memset(data, 0, sizeof(BrushDlgData)); if (!data) return TRUE; @@ -402,7 +402,7 @@ INT_PTR CALLBACK BrushProp_DlgProc(HWND hwndDlg, UINT message, WPARAM wParam, /* * Preview would be drawn here but freezes for an unknown reason - * Perhaps GDI is cloning it internally if there any pixels in buffer + * Perhaps GDI is cloning it internally if there any pixels in pszBuffer * I have read something like this on MSDN */ SendMessage(GetDlgItem(hwndDlg, IDC_BRUSHPREVIEW), STM_SETIMAGE, @@ -639,7 +639,8 @@ void OptionBarWindow_Init(OptionBarWindow* pOptionBarWindow, struct Application* OptionBarWindow* OptionBarWindow_Create(struct Application* app) { - OptionBarWindow* pOptionBarWindow = calloc(1, sizeof(OptionBarWindow)); + OptionBarWindow* pOptionBarWindow = (OptionBarWindow*)malloc(sizeof(OptionBarWindow)); + memset(pOptionBarWindow, 0, sizeof(OptionBarWindow)); if (pOptionBarWindow) { diff --git a/src/palette.c b/src/palette.c index b9f3046..b62bf88 100644 --- a/src/palette.c +++ b/src/palette.c @@ -92,7 +92,8 @@ void Palette_InitDefault(Palette*); Palette* Palette_Create() { - Palette* palette = calloc(1, sizeof(Palette)); + Palette* palette = (Palette*)malloc(sizeof(Palette)); + memset(palette, 0, sizeof(Palette)); if (palette) { kv_init(*palette); diff --git a/src/palette_window.c b/src/palette_window.c index 5853417..255b8d6 100644 --- a/src/palette_window.c +++ b/src/palette_window.c @@ -15,19 +15,19 @@ static const WCHAR szClassName[] = L"PaletteWindowClass"; static int swatch_margin = 2; /* Private forward declarations */ -PaletteWindow* PaletteWindow_Create(struct Application*, Palette*); -void PaletteWindow_Init(PaletteWindow*, struct Application*, Palette*); +PaletteWindow* PaletteWindow_Create(Application* pApplication, Palette* pPalette); +void PaletteWindow_Init(PaletteWindow* pPaletteWindow, Application* pApplication, Palette* pPalette); -void PaletteWindow_PreRegister(LPWNDCLASSEX); -void PaletteWindow_PreCreate(LPCREATESTRUCT); +void PaletteWindow_PreRegister(LPWNDCLASSEX lpwcex); +void PaletteWindow_PreCreate(LPCREATESTRUCT lpcs); -BOOL PaletteWindow_OnCreate(PaletteWindow*, LPCREATESTRUCT); -void PaletteWindow_OnPaint(PaletteWindow*); -void PaletteWindow_OnLButtonUp(PaletteWindow*, int, int); -void PaletteWindow_OnRButtonUp(PaletteWindow*, int, int); -void PaletteWindow_OnContextMenu(PaletteWindow*, int, int); -void PaletteWindow_OnDestroy(PaletteWindow*); -LRESULT CALLBACK PaletteWindow_UserProc(PaletteWindow*, HWND hWnd, UINT, WPARAM, LPARAM); +BOOL PaletteWindow_OnCreate(PaletteWindow* pPaletteWindow, LPCREATESTRUCT lpcs); +void PaletteWindow_OnPaint(PaletteWindow* pPaletteWindow); +void PaletteWindow_OnLButtonUp(PaletteWindow* pPaletteWindow, int x, int y); +void PaletteWindow_OnRButtonUp(PaletteWindow* pPaletteWindow, int x, int y); +void PaletteWindow_OnContextMenu(PaletteWindow* pPaletteWindow, int x, int y); +void PaletteWindow_OnDestroy(PaletteWindow* pPaletteWindow); +LRESULT CALLBACK PaletteWindow_UserProc(PaletteWindow* pPaletteWindow, HWND hWnd, UINT, WPARAM, LPARAM); void PaletteWindow_DrawSwatch(PaletteWindow*, HDC, int, int, uint32_t); int PaletteWindow_PosToSwatchIndex(PaletteWindow*, int, int); @@ -37,230 +37,231 @@ INT_PTR CALLBACK PaletteSettingsDlgProc(HWND hwndDlg, UINT message, WPARAM wPara PaletteWindow* PaletteWindow_Create(struct Application* app, Palette* palette) { - PaletteWindow* window = calloc(1, sizeof(PaletteWindow)); + PaletteWindow* pPaletteWindow = (PaletteWindow*)malloc(sizeof(PaletteWindow)); - if (window) - { - PaletteWindow_Init(window, app, palette); - } + if (pPaletteWindow) + { + memset(pPaletteWindow, 0, sizeof(PaletteWindow)); + PaletteWindow_Init(pPaletteWindow, app, palette); + } - return window; + return pPaletteWindow; } void PaletteWindow_Init(PaletteWindow* pPaletteWindow, struct Application* app, Palette* palette) { - Window_Init(&pPaletteWindow->base, app); + Window_Init(&pPaletteWindow->base, app); - pPaletteWindow->base.szClassName = szClassName; + pPaletteWindow->base.szClassName = szClassName; - pPaletteWindow->base.OnCreate = (FnWindowOnCreate)PaletteWindow_OnCreate; - pPaletteWindow->base.OnDestroy = (FnWindowOnDestroy)PaletteWindow_OnDestroy; - pPaletteWindow->base.OnPaint = (FnWindowOnPaint)PaletteWindow_OnPaint; - pPaletteWindow->base.PreRegister = (FnWindowPreRegister)PaletteWindow_PreRegister; - pPaletteWindow->base.PreCreate = (FnWindowPreCreate)PaletteWindow_PreCreate; - pPaletteWindow->base.UserProc = (FnWindowUserProc)PaletteWindow_UserProc; + pPaletteWindow->base.OnCreate = (FnWindowOnCreate)PaletteWindow_OnCreate; + pPaletteWindow->base.OnDestroy = (FnWindowOnDestroy)PaletteWindow_OnDestroy; + pPaletteWindow->base.OnPaint = (FnWindowOnPaint)PaletteWindow_OnPaint; + pPaletteWindow->base.PreRegister = (FnWindowPreRegister)PaletteWindow_PreRegister; + pPaletteWindow->base.PreCreate = (FnWindowPreCreate)PaletteWindow_PreCreate; + pPaletteWindow->base.UserProc = (FnWindowUserProc)PaletteWindow_UserProc; - pPaletteWindow->palette = palette; + pPaletteWindow->palette = palette; } void PaletteWindow_PreRegister(LPWNDCLASSEX lpwcex) { - lpwcex->style = CS_HREDRAW | CS_VREDRAW; - lpwcex->hCursor = LoadCursor(NULL, IDC_ARROW); - lpwcex->hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); - lpwcex->lpszClassName = szClassName; + lpwcex->style = CS_HREDRAW | CS_VREDRAW; + lpwcex->hCursor = LoadCursor(NULL, IDC_ARROW); + lpwcex->hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + lpwcex->lpszClassName = szClassName; } BOOL PaletteWindow_OnCreate(PaletteWindow* window, LPCREATESTRUCT lpcs) { - UNREFERENCED_PARAMETER(lpcs); + UNREFERENCED_PARAMETER(lpcs); - CheckerConfig checkerCfg = { 0 }; - checkerCfg.tileSize = 8; - checkerCfg.color[0] = 0x00CCCCCC; - checkerCfg.color[1] = 0x00FFFFFF; + CheckerConfig checkerCfg = { 0 }; + checkerCfg.tileSize = 8; + checkerCfg.color[0] = 0x00CCCCCC; + checkerCfg.color[1] = 0x00FFFFFF; - HDC hdc = GetDC(window->base.hWnd); - window->hbrChecker = CreateChecker(&checkerCfg, hdc); - ReleaseDC(window->base.hWnd, hdc); + HDC hdc = GetDC(window->base.hWnd); + window->hbrChecker = CreateChecker(&checkerCfg, hdc); + ReleaseDC(window->base.hWnd, hdc); - // RegisterColorObserver(Palette_ColorChangeObserver, (void*)window->base.hWnd); + // RegisterColorObserver(Palette_ColorChangeObserver, (void*)window->base.hWnd); } void PaletteWindow_OnPaint(PaletteWindow* pPaletteWindow) { - HWND hwnd = pPaletteWindow->base.hWnd; - PAINTSTRUCT ps; - HDC hdc; - - /* Get the size of swatch based on settings */ - int swatch_size = g_paletteSettings.swatchSize; - - /* Calculate the number of swatches that can fit in a row */ - RECT rc = { 0 }; - GetClientRect(pPaletteWindow->base.hWnd, &rc); - int width_indices = (rc.right - 20) / (swatch_size + swatch_margin); - - /* Ensure there is at least one swatch per row*/ - if (width_indices < 1) - { - width_indices = 1; - } - - /* Begin painting the window */ - hdc = BeginPaint(hwnd, &ps); - - /* Draw the foregroud and background color swatches */ - PaletteWindow_DrawSwatch(pPaletteWindow, hdc, 10, 10, (COLORREF)ABGRToARGB(g_color_context.bg_color)); - PaletteWindow_DrawSwatch(pPaletteWindow, hdc, 16, 16, (COLORREF)ABGRToARGB(g_color_context.fg_color)); - - /* Draw the palette swatches */ - for (size_t i = 0; i < Palette_GetSize(pPaletteWindow->palette); ++i) - { - PaletteWindow_DrawSwatch(pPaletteWindow, hdc, - 10 + ((int)i % width_indices) * (swatch_size + swatch_margin), - 40 + ((int)i / width_indices) * (swatch_size + swatch_margin), - Palette_At(pPaletteWindow->palette, i)); - } - - /* End painting the window */ - EndPaint(hwnd, &ps); + HWND hwnd = pPaletteWindow->base.hWnd; + PAINTSTRUCT ps; + HDC hdc; + + /* Get the size of swatch based on settings */ + int swatch_size = g_paletteSettings.swatchSize; + + /* Calculate the number of swatches that can fit in a row */ + RECT rc = { 0 }; + GetClientRect(pPaletteWindow->base.hWnd, &rc); + int width_indices = (rc.right - 20) / (swatch_size + swatch_margin); + + /* Ensure there is at least one swatch per row*/ + if (width_indices < 1) + { + width_indices = 1; + } + + /* Begin painting the window */ + hdc = BeginPaint(hwnd, &ps); + + /* Draw the foregroud and background color swatches */ + PaletteWindow_DrawSwatch(pPaletteWindow, hdc, 10, 10, (COLORREF)ABGRToARGB(g_color_context.bg_color)); + PaletteWindow_DrawSwatch(pPaletteWindow, hdc, 16, 16, (COLORREF)ABGRToARGB(g_color_context.fg_color)); + + /* Draw the palette swatches */ + for (size_t i = 0; i < Palette_GetSize(pPaletteWindow->palette); ++i) + { + PaletteWindow_DrawSwatch(pPaletteWindow, hdc, + 10 + ((int)i % width_indices) * (swatch_size + swatch_margin), + 40 + ((int)i / width_indices) * (swatch_size + swatch_margin), + Palette_At(pPaletteWindow->palette, i)); + } + + /* End painting the window */ + EndPaint(hwnd, &ps); } void PaletteWindow_OnLButtonUp(PaletteWindow* window, int x, int y) { - int swatch_size = g_paletteSettings.swatchSize; + int swatch_size = g_paletteSettings.swatchSize; - RECT rc = { 0 }; - GetClientRect(window->base.hWnd, &rc); - int swatch_count = (int)Palette_GetSize(window->palette); - int width_indices = (rc.right - 20) / (swatch_size + swatch_margin); - int height_indices = swatch_count / width_indices + 1; + RECT rc = { 0 }; + GetClientRect(window->base.hWnd, &rc); + int swatch_count = (int)Palette_GetSize(window->palette); + int width_indices = (rc.right - 20) / (swatch_size + swatch_margin); + int height_indices = swatch_count / width_indices + 1; - if (((x - 10) < ((swatch_size + swatch_margin) * width_indices)) && - ((y - 40) < ((swatch_size + swatch_margin) * height_indices))) { - int index = PaletteWindow_PosToSwatchIndex(window, x, y); - if (index < 0 || index >= swatch_count) - return; + if (((x - 10) < ((swatch_size + swatch_margin) * width_indices)) && + ((y - 40) < ((swatch_size + swatch_margin) * height_indices))) { + int index = PaletteWindow_PosToSwatchIndex(window, x, y); + if (index < 0 || index >= swatch_count) + return; - g_color_context.fg_color = ABGRToARGB(Palette_At(window->palette, index)); + g_color_context.fg_color = ABGRToARGB(Palette_At(window->palette, index)); - RECT rcInvalidate = { 10, 10, 34, 34 }; - InvalidateRect(window->base.hWnd, &rcInvalidate, FALSE); - } + RECT rcInvalidate = { 10, 10, 34, 34 }; + InvalidateRect(window->base.hWnd, &rcInvalidate, FALSE); + } } void PaletteWindow_OnRButtonUp(PaletteWindow* pPaletteWindow, int x, int y) { - int swatch_size = g_paletteSettings.swatchSize; - Palette* palette = ((struct PanitentApplication*)(pPaletteWindow->base.app))->palette; - - RECT rc = { 0 }; - GetClientRect(pPaletteWindow->base.hWnd, &rc); - int swatch_count = (int)Palette_GetSize(palette); - int width_indices = (rc.right - 20) / (swatch_size + swatch_margin); - int height_indices = swatch_count / width_indices + 1; - - if (((x - 10) < ((swatch_size + swatch_margin) * width_indices)) && - ((y - 40) < ((swatch_size + swatch_margin) * height_indices))) { - int index = PaletteWindow_PosToSwatchIndex(pPaletteWindow, x, y); - if (index < 0 || index >= swatch_count) - return; - - g_color_context.bg_color = ABGRToARGB(Palette_At(palette, index)); - - RECT rcInvalidate = { 40, 10, 64, 34 }; - InvalidateRect(pPaletteWindow->base.hWnd, &rcInvalidate, FALSE); - } + int swatch_size = g_paletteSettings.swatchSize; + Palette* palette = ((struct PanitentApplication*)(pPaletteWindow->base.app))->palette; + + RECT rc = { 0 }; + GetClientRect(pPaletteWindow->base.hWnd, &rc); + int swatch_count = (int)Palette_GetSize(palette); + int width_indices = (rc.right - 20) / (swatch_size + swatch_margin); + int height_indices = swatch_count / width_indices + 1; + + if (((x - 10) < ((swatch_size + swatch_margin) * width_indices)) && + ((y - 40) < ((swatch_size + swatch_margin) * height_indices))) { + int index = PaletteWindow_PosToSwatchIndex(pPaletteWindow, x, y); + if (index < 0 || index >= swatch_count) + return; + + g_color_context.bg_color = ABGRToARGB(Palette_At(palette, index)); + + RECT rcInvalidate = { 40, 10, 64, 34 }; + InvalidateRect(pPaletteWindow->base.hWnd, &rcInvalidate, FALSE); + } } void PaletteWindow_OnContextMenu(PaletteWindow* window, int x, int y) { - x = x < 0 ? 0 : x; - y = y < 0 ? 0 : y; + x = x < 0 ? 0 : x; + y = y < 0 ? 0 : y; - HMENU hMenu = CreatePopupMenu(); - InsertMenu(hMenu, 0, MF_BYPOSITION | MF_STRING, IDM_PALETTESETTINGS, - L"Settings"); - TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN, x, y, 0, window->base.hWnd, NULL); + HMENU hMenu = CreatePopupMenu(); + InsertMenu(hMenu, 0, MF_BYPOSITION | MF_STRING, IDM_PALETTESETTINGS, + L"Settings"); + TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN, x, y, 0, window->base.hWnd, NULL); } void PaletteWindow_OnCommand(PaletteWindow* window, WPARAM wParam, LPARAM lParam) { - UNREFERENCED_PARAMETER(lParam); + UNREFERENCED_PARAMETER(lParam); - if (LOWORD(wParam) == IDM_PALETTESETTINGS) - { - DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_PALETTESETTINGS), - window->base.hWnd, (DLGPROC)PaletteSettingsDlgProc); - } + if (LOWORD(wParam) == IDM_PALETTESETTINGS) + { + DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_PALETTESETTINGS), + window->base.hWnd, (DLGPROC)PaletteSettingsDlgProc); + } } void PaletteWindow_OnDestroy(PaletteWindow* window) { - // RemoveColorObserver(Palette_ColorChangeObserver, (void*)window->base.hWnd); + // RemoveColorObserver(Palette_ColorChangeObserver, (void*)window->base.hWnd); } LRESULT CALLBACK PaletteWindow_UserProc(PaletteWindow* pPaletteWindow, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { - switch (message) { + switch (message) { - case WM_RBUTTONUP: - PaletteWindow_OnRButtonUp(pPaletteWindow, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); - return TRUE; - break; + case WM_RBUTTONUP: + PaletteWindow_OnRButtonUp(pPaletteWindow, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + return TRUE; + break; - case WM_LBUTTONUP: - PaletteWindow_OnLButtonUp(pPaletteWindow, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); - return TRUE; - break; + case WM_LBUTTONUP: + PaletteWindow_OnLButtonUp(pPaletteWindow, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + return TRUE; + break; - case WM_CONTEXTMENU: - PaletteWindow_OnContextMenu(pPaletteWindow, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); - break; - } + case WM_CONTEXTMENU: + PaletteWindow_OnContextMenu(pPaletteWindow, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + break; + } - return Window_UserProcDefault(pPaletteWindow, hWnd, message, wParam, lParam); + return Window_UserProcDefault(pPaletteWindow, hWnd, message, wParam, lParam); } void PaletteWindow_DrawSwatch(PaletteWindow* window, HDC hdc, int x, int y, uint32_t color) { - int swatchSize = g_paletteSettings.swatchSize; + int swatchSize = g_paletteSettings.swatchSize; - SetBrushOrgEx(hdc, x, y, NULL); + SetBrushOrgEx(hdc, x, y, NULL); - RECT rcChecker = { - x, y, x + swatchSize, y + swatchSize - }; - FillRect(hdc, &rcChecker, window->hbrChecker); - // FillRect(hdc, &checkerRc, GetStockObject(BLACK_BRUSH)); + RECT rcChecker = { + x, y, x + swatchSize, y + swatchSize + }; + FillRect(hdc, &rcChecker, window->hbrChecker); + // FillRect(hdc, &checkerRc, GetStockObject(BLACK_BRUSH)); - HDC hdcFill = CreateCompatibleDC(hdc); - HBITMAP hbmFill = CreateCompatibleBitmap(hdc, swatchSize, swatchSize); - HGDIOBJ hOldObj = SelectObject(hdcFill, hbmFill); + HDC hdcFill = CreateCompatibleDC(hdc); + HBITMAP hbmFill = CreateCompatibleBitmap(hdc, swatchSize, swatchSize); + HGDIOBJ hOldObj = SelectObject(hdcFill, hbmFill); - HBRUSH hbrColor = CreateSolidBrush(color & 0x00FFFFFF); - RECT swatchRc = {0, 0, swatchSize, swatchSize}; - FillRect(hdcFill, &swatchRc, hbrColor); - DeleteObject(hbrColor); + HBRUSH hbrColor = CreateSolidBrush(color & 0x00FFFFFF); + RECT swatchRc = { 0, 0, swatchSize, swatchSize }; + FillRect(hdcFill, &swatchRc, hbrColor); + DeleteObject(hbrColor); - BLENDFUNCTION blendFunc = { - .BlendOp = AC_SRC_OVER, - .BlendFlags = 0, - .SourceConstantAlpha = color >> 24, - .AlphaFormat = 0 - }; + BLENDFUNCTION blendFunc = { + .BlendOp = AC_SRC_OVER, + .BlendFlags = 0, + .SourceConstantAlpha = color >> 24, + .AlphaFormat = 0 + }; - AlphaBlend(hdc, x, y, swatchSize, swatchSize, hdcFill, 0, 0, swatchSize, - swatchSize, blendFunc); + AlphaBlend(hdc, x, y, swatchSize, swatchSize, hdcFill, 0, 0, swatchSize, + swatchSize, blendFunc); - SelectObject(hdcFill, hOldObj); + SelectObject(hdcFill, hOldObj); - DeleteObject(hbmFill); - DeleteDC(hdcFill); + DeleteObject(hbmFill); + DeleteDC(hdcFill); - RECT frameRc = {x, y, x+swatchSize, y+swatchSize}; - FrameRect(hdc, &frameRc, GetStockObject(BLACK_BRUSH)); + RECT frameRc = { x, y, x + swatchSize, y + swatchSize }; + FrameRect(hdc, &frameRc, GetStockObject(BLACK_BRUSH)); } static const int swatchListXOffset = 10; @@ -268,42 +269,42 @@ static const int swatchListYOffset = 40; int PaletteWindow_PosToSwatchIndex(PaletteWindow* window, int x, int y) { - int swatch_size = g_paletteSettings.swatchSize; + int swatch_size = g_paletteSettings.swatchSize; - RECT rc = {0}; - GetClientRect(window->base.hWnd, &rc); - int width_indices = (rc.right - 20) / (swatch_size + swatch_margin); - int swatch_outer = swatch_size + swatch_margin; + RECT rc = { 0 }; + GetClientRect(window->base.hWnd, &rc); + int width_indices = (rc.right - 20) / (swatch_size + swatch_margin); + int swatch_outer = swatch_size + swatch_margin; - x -= swatchListXOffset; - y -= swatchListYOffset; + x -= swatchListXOffset; + y -= swatchListYOffset; - int index = y / swatch_outer * width_indices + x / swatch_outer; - printf("Index: %d\n", index); + int index = y / swatch_outer * width_indices + x / swatch_outer; + printf("Index: %d\n", index); - return index; + return index; } void Palette_ColorChangeObserver(void* userData, uint32_t fg, uint32_t bg) { - UNREFERENCED_PARAMETER(fg); - UNREFERENCED_PARAMETER(bg); + UNREFERENCED_PARAMETER(fg); + UNREFERENCED_PARAMETER(bg); - HWND hWnd = (HWND)userData; - if (!hWnd) - return; + HWND hWnd = (HWND)userData; + if (!hWnd) + return; - InvalidateRect(hWnd, NULL, TRUE); + InvalidateRect(hWnd, NULL, TRUE); } void PaletteWindow_PreCreate(LPCREATESTRUCT lpcs) { - lpcs->dwExStyle = WS_EX_PALETTEWINDOW; - lpcs->lpszClass = szClassName; - lpcs->lpszName = L"Palette"; - lpcs->style = WS_CAPTION | WS_THICKFRAME | WS_VISIBLE; - lpcs->x = CW_USEDEFAULT; - lpcs->y = CW_USEDEFAULT; - lpcs->cx = 300; - lpcs->cy = 200; + lpcs->dwExStyle = WS_EX_PALETTEWINDOW; + lpcs->lpszClass = szClassName; + lpcs->lpszName = L"Palette"; + lpcs->style = WS_CAPTION | WS_THICKFRAME | WS_VISIBLE; + lpcs->x = CW_USEDEFAULT; + lpcs->y = CW_USEDEFAULT; + lpcs->cx = 300; + lpcs->cy = 200; } diff --git a/src/palette_window.h b/src/palette_window.h index eccd49a..1b19d35 100644 --- a/src/palette_window.h +++ b/src/palette_window.h @@ -9,11 +9,11 @@ typedef struct PaletteWindow PaletteWindow; struct PaletteWindow { - Window base; - HBRUSH hbrChecker; - Palette* palette; + Window base; + HBRUSH hbrChecker; + Palette* palette; }; PaletteWindow* PaletteWindow_Create(Application*, Palette*); -#endif /* PANITENT_PALETTE_WINDOW_H_ */ +#endif /* PANITENT_PALETTE_WINDOW_H_ */ diff --git a/src/panitent.c b/src/panitent.c index ff16dbc..b260f62 100644 --- a/src/panitent.c +++ b/src/panitent.c @@ -34,8 +34,14 @@ #include "panitentwindow.h" #include "crashhandler.h" #include "workspacecontainer.h" -#include "tree.h" +#include "util/tree.h" #include "layerswindow.h" +#include "sharing/activitysharingmanager.h" +#include "sharing/activitystubdialog.h" +#include "propgriddialog.h" +#include "pntxml.h" +#include "experimental/docklib.h" +#include "util/assert.h" #include "resource.h" @@ -60,30 +66,8 @@ const WCHAR szAppName[] = L"Panit.ent"; const WCHAR szPanitentWndClass[] = L"Win32Class_PanitentWindow"; BOOL Panitent_RegisterClasses(HINSTANCE hInstance); -HMENU CreateMainMenu(); void FetchSystemFont(); -/* Menu ID values */ -enum { - IDM_FILE_NEW = 1001, - IDM_FILE_OPEN, - IDM_FILE_SAVE, - IDM_FILE_CLIPBOARD_EXPORT, - IDM_FILE_CLOSE, - - IDM_EDIT_UNDO, - IDM_EDIT_REDO, - IDM_EDIT_CLRCANVAS, - - IDM_WINDOW_TOOLS, - - IDM_OPTIONS_SETTINGS, - - IDM_HELP_TOPICS, - IDM_HELP_LOG, - IDM_HELP_ABOUT -}; - struct PanitentApplication* g_app; #ifdef _MSC_VER @@ -93,280 +77,261 @@ struct PanitentApplication* g_app; /* Entry point for application */ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, - LPWSTR lpCmdLine, int nCmdShow) + LPWSTR lpCmdLine, int nCmdShow) { - UNREFERENCED_PARAMETER(hPrevInstance); - - BOOL bResult; - LPWSTR *pszArgList; - int nArgs; - LPWSTR pszArgFile; - WCHAR szModulePath[MAX_PATH]; - WCHAR szWorkDir[MAX_PATH]; - WCHAR szAppData[MAX_PATH]; - MSG msg; - - /* Request Common Controls v6 */ - INITCOMMONCONTROLSEX icex = { 0 }; - icex.dwSize = sizeof(INITCOMMONCONTROLSEX); - icex.dwICC = ICC_WIN95_CLASSES; - InitCommonControlsEx(&icex); - - struct PanitentApplication* app = PanitentApplication_Create(); - g_app = app; - - AddVectoredExceptionHandler(TRUE, PanitentUnhandledExceptionFilter); - - WindowingInit(); - - // Window_CreateWindow((struct Window*)app->m_pLayeredWindow, NULL); - - pszArgFile = NULL; - - /* Get command line arguments - * - * If lpCmdLine is empty, the CommandLineToArgvW will return the - * executable path - */ - if (lpCmdLine && *lpCmdLine != L'\0') - { - pszArgList = CommandLineToArgvW(lpCmdLine, &nArgs); - - if (nArgs > 0) - pszArgFile = pszArgList[0]; - } - - ZeroMemory(szModulePath, sizeof(szModulePath)); - GetModuleFileName(GetModuleHandle(NULL), szModulePath, MAX_PATH); - - ZeroMemory(szWorkDir, sizeof(szWorkDir)); - GetCurrentDirectory(MAX_PATH, szWorkDir); - - PWSTR lpszAppData; - ZeroMemory(szAppData, sizeof(szAppData)); - SHGetKnownFolderPath(&FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, NULL, &lpszAppData); - StringCchCopy(szAppData, MAX_PATH, lpszAppData); - CoTaskMemFree(lpszAppData); - PathAppend(szAppData, L"\\Aragajaga\\Panit.ent"); - - /* Create %APPDATA% application folder - * - * This function is available through XP SP2 and Server 2003. - * It might be altered or unavailable in subsequent versions of OS. - */ - SHCreateDirectoryEx(NULL, szAppData, NULL); - -#ifdef HAS_DISCORDSDK - /* Initialize Discord SDK and set initial activity message */ - - g_panitent.discord = DiscordSDKInit(); - Discord_SetActivityStatus(g_panitent.discord, L"Idle"); -#endif /* HAS_DISCORDSDK */ - - /* Share application instance */ - g_panitent.hInstance = hInstance; - - FetchSystemFont(); - InitColorContext(); - - /* Register custom controls and windows classes */ - bResult = Panitent_RegisterClasses(hInstance); - assert(bResult); - if (!bResult) - { - return -1; - } - - bresenham_init(); - wu_init(); - g_primitives_context = g_bresenham_primitives; - g_primitives_context.fStroke = TRUE; - g_primitives_context.fFill = TRUE; - - InitializeBrushList(); - g_pBrush = &g_brushList[0]; - g_brushSize = 24; - - HMENU hMenu = CreateMainMenu(); - - PNTSETTINGS* pSettings; - pSettings = Panitent_GetSettings(); - - WCHAR szSettingsPath[MAX_PATH]; - StringCchCopy(szSettingsPath, MAX_PATH, szAppData); - PathAppend(szSettingsPath, L"\\settings.dat"); - - FILE* fp; - errno_t result = _wfopen_s(&fp, szSettingsPath, L"rb"); - assert(result); - if (result && fp) - { - fread(pSettings, sizeof(PNTSETTINGS), 1, fp); - fclose(fp); - } - - int windowX = CW_USEDEFAULT; - int windowY = 0; - int windowWidth = CW_USEDEFAULT; - int windowHeight = 0; - - if (pSettings->bRememberWindowPos) - { - windowX = pSettings->x; - windowY = pSettings->y; - windowWidth = pSettings->width; - windowHeight = pSettings->height; - } - - HWND hWnd = Window_CreateWindow((struct Window*)app->m_pPanitentWindow, NULL); - - g_panitent.hwnd = hWnd; - - ShowWindow(hWnd, nCmdShow); - SetMenu(hWnd, hMenu); - - /* Initialize Wintab API */ - /* - if (pSettings->bEnablePenTablet) - LoadWintab(); - */ + UNREFERENCED_PARAMETER(hPrevInstance); + + BOOL bResult; + LPWSTR* pszArgList; + int nArgs; + LPWSTR pszArgFile; + WCHAR szModulePath[MAX_PATH]; + WCHAR szWorkDir[MAX_PATH]; + WCHAR szAppData[MAX_PATH]; + + /* Request Common Controls v6 */ + INITCOMMONCONTROLSEX icex = { 0 }; + icex.dwSize = sizeof(INITCOMMONCONTROLSEX); + icex.dwICC = ICC_WIN95_CLASSES; + InitCommonControlsEx(&icex); + + struct PanitentApplication* app = PanitentApplication_Create(); + g_app = app; + + AddVectoredExceptionHandler(TRUE, PanitentUnhandledExceptionFilter); + + WindowingInit(); + + pszArgFile = NULL; + + /* Get command line arguments + * + * If lpCmdLine is empty, the CommandLineToArgvW will return the + * executable path + */ + if (lpCmdLine && *lpCmdLine != L'\0') + { + pszArgList = CommandLineToArgvW(lpCmdLine, &nArgs); + + if (nArgs > 0) + { + pszArgFile = pszArgList[0]; + } + } + + ZeroMemory(szModulePath, sizeof(szModulePath)); + GetModuleFileName(GetModuleHandle(NULL), szModulePath, MAX_PATH); + + ZeroMemory(szWorkDir, sizeof(szWorkDir)); + GetCurrentDirectory(MAX_PATH, szWorkDir); + + PWSTR lpszAppData; + ZeroMemory(szAppData, sizeof(szAppData)); + SHGetKnownFolderPath(&FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, NULL, &lpszAppData); + StringCchCopy(szAppData, MAX_PATH, lpszAppData); + CoTaskMemFree(lpszAppData); + PathAppend(szAppData, L"\\Aragajaga\\Panit.ent"); + + /* Create %APPDATA% application folder + * + * This function is available through XP SP2 and Server 2003. + * It might be altered or unavailable in subsequent versions of OS. + */ + SHCreateDirectoryEx(NULL, szAppData, NULL); + + Panitent_SetActivityStatus(Panitent_GetApp(), L"Idle"); + + /* Share application instance */ + g_panitent.hInstance = hInstance; + + FetchSystemFont(); + InitColorContext(); + + /* Register custom controls and windows classes */ + bResult = Panitent_RegisterClasses(hInstance); + assert(bResult); + if (!bResult) + { + return -1; + } + + bresenham_init(); + wu_init(); + g_primitives_context = g_bresenham_primitives; + g_primitives_context.fStroke = TRUE; + g_primitives_context.fFill = TRUE; + + InitializeBrushList(); + g_pBrush = &g_brushList[0]; + g_brushSize = 24; + + PNTSETTINGS* pSettings; + pSettings = Panitent_GetSettings(); + + WCHAR szSettingsPath[MAX_PATH]; + StringCchCopy(szSettingsPath, MAX_PATH, szAppData); + PathAppend(szSettingsPath, L"\\settings.dat"); - if (pszArgFile) - { - Panitent_OpenFile(pszArgFile); - } - - /* Application message loop. - * Just pump inbound window messages and forward them to associated window - procedure */ - ZeroMemory(&msg, sizeof(MSG)); - while (GetMessage(&msg, NULL, 0, 0)) { - DispatchMessage(&msg); - TranslateMessage(&msg); - } - - return (int)msg.wParam; + FILE* fp; + errno_t result = _wfopen_s(&fp, szSettingsPath, L"rb"); + assert(result); + if (result && fp) + { + fread(pSettings, sizeof(PNTSETTINGS), 1, fp); + fclose(fp); + } + + int windowX = CW_USEDEFAULT; + int windowY = 0; + int windowWidth = CW_USEDEFAULT; + int windowHeight = 0; + + if (pSettings->bRememberWindowPos) + { + windowX = pSettings->x; + windowY = pSettings->y; + windowWidth = pSettings->width; + windowHeight = pSettings->height; + } + + HWND hWnd = Window_CreateWindow((struct Window*)app->m_pPanitentWindow, NULL); + g_panitent.hwnd = hWnd; + Window_Show((Window*)app->m_pPanitentWindow, nCmdShow); + + if (pszArgFile) + { + Panitent_OpenFile(pszArgFile); + } + + DockPanel_RegisterClass(hInstance); + + /* Experimental DockWindow */ + { + DockHostWindow2* pDockHostWindow2 = NULL; + pDockHostWindow2 = DockHostWindow2_Create(app); + Window_CreateWindow(pDockHostWindow2, NULL); + Window_Show(&pDockHostWindow2, SW_SHOW); + } + + /* Application message loop. + * Just pump inbound window messages and forward them to associated window + procedure */ + MSG msg = {0}; + while (GetMessage(&msg, NULL, 0, 0)) + { + DispatchMessage(&msg); + TranslateMessage(&msg); + } + + return (int)msg.wParam; } #ifdef _MSCVER #pragma warning( pop ) #endif /* _MSC_VER */ +void Panitent_SaveSettingsFile(PNTSETTINGS* pPanitentSettings) +{ + FILE* fp = NULL; + errno_t result = _wfopen_s(&fp, L"settings.dat", L"wb"); + assert(result == 0 && fp); + if (result == 0 && fp) + { + fwrite(pPanitentSettings, sizeof(PNTSETTINGS), 1, fp); + fclose(fp); + } +} + void Panitent_OnClose(HWND hWnd) { - PNTSETTINGS* pSettings = NULL; - RECT rc = { 0 }; + PNTSETTINGS* pSettings = NULL; + RECT rc = { 0 }; - pSettings = Panitent_GetSettings(); - assert(pSettings); + pSettings = Panitent_GetSettings(); + assert(pSettings); - GetWindowRect(hWnd, &rc); + GetWindowRect(hWnd, &rc); - if (pSettings->bRememberWindowPos) - { - if (pSettings->x == CW_USEDEFAULT || - pSettings->y == CW_USEDEFAULT) + if (pSettings->bRememberWindowPos) { - pSettings->x = CW_USEDEFAULT; - pSettings->y = 0; - } - else { - pSettings->x = rc.left; - pSettings->y = rc.top; + if (pSettings->x == CW_USEDEFAULT || + pSettings->y == CW_USEDEFAULT) + { + pSettings->x = CW_USEDEFAULT; + pSettings->y = 0; + } + else { + pSettings->x = rc.left; + pSettings->y = rc.top; + } + + if (pSettings->width == CW_USEDEFAULT || + pSettings->height == CW_USEDEFAULT) + { + pSettings->width = CW_USEDEFAULT; + pSettings->height = 0; + } + else { + pSettings->width = rc.right - rc.left; + pSettings->height = rc.bottom - rc.top; + } } - if (pSettings->width == CW_USEDEFAULT || - pSettings->height == CW_USEDEFAULT) - { - pSettings->width = CW_USEDEFAULT; - pSettings->height = 0; - } - else { - pSettings->width = rc.right - rc.left; - pSettings->height = rc.bottom - rc.top; - } - } - - FILE* fp = NULL; - errno_t result = _wfopen_s(&fp, L"settings.dat", L"wb"); - assert(result == 0 && fp); - if (result == 0 && fp) - { - fwrite(pSettings, sizeof(PNTSETTINGS), 1, fp); - fclose(fp); - } - - PostQuitMessage(0); + Panitent_SaveSettingsFile(pSettings); + + PostQuitMessage(0); } static inline void PopupClassRegistrationFail(LPWSTR lpszTip) { - WCHAR szMessage[256]; - ZeroMemory(szMessage, sizeof(szMessage)); + WCHAR szMessage[256]; + ZeroMemory(szMessage, sizeof(szMessage)); - StringCchPrintf(szMessage, 256, L"Failed to register %s class", lpszTip); + StringCchPrintf(szMessage, 256, L"Failed to register %s class", lpszTip); - MessageBox(NULL, szMessage, NULL, MB_OK | MB_ICONERROR); + MessageBox(NULL, szMessage, NULL, MB_OK | MB_ICONERROR); } static inline BOOL AssertClassRegistration( HINSTANCE hInstance, LPWSTR lpszClassName, - BOOL (*lpfnClassRegisterer)(HINSTANCE)) + BOOL(*lpfnClassRegisterer)(HINSTANCE)) { - BOOL bResult; + BOOL bResult; - bResult = (*lpfnClassRegisterer)(hInstance); - assert(bResult); - if (!bResult) - { - PopupClassRegistrationFail(lpszClassName); - } + bResult = (*lpfnClassRegisterer)(hInstance); + assert(bResult); + if (!bResult) + { + PopupClassRegistrationFail(lpszClassName); + } - return TRUE; + return TRUE; } BOOL Panitent_RegisterClasses(HINSTANCE hInstance) { - BOOL bStatus; - - bStatus = TRUE; + BOOL bStatus; - bStatus = bStatus && AssertClassRegistration( hInstance, L"SettingsDialog", - SettingsDialog_RegisterClass); + bStatus = TRUE; - /* - bStatus = bStatus && AssertClassRegistration(hInstance, L"DockHost", - DockHost_RegisterClass); - */ + bStatus = bStatus && AssertClassRegistration(hInstance, L"SettingsDialog", SettingsDialog_RegisterClass); + bStatus = bStatus && AssertClassRegistration(hInstance, L"SettingsWindow", SettingsWindow_Register); + bStatus = bStatus && AssertClassRegistration(hInstance, L"SwatchControl2", SwatchControl2_RegisterClass); + bStatus = bStatus && AssertClassRegistration(hInstance, L"BrushSel", BrushSel_RegisterClass); - bStatus = bStatus && AssertClassRegistration(hInstance, L"SettingsWindow", - SettingsWindow_Register); - - bStatus = bStatus && AssertClassRegistration(hInstance, L"SwatchControl2", - SwatchControl2_RegisterClass); - - bStatus = bStatus && AssertClassRegistration(hInstance, L"BrushSel", - BrushSel_RegisterClass); - - bStatus = bStatus && AssertClassRegistration(hInstance, L"Logwindow", - LogWindow_Register); - - return bStatus; + return bStatus; } void Panitent_DockHostInit(DockHostWindow* pDockHostWindow, TreeNode* pNodeParent) { - - DockData* pDockDataParent = (DockData*)calloc(1, sizeof(DockData)); + DockData* pDockDataParent = DockData_Create(64, DGA_END | DGP_ABSOLUTE | DGD_VERTICAL, FALSE); pNodeParent->data = (void*)pDockDataParent; - pDockDataParent->dwStyle = DGA_END | DGP_ABSOLUTE | DGD_VERTICAL; - pDockDataParent->iGripPos = 64; - pDockDataParent->bShowCaption = FALSE; wcscpy_s(pDockDataParent->lpszName, MAX_PATH, L"Root"); TreeNode* pNodeOptionBar = BinaryTree_AllocEmptyNode(); - DockData* pDockDataOptionBar = (DockData*)calloc(1, sizeof(DockData)); + + DockData* pDockDataOptionBar = DockData_Create(0, DGA_END | DGP_ABSOLUTE | DGD_HORIZONTAL, FALSE); pNodeOptionBar->data = (void*)pDockDataOptionBar; wcscpy_s(pDockDataOptionBar->lpszName, MAX_PATH, L"OptionBar"); OptionBarWindow* pOptionBarWindow = OptionBarWindow_Create((Application*)g_app); @@ -375,7 +340,8 @@ void Panitent_DockHostInit(DockHostWindow* pDockHostWindow, TreeNode* pNodeParen /* Main node */ TreeNode* pNodeZ = BinaryTree_AllocEmptyNode(); - DockData* pDockDataZ = (DockData*)calloc(1, sizeof(DockData)); + DockData* pDockDataZ = (DockData*)malloc(sizeof(DockData)); + memset(pDockDataOptionBar, 0, sizeof(DockData)); pNodeZ->data = (void*)pDockDataZ; pDockDataZ->dwStyle = DGA_END | DGP_ABSOLUTE | DGD_HORIZONTAL; pDockDataZ->iGripPos = 192; @@ -386,7 +352,8 @@ void Panitent_DockHostInit(DockHostWindow* pDockHostWindow, TreeNode* pNodeParen /* Toolbox node */ TreeNode* pNodeToolbox = BinaryTree_AllocEmptyNode(); - DockData* pDockDataToolbox = (DockData*)calloc(1, sizeof(DockData)); + DockData* pDockDataToolbox = (DockData*)malloc(sizeof(DockData)); + memset(pDockDataToolbox, 0, sizeof(DockData)); pNodeToolbox->data = (void*)pDockDataToolbox; wcscpy_s(pDockDataToolbox->lpszName, MAX_PATH, L"Toolbox"); ToolboxWindow* pToolboxWindow = ToolboxWindow_Create((struct Application*)g_app); @@ -395,7 +362,8 @@ void Panitent_DockHostInit(DockHostWindow* pDockHostWindow, TreeNode* pNodeParen /* Viewport node */ TreeNode* pNodeViewport = BinaryTree_AllocEmptyNode(); - DockData* pDockDataViewport = (DockData*)calloc(1, sizeof(DockData)); + DockData* pDockDataViewport = (DockData*)malloc(sizeof(DockData)); + memset(pDockDataViewport, 0, sizeof(DockData)); pNodeViewport->data = (void*)pDockDataViewport; wcscpy_s(pDockDataViewport->lpszName, MAX_PATH, L"WorkspaceContainer"); WorkspaceContainer* pWorkspaceContainer = WorkspaceContainer_Create((Application*)g_app); @@ -405,7 +373,8 @@ void Panitent_DockHostInit(DockHostWindow* pDockHostWindow, TreeNode* pNodeParen /* Toolbox/Viewport node */ TreeNode* pNodeY = BinaryTree_AllocEmptyNode(); - DockData* pDockDataY = (DockData*)calloc(1, sizeof(DockData)); + DockData* pDockDataY = (DockData*)malloc(sizeof(DockData)); + memset(pDockDataY, 0, sizeof(DockData)); pNodeY->data = (void*)pDockDataY; wcscpy_s(pDockDataY->lpszName, MAX_PATH, L"Toolbox/Viewport"); pDockDataY->dwStyle = DGA_START | DGP_ABSOLUTE | DGD_HORIZONTAL; @@ -419,7 +388,8 @@ void Panitent_DockHostInit(DockHostWindow* pDockHostWindow, TreeNode* pNodeParen /* GLWindow node */ TreeNode* pNodeGLWindow = BinaryTree_AllocEmptyNode(); - DockData* pDockDataGLWindow = (DockData*)calloc(1, sizeof(DockData)); + DockData* pDockDataGLWindow = (DockData*)malloc(sizeof(DockData)); + memset(pDockDataGLWindow, 0, sizeof(DockData)); pNodeGLWindow->data = (void*)pDockDataGLWindow; wcscpy_s(pDockDataGLWindow->lpszName, MAX_PATH, L"GLWindow"); GLWindow* pGLWindow = GLWindow_Create((struct Application*)g_app); @@ -428,7 +398,8 @@ void Panitent_DockHostInit(DockHostWindow* pDockHostWindow, TreeNode* pNodeParen /* Create and pin palette window */ TreeNode* pNodePalette = BinaryTree_AllocEmptyNode(); - DockData* pDockDataPalette = (DockData*)calloc(1, sizeof(DockData)); + DockData* pDockDataPalette = (DockData*)malloc(sizeof(DockData)); + memset(pDockDataPalette, 0, sizeof(DockData)); pNodePalette->data = (void*)pDockDataPalette; wcscpy_s(pDockDataPalette->lpszName, MAX_PATH, L"Palette"); PaletteWindow* pPaletteWindow = PaletteWindow_Create((Application*)g_app, g_app->palette); @@ -437,7 +408,8 @@ void Panitent_DockHostInit(DockHostWindow* pDockHostWindow, TreeNode* pNodeParen /* Create and pin layers window */ TreeNode* pNodeLayers = BinaryTree_AllocEmptyNode(); - DockData* pDockDataLayers = (DockData*)calloc(1, sizeof(DockData)); + DockData* pDockDataLayers = (DockData*)malloc(sizeof(DockData)); + memset(pDockDataLayers, 0, sizeof(DockData)); pNodeLayers->data = (void*)pDockDataLayers; wcscpy_s(pDockDataLayers->lpszName, MAX_PATH, L"Layers"); LayersWindow* pLayersWindow = LayersWindow_Create((Application*)g_app); @@ -446,7 +418,8 @@ void Panitent_DockHostInit(DockHostWindow* pDockHostWindow, TreeNode* pNodeParen /* Palette/Layers node */ TreeNode* pNodeB = BinaryTree_AllocEmptyNode(); - DockData* pDockDataB = (DockData*)calloc(1, sizeof(DockData)); + DockData* pDockDataB = (DockData*)malloc(sizeof(DockData)); + memset(pDockDataB, 0, sizeof(DockData)); pNodeB->data = (void*)pDockDataB; wcscpy_s(pDockDataB->lpszName, MAX_PATH, L"Palette/Layers"); pDockDataB->dwStyle = DGA_START | DGP_ABSOLUTE | DGD_VERTICAL; @@ -461,7 +434,8 @@ void Panitent_DockHostInit(DockHostWindow* pDockHostWindow, TreeNode* pNodeParen /* right bar node */ TreeNode* pNodeA = BinaryTree_AllocEmptyNode(); - DockData* pDockDataA = (DockData*)calloc(1, sizeof(DockData)); + DockData* pDockDataA = (DockData*)malloc(sizeof(DockData)); + memset(pDockDataA, 0, sizeof(DockData)); pNodeA->data = (void*)pDockDataA; wcscpy_s(pDockDataA->lpszName, MAX_PATH, L"Right bar"); pDockDataA->dwStyle = DGA_START | DGP_ABSOLUTE | DGD_VERTICAL; @@ -483,86 +457,89 @@ void Panitent_DockHostInit(DockHostWindow* pDockHostWindow, TreeNode* pNodeParen /* OptionBar / Other */ pNodeParent->node1 = pNodeZ; pNodeParent->node2 = pNodeOptionBar; + + WCHAR pszSource[] = L"" + L"" + L"World" + L"" + L":-)" + L":-0" + L":-D" + L"" + L"" + ; + + struct XMLDocument* pXMLDocument = XML_ParseDocument(pszSource, wcslen(pszSource)); + + if (!pXMLDocument) + { + Panitent_RaiseException(L"Could not parse pXMLDocument\n"); + } + XMLNode* root = XMLDocument_GetRoot(pXMLDocument); + + /* Say Hello World :-) */ + XMLNode* pRootHello = XMLNode_GetChild(root, 0); + XMLString* pxsHello = XMLNode_GetName(pRootHello); + XMLString* pxsWorld = XMLNode_GetContent(pRootHello); + + /* Watch out: `xml_string_copy` will not 0-ternimate your `calloc` will :-) */ + PWSTR hello_0 = calloc(XMLString_Length(pxsHello) + 1, sizeof(WCHAR)); + PWSTR world_0 = calloc(XMLString_Length(pxsHello) + 1, sizeof(WCHAR)); + XMLString_Copy(pxsHello, hello_0, XMLString_Length(pxsHello)); + XMLString_Copy(pxsWorld, world_0, XMLString_Length(pxsWorld)); + + wprintf_s(L"%s %s\n", hello_0, world_0); + free(hello_0); + free(world_0); + + /* Extract amout of Root/This children */ + XMLNode* pRootThis = XMLNode_GetChild(root, 1); + wprintf_s(L"Root/This has %lu ppChildren\n", (unsigned long)XMLNode_GetChildrenCount(pRootThis)); + + /* Remember to free the document or you'ss risk a memory leak */ + XMLDocument_Free(pXMLDocument, FALSE); } void FetchSystemFont() { - NONCLIENTMETRICS ncm = {0}; - ncm.cbSize = sizeof(NONCLIENTMETRICS); - - BOOL bResult = SystemParametersInfo(SPI_GETNONCLIENTMETRICS, - sizeof(NONCLIENTMETRICS), - &ncm, - 0); - if (!bResult) { - return; - } - - HFONT hFontNew = CreateFontIndirect(&ncm.lfMessageFont); - if (!hFontNew) { - return; - } - - DeleteObject(hFontSys); - hFontSys = hFontNew; + NONCLIENTMETRICS ncm = { 0 }; + ncm.cbSize = sizeof(NONCLIENTMETRICS); + + BOOL bResult = SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0); + if (!bResult) + { + return; + } + + HFONT hFontNew = CreateFontIndirect(&ncm.lfMessageFont); + if (!hFontNew) + { + return; + } + + DeleteObject(hFontSys); + hFontSys = hFontNew; } HFONT GetGuiFont() { - if (!hFontSys) - FetchSystemFont(); + if (!hFontSys) + { + FetchSystemFont(); + } - return hFontSys; + return hFontSys; } void SetGuiFont(HWND hwnd) { - SendMessage(hwnd, WM_SETFONT, (WPARAM)hFontSys, MAKELPARAM(FALSE, 0)); -} - -HMENU CreateMainMenu() -{ - HMENU hMenu; - HMENU hSubMenu; - - hMenu = CreateMenu(); - - hSubMenu = CreatePopupMenu(); - AppendMenu(hSubMenu, MF_STRING, IDM_FILE_NEW, L"&New\tCtrl+N"); - AppendMenu(hSubMenu, MF_STRING, IDM_FILE_OPEN, L"&Open\tCtrl+O"); - AppendMenu(hSubMenu, MF_STRING, IDM_FILE_SAVE, L"&Save\tCtrl+S"); - AppendMenu(hSubMenu, MF_STRING, IDM_FILE_CLIPBOARD_EXPORT, L"Export image to clipboard"); - AppendMenu(hSubMenu, MF_STRING, IDM_FILE_CLOSE, L"&Close"); - AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hSubMenu, L"&File"); - - hSubMenu = CreatePopupMenu(); - AppendMenu(hSubMenu, MF_STRING, IDM_EDIT_UNDO, L"&Undo"); - AppendMenu(hSubMenu, MF_STRING, IDM_EDIT_REDO, L"&Redo"); - AppendMenu(hSubMenu, MF_STRING, IDM_EDIT_CLRCANVAS, L"&Clear canvas"); - AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hSubMenu, L"&Edit"); - - hSubMenu = CreatePopupMenu(); - AppendMenu(hSubMenu, MF_STRING, IDM_WINDOW_TOOLS, L"&Tools"); - AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hSubMenu, L"&Window"); - CheckMenuItem(hSubMenu, IDM_WINDOW_TOOLS, MF_BYCOMMAND | MF_CHECKED); - - hSubMenu = CreatePopupMenu(); - AppendMenu(hSubMenu, MF_STRING, IDM_OPTIONS_SETTINGS, L"&Settings"); - AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hSubMenu, L"&Options"); - - hSubMenu = CreatePopupMenu(); - AppendMenu(hSubMenu, MF_STRING, IDM_HELP_TOPICS, L"Help &Topics"); - AppendMenu(hSubMenu, MF_STRING, IDM_HELP_LOG, L"&Log"); - AppendMenu(hSubMenu, MF_STRING, IDM_HELP_ABOUT, L"&About"); - AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hSubMenu, L"&Help"); - - return hMenu; + SendMessage(hwnd, WM_SETFONT, (WPARAM)hFontSys, MAKELPARAM(FALSE, 0)); } Document* Panitent_GetActiveDocument() { - ViewportWindow* pViewportWindow = Panitent_GetActiveViewport(); - return ViewportWindow_GetDocument(pViewportWindow); + ViewportWindow* pViewportWindow = Panitent_GetActiveViewport(); + return ViewportWindow_GetDocument(pViewportWindow); } void Panitent_SetActiveViewport(ViewportWindow* pViewportWindow) @@ -577,110 +554,112 @@ ViewportWindow* Panitent_GetActiveViewport() HWND Panitent_GetHWND() { - return g_panitent.hwnd; + return g_panitent.hwnd; } -PNTSETTINGS *Panitent_GetSettings() +PNTSETTINGS* Panitent_GetSettings() { - return &g_panitent.settings; + return &g_panitent.settings; } void Panitent_Open() { - Document_Open(NULL); + Document_Open(NULL); } void Panitent_OpenFile(LPWSTR pszPath) { - Document_OpenFile(pszPath); + Document_OpenFile(pszPath); } ViewportWindow* Panitent_CreateViewport() { - ViewportWindow* pViewportWindow = Panitent_GetActiveViewport(); + ViewportWindow* pViewportWindow = Panitent_GetActiveViewport(); - if (!pViewportWindow) - { - Panitent_SetActiveViewport(NULL); - } + if (!pViewportWindow) + { + Panitent_SetActiveViewport(NULL); + } - return pViewportWindow; + return pViewportWindow; } void Panitent_ClipboardExport() { - HWND hWndPanitent; - /* HGLOBAL hglbCopy; */ + HWND hWndPanitent; + /* HGLOBAL hglbCopy; */ - hWndPanitent = Panitent_GetHWND(); + hWndPanitent = Panitent_GetHWND(); - if (!OpenClipboard(hWndPanitent)) { - return; - } - EmptyClipboard(); + if (!OpenClipboard(hWndPanitent)) { + return; + } + EmptyClipboard(); - Document *doc = Panitent_GetActiveDocument(); - if (!doc) - return; + Document* doc = Panitent_GetActiveDocument(); + if (!doc) + return; - Canvas *canvas = Document_GetCanvas(doc); - if (!canvas) - return; + Canvas* canvas = Document_GetCanvas(doc); + if (!canvas) + return; - unsigned char *pData = malloc(canvas->buffer_size); - if (!pData) - { - return; - } + unsigned char* pData = malloc(canvas->buffer_size); + memset(pData, 0, canvas->buffer_size); + if (!pData) + { + return; + } - ZeroMemory(pData, canvas->buffer_size); - memcpy(pData, canvas->buffer, canvas->buffer_size); + ZeroMemory(pData, canvas->buffer_size); + memcpy(pData, canvas->buffer, canvas->buffer_size); - for (size_t y = 0; y < (size_t) canvas->height; y++) { - for (size_t x = 0; x < (size_t) canvas->width; x++) { - uint8_t *pixel = pData + (x + y * canvas->width) * 4; + for (size_t y = 0; y < (size_t)canvas->height; y++) { + for (size_t x = 0; x < (size_t)canvas->width; x++) { + uint8_t* pixel = pData + (x + y * canvas->width) * 4; - float factor = (float)(pixel[3]) / 255.f; + float factor = (float)(pixel[3]) / 255.f; - pixel[0] *= (uint8_t)factor; - pixel[1] *= (uint8_t)factor; - pixel[2] *= (uint8_t)factor; + pixel[0] *= (uint8_t)factor; + pixel[1] *= (uint8_t)factor; + pixel[2] *= (uint8_t)factor; + } } - } - HBITMAP hBitmap = CreateBitmap(canvas->width, - canvas->height, 1, sizeof(uint32_t) * 8, pData); + HBITMAP hBitmap = CreateBitmap(canvas->width, + canvas->height, 1, sizeof(uint32_t) * 8, pData); - /* - hglbCopy = GlobalAlloc(GMEM_MOVEABLE, sizeof(HBITMAP)); - if (!hglbCopy) { - CloseClipboard(); - return; - } - */ + /* + hglbCopy = GlobalAlloc(GMEM_MOVEABLE, sizeof(HBITMAP)); + if (!hglbCopy) { + CloseClipboard(); + return; + } + */ - SetClipboardData(CF_BITMAP, hBitmap); - CloseClipboard(); + SetClipboardData(CF_BITMAP, hBitmap); + CloseClipboard(); } -struct PanitentApplication* PanitentApplication_Create() +PanitentApplication* PanitentApplication_Create() { - struct PanitentApplication* app = calloc(1, sizeof(struct PanitentApplication)); + PanitentApplication* pPanitentApplication = (PanitentApplication*)malloc(sizeof(PanitentApplication)); - if (app) - { - PanitentApplication_Init(app); - } + if (pPanitentApplication) + { + memset(pPanitentApplication, 0, sizeof(PanitentApplication)); + PanitentApplication_Init(pPanitentApplication); + } - return app; + return pPanitentApplication; } void PanitentApplication_Init(struct PanitentApplication* app) { - Application_Init(&app->base); - - app->m_pPanitentWindow = PanitentWindow_Create((Application *)app); + Application_Init(&app->base); - app->palette = Palette_Create(); + app->m_pPanitentWindow = PanitentWindow_Create((Application*)app); + app->palette = Palette_Create(); + app->m_pActivitySharingManager = ActivitySharingManager_Create(); } void Panitent_CmdClearCanvas(struct PanitentApplication* app) @@ -688,7 +667,7 @@ void Panitent_CmdClearCanvas(struct PanitentApplication* app) Document* pDocument = Panitent_GetActiveDocument(); Canvas* pCanvas = Document_GetCanvas(pDocument); Canvas_Clear(pCanvas); - Window_Invalidate((Window *)Panitent_GetActiveViewport()); + Window_Invalidate((Window*)Panitent_GetActiveViewport()); } void Panitent_CmdSaveFile(struct PanitentApplication* app) @@ -706,7 +685,45 @@ WorkspaceContainer* Panitent_GetWorkspaceContainer() return g_app->m_pWorkspaceContainer; } -Tool* Panitent_GetTool() +Tool* Panitent_GetSelectedTool() { return g_app->m_pTool; } + +void Panitent_CmdShowActivityDialog(PanitentApplication* pApp) +{ + ActivityStubDialog* pActivityStubDialog = ActivityStubDialog_Create(pApp); + ActivitySharingManager_AddClient(pApp->m_pActivitySharingManager, &pActivityStubDialog->m_activitySharingClient); + HWND hWnd = Dialog_CreateWindow(pActivityStubDialog, IDD_ACTIVITYSTUB, NULL, FALSE); + ShowWindow(hWnd, SW_SHOW); +} + +void Panitent_SetActivityStatus(PanitentApplication* pApp, PCWSTR pszStatusMessage) +{ + ActivitySharingManager_SetStatus(pApp->m_pActivitySharingManager, pszStatusMessage); +} + +void Panitent_SetActivityStatusF(PanitentApplication* pApp, PCWSTR pszFormat, ...) +{ + va_list argp; + va_start(argp, pszFormat); + + WCHAR szMessage[256]; + vswprintf_s(szMessage, 256, pszFormat, argp); + + va_end(argp); + + Panitent_SetActivityStatus(pApp, szMessage); +} + +void Panitent_RaiseException(PCWSTR pszExceptionMessage) +{ + MessageBox(NULL, pszExceptionMessage, NULL, MB_ICONERROR); +} + +void Panitent_CmdShowPropertyGridDialog(PanitentApplication* pApp) +{ + PropertyGridDialog* pPropertyGridDialog = PropertyGridDialog_Create(pApp); + HWND hDlg = Dialog_CreateWindow(pPropertyGridDialog, IDD_PROPERTYGRIDDLG, NULL, FALSE); + ShowWindow(hDlg, SW_SHOW); +} diff --git a/src/panitent.h b/src/panitent.h index ece4742..791bef9 100644 --- a/src/panitent.h +++ b/src/panitent.h @@ -12,6 +12,7 @@ typedef struct Tool Tool; typedef struct TreeNode TreeNode; typedef struct ViewportWindow ViewportWindow; typedef struct WorkspaceContainer WorkspaceContainer; +typedef struct ActivitySharingManager ActivitySharingManager; struct PanitentApplication { struct Application base; @@ -26,6 +27,7 @@ struct PanitentApplication { OptionBarWindow* m_pOptionBarWindow; WorkspaceContainer* m_pWorkspaceContainer; ViewportWindow* m_pViewportWindow; + ActivitySharingManager* m_pActivitySharingManager; Tool* m_pTool; }; @@ -33,10 +35,6 @@ struct PanitentApplication { void PanitentApplication_Init(struct PanitentApplication*); struct PanitentApplication* PanitentApplication_Create(); -#ifdef HAS_DISCORDSDK -#include "discordsdk.h" -#endif /* HAS_DISCORDSDK */ - #include "settings.h" typedef struct _Document Document; @@ -77,4 +75,11 @@ PanitentApplication* Panitent_GetApp(); WorkspaceContainer* Panitent_GetWorkspaceContainer(); -Tool* Panitent_GetTool(); +Tool* Panitent_GetSelectedTool(); +void Panitent_CmdShowActivityDialog(PanitentApplication* pApp); + +void Panitent_SetActivityStatus(PanitentApplication* pApp, PCWSTR pszStatusMessage); +void Panitent_SetActivityStatusF(PanitentApplication* pApp, PCWSTR pszFormat, ...); + +void Panitent_RaiseException(PCWSTR pszExceptionMessage); +void Panitent_CmdShowPropertyGridDialog(PanitentApplication* pApp); \ No newline at end of file diff --git a/src/panitentwindow.c b/src/panitentwindow.c index 5fdf1cb..1c4124e 100644 --- a/src/panitentwindow.c +++ b/src/panitentwindow.c @@ -12,10 +12,19 @@ #include "aboutbox.h" #include "log_window.h" #include "new.h" +#include "propgriddialog.h" + +#include "win32/util.h" + +#include +#include +#include static const WCHAR szClassName[] = L"__PanitentWindow"; /* Private forward declarations */ +void PanitentWindow_PaintCustomCaption(PanitentWindow* pPanitentWindow, HDC hdc); + PanitentWindow* PanitentWindow_Create(struct Application*); void PanitentWindow_Init(PanitentWindow*, struct Application*); @@ -30,34 +39,43 @@ void PanitentWindow_OnRButtonUp(PanitentWindow* pPanitentWindow, int x, int y); void PanitentWindow_OnContextMenu(PanitentWindow* pPanitentWindow, int x, int y); void PanitentWindow_OnDestroy(PanitentWindow* pPanitentWindow); void PanitentWindow_OnSize(PanitentWindow* pPanitentWindow, UINT state, int cx, int cy); +void PanitentWindow_OnActivate(PanitentWindow* pPanitentWindow, UINT state, HWND hwndActDeact, BOOL fMinimized); +BOOL PanitentWindow_OnNCCreate(PanitentWindow* pPanitentWindow, LPCREATESTRUCT lpcs); +LRESULT PanitentWindow_OnNCCalcSize(PanitentWindow* pPanitentWindow, BOOL fCalcValidRects, NCCALCSIZE_PARAMS* lpcsp); +LRESULT PanitentWindow_OnNCHitTest(PanitentWindow* pPanitentWindow, int x, int y); +LRESULT PanitentWindow_OnNCPaint(PanitentWindow* pPanitentWindow, HRGN hrgn); LRESULT CALLBACK PanitentWindow_UserProc(PanitentWindow* pPanitentWindow, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); PanitentWindow* PanitentWindow_Create(struct Application* app) { - PanitentWindow* window = calloc(1, sizeof(PanitentWindow)); + PanitentWindow* pPanitentWindow = malloc(sizeof(PanitentWindow)); - if (window) + if (pPanitentWindow) { - PanitentWindow_Init(window, app); + memset(pPanitentWindow, 0, sizeof(PanitentWindow)); + PanitentWindow_Init(pPanitentWindow, app); } - return window; + return pPanitentWindow; } -void PanitentWindow_Init(PanitentWindow* window, struct Application* app) +void PanitentWindow_Init(PanitentWindow* pPanitentWindow, struct Application* app) { - Window_Init(&window->base, app); - - window->base.szClassName = szClassName; - - window->base.OnCreate = (FnWindowOnCreate)PanitentWindow_OnCreate; - window->base.OnDestroy = (FnWindowOnDestroy)PanitentWindow_OnDestroy; - window->base.OnPaint = (FnWindowOnPaint)PanitentWindow_OnPaint; - window->base.OnSize = (FnWindowOnSize)PanitentWindow_OnSize; - window->base.PreRegister = (FnWindowPreRegister)PanitentWindow_PreRegister; - window->base.PreCreate = (FnWindowPreCreate)PanitentWindow_PreCreate; - window->base.UserProc = (FnWindowUserProc)PanitentWindow_UserProc; - window->base.PostCreate = (FnWindowPostCreate)PanitentWindow_PostCreate; + Window_Init(&pPanitentWindow->base, app); + + pPanitentWindow->base.szClassName = szClassName; + + pPanitentWindow->base.OnCreate = (FnWindowOnCreate)PanitentWindow_OnCreate; + pPanitentWindow->base.OnDestroy = (FnWindowOnDestroy)PanitentWindow_OnDestroy; + pPanitentWindow->base.OnPaint = (FnWindowOnPaint)PanitentWindow_OnPaint; + pPanitentWindow->base.OnSize = (FnWindowOnSize)PanitentWindow_OnSize; + pPanitentWindow->base.PreRegister = (FnWindowPreRegister)PanitentWindow_PreRegister; + pPanitentWindow->base.PreCreate = (FnWindowPreCreate)PanitentWindow_PreCreate; + pPanitentWindow->base.UserProc = (FnWindowUserProc)PanitentWindow_UserProc; + pPanitentWindow->base.PostCreate = (FnWindowPostCreate)PanitentWindow_PostCreate; + + pPanitentWindow->fCallDWP = FALSE; + pPanitentWindow->bCustomFrame = TRUE; } void PanitentWindow_PreRegister(LPWNDCLASSEX lpwcex) @@ -77,6 +95,8 @@ void PanitentWindow_OnPaint(PanitentWindow* window) hdc = BeginPaint(hwnd, &ps); + // PanitentWindow_PaintCustomCaption(window, hdc); + /* End painting the window */ EndPaint(hwnd, &ps); } @@ -115,6 +135,193 @@ void PanitentWindow_OnSize(PanitentWindow* pPanitentWindow, UINT state, int cx, } } +void PanitentWindow_OnActivate(PanitentWindow* pPanitentWindow, UINT state, HWND hwndActDeact, BOOL fMinimized) +{ + /* + HWND hWnd = Window_GetHWND(pPanitentWindow); + + MARGINS margins; + margins.cxLeftWidth = 0; + margins.cxRightWidth = 0; + margins.cyBottomHeight = 0; + margins.cyTopHeight = 64; + DwmExtendFrameIntoClientArea(hWnd, &margins); + */ +} + +BOOL PanitentWindow_OnNCCreate(PanitentWindow* pPanitentWindow, LPCREATESTRUCT lpcs) +{ + HWND hWnd = Window_GetHWND(pPanitentWindow); + + SetWindowTheme(hWnd, L"", L""); + + return TRUE; +} + +LRESULT PanitentWindow_OnNCCalcSize(PanitentWindow* pPanitentWindow, BOOL fCalcValidRects, NCCALCSIZE_PARAMS* lpcsp) +{ + + HWND hWnd = Window_GetHWND(pPanitentWindow); + /* + if (fCalcValidRects) + { + return 0; + } + + return Window_UserProcDefault(pPanitentWindow, hWnd, WM_NCCALCSIZE, (WPARAM)fCalcValidRects, (LPARAM)lpcsp); + */ + + + int g_borderSize = 4; + int g_captionHeight = 32; + + + DWORD dwStyle = GetWindowStyle(hWnd); + + if (fCalcValidRects) + { + lpcsp->rgrc[0].left += g_borderSize; + lpcsp->rgrc[0].top += g_borderSize; + lpcsp->rgrc[0].right -= g_borderSize; + lpcsp->rgrc[0].bottom -= g_borderSize; + + if (dwStyle & WS_CAPTION) + { + lpcsp->rgrc[0].top += g_captionHeight + g_borderSize; + if (!(dwStyle & WS_THICKFRAME)) + { + lpcsp->rgrc[0].top += g_borderSize; + } + } + + return WVR_ALIGNTOP; + } + + PRECT rc = (PRECT)lpcsp; + + rc->left += g_borderSize; + rc->top += g_borderSize; + + if (dwStyle & WS_CAPTION) + { + rc->top += g_captionHeight + g_borderSize; + if (!(dwStyle & WS_THICKFRAME)) + { + rc->top += g_borderSize; + } + } + + rc->right -= g_borderSize; + rc->bottom -= g_borderSize; + + return 0; +} + +LRESULT HitTestNCA(HWND hWnd, WPARAM wParam, LPARAM lParam) +{ + // Get the point coordinates for the hit test. + POINT ptMouse = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + + // Get the window rectangle. + RECT rcWindow; + GetWindowRect(hWnd, &rcWindow); + + // Get the frame rectangle, adjusted for the style without a caption. + RECT rcFrame = { 0 }; + AdjustWindowRectEx(&rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION, FALSE, NULL); + + // Determine if the hit test is for resizing. Default middle (1,1). + USHORT uRow = 1; + USHORT uCol = 1; + BOOL fOnResizeBorder = FALSE; + + // Determine if the point is at the top or bottom of the window. + if (ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + 64) + { + fOnResizeBorder = (ptMouse.y < (rcWindow.top - rcFrame.top)); + uRow = 0; + } + else if (ptMouse.y < rcWindow.bottom && ptMouse.y >= rcWindow.bottom - 0) + { + uRow = 2; + } + + // Determine if the point is at the left or right of the window. + if (ptMouse.x >= rcWindow.left && ptMouse.x < rcWindow.left + 0) + { + uCol = 0; // left side + } + else if (ptMouse.x < rcWindow.right && ptMouse.x >= rcWindow.right - 0) + { + uCol = 2; // right side + } + + // Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT) + LRESULT hitTests[3][3] = + { + { HTTOPLEFT, fOnResizeBorder ? HTTOP : HTCAPTION, HTTOPRIGHT }, + { HTLEFT, HTNOWHERE, HTRIGHT }, + { HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT }, + }; + + return hitTests[uRow][uCol]; +} + +LRESULT PanitentWindow_OnNCHitTest(PanitentWindow* pPanitentWindow, int x, int y) +{ + HWND hWnd = Window_GetHWND(pPanitentWindow); + + LRESULT lRet = HitTestNCA(hWnd, NULL, MAKELPARAM(x, y)); + + if (lRet != HTNOWHERE) + { + pPanitentWindow->fCallDWP = FALSE; + } + + return lRet; +} + +LRESULT PanitentWindow_OnNCPaint(PanitentWindow* pPanitentWindow, HRGN hrgn) +{ + HWND hWnd = Window_GetHWND(pPanitentWindow); + + HDC hdc = GetWindowDC(hWnd); + + RECT rcWindow; + GetWindowRect(hWnd, &rcWindow); + rcWindow.right -= rcWindow.left; + rcWindow.bottom -= rcWindow.top; + rcWindow.top = rcWindow.left = 0; + + SetDCBrushColor(hdc, Win32_HexToCOLORREF(L"#9185be")); + SetDCPenColor(hdc, Win32_HexToCOLORREF(L"#6d648e")); + SelectObject(hdc, GetStockObject(DC_BRUSH)); + SelectObject(hdc, GetStockObject(DC_PEN)); + Rectangle(hdc, rcWindow.left, rcWindow.top, rcWindow.right, rcWindow.bottom); + + SetDCBrushColor(hdc, Win32_HexToCOLORREF(L"#FF8800")); + Rectangle(hdc, 4, 0, 96, 24); + + SelectObject(hdc, Win32_GetUIFont()); + + int smIconWidth = GetSystemMetrics(SM_CXSMICON); + int smIconHeight = GetSystemMetrics(SM_CYSMICON); + HICON hIcon = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, smIconWidth, smIconHeight, 0); + DrawIconEx(hdc, 14, 2, hIcon, smIconWidth, smIconHeight, 0, NULL, DI_NORMAL); + + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, RGB(255, 255, 255)); + TextOut(hdc, 34, 4, L"Panit.ent", 9); + + + /* Draw caption buttons */ + SetDCBrushColor(hdc, Win32_HexToCOLORREF(L"#9185be")); + + Rectangle(hdc, rcWindow.right - 48 - 8, rcWindow.top, rcWindow.right - 8, rcWindow.top + 24); + + return 0; +} + /* Menu ID values */ enum { IDM_FILE_NEW = 1001, @@ -128,6 +335,8 @@ enum { IDM_EDIT_CLRCANVAS, IDM_WINDOW_TOOLS, + IDM_WINDOW_ACTIVITY_DIALOG, + IDM_WINDOW_PROPERTY_GRID, IDM_OPTIONS_SETTINGS, @@ -159,6 +368,8 @@ HMENU PanitentWindow_CreateMenu() hSubMenu = CreatePopupMenu(); AppendMenu(hSubMenu, MF_STRING, IDM_WINDOW_TOOLS, L"&Tools"); + AppendMenu(hSubMenu, MF_STRING, IDM_WINDOW_ACTIVITY_DIALOG, L"&Activity..."); + AppendMenu(hSubMenu, MF_STRING, IDM_WINDOW_PROPERTY_GRID, L"&Property Grid..."); AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hSubMenu, L"&Window"); CheckMenuItem(hSubMenu, IDM_WINDOW_TOOLS, MF_BYCOMMAND | MF_CHECKED); @@ -210,7 +421,15 @@ LRESULT PanitentWindow_OnCommand(PanitentWindow* pPanitentWindow, WPARAM wParam, break; case IDM_EDIT_CLRCANVAS: - Panitent_CmdClearCanvas((struct PanitentApplication *)pPanitentWindow->base.app); + Panitent_CmdClearCanvas((PanitentApplication*)pPanitentWindow->base.app); + break; + + case IDM_WINDOW_ACTIVITY_DIALOG: + Panitent_CmdShowActivityDialog((PanitentApplication*)pPanitentWindow->base.app); + break; + + case IDM_WINDOW_PROPERTY_GRID: + Panitent_CmdShowPropertyGridDialog((PanitentApplication*)pPanitentWindow->base.app); break; case IDM_OPTIONS_SETTINGS: @@ -218,7 +437,11 @@ LRESULT PanitentWindow_OnCommand(PanitentWindow* pPanitentWindow, WPARAM wParam, break; case IDM_HELP_LOG: - LogWindow_Create(Window_GetHWND((Window *)pPanitentWindow)); + { + LogWindow* pLogWindow = (LogWindow*)LogWindow_Create(pPanitentWindow->base.app); + Window_CreateWindow((Window*)pLogWindow, NULL); + Window_Show((Window*)pLogWindow, SW_SHOW); + } break; case IDM_HELP_ABOUT: @@ -231,7 +454,11 @@ LRESULT PanitentWindow_OnCommand(PanitentWindow* pPanitentWindow, WPARAM wParam, LRESULT CALLBACK PanitentWindow_UserProc(PanitentWindow* pPanitentWindow, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + LRESULT lRet = 0; + BOOL fCallDWP = !DwmDefWindowProc(hWnd, message, wParam, lParam, &lRet); + switch (message) { + case WM_RBUTTONUP: PanitentWindow_OnRButtonUp(pPanitentWindow, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); return TRUE; @@ -252,6 +479,32 @@ LRESULT CALLBACK PanitentWindow_UserProc(PanitentWindow* pPanitentWindow, HWND h break; } + if (pPanitentWindow->bCustomFrame) + { + switch (message) + { + case WM_NCCREATE: + return FloatingWindowContainer_OnNCCreate(pPanitentWindow, (LPCREATESTRUCT)lParam); + break; + + case WM_NCPAINT: + return PanitentWindow_OnNCPaint(pPanitentWindow, (HRGN)(wParam)); + break; + + case WM_NCCALCSIZE: + return PanitentWindow_OnNCCalcSize(pPanitentWindow, (BOOL)(wParam), (NCCALCSIZE_PARAMS*)(lParam)); + break; + + case WM_NCHITTEST: + return PanitentWindow_OnNCHitTest(pPanitentWindow, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam)); + break; + + case WM_ACTIVATE: + PanitentWindow_OnActivate(pPanitentWindow, (UINT)LOWORD(wParam), (HWND)(lParam), (BOOL)HIWORD(wParam)); + break; + } + } + return Window_UserProcDefault(pPanitentWindow, hWnd, message, wParam, lParam); } @@ -269,6 +522,14 @@ void PanitentWindow_PreCreate(LPCREATESTRUCT lpcs) BOOL PanitentWindow_OnCreate(PanitentWindow* pPanitentWindow, LPCREATESTRUCT lpcs) { + HWND hWnd = Window_GetHWND(pPanitentWindow); + + RECT rcClient; + GetWindowRect(hWnd, &rcClient); + + /* Inform the application of the frame change */ + SetWindowPos(hWnd, NULL, rcClient.left, rcClient.top, Win32_Rect_GetWidth(&rcClient), Win32_Rect_GetHeight(&rcClient), SWP_FRAMECHANGED); + return TRUE; } @@ -280,13 +541,12 @@ void PanitentWindow_PostCreate(PanitentWindow* pPanitentWindow) /* Allocate dockhost window data structure */ DockHostWindow* pDockHostWindow = DockHostWindow_Create(pPanitentWindow->base.app); - - HWND hWndDockHost = Window_CreateWindow((Window *)pDockHostWindow, hWnd); + Window_CreateWindow((Window *)pDockHostWindow, hWnd); pPanitentWindow->m_pDockHostWindow = pDockHostWindow; /* Get dockhost client rect*/ - RECT rcDockHost = {0}; - GetClientRect(hWndDockHost, &rcDockHost); + RECT rcDockHost = { 0 }; + Window_GetClientRect(pDockHostWindow, &rcDockHost); /* Create and assign root dock node */ TreeNode* pRoot = (TreeNode*)calloc(1, sizeof(TreeNode)); @@ -298,10 +558,91 @@ void PanitentWindow_PostCreate(PanitentWindow* pPanitentWindow) Panitent_DockHostInit(pDockHostWindow, pRoot); - RECT rcClient = { 0 }; - Window_GetClientRect((Window *)pPanitentWindow, &rcClient); - if (pPanitentWindow->m_pDockHostWindow) + Win32_FitChild((Window*)pDockHostWindow, (Window*)pPanitentWindow); +} + +// Paint the title on the custom frame. +void PanitentWindow_PaintCustomCaption(PanitentWindow* pPanitentWindow, HDC hdc) +{ + HWND hWnd = Window_GetHWND(pPanitentWindow); + + RECT rcClient; + Window_GetClientRect(pPanitentWindow, &rcClient); + + HTHEME hTheme = OpenThemeData(NULL, L"CompositedWindow::Window"); + if (hTheme) { - SetWindowPos(Window_GetHWND((Window*)pPanitentWindow->m_pDockHostWindow), NULL, 0, 0, rcClient.right, rcClient.bottom, SWP_NOACTIVATE | SWP_NOZORDER); + HDC hdcPaint = CreateCompatibleDC(hdc); + + if (hdcPaint) + { + int cx = Win32_Rect_GetWidth(&rcClient); + int cy = Win32_Rect_GetHeight(&rcClient); + + // Define the BITMAPINFO structure used to draw text. + // Note that biHeight is negative. This is done because + // DrawThemeTextEx() needs the bitmap to be in top-to-bottom + // order. + BITMAPINFO dib = { 0 }; + dib.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + dib.bmiHeader.biWidth = cx; + dib.bmiHeader.biHeight = -cy; + dib.bmiHeader.biPlanes = 1; + dib.bmiHeader.biBitCount = 32; + dib.bmiHeader.biCompression = BI_RGB; + + HBITMAP hbm = CreateDIBSection(hdc, &dib, DIB_RGB_COLORS, NULL, NULL, 0); + if (hbm) + { + HBITMAP hbmOld = (HBITMAP)SelectObject(hdcPaint, hbm); + + // Setup the theme drawing options. + DTTOPTS dttOpts = { sizeof(DTTOPTS) }; + dttOpts.dwFlags = DTT_COMPOSITED | DTT_GLOWSIZE; + dttOpts.iGlowSize = 15; + + // Select a font. + LOGFONT lgFont; + HFONT hFontOld = NULL; + if (SUCCEEDED(GetThemeSysFont(hTheme, TMT_CAPTIONFONT, &lgFont))) + { + lgFont.lfWeight = FW_BOLD; + + HFONT hFont = CreateFontIndirect(&lgFont); + hFontOld = (HFONT)SelectObject(hdcPaint, hFont); + } + + WCHAR szTitle[256] = L""; + GetWindowText(hWnd, szTitle, 256); + + + // Draw the title. + RECT rcPaint = rcClient; + rcPaint.top += 8; + rcPaint.right -= 125; + rcPaint.left += 8; + rcPaint.bottom = 50; + DrawThemeTextEx(hTheme, + hdcPaint, + 0, 0, + szTitle, + -1, + DT_LEFT | DT_WORD_ELLIPSIS, + &rcPaint, + &dttOpts); + + // Blit text to the frame. + BitBlt(hdc, 0, 0, cx, cy, hdcPaint, 0, 0, SRCCOPY); + + SelectObject(hdcPaint, hbmOld); + if (hFontOld) + { + SelectObject(hdcPaint, hFontOld); + } + DeleteObject(hbm); + } + DeleteDC(hdcPaint); + } + CloseThemeData(hTheme); } } diff --git a/src/panitentwindow.h b/src/panitentwindow.h index 1e668e2..814f0fa 100644 --- a/src/panitentwindow.h +++ b/src/panitentwindow.h @@ -13,6 +13,8 @@ struct PanitentWindow HWND m_hWndPalette; HWND m_hWndToolbox; TreeNode* m_viewportNode; + BOOL fCallDWP; + BOOL bCustomFrame; }; PanitentWindow* PanitentWindow_Create(struct Application*); diff --git a/src/pntxml.c b/src/pntxml.c new file mode 100644 index 0000000..09bd057 --- /dev/null +++ b/src/pntxml.c @@ -0,0 +1,1132 @@ +/** + * pntxml - a fork of ooxi/xml.c adapted for using with WinAPI and + * wide-character strings + * + * CHANGES: + * - uint8_t, char, etc. replaced with wide-character PWSTR, PCWSTR and WCHAR + * - Symbols renamed to distinguish it from original xml.c and accomplish + Panit.ent coding style + * - Fixed common typos in comments + * + * Copyright (c) 2012 ooxi/xml.c + * https://github.com/ooxi/xml.c + * + * Copyright (c) 2023 Aragajaga + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "precomp.h" + +#include "pntxml.h" + +#include +#include + +/** + * Public domain strtok_r by Charlie Gordon + * + * from comp.lang.c 9/14/2007 + * + * http://groups.google.com/group/comp.lang.c/msg/2ab1ecbb86646684 + * + * (Declaration that it's public domain): + * http://groups.google.com/group/comp.lang.c/msg/7c7b39328fefab9c + */ +static PWSTR XML_strtok_r(PWSTR pszStr, PCWSTR pszDelim, PCWSTR* ppszNextp) +{ + PWSTR pszRet; + + if (!pszStr) + { + pszStr = *ppszNextp; + } + + pszStr += wcsspn(pszStr, pszDelim); + + if (*pszStr == L'\0') + { + return NULL; + } + + pszRet = pszStr; + + pszStr += wcscspn(pszStr, pszDelim); + + if (*pszStr) + { + *pszStr++ = L'\0'; + } + + *ppszNextp = pszStr; + + return pszRet; +} + +/** + * [OPAQUE API] + * + * UTF-8 text + */ +typedef struct XMLString XMLString; +struct XMLString { + PCWSTR pszBuffer; + size_t nLength; +}; + +/** + * [OPAQUE API] + * + * An XMLAttribute may contain text content. + */ +typedef struct XMLAttribute XMLAttribute; +struct XMLAttribute { + XMLString* pxsName; + XMLString* pxsContent; +}; + +/** + * [OPAQUE API] + * + * An XMLNode will always contain a tag name, a 0-ternimated list of attributes + * and a 0-terminated list of children. Moreover it node may contain text content. + */ +typedef struct XMLNode XMLNode; +struct XMLNode { + XMLString* pxsName; + XMLString* pxsContent; + XMLAttribute** ppAttributes; + XMLNode** ppChildren; +}; + +/** + * [OPAQUE API] + * + * An XMLDocument simply contains the root node and the underlying buffer + */ +typedef struct XMLDocument XMLDocument; +struct XMLDocument { + struct { + PWSTR pszBuffer; + size_t length; + } buffer; + + XMLNode* pRoot; +}; + +/** + * [PRIVATE] + * + * Parser context + */ +typedef struct XMLParser XMLParser; +struct XMLParser { + PWSTR pszBuffer; + size_t nPosition; + size_t nLength; +}; + +/** + * [PRIVATE] + * + * Character offsets + */ +typedef enum XMLParserOffset XMLParserOffset; +enum XMLParserOffset { + NO_CHARACTER = -1, + CURRENT_CHARACTER = 0, + NEXT_CHARACTER = 1, +}; + +/** + * [PRIVATE] + * + * @return Number of attributes in 0-terminated array + */ +static size_t XML_GetAttributeCount(XMLAttribute** ppAttributes) +{ + size_t nElements = 0; + + while (ppAttributes[nElements]) + { + ++nElements; + } + + return nElements; +} + +/** + * [PRIVATE] + * + * @return Number of nodes in 0-terminated array + */ +static size_t XML_GetNodeCount(XMLAttribute** ppNodes) +{ + size_t nElements = 0; + + while (ppNodes[nElements]) + { + ++nElements; + } + + return nElements; +} + +/** + * [PRIVATE] + * + * @warning No UTF conversions will be attempted + * + * @return true if a == b + */ +static BOOL XMLString_Equals(XMLString* a, XMLString* b) +{ + if (a->nLength != b->nLength) + { + return FALSE; + } + + for (size_t i = 0; i < a->nLength; ++i) + { + if (a->pszBuffer[i] != b->pszBuffer[i]) + { + return FALSE; + } + } + + return TRUE; +} + +/** + * [PRIVATE] + * + * Clone XMLString to raw pXMLString + * + * @return Raw pXMLString pointer, otherwise NULL if failed + */ +static PWSTR XMLString_Clone(XMLString* pXMLString) +{ + if (!pXMLString) + { + return 0; + } + + PWSTR pszClone = (PWSTR)malloc((pXMLString->nLength + 1) * sizeof(WCHAR)); + + if (pszClone) + { + memset(pszClone, 0, (pXMLString->nLength + 1) * sizeof(WCHAR)); + + XMLString_Copy(pXMLString, pszClone, pXMLString->nLength); + pszClone[pXMLString->nLength] = 0; + + return pszClone; + } + + + return NULL; +} + +/** + * [PRIVATE] + * + * Frees the resources allocated by the string + * + * @warning `buffer` must _not_ be freed, since it is a reference to the + * document's buffer + */ +static void XMLString_Free(XMLString* pXMLString) +{ + free(pXMLString); +} + +/** + * [PRIVATE] + * + * Frees the resources allocated by the pXMLAttribute + */ +static void XMLAttribute_Free(XMLAttribute* pXMLAttribute) +{ + if (pXMLAttribute->pxsName) + { + XMLString_Free(pXMLAttribute->pxsName); + } + + if (pXMLAttribute->pxsContent) + { + XMLString_Free(pXMLAttribute->pxsContent); + } + + free(pXMLAttribute); +} + +/** + * [PRIVATE] + * + * Frees the resources allocated by the node + */ +static void XMLNode_Free(XMLNode* pNode) +{ + XMLString_Free(pNode->pxsName); + + if (pNode->pxsContent) + { + XMLString_Free(pNode->pxsContent); + } + + XMLAttribute** ppAttribute = pNode->ppAttributes; + while (*ppAttribute) + { + XMLAttribute_Free(*ppAttribute); + ++ppAttribute; + } + free(pNode->ppAttributes); + + XMLNode** ppChildNode = pNode->ppChildren; + while (*ppChildNode) + { + XMLNode_Free(*ppChildNode); + ++ppChildNode; + } + free(pNode->ppChildren); + + free(pNode); +} + +/** + * [PRIVATE] + * + * Echos the parsers call stack for debugging purposes + */ +#ifdef XML_PARSER_VERBOSE +static void XMLParser_Info(XMLParser* pXMLParser, PWSTR pszMessage) +{ + fwprintf_s(stdout, L"XMLParser_Info %pXMLString\n", pszMessage); +} +#else +#define XMLParser_Info(parser, pszMessage) {} +#endif + +/** + * [PRIVATE] + * + * Echos an error regarding the parser's source to the console + */ +static void XMLParser_Error(XMLParser* pXMLParser, XMLParserOffset offset, PCWSTR pszMessage) +{ + int nRow = 0; + int nColumn = 0; + + #define min(X,Y) ((X) < (Y) ? (X) : (Y)) + #define MaxOf(X,Y) ((X) > (Y) ? (X) : (Y)) + size_t nCharacter = MaxOf(0, min(pXMLParser->nLength, pXMLParser->nPosition + offset)); + #undef min + #undef max + + for (size_t nPosition = 0; nPosition < nCharacter; ++nPosition) + { + nColumn++; + + if ('\n' == pXMLParser->pszBuffer[nPosition]) + { + nRow++; + nColumn = 0; + } + } + + if (NO_CHARACTER != offset) + { + fwprintf_s(stderr, L"XMLParser_Error at %i:%i (is %c): %s\n", + nRow + 1, nColumn, pXMLParser->pszBuffer[nCharacter], pszMessage + ); + } + else { + fwprintf_s(stderr, L"XMLParser_Error at %i:%i: %s\n", + nRow + 1, nColumn, pszMessage + ); + } +} + +/** + * [PRIVATE] + * + * Returns the n-th not-whitespace byte in parser and 0 if such a byte does not + * exist + */ +static WCHAR XMLParser_Peek(XMLParser* pXMLParser, size_t n) +{ + size_t nPosition = pXMLParser->nPosition; + + while (nPosition < pXMLParser->nLength) + { + if (!isspace(pXMLParser->pszBuffer[nPosition])) + { + if (n == 0) + { + return pXMLParser->pszBuffer[nPosition]; + } + else { + --n; + } + } + + nPosition++; + } + + return 0; +} + +/** + * [PRIVATE] + * + * Moves the parser's position n bytes. If the new position would be out of + * bounds, it will be converted to the bounds itself + */ +static void XMLParser_Consume(XMLParser* pXMLParser, size_t n) +{ + /* Debug information */ + #ifdef XML_PARSER_VERBOSE + #define min(X,Y) ((X) < (Y) ? (X) : (Y)) + + PWSTR pszConsumed = alloca((n + 1) * sizeof(WCHAR)); + memcpy(pszConsumed, &pXMLParser->pszBuffer[pXMLParser->nPosition], min(n, pXMLParser->nLength - pXMLParser->nPosition)); + pszConsumed[n] = 0; + #undef min + + size_t nMessageBufferLength = 512; + PWSTR pszMessageBuffer = alloca(512 * sizeof(WCHAR)); + swprintf_s(pszMessageBuffer, nMessageBufferLength, L"Consuming %li bytes \"%s\"", (long)n, pszConsumed); + pszMessageBuffer[nMessageBufferLength - 1] = 0; + + XMLParser_Info(pXMLParser, pszMessageBuffer); + #endif + + /* Move the position forward */ + pXMLParser->nPosition += n; + + /* Don't go too far + * + * @warning Valid because pXMLParser->nLength must be greater than 0 + */ + if (pXMLParser->nPosition >= pXMLParser->nLength) + { + pXMLParser->nPosition = pXMLParser->nLength - 1; + } +} + +/** + * [PRIVATE] + * + * Skips to the next non-whitespace character + */ +static void XMLParser_SkipWhitespace(XMLParser* pXMLParser) +{ + XMLParser_Info(pXMLParser, L"whilespace"); + + while (isspace(pXMLParser->pszBuffer[pXMLParser->nPosition])) + { + if (pXMLParser->nPosition + 1 >= pXMLParser->nLength) + { + return; + } + else { + pXMLParser->nPosition++; + } + } +} + +/** + * [PRIVATE] + * + * Finds and creates all attributes on the given node. + * + * @author Blake Felt + * @see http://github.com/Molorius + */ +static XMLAttribute** XMLParser_FindAttributes(XMLParser* pXMLParser, XMLString* pxsTagOpen) +{ + XMLParser_Info(pXMLParser, L"XMLParser_FindAttributes"); + + PWSTR pszTemp; + PWSTR pszRest = NULL; + PWSTR pszToken; + PWSTR pszName; + PWSTR pszContent; + PCWSTR pszStartName; + PCWSTR pszStartContent; + size_t nOldElements; + size_t nNewElements; + XMLAttribute* pNewAttribute; + XMLAttribute** ppAttributes; + int nPosition; + + ppAttributes = (XMLAttribute**)malloc(sizeof(XMLAttribute*)); + if (!ppAttributes) { + /* Failed to allocate memory */ + return NULL; + } + + ppAttributes[0] = NULL; + + pszTemp = (PWSTR)XMLString_Clone(pxsTagOpen); + + /* Skip the first value */ + pszToken = XML_strtok_r(pszTemp, L" ", &pszRest); + if (!pszToken) + { + goto cleanup; + } + pxsTagOpen->nLength = wcslen(pszToken); + + for (pszToken = XML_strtok_r(NULL, L" ", &pszRest); pszToken != NULL; pszToken = XML_strtok_r(NULL, L" ", &pszRest)) + { + pszName = (PWSTR)malloc((wcslen(pszToken) + 1) * sizeof(WCHAR)); + pszContent = (PWSTR)malloc((wcslen(pszToken) + 1) * sizeof(WCHAR)); + /* %s=\"%s\" wasn't working for some reason, ugly hack to make it work */ + if (swscanf_s(pszToken, L"%[^=]=\"%[^\"]", pszName, pszContent) != 2) + { + if (swscanf_s(pszToken, L"%[^=]=\'%[^\']", pszName, pszContent) != 2) + { + free(pszName); + free(pszContent); + continue; + } + } + nPosition = pszToken - pszTemp; + pszStartName = &pxsTagOpen->pszBuffer[nPosition]; + pszStartContent = &pxsTagOpen->pszBuffer[nPosition + wcslen(pszName) + 2]; + + pNewAttribute = (XMLAttribute*)malloc(sizeof(XMLAttribute)); + + pNewAttribute->pxsName = (XMLString*)malloc(sizeof(XMLString)); + pNewAttribute->pxsName->pszBuffer = (PWSTR)pszStartName; + pNewAttribute->pxsName->nLength = wcslen(pszStartName); + + pNewAttribute->pxsContent = (XMLString*)malloc(sizeof(XMLString)); + pNewAttribute->pxsContent->pszBuffer = (PWSTR)pszStartContent; + pNewAttribute->pxsContent->nLength = wcslen(pszStartContent); + + nOldElements = XML_GetAttributeCount(ppAttributes); + nNewElements = nOldElements + 1; + ppAttributes = (XMLAttribute**)realloc(ppAttributes, (nNewElements + 1) * sizeof(XMLAttribute*)); + + ppAttributes[nNewElements - 1] = pNewAttribute; + ppAttributes[nNewElements] = 0; + + free(pszName); + free(pszContent); + } + +cleanup: + free(pszTemp); + return ppAttributes; +} + +/** + * [PRIVATE] + * + * Parses the pxsTagName out of the an XML tag'pXMLString ending + * + * ---( Example )--- + * tag_name> + * --- + */ +static XMLString* XMLParser_ParseTagEnd(XMLParser* pXMLParser) +{ + XMLParser_Info(pXMLParser, L"XMLParser_ParseTagEnd"); + size_t nStart = pXMLParser->nPosition; + size_t nLength = 0; + + /* Parse until `>' or a whitespace is reached */ + while (nStart + nLength < pXMLParser->nLength) + { + WCHAR chCurrent = XMLParser_Peek(pXMLParser, CURRENT_CHARACTER); + + if ((L'>' == chCurrent) || isspace(chCurrent)) + { + break; + } + else { + XMLParser_Consume(pXMLParser, 1); + nLength++; + } + } + + /* Consume `>' */ + if (L'>' != XMLParser_Peek(pXMLParser, CURRENT_CHARACTER)) + { + XMLParser_Error(pXMLParser, CURRENT_CHARACTER, L"XMLParser_ParseTagEnd: Expected tag end"); + return 0; + } + XMLParser_Consume(pXMLParser, 1); + + /* Return parsed tag pxsTagName */ + XMLString* pxsTagName = (XMLString*)malloc(sizeof(XMLString)); + pxsTagName->pszBuffer = &pXMLParser->pszBuffer[nStart]; + pxsTagName->nLength = nLength; + return pxsTagName; +} + +/** + * [PRIVATE] + * + * Parses an opening XML tag without attributes + * + * ---( Example )--- + * + * --- + */ +static XMLString* XMLParser_ParseTagOpen(XMLParser* pXMLParser) +{ + XMLParser_Info(pXMLParser, L"XMLParser_ParseTagOpen"); + + XMLParser_SkipWhitespace(pXMLParser); + + /* Consume `<' */ + if (L'<' != XMLParser_Peek(pXMLParser, CURRENT_CHARACTER)) + { + XMLParser_Error(pXMLParser, CURRENT_CHARACTER, L"XMLParser_ParseTagOpen: Expected opening tag"); + return 0; + } + XMLParser_Consume(pXMLParser, 1); + + /* Consume tag name */ + return XMLParser_ParseTagEnd(pXMLParser); +} + +/** + * [PRIVATE] + * + * Parses an closing XML tag without attributes + * + * ---( Example )--- + * + * --- + */ +static XMLString* XMLParser_ParseTagClose(XMLParser* pXMLParser) +{ + XMLParser_Info(pXMLParser, L"XMLParser_ParseTagClose"); + + XMLParser_SkipWhitespace(pXMLParser); + + /* Consume `nPosition; + size_t nLength = 0; + + /* Consume until `<' is reached */ + while (nStart + nLength < pXMLParser->nLength) + { + WCHAR current = XMLParser_Peek(pXMLParser, CURRENT_CHARACTER); + + if (L'<' == current) + { + break; + } + else { + XMLParser_Consume(pXMLParser, 1); + nLength++; + } + } + + /* Next character must be an `<' or we have reached end of file */ + if (L'<' != XMLParser_Peek(pXMLParser, CURRENT_CHARACTER)) + { + XMLParser_Error(pXMLParser, CURRENT_CHARACTER, L"XMLParser_ParseContent: Expected <"); + return 0; + } + + /* Ignore tailing whitespace */ + while ((nLength > 0) && isspace(pXMLParser->pszBuffer[nStart + nLength - 1])) + { + nLength--; + } + + /* Return text */ + XMLString* pxsContent = (XMLString*)malloc(sizeof(XMLString)); + pxsContent->pszBuffer = &pXMLParser->pszBuffer[nStart]; + pxsContent->nLength = nLength; + return pxsContent; +} + +/** + * [PRIVATE] + * + * Parses an XML fragment node + * + * ---( Example without children )--- + * Text + * --- + * + * ---( Example with children )--- + * + * Text + * Text + * Content + * + * --- + */ +static XMLNode* XMLParser_ParseNode(XMLParser* pXMLParser) +{ + XMLParser_Info(pXMLParser, L"XMLParser_ParseNode"); + + /* Setup variables */ + XMLString* pxsTagOpen = NULL; + XMLString* pxsTagClose = NULL; + XMLString* pxsContent = NULL; + + size_t nOriginalLength; + XMLAttribute** ppAttributes; + + XMLNode** ppChildren = (XMLNode**)malloc(sizeof(XMLNode*)); + ppChildren[0] = 0; + + /* Parse open tag */ + pxsTagOpen = XMLParser_ParseTagOpen(pXMLParser); + if (!pxsTagOpen) + { + XMLParser_Error(pXMLParser, NO_CHARACTER, L"XMLParser_ParseNode: tag open"); + goto exit_failure; + } + + nOriginalLength = pxsTagOpen->nLength; + ppAttributes = XMLParser_FindAttributes(pXMLParser, pxsTagOpen); + + /* If tag ends with `/' it's self closing, skip content lookup */ + if (pxsTagOpen->nLength > 0 && L'/' == pxsTagOpen->pszBuffer[nOriginalLength - 1]) + { + /* Drop `/' */ + goto node_creation; + } + + /* If the content does not start with '<', a text content is assumed */ + if (L'<' != XMLParser_Peek(pXMLParser, CURRENT_CHARACTER)) + { + pxsContent = XMLParser_ParseContent(pXMLParser); + + if (!pxsContent) + { + XMLParser_Error(pXMLParser, 0, L"XMLParser_ParseNode: content"); + goto exit_failure; + } + } + /* Otherwise ppChildren are to be expected */ + else { + while (L'/' != XMLParser_Peek(pXMLParser, NEXT_CHARACTER)) + { + /* Parse child pNode */ + XMLNode* pChildNode = XMLParser_ParseNode(pXMLParser); + if (!pChildNode) + { + XMLParser_Error(pXMLParser, NEXT_CHARACTER, L"XMLParser_ParseNode: child"); + goto exit_failure; + } + + /* Grow child array */ + size_t nOldElements = XML_GetNodeCount(ppChildren); + size_t nNewElements = nOldElements + 1; + ppChildren = (XMLNode**)realloc(ppChildren, (nNewElements + 1) * sizeof(XMLNode*)); + + /* Save chld */ + ppChildren[nNewElements - 1] = pChildNode; + ppChildren[nNewElements] = 0; + } + } + + /* Parse close tag */ + pxsTagClose = XMLParser_ParseTagClose(pXMLParser); + if (!pxsTagClose) + { + XMLParser_Error(pXMLParser, NO_CHARACTER, L"XMLParser_ParseNode: tag close"); + goto exit_failure; + } + + /* Close tag has to match open tag */ + if (!XMLString_Equals(pxsTagOpen, pxsTagClose)) + { + XMLParser_Error(pXMLParser, NO_CHARACTER, L"XMLParser_ParseNode: tag missmatch"); + goto exit_failure; + } + + /* Return parsed node */ + XMLString_Free(pxsTagClose); + +node_creation:; + XMLNode* pXMLNode = (XMLNode*)malloc(sizeof(XMLNode)); + pXMLNode->pxsName = pxsTagOpen; + pXMLNode->pxsContent = pxsContent; + pXMLNode->ppAttributes = ppAttributes; + pXMLNode->ppChildren = ppChildren; + return pXMLNode; + + /* A failure occured, so free all allocated resources */ +exit_failure: + if (pxsTagOpen) + { + XMLString_Free(pxsTagOpen); + } + if (pxsTagClose) + { + XMLString_Free(pxsTagClose); + } + if (pxsContent) + { + XMLString_Free(pxsContent); + } + + XMLNode** pChild = ppChildren; + while (*pChild) + { + XMLNode_Free(*pChild); + ++pChild; + } + free(ppChildren); + + return 0; +} + +/** + * [PUBLIC API] + */ +XMLDocument* XML_ParseDocument(PWSTR pszBuffer, size_t nLength) +{ + /* Initialize parser */ + XMLParser parser = { + .pszBuffer = pszBuffer, + .nPosition = 0, + .nLength = nLength + }; + + /* An empty buffer can never contain a valid document */ + if (!nLength) + { + XMLParser_Error(&parser, NO_CHARACTER, L"XML_ParseDocument: length equals zero"); + return 0; + } + + /* Parse the root node */ + XMLNode* pRoot = XMLParser_ParseNode(&parser); + if (!pRoot) + { + XMLParser_Error(&parser, NO_CHARACTER, L"XML_ParseDocument: parsing pXMLDocument failed"); + return 0; + } + + /* Return parsed pXMLDocument */ + XMLDocument* pXMLDocument = (XMLDocument*)malloc(sizeof(XMLDocument)); + pXMLDocument->buffer.pszBuffer = pszBuffer; + pXMLDocument->buffer.length = nLength; + pXMLDocument->pRoot = pRoot; + + return pXMLDocument; +} + +/** + * [PUBLIC API] + */ +XMLDocument* XML_OpenDocument(FILE* pfSource) +{ + /* Prepare buffer */ + size_t const nReadChunk = 1; // TODO 4096 + + size_t nDocumentLength = 0; + size_t nBufferSize = 1; // TODO 4096 + PWSTR pszBuffer = (PWSTR)malloc(nBufferSize * sizeof(WCHAR)); + + /* Read whole file into buffer */ + while (!feof(pfSource)) + { + /* Reallocate buffer */ + if (nBufferSize - nDocumentLength < nReadChunk) + { + pszBuffer = (PWSTR)realloc(pszBuffer, nBufferSize + 2 * nReadChunk); + nBufferSize += 2 * nReadChunk; + } + + size_t nRead = fread( + &pszBuffer[nDocumentLength], + sizeof(WCHAR), nReadChunk, + pfSource + ); + + nDocumentLength += nRead; + } + fclose(pfSource); + + /* Try to parse buffer */ + XMLDocument* pXMLDocument = XML_ParseDocument(pszBuffer, nDocumentLength); + + if (!pXMLDocument) + { + free(pszBuffer); + return 0; + } + + return pXMLDocument; +} + +/** + * [PUBLIC API] + */ +void XMLDocument_Free(XMLDocument* document, BOOL bFreeBuffer) +{ + XMLNode_Free(document->pRoot); + + if (bFreeBuffer) + { + free(document->buffer.pszBuffer); + } + + free(document); +} + +/** + * [PUBLIC API] + */ +XMLNode* XMLDocument_GetRoot(XMLDocument* pXMLDocument) +{ + return pXMLDocument->pRoot; +} + +/** + * [PUBLIC API] + */ +XMLString* XMLNode_GetName(XMLNode* pXMLNode) +{ + return pXMLNode->pxsName; +} + +/** + * [PUBLIC API] + */ +XMLString* XMLNode_GetContent(XMLNode* pXMLNode) +{ + return pXMLNode->pxsContent; +} + +/** + * [PUBLIC API] + * + * @warning O(n) + */ +size_t XMLNode_GetChildrenCount(XMLNode* pXMLNode) +{ + return XML_GetNodeCount(pXMLNode->ppChildren); +} + +/** + * [PUBLIC API] + */ +XMLNode* XMLNode_GetChild(XMLNode* pXMLNode, size_t nChild) +{ + if (nChild >= XMLNode_GetChildrenCount(pXMLNode)) + { + return 0; + } + + return pXMLNode->ppChildren[nChild]; +} + +/** + * [PUBLIC API] + */ +size_t XMLNode_GetAttributesCount(XMLNode* pXMLNode) +{ + return XML_GetAttributeCount(pXMLNode->ppAttributes); +} + +/** + * [PUBLIC API] + */ +XMLString* XMLNode_GetAttributeName(XMLNode* pXMLNode, size_t nAttribute) +{ + if (nAttribute >= XMLNode_GetAttributesCount(pXMLNode)) + { + return 0; + } + + return pXMLNode->ppAttributes[nAttribute]->pxsName; +} + +/** + * [PUBLIC API] + */ +XMLString* XMLNode_GetAttributeContent(XMLNode* pNode, size_t nAttribute) +{ + if (nAttribute >= XMLNode_GetAttributesCount(pNode)) + { + return 0; + } + + return pNode->ppAttributes[nAttribute]->pxsContent; +} + +/** + * [PUBLIC API] + */ +XMLNode* XMLNode_EasyChild(XMLNode* pNodeParent, PCWSTR pszChildName, ...) +{ + /* Find children, one by one */ + XMLNode* pCurrentNode = pNodeParent; + + va_list arguments; + va_start(arguments, pszChildName); + + /* Descent to chCurrent child */ + while (pszChildName) + { + /* Convert pszChildName to XMLString for easy comparison */ + XMLString xsChildName = { + .pszBuffer = pszChildName, + .nLength = wcslen(pszChildName) + }; + + /* Iterate through all children */ + XMLNode* pNextNode = NULL; + + for (size_t i = 0; i < XMLNode_GetChildrenCount(pCurrentNode); ++i) + { + XMLNode* child = XMLNode_GetChild(pCurrentNode, i); + + if (XMLString_Equals(XMLNode_GetName(child), &xsChildName)) + { + if (!pNextNode) + { + pNextNode = child; + } + /* Two children with the same name */ + else { + va_end(arguments); + return 0; + } + } + } + + /* No child with that name found */ + if (!pNextNode) + { + va_end(arguments); + return 0; + } + pCurrentNode = pNextNode; + + /* Find name of next child */ + pszChildName = va_arg(arguments, PCWSTR); + } + va_end(arguments); + + /* Return chrreutn element */ + return pCurrentNode; +} + +/** + * [PUBLIC API] + */ +PWSTR XMLNode_EasyName(XMLNode* pXMLNode) +{ + if (!pXMLNode) + { + return 0; + } + + return XMLString_Clone(XMLNode_GetName(pXMLNode)); +} + +/** + * [PUBLIC API] + */ +PWSTR XMLNode_EasyContent(XMLNode* pXMLNode) +{ + if (!pXMLNode) + { + return 0; + } + + return XMLString_Clone(XMLNode_GetContent(pXMLNode)); +} + +/** + * [PUBLIC API] + */ +size_t XMLString_Length(XMLString* pXMLString) +{ + if (!pXMLString) + { + return 0; + } + + return pXMLString->nLength; +} + +/** + * [PUBLIC API] + */ +void XMLString_Copy(XMLString* pXMLString, PWSTR pszBuffer, size_t length) +{ + if (!pXMLString) + { + return; + } + + #define min(X,Y) ((X) < (Y) ? (X) : (Y)) + length = min(length, pXMLString->nLength); + #undef min + + memcpy(pszBuffer, pXMLString->pszBuffer, length * sizeof(WCHAR)); +} diff --git a/src/pntxml.h b/src/pntxml.h new file mode 100644 index 0000000..c4cce76 --- /dev/null +++ b/src/pntxml.h @@ -0,0 +1,146 @@ +#pragma once + +/** + * pntxml - a fork of ooxi/xml.c adapted for using with WinAPI and + * wide-character strings + * + * CHANGES: + * - uint8_t, char, etc. replaced with wide-character PWSTR, PCWSTR and WCHAR + * - Symbols renamed to distinguish it from original xml.c and accomplish + Panit.ent coding style + * - Fixed common typos in comments + * + * Copyright (c) 2012 ooxi/xml.c + * https://github.com/ooxi/xml.c + * + * Copyright (c) 2023 Aragajaga + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* Opaque structure holding the parsed xml document */ +typedef struct XMLDocument XMLDocument; +typedef struct XMLNode XMLNode; +typedef struct XMLAttribute XMLAttribute; + +/* Internal character dequence representation */ +typedef struct XMLString XMLString; + +/** + * Tries to parse the XML fragment in pszBuffer + * + * @param pszBuffer Chunk to parse + * @param length Size of the pszBuffer + * + * @warning `pszBuffer` will be referenced by the document, you may not free it + * until you free the XMLDocument + * @warning You have to call XMLDocument_Free after you finished using the + * document + * + * @return The parsed xml fragment if parsing was successful, NULL otherwise + */ +XMLDocument* XML_ParseDocument(PWSTR pszBuffer, size_t length); + +/** + * Tries to read an XML document from disk + * + * @param source File that will be read into an xml document. Will be closed + * + * @warning YOu have to call xml_document_free with free_buffer = true after you + * finished using the document + * + * @return The parsed xml fragment if parsing was successful, 0 otherwise + */ +XMLDocument* XML_OpenDocument(FILE* source); + +/** + * Frees all resources associated with the document. All XMLNode and XMLString + * references obtained through the document will be invalidated + * + * @param document XMLDocument to free + * @param free_buffer if true the internal pszBuffer supplied via pntxml_parse_buffer + * will be freed with the `free` system call + */ +void XMLDocument_Free(XMLDocument* pXMLDocument, BOOL bFreeBuffer); + +/** + * @return XMLNode representing the document root + */ +XMLNode* XMLDocument_GetRoot(XMLDocument* pXMLDocument); + +/** + * @return The XMLNode's tag name + */ +XMLString* XMLNode_GetName(XMLNode* pXMLNode); + +/** + * @return The XMLNode's string content (if available, otherwise NULL) + */ +XMLString* XMLNode_GetContent(XMLNode* pXMLNode); + +/** + * @return Number of child nodes + */ +size_t XMLNode_GetChildrenCount(XMLNode* pXMLNode); + +/** + * @return The n-th child or 0 if out of range + */ +XMLNode* XMLNode_GetChild(XMLNode* pXMLNode, size_t nChild); + +/** + * @return Number of attribute nodes + */ +size_t XMLNode_GetAttributesCount(XMLNode* pXMLNode); + +/** + * @return the n-th attribute name or 0 if out of range + */ +XMLString* XMLNode_GetAttributeName(XMLNode* pXMLNode, size_t nAttribute); + +/** + * @return the n-th attribute content or 0 if out of range + */ +XMLString* XMLNode_GetAttributeContent(XMLNode* pXMLNode, size_t nAttribute); + +/** + * @return The node described by the path or NULL if child cannot be found + * @warning Each element on the way must be unique + * @warning Last argument must be 0 + */ +XMLNode* XMLNode_GetChild(XMLNode* pXMLNode, PCWSTR pszChild, ...); + +/** + * @return 0-terminated copy of node name + * @warning User must free the result + */ +PWSTR XMLNode_EasyName(struct XMLNode* node); + +/** + * @return Length of the string + */ +size_t XMLString_Length(XMLString* pxsString); + +/** + * Copies the string into the supplied pszBuffer + * + * @warning String will not be 0-terminated + * @warning Will write at most length bytes, even if the string is longer + */ +void XMLString_Copy(XMLString* pxsString, PWSTR pszBuffer, size_t nLength); diff --git a/src/primitives_context.c b/src/primitives_context.c index dccfc68..e38a62f 100644 --- a/src/primitives_context.c +++ b/src/primitives_context.c @@ -45,7 +45,8 @@ void draw_rectangle(Canvas* canvas, int x0, int y0, int x1, int y1) if (g_primitives_context.fFill) { Plotter p; - PlotterData *pdat = calloc(1, sizeof(PlotterData)); + PlotterData *pdat = (PlotterData*)malloc(sizeof(PlotterData)); + memset(pdat, 0, sizeof(PlotterData)); if (!pdat) return; @@ -78,7 +79,8 @@ void draw_circle(Canvas* canvas, int cx, int cy, int radius) if (g_primitives_context.fFill) { Plotter p; - PlotterData *pdat = calloc(1, sizeof(PlotterData)); + PlotterData *pdat = (PlotterData*)malloc(sizeof(PlotterData)); + memset(pdat, 0, sizeof(PlotterData)); if (!pdat) return; @@ -95,7 +97,8 @@ void draw_circle(Canvas* canvas, int cx, int cy, int radius) if (g_primitives_context.fStroke) { Plotter p; - PlotterData *pdat = calloc(1, sizeof(PlotterData)); + PlotterData *pdat = (PlotterData*)malloc(sizeof(PlotterData)); + memset(pdat, 0, sizeof(PlotterData)); if (!pdat) return; @@ -114,7 +117,8 @@ void draw_circle(Canvas* canvas, int cx, int cy, int radius) void draw_filled_circle_color(Canvas* canvas, int cx, int cy, int radius, uint32_t color) { Plotter p; - PlotterData *pdat = calloc(1, sizeof(PlotterData)); + PlotterData *pdat = (PlotterData*)malloc(sizeof(PlotterData)); + memset(pdat, 0, sizeof(PlotterData)); if (!pdat) return; @@ -134,7 +138,8 @@ void draw_line_color(Canvas* canvas, int x0, int y0, int x1, int y1, if (g_thickness < 2) { Plotter p; - PlotterData *pdat = calloc(1, sizeof(PlotterData)); + PlotterData *pdat = (PlotterData*)malloc(sizeof(PlotterData)); + memset(pdat, 0, sizeof(PlotterData)); if (!pdat) return; @@ -202,7 +207,8 @@ void draw_line_color(Canvas* canvas, int x0, int y0, int x1, int y1, free(poly); Plotter p; - PlotterData *pdat = calloc(1, sizeof(PlotterData)); + PlotterData *pdat = (PlotterData*)malloc(sizeof(PlotterData)); + memset(pdat, 0, sizeof(PlotterData)); if (!pdat) return; @@ -234,7 +240,8 @@ void SetThickness(unsigned int thickness) PolygonPath* PolygonPath_Create() { - PolygonPath* poly = calloc(1, sizeof(PolygonPath)); + PolygonPath* poly = (PlotterData*)malloc(sizeof(PolygonPath)); + memset(poly, 0, sizeof(PolygonPath)); return poly; } @@ -252,7 +259,8 @@ void PolygonPath_Enclose(PolygonPath* poly) void PolygonPath_Fill(Canvas* canvas, PolygonPath* poly) { Plotter p; - PlotterData *pdat = calloc(1, sizeof(PlotterData)); + PlotterData *pdat = (PlotterData*)malloc(sizeof(PlotterData)); + memset(pdat, 0, sizeof(PlotterData)); if (!pdat) return; diff --git a/src/propgriddialog.c b/src/propgriddialog.c new file mode 100644 index 0000000..531a60d --- /dev/null +++ b/src/propgriddialog.c @@ -0,0 +1,86 @@ +#include "precomp.h" + +#include "win32/propertygridimpl.h" +#include "propgriddialog.h" +#include "resource.h" + +INT_PTR PropertyGridDialog_DlgUserProc(PropertyGridDialog* pPropertyGridDialog, UINT message, WPARAM wParam, LPARAM lParam); +void PropertyGridDialog_OnInitDialog(PropertyGridDialog* pPropertyGridDialog); +void PropertyGridDialog_OnOK(PropertyGridDialog* pPropertyGridDialog); +void PropertyGridDialog_OnCancel(PropertyGridDialog* pPropertyGridDialog); + + +PropertyGridDialog* PropertyGridDialog_Create(Application* pApp) +{ + PropertyGridDialog* pPropertyGridDialog = (PropertyGridDialog*)malloc(sizeof(PropertyGridDialog)); + + if (pPropertyGridDialog) + { + memset(pPropertyGridDialog, 0, sizeof(PropertyGridDialog)); + + PropertyGridDialog_Init(pPropertyGridDialog, pApp); + } + + return pPropertyGridDialog; +} + +void PropertyGridDialog_Init(PropertyGridDialog* pPropertyGridDialog, Application* pApp) +{ + Dialog_Init((Dialog*)&pPropertyGridDialog->base, pApp); + + pPropertyGridDialog->base.DlgUserProc = PropertyGridDialog_DlgUserProc; + pPropertyGridDialog->base.OnInitDialog = PropertyGridDialog_OnInitDialog; + pPropertyGridDialog->base.OnCancel = PropertyGridDialog_OnCancel; + pPropertyGridDialog->base.OnOK = PropertyGridDialog_OnOK; + + pPropertyGridDialog->m_pPropertyGrid = PropertyGridCtl_Create(pApp); +} + +INT_PTR PropertyGridDialog_DlgUserProc(PropertyGridDialog* pPropertyGridDialog, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + } + + return Dialog_DefaultDialogProc(pPropertyGridDialog, message, wParam, lParam); +} + +void PropertyGridDialog_OnInitDialog(PropertyGridDialog* pPropertyGridDialog) +{ + HWND hDlg = Window_GetHWND(pPropertyGridDialog); + + HWND hPropertyGrid = GetDlgItem(hDlg, IDC_PROPERTYGRID); + Window_Attach(pPropertyGridDialog->m_pPropertyGrid, hPropertyGrid); + + PROPGRIDITEM pgi; + PropGrid_ItemInit(pgi); + + WCHAR szCurValue[256] = L""; + + pgi.lpszCatalog = L"Edit, Static and combos"; + pgi.lpszPropName = L"Edit Field"; + pgi.lpCurValue = (LPARAM)szCurValue; + pgi.lpszPropDesc = L"This field is editable"; + pgi.iItemType = PIT_EDIT; + PropGrid_AddItem(hPropertyGrid, &pgi); + + pgi.lpszPropName = L"Static combo field"; + pgi.lpszPropDesc = L"Press F4 to view drop down."; + pgi.iItemType = PIT_COMBO; + PropGrid_AddItem(hPropertyGrid, &pgi); + + pgi.lpszCatalog = L"Check Boxes"; + pgi.lpszPropName = L"Save settings"; + pgi.iItemType = PIT_CHECK; + PropGrid_AddItem(hPropertyGrid, &pgi); +} + +void PropertyGridDialog_OnOK(PropertyGridDialog* pPropertyGridDialog) +{ + EndDialog(Window_GetHWND((Window*)pPropertyGridDialog), 0); +} + +void PropertyGridDialog_OnCancel(PropertyGridDialog* pPropertyGridDialog) +{ + EndDialog(Window_GetHWND((Window*)pPropertyGridDialog), 0); +} diff --git a/src/propgriddialog.h b/src/propgriddialog.h new file mode 100644 index 0000000..81d82d5 --- /dev/null +++ b/src/propgriddialog.h @@ -0,0 +1,13 @@ +#pragma once + +#include "win32/dialog.h" +#include "win32/propertygrid.h" + +typedef struct PropertyGridDialog PropertyGridDialog; +struct PropertyGridDialog { + Dialog base; + PropertyGridCtl* m_pPropertyGrid; +}; + +PropertyGridDialog* PropertyGridDialog_Create(Application* pApp); +void PropertyGridDialog_Init(PropertyGridDialog* pPropertyGridDialog, Application* pApp); diff --git a/src/res2.aps b/src/res2.aps new file mode 100644 index 0000000000000000000000000000000000000000..24874e7e19e08271645fd45e0b5df569eeaef9c1 GIT binary patch literal 448 zcmZQzU|>)H;{X347|28cjzFFY5PL9$GdKb1|Nj}G@{%l21_KXJ$k{3;v^ce>7)-_# z=a&{Gr^Xbe7UUPl1Y~69Zn1209iApz1pbmO<7Baww{rQy6MgfIgdmp$=j*vRRHmH7-Dm nFFZhgS%abm;TNDV96SNC5auA<4O0MeHwQ3m5o!?bhN%GnCO$-O literal 0 HcmV?d00001 diff --git a/src/resource.h b/src/resource.h index 56f2612..66697c7 100644 --- a/src/resource.h +++ b/src/resource.h @@ -15,6 +15,7 @@ #define IDB_DOCK_ICONS 1025 #define IDB_DOCK_BTNFRAME 1026 #define IDB_DOCK_SUGGEST 1027 +#define IDB_DOCKHOSTBG 1028 #define IDC_PICKER 1601 @@ -60,6 +61,12 @@ #define IDC_TREE1 1023 #define IDC_LIST1 1024 +#define IDD_ACTIVITYSTUB 135 +#define IDS_ACTIVITYTEXT 1025 + +#define IDD_PROPERTYGRIDDLG 136 +#define IDC_PROPERTYGRID 1026 + #define CBS_CDCLRSEL_SWATCHBIAS 128 #endif diff --git a/src/settings.c b/src/settings.c index 4c08f19..98071cf 100644 --- a/src/settings.c +++ b/src/settings.c @@ -442,7 +442,8 @@ void register_event_handler(int id, void (*callback)(WPARAM wParam, *cap = new_cap; } - struct callback_entry* e = calloc(1, sizeof(struct callback_entry)); + struct callback_entry* e = (struct callback_entry*)malloc(sizeof(struct callback_entry)); + memset(e, 0, sizeof(struct callback_entry)); if (!e) return; @@ -464,7 +465,8 @@ void process_event(WPARAM wParam, LPARAM lParam) void rlist_create(struct rlist* list, size_t capacity) { - list->array = calloc(capacity, sizeof(HWND)); + list->array = malloc(capacity * sizeof(HWND)); + memset(list->array, 0, capacity * sizeof(HWND)); list->capacity = capacity; list->size = 0; } @@ -485,7 +487,8 @@ void rlist_add(struct rlist* list, HWND hWnd) void alloc_handlers_vault() { - g_handlers.entries = calloc(2, sizeof(struct callback_entry)); + g_handlers.entries = malloc(2 * sizeof(struct callback_entry)); + memset(g_handlers.entries, 0, sizeof(struct callback_entry)); g_handlers.capacity = 2; g_handlers.size = 0; } diff --git a/src/settings_dialog.c b/src/settings_dialog.c index 733ec70..427fe21 100644 --- a/src/settings_dialog.c +++ b/src/settings_dialog.c @@ -151,7 +151,7 @@ void BuildWindowInitializationSettingsGroup(PGROUPBOX *ppGroupBox, HWND hParent) GroupBox_SetLayoutBox(*ppGroupBox, lbWindowInitialization); /* Set caption and size to group */ - GroupBox_SetCaption(*ppGroupBox, L"Initial window position and size"); + GroupBox_SetCaption(*ppGroupBox, L"Initial window nPosition and nSize"); GroupBox_SetSize(*ppGroupBox, 400, 200); GroupBox_Update(*ppGroupBox); } @@ -214,7 +214,8 @@ void SettingsDialog_OnCreate(HWND hWnd, LPCREATESTRUCT lpcs) PSETTINGSDIALOGDATA pData; PLAYOUTBOX pLayoutBox; - pData = (PSETTINGSDIALOGDATA) malloc(sizeof(SETTINGSDIALOGDATA)); + pData = (PSETTINGSDIALOGDATA)malloc(sizeof(SETTINGSDIALOGDATA)); + memset(pData, 0, sizeof(SETTINGSDIALOGDATA)); if (!pData) return; diff --git a/src/sharing/activitysharingclient.c b/src/sharing/activitysharingclient.c new file mode 100644 index 0000000..e1e2a15 --- /dev/null +++ b/src/sharing/activitysharingclient.c @@ -0,0 +1,3 @@ +#include "../precomp.h" + +#include "activitysharingclient.h" diff --git a/src/sharing/activitysharingclient.h b/src/sharing/activitysharingclient.h new file mode 100644 index 0000000..ccafbb6 --- /dev/null +++ b/src/sharing/activitysharingclient.h @@ -0,0 +1,7 @@ +#pragma once + +typedef struct ActivitySharingClient ActivitySharingClient; +struct ActivitySharingClient { + void* m_handler; + void (*SetStatusMessage)(void* handler, PCWSTR pszStatusMessage); +}; diff --git a/src/sharing/activitysharingmanager.c b/src/sharing/activitysharingmanager.c new file mode 100644 index 0000000..4e28094 --- /dev/null +++ b/src/sharing/activitysharingmanager.c @@ -0,0 +1,82 @@ +#include "../precomp.h" + +#include "activitysharingmanager.h" +#include "../log.h" + +ActivitySharingManager* ActivitySharingManager_Create() +{ + ActivitySharingManager* pActivitySharingManager = (ActivitySharingManager*)malloc(sizeof(ActivitySharingManager)); + + if (pActivitySharingManager) + { + memset(pActivitySharingManager, 0, sizeof(ActivitySharingManager)); + ActivitySharingManager_Init(pActivitySharingManager); + } + + return pActivitySharingManager; +} + +void ActivitySharingManager_Destroy(ActivitySharingManager* pActivitySharingManager) +{ + if (pActivitySharingManager->pszCurrentStatus) + { + free(pActivitySharingManager->pszCurrentStatus); + } +} + +void ActivitySharingManager_Init(ActivitySharingManager* pActivitySharingManager) +{ + ActivitySharingClient** pClients = (ActivitySharingClient**)malloc(sizeof(ActivitySharingClient*) * 16); + + if (pClients) + { + memset(pClients, 0, sizeof(ActivitySharingClient*) * 16); + pActivitySharingManager->pClients = pClients; + } + + pActivitySharingManager->nCount = 0; +} + +void ActivitySharingManager_AddClient(ActivitySharingManager* pActivitySharingManager, ActivitySharingClient* pActivitySharingClient) +{ + pActivitySharingManager->pClients[pActivitySharingManager->nCount++] = pActivitySharingClient; + + pActivitySharingClient->SetStatusMessage(pActivitySharingClient, pActivitySharingManager->pszCurrentStatus); +} + +void ActivitySharingManager_SetStatus(ActivitySharingManager* pActivitySharingManager, PCWSTR pszStatusMessage) +{ + LogMessage(LOGENTRY_TYPE_INFO, L"Activity", pszStatusMessage); + + /* Remove last activity status message */ + if (pActivitySharingManager->pszCurrentStatus) + { + free(pActivitySharingManager->pszCurrentStatus); + pActivitySharingManager->pszCurrentStatus = NULL; + } + + size_t len = wcslen(pszStatusMessage); + PWSTR pszCurrentStatus = (PWSTR)malloc((len + 1) * sizeof(WCHAR)); + if (pszCurrentStatus) + { + memcpy(pszCurrentStatus, pszStatusMessage, (len + 1) * sizeof(WCHAR)); + pszCurrentStatus[len] = L'\0'; + + pActivitySharingManager->pszCurrentStatus = pszCurrentStatus; + } + + for (size_t i = 0; i < pActivitySharingManager->nCount; ++i) + { + ActivitySharingClient* pClient = pActivitySharingManager->pClients[i]; + + if (pClient) + { + pClient->SetStatusMessage(pClient->m_handler, pActivitySharingManager->pszCurrentStatus); + } + } +} + +PCWSTR ActivitySharingManager_GetStatus(ActivitySharingManager* pActivitySharingManager) +{ + return pActivitySharingManager->pszCurrentStatus; +} diff --git a/src/sharing/activitysharingmanager.h b/src/sharing/activitysharingmanager.h new file mode 100644 index 0000000..a205ece --- /dev/null +++ b/src/sharing/activitysharingmanager.h @@ -0,0 +1,17 @@ +#pragma once + +#include "activitysharingclient.h" + +typedef struct ActivitySharingManager ActivitySharingManager; +struct ActivitySharingManager { + ActivitySharingClient** pClients; + size_t nCount; + PCWSTR pszCurrentStatus; + size_t nStatusLength; +}; + +ActivitySharingManager* ActivitySharingManager_Create(); +void ActivitySharingManager_Init(ActivitySharingManager* pActivitySharingManager); +void ActivitySharingManager_AddClient(ActivitySharingManager* pActivitySharingManager, ActivitySharingClient* pActivitySharingClient); +void ActivitySharingManager_SetStatus(ActivitySharingManager* pActivitySharingManager, PCWSTR pszStatusMessage); +PCWSTR ActivitySharingManager_GetStatus(ActivitySharingManager* pActivitySharingManager); diff --git a/src/sharing/activitystubdialog.c b/src/sharing/activitystubdialog.c new file mode 100644 index 0000000..3290b3c --- /dev/null +++ b/src/sharing/activitystubdialog.c @@ -0,0 +1,74 @@ +#include "../precomp.h" + +#include "../panitent.h" +#include "activitysharingmanager.h" +#include "../win32/dialog.h" +#include "activitystubdialog.h" +#include "../resource.h" + +void ActivityStubDialog_OnInitDialog(ActivityStubDialog* pActivityStubDialog); +void ActivityStubDialog_OnOK(ActivityStubDialog* pActivityStubDialog); +void ActivityStubDialog_OnCancel(ActivityStubDialog* pActivityStubDialog); +INT_PTR ActivityStubDialog_DlgUserProc(ActivityStubDialog* pActivityStubDialog, UINT message, WPARAM wParam, LPARAM lParam); +void ActivityStubDialog_SetStatusMessage(ActivityStubDialog* pActivityStubDialog, PCWSTR pszStatusMessage); + +ActivityStubDialog* ActivityStubDialog_Create(Application* pApp) +{ + ActivityStubDialog* pActivityStubDialog = (ActivityStubDialog*)malloc(sizeof(ActivityStubDialog)); + + if (pActivityStubDialog) + { + memset(pActivityStubDialog, 0, sizeof(ActivityStubDialog)); + ActivityStubDialog_Init(pActivityStubDialog, pApp); + } + + return pActivityStubDialog; +} + +void ActivityStubDialog_Init(ActivityStubDialog* pActivityStubDialog, Application* pApp) +{ + Dialog_Init((Dialog*)&pActivityStubDialog->base, pApp); + + pActivityStubDialog->base.DlgUserProc = (FnDialogDlgUserProc)ActivityStubDialog_DlgUserProc; + pActivityStubDialog->base.OnInitDialog = (FnDialogOnInitDialog)ActivityStubDialog_OnInitDialog; + pActivityStubDialog->base.OnOK = (FnDialogOnOK)ActivityStubDialog_OnOK; + pActivityStubDialog->base.OnCancel = (FnDialogOnCancel)ActivityStubDialog_OnCancel; + + pActivityStubDialog->m_activitySharingClient.m_handler = pActivityStubDialog; + pActivityStubDialog->m_activitySharingClient.SetStatusMessage = ActivityStubDialog_SetStatusMessage; +} + +void ActivityStubDialog_OnInitDialog(ActivityStubDialog* pActivityStubDialog) +{ + HWND hDlg = Window_GetHWND((Window*)pActivityStubDialog); + + HWND hStatic = GetDlgItem(hDlg, IDS_ACTIVITYTEXT); + PCWSTR pszCurrentStatus = ActivitySharingManager_GetStatus(Panitent_GetApp()->m_pActivitySharingManager); + SetWindowText(hStatic, pszCurrentStatus); +} + +void ActivityStubDialog_OnOK(ActivityStubDialog* pActivityStubDialog) +{ + EndDialog(Window_GetHWND((Window*)pActivityStubDialog), 0); +} + +void ActivityStubDialog_OnCancel(ActivityStubDialog* pActivityStubDialog) +{ + EndDialog(Window_GetHWND((Window*)pActivityStubDialog), 0); +} + +INT_PTR ActivityStubDialog_DlgUserProc(ActivityStubDialog* pActivityStubDialog, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + } + + return Dialog_DefaultDialogProc(pActivityStubDialog, message, wParam, lParam); +} + +void ActivityStubDialog_SetStatusMessage(ActivityStubDialog* pActivityStubDialog, PCWSTR pszStatusMessage) +{ + HWND hDlg = Window_GetHWND((Window*)pActivityStubDialog); + HWND hStatic = GetDlgItem(hDlg, IDS_ACTIVITYTEXT); + SetWindowText(hStatic, pszStatusMessage); +} diff --git a/src/sharing/activitystubdialog.h b/src/sharing/activitystubdialog.h new file mode 100644 index 0000000..0c8d42e --- /dev/null +++ b/src/sharing/activitystubdialog.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../win32/dialog.h" +#include "activitysharingclient.h" + +typedef struct ActivityStubDialog ActivityStubDialog; +struct ActivityStubDialog { + Dialog base; + ActivitySharingClient m_activitySharingClient; +}; + +ActivityStubDialog* ActivityStubDialog_Create(Application* pApp); +void ActivityStubDialog_Init(ActivityStubDialog* pActivityStubDialog, Application* pApp); diff --git a/src/sharing/discordasp.c b/src/sharing/discordasp.c new file mode 100644 index 0000000..711c491 --- /dev/null +++ b/src/sharing/discordasp.c @@ -0,0 +1,33 @@ +#include "../precomp.h" +#include "activitysharingclient.h" + +#include "discordasp.h" + +#include "../panitent.h" +#include "discordsdk.h" + +DiscordASP* DiscordASP_Create() +{ + DiscordASP* pDiscordASP = (DiscordASP*)malloc(sizeof(DiscordASP)); + + if (pDiscordASP) + { + memset(pDiscordASP, 0, sizeof(DiscordASP)); + + DiscordASP_Init(pDiscordASP); + } + + return pDiscordASP; +} + +void DiscordASP_Init(DiscordASP* pDiscordASP) +{ + pDiscordASP->base->SetStatusMessage = DiscordASP_SetStatusMessage; +} + +void DiscordASP_SetStatusMessage(LPCWSTR lpszStatusMessage) +{ +#ifdef HAS_DISCORDSDK + Discord_SetActivityStatus(g_panitent.discord, lpszStatusMessage); +#endif /* HAS_DISCORDSDK */ +} diff --git a/src/sharing/discordasp.h b/src/sharing/discordasp.h new file mode 100644 index 0000000..805aceb --- /dev/null +++ b/src/sharing/discordasp.h @@ -0,0 +1,10 @@ +#pragma once + +typedef struct DiscordASP DiscordASP; +struct DiscordASP { + ActivitySharingClient* base; +}; + +DiscordASP* DiscordASP_Create(); +void DiscordASP_Init(DiscordASP* pDiscordASP); +void DiscordASP_SetStatusMessage(LPCWSTR lpszStatusMessage); diff --git a/src/discordsdk.c b/src/sharing/discordsdk.c similarity index 91% rename from src/discordsdk.c rename to src/sharing/discordsdk.c index 8aa44f9..90a4ae4 100644 --- a/src/discordsdk.c +++ b/src/sharing/discordsdk.c @@ -1,4 +1,4 @@ -#include "precomp.h" +#include "../precomp.h" #include "discordsdk.h" @@ -9,25 +9,12 @@ #include #include +#include "../win32/util.h" + #define DISCORD_REQUIRE(x) assert(x == DiscordResult_Ok) #define DISCORD_APPLICATION_ID 861979642411483186 -uint64_t GetSystemTimeAsUnixTime() -{ - const uint64_t unixTimeStart = 0x019DB1DED53E8000; - const uint64_t tps = 10000000; - - FILETIME ft; - GetSystemTimeAsFileTime(&ft); - - LARGE_INTEGER li; - li.LowPart = ft.dwLowDateTime; - li.HighPart = ft.dwHighDateTime; - - return (li.QuadPart - unixTimeStart) / tps; -} - CRITICAL_SECTION g_csDiscord; typedef struct _DiscordSDKInstance { @@ -138,7 +125,7 @@ void Discord_SetActivityStatus(DiscordSDKInstance* discord, LPCWSTR lpszStatus) NULL); StringCchPrintfA(activity.assets.large_image, 80, "panitent"); - activity.timestamps.start = GetSystemTimeAsUnixTime(); + activity.timestamps.start = Win32_GetSystemTimeAsUnixTime(); EnterCriticalSection(&g_csDiscord); discord->activities->update_activity(discord->activities, diff --git a/src/discordsdk.h b/src/sharing/discordsdk.h similarity index 100% rename from src/discordsdk.h rename to src/sharing/discordsdk.h diff --git a/src/splittercontainer.c b/src/splittercontainer.c index 06f704a..6f1a2ee 100644 --- a/src/splittercontainer.c +++ b/src/splittercontainer.c @@ -23,14 +23,15 @@ LRESULT CALLBACK SplitterContainer_UserProc(SplitterContainer* pSplitterContaine SplitterContainer* SplitterContainer_Create(struct Application* app) { - SplitterContainer* window = calloc(1, sizeof(SplitterContainer)); + SplitterContainer* pWindow = (SplitterContainer*)malloc(sizeof(SplitterContainer)); + memset(pWindow, 0, sizeof(SplitterContainer)); - if (window) + if (pWindow) { - SplitterContainer_Init(window, app); + SplitterContainer_Init(pWindow, app); } - return window; + return pWindow; } void SplitterContainer_Init(SplitterContainer* window, struct Application* app) @@ -87,6 +88,7 @@ struct HashMap2 { void InitHashMap2(struct HashMap2* map, size_t capacity) { map->pairs = (struct KeyValuePair*)malloc(capacity * sizeof(KeyValuePair)); + memset(map->pairs, 0, capacity * sizeof(KeyValuePair)); map->capacity = capacity; map->size = 0; } diff --git a/src/swatch.c b/src/swatch.c index 98abb22..2acb85d 100644 --- a/src/swatch.c +++ b/src/swatch.c @@ -22,7 +22,8 @@ LRESULT CALLBACK SwatchControl_WndProc(HWND hWnd, case WM_LBUTTONDOWN: { MessageBox(NULL, L"Created!", L"Info", MB_OK); - uint8_t* custColors = calloc(16, sizeof(uint32_t)); + uint8_t* custColors = (uint8_t*)malloc(16 * sizeof(uint32_t)); + memset(custColors, 0, 16 * sizeof(uint32_t)); CHOOSECOLOR cc = {0}; cc.lStructSize = sizeof(CHOOSECOLOR); diff --git a/src/swatch2.c b/src/swatch2.c index 7b31a1c..65d7057 100644 --- a/src/swatch2.c +++ b/src/swatch2.c @@ -12,7 +12,8 @@ static void OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) { UNREFERENCED_PARAMETER(lpcs); - SwatchControlData *data = calloc(1, sizeof(SwatchControlData)); + SwatchControlData *data = (SwatchControlData*)malloc(sizeof(SwatchControlData)); + memset(data, 0, sizeof(SwatchControlData)); assert(data); SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)data); diff --git a/src/toolbox.c b/src/toolbox.c index 8d285e1..169121c 100644 --- a/src/toolbox.c +++ b/src/toolbox.c @@ -5,10 +5,13 @@ #include #include "win32/window.h" +#include "win32/util.h" #include "viewport.h" #include "tool.h" #include "tools/common.h" +#include "panitent.h" + #include "toolbox.h" #include "resource.h" @@ -19,8 +22,8 @@ #include "primitives_context.h" #include "util.h" #include "brush.h" -#include "panitent.h" #include "history.h" +#include "sharing/activitysharingmanager.h" const WCHAR szClassName[] = L"__ToolboxWindow"; @@ -53,6 +56,7 @@ void ToolboxWindow_RemoveTool(ToolboxWindow* pToolboxWindow) HBITMAP img_layout; +unsigned int g_uToolPrevious; unsigned int g_uToolSelected; HTHEME hTheme = NULL; @@ -64,6 +68,8 @@ enum { PUSHED }; +const BOOL bOrange = TRUE; + void Toolbox_ButtonDraw(HDC hdc, int x, int y, unsigned int state) { if (!hTheme) { @@ -77,11 +83,30 @@ void Toolbox_ButtonDraw(HDC hdc, int x, int y, unsigned int state) rc.right = x + btnSize; rc.bottom = y + btnSize; - if (hTheme) + if (bOrange) { - int iStateId = PBS_NORMAL; + HPEN hOldPen = SelectObject(hdc, GetStockObject(DC_PEN)); + HBRUSH hOldBrush = SelectObject(hdc, GetStockObject(DC_BRUSH)); + if (state == PUSHED) - iStateId = PBS_PRESSED; + { + SetDCPenColor(hdc, Win32_HexToCOLORREF(L"#6d648e")); + SetDCBrushColor(hdc, Win32_HexToCOLORREF(L"#9185be")); + + } + else { + SetDCPenColor(hdc, Win32_HexToCOLORREF(L"#aaaaaa")); + SetDCBrushColor(hdc, Win32_HexToCOLORREF(L"#eeeeee")); + } + + RoundRect(hdc, rc.left, rc.top, rc.right, rc.bottom, 3, 3); + + SelectObject(hdc, hOldPen); + SelectObject(hdc, hOldBrush); + } + else if (hTheme) + { + int iStateId = state == PUSHED ? PBS_PRESSED : PBS_NORMAL; if (IsThemeBackgroundPartiallyTransparent(hTheme, BP_PUSHBUTTON, iStateId)) { @@ -102,7 +127,9 @@ void Toolbox_ButtonDraw(HDC hdc, int x, int y, unsigned int state) else { DWORD edge = EDGE_RAISED; if (state == PUSHED) + { edge = EDGE_SUNKEN; + } DrawEdge(hdc, &rc, edge, BF_RECT); } @@ -133,13 +160,7 @@ void ToolboxWindow_DrawButtons(ToolboxWindow* pToolboxWindow, HDC hdc) int x = btnOffset + (i % rowCount) * extSize; int y = btnOffset + (i / rowCount) * extSize; - /* Rectangle(hdc, x, y, x+btnSize, y+btnSize); */ - - unsigned int uState = NORMAL; - if ((unsigned int)i == g_uToolSelected) - { - uState = PUSHED; - } + unsigned int uState = (unsigned int)i == g_uToolSelected ? PUSHED : NORMAL; Toolbox_ButtonDraw(hdc, x, y, uState); @@ -162,21 +183,78 @@ void ToolboxWindow_DrawButtons(ToolboxWindow* pToolboxWindow, HDC hdc) DeleteDC(hdcMem); } +#define ANIMATION_DURATION 200 + void ToolboxWindow_OnPaint(ToolboxWindow* pToolboxWindow) { - PAINTSTRUCT ps = { 0 }; HWND hWnd = Window_GetHWND((Window*)pToolboxWindow); + + PAINTSTRUCT ps = { 0 }; HDC hdc = BeginPaint(hWnd, &ps); - ToolboxWindow_DrawButtons(pToolboxWindow, hdc); - EndPaint(hWnd, &ps); + + if (hdc) + { + RECT rcClient; + GetClientRect(hWnd, &rcClient); + + // Rectangle(hdc, rcClient.left, rcClient.top, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); + + if (!BufferedPaintRenderAnimation(hWnd, hdc)) + { + BP_ANIMATIONPARAMS bpap; + memset(&bpap, 0, sizeof(BP_ANIMATIONPARAMS)); + bpap.cbSize = sizeof(BP_ANIMATIONPARAMS); + bpap.style = BPAS_CUBIC; + + /* Check if animation is needed. If not set dwDuration to 0 */ + bpap.dwDuration = (g_uToolSelected != g_uToolPrevious ? ANIMATION_DURATION : 0); + + HDC hdcFrom; + HDC hdcTo; + HANIMATIONBUFFER hbpAnimation = BeginBufferedAnimation(hWnd, hdc, &rcClient, BPBF_COMPATIBLEBITMAP, NULL, &bpap, &hdcFrom, &hdcTo); + if (hbpAnimation) + { + /* Hack */ + HBRUSH hbrBackground = GetClassLongPtr(hWnd, GCLP_HBRBACKGROUND); + if (hdcFrom) + { + /* Hack */ + FillRect(hdcFrom, &rcClient, hbrBackground); + + unsigned int temp = g_uToolSelected; + g_uToolSelected = g_uToolPrevious; + ToolboxWindow_DrawButtons(pToolboxWindow, hdcFrom); + g_uToolSelected = temp; + } + + if (hdcTo) + { + /* Hack */ + FillRect(hdcTo, &rcClient, hbrBackground); + + ToolboxWindow_DrawButtons(pToolboxWindow, hdcTo); + } + + g_uToolPrevious = g_uToolSelected; + EndBufferedAnimation(hbpAnimation, TRUE); + } + /* If animation unavailable just draw buttons */ + else + { + ToolboxWindow_DrawButtons(pToolboxWindow, hdc); + } + } + + EndPaint(hWnd, &ps); + } } void ToolboxWindow_OnLButtonUp(ToolboxWindow* pToolboxWindow, int x, int y) { int btnHot = btnSize + 5; - if (x >= 0 && y >= 0 && x < btnHot * 2 && - y < (int)pToolboxWindow->tool_count * btnHot) { + if (x >= 0 && y >= 0 && x < btnHot * 2 && y < (int)pToolboxWindow->tool_count * btnHot) + { size_t extSize = (size_t)btnSize + (size_t)btnOffset; RECT rcClient = { 0 }; @@ -189,57 +267,34 @@ void ToolboxWindow_OnLButtonUp(ToolboxWindow* pToolboxWindow, int x, int y) unsigned int pressed = (y - btnOffset) / (int)extSize * (int)rowCount + (x - btnOffset) / (int)extSize; + g_uToolPrevious = g_uToolSelected; g_uToolSelected = pressed; Tool* pTool = NULL; + Tool* tools[] = { + (Tool*)pToolboxWindow->m_pPointerTool, + (Tool*)pToolboxWindow->m_pPencilTool, + (Tool*)pToolboxWindow->m_pCircleTool, + (Tool*)pToolboxWindow->m_pLineTool, + (Tool*)pToolboxWindow->m_pRectangleTool, + (Tool*)pToolboxWindow->m_pTextTool, + (Tool*)pToolboxWindow->m_pFillTool, + (Tool*)pToolboxWindow->m_pPickerTool, + (Tool*)pToolboxWindow->m_pBrushTool, + (Tool*)pToolboxWindow->m_pEraserTool + }; + if (pressed <= pToolboxWindow->tool_count) { - switch (pressed) { - case 1: - pTool = (Tool*)pToolboxWindow->m_pPencilTool; - break; - case 2: - pTool = (Tool*)pToolboxWindow->m_pCircleTool; - break; - case 3: - pTool = (Tool*)pToolboxWindow->m_pLineTool; - break; - case 4: - pTool = (Tool*)pToolboxWindow->m_pRectangleTool; - break; - case 5: - pTool = (Tool*)pToolboxWindow->m_pTextTool; - break; - case 6: - pTool = (Tool*)pToolboxWindow->m_pFillTool; - break; - case 7: - pTool = (Tool*)pToolboxWindow->m_pPickerTool; - break; - case 8: - pTool = (Tool*)pToolboxWindow->m_pBrushTool; - break; - case 9: - pTool = (Tool*)pToolboxWindow->m_pEraserTool; - break; - default: - pTool = (Tool*)pToolboxWindow->m_pPointerTool; - break; - } + pTool = (Tool*)tools[pressed]; - WCHAR szLogMessage[80] = L""; - StringCchPrintf(szLogMessage, 80, L"Selected tool: %s", pTool->pszLabel); - LogMessage(LOGENTRY_TYPE_DEBUG, L"Toolbox", szLogMessage); + LogMessageF(LOGENTRY_TYPE_DEBUG, L"Toolbox", L"Selected tool: %s", pTool->pszLabel); PanitentApplication* pPanitentApplication = Panitent_GetApp(); pPanitentApplication->m_pTool = (Tool*)pTool; -#ifdef HAS_DISCORDSDK - WCHAR szStatus[80] = L""; - StringCchPrintf(szStatus, 80, L"Drawing with %s", pTool->pszLabel); - Discord_SetActivityStatus(g_panitent.discord, szStatus); -#endif /* HAS_DISCORDSDK */ + Panitent_SetActivityStatusF(pPanitentApplication, L"Drawing with %s", pTool->pszLabel); } } @@ -257,15 +312,13 @@ void Toolbox_OnContextMenu(HWND hWnd, WPARAM wParam, LPARAM lParam) HMENU hMenu; hMenu = CreatePopupMenu(); - InsertMenu(hMenu, 0, MF_BYPOSITION | MF_STRING, IDM_TOOLBOXSETTINGS, - L"Settings"); + InsertMenu(hMenu, 0, MF_BYPOSITION | MF_STRING, IDM_TOOLBOXSETTINGS, L"Settings"); TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN, x, y, 0, hWnd, NULL); } static inline void Toolbox_OpenSettings(HWND hParent) { - DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_TOOLBOXSETTINGS), - hParent, (DLGPROC)ToolboxSettingsDlgProc); + DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_TOOLBOXSETTINGS), hParent, (DLGPROC)ToolboxSettingsDlgProc); } BOOL ToolboxWindow_OnCreate(ToolboxWindow* pToolboxWindow, LPCREATESTRUCT lpcs) @@ -314,10 +367,13 @@ INT_PTR CALLBACK ToolboxSettingsDlgProc(HWND hDlg, UINT message, { PNTSETTINGS* settings; - PTOOLBOXSETTINGS pTempSettings = calloc(1, sizeof(TOOLBOXSETTINGS)); + PTOOLBOXSETTINGS pTempSettings = (PTOOLBOXSETTINGS)malloc(sizeof(TOOLBOXSETTINGS)); + memset(pTempSettings, 0, sizeof(TOOLBOXSETTINGS)); assert(pTempSettings); if (!pTempSettings) + { EndDialog(hDlg, 0); + } SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)pTempSettings); @@ -379,9 +435,9 @@ void ToolboxWindow_PreRegister(LPWNDCLASSEX lpwcex); void ToolboxWindow_PreCreate(LPCREATESTRUCT lpcs); LRESULT ToolboxWindow_UserProc(ToolboxWindow* pToolboxWindow, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); -void ToolboxWindow_Init(ToolboxWindow* pToolboxWindow, struct Application* app) +void ToolboxWindow_Init(ToolboxWindow* pToolboxWindow, PanitentApplication* pPanitentApplication) { - Window_Init(&pToolboxWindow->base, app); + Window_Init(&pToolboxWindow->base, pPanitentApplication); pToolboxWindow->base.szClassName = szClassName; @@ -403,15 +459,16 @@ void ToolboxWindow_Init(ToolboxWindow* pToolboxWindow, struct Application* app) pToolboxWindow->m_pBrushTool = BrushTool_Create(); pToolboxWindow->m_pEraserTool = EraserTool_Create(); - PanitentApplication* pPanitentApplication = Panitent_GetApp(); pPanitentApplication->m_pTool = (Tool*)pToolboxWindow->m_pPointerTool; + g_uToolPrevious = 0; g_uToolSelected = 0; img_layout = (HBITMAP)LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_TOOLS24)); - pToolboxWindow->tools = (Tool**)calloc(16, sizeof(Tool*)); + pToolboxWindow->tools = (Tool**)malloc(16 * sizeof(Tool*)); + memset(pToolboxWindow, 0, 16 * sizeof(Tool*)); pToolboxWindow->tool_count = 0; ToolboxWindow_AddTool(pToolboxWindow, (Tool*)pToolboxWindow->m_pPointerTool); @@ -446,13 +503,14 @@ void ToolboxWindow_PreCreate(LPCREATESTRUCT lpcs) lpcs->cy = 200; } -ToolboxWindow* ToolboxWindow_Create(struct Application* app) +ToolboxWindow* ToolboxWindow_Create(PanitentApplication* pPanitentApplication) { - ToolboxWindow* pToolboxWindow = calloc(1, sizeof(ToolboxWindow)); - + ToolboxWindow* pToolboxWindow = (ToolboxWindow*)malloc(sizeof(ToolboxWindow)); + if (pToolboxWindow) { - ToolboxWindow_Init(pToolboxWindow, app); + memset(pToolboxWindow, 0, sizeof(ToolboxWindow)); + ToolboxWindow_Init(pToolboxWindow, pPanitentApplication); } return pToolboxWindow; diff --git a/src/toolbox.h b/src/toolbox.h index 8f94e50..c97dad1 100644 --- a/src/toolbox.h +++ b/src/toolbox.h @@ -35,4 +35,4 @@ typedef struct _tagTOOLBOXICONTHEME { LPWSTR lpszResource; } TOOLBOXICONTHEME, *PTOOLBOXICONTHEME; -ToolboxWindow* ToolboxWindow_Create(struct Application* app); +ToolboxWindow* ToolboxWindow_Create(PanitentApplication* pPanitentApplication); diff --git a/src/tools/brushtool.c b/src/tools/brushtool.c index 59ba38a..b8b462d 100644 --- a/src/tools/brushtool.c +++ b/src/tools/brushtool.c @@ -20,7 +20,8 @@ void BrushTool_OnMouseMove(BrushTool* pBrushTool, ViewportWindow* pViewportWindo BrushTool* BrushTool_Create() { - BrushTool* pBrushTool = (BrushTool*)calloc(1, sizeof(BrushTool)); + BrushTool* pBrushTool = (BrushTool*)malloc(sizeof(BrushTool)); + memset(pBrushTool, 0, sizeof(BrushTool)); BrushTool_Init(pBrushTool); return pBrushTool; } diff --git a/src/tools/circletool.c b/src/tools/circletool.c index 8858a90..fb3d55d 100644 --- a/src/tools/circletool.c +++ b/src/tools/circletool.c @@ -16,7 +16,8 @@ void CircleTool_OnLButtonUp(CircleTool* pCircleTool, ViewportWindow* pViewportWi CircleTool* CircleTool_Create() { - CircleTool* pCircleTool = (CircleTool*)calloc(1, sizeof(CircleTool)); + CircleTool* pCircleTool = (CircleTool*)malloc(sizeof(CircleTool)); + memset(pCircleTool, 0, sizeof(CircleTool)); CircleTool_Init(pCircleTool); return pCircleTool; } diff --git a/src/tools/erasertool.c b/src/tools/erasertool.c index 6d061e7..33c92a2 100644 --- a/src/tools/erasertool.c +++ b/src/tools/erasertool.c @@ -20,7 +20,8 @@ void EraserTool_OnMouseMove(EraserTool* pEraserTool, ViewportWindow* pViewportWi EraserTool* EraserTool_Create() { - EraserTool* pEraserTool = (EraserTool*)calloc(1, sizeof(EraserTool)); + EraserTool* pEraserTool = (EraserTool*)malloc(sizeof(EraserTool)); + memset(pEraserTool, 0, sizeof(EraserTool)); EraserTool_Init(pEraserTool); return pEraserTool; } diff --git a/src/tools/filltool.c b/src/tools/filltool.c index c04faee..6d3f7cc 100644 --- a/src/tools/filltool.c +++ b/src/tools/filltool.c @@ -21,7 +21,8 @@ void FillTool_OnLButtonUp(FillTool* pFillTool, ViewportWindow* pViewportWindow, FillTool* FillTool_Create() { - FillTool* pFillTool = (FillTool*)calloc(1, sizeof(FillTool)); + FillTool* pFillTool = (FillTool*)malloc(sizeof(FillTool)); + memset(pFillTool, 0, sizeof(FillTool)); FillTool_Init(pFillTool); return pFillTool; } diff --git a/src/tools/linetool.c b/src/tools/linetool.c index 9d4a412..471f4b5 100644 --- a/src/tools/linetool.c +++ b/src/tools/linetool.c @@ -15,7 +15,8 @@ void LineTool_OnLButtonUp(LineTool* pLineTool, ViewportWindow* pViewportWindow, LineTool* LineTool_Create() { - LineTool* pLineTool = (LineTool*)calloc(1, sizeof(LineTool)); + LineTool* pLineTool = (LineTool*)malloc(sizeof(LineTool)); + memset(pLineTool, 0, sizeof(LineTool)); LineTool_Init(pLineTool); return pLineTool; } diff --git a/src/tools/penciltool.c b/src/tools/penciltool.c index bafcceb..f41d562 100644 --- a/src/tools/penciltool.c +++ b/src/tools/penciltool.c @@ -16,7 +16,8 @@ void PencilTool_OnMouseMove(PencilTool* pPencilTool, ViewportWindow* pViewportWi PencilTool* PencilTool_Create() { - PencilTool* pPencilTool = (PencilTool*)calloc(1, sizeof(PencilTool)); + PencilTool* pPencilTool = (PencilTool*)malloc(sizeof(PencilTool)); + memset(pPencilTool, 0, sizeof(PencilTool)); PencilTool_Init(pPencilTool); return pPencilTool; } diff --git a/src/tools/pickertool.c b/src/tools/pickertool.c index 05bb882..edcb14c 100644 --- a/src/tools/pickertool.c +++ b/src/tools/pickertool.c @@ -15,7 +15,8 @@ void PickerTool_OnRButtonUp(PickerTool* pPickerTool, ViewportWindow* pViewportWi PickerTool* PickerTool_Create() { - PickerTool* pPickerTool = (PickerTool*)calloc(1, sizeof(PickerTool)); + PickerTool* pPickerTool = (PickerTool*)malloc(sizeof(PickerTool)); + memset(pPickerTool, 0, sizeof(PickerTool)); PickerTool_Init(pPickerTool); return pPickerTool; } diff --git a/src/tools/pointertool.c b/src/tools/pointertool.c index fc895c2..7545424 100644 --- a/src/tools/pointertool.c +++ b/src/tools/pointertool.c @@ -7,7 +7,8 @@ void PointerTool_Init(PointerTool* pPointerTool); PointerTool* PointerTool_Create() { - PointerTool* pPointerTool = (PointerTool*)calloc(1, sizeof(PointerTool)); + PointerTool* pPointerTool = (PointerTool*)malloc(sizeof(PointerTool)); + memset(pPointerTool, 0, sizeof(PointerTool)); PointerTool_Init(pPointerTool); return pPointerTool; } diff --git a/src/tools/rectangletool.c b/src/tools/rectangletool.c index 556080b..5caa909 100644 --- a/src/tools/rectangletool.c +++ b/src/tools/rectangletool.c @@ -15,7 +15,8 @@ void RectangleTool_OnLButtonUp(RectangleTool* pRectangleTool, ViewportWindow* pV RectangleTool* RectangleTool_Create() { - RectangleTool* pRectangleTool = (RectangleTool*)calloc(1, sizeof(RectangleTool)); + RectangleTool* pRectangleTool = (RectangleTool*)malloc(sizeof(RectangleTool)); + memset(pRectangleTool, 0, sizeof(pRectangleTool)); RectangleTool_Init(pRectangleTool); return pRectangleTool; } diff --git a/src/tools/texttool.c b/src/tools/texttool.c index 8b3a8da..0b97eaa 100644 --- a/src/tools/texttool.c +++ b/src/tools/texttool.c @@ -15,7 +15,8 @@ void TextTool_OnLButtonUp(TextTool* pTextTool, ViewportWindow* pViewportWindow, TextTool* TextTool_Create() { - TextTool* pTextTool = (TextTool*)calloc(1, sizeof(TextTool)); + TextTool* pTextTool = (TextTool*)malloc(sizeof(TextTool)); + memset(pTextTool, 0, sizeof(TextTool)); TextTool_Init(pTextTool); return pTextTool; } diff --git a/src/toolwndframe.c b/src/toolwndframe.c new file mode 100644 index 0000000..94c8c1d --- /dev/null +++ b/src/toolwndframe.c @@ -0,0 +1,38 @@ +#include "precomp.h" + +#include "toolwndframe.h" + +#include "win32/util.h" +#include "resource.h" + +#define GLYPH_SIZE 8 + +void DrawCaptionGlyph(HDC hdc, int x, int y, int iGlyph) +{ + HBITMAP hbmGlyphs = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_FLOATINGGLYPHS)); + HDC hdcGlyphs = CreateCompatibleDC(hdc); + HBITMAP hPrevBm = SelectObject(hdcGlyphs, hbmGlyphs); + + TransparentBlt(hdc, x, y, GLYPH_SIZE, GLYPH_SIZE, hdcGlyphs, iGlyph * GLYPH_SIZE, 0, GLYPH_SIZE, GLYPH_SIZE, COLORREF_MAGENTA); + + SelectObject(hdcGlyphs, hPrevBm); + DeleteObject(hbmGlyphs); +} + +void DrawCaptionButton(CaptionButton* pCaptionButton, HDC hdc, int x, int y) +{ + RECT rcButton = { 0 }; + rcButton.left = x; + rcButton.top = y; + rcButton.right = x + pCaptionButton->size.cx; + rcButton.bottom = y + pCaptionButton->size.cy; + + SetDCPenColor(hdc, RGB(0xFF, 0xFF, 0xFF)); + SetDCBrushColor(hdc, Win32_HexToCOLORREF(L"#9185be")); + SelectObject(hdc, GetStockObject(DC_PEN)); + SelectObject(hdc, GetStockObject(DC_BRUSH)); + + // Rectangle(hdc, rcButton.left, rcButton.top, rcButton.right, rcButton.bottom); + + DrawCaptionGlyph(hdc, rcButton.left + (rcButton.right - rcButton.left - 8) / 2, rcButton.top + (rcButton.bottom - rcButton.top - 8) / 2, pCaptionButton->glyph); +} diff --git a/src/toolwndframe.h b/src/toolwndframe.h new file mode 100644 index 0000000..1e532d0 --- /dev/null +++ b/src/toolwndframe.h @@ -0,0 +1,21 @@ +#pragma once + +enum { + GLYPH_MORE = 0, + GLYPH_PIN, + GLYPH_MINIMIZE, + GLYPH_MAXIMIZE, + GLYPH_CLOSE, + GLYPH_RESTORE, + GLYPH_HELP +}; + +typedef struct CaptionButton CaptionButton; + +struct CaptionButton { + SIZE size; + int glyph; +}; + +void DrawCaptionGlyph(HDC hdc, int x, int y, int iGlyph); +void DrawCaptionButton(CaptionButton* pCaptionButton, HDC hdc, int x, int y); diff --git a/src/util.h b/src/util.h index 671b8a4..618661b 100644 --- a/src/util.h +++ b/src/util.h @@ -88,7 +88,8 @@ void pntqueue_init$$##T(pntqueue$$##T *q) \ q->capacity = 0; \ \ T* pData = NULL; \ - pData = calloc(DEFAULT_CAPACITY, sizeof(T)); \ + pData = malloc(DEFAULT_CAPACITY * sizeof(T)); \ + memset(pData, 0, DEFAULT_CAPACITY * sizeof(T)); \ if (!pData) { \ assert(FALSE); \ return; \ @@ -191,6 +192,7 @@ struct __pntmap$$##NAME { \ }; \ void pntmap$$##NAME_init(pntmap$$##NAME* map) { \ map->data = malloc(sizeof(map->data[0]) * DEFAULT_CAPACITY); \ + memset(map->data, 0, sizeof(map->data[0]) * DEFAULT_CAPACITY); \ map->capacity = DEFAULT_CAPACITY; \ map->size = 0; \ } \ diff --git a/src/util/assert.h b/src/util/assert.h new file mode 100644 index 0000000..586646d --- /dev/null +++ b/src/util/assert.h @@ -0,0 +1,9 @@ +#pragma once + +#define ASSERT(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "Assertion failed: %s, file %s, line %d\n", #condition, __FILE__, __LINE__); \ + DebugBreak(); \ + } \ + } while(0) diff --git a/src/util/bytestream.c b/src/util/bytestream.c new file mode 100644 index 0000000..c1d78a9 --- /dev/null +++ b/src/util/bytestream.c @@ -0,0 +1,199 @@ +#include "bytestream.h" + +#include +#include + +typedef struct ByteStream ByteStream; +struct ByteStream { + uint8_t* pData; + uint8_t* pReadPtr; + uint8_t* pWritePtr; + size_t capacity; + size_t length; + int state; +}; + +#define MIN_CAPACITY_WINDOW 16 + +void ByteStream_Init(ByteStream* pByteStream); + +/** + * [PUBLIC] + * + * Create the ByteStream object + * + * @return Pointer to new ByteStream instance + */ +ByteStream* ByteStream_Create() +{ + ByteStream *pByteStream = (ByteStream*)malloc(sizeof(ByteStream)); + if (pByteStream) + { + ByteStream_Init(pByteStream); + } + + return pByteStream; +} + +/** + * [PRIVATE] + * + * Initialize the ByteStream structure + * + * @param pByteStream Pointer to ByteStream memory location + */ +void ByteStream_Init(ByteStream* pByteStream) +{ + memset(pByteStream, 0, sizeof(ByteStream)); + + pByteStream->pData = malloc(MIN_CAPACITY_WINDOW); + if (pByteStream->pData) + { + memset(pByteStream->pData, 0, MIN_CAPACITY_WINDOW); + pByteStream->capacity = MIN_CAPACITY_WINDOW; + pByteStream->pReadPtr = pByteStream->pData; + pByteStream->pWritePtr = pByteStream->pData; + } +} + +uint8_t* ByteStream_GetBuffer(const ByteStream* pByteStream) +{ + return pByteStream->pReadPtr; +} + +uint8_t ByteStream_Get(ByteStream* pByteStream) +{ + return *pByteStream->pReadPtr++; +} + +void ByteStream_Put(ByteStream* pByteStream, uint8_t b) +{ + if (!pByteStream) + { + return; + } + + if (pByteStream->pWritePtr == (pByteStream->pData + pByteStream->capacity)) + { + if ((pByteStream->pWritePtr - pByteStream->pReadPtr) < pByteStream->capacity) + { + ptrdiff_t writeDiff = pByteStream->pWritePtr - pByteStream->pData; + + uint8_t* pData = memmove(pByteStream->pData, pByteStream->pReadPtr, pByteStream->pWritePtr - pByteStream->pReadPtr); + pByteStream->pData = pData; + pByteStream->pReadPtr = pData; + pByteStream->pWritePtr = pData + writeDiff; + } + else { + size_t newCapacity = pByteStream->capacity * 2; + ptrdiff_t readDiff = pByteStream->pReadPtr - pByteStream->pData; + ptrdiff_t writeDiff = pByteStream->pWritePtr - pByteStream->pData; + uint8_t* pData = realloc(pByteStream->pData, newCapacity); + if (!pData) + { + exit(1); + } + + pByteStream->capacity = newCapacity; + pByteStream->pData = pData; + pByteStream->pReadPtr = pData + readDiff; + pByteStream->pWritePtr = pData + writeDiff; + } + } + + *pByteStream->pWritePtr++ = b; + pByteStream->length++; +} + +/** + * [PUBLIC] + * + * Destroy the ByteStream object and internal buffer + * + * @param pByteStream ByteStream instance + */ +void ByteStream_Destroy(ByteStream* pByteStream) +{ + free(pByteStream->pData); + free(pByteStream); +} + +/** + * [PUBLIC] + * + * Get the length of ByteStream internal buffer + * + * Length is a difference between reading and writing locations + * + * @param pByteStream ByteStream instance + * + * @return the length of the stream + */ +size_t ByteStream_Length(ByteStream* pByteStream) +{ + return pByteStream->pWritePtr - pByteStream->pReadPtr; +} + +/** + * [PUBLIC] + * + * Copy the actual buffer into plain memory pointed by pDest + * + * pDest should be large enough to successfully copy the buffer contents. + * Use ByteStream_Length to acknowledge the length of the data to be copied + * + * @param pByteStream ByteStream instance + * @param pDest The destination buffer + */ +void ByteStream_Copy(ByteStream* pByteStream, uint8_t* pDest) +{ + if (!pByteStream || !pDest) + { + return; + } + + memcpy(pDest, pByteStream->pReadPtr, pByteStream->pWritePtr - pByteStream->pReadPtr); +} + +/** + * [PUBLIC] + * + * Replace buffer contents with provided one + * + * Read pointer will be set at the start of the buffer + * and write pointer will be set at the end + * + * @param pByteStream ByteStream instance + * @param pSrc New buffer data + * @param len Length of the data to be copied + */ +void ByteStream_SetBuffer(ByteStream* pByteStream, uint8_t* pSrc, size_t len) +{ + /* Calculate the new capacity */ + size_t newCapacity = (len / MIN_CAPACITY_WINDOW + 1) * MIN_CAPACITY_WINDOW; + + uint8_t* pData; + if (pByteStream->pData) + { + if (pByteStream->capacity != newCapacity) + { + pData = (uint8_t)realloc(pByteStream->pData, newCapacity); + } + else { + pData = pByteStream->pData; + } + + } + else { + pData = (uint8_t)malloc(newCapacity); + } + + if (pData) + { + memcpy(pData, pSrc, len); + pByteStream->pData = pData; + pByteStream->capacity = newCapacity; + pByteStream->pReadPtr = pData; + pByteStream->pWritePtr = pData + len; + } +} diff --git a/src/util/bytestream.h b/src/util/bytestream.h new file mode 100644 index 0000000..1dcd56e --- /dev/null +++ b/src/util/bytestream.h @@ -0,0 +1,99 @@ +#ifndef _BYTESTREAM_H_ +#define _BYTESTREAM_H_ + +#pragma once + +#include +#include + +typedef struct ByteStream ByteStream; + +/** + * [PUBLIC] + * + * Create the ByteStream object + * + * @return Pointer to new ByteStream instance + */ +ByteStream* ByteStream_Create(); + +/** + * [PUBLIC] + * + * Get current read pointer + * + * @return Pointer to read position + */ +uint8_t* ByteStream_GetBuffer(const ByteStream* pByteStream); + +/** + * [PUBLIC] + * + * Consume one byte from back of the stream + * + * @param pByteStream ByteStream instance + * + * @return byte + */ +uint8_t ByteStream_Get(ByteStream* pByteStream); + +/** + * [PUBLIC] + * + * Put one byte in front of the stream + * + * @param pByteStream ByteStream instance + * @param b The byte to put + */ +void ByteStream_Put(ByteStream* pByteStream, uint8_t b); + +/** + * [PUBLIC] + * + * Destroy the ByteStream object and internal buffer + * + * @param pByteStream ByteStream instance + */ +void ByteStream_Destroy(ByteStream* pByteStream); + +/** + * [PUBLIC] + * + * Get the length of ByteStream internal buffer + * + * Length is a difference between reading and writing locations + * + * @param pByteStream ByteStream instance + * + * @return the length of the stream + */ +size_t ByteStream_Length(ByteStream* pByteStream); + +/** + * [PUBLIC] + * + * Copy the actual buffer into plain memory pointed by pDest + * + * pDest should be large enough to successfully copy the buffer contents. + * Use ByteStream_Length to acknowledge the length of the data to be copied + * + * @param pByteStream ByteStream instance + * @param pDest The destination buffer + */ +void ByteStream_Copy(ByteStream* pByteStream, uint8_t* pDest); + +/** + * [PUBLIC] + * + * Replace buffer contents with provided one + * + * Read pointer will be set at the start of the buffer + * and write pointer will be set at the end + * + * @param pByteStream ByteStream instance + * @param pSrc New buffer data + * @param len Length of the data to be copied + */ +void ByteStream_SetBuffer(ByteStream* pByteStream, uint8_t* pSrc, size_t len); + +#endif /* _BYTESTREAM_H_ */ diff --git a/src/util/hashmap.c b/src/util/hashmap.c new file mode 100644 index 0000000..b374629 --- /dev/null +++ b/src/util/hashmap.c @@ -0,0 +1,301 @@ +#include "../precomp.h" + +#include "hashmap.h" + +/** + * [PUBLIC] + * + * Function to create a new AVL tree node + * + * @return Node created + */ +AVLNode* AVLNode_Create(void* pKey, void* pValue, FnAVLNodeKeyCompare* pfnCompare) +{ + AVLNode* pNode = (AVLNode*)malloc(sizeof(AVLNode)); + if (!pNode) + { + return NULL; + } + + pNode->pKey = pKey; + pNode->pValue = pValue; + pNode->pLeft = NULL; + pNode->pRight = NULL; + pNode->nHeight = 1; + pNode->pfnCompare = pfnCompare; + + return pNode; +} + +/** + * [PUBLIC] + * + * Function to get height of a node + * + * @return Node height + */ +int AVLNode_Height(AVLNode* pAVLNode) +{ + if (!pAVLNode) + { + return 0; + } + + return pAVLNode->nHeight; +} + +/** + * [PRIVATE] + * + * Function to perform right rotation on an AVL tree + */ +AVLNode* AVLNode_RightRotate(AVLNode* pNodeY) +{ + AVLNode* pNodeX = pNodeY->pLeft; + AVLNode* pNodeTemp = pNodeX->pRight; + + /* Perform rotation */ + pNodeX->pRight = pNodeY; + pNodeY->pLeft = pNodeTemp; + + /* Update heights */ + pNodeY->nHeight = max(AVLNode_Height(pNodeY->pLeft), AVLNode_Height(pNodeY->pRight)) + 1; + pNodeX->nHeight = max(AVLNode_Height(pNodeX->pLeft), AVLNode_Height(pNodeX->pRight)) + 1; + + /* Return new root */ + return pNodeX; +} + +/** + * [PRIVATE] + * + * Function to perform left rotation on an AVL tree + */ +AVLNode* AVLNode_LeftRotate(AVLNode* pNodeX) +{ + AVLNode* pNodeY = pNodeX->pRight; + AVLNode* pNodeTemp = pNodeY->pLeft; + + /* Perform rotation */ + pNodeY->pLeft = pNodeX; + pNodeX->pRight = pNodeTemp; + + /* Update heights */ + pNodeX->nHeight = max(AVLNode_Height(pNodeX->pLeft), AVLNode_Height(pNodeX->pRight)) + 1; + pNodeY->nHeight = max(AVLNode_Height(pNodeY->pLeft), AVLNode_Height(pNodeY->pRight)) + 1; + + /* Return new root */ + return pNodeY; +} + +/** + * [PRIVATE] + * + * Function to get balance factor of a node + */ +int AVLNode_GetBalance(AVLNode* pAVLNode) +{ + if (!pAVLNode) + { + return 0; + } + + return AVLNode_Height(pAVLNode->pLeft) - AVLNode_Height(pAVLNode->pRight); +} + +/** + * [PRIVATE] + * + * Function to insert a node into AVL tree + */ +AVLNode* AVLNode_Insert(AVLNode* pAVLNode, void* pKey, void* pValue, FnAVLNodeKeyCompare* pfnCompare) +{ + /* Perform standard BST insertion */ + if (!pAVLNode) + { + return AVLNode_Create(pKey, pValue, pfnCompare); + } + + if (pKey < pAVLNode->pKey) + { + pAVLNode->pLeft = AVLNode_Insert(pAVLNode->pLeft, pKey, pValue, pfnCompare); + } + else if (pKey > pAVLNode->pKey) + { + pAVLNode->pRight = AVLNode_Insert(pAVLNode->pRight, pKey, pValue, pfnCompare); + } + else { + pAVLNode->pValue = pValue; + return pAVLNode; + } + + /* Update height of this ancestor node */ + pAVLNode->nHeight = 1 + max(AVLNode_Height(pAVLNode->pLeft), AVLNode_Height(pAVLNode->pRight)); + + /* Get the balance factor of this ancestor node to check wheter this node became unbalanced */ + int nBalance = AVLNode_GetBalance(pAVLNode); + + /* If the node becomes unbalanced, there are 4 cases: */ + + /* Left Left Case */ + if (nBalance > 1 && CMP_LOWER == pfnCompare(pKey, pAVLNode->pLeft->pKey)) + { + return AVLNode_RightRotate(pAVLNode); + } + + /* Right Right Case */ + if (nBalance < -1 && CMP_GREATER == pfnCompare(pKey, pAVLNode->pRight->pKey)) + { + return AVLNode_LeftRotate(pAVLNode); + } + + /* Left Right Case */ + if (nBalance > 1 && CMP_GREATER == pfnCompare(pKey, pAVLNode->pLeft->pKey)) + { + pAVLNode->pLeft = AVLNode_LeftRotate(pAVLNode->pLeft); + return AVLNode_RightRotate(pAVLNode); + } + + /* Right Left Case */ + if (nBalance < -1 && CMP_LOWER == pfnCompare(pKey, pAVLNode->pRight->pKey)) + { + pAVLNode->pRight = AVLNode_RightRotate(pAVLNode->pRight); + return AVLNode_LeftRotate(pAVLNode); + } + + /* Return the unchanged node pointer */ + return pAVLNode; +} + +/** + * Function to search for a key in the AVL tree + * + * @return Found AVL node or NULL if failed + */ +AVLNode* AVLNode_Search(AVLNode* pNodeRoot, void* pKey, FnAVLNodeKeyCompare* pfnCompare) +{ + /* + * If pNodeRoot is NULL, return itself as NULL + * If pNodeRoot is search target, return itself as found result + */ + if (!pNodeRoot || CMP_EQUAL == pfnCompare(pNodeRoot->pKey, pKey)) + { + return pNodeRoot; + } + + if (CMP_LOWER == pfnCompare(pNodeRoot->pKey, pKey)) + { + return AVLNode_Search(pNodeRoot->pRight, pKey, pfnCompare); + } + + return AVLNode_Search(pNodeRoot->pLeft, pKey, pfnCompare); +} + +/** + * Function to create a new hash map + */ +HashMap* HashMap_Create(int nCapacity, FnAVLNodeKeyCompare* pfnCompare) +{ + HashMap* pHashMap = (HashMap*)malloc(sizeof(HashMap)); + if (!pHashMap) + { + return NULL; + } + + pHashMap->nCapacity = nCapacity; + pHashMap->nSize = 0; + pHashMap->pfnCompare = pfnCompare; + + AVLNode** ppBuckets = (AVLNode**)malloc(nCapacity * sizeof(AVLNode*)); + if (!ppBuckets) + { + free(pHashMap); + return NULL; + } + + memset(ppBuckets, 0, nCapacity * sizeof(AVLNode*)); + pHashMap->ppBuckets = ppBuckets; + + return pHashMap; +} + +/** + * [PRIVATE] + * + * Function to hash the key to a bucket index + */ +int BucketHash(int key, int capacity) +{ + return key % capacity; +} + +/** + * [PUBLIC] + * + * Function to insert a key-value pair into the hash map + */ +void HashMap_Put(HashMap* pHashMap, void* pKey, void* pValue) +{ + int nIndex = BucketHash(pKey, pHashMap->nCapacity); + pHashMap->ppBuckets[nIndex] = AVLNode_Insert(pHashMap->ppBuckets[nIndex], pKey, pValue, pHashMap->pfnCompare); + pHashMap->nSize++; +} + +/** + * [PUBLIC] + * + * Function to get the value associated with a key from the hash map + * + * @param pHashMap HashMap instance pointer + * @param pKey Key to get value of + * + * @return The value associated with pKey + */ +void* HashMap_Get(HashMap* pHashMap, void* pKey) +{ + int nIndex = BucketHash(pKey, pHashMap->nCapacity); + AVLNode* pAVLNode = AVLNode_Search(pHashMap->ppBuckets[nIndex], pKey, pHashMap->pfnCompare); + + if (!pAVLNode) + { + return NULL; + } + + return pAVLNode->pValue; +} + +/** + * [PRIVATE] + * + * Function to destroy the AVL tree + * + * @param pNodeRoot An AVLNode pointer that's downstream tree will be deleted + */ +void AVLNode_DestroyTree(AVLNode* pNodeRoot) +{ + if (pNodeRoot) + { + AVLNode_DestroyTree(pNodeRoot->pLeft); + AVLNode_DestroyTree(pNodeRoot->pRight); + free(pNodeRoot); + } +} + +/** + * Function to destroy the hash map and free memory + */ +void HashMap_Destroy(HashMap* pHashMap) +{ + /* Destroy each bucket tree */ + for (int i = 0; i < pHashMap->nCapacity; ++i) + { + AVLNode_DestroyTree(pHashMap->ppBuckets[i]); + } + + /* + * Free resources + * Delete buckets memory and hashmap object memory itself + */ + free(pHashMap->ppBuckets); + free(pHashMap); +} diff --git a/src/util/hashmap.h b/src/util/hashmap.h new file mode 100644 index 0000000..9983a48 --- /dev/null +++ b/src/util/hashmap.h @@ -0,0 +1,108 @@ +#pragma once + +typedef int (FnAVLNodeKeyCompare)(void* pKey1, void* pKey2); + +enum { + CMP_LOWER = -1, + CMP_EQUAL = 0, + CMP_GREATER = 1 +}; + +typedef struct AVLNode AVLNode; +struct AVLNode { + void* pKey; + void* pValue; + AVLNode* pLeft; + AVLNode* pRight; + int nHeight; + FnAVLNodeKeyCompare* pfnCompare; +}; + +typedef struct HashMap HashMap; +struct HashMap { + AVLNode** ppBuckets; + int nSize; + int nCapacity; + FnAVLNodeKeyCompare* pfnCompare; +}; + +/** + * [PUBLIC] + * + * Function to create a new AVL tree node + * + * @return Node created + */ +AVLNode* AVLNode_Create(void* pKey, void* pValue, FnAVLNodeKeyCompare* pfnCompare); + +/** + * [PUBLIC] + * + * Function to get height of a node + * + * @return Node height + */ +int AVLNode_Height(AVLNode* pAVLNode); + +/** + * [PRIVATE] + * + * Function to perform right rotation on an AVL tree + */ +AVLNode* AVLNode_RightRotate(AVLNode* pNodeY); + +/** + * [PRIVATE] + * + * Function to perform left rotation on an AVL tree + */ +AVLNode* AVLNode_LeftRotate(AVLNode* pNodeX); + +/** + * [PRIVATE] + * + * Function to get balance factor of a node + */ +int AVLNode_GetBalance(AVLNode* pAVLNode); + +/** + * [PRIVATE] + * + * Function to insert a node into AVL tree + */ +AVLNode* AVLNode_Insert(AVLNode* pAVLNode, void* pKey, void* pValue, FnAVLNodeKeyCompare* pfnCompare); + +/** + * Function to search for a key in the AVL tree + */ +AVLNode* AVLNode_Search(AVLNode* pNodeRoot, void* pKey, FnAVLNodeKeyCompare* pfnCompare); + +/** + * Function to create a new hash map + */ +HashMap* HashMap_Create(int nCapacity, FnAVLNodeKeyCompare* pfnCompare); + +/** + * Function to hash the key to an index + */ +int BucketHash(int key, int capacity); + +/** + * Function to insert a key-value pair into the hash map + */ +void HashMap_Put(HashMap* pHashMap, void* pKey, void* pValue); + +/** + * Function to get the value associated with a key from the hash map + */ +void* HashMap_Get(HashMap* pHashMap, void* pKey); + +/** + * Function to destroy the AVL tree + */ +void AVLNode_DestroyTree(AVLNode* pNodeRoot); + +/** + * Function to destroy the hash map and free memory + */ +void HashMap_Destroy(HashMap* pHashMap); diff --git a/src/util/list.c b/src/util/list.c new file mode 100644 index 0000000..d4fc374 --- /dev/null +++ b/src/util/list.c @@ -0,0 +1,91 @@ +#include "../precomp.h" + +#include "list.h" + +typedef struct List List; +struct List { + void* pData; + size_t nCapacity; + size_t nSize; + size_t nElementSize; +}; + +void List_Init(List* pList); + +List* List_Create(size_t nElementSize) +{ + List* pList = (List*)malloc(sizeof(List)); + if (pList) + { + List_Init(pList, nElementSize); + } + + return pList; +} + +#define LIST_CAPACITY 16 + +void List_Init(List* pList, size_t nElementSize) +{ + memset(pList, 0, sizeof(List)); + pList->pData = malloc(nElementSize * LIST_CAPACITY); + memset(pList->pData, 0, nElementSize * LIST_CAPACITY); + pList->nCapacity = LIST_CAPACITY; + pList->nSize = 0; + pList->nElementSize = nElementSize; + + return; +} + +void List_InsertFront(List* pList, void* pElement) +{ + if (pList->nSize >= pList->nCapacity) + { + printf("Reallocation triggered\n"); + size_t newCapacity = pList->nCapacity * 2; + void* newpData = realloc(pList->pData, newCapacity * pList->nElementSize); + if (!newpData) + { + fprintf(stderr, "Error: Memory allocation failed\n"); + return; + } + pList->pData = newpData; + pList->nCapacity = newCapacity; + } + + if (pList->nSize) + { + memmove((unsigned char*)pList->pData + pList->nElementSize, pList->pData, pList->nSize * pList->nElementSize); + } + memcpy(pList->pData, pElement, pList->nElementSize); + pList->nSize++; +} + +void List_InsertBack(List* pList, void* pElement) +{ + if (pList->nSize >= pList->nCapacity) + { + printf("Reallocation triggered\n"); + size_t newCapacity = pList->nCapacity * 2; + void* newpData = realloc(pList->pData, newCapacity * pList->nElementSize); + if (!newpData) { + fprintf(stderr, "Error: Memory allocation failed\n"); + return; + } + pList->pData = newpData; + pList->nCapacity = newCapacity; + } + + memcpy((unsigned char*)pList->pData + pList->nSize * pList->nElementSize, pElement, pList->nElementSize); + pList->nSize++; +} + +void* List_Get(List* pList, int idx) +{ + return (unsigned char*)pList->pData + idx * pList->nElementSize; +} + +size_t List_GetLength(List* pList) +{ + return pList->nSize; +} diff --git a/src/util/list.h b/src/util/list.h new file mode 100644 index 0000000..a45f8a7 --- /dev/null +++ b/src/util/list.h @@ -0,0 +1,13 @@ +#pragma once + +typedef struct List List; + +List* List_Create(size_t nElementSize); + +void List_InsertFront(List* pList, void* pElement); + +void List_InsertBack(List* pList, void* pElement); + +void* List_Get(List* pList, int idx); + +size_t List_GetLength(List* pList); diff --git a/src/util/pntrtti.c b/src/util/pntrtti.c new file mode 100644 index 0000000..1dba270 --- /dev/null +++ b/src/util/pntrtti.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +#include "bytestream.h" +#include "utf.h" +#include "pntstring.h" +#include "hashmap.h" + +#include + +typedef struct PntRTTI PntRTTI; +struct PntRTTI { + PntString* typeName; +}; + +void PntRTTI_Init(PntRTTI* pPntRTTI); + +PntRTTI* PntRTTI_Create() +{ + PntRTTI* pPntRTTI = malloc(sizeof(PntRTTI)); + if (pPntRTTI) + { + PntRTTI_Init(pPntRTTI); + } + + return pPntRTTI; +} + +void PntRTTI_Init(PntRTTI* pPntRTTI) +{ + memset(pPntRTTI, 0, sizeof(PntRTTI)); + + pPntRTTI->typeName = PntString_Create(); +} + +void PntRTTI_SetTypeName(PntRTTI* pPntRTTI, PCWSTR pszString) +{ + PntString_SetFromUTF16(pPntRTTI->typeName, pszString); +} + +DWORD PntRTTI_GetTypeName(PntRTTI* pPntRTTI, PWSTR pszString) +{ + return (DWORD)PntString_CopyToUTF16String(pPntRTTI->typeName, (uint16_t*)pszString); +} diff --git a/src/util/pntrtti.h b/src/util/pntrtti.h new file mode 100644 index 0000000..facbe2a --- /dev/null +++ b/src/util/pntrtti.h @@ -0,0 +1,7 @@ +#pragma once + +typedef struct PntRTTI PntRTTI; + +PntRTTI* PntRTTI_Create(); +void PntRTTI_SetTypeName(PntRTTI* pPntRTTI, PCWSTR pszString); +DWORD PntRTTI_GetTypeName(PntRTTI* pPntRTTI, PWSTR pszString); diff --git a/src/util/pntstring.c b/src/util/pntstring.c new file mode 100644 index 0000000..a9db39e --- /dev/null +++ b/src/util/pntstring.c @@ -0,0 +1,312 @@ +#include /* size_t */ +#include /* malloc, realloc etc. memory functions */ +#include + +/* uint16_t defined in inclusion of "stdint.h" in pntstring.h */ + +#include "bytestream.h" +#include "utf.h" +#include "pntstring.h" + +typedef struct PntString PntString; +struct PntString { + void* data; + size_t len; +}; + +/* Private functions forward declaration */ +void PntString_Init(PntString* pPntString); +void PntString_InitFromUTF16(PntString* pPntString, const uint16_t* pStr); + +/**************************************************************************** + * PUBLIC FUNCTIONS * + ****************************************************************************/ + +/** + * [PUBLIC] + * + * Create the PntString object + * + * @return PntString instance + */ +PntString* PntString_Create() +{ + PntString* pPntString = (PntString*)malloc(sizeof(PntString)); + if (pPntString) + { + PntString_Init(pPntString); + } + + return pPntString; +} + +/** + * [PUBLIC] + * + * Create the PntString object from UTF16 string + * + * @return PntString instance + */ +PntString* PntString_CreateFromUTF16(const uint16_t* pStr) +{ + PntString* pPntString = (PntString*)malloc(sizeof(PntString)); + if (pPntString) + { + PntString_InitFromUTF16(pPntString, pStr); + } + + return pPntString; +} + +/** + * [PUBLIC] + * + * Re-initialize existing PntString object with UTF16 string + * + * @param pPntString PntString instance + */ +void PntString_SetFromUTF16(PntString* pPntString, const uint16_t* pStr) +{ + /* Check that pPntString is valid */ + if (!pPntString) + { + return; + } + + ByteStream* pByteStream = ByteStream_Create(); + PutUTF16StringIntoUTF8Stream(pByteStream, pStr); + + size_t len = ByteStream_Length(pByteStream); + + uint8_t* pData; + if (pPntString->data) + { + pData = realloc(pPntString->data, len); + } + else { + pData = malloc(pPntString->data, len); + } + + if (!pData) + { + goto error; + } + + uint8_t* pBuffer = ByteStream_GetBuffer(pByteStream); + memcpy(pData, pBuffer, len); + + pPntString->data = pData; + pPntString->len = len; + +error: + ByteStream_Destroy(pByteStream); +} + +/** + * [PUBLIC] + * + * Get raw pointer to internal UTF8 string + * + * @param pPntString PntString instance + * + * @return Pointer to UTF8 string buffer + */ +const uint8_t* PntString_GetAsUTF8(const PntString* pPntString) +{ + return (const uint8_t*)pPntString->data; +} + +/** + * [PUBLIC] + * + * Convert to UTF16 string and copy it to destination buffer + * + * @param pPntString PntString instance + * @param pDest Destination buffer + * + * @return Length of data copied + */ +size_t PntString_CopyToUTF16String(PntString* pPntString, uint16_t* pDest) +{ + size_t utf16_len = 0; + size_t i = 0; + + ByteStream* pByteStream = ByteStream_Create(); + ByteStream_SetBuffer(pByteStream, pPntString->data, pPntString->len); + + uint32_t codepoint; + while (codepoint = ReadCodepointFromUTF8Stream(pByteStream)) + { + if (codepoint < 0x10000) + { + if (pDest) + { + pDest[utf16_len++] = (uint16_t)codepoint; + } + /* Dry run */ + else { + utf16_len++; + } + } + else { + if (pDest) + { + pDest[utf16_len++] = (uint16_t)((codepoint >> 10) + 0xD7C0); + pDest[utf16_len++] = (uint16_t)((codepoint & 0x3FF) + 0xDC00); + } + /* dry run */ + else { + utf16_len += 2; + } + + } + } + + if (pDest) + { + pDest[utf16_len] = 0; + } + + return utf16_len; +} + +/** + * [PUBLIC] + * + * Get the length of the string + * + * @param pPntString PntString instance to get length from + * + * @return String length + */ +size_t PntString_GetLength(const PntString* pPntString) +{ + return pPntString->len; +} + +/** + * [PUBLIC] + * + * Compare one PntString with another PntString + * + * returns -1 if the first string is lower, 1 if the first string is higher and 0 if both equal + * + * @param pPntString PntString to compare with + * @param pCompareWith PntString to compare to + * + * @return Comparison result + */ +int PntString_CompareWithAnother(const PntString* pPntString, const PntString* pCompareWith) +{ + if (!pPntString || !pCompareWith) + { + return -2; + } + + if (pPntString->len < pCompareWith->len) + { + return -1; + } + + if (pPntString->len > pCompareWith->len) + { + return 1; + } + + ByteStream* pByteStream1 = ByteStream_Create(); + ByteStream* pByteStream2 = ByteStream_Create(); + ByteStream_SetBuffer(pByteStream1, pPntString->data, pPntString->len); + ByteStream_SetBuffer(pByteStream2, pCompareWith->data, pCompareWith->len); + + int result = 0; + + for (size_t i = 0; i < pPntString->len; ++i) + { + uint32_t codepoint1 = ReadCodepointFromUTF8Stream(pByteStream1); + uint32_t codepoint2 = ReadCodepointFromUTF8Stream(pByteStream2); + + if (codepoint1 == codepoint2) + { + continue; + } + + /* As we deal with unsigned 32-bit values, casting it to + * signed 64-bit only for a subtraction would be too ambiguous + */ + if (codepoint1 < codepoint2) + { + result = -1; + goto final; + } + else { + result = 1; + goto final; + } + } + + final: + ByteStream_Destroy(pByteStream1); + ByteStream_Destroy(pByteStream2); + + return result; +} + +/** + * [PUBLIC] + * + * Destroy the PntString object and it's internal buffer + * + * @param pPntString PntString instance + */ +void PntString_Destroy(PntString* pPntString) +{ + free(pPntString->data); + free(pPntString); +} + +/**************************************************************************** + * PRIVATE FUNCTIONS * + ****************************************************************************/ + + /** + * [PRIVATE] + * + * Init empty PntString with NULL pData buffer and zero length + * + * @param pPntString PntString instance + */ +void PntString_Init(PntString* pPntString) +{ + memset(pPntString, 0, sizeof(PntString)); +} + +/** + * [PRIVATE] + * + * Init a PntString with predefined data copied from provided UTF16 string + * + * @param pPntString PntString instance + * @param pStr UTF16 string + */ +void PntString_InitFromUTF16(PntString* pPntString, const uint16_t* pStr) +{ + memset(pPntString, 0, sizeof(PntString)); + + ByteStream* pByteStream = ByteStream_Create(); + PutUTF16StringIntoUTF8Stream(pByteStream, pStr); + + size_t len = ByteStream_Length(pByteStream); + uint8_t* pData = (uint8_t*)malloc(len * sizeof(uint8_t)); + if (!pData) + { + goto error; + } + + ByteStream_Copy(pByteStream, pData); + + pPntString->data = pData; + pPntString->len = len; + +error: + ByteStream_Destroy(pByteStream); +} diff --git a/src/util/pntstring.h b/src/util/pntstring.h new file mode 100644 index 0000000..0320562 --- /dev/null +++ b/src/util/pntstring.h @@ -0,0 +1,89 @@ +#pragma once + +#include /* uint16_t */ + +typedef struct PntString PntString; + +/** + * [PUBLIC] + * + * Create the PntString object + * + * @return PntString instance + */ +PntString* PntString_Create(); + +/** + * [PUBLIC] + * + * Create the PntString object from UTF16 string + * + * @return PntString instance + */ +PntString* PntString_CreateFromUTF16(const uint16_t* pStr); + +/** + * [PUBLIC] + * + * Re-initialize existing PntString object with UTF16 string + * + * @param pPntString PntString instance + */ +void PntString_SetFromUTF16(PntString* pPntString, const uint16_t* pStr); + +/** + * [PUBLIC] + * + * Get raw pointer to internal UTF8 string + * + * @param pPntString PntString instance + * + * @return Pointer to UTF8 string buffer + */ +const uint8_t* PntString_GetAsUTF8(const PntString* pPntString); + +/** + * [PUBLIC] + * + * Convert to UTF16 string and copy it to destination buffer + * + * @param pPntString PntString instance + * @param pDest Destination buffer + * + * @return Length of data copied + */ +size_t PntString_CopyToUTF16String(PntString* pPntString, uint16_t* pDest); + +/** + * [PUBLIC] + * + * Get the length of the string + * + * @param pPntString PntString instance to get length from + * + * @return String length + */ +size_t PntString_GetLength(const PntString* pPntString); + +/** + * [PUBLIC] + * + * Compare one PntString with another PntString + * + * returns -1 if the first string is lower, 1 if the first string is higher and 0 if both equal + * + * @param pPntString PntString to compare with + * @param pCompareWith PntString to compare to + * + * @return Comparison result + */ +int PntString_CompareWithAnother(const PntString* pPntString, const PntString* pCompareWith); + +/** + * [PUBLIC] + * + * Destroy the PntString object and it's internal buffer + * + * @param pPntString PntString instance + */ +void PntString_Destroy(PntString* pPntString); diff --git a/src/queue.c b/src/util/queue.c similarity index 88% rename from src/queue.c rename to src/util/queue.c index 855214e..107aa3a 100644 --- a/src/queue.c +++ b/src/util/queue.c @@ -1,11 +1,12 @@ -#include "precomp.h" +#include "../precomp.h" #include "queue.h" /* Function to create a new queue */ Queue* CreateQueue() { - Queue* pQueue = (Queue*)calloc(1, sizeof(Queue)); + Queue* pQueue = (Queue*)malloc(sizeof(Queue)); + memset(pQueue, 0, sizeof(Queue)); if (!pQueue) { @@ -27,7 +28,8 @@ BOOL Queue_IsEmpty(Queue* pQueue) void Queue_Enqueue(Queue* pQueue, void* pData) { /* Create a new node */ - QueueNode* pNode = (QueueNode*)calloc(1, sizeof(QueueNode)); + QueueNode* pNode = (QueueNode*)malloc(sizeof(QueueNode)); + memset(pNode, 0, sizeof(QueueNode)); if (!pNode) { diff --git a/src/queue.h b/src/util/queue.h similarity index 100% rename from src/queue.h rename to src/util/queue.h diff --git a/src/stack.c b/src/util/stack.c similarity index 85% rename from src/stack.c rename to src/util/stack.c index e9105a4..1ef1658 100644 --- a/src/stack.c +++ b/src/util/stack.c @@ -1,10 +1,11 @@ -#include "precomp.h" +#include "../precomp.h" #include "stack.h" Stack* CreateStack() { Stack* stack = (Stack*)malloc(sizeof(Stack)); + memset(stack, 0, sizeof(Stack)); if (!stack) { return NULL; @@ -16,6 +17,7 @@ Stack* CreateStack() void Stack_Push(Stack* stack, void* node) { StackNode* newNode = (StackNode*)malloc(sizeof(StackNode)); + memset(newNode, 0, sizeof(StackNode)); if (newNode) { newNode->node = node; diff --git a/src/stack.h b/src/util/stack.h similarity index 100% rename from src/stack.h rename to src/util/stack.h diff --git a/src/tree.c b/src/util/tree.c similarity index 96% rename from src/tree.c rename to src/util/tree.c index 9477f13..fad61e7 100644 --- a/src/tree.c +++ b/src/util/tree.c @@ -1,11 +1,13 @@ -#include "precomp.h" +#include "../precomp.h" #include "tree.h" #include "stack.h" TreeNode* BinaryTree_AllocEmptyNode() { - return (TreeNode*)calloc(1, sizeof(TreeNode)); + TreeNode* pTreeNode = (TreeNode*)malloc(sizeof(TreeNode)); + memset(pTreeNode, 0, sizeof(TreeNode)); + return pTreeNode; } void TreeTraversalRLOT_Init(TreeTraversalRLOT* pTreeTraversalRLOT, TreeNode* pNode) diff --git a/src/tree.h b/src/util/tree.h similarity index 100% rename from src/tree.h rename to src/util/tree.h diff --git a/src/util/utf.c b/src/util/utf.c new file mode 100644 index 0000000..6ca6741 --- /dev/null +++ b/src/util/utf.c @@ -0,0 +1,179 @@ +#include "utf.h" +#include "bytestream.h" + +uint32_t ReadCodepointFromUTF8Stream(ByteStream* pStream) +{ + uint32_t codepoint; + + uint8_t b = ByteStream_Get(pStream); + uint8_t next; + + if ((b & 0x80) == 0) + { + return b; + } + else if ((b & 0xE0) == 0xC0) + { + codepoint = (b & 0b00011111) << 6; + + next = ByteStream_Get(pStream); + if ((next & 0xC0) == 0x80) + { + codepoint |= next & 0x3F; + } + else { + exit(1); + } + } + else if ((b & 0xF0) == 0xE0) + { + codepoint = (b & 0xb00001111) << 12; + + next = ByteStream_Get(pStream); + if ((next & 0xC0) == 0x80) + { + codepoint |= (next & 0x3F) << 6; + } + else { + exit(1); + } + + next = ByteStream_Get(pStream); + if ((next & 0xC0) == 0x80) + { + codepoint |= next & 0x3F; + } + else { + exit(1); + } + } + else if ((b & 0xF8) == 0xF0) + { + codepoint = ((b & 0x07) << 18); + + next = ByteStream_Get(pStream); + if ((next & 0xC0) == 0x80) + { + codepoint |= (next & 0x3F) << 12; + } + else { + exit(1); + } + + next = ByteStream_Get(pStream); + if ((next & 0xC0) == 0x80) + { + codepoint |= (next & 0x3F) < 6; + } + else { + exit(1); + } + + next = ByteStream_Get(pStream); + if ((next & 0xC0) == 0x80) + { + codepoint |= next & 0x3F; + } + else { + exit(1); + } + } + else { + exit(1); + } + + return codepoint; +} + +void PutCodepointIntoUTF8Stream(ByteStream* pStream, uint32_t codepoint) +{ + if (codepoint < 0x80) + { + ByteStream_Put(pStream, (char)codepoint); + } + else if (codepoint < 0x800) + { + ByteStream_Put(pStream, ((codepoint >> 6) & 0b00011111) | 0b11000000); + ByteStream_Put(pStream, (codepoint & 0b00111111) | 0b10000000); + } + else if (codepoint < 0x00010000) + { + ByteStream_Put(pStream, ((codepoint >> 12) & 0b00001111) | 0b11100000); + ByteStream_Put(pStream, ((codepoint >> 6) & 0b00111111) | 0b10000000); + ByteStream_Put(pStream, ((codepoint >> 0) & 0b00111111) | 0b10000000); + } + else if (codepoint < 0x00110000) + { + ByteStream_Put(pStream, ((codepoint >> 18) & 0b00000111) | 0b11110000); + ByteStream_Put(pStream, ((codepoint >> 12) & 0b00111111) | 0b10000000); + ByteStream_Put(pStream, ((codepoint >> 6) & 0b00111111) | 0b10000000); + ByteStream_Put(pStream, ((codepoint >> 0) & 0b00111111) | 0b10000000); + } +} + +void PutUTF16StringIntoUTF8Stream(ByteStream* pStream, const uint16_t* utf16_string) +{ + while (*utf16_string != 0) + { + uint32_t codepoint; + uint16_t utf16_char = *utf16_string++; + + /* Check if it's a surrogate pair */ + if ((utf16_char & 0xFC00) == 0xD800) + { + uint16_t utf16_char2 = *utf16_string++; + codepoint = ((utf16_char & 0x3FF) << 10) + (utf16_char2 & 0x3FF) + 0x10000; + } + else { + codepoint = utf16_char; + } + + PutCodepointIntoUTF8Stream(pStream, codepoint); + } +} + +void PutUTF32StringIntoByteStream(ByteStream* pByteStream, const uint32_t* str, int len) +{ + if (len == -1) + { + /* Calculate the length if not provided */ + len = 0; + while (str[len] != 0) + { + ++len; + } + } + + for (size_t i = 0; i < len; ++i) + { + uint32_t ch = str[i]; + + /* Little-endian encoding */ + ByteStream_Put(pByteStream, ch & 0xFF); + ByteStream_Put(pByteStream, ch >> 8 & 0xFF); + ByteStream_Put(pByteStream, ch >> 16 & 0xFF); + ByteStream_Put(pByteStream, ch >> 24 & 0xFF); + } +} + +void PutUTF16StringIntoByteStream(ByteStream* pByteStream, const uint16_t* str, int len) +{ + if (len == -1) + { + /* Calculate the length if not provided */ + len = 0; + while (str[len] != 0) + { + ++len; + } + } + + for (size_t i = 0; i < len; ++i) + { + uint16_t ch = str[i]; + + /* Little-endian encoding */ + ByteStream_Put(pByteStream, ch & 0xFF); + ByteStream_Put(pByteStream, ch >> 8 & 0xFF); + } +} diff --git a/src/util/utf.h b/src/util/utf.h new file mode 100644 index 0000000..56419ab --- /dev/null +++ b/src/util/utf.h @@ -0,0 +1,16 @@ +#ifndef _UTF_H_ +#define _UTF_H_ + +#pragma once + +#include + +#include "bytestream.h" + +uint32_t ReadCodepointFromUTF8Stream(ByteStream* pStream); +void PutCodepointIntoUTF8Stream(ByteStream* pStream, uint32_t codepoint); +void PutUTF16StringIntoUTF8Stream(ByteStream* pStream, const uint16_t* utf16_string); +void PutUTF32StringIntoByteStream(ByteStream* pByteStream, const uint32_t* str, int len); +void PutUTF16StringIntoByteStream(ByteStream* pByteStream, const uint16_t* str, int len); + +#endif /* _UTF_H_ */ diff --git a/src/util/vector.c b/src/util/vector.c new file mode 100644 index 0000000..751ab76 --- /dev/null +++ b/src/util/vector.c @@ -0,0 +1,74 @@ +#include "../precomp.h" + +#include "vector.h" + +#include "../panitent.h" + +// Initialize the vector +void Vector_Init(Vector* pVector, size_t nElementSize) +{ + pVector->pData = NULL; + pVector->nSize = 0; + pVector->nCapacity = 0; + pVector->nElementSize = nElementSize; +} + +// Push an element to the back of the vector +void Vector_PushBack(Vector* pVector, void* pValue, size_t nValueSize) +{ + // Check if resize is needed + if (pVector->nSize >= pVector->nCapacity) + { + // Double the capacity + pVector->nCapacity = (!pVector->nCapacity) ? 1 : pVector->nCapacity * 2; + pVector->pData = realloc(pVector->pData, pVector->nCapacity * sizeof(void*)); + if (!pVector->pData) + { + Panitent_RaiseException(L"Memory allocation failed"); + } + } + + // Allocate memory for the new element + pVector->pData[pVector->nSize] = malloc(nValueSize); + if (!pVector->pData[pVector->nSize]) + { + Panitent_RaiseException(L"Memory allocation failed"); + } + + // Copy the value into the new element + memcpy(pVector->pData[pVector->nSize], pValue, nValueSize); + pVector->nSize++; +} + +// Access an element at a specific index +void* Vector_At(Vector* pVector, size_t nIndex) +{ + if (nIndex >= pVector->nSize) + { + Panitent_RaiseException(L"Index out of bounds"); + } + return pVector->pData[nIndex]; +} + +// Get the number of elements stored in the vector +size_t Vector_GetSize(Vector* pVector) +{ + return pVector->nSize; +} + +// Free the memory allocated by the vector +void Vector_Free(Vector* pVector) +{ + if (pVector->pData) + { + for (size_t i = 0; i < pVector->nSize; ++i) + { + free(pVector->pData[i]); + } + free(pVector->pData); + } + + pVector->pData = NULL; + pVector->nSize = 0; + pVector->nCapacity = 0; +} diff --git a/src/util/vector.h b/src/util/vector.h new file mode 100644 index 0000000..6262c9d --- /dev/null +++ b/src/util/vector.h @@ -0,0 +1,15 @@ +#pragma once + +typedef struct Vector Vector; +struct Vector { + void** pData; + size_t nSize; + size_t nCapacity; + size_t nElementSize; +}; + +void Vector_Init(Vector* pVector, size_t nElementSize); +void Vector_PushBack(Vector* pVector, void* pValue, size_t nValueSize); +void* Vector_At(Vector* pVector, size_t nIndex); +size_t Vector_GetSize(Vector* pVector); +void Vector_Free(Vector* pVector); diff --git a/src/viewport.c b/src/viewport.c index 1fa1e1b..9d0e289 100644 --- a/src/viewport.c +++ b/src/viewport.c @@ -74,14 +74,15 @@ LRESULT ViewportWindow_UserProc(ViewportWindow* pViewportWindow, HWND hWnd, UINT ViewportWindow* ViewportWindow_Create(struct Application* app) { - ViewportWindow* window = calloc(1, sizeof(ViewportWindow)); + ViewportWindow* pViewportWindow = (ViewportWindow*)malloc(sizeof(ViewportWindow)); + memset(pViewportWindow, 0, sizeof(ViewportWindow)); - if (window) + if (pViewportWindow) { - ViewportWindow_Init(window, app); + ViewportWindow_Init(pViewportWindow, app); } - return window; + return pViewportWindow; } void ViewportWindow_Init(ViewportWindow* pViewportWindow, struct Application* app) @@ -198,7 +199,7 @@ static inline void ViewportWindow_DrawDebugText(ViewportWindow* pViewportWindow, L"Offset x: %d, y: %d\n" L"Scale: %f\n" L"Document set: %s\n" - L"Document dimensions width: %d, height %d"; + L"Document dimensions width: %d, AVLNode_Height %d"; hFont = GetGuiFont(); @@ -297,6 +298,7 @@ BOOL Viewport_BlitCanvas(HDC hDC, LPRECT prcView, Canvas* canvas) HDC hBmDC = CreateCompatibleDC(hDC); unsigned char* pData = malloc(canvas->buffer_size); + memset(pData, 0, canvas->buffer_size); if (!pData) return FALSE; @@ -540,7 +542,7 @@ void ViewportWindow_OnMouseMove(ViewportWindow* pViewportWindow, int x, int y, U { HWND hWnd = Window_GetHWND((Window *)pViewportWindow); - Tool *pTool = Panitent_GetTool(); + Tool *pTool = Panitent_GetSelectedTool(); if (pViewportWindow->bDrag) { @@ -592,7 +594,7 @@ void ViewportWindow_OnLButtonDown(ViewportWindow* pViewportWindow, int x, int y, /* Receive keyboard messages */ SetFocus(hWnd); - Tool* pTool = Panitent_GetTool(); + Tool* pTool = Panitent_GetSelectedTool(); if (pTool && pTool->OnLButtonDown) { POINT ptCanvas; @@ -606,7 +608,7 @@ void ViewportWindow_OnLButtonUp(ViewportWindow* pViewportWindow, int x, int y, U { HWND hWnd = Window_GetHWND((Window *)pViewportWindow); - Tool* pTool = Panitent_GetTool(); + Tool* pTool = Panitent_GetSelectedTool(); if (pTool && pTool->OnLButtonUp) { @@ -624,7 +626,7 @@ void ViewportWindow_OnRButtonUp(ViewportWindow* pViewportWindow, int x, int y, U { HWND hWnd = Window_GetHWND((Window *)pViewportWindow); - Tool* pTool = Panitent_GetTool(); + Tool* pTool = Panitent_GetSelectedTool(); if (pTool && pTool->OnRButtonUp) { diff --git a/src/wic.c b/src/wic.c index 6796e75..e3d4bf6 100644 --- a/src/wic.c +++ b/src/wic.c @@ -291,6 +291,7 @@ ImageBuffer ImageFileReader(LPWSTR szFilePath) nStride = DIB_WIDTHBYTES(nWidth * 32); nImage = nStride * nHeight; imageBits = malloc(nImage); + memset(imageBits, 0, nImage); hr = pConverter->lpVtbl->CopyPixels(pConverter, NULL, nStride, nImage, (LPVOID)imageBits); diff --git a/src/win32/application.c b/src/win32/application.c index 4759658..3b8a7e1 100644 --- a/src/win32/application.c +++ b/src/win32/application.c @@ -9,7 +9,8 @@ int Application_Run(Application*); Application* Application_Create() { - Application* app = calloc(1, sizeof(Application)); + Application* app = (Application*)malloc(sizeof(Application)); + memset(app, 0, sizeof(Application)); Application_Init(app); return app; } diff --git a/src/win32/common.h b/src/win32/common.h index 2b8cc52..8471a42 100644 --- a/src/win32/common.h +++ b/src/win32/common.h @@ -1,9 +1,17 @@ #ifndef _WIN32_COMMON_H #define _WIN32_COMMON_H -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/win32/dialog.c b/src/win32/dialog.c index d98df15..733f834 100644 --- a/src/win32/dialog.c +++ b/src/win32/dialog.c @@ -12,6 +12,8 @@ INT_PTR Dialog_DefaultDialogProc(Dialog* pDialog, UINT message, WPARAM wParam, L INT_PTR CALLBACK Dialog_DialogProcStatic(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); INT_PTR Dialog_CreateWindow(Dialog* pDialog, UINT uResourceId, HWND hWndParent, BOOL bModal); +Dialog* g_pCurrentDialog; + BOOL Dialog_OnCommand(Dialog* pDialog, WPARAM wParam, LPARAM lParam) { return FALSE; @@ -24,10 +26,12 @@ void Dialog_OnInitDialog(Dialog* pDialog) Dialog* Dialog_Create(Application* pApp) { - Dialog* pDialog = (Dialog*)calloc(1, sizeof(Dialog)); - + Dialog* pDialog = (Dialog*)malloc(sizeof(Dialog)); + if (pDialog) { + memset(pDialog, 0, sizeof(Dialog)); + Dialog_Init(pDialog, pApp); } @@ -91,15 +95,15 @@ INT_PTR Dialog_DefaultDialogProc(Dialog* pDialog, UINT message, WPARAM wParam, L INT_PTR CALLBACK Dialog_DialogProcStatic(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { Dialog* pDialog = NULL; - pDialog = (Dialog*)GetFromHashMap(&g_hWndMap, hWnd); + pDialog = (Dialog*)WindowMap_Get(hWnd); - if (!pDialog && message == WM_INITDIALOG) + if (!pDialog) { - pDialog = (Dialog*)lParam; + pDialog = (Dialog*)g_pCurrentDialog; if (pDialog) { ((Window*)pDialog)->hWnd = hWnd; - InsertIntoHashMap(&g_hWndMap, hWnd, pDialog); + WindowMap_Insert(hWnd, pDialog); } } @@ -111,15 +115,20 @@ INT_PTR CALLBACK Dialog_DialogProcStatic(HWND hWnd, UINT message, WPARAM wParam, INT_PTR Dialog_CreateWindow(Dialog* pDialog, UINT uResourceId, HWND hWndParent, BOOL bModal) { + g_pCurrentDialog = pDialog; + if (bModal) { INT_PTR result = DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(uResourceId), hWndParent, Dialog_DialogProcStatic, (LPARAM)pDialog); pDialog->base.hWnd = NULL; + g_pCurrentDialog = NULL; return result; } else { HWND hWnd = CreateDialogParam(GetModuleHandle(NULL), MAKEINTRESOURCE(uResourceId), hWndParent, Dialog_DialogProcStatic, (LPARAM)pDialog); + g_pCurrentDialog = NULL; + return (INT_PTR)hWnd; } } diff --git a/src/win32/framework.c b/src/win32/framework.c index 4000695..36509cb 100644 --- a/src/win32/framework.c +++ b/src/win32/framework.c @@ -1,9 +1,8 @@ #include "common.h" -typedef struct HashMap HashMap; -extern HashMap g_hWndMap; +#include "windowmap.h" void WindowingInit() { - InitHashMap(&g_hWndMap, 127); + WindowMap_GlobalInit(); } diff --git a/src/win32/listbox.c b/src/win32/listbox.c index 5993f0c..3a88a6b 100644 --- a/src/win32/listbox.c +++ b/src/win32/listbox.c @@ -9,6 +9,7 @@ void ListBoxCtl_PreCreate(LPCREATESTRUCT lpcs); ListBoxCtl* ListBoxCtl_Create(Application* pApplication) { ListBoxCtl* pListBoxCtl = (ListBoxCtl*)malloc(sizeof(ListBoxCtl)); + memset(pListBoxCtl, 0, sizeof(ListBoxCtl)); if (pListBoxCtl) { diff --git a/src/win32/propertygrid.c b/src/win32/propertygrid.c new file mode 100644 index 0000000..cd355bd --- /dev/null +++ b/src/win32/propertygrid.c @@ -0,0 +1,39 @@ +#include "common.h" + +#include "propertygrid.h" +#include "propertygridimpl.h" + +PropertyGridCtl* PropertyGridCtl_Create(Application* pApplication); +void PropertyGridCtl_Init(PropertyGridCtl* pPropertyGridCtl, Application* pApplication); +void PropertyGridCtl_PreCreate(LPCREATESTRUCT lpcs); + +PropertyGridCtl* PropertyGridCtl_Create(Application* pApplication) +{ + PropertyGridCtl* pPropertyGridCtl = (PropertyGridCtl*)malloc(sizeof(PropertyGridCtl)); + memset(pPropertyGridCtl, 0, sizeof(PropertyGridCtl)); + + if (pPropertyGridCtl) + { + memset(pPropertyGridCtl, 0, sizeof(PropertyGridCtl)); + PropertyGridCtl_Init(pPropertyGridCtl, pApplication); + } + + return pPropertyGridCtl; +} + +void PropertyGridCtl_Init(PropertyGridCtl* pPropertyGridCtl, Application* pApplication) +{ + InitPropertyGrid(GetModuleHandle(NULL)); + + Window_Init((Window*)pPropertyGridCtl, pApplication); + + pPropertyGridCtl->base.PreRegister = NULL; + pPropertyGridCtl->base.PreCreate = PropertyGridCtl_PreCreate; +} + +void PropertyGridCtl_PreCreate(LPCREATESTRUCT lpcs) +{ + lpcs->dwExStyle = WS_EX_CLIENTEDGE; + lpcs->lpszClass = WC_LISTBOX; + lpcs->style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | LBS_HASSTRINGS; +} diff --git a/src/win32/propertygrid.h b/src/win32/propertygrid.h new file mode 100644 index 0000000..d9dacf3 --- /dev/null +++ b/src/win32/propertygrid.h @@ -0,0 +1,14 @@ +#pragma once + +#include "window.h" + +typedef struct PropertyGridCtl PropertyGridCtl; + +struct PropertyGridCtl { + Window base; +}; + +PropertyGridCtl* PropertyGridCtl_Create(Application* pApplication); +void PropertyGridCtl_Init(PropertyGridCtl* pPropertyGridCtl, Application* pApplication); + +void PropertyGridCtl_PreCreate(LPCREATESTRUCT lpcs); diff --git a/src/win32/propertygridimpl.c b/src/win32/propertygridimpl.c new file mode 100644 index 0000000..2e66b90 --- /dev/null +++ b/src/win32/propertygridimpl.c @@ -0,0 +1,5282 @@ +////////////////////////////////////////////////////////////////////////////// +/// +/// @file propertyGrid.c +/// +/// @brief A property grid control in Win32 SDK C. +/// +/// @author David MacDermot +/// +/// @par Comments: +/// This source is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +/// +/// @date 11-23-21 +/// +/// @version 2.4 +/// +/// @todo +/// +/// @bug +/// +////////////////////////////////////////////////////////////////////////////// + +// Suppress POCC Warning "Argument x to 'sscanf' does not match the format string; +// expected 'unsigned char *' but found 'unsigned long'" +#ifdef __POCC__ +#pragma warn(disable:2234) +#endif + +#ifndef _WIN32_WINNT // Necessary for WM_MOUSEWHEEL support +#define _WIN32_WINNT 0x0500 +#endif + +#if defined(UNICODE) || defined(_UNICODE) +#define STR_SPEC "ls" +#define CHR_SPEC "lc" +#define SCN_SPEC "l" +#else +#define STR_SPEC "s" +#define CHR_SPEC "c" +#define SCN_SPEC "" +#endif + +#define _CRT_SECURE_NO_WARNINGS + +/* +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +*/ +#include "common.h" +#include "propertygridimpl.h" + +#define ID_LISTBOX 2000 /// ComboBox_SetItemData((hwndCtl), (iIndex), \ + MAKELPARAM(0,(fCheck))) ? CB_ERR : \ + (InvalidateRgn((hwndCtl), NULL, FALSE), (iIndex))) + +/// @def CheckedComboBox_GetCheckState(hwndCtl, iIndex) +/// +/// @brief Gets the checked state of an item in a checked combobox control. +/// +/// @param hwndCtl The handle of a checked combobox. +/// @param iIndex The zero-based index of the item for which to get the check state. +/// +/// @returns Nonzero if the given item is checked, or zero otherwise. +#define CheckedComboBox_GetCheckState(hwndCtl, iIndex) \ + (BOOL) HIWORD(ComboBox_GetItemData(hwndCtl, iIndex)) + +/// @} + +/// @brief A nice assortment of custom colors. +static COLORREF g_CustomColors[] = +{ + 0xDCDCDC, 0xCDCDDB, 0xBDBED2, 0xE3C4C3, + 0xFFE0C0, 0xE0C8AB, 0xD7AFB0, 0xF0BFEB, + 0xE2A7DA, 0x9697FF, 0xBFBFFF, 0xBBDBFF, + 0xCBFFFF, 0xBFEFCB, 0xA6DEB3, 0x82D28B +}; + +/****************************************************************************/ +//Forward Declarations +static LRESULT CALLBACK Grid_Proc(HWND, UINT, WPARAM, LPARAM); +static LRESULT CALLBACK ListBox_Proc(HWND, UINT, WPARAM, LPARAM); +static VOID Grid_NotifyParent(VOID); + +/// @brief Default window procedure for the grid and child windows. +/// +/// @param hwnd Handle of grid or child. +/// @param msg Which message? +/// @param wParam Message parameter. +/// @param lParam Message parameter. +/// +/// @returns LRESULT depends on message. +static LRESULT DefProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + return CallWindowProc((WNDPROC)GetProp(hwnd, WPRC), hwnd, msg, wParam, lParam); +} + +#pragma region Instance Data + +/// @var LISTBOXITEM +/// @brief An item object + +/// @var LPLISTBOXITEM +/// @brief Pointer to an item + +/// @struct tagLISTBOXITEM +/// @brief This is the data associated with a grid item +typedef struct tagLISTBOXITEM { + LPTSTR lpszCatalog; ///< Catalog (group) name + LPTSTR lpszPropName; ///< Property (item) name + LPTSTR lpszMisc; ///< Item specific data string + LPTSTR lpszPropDesc; ///< Property (item) description + LPTSTR lpszCurValue; ///< Property (item) value + LPVOID lpUserData; ///< Additional user data + INT iItemType; ///< Property (item) type identifier + BOOL fCollapsed; ///< Catalog (group) collapsed flag +} +LISTBOXITEM, *LPLISTBOXITEM; + +/// @var INSTANCEDATA +/// @brief Data for this instance of the control + +/// @var LPINSTANCEDATA +/// @brief Pointer instance data + +/// @struct tagINSTANCEDATA +/// @brief This is the data associated with an instance of the grid +typedef struct tagINSTANCEDATA { + HINSTANCE hInstance; ///< Handle to this instance + LPLISTBOXITEM lpCurrent;///< Currently selected grid item + HWND hwndParent; ///< Handle of grid's parent + HWND hwndToolTip; ///< ToolTip handle + HWND hwndPropDesc; ///< Description pane handle + HWND hwndListBox; ///< Handle of the visible list box + HWND hwndListMap; ///< Handle of the hidden list box + HWND hwndCtl1; ///< Handle of the primary edit control + HWND hwndCtl2; ///< Handle of the secondary edit control + INT iHDivider; ///< Position of horizontal divider + INT iVDivider; ///< Position of verticle divider + INT iDescHeight; ///< Height of description pane + INT iPrevSel; ///< Index of previously selected item + BOOL fXpOrLower; ///< TRUE is running on Xp or earlier version of OS + BOOL fGotFocus; ///< TRUE while focus resides within property grid + BOOL fScrolling; ///< TRUE while scrolling + BOOL fTracking; ///< TRUE while moving dividers + LONG nOldDivX; ///< Previous divider x position + LONG nOldDivY; ///< Previous divider y position + LONG nDivTop; ///< Horizontal Divider positon limit + LONG nDivBtm; ///< Horizontal Divider positon limit + LONG nDivLft; ///< Vertical Divider positon limit + LONG nDivRht; ///< Vertical Divider positon limit +} +INSTANCEDATA, *LPINSTANCEDATA; + +static LPINSTANCEDATA g_lpInst; ///< instance data (this) pointer + +/// @brief Get the Instance data associated with this instance. +/// +/// @param hControl Handle to Current instance. +/// @param ppInstanceData - Pointer to the address of an INSTANCEDATA struct. +/// +/// @returns TRUE if successful +static BOOL Control_GetInstanceData(HWND hControl, LPINSTANCEDATA *ppInstanceData) +{ + *ppInstanceData = (LPINSTANCEDATA)GetProp(hControl, (LPCTSTR)_T("lpInsData")); + if (NULL != *ppInstanceData) + return TRUE; + return FALSE; +} + +/// @brief Allocate the Instance data associated with this instance. +/// +/// @param hControl Handle to current instance. +/// @param pInstanceData Pointer to an INSTANCEDATA struct +/// +/// @returns TRUE if successful +static BOOL Control_CreateInstanceData(HWND hControl, LPINSTANCEDATA pInstanceData) +{ + LPINSTANCEDATA pInst = (LPINSTANCEDATA)malloc(sizeof(INSTANCEDATA)); + memmove(pInst, pInstanceData, sizeof(INSTANCEDATA)); + return SetProp(hControl, (LPCTSTR)_T("lpInsData"), pInst); +} + +/// @brief Free the instance data allocation of an instance of the Grid Control. +/// +/// @param hControl Handle to current instance. +/// +/// @returns TRUE if successful +static BOOL Control_FreeInstanceData(HWND hControl) +{ + LPINSTANCEDATA pInst; + if (Control_GetInstanceData(hControl, &pInst)) + { + free((LPINSTANCEDATA)pInst); + RemoveProp(hControl, (LPCTSTR)_T("lpInsData")); + return TRUE; + } + return FALSE; +} + +/// @brief Get Item data associated with a property grid item. +/// +/// @param hwnd Handle of a listbox. +/// @param idx The item index. +/// +/// @returns a listbox item pointer if successful, otherwise NULL +static LPLISTBOXITEM ListBox_GetItemDataSafe(HWND hwnd, INT idx) +{ + LRESULT lres = ListBox_GetItemData(hwnd, idx); + // Don't return LB_ERR cast to an item! + if (LB_ERR == lres) + return NULL; + return (LPLISTBOXITEM)lres; +} + +#pragma endregion Instance Data + +#pragma region memory management + +/// @brief Allocate and store a string. +/// +/// @param str The string to store. +/// +/// @returns a Pointer to the allocated string. +static LPTSTR NewString(LPTSTR str) +{ + if (NULL == str || _T('\0') == *str) + str = _T(""); + LPTSTR tmp = (LPTSTR)calloc(_tcslen(str) + 1, sizeof(TCHAR)); + + if (NULL == tmp) + { + return (LPTSTR)calloc(1, sizeof(TCHAR)); + } + return _tmemmove(tmp, str, _tcslen(str)); +} + +/// @brief Allocate and store a string array (double-null-terminated string). +/// +/// @param szzStr The double-null-terminated string to store. +/// +/// @returns a Pointer to the allocated string array. +static LPTSTR NewStringArray(LPTSTR szzStr) +{ + if (NULL == szzStr || _T('\0') == *szzStr) + szzStr = _T(""); + + //Determine total pszBuffer length + INT iLen = 0; + //Walk the pszBuffer to find the terminating empty string. + for (LPTSTR p = szzStr; *p; (p += _tcslen(p) + 1, iLen = p - szzStr)) ; + + //Allocate for array + LPTSTR tmp = (LPTSTR)calloc(iLen + 1, sizeof(TCHAR)); + + if (NULL == tmp) + { + return (LPTSTR)calloc(1, sizeof(TCHAR)); + } + return _tmemmove(tmp, szzStr, iLen); +} + +/// @brief Allocate and populate a list box item data structure. +/// +/// @param szCatalog The catalog this item belongs to. +/// @param szPropName The item's name. +/// @param szCurValue The item's current value. +/// @param szMisc This data varies with item type. +/// @param szPropDesc The item's description string. +/// @param iItemType The item type designation. +/// @param lpUserData Optional pointer to user data. +/// +/// @returns a Pointer to the allocated list box item. +static LPLISTBOXITEM NewItem(LPTSTR szCatalog, LPTSTR szPropName, LPTSTR szCurValue, + LPTSTR szMisc, LPTSTR szPropDesc, INT iItemType, LPVOID lpUserData) +{ + LPLISTBOXITEM lpItem = (LPLISTBOXITEM)calloc(1, sizeof(LISTBOXITEM)); + + lpItem->iItemType = iItemType; + lpItem->lpszCatalog = NewString(szCatalog); + lpItem->lpszPropName = NewString(szPropName); + lpItem->lpszCurValue = NewString(szCurValue); + + if (PIT_COMBO == iItemType || PIT_EDITCOMBO == iItemType || PIT_CHECKCOMBO == iItemType) + lpItem->lpszMisc = NewStringArray(szMisc); + else + lpItem->lpszMisc = NewString(szMisc); + + lpItem->lpszPropDesc = NewString(szPropDesc); + + lpItem->lpUserData = lpUserData; + + return lpItem; +} + +/// @brief Free a list box item's data structure. +/// +/// @param lpItem A pointer to a LISTBOXITEM object. +/// +/// @returns VOID. +static VOID DeleteItem(LPLISTBOXITEM lpItem) +{ + free(lpItem->lpszCatalog); + free(lpItem->lpszPropName); + if (PIT_CHECK != lpItem->iItemType) //Don't attempt to free a constant + { + free(lpItem->lpszCurValue); + free(lpItem->lpszMisc); + } + free(lpItem->lpszPropDesc); + free(lpItem); +} + +/// @brief Handle the WM_DELETEIETM message. +/// +/// @param hwnd The handle of a list box (in this case the list map). +/// @param lpDeleteItem A pointer to the delete item struct. +/// +/// @returns VOID. +static VOID Grid_OnDeleteItem(HWND hwnd, const DELETEITEMSTRUCT *lpDeleteItem) +{ + if (g_lpInst->hwndListMap == lpDeleteItem->hwndItem) + DeleteItem((LPLISTBOXITEM)lpDeleteItem->itemData); + + // Catalogs only reside in visible list box + if (g_lpInst->hwndListBox == lpDeleteItem->hwndItem) + { + if (PIT_CATALOG == ((LPLISTBOXITEM)lpDeleteItem->itemData)->iItemType) + DeleteItem((LPLISTBOXITEM)lpDeleteItem->itemData); + } +} + +#pragma endregion memory management + +#pragma region Drawing + +/// @brief Pass keyboard focus to parent and refresh grid. +/// +/// @returns VOID. +static VOID SetFocusToParent(VOID) +{ + SetFocus(g_lpInst->hwndParent); +} + +/// @brief Handle WM_KILLFOCUS in editors. +/// +/// @param hwnd Handle of the editor. +/// @param hwndNewFocus Handle of the window that recieved focus. +/// +/// @returns VOID. +static VOID Editor_OnKillFocus(HWND hwnd, HWND hwndNewFocus) +{ + //Draw grid selection inactive when grid doesn't have focus. + g_lpInst->fGotFocus = (NULL != hwndNewFocus && ( + g_lpInst->hwndListBox == hwndNewFocus || + g_lpInst->hwndCtl1 == hwndNewFocus || + g_lpInst->hwndCtl2 == hwndNewFocus || + g_lpInst->hwndPropDesc == hwndNewFocus || + g_lpInst->hwndToolTip == hwndNewFocus)); + + if (!g_lpInst->fGotFocus) + Refresh(g_lpInst->hwndListBox); +} + +/// @brief Handle WM_KILLFOCUS in listbox. +/// +/// @param hwnd Handle of the editor. +/// @param hwndNewFocus Handle of the window that recieved focus. +/// +/// @returns VOID. +static VOID ListBox_OnKillFocus(HWND hwnd, HWND hwndNewFocus) +{ + if (NULL != g_lpInst->lpCurrent) + { + if (PIT_CHECK == g_lpInst->lpCurrent->iItemType) + g_lpInst->lpCurrent->lpszMisc = UNSELECT; + } + Editor_OnKillFocus(hwnd, hwndNewFocus); +} + +/// @brief Set a control's font to bold or back to normal. +/// +/// @param hwndCtl The handle of a control. +/// @param fBold TRUE if bold desired. +/// +/// @returns HFONT A new font with the desired font weight. +static HFONT Font_SetBold(HWND hwndCtl, BOOL fBold) +{ + HFONT hFont; + LOGFONT lf; + + // Get a handle to the control's font object + hFont = (HFONT)SendMessage(hwndCtl, WM_GETFONT, 0, 0); + + // Pull the handle into a Logical Font UDT type + GetObject(hFont, sizeof(LOGFONT), &lf); + + // Determine if that font should be bold or not + if (fBold) + lf.lfWeight = FW_BOLD; + else + lf.lfWeight = FW_NORMAL; + + // Create a new font based off the logical font UDT + return CreateFontIndirect(&lf); +} + +/// @brief Draw a filled rectangle of a desired color. +/// +/// @param hdc A handle to a device context. +/// @param lprc The address of a RECT structure with drawing coordinates. +/// @param clr The desired fill color value. +/// +/// @returns VOID. +static VOID FillSolidRect(HDC hdc, LPRECT lprc, COLORREF clr) +{ + HBRUSH hbrush = CreateSolidBrush(clr); + FillRect(hdc, lprc, hbrush); + DeleteObject(hbrush); +} + +/// @brief Draw a line. +/// +/// @param hdc A handle to a device context. +/// @param x1 From point x-coordinate. +/// @param y1 From point y-coordinate. +/// @param x2 To point x-coordinate. +/// @param y2 To point y-coordinate. +/// +/// @returns VOID. +static VOID DrawLine(HDC hdc, LONG x1, LONG y1, LONG x2, LONG y2) +{ + MoveToEx(hdc, x1, y1, NULL); + LineTo(hdc, x2, y2); +} + +/// @brief Draw an inverted line. +/// +/// @param hdc A handle to a device context. +/// @param x1 From point x-coordinate. +/// @param y1 From point y-coordinate. +/// @param x2 To point x-coordinate. +/// @param y2 To point y-coordinate. +/// +/// @returns VOID. +static VOID InvertLine(HDC hdc, LONG x1, LONG y1, LONG x2, LONG y2) +{ + INT nOldMode = SetROP2(hdc, R2_NOT); + DrawLine(hdc, x1, y1, x2, y2); + SetROP2(hdc, nOldMode); +} + +/// @brief Draw specified borders of a rectangle. +/// +/// @param hdc A handle to a device context. +/// @param lprc The address of a RECT structure with drawing coordinates. +/// @param dwBorder specifies which borders to draw. +/// @param clr The desired line color value. +/// +/// @returns VOID. +static VOID DrawBorder(HDC hdc, LPRECT lprc, DWORD dwBorder, COLORREF clr) +{ + LOGPEN oLogPen; + + HPEN hOld; + GetObject(hOld = (HPEN)SelectObject(hdc, GetStockObject(BLACK_PEN)), sizeof(oLogPen), &oLogPen); + oLogPen.lopnColor = clr; + + SelectObject(hdc, CreatePenIndirect(&oLogPen)); + + if (dwBorder & BF_LEFT) + DrawLine(hdc, lprc->left, lprc->top, lprc->left, lprc->bottom); + if (dwBorder & BF_TOP) + DrawLine(hdc, lprc->left, lprc->top, lprc->right, lprc->top); + if (dwBorder & BF_RIGHT) + DrawLine(hdc, lprc->right, lprc->top, lprc->right, lprc->bottom); + if (dwBorder & BF_BOTTOM) + DrawLine(hdc, lprc->left, lprc->bottom, lprc->right, lprc->bottom); + + DeleteObject(SelectObject(hdc, hOld)); +} + +/// @brief Convert string value, Ex: "255 255 255" to RGB COLORREF. +/// +/// @param src The string to convert. +/// +/// @returns COLORREF The converted color. +static COLORREF GetColor(LPTSTR src) +{ + INT RVal, GVal, BVal; + RVal = GVal = BVal = 0; + if (_tcslen(src) > 0) + _stscanf(src, _T("%d,%d,%d"), &RVal, &GVal, &BVal); + + return RGB(RVal, GVal, BVal); +} + +/// @brief Handle the WM_PAINT message for most of the edit controls. +/// Obliterate the control's border to get that integrated flat look. +/// +/// @param hwnd The control's handle. +/// @param msg The window message (in this case WM_PAINT). +/// @param wParam The message WPARAM. +/// @param lParam The message LPARAM. +/// +/// @returns BOOL Always TRUE. +static BOOL Editor_OnPaint(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HDC hdc = GetWindowDC(hwnd); + RECT rect; + + // First let the system do its thing + DefProc(hwnd, msg, wParam, lParam); + + // Next obliterate the border + GetWindowRect(hwnd, &rect); + MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT) & rect.left, 2); + + rect.top += 2; + rect.left += 2; + DrawBorder(hdc, &rect, BF_RECT, GetSysColor(COLOR_WINDOW)); + + rect.top += 1; + rect.left += 1; + rect.bottom += 1; + rect.right += 1; + + DrawBorder(hdc, &rect, BF_RECT, GetSysColor(COLOR_WINDOW)); + + ReleaseDC(hwnd, hdc); + return TRUE; +} + +/// @brief Set the colors used to paint controls in WM_CTLCOLORLISTBOX handler. +/// +/// @param hdc Handle of a device context. +/// @param TxtColr Desired text color. +/// @param BkColr Desired back color. +/// +/// @returns HBRUSH A reusable brush object. +static HBRUSH SetColor(HDC hdc, COLORREF TxtColr, COLORREF BkColr) +{ + static HBRUSH ReUsableBrush; + DeleteObject(ReUsableBrush); + ReUsableBrush = CreateSolidBrush(BkColr); + SetTextColor(hdc, TxtColr); + SetBkColor(hdc, BkColr); + return ReUsableBrush; +} + +#pragma endregion Drawing + +#pragma region ToolTip + +/// @brief Create the tool tip object that will display tips for grid items. +/// +/// @param hInstance The handle of an instance. +/// @param hwndParent The handle of the tooltip's parent. +/// +/// @returns HWND A handle to a tooltip object. +static HWND CreateToolTip(HINSTANCE hInstance, HWND hwndParent) +{ + RECT rect; + TOOLINFO ti; + DWORD dwStyle, dwExStyle; + HWND hwnd; + + dwStyle = WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP; + dwExStyle = WS_EX_TOPMOST; + + hwnd = CreateWindowEx( + dwExStyle, + TOOLTIPS_CLASS, + NULL, + dwStyle, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + hwndParent, + NULL, + hInstance, + NULL); + + if (!hwnd) + return NULL; + + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + + SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0); + + GetClientRect(hwndParent, &rect); + + // Initialize members of toolinfo structure + ti.cbSize = sizeof(TOOLINFO); + ti.uFlags = TTF_SUBCLASS; + ti.hwnd = hwndParent; + ti.hinst = hInstance; + ti.uId = 0; + ti.lpszText = _T(""); + // ToolTip control will cover the entire window + ti.rect.left = rect.left; + ti.rect.top = rect.top; + ti.rect.right = rect.right; + ti.rect.bottom = rect.bottom; + SendMessage(hwnd, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); + + return hwnd; +} + +#pragma endregion ToolTip + +#pragma region Static + +/// @brief Create the static control for the property description pane. +/// +/// @param hInstance The handle of an instance. +/// @param hwndParent The handle of the parent (the Property Grid window). +/// @param id An id tag for this control +/// +/// @returns HWND A handle to a static control. +static HWND CreateStatic(HINSTANCE hInstance, HWND hwndParent, INT id) +{ + DWORD dwStyle, dwExStyle; + HWND hwnd; + + dwStyle = WS_CHILD | SS_LEFT; + + dwExStyle = WS_EX_LEFT | WS_EX_CLIENTEDGE; + + hwnd = CreateWindowEx( + dwExStyle, + WC_STATIC, + NULL, + dwStyle, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + hwndParent, + NULL, + hInstance, + NULL); + + if (!hwnd) + return NULL; + + SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0); + + return hwnd; +} + +#pragma endregion Static + +#pragma region ListBox and ListMap + +/// @brief Create the owner draw listbox for item display. +/// +/// @param hInstance The handle of an instance. +/// @param hwndParent The handle of the parent (the Property Grid window). +/// @param id An id tag for this control +/// +/// @par Comments: +/// There is some kind of bug where a listbox created as an +/// LBS_OWNERDRAWVARIABLE will scroll erratically when the mouse +/// wheel is used. +/// +/// @returns HWND A handle to the owner draw listbox control. +static HWND CreateListBox(HINSTANCE hInstance, HWND hwndParent, INT id) +{ + DWORD dwStyle, dwExStyle; + HWND hwnd; + + dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL + | LBS_NOTIFY | LBS_OWNERDRAWFIXED | LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT; + + dwExStyle = WS_EX_LEFT | WS_EX_RTLREADING | WS_EX_RIGHTSCROLLBAR | WS_EX_CLIENTEDGE; + hwnd = CreateWindowEx( + dwExStyle, + WC_LISTBOX, + NULL, + dwStyle, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + hwndParent, + (HMENU)id, + hInstance, + NULL); + + if (!hwnd) + return NULL; + + SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0); + + // Subclass listbox and save the old proc + SetProp(hwnd, WPRC, (HANDLE)GetWindowLongPtr(hwnd, GWLP_WNDPROC)); + SubclassWindow(hwnd, ListBox_Proc); + + return hwnd; +} + +/// @brief Create a minimal hidden listbox (the listmap) to store pointers +/// to all items, including those not displayed. +/// +/// @param hInstance The handle of an instance. +/// @param hwndParent The handle of the parent (the Property Grid window). +/// @param id An id tag for this control +/// +/// @returns HWND A handle to the hidden listmap control. +static HWND CreateListMap(HINSTANCE hInstance, HWND hwndParent, INT id) +{ + DWORD dwStyle, dwExStyle; + HWND hwnd; + + dwStyle = WS_CHILD | LBS_OWNERDRAWFIXED; // Want WM_DELETEITEM messages + + dwExStyle = 0; + hwnd = CreateWindowEx( + dwExStyle, + WC_LISTBOX, + NULL, + dwStyle, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + hwndParent, + (HMENU)id, + hInstance, + NULL); + + if (!hwnd) + return NULL; + + return hwnd; +} + +#pragma endregion ListBox and ListMap + +#pragma region Edit Control + +/// @brief Notify Property Grid parent of editor change. +/// +/// @param hwnd Handle of parent. +/// @param hwndCtl The hwnd of the sender. +/// @param fExitEd TRUE to exit editor following update, +/// FALSE to keep editor visible and focused. +/// +/// @returns VOID. +VOID Editor_DoUpdate(HWND hwnd, HWND hwndCtl, BOOL fExitEd) +{ + // Only update if value changed + BOOL fDirty = FALSE; + if (NULL != g_lpInst->lpCurrent) + { + INT len = GetWindowTextLength(hwndCtl) + 1; + INT len2 = _tcslen(g_lpInst->lpCurrent->lpszCurValue); + LPTSTR buf = (LPTSTR)calloc(len, sizeof(TCHAR)); + GetWindowText(hwndCtl, buf, len); + len = len2 > len? len2 : len; + fDirty = 0 != _tcsncmp(g_lpInst->lpCurrent->lpszCurValue, buf, len); + if(fDirty) + { + AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf); + } + free(buf); + } + if (fExitEd) + { + ShowWindow(hwndCtl, SW_HIDE); + SetFocus(hwnd); + } + if (fDirty) + { + Grid_NotifyParent(); + } +} + +/// @brief Handles WM_COMMAND notifications targeting the Edit control. +/// +/// @param hwnd Handle of parent. +/// @param id The id of the sender. +/// @param hwndCtl The hwnd of the sender. +/// @param codeNotify The notification code sent. +/// +/// @returns VOID. +static VOID EditOwner_OnNotify(HWND hwnd, INT id, HWND hwndCtl, UINT codeNotify) +{ + switch(id) + { + case ID_EDIT: + if(EN_KILLFOCUS == codeNotify) + { + Editor_DoUpdate(hwnd,hwndCtl, TRUE); + } + } +} + +/// @brief Window procedure for the edit control. +/// +/// @param hEdit Handle of editor. +/// @param msg Which message? +/// @param wParam Message parameter. +/// @param lParam Message parameter. +/// +/// @returns LRESULT depends on message. +static LRESULT CALLBACK Edit_Proc(HWND hEdit, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HWND hGParent = GetParent(GetParent(hEdit)); + + // Note: Instance data is attached to Edit's grandparent + Control_GetInstanceData(hGParent, &g_lpInst); + + if (WM_DESTROY == msg) // Unsubclass the Edit Control + { + SetWindowLongPtr(hEdit, GWLP_WNDPROC, (DWORD_PTR)GetProp(hEdit, WPRC)); + RemoveProp(hEdit, WPRC); + return 0; + } + else if (WM_KILLFOCUS == msg) + { + Editor_OnKillFocus(hEdit, (HWND)wParam); + } + else if (WM_PAINT == msg) // Obliterate border + { + return Editor_OnPaint(hEdit, msg, wParam, lParam); + } + else if (WM_MOUSEWHEEL == msg) + { + FORWARD_WM_CHAR(hEdit, VK_RETURN, 0, SNDMSG); + } + else if (WM_CHAR == msg && VK_RETURN == wParam) + { + SetFocusToParent(); // trigger EN_KILLFOCUS notification + ShowWindow(g_lpInst->hwndCtl1, SW_HIDE); + return TRUE; // handle Enter (NO BELL) + } + else if (WM_KEYDOWN == msg) + { + switch (wParam) + { + case VK_TAB: + SetFocusToParent(); + return TRUE; + case VK_ESCAPE: + ShowWindow(hEdit, SW_HIDE); + SetFocus(g_lpInst->hwndListBox); + return FALSE; + } + } + return DefProc(hEdit, msg, wParam, lParam); +} + +/// @brief Create an Edit control to edit PIT_EDIT fields. +/// +/// @param hInstance The handle of an instance. +/// @param hwndParent The handle of the parent (the visible listbox). +/// @param id An id tag for this control. +/// +/// @returns HWND A handle to the edit control. +static HWND CreateEdit(HINSTANCE hInstance, HWND hwndParent, INT id) +{ + DWORD dwStyle, dwExStyle; + HWND hwnd; + + dwStyle = WS_CHILD | ES_LEFT | ES_AUTOHSCROLL | ES_MULTILINE | ES_WANTRETURN; + + dwExStyle = WS_EX_LEFT | WS_EX_CLIENTEDGE; + + hwnd = CreateWindowEx( + dwExStyle, + WC_EDIT, + NULL, + dwStyle, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + hwndParent, + (HMENU)id, + hInstance, + NULL); + + if (!hwnd) + return NULL; + + //Disable visual styles for this control. + SetWindowTheme(hwnd, L" ", L" "); + + SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0L); + + // Subclass Editor and save the OldProc + SetProp(hwnd, WPRC, (HANDLE)GetWindowLongPtr(hwnd, GWLP_WNDPROC)); + SubclassWindow(hwnd, Edit_Proc); + + return hwnd; +} + +#pragma endregion Edit Control + +#pragma region IP Control + +/// @brief Handle IPEdit IPN_FIELDCHANGED notification. +/// +/// @param hwnd Handle of parent. +/// @param hwndCtl The hwnd of the sender. +/// +/// @returns VOID. +static VOID IpEdit_OnFieldChanged(HWND hwnd, HWND hwndCtl) +{ + // Only update if value changed + BOOL fDirty = FALSE; + if (NULL != g_lpInst->lpCurrent) + { + DWORD ip; + TCHAR buf[MAX_PATH]; + if (4 == SNDMSG(hwndCtl, IPM_GETADDRESS, 0, (LPARAM) & ip)) + { + _stprintf(buf, MAX_PATH, + _T("%d.%d.%d.%d"), + FIRST_IPADDRESS(ip), + SECOND_IPADDRESS(ip), + THIRD_IPADDRESS(ip), + FOURTH_IPADDRESS(ip)); + fDirty = 0 != _tcsncmp(g_lpInst->lpCurrent->lpszCurValue, buf, NELEMS(buf)); + if(fDirty) + { + AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf); + } + } + } + Grid_NotifyParent(); +} + +/// @brief Window procedure for the ipedit control. +/// +/// @param hwnd Handle of the ipedit or a child edit control. +/// @param msg Which message? +/// @param wParam Message parameter. +/// @param lParam Message parameter. +/// +/// @par Comments +/// The ipedit control wraps four edit control children. +/// Each child needs to be subclassed in order to handle +/// keyboard events. Each edit control posts some notification +/// to the parent ipedit control and I use this behavior to capture +/// and subclass these children. These children are subclassed to +/// this very procedure and so 'hwnd' could be parent ipedit or one +/// of the child Edits. Care then, must be taken to differentiate between +/// child and parent. I do this by getting the class name and restricting +/// the handling of certain messages to one or the other control type. +/// +/// @returns LRESULT depends on message. +static LRESULT CALLBACK IpEdit_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static TCHAR buf[MAX_PATH]; + HWND hGParent = GetParent(GetParent(hwnd)); + + // Note: Instance data is attached to ipedit's grandparent + // or the edit field's greatgrandparent + GetClassName(hwnd, buf, NELEMS(buf)); + BOOL fEdit = (0 == _tcsicmp(buf, WC_EDIT)); + + if (fEdit) + Control_GetInstanceData(GetParent(hGParent), &g_lpInst); + else + Control_GetInstanceData(hGParent, &g_lpInst); + + HWND hIpEdit = fEdit ? GetParent(hwnd) : hwnd; + + if (WM_DESTROY == msg) //Unsubclass the ipedit or child edit control + { + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (DWORD_PTR)GetProp(hwnd, WPRC)); + RemoveProp(hwnd, WPRC); + return 0; + } + else if (WM_KILLFOCUS == msg) + { + if (fEdit) + { + // Determine if hwndNewfocus is a child of the IpEdit + if (hIpEdit != GetParent((HWND)wParam)) //No, result of mouse click + { + //IpEdit_OnFieldChanged(GetParent(hIpEdit), hIpEdit); + Editor_OnKillFocus(hIpEdit, (HWND)wParam); + } + } + } + else if (WM_CHAR == msg && VK_RETURN == wParam) + { + ShowWindow(hIpEdit, SW_HIDE); + SetFocus(g_lpInst->hwndListBox); + return TRUE; + } + if (fEdit) //Handle keyboard events in the child edit controls + { + if (WM_GETDLGCODE == msg) + { + return DLGC_WANTALLKEYS; + } + else if (WM_MOUSEWHEEL == msg) + { + FORWARD_WM_CHAR(hwnd, VK_RETURN, 0, SNDMSG); + } + else if (WM_CHAR == msg && VK_TAB == wParam) + { + HWND hNext; + if (GetKeyState(VK_SHIFT) & 0x8000) //Shift tab + { + hNext = GetWindow(hwnd, GW_HWNDNEXT); + if (NULL == hNext) + { + //Trigger WM_NOTIFY + FORWARD_WM_CHAR(hIpEdit, VK_RETURN, 0, SNDMSG); + return TRUE; + } + } + else + { + hNext = GetWindow(hwnd, GW_HWNDPREV); + if (NULL == hNext) //Focus to grid parent + { + //Trigger WM_NOTIFY + FORWARD_WM_CHAR(hIpEdit, VK_RETURN, 0, SNDMSG); + Editor_OnKillFocus(hIpEdit, NULL); + SetFocusToParent(); + return TRUE; + } + } + Edit_SetSel(hNext, 0, -1); + SetFocus(hNext); + return TRUE; + } + else if (WM_CHAR == msg && VK_ESCAPE == wParam) + { + ShowWindow(hIpEdit, SW_HIDE); + SetFocus(g_lpInst->hwndListBox); + return TRUE; + } + else if (WM_PASTE == msg) + return TRUE; //Do not allow paste + } + else //Handle messages (events) in the parent ipedit control + { + if (WM_PAINT == msg) // Obliterate border + { + return Editor_OnPaint(hwnd, msg, wParam, lParam); + } + else if (WM_COMMAND == msg) + { + // Each of the control's edit fields posts notifications on showing, + // the first time they do so we'll grab and subclass them. + HWND hwndCtl = GET_WM_COMMAND_HWND(wParam, lParam); + { + WNDPROC lpfn = (WNDPROC)GetProp(hwndCtl, WPRC); + if (NULL == lpfn) + { + //Subclass child and save the OldProc + SetProp(hwndCtl, WPRC, (HANDLE)GetWindowLongPtr(hwndCtl, GWLP_WNDPROC)); + SubclassWindow(hwndCtl, IpEdit_Proc); + } + } + } + } + return DefProc(hwnd, msg, wParam, lParam); +} + +/// @brief Create an ipedit control to edit PIT_IP fields. +/// +/// @param hInstance The handle of an instance. +/// @param hwndParent The handle of the parent (the visible listbox). +/// @param id An id tag for this control. +/// @param lprc A pointer to RECT struct that contains initial size data. +/// +/// @par Comments +/// It is not possible to create this control with CW_USEDEFAULT for width +/// and height. The control cannot be properly resized once created and +/// so it is necessary to specify width and height at the time of creation. +/// +/// @returns HWND A handle to the ipedit control. +static HWND CreateIpEdit(HINSTANCE hInstance, HWND hwndParent, INT id, LPRECT lprc) +{ + INITCOMMONCONTROLSEX cc; + cc.dwSize = sizeof(INITCOMMONCONTROLSEX); + cc.dwICC = ICC_INTERNET_CLASSES; + if (!InitCommonControlsEx(&cc)) + return NULL; + + DWORD dwStyle, dwExStyle; + HWND hwnd; + + dwStyle = WS_CHILD; + + dwExStyle = WS_EX_LEFT; + + hwnd = CreateWindowEx( + dwExStyle, + WC_IPADDRESS, + NULL, + dwStyle, + CW_USEDEFAULT, // x position can be changed after creation + CW_USEDEFAULT, // y position can be changed after creation + lprc->right - lprc->left, // width can only be set here + lprc->bottom - lprc->top, // height can only be set here + hwndParent, + (HMENU)id, + hInstance, + NULL); + + if (!hwnd) + return NULL; + + SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0L); + + SetProp(hwnd, WPRC, (HANDLE)GetWindowLongPtr(hwnd, GWLP_WNDPROC)); + SubclassWindow(hwnd, IpEdit_Proc); + + return hwnd; +} + +#pragma endregion IP Control + +#pragma region choose font + +/// @brief Create a string from a property grid font dialog item. +/// +/// @param lpLogFontItem The address of a PROPGRIDFONTITEM struct. +/// @param lpszFont The string representation of PROPGRIDFONTITEM elements. +/// +/// @returns VOID. +static VOID LogFontItem_FromString(LPPROPGRIDFONTITEM lpLogFontItem, LPTSTR lpszFont) +{ + LPLOGFONT lpLf = (LPLOGFONT)lpLogFontItem; + _stscanf(lpszFont, + _T( "Height: %d " + "Width: %d " + "Escapement: %d " + "Orientation: %d " + "Weight: %d " + "Italic: %hhd " + "Underline: %hhd " + "StrikeOut: %hhd " + "CharSet: %hhd " + "OutPrecision: %hhd " + "ClipPrecision: %hhd " + "Quality: %hhd " + "PitchAndFamily: %hhd " + "FaceName: %32")_T(SCN_SPEC)_T("[^\r\n] " + "Color: %d"), + &lpLf->lfHeight, + &lpLf->lfWidth, + &lpLf->lfEscapement, + &lpLf->lfOrientation, + &lpLf->lfWeight, + &lpLf->lfItalic, + &lpLf->lfUnderline, + &lpLf->lfStrikeOut, + &lpLf->lfCharSet, + &lpLf->lfOutPrecision, + &lpLf->lfClipPrecision, + &lpLf->lfQuality, + &lpLf->lfPitchAndFamily, + (LPTSTR) & lpLf->lfFaceName, + &lpLogFontItem->crFont); +} + +/// @brief Create a string from a property grid font dialog item. +/// +/// @param lpLogFontItem A pointer to a PROPGRIDFONTITEM. +/// +/// @returns LPTSTR The string representation of PROPGRIDFONTITEM elements. +static LPTSTR LogFontItem_ToString(LPPROPGRIDFONTITEM lpLogFontItem) +{ + static TCHAR buf[MAX_PATH]; + _tmemset(buf, (TCHAR)0, MAX_PATH); + + LPLOGFONT lpLf = (LPLOGFONT)lpLogFontItem; + _stprintf(buf, MAX_PATH, + _T( "Height: %d\r\n" + "Width: %d\r\n" + "Escapement: %d\r\n" + "Orientation: %d\r\n" + "Weight: %d\r\n" + "Italic: %d\r\n" + "Underline: %d\r\n" + "StrikeOut: %d\r\n" + "CharSet: %d\r\n" + "OutPrecision: %d\r\n" + "ClipPrecision: %d\r\n" + "Quality: %d\r\n" + "PitchAndFamily: %d\r\n" + "FaceName: %")_T(STR_SPEC)_T("\r\n" + "Color: %d"), + lpLf->lfHeight, + lpLf->lfWidth, + lpLf->lfEscapement, + lpLf->lfOrientation, + lpLf->lfWeight, + lpLf->lfItalic, + lpLf->lfUnderline, + lpLf->lfStrikeOut, + lpLf->lfCharSet, + lpLf->lfOutPrecision, + lpLf->lfClipPrecision, + lpLf->lfQuality, + lpLf->lfPitchAndFamily, + lpLf->lfFaceName, + lpLogFontItem->crFont); + return buf; +} + +#pragma endregion choose font + +#pragma region browse folder + +/// @brief Create a string from a property grid file dialog item. +/// +/// @param lpPgFdItem The address of a PROPGRIDFDITEM struct. +/// @param lpszFdItem The string representation of PROPGRIDFDITEM elements. +/// +/// @returns VOID. +static VOID FileDialogItem_FromString(LPPROPGRIDFDITEM lpPgFdItem, LPTSTR lpszFdItem) +{ + static TCHAR PgFdItem[4][MAX_PATH]; + memset(PgFdItem, (TCHAR)0, sizeof(PgFdItem)); + + _stscanf(lpszFdItem, + _T( "Title: %256")_T(SCN_SPEC)_T("[^\r\n] " + "Path: %256")_T(SCN_SPEC)_T("[^\r\n] " + "Filter: %256")_T(SCN_SPEC)_T("[^\r\n] " + "Default Extension: %3")_T(STR_SPEC), + (LPTSTR) &PgFdItem[0], + (LPTSTR) &PgFdItem[1], + (LPTSTR) &PgFdItem[2], + (LPTSTR) &PgFdItem[3]); + + for (int i = 0; i < NELEMS(PgFdItem); i++) + { + if (0 == _tcscmp(_T("?"), PgFdItem[i])) + { + //Convert back to empty string + PgFdItem[i][0] = (TCHAR)0; + } + else if (2 == i) + { + //Convert back to double null-terminated string + for (LPTSTR ptr = PgFdItem[2]; *ptr; ptr++) + if (_T('\t') == *ptr) + *ptr = _T('\0'); + } + } + lpPgFdItem->lpszDlgTitle = PgFdItem[0]; + lpPgFdItem->lpszFilePath = PgFdItem[1]; + lpPgFdItem->lpszFilter = PgFdItem[2]; + lpPgFdItem->lpszDefExt = PgFdItem[3]; +} + +/// @brief Create a string from a property grid file dialog item. +/// +/// @param lpPgFdItem A pointer to a PROPGRIDFDITEM. +/// +/// @returns LPTSTR The string representation of PROPGRIDFDITEM elements. +static LPTSTR FileDialogItem_ToString(LPPROPGRIDFDITEM lpPgFdItem) +{ + static TCHAR szBuf[3 *MAX_PATH]; + _tmemset(szBuf, (TCHAR)0, NELEMS(szBuf)); + + TCHAR filter[MAX_PATH] = { 0 }; + + //Copy filter string replacing \0 with \t" + INT iLen = 0; + for (LPTSTR psz = (LPTSTR)lpPgFdItem->lpszFilter, + ps = filter, pe = filter + NELEMS(filter) - 1; + *psz && ps < pe; psz += iLen + 1) + { + _tmemmove(ps, psz, (iLen = _tcslen(psz))); + ps += iLen; + *ps++ = _T('\t'); + } + + _stprintf(szBuf, NELEMS(szBuf), + _T( "Title: %")_T(STR_SPEC)_T("\r\n" + "Path: %")_T(STR_SPEC)_T("\r\n" + "Filter: %")_T(STR_SPEC)_T("\r\n" + "Default Extension: %3")_T(STR_SPEC), + 0 < _tcslen(lpPgFdItem->lpszDlgTitle) ? lpPgFdItem->lpszDlgTitle : _T("?"), + 0 < _tcslen(lpPgFdItem->lpszFilePath) ? lpPgFdItem->lpszFilePath : _T("?"), + 0 < _tcslen(filter) ? filter : _T("?"), + 0 < _tcslen(lpPgFdItem->lpszDefExt) ? lpPgFdItem->lpszDefExt : _T("?")); + return szBuf; +} + +#if !defined(UNICODE) || !defined(_UNICODE) + +/// @brief Convert a CHAR string to a WCHAR string. +/// +/// @param dest Pointer to a pszBuffer that will contain the converted string. +/// @param ccDest destination pszBuffer size (number of characters) +/// @param source Constant pointer to a source string to be converted to WCHAR. +/// +/// @returns VOID. +static VOID CharToWide(LPWSTR dest, INT ccDest, const LPSTR source) +{ + int i = 0; + + while (source[i] != '\0' && i <= ccDest) + { + dest[i] = (WCHAR)source[i]; + ++i; + } +} + +#endif + +/// @brief Convert a path to an item id list object. +/// +/// @param pszPath The file path string. +/// +/// @returns LPITEMIDLIST A pointer to an item id list object. +static LPITEMIDLIST ConvertPathToLpItemIdList(LPTSTR pszPath) +{ + LPITEMIDLIST pidl = NULL; + LPSHELLFOLDER pDesktopFolder; + ULONG chEaten; + ULONG dwAttributes; + +#ifdef _UNICODE + LPWSTR pwzPath = pszPath; +#else + int iLen = strlen(pszPath); + WCHAR pwzPath[iLen + 1]; + wmemset(pwzPath, (WCHAR)0, NELEMS(pwzPath)); + CharToWide(pwzPath, NELEMS(pwzPath), pszPath); +#endif + + if (SUCCEEDED(SHGetDesktopFolder(&pDesktopFolder))) + { + pDesktopFolder->lpVtbl->ParseDisplayName(pDesktopFolder, + NULL, NULL, pwzPath, &chEaten, &pidl, &dwAttributes); + pDesktopFolder->lpVtbl->Release(pDesktopFolder); + } + return pidl; +} + +/// @brief An application-defined callback function used with the +/// SHBrowseForFolder function. +/// +/// @param hwnd Handle of the dialog. +/// @param uMsg Which message? +/// @param lParam Message parameter. +/// @param lpData Pointer to message data. +/// +/// @returns BOOL FALSE. +static BOOL CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) +{ + //Buffer for the folder dialog + static TCHAR SelectedDir[MAX_PATH]; + + switch (uMsg) + { + case BFFM_INITIALIZED: + { + // change the selected folder. + SNDMSG(hwnd, BFFM_SETSELECTION, TRUE, lpData); + break; + } + case BFFM_SELCHANGED: + { + // Set the status window to the currently selected path. + if (SHGetPathFromIDList((LPITEMIDLIST)lParam, SelectedDir)) + SNDMSG(hwnd, BFFM_SETSTATUSTEXT, 0, (LPARAM)SelectedDir); + break; + } + default: + break; + } + return FALSE; +} + +/// @brief Show the folder browser dialog to get a directory pathname. +/// +/// @param hwnd Handle of the dialog's owner. +/// @param curPath The path of the currently selected folder. +/// @param title Browser dialog title. +/// @param rootPath Path location to begin browsing. +/// +/// @returns BOOL Depends on message. +static LPTSTR BrowseFolder(HWND hwnd, LPTSTR curPath, LPTSTR title, LPTSTR rootPath) +{ + BROWSEINFO bi; + static TCHAR szDir[MAX_PATH]; + LPITEMIDLIST pidl; + LPMALLOC pMalloc; + + if (SUCCEEDED(SHGetMalloc(&pMalloc))) + { + memset(&bi, 0, sizeof(bi)); + bi.hwndOwner = hwnd; + bi.pszDisplayName = 0; + bi.lpszTitle = title; + bi.pidlRoot = ConvertPathToLpItemIdList(rootPath); + bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT; + bi.lpfn = BrowseCallbackProc; + bi.lParam = (long)curPath; + + pidl = SHBrowseForFolder(&bi); + if (pidl) + SHGetPathFromIDList(pidl, szDir); + else + _tmemset(szDir, (TCHAR)0, MAX_PATH); + + pMalloc->lpVtbl->Free(pMalloc, pidl); + pMalloc->lpVtbl->Release(pMalloc); + } + return szDir; +} + +#pragma endregion browse folder + +#pragma region Button Control + +/// @brief Handles WM_COMMAND notifications targeting the Button control. +/// +/// @param hwnd Handle of parent. +/// @param id The id of the sender. +/// @param hwndCtl The hwnd of the sender. +/// @param codeNotify The notification code sent. +/// +/// @returns VOID. +static VOID ButtonOwner_OnNotify(HWND hwnd, INT id, HWND hwndCtl, UINT codeNotify) +{ + // Note: this procedure updates all Properties that employ the launch of + // Dialogs + if (NULL == g_lpInst->lpCurrent) + return; + + if(BN_CLICKED == codeNotify) + { + //Display the appropriate common dialog depending on what type + // of chooser is associated with the property + switch (g_lpInst->lpCurrent->iItemType) + { + case PIT_COLOR: + { + CHOOSECOLOR cc; + memset(&cc, 0, sizeof(cc)); + cc.lStructSize = sizeof(cc); + cc.hwndOwner = hwnd; + cc.hInstance = (HWND)g_lpInst->hInstance; + cc.rgbResult = GetColor(g_lpInst->lpCurrent->lpszCurValue); + cc.lpCustColors = (LPDWORD)g_CustomColors; + cc.Flags = CC_FULLOPEN | CC_RGBINIT; + if (ChooseColor(&cc)) + { + TCHAR buf[MAX_PATH]; + _stprintf(buf, MAX_PATH, + _T("%d,%d,%d"), + GetRValue(cc.rgbResult), + GetGValue(cc.rgbResult), + GetBValue(cc.rgbResult)); + AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf); + } + } + break; + + case PIT_FILE: + { + TCHAR title[MAX_PATH] = { 0 }; + TCHAR filename[MAX_PATH] = { 0 }; + TCHAR path[MAX_PATH] = { 0 }; + TCHAR filter[MAX_PATH] = { 0 }; + TCHAR ext[4] = { 0 }; + + OPENFILENAME ofn; + memset(&ofn, 0, sizeof(OPENFILENAME)); + + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = hwnd; + ofn.hInstance = g_lpInst->hInstance; + + _stscanf(g_lpInst->lpCurrent->lpszMisc, + _T( "Title: %32")_T(SCN_SPEC)_T("[^\r\n] " + "Path: %256")_T(SCN_SPEC)_T("[^\r\n] " + "Filter: %256")_T(SCN_SPEC)_T("[^\r\n] " + "Default Extension: %3")_T(SCN_SPEC)_T("[^\r\n] "), + (LPTSTR) &title, + (LPTSTR) &path, + (LPTSTR) &filter, + (LPTSTR) &ext); + + if (0 != _tcscmp(_T("?"), title)) + ofn.lpstrTitle = title; + else + ofn.lpstrTitle = _T("Select file"); + + if (0 != _tcscmp(_T("?"), path)) + { + //Exclude the filename + for (LPTSTR ptr = path + _tcslen(path) - 1; *ptr; ptr--) + if (*ptr == _T('\\')) + { + *ptr = _T('\0'); + break; + } + ofn.lpstrInitialDir = path; + } + if (0 != _tcscmp(_T("?"), filter)) + { + //Convert back to double null-terminated string + for (LPTSTR ptr = filter; *ptr; ptr++) + if (_T('\t') == *ptr) + *ptr = _T('\0'); + + ofn.lpstrFilter = filter; + } + else + ofn.lpstrFilter = _T("All Files (*.*)\0*.*\0"); + + if (0 != _tcscmp(_T("?"), ext)) + { + ofn.lpstrDefExt = ext; + } + else + { + ofn.lpstrDefExt = _T("txt"); + } + ofn.lpstrFile = filename; + ofn.nMaxFile = MAX_PATH; + ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | + OFN_PATHMUSTEXIST | OFN_HIDEREADONLY; + + if (GetOpenFileName(&ofn)) + { + PROPGRIDFDITEM pgi; + pgi.lpszDlgTitle = (LPTSTR)ofn.lpstrTitle; + pgi.lpszFilePath = (LPTSTR)ofn.lpstrFile; + pgi.lpszFilter = (LPTSTR)ofn.lpstrFilter; + pgi.lpszDefExt = (LPTSTR)ofn.lpstrDefExt; + AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, pgi.lpszFilePath); + AllocatedString_Replace(g_lpInst->lpCurrent->lpszMisc, + FileDialogItem_ToString(&pgi)); + } + else //Reset to unselected file + { + PROPGRIDFDITEM pgi; + pgi.lpszDlgTitle = (LPTSTR)ofn.lpstrTitle; + pgi.lpszFilePath = _T(""); + pgi.lpszFilter = (LPTSTR)ofn.lpstrFilter; + pgi.lpszDefExt = (LPTSTR)ofn.lpstrDefExt; + AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, pgi.lpszFilePath); + AllocatedString_Replace(g_lpInst->lpCurrent->lpszMisc, + FileDialogItem_ToString(&pgi)); + } + + } + break; + + case PIT_FONT: + { + CHOOSEFONT ocf; + memset(&ocf, 0, sizeof(ocf)); + + PROPGRIDFONTITEM pgfi; + LogFontItem_FromString(&pgfi, g_lpInst->lpCurrent->lpszCurValue); + + ocf.lStructSize = sizeof(ocf); + ocf.hwndOwner = hwnd; + ocf.hInstance = g_lpInst->hInstance; + ocf.Flags = CF_INITTOLOGFONTSTRUCT | CF_EFFECTS | CF_SCREENFONTS; + ocf.lpLogFont = &pgfi.logFont; + ocf.rgbColors = pgfi.crFont; + + if (ChooseFont(&ocf)) + { + pgfi.crFont = ocf.rgbColors; + AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, + LogFontItem_ToString(&pgfi)); + } + } + break; + + case PIT_FOLDER: + { + LPTSTR temp = BrowseFolder(hwnd, g_lpInst->lpCurrent->lpszCurValue, _T(""), _T("")); + //Reset to unselected folder + AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, temp); + } + break; + } + ShowWindow(hwndCtl, SW_HIDE); + SetFocus(hwnd); + Refresh(hwnd); //Trigger WM_DRAWITEM + Grid_NotifyParent(); + } +} + +/// @brief Window procedure for the button control. +/// +/// @param hButton Handle of button control. +/// @param msg Which message? +/// @param wParam Message parameter. +/// @param lParam Message parameter. +/// +/// @returns LRESULT depends on message. +static LRESULT CALLBACK Button_Proc(HWND hButton, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HWND hGParent = GetParent(GetParent(hButton)); + + // Note: Instance data is attached to buttons's grandparent + Control_GetInstanceData(hGParent, &g_lpInst); + + if (WM_DESTROY == msg) // Unsubclass the button control + { + SetWindowLongPtr(hButton, GWLP_WNDPROC, (DWORD_PTR)GetProp(hButton, WPRC)); + RemoveProp(hButton, WPRC); + return 0; + } + else if (WM_KILLFOCUS == msg) + { + ShowWindow(hButton, SW_HIDE); + Editor_OnKillFocus(hButton, (HWND)wParam); + } + else if (WM_MOUSEWHEEL == msg) + { + FORWARD_WM_KEYDOWN(hButton, VK_ESCAPE, 0, 0, SNDMSG); + } + else if (WM_GETDLGCODE == msg) + { + return DLGC_WANTALLKEYS; + } + else if (WM_KEYUP == msg && VK_RETURN == wParam) + { + FORWARD_WM_KEYUP(hButton, VK_SPACE, 0, 0, SNDMSG); + return TRUE; + } + else if (WM_KEYDOWN == msg) + { + switch (wParam) + { + case VK_RETURN: + FORWARD_WM_KEYDOWN(hButton, VK_SPACE, 0, 0, SNDMSG); + return TRUE; + case VK_TAB: + if (GetKeyState(VK_SHIFT) & 0x8000) + { + FORWARD_WM_KEYDOWN(hButton, VK_ESCAPE, 0, 0, SNDMSG); + } + else //Set focus to grid parent + { + ShowWindow(hButton, SW_HIDE); + SetFocusToParent(); + } + return FALSE; + case VK_ESCAPE: + { + ShowWindow(hButton, SW_HIDE); + SetFocus(g_lpInst->hwndListBox); + return FALSE; + } + } + } + return DefProc(hButton, msg, wParam, lParam); +} + +/// @brief Create button control to launch dialogs. +/// +/// @param hInstance The handle of an instance. +/// @param hwndParent The handle of the parent (the visible listbox). +/// @param id An id tag for this control. +/// +/// @returns HWND A handle to the button control. +static HWND CreateButton(HINSTANCE hInstance, HWND hwndParent, INT id) +{ + DWORD dwStyle, dwExStyle; + HWND hwnd; + + dwStyle = WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON; + dwExStyle = WS_EX_LEFT; + + hwnd = CreateWindowEx( + dwExStyle, + WC_BUTTON, + TEXT("..."), + dwStyle, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + hwndParent, + (HMENU)id, + hInstance, + NULL); + + if (!hwnd) + return NULL; + + //Disable visual styles for this control. + SetWindowTheme(hwnd, L" ", L" "); + + SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0L); + + SetProp(hwnd, WPRC, (HANDLE)GetWindowLongPtr(hwnd, GWLP_WNDPROC)); + SubclassWindow(hwnd, Button_Proc); + + return hwnd; +} + +#pragma endregion Button Control + +#pragma region Date Picker + +/// @brief Notify Property Grid parent of DatePicker change. +/// +/// @param iItemType The item type id of the control +/// +/// @returns VOID. +static VOID DatePicker_DoUpdate(INT iItemType) +{ + switch (iItemType) + { + case PIT_DATE: + { + SYSTEMTIME st = { 0 }; + TCHAR buf[MAX_PATH] = { 0 }; + DateTime_GetSystemtime(g_lpInst->hwndCtl1, &st); + GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, buf, MAX_PATH); + AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf); + ShowWindow(g_lpInst->hwndCtl1, SW_HIDE); + } + break; + case PIT_TIME: + { + SYSTEMTIME st = { 0 }; + TCHAR buf[MAX_PATH] = { 0 }; + DateTime_GetSystemtime(g_lpInst->hwndCtl1, &st); + GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, _T("hh':'mm':'ss tt"), buf, MAX_PATH); + AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf); + ShowWindow(g_lpInst->hwndCtl1, SW_HIDE); + } + break; + case PIT_DATETIME: + { + SYSTEMTIME st = { 0 }; + TCHAR buf[MAX_PATH] = { 0 }; + DateTime_GetSystemtime(g_lpInst->hwndCtl1, &st); + GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, buf, MAX_PATH); + _tcscat(buf, _T(" ")); + DateTime_GetSystemtime(g_lpInst->hwndCtl2, &st); + GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, _T("hh':'mm':'ss tt"), + (LPTSTR) (buf + _tcslen(buf)), MAX_PATH - _tcslen(buf)); + AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf); + ShowWindow(g_lpInst->hwndCtl1, SW_HIDE); + ShowWindow(g_lpInst->hwndCtl2, SW_HIDE); + } + break; + } + SetFocus(g_lpInst->hwndListBox); + Grid_NotifyParent(); +} + +/// @brief Window Procedure for the datepicker control. +/// +/// @param hDate Handle of the datepicker. +/// @param msg Which message? +/// @param wParam Message parameter. +/// @param lParam Message parameter. +/// +/// @par Comments +/// This procedure handles datepicker controls configured either as +/// date pickers or time pickers for three grid field types PIT_DATE, +/// PIT_TIME, and PIT_DATETIME. +/// +/// @returns LRESULT depends on message. +static LRESULT CALLBACK DatePicker_Proc(HWND hDate, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HWND hGParent = GetParent(GetParent(hDate)); + + // Note: Instance data is attached to datepicker's grandparent + Control_GetInstanceData(hGParent, &g_lpInst); + + if (WM_DESTROY == msg) // Unsubclass the control + { + SetWindowLongPtr(hDate, GWLP_WNDPROC, (DWORD_PTR)GetProp(hDate, WPRC)); + RemoveProp(hDate, WPRC); + return 0; + } + else if (WM_KILLFOCUS == msg) + { + if (NULL != g_lpInst->lpCurrent) + { + if (PIT_DATETIME == g_lpInst->lpCurrent->iItemType) + { + HWND hFocus = (HWND)wParam; + if (g_lpInst->hwndCtl1 == hFocus || g_lpInst->hwndCtl2 == hFocus) + return 0; // ignore these two windows + + ShowWindow(g_lpInst->hwndCtl2, SW_HIDE); + } + } + //force update of grid data + Editor_OnKillFocus(hDate, (HWND)wParam); + } + else if (WM_PAINT == msg) + { + if (g_lpInst->fXpOrLower) + { + return Editor_OnPaint(hDate, msg, wParam, lParam); + } + else + { + // First let the system do its thing + DefProc(hDate, msg, wParam, lParam); + + // Next obliterate the border + HDC hdc = GetWindowDC(hDate); + RECT rect; + + GetClientRect(hDate, &rect); + + DrawBorder(hdc, &rect, BF_TOPLEFT, GetSysColor(COLOR_WINDOW)); + rect.top += 1; + rect.left += 1; + +#ifdef __POCC__ + rect.bottom -= 2; +#else + rect.bottom += 2; +#endif + DrawBorder(hdc, &rect, BF_RECT, GetSysColor(COLOR_WINDOW)); + + ReleaseDC(hDate, hdc); + + return TRUE; + } + } + else if (WM_MOUSEWHEEL == msg) + { + FORWARD_WM_CHAR(hDate, VK_RETURN, 0, SNDMSG); + } + else if (WM_GETDLGCODE == msg) + { + return DLGC_WANTALLKEYS; + } + else if (WM_CHAR == msg && VK_RETURN == wParam) + { + if (NULL != g_lpInst->lpCurrent) + { + DatePicker_DoUpdate(g_lpInst->lpCurrent->iItemType); + } + return TRUE; // handle Enter (NO BELL) + } + else if (WM_CHAR == msg && VK_TAB == wParam) + { + if (NULL != g_lpInst->lpCurrent) + { + if (PIT_DATETIME == g_lpInst->lpCurrent->iItemType) + { + HWND hFocus = GetFocus(); + if (g_lpInst->hwndCtl1 == hFocus) + { + if (GetKeyState(VK_SHIFT) & 0x8000) + { + FORWARD_WM_CHAR(hDate, VK_RETURN, 0, SNDMSG); + } + else + { + SetFocus(g_lpInst->hwndCtl2); + } + } + else if (g_lpInst->hwndCtl2 == hFocus) + { + if (GetKeyState(VK_SHIFT) & 0x8000) + { + SetFocus(g_lpInst->hwndCtl1); + } + else + { + FORWARD_WM_CHAR(hDate, VK_RETURN, 0, SNDMSG); + SetFocusToParent(); + } + } + } + else + { + if (GetKeyState(VK_SHIFT) & 0x8000) + { + FORWARD_WM_CHAR(hDate, VK_RETURN, 0, SNDMSG); + } + else + { + FORWARD_WM_CHAR(hDate, VK_RETURN, 0, SNDMSG); + SetFocusToParent(); + } + } + return TRUE; + } + } + else if (WM_KEYDOWN == msg && VK_ESCAPE == wParam) + { + if (NULL != g_lpInst->lpCurrent) + { + switch (g_lpInst->lpCurrent->iItemType) + { + case PIT_DATE: + case PIT_TIME: + ShowWindow(g_lpInst->hwndCtl1, SW_HIDE); + break; + case PIT_DATETIME: + { + ShowWindow(g_lpInst->hwndCtl1, SW_HIDE); + ShowWindow(g_lpInst->hwndCtl2, SW_HIDE); + } + break; + } + } + } + return DefProc(hDate, msg, wParam, lParam); +} + +/// @brief Create datepicker control configured either as a date or time picker. +/// +/// @param hInstance The handle of an instance. +/// @param hwndParent The handle of the parent (the visible listbox). +/// @param id An id tag for this control. +/// @param fDate TRUE to create a date picker, FALSE for time picker. +/// +/// @returns HWND A handle to the control. +static HWND CreateDatePicker(HINSTANCE hInstance, HWND hwndParent, INT id, BOOL fDate) +{ + DWORD dwStyle, dwExStyle; + HWND hwnd; + + dwStyle = WS_CHILD | (fDate ? DTS_SHORTDATEFORMAT : DTS_TIMEFORMAT); + dwExStyle = WS_EX_LEFT; + hwnd = CreateWindowEx( + dwExStyle, + DATETIMEPICK_CLASS, + NULL, + dwStyle, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + hwndParent, + (HMENU)id, + hInstance, + NULL); + + if (!hwnd) + return NULL; + + SetWindowTheme(hwnd, L" ", L" "); + SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0L); + + // Subclass date or timepicker and save the old proc + SetProp(hwnd, WPRC, (HANDLE)GetWindowLongPtr(hwnd, GWLP_WNDPROC)); + SubclassWindow(hwnd, DatePicker_Proc); + + return hwnd; +} + +#pragma endregion Date Picker + +#pragma region Combo Box + +/// @brief Return a default seperator token based on the OS Locale settings. +/// +/// @returns LPTSTR - A defualt seperator token string. +LPTSTR DefaultSeperator(VOID) +{ + // Get the list separator + static TCHAR strSeparator[10] = { 0 }; + GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SLIST, strSeparator, sizeof(strSeparator)); + + // If none found, then the ';' + if (0 == _tcslen(strSeparator)) + strSeparator[0] = _T(';'); + + // And one... + strSeparator[1] = _T(' '); + + // Trim extra spaces + strSeparator[2] = (TCHAR)0; + + return strSeparator; +} + +/// @brief Concatenates two sub strings into a new string. +/// +/// @param hwnd The handle to this control (where circular pszBuffer stored) +/// @param str1 A string +/// @param str2 Another string. +/// +/// @returns A pointer to a temporarily allocated string. +LPTSTR Join(HWND hwnd, LPTSTR str1, LPTSTR str2) +{ + register INT tmplen = 0; + register LPTSTR strtmp; + static INT StrCnt = 0; + + tmplen = _tcslen(str1) + _tcslen(str2); + LPTSTR *ppStoriage = (LPTSTR*)GetProp(hwnd, PROPSTORAGE); + if (NULL == ppStoriage) + { + // Create and store a circular pszBuffer + ppStoriage = (LPTSTR*)calloc(2, sizeof(LPTSTR)); + SetProp(hwnd, PROPSTORAGE, ppStoriage); + } + StrCnt = (StrCnt + 1) & 1; + if (ppStoriage[StrCnt]) + free(ppStoriage[StrCnt]); + + strtmp = (ppStoriage[StrCnt] = (LPTSTR)calloc(tmplen + 1, sizeof(TCHAR))); + + _tcscat(strtmp, str1); + _tcscat(strtmp, str2); + + return strtmp; +} + +/// @brief Allocate and store window text. +/// +/// @param hwnd The handle to the the control's window. +/// @param szText The text to store. +/// +/// @returns VOID. +VOID Text_SetProp(HWND hwnd, LPTSTR szText) +{ + LPTSTR szProp = (LPTSTR)GetProp(hwnd, PROPTEXT); + if (NULL != szProp) + free(szProp); + SetProp(hwnd, PROPTEXT, NewString(szText)); +} + +/// @brief Retrieve a pointer to stored window text. +/// +/// @param hwnd The handle to the the control's window. +/// +/// @returns a pointer to a string of text. +LPTSTR Text_GetProp(HWND hwnd) +{ + static LPTSTR szProp; + szProp = (LPTSTR)GetProp(hwnd, PROPTEXT); + if (NULL == szProp) + szProp = TEXT(""); + return szProp; +} + +/// @brief Create the display text from the list of checked items. +/// +/// @param hwnd The handle to this control (where circular pszBuffer stored) +/// @param hwndCtl The handle of the combobox +/// @param pText Pointer to recieve text +/// +/// @returns VOID. +VOID RecalcText(HWND hwnd, HWND hwndCtl, LPTSTR *pText) +{ + *pText = TEXT(""); + INT nCount = ComboBox_GetCount(hwndCtl); + LPTSTR strSeparator = DefaultSeperator(); + + for (INT i = 0; i < nCount; i++) + { + if (CheckedComboBox_GetCheckState(hwndCtl, i)) + { + INT len = ComboBox_GetLBTextLen(hwndCtl, i) + 1; + LPTSTR buf = (LPTSTR)calloc(len, sizeof(TCHAR)); + ComboBox_GetLBText(hwndCtl, i, buf); + + if (0 < _tcslen(*pText)) + *pText = Join(hwndCtl, *pText, strSeparator); + + *pText = Join(hwndCtl, *pText, buf); + free(buf); + } + } +} + +/// @brief Handles WM_DRAWITEM message sent to the control when a visual +/// aspect of the owner-drawn combobox has changed. +/// +/// @param hwnd Handle of control. +/// @param lpDrawItem The structure that contains information about the item +/// to be drawn and the type of drawing required. +/// +/// @returns VOID. +VOID ComboBoxOwner_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT *lpDrawItem) +{ + if (lpDrawItem->itemAction & (ODA_DRAWENTIRE | ODA_SELECT)) + { + COLORREF crBk = 0; + COLORREF crTx = 0; + + HDC dc = lpDrawItem->hDC; + + RECT rcBitmap = lpDrawItem->rcItem; + RECT rcText = lpDrawItem->rcItem; + + if (lpDrawItem->itemState & ODS_COMBOBOXEDIT) + { + LPTSTR strText; + + // Make sure the Text member is updated + RecalcText(hwnd, lpDrawItem->hwndItem, &strText); + Text_SetProp(lpDrawItem->hwndItem, strText); + + // Erase and draw + ExtTextOut(dc, 0, 0, ETO_OPAQUE, &rcText, 0, 0, 0); + + DrawText(dc, strText, _tcslen(strText), &rcText, + DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS); + } + else // it is one of the items + { + INT len = ComboBox_GetLBTextLen(lpDrawItem->hwndItem, lpDrawItem->itemID) +1; + LPTSTR buf = (LPTSTR)calloc(len, sizeof(TCHAR)); + ComboBox_GetLBText(lpDrawItem->hwndItem, lpDrawItem->itemID, buf); + + TEXTMETRIC metrics; + GetTextMetrics(dc, &metrics); + + // Checkboxes for larger sized fonts seem a bit crowded so adjust + // accordingly. + INT iFactor = 24 < metrics.tmHeight ? -2 : -1; + InflateRect(&rcBitmap, iFactor, iFactor); + rcBitmap.right = rcBitmap.left + (rcBitmap.bottom - rcBitmap.top); + + rcText.left = rcBitmap.right + metrics.tmAveCharWidth; + + UINT nState = DFCS_BUTTONCHECK; + + if (HIWORD(lpDrawItem->itemData)) + { + nState |= DFCS_CHECKED; + } + if (LOWORD(lpDrawItem->itemData)) + { + nState |= DFCS_INACTIVE; + } + + // Draw the checkmark using DrawFrameControl + DrawFrameControl(dc, &rcBitmap, DFC_BUTTON, nState); + + if (FLATCHECKS & (DWORD)GetWindowLongPtr(hwnd, GWLP_USERDATA)) + { + //Make border thin + FrameRect(lpDrawItem->hDC, &rcBitmap, GetSysColorBrush(COLOR_BTNSHADOW)); + // Extra pass for larger font sized checkboxes + for (int i = 24 < metrics.tmHeight ? 3 : 2; 0 < i; i--) + { + InflateRect(&rcBitmap, -1, -1); + FrameRect(lpDrawItem->hDC, &rcBitmap, GetSysColorBrush(COLOR_WINDOW)); + } + } + if (lpDrawItem->itemState & (ODS_SELECTED)) + { + crBk = GetBkColor(dc); + crTx = GetTextColor(dc); + + SetBkColor(dc, GetSysColor(COLOR_HIGHLIGHT)); + SetTextColor(dc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + } + + // Erase and draw + ExtTextOut(dc, 0, 0, ETO_OPAQUE, &rcText, 0, 0, 0); + + DrawText(dc, buf, _tcslen(buf), &rcText, DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS); + + if (lpDrawItem->itemState & (ODS_FOCUS | ODS_SELECTED) == (ODS_FOCUS | ODS_SELECTED)) + DrawFocusRect(dc, &rcText); + + // Ensure Defaults + if (lpDrawItem->itemState & (ODS_SELECTED)) + { + SetBkColor(dc, crBk); + SetTextColor(dc, crTx); + } + free(buf); + } + } +} + +/// @brief Handles WM_MEASUREITEM message sent to the control when the owner-drawn +/// combobox is created. +/// +/// @param hwnd Handle of the listbox. +/// @param lpMeasureItem The structure that contains the dimensions of the +/// owner-drawn combobox. +/// +/// @returns VOID. +VOID ComboBoxOwner_OnMeasureItem(HWND hwnd, LPMEASUREITEMSTRUCT lpMeasureItem) +{ + HDC hDC = GetDC(hwnd); + + // For common device contexts, GetDC assigns default attributes to the device + // context each time it is retrieved. I guess the parent dialog's DC is common + // because I do not get the metrics for the dialog's font. So I'll have to + // explicitly select the font into the device context to get it's metrics. + HFONT hFont = (HFONT)SelectObject(hDC, (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0L)); + TEXTMETRIC metrics; + GetTextMetrics(hDC, &metrics); + + // Now that we have the metrics clean up (but don't delete the Parent's HFONT) + SelectObject(hDC, hFont); + ReleaseDC(hwnd, hDC); + + LONG lHeight = metrics.tmHeight + metrics.tmExternalLeading; + if (lpMeasureItem->itemHeight < lHeight) + lpMeasureItem->itemHeight = lHeight; +} + +/// @brief Handles WM_COMMAND notifications targeting the ComboBoxes. +/// +/// @param hwnd Handle of parent. +/// @param id The id of the sender. +/// @param hwndCtl The hwnd of the sender. +/// @param codeNotify The notification code sent. +/// +/// @returns VOID. +static VOID ComboBoxOwner_OnNotify(HWND hwnd, INT id, HWND hwndCtl, UINT codeNotify) +{ + if((ID_EDCOMBO == id || ID_CHKCOMBO == id) && CBN_CLOSEUP == codeNotify) + { + Editor_DoUpdate(hwnd, hwndCtl, TRUE); + } + else if(ID_COMBO == id && CBN_SELCHANGE == codeNotify) + { + Editor_DoUpdate(hwnd, hwndCtl, FALSE); + } + else if(ID_EDCOMBO == id && CBN_SELCHANGE == codeNotify) + { + // The Editbox control is updated following the + // CBN_SELCHANGE notification (unlike the combobox + // Static label and is unhelpfull behavior in this use case.) + // This necessitates performing the update in this procedure. + INT index = ComboBox_GetCurSel(hwndCtl); + INT len = ComboBox_GetLBTextLen(hwndCtl,index) + 1; + LPTSTR buf = (LPTSTR)calloc(len, sizeof(TCHAR)); + ComboBox_GetLBText(hwndCtl,index,buf); + ComboBox_SetText(hwndCtl,buf); + free(buf); + Editor_DoUpdate(hwnd, hwndCtl, FALSE); + } + else if(CBN_KILLFOCUS == codeNotify) + { + Editor_DoUpdate(hwnd, hwndCtl, TRUE); + } +} + +/// @brief Handles WM_CHAR message sent to the comboListbox. +/// +/// @param hwnd Handle of the comboListbox. +/// @param ch The character. +/// @param cRepeat The number of times the keystroke is repeated +/// as a result of the user holding down the key. +/// +/// @returns VOID. +VOID ComboList_OnChar(HWND hwnd, TCHAR ch, INT cRepeat) +{ + if (VK_SPACE == ch) + { + HWND hCombo = (HWND)GetProp(hwnd, HCOMBO); + if (NULL != hCombo) + { + // Get the current selection + INT nIndex = CallWindowProc((WNDPROC)GetProp(hwnd, WPRC), + hwnd, LB_GETCURSEL, 0, 0L); + if (LB_ERR != nIndex) + { + RECT rcItem; + ListBox_GetItemRect(hwnd, nIndex, &rcItem); + InvalidateRect(hwnd, &rcItem, FALSE); + CheckedComboBox_SetCheckState(hCombo, nIndex, + !CheckedComboBox_GetCheckState(hCombo, nIndex)); + } + } + } + FORWARD_WM_CHAR(hwnd, ch, cRepeat, DefWindowProc); +} + +/// @brief Handles WM_LBUTTONDOWN message in the comboListbox. +/// +/// @param hwnd Handle of comboListbox. +/// @param fDoubleClick TRUE if this is a double click event. +/// @param x The xpos of the mouse. +/// @param y The ypos of the mouse. +/// @param keyFlags Set if certain keys down at time of click. +/// +/// @returns VOID. +VOID ComboList_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, INT x, INT y, UINT keyFlags) +{ + HWND hCombo; + + hCombo = (HWND)GetProp(hwnd, HCOMBO); + if (NULL != hCombo) + { + DWORD dwRtn = ListBox_ItemFromPoint(hwnd, x, y); + INT nIndex = (INT)LOWORD(dwRtn); + BOOL fNonClient = (BOOL)HIWORD(dwRtn); + + if (!fNonClient) + { + // Invalidate this window + RECT rcItem; + ListBox_GetItemRect(hwnd, nIndex, &rcItem); + InvalidateRect(hwnd, &rcItem, FALSE); + + if ((nIndex == ListBox_GetCurSel(hwnd)) || fDoubleClick) + { + CheckedComboBox_SetCheckState(hCombo, nIndex, + !CheckedComboBox_GetCheckState(hCombo, nIndex)); + } + } + else + { + // Do the default outside of box handling now (Single click close the popup + // window when clicked outside) + FORWARD_WM_LBUTTONDOWN(hwnd, fDoubleClick, x, y, keyFlags, DefProc); + return; + } + } + // Do the default inside of box handling now (such as hilite item and toggle + // check while keeping keyboard focus when clicked inside) + FORWARD_WM_LBUTTONDOWN(hwnd, fDoubleClick, x, y, keyFlags, DefWindowProc); +} + +/// @brief Handles WM_RBUTTONDOWN message in the comboListbox. +/// +/// @param hwnd Handle of comboListbox. +/// @param fDoubleClick TRUE if this is a double click event. +/// @param x The xpos of the mouse. +/// @param y The ypos of the mouse. +/// @param keyFlags Set if certain keys down at time of click. +/// +/// @returns VOID. +VOID ComboList_OnRButtonDown(HWND hwnd, BOOL fDoubleClick, INT x, INT y, UINT keyFlags) +{ + HWND hCombo; + + hCombo = (HWND)GetProp(hwnd, HCOMBO); + if (NULL != hCombo) // Check/Uncheck All + { + INT nCount = ComboBox_GetCount(hCombo); + INT nCheckCount = 0; + + for (INT i = 0; i < nCount; i++) + { + if (CheckedComboBox_GetCheckState(hCombo, i)) + nCheckCount++; + } + BOOL bCheck = nCheckCount != nCount; + for (INT i = 0; i < nCount; i++) + { + CheckedComboBox_SetCheckState(hCombo, i, bCheck); + } + + // Make sure to invalidate this window as well + InvalidateRgn(hwnd, NULL, FALSE); + + // Do the default handling now + FORWARD_WM_RBUTTONDOWN(hwnd, fDoubleClick, x, y, keyFlags, DefWindowProc); + } +} + +/// @brief Handle WM_DESTROY message. +/// +/// @param hwnd Handle of parent. +/// +/// @returns VOID. +void ComboList_OnDestroy(HWND hwnd) +{ + WNDPROC wp = (WNDPROC)GetProp(hwnd, WPRC); + if (NULL != wp) + { + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (DWORD)wp); + RemoveProp(hwnd, WPRC); + RemoveProp(hwnd, HCOMBO); + } +} + +/// @brief Window procedure for the comboBox's dropdown comboListbox. +/// +/// @param hwnd Handle of comboListbox. +/// @param msg Which message? +/// @param wParam Message parameter. +/// @param lParam Message parameter. +/// +/// @returns LRESULT depends on message. +static LRESULT CALLBACK ComboList_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + //Note: this callback only applies to the checked combo. + switch (msg) + { + HANDLE_MSG(hwnd, WM_CHAR, ComboList_OnChar); + HANDLE_MSG(hwnd, WM_LBUTTONDOWN, ComboList_OnLButtonDown); + HANDLE_MSG(hwnd, WM_RBUTTONDOWN, ComboList_OnRButtonDown); + HANDLE_MSG(hwnd, WM_DESTROY, ComboList_OnDestroy); + + case WM_LBUTTONUP: + // Don't do anything here so that the mouse continues to + // track over the choices after keyboard input + return 0; + + default: + return DefProc(hwnd, msg, wParam, lParam); + } +} + +/// @brief Perform auto completion in edit control. +/// +/// @param hwnd Handle of the combobox. +/// @param ch the keyboard char to handle. +/// +/// @returns VOID +static void ComboEdit_DoAutoComplete(HWND hwnd, TCHAR ch) +{ + // Note: If user presses VK_RETURN or VK_TAB then + // the ComboBox Notification = CBN_SELENDCANCEL and + // a call to ComboBox_GetCurSel() will return the canceled index. + // If the user presses any other key that causes a selection + // and closure of the dropdown then + // the ComboBox Notification = CBN_SELCHANGE + + INT len = ComboBox_GetTextLength(hwnd) + 10; + LPTSTR buf = (LPTSTR)calloc(len, sizeof(TCHAR)); + LPTSTR toFind = (LPTSTR)calloc(len, sizeof(TCHAR)); + if(NULL != buf && NULL != toFind) + { + static BOOL fMatched = TRUE; + int index = 0; + + // Handle keyboard input + if (VK_RETURN == ch) + { + ComboBox_ShowDropdown(hwnd, FALSE); + ComboBox_SetEditSel(hwnd, 0, -1); //selects the entire item + } + else if (VK_BACK == ch) + { + if(fMatched) + { + //Backspace normally erases highlighted match + // we only want to move the highlighter back a step + index = ComboBox_GetCurSel(hwnd); + int bs = LOWORD(ComboBox_GetEditSel(hwnd)) - 1; + + // keep current selection selected + ComboBox_SetCurSel(hwnd, index); + + // Move cursor back one space to the insertion point for new text + // and hilight the remainder of the selected match or near match + ComboBox_SetEditSel(hwnd, bs, -1); + } + else + { + toFind[_tcslen(toFind) -1] = 0; + ComboBox_SetText(hwnd, toFind); + ComboBox_SetEditSel(hwnd, -1, -1); + FORWARD_WM_KEYDOWN(hwnd, VK_END, 0, 0, SNDMSG); + } + } + else if (!_istcntrl(ch)) + { + BOOL status = GetWindowLongPtr(hwnd, GWL_STYLE) & CBS_DROPDOWN; + if (status) + ComboBox_ShowDropdown(hwnd, TRUE); + + // Get the substring from 0 to start of selection + ComboBox_GetText(hwnd, buf, len); + buf[LOWORD(ComboBox_GetEditSel(hwnd))] = 0; + + _stprintf(toFind, len, + _T("%" STR_SPEC "%" CHR_SPEC), buf, ch); + + // Find the first item in the combo box that matches ToFind + index = ComboBox_FindStringExact(hwnd, -1, toFind); + + if (CB_ERR == index) //no match + { + // Find the first item in the combo box that starts with ToFind + index = ComboBox_FindString(hwnd, -1, toFind); + } + if (CB_ERR != index) + { + // Else for match + fMatched = TRUE; + ComboBox_SetCurSel(hwnd, index); + ComboBox_SetEditSel(hwnd, _tcslen(toFind), -1); + } + else // Display text that is not in the selected list + { + fMatched = FALSE; + ComboBox_SetText(hwnd, toFind); + ComboBox_SetEditSel(hwnd, _tcslen(toFind), -1); + FORWARD_WM_KEYDOWN(hwnd, VK_END, 0, 0, SNDMSG); + } + } + } + free(buf); + free(toFind); +} + +/// @brief Window procedure for the combobox' edit control. +/// +/// @param hwnd Handle of combobox. +/// @param msg Which message? +/// @param wParam Message parameter. +/// @param lParam Message parameter. +/// +/// @returns LRESULT depends on message. +LRESULT CALLBACK ComboEdit_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + //Note: this callback only applies to the editable combobox + if(WM_GETDLGCODE == msg) + { + return VK_TAB == wParam ? FALSE: DLGC_WANTALLKEYS; + } + else if (WM_KEYDOWN == msg && VK_ESCAPE == wParam) + { + ShowWindow(GetParent(hwnd), SW_HIDE); + SetFocus(g_lpInst->hwndListBox); + return TRUE; + } + else if (WM_KEYDOWN == msg && VK_RETURN == wParam) + { + ComboBox_ShowDropdown(GetParent(hwnd), TRUE); + return TRUE; + } + else if (WM_CHAR == msg) + { + ComboEdit_DoAutoComplete(GetParent(hwnd), (TCHAR)wParam); + return FALSE; + } + else if (WM_DESTROY == msg) //Unsubclass the edit control + { + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (INT_PTR)GetProp(hwnd, TEXT("Wprc"))); + RemoveProp(hwnd, TEXT("Wprc")); + return FALSE; + } + else + return DefProc(hwnd, msg, wParam, lParam); +} + +/// @brief Handles WM_COMMAND message. +/// +/// @param hwnd Handle of Combobox. +/// @param id The id of the sender. +/// @param hwndCtl The hwnd of the sender. +/// @param codeNotify The notification code sent. +/// +/// @returns VOID. +void ComboBox_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) +{ + // The combo's edit box posts a notification on loading. + // The first time it does so we'll grab and subclass them. + WNDPROC lpfn = (WNDPROC)GetProp(hwndCtl, WPRC); + if (NULL == lpfn) + { + //Subclass edit and save the old proc + SetProp(hwndCtl, WPRC, (HANDLE)GetWindowLongPtr(hwndCtl, GWLP_WNDPROC)); + static TCHAR classname[MAX_PATH]; + GetClassName(hwndCtl, classname, NELEMS(classname)); + if (0 == _tcsicmp(classname, WC_EDIT)) + { + SubclassWindow(hwndCtl, ComboEdit_Proc); + } + } + FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, DefProc); +} + +/// @brief Handles WM_CTLCOLORLISTBOX message. +/// +/// @param hwnd Handle of parent. +/// @param hdc Handle of child control's display context. +/// @param hwndChild Handle of Child control +/// @param type The WM_CTLCOLORxx message control type. +/// +/// @returns HBRUSH. +HBRUSH ComboBox_OnCtlColorListbox(HWND hwnd, HDC hdc, HWND hwndChild, int type) +{ + // The combo's list box posts a notification on each time it is painted. + // The first time it does so we'll grab and subclass it. + // Note: this message is fired prior to WM_COMMAND and presents the best + // timing for list box capture and subclassing. + if (CBS_OWNERDRAWFIXED & GetWindowLongPtr(hwnd, GWL_STYLE))// checked listbox + { + WNDPROC lpfn = (WNDPROC)GetProp(hwndChild, WPRC); + if (NULL == lpfn) + { + //Store referance to Parent comboBox + SetProp(hwndChild, HCOMBO, hwnd); + + //Subclass child and save the OldProc + SetProp(hwndChild, WPRC, (HANDLE)GetWindowLongPtr(hwndChild, GWLP_WNDPROC)); + SubclassWindow(hwndChild, ComboList_Proc); + } + return FORWARD_WM_CTLCOLORLISTBOX(hwnd, hdc, hwndChild, DefProc); + } + return FORWARD_WM_CTLCOLORLISTBOX(hwnd, hdc, hwndChild, DefWindowProc); +} + +/// @brief Handles WM_CHAR message. +/// +/// @param hwnd Handle of Combobox. +/// @param ch The character code. +/// @param cRepeat The repeat count (while holding the key down). +/// +/// @returns VOID. +void ComboBox_OnChar(HWND hwnd, TCHAR ch, int cRepeat) +{ + HWND hGParent = GetParent(GetParent(hwnd)); + Control_GetInstanceData(hGParent, &g_lpInst); + switch (ch) + { + case VK_TAB: + if (GetKeyState(VK_SHIFT) & 0x8000) + { + FORWARD_WM_CHAR(hwnd,VK_ESCAPE,cRepeat, SNDMSG); + } + else //Set focus to grid parent + { + ShowWindow(hwnd, SW_HIDE); + SetFocusToParent(); + } + return; + case VK_ESCAPE: + ShowWindow(hwnd, SW_HIDE); + SetFocus(g_lpInst->hwndListBox); + return; + case VK_RETURN: + //Prevent the this message from being forwarded to + // ComboEdit_Proc(). + return; + } + FORWARD_WM_CHAR(hwnd, ch, cRepeat, DefProc); +} + +/// @brief Handles WM_KEYDOWN messages sent to the combobox. +/// +/// @param hwnd Handle of the combobox. +/// @param vk The virtual key code. +/// @param fDown TRUE for keydown (always TRUE). +/// @param cRepeat The number of times the keystroke is repeated +/// as a result of the user holding down the key. +/// @param flags Indicate OEM scan codes etc. +/// +/// @returns VOID. +void ComboBox_OnKeyDown(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags) +{ + if(VK_RETURN == vk) + { + DWORD dwID = GetWindowID(hwnd); + if(ID_COMBO == dwID || ID_CHKCOMBO == dwID) + { + ComboBox_ShowDropdown(hwnd, + !ComboBox_GetDroppedState(hwnd)); + return; + } + } + FORWARD_WM_KEYDOWN(hwnd, vk, cRepeat, flags, DefProc); +} + +/// @brief Handles WM_DESTROY message. +/// +/// @param hwnd Handle of Combobox. +/// +/// @returns VOID. +VOID ComboBox_OnDestroy(HWND hwnd) +{ + if (ID_CHKCOMBO == GetWindowID(hwnd)) + { + LPTSTR *ppStoriage = (LPTSTR*)GetProp(hwnd, PROPSTORAGE); + if (NULL != ppStoriage) + { + free(ppStoriage[0]); + free(ppStoriage[1]); + free(ppStoriage); + RemoveProp(hwnd, PROPSTORAGE); + } + LPTSTR szText = (LPTSTR)GetProp(hwnd, PROPTEXT); + if (NULL != szText) + { + free(szText); + RemoveProp(hwnd, PROPTEXT); + } + } + //All Combos + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (DWORD_PTR)GetProp(hwnd, WPRC)); + RemoveProp(hwnd, WPRC); +} + +/// @brief Handles WM_GETTEXT message. +/// +/// @param hwnd Handle of Combobox. +/// @param cchTextMax Specifies the maximum number of characters to be copied, +/// including the terminating null character. +/// @param lpszText Points to the pszBuffer that is to receive the text. +/// +/// @returns The number of characters copied. +INT ComboBox_OnGetText(HWND hwnd, INT cchTextMax, LPTSTR lpszText) +{ + if (CBS_OWNERDRAWFIXED & GetWindowLongPtr(hwnd, GWL_STYLE)) + { + LPTSTR strText = Text_GetProp(hwnd); + INT nChars = lstrlen(strText) + 1; + if (nChars > cchTextMax) + nChars = cchTextMax; + + lstrcpyn(lpszText, strText, nChars); + return nChars; + } + return FORWARD_WM_GETTEXT(hwnd, cchTextMax, lpszText, DefProc); +} + +/// @brief Handles WM_GETTEXTLENGTH message. +/// +/// @param hwnd Handle of Combobox. +/// +/// @returns The length, in characters, of the text. +INT ComboBox_OnGetTextLength(HWND hwnd) +{ + if (CBS_OWNERDRAWFIXED & GetWindowLongPtr(hwnd, GWL_STYLE)) + { + LPTSTR strText = Text_GetProp(hwnd); + return lstrlen(strText); + } + return FORWARD_WM_GETTEXTLENGTH(hwnd, DefProc); +} + +/// @brief Handle WM_KILLFOCUS message. +/// +/// @param hwnd Handle of the Combobox. +/// @param hwndNewFocus Handle of the window that recieved focus. +/// +/// @returns VOID. +void ComboBox_OnKillFocus(HWND hwnd, HWND hwndNewFocus) +{ + if (hwnd != GetParent(hwndNewFocus)) // not a combobox edit control or listbox + { + Editor_OnKillFocus(hwnd, hwndNewFocus); + } + FORWARD_WM_KILLFOCUS(hwnd, hwndNewFocus, DefProc); +} + +/// @brief Handle WM_SETFOCUS message. +/// +/// @param hwnd Handle of the Combobox. +/// @param hwndOldFocus Handle of the window losing focus. +/// +/// @returns VOID. +void ComboBox_OnSetFocus(HWND hwnd, HWND hwndOldFocus) +{ + if(ID_EDCOMBO == GetWindowID(hwnd)) + { + FORWARD_WM_SETFOCUS(FindWindowEx(hwnd, NULL, WC_EDIT, NULL), + hwndOldFocus, SNDMSG); + } + else + { + FORWARD_WM_SETFOCUS(hwnd, hwndOldFocus, DefProc); + } +} + +/// @brief Handles WM_PAINT message. +/// +/// @param hwnd Handle of Combobox. +/// +/// @returns VOID. +void ComboBox_OnPaint(HWND hwnd) +{ + // First let the system do its thing + FORWARD_WM_PAINT(hwnd, DefProc); + + // Next obliterate the border + HDC hdc = GetWindowDC(hwnd); + RECT rect; + + GetClientRect(hwnd, &rect); + + DrawBorder(hdc, &rect, BF_TOPLEFT, GetSysColor(COLOR_WINDOW)); + + rect.top += 1; + rect.left += 1; + DrawBorder(hdc, &rect, BF_TOPLEFT, GetSysColor(COLOR_WINDOW)); + + ReleaseDC(hwnd, hdc); +} + +/// @brief Window procedure for the combobox control. +/// +/// @param hwnd Handle of combobox. +/// @param msg Which message? +/// @param wParam Message parameter. +/// @param lParam Message parameter. +/// +/// @returns LRESULT depends on message. +static LRESULT CALLBACK ComboBox_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + HANDLE_MSG(hwnd, WM_CHAR, ComboBox_OnChar); + HANDLE_MSG(hwnd, WM_COMMAND, ComboBox_OnCommand); + HANDLE_MSG(hwnd, WM_CTLCOLORLISTBOX, ComboBox_OnCtlColorListbox); + HANDLE_MSG(hwnd, WM_DESTROY, ComboBox_OnDestroy); + HANDLE_MSG(hwnd, WM_GETTEXT, ComboBox_OnGetText); + HANDLE_MSG(hwnd, WM_GETTEXTLENGTH, ComboBox_OnGetTextLength); + HANDLE_MSG(hwnd, WM_KEYDOWN, ComboBox_OnKeyDown); + HANDLE_MSG(hwnd, WM_KILLFOCUS, ComboBox_OnKillFocus); + HANDLE_MSG(hwnd, WM_PAINT, ComboBox_OnPaint); + HANDLE_MSG(hwnd, WM_SETFOCUS, ComboBox_OnSetFocus); + + case WM_GETDLGCODE: + return DLGC_WANTMESSAGE; + + default: + return DefProc(hwnd, msg, wParam, lParam); + } +} + +/// @brief Create combobox control configured either as editable, static, or checked. +/// +/// @param hInstance The handle of an instance. +/// @param hwndParent The handle of the parent (the visible listbox). +/// @param type The combo type identifier. +/// +/// @returns HWND A handle to the control. +static HWND CreateComboBox(HINSTANCE hInstance, HWND hwndParent, INT type) +{ + DWORD dwStyle = 0, dwExStyle = 0, dwID = 0; + HWND hwnd; + + switch (type) + { + case PIT_EDITCOMBO: + dwStyle = WS_CHILD | WS_VSCROLL | CBS_NOINTEGRALHEIGHT | CBS_DROPDOWN; + dwID = ID_EDCOMBO; + break; + case PIT_COMBO: + dwStyle = WS_CHILD | WS_VSCROLL | CBS_NOINTEGRALHEIGHT | CBS_DROPDOWNLIST; + dwID = ID_COMBO; + break; + case PIT_CHECKCOMBO: + dwStyle = WS_CHILD | WS_VSCROLL | CBS_NOINTEGRALHEIGHT | + CBS_DROPDOWNLIST | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS; + dwID = ID_CHKCOMBO; + } + dwExStyle = WS_EX_LEFT; + + hwnd = CreateWindowEx( + dwExStyle, + WC_COMBOBOX, + NULL, + dwStyle, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + hwndParent, + (HMENU)dwID, + hInstance, + NULL); + + if (!hwnd) + { + return NULL; + } + + //Disable visual styles for the time being since the combo looks bad + // in this grid when drawn using the Vista and later styles. + SetWindowTheme(hwnd, L" ", L" "); + + SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0L); + + SetProp(hwnd, WPRC, (HANDLE)GetWindowLongPtr(hwnd, GWLP_WNDPROC)); + SubclassWindow(hwnd, ComboBox_Proc); + + return hwnd; +} + +#pragma endregion Combo Box + +#pragma region list box message handlers + +/// @brief Find the catalog item that matches the given catalog name. +/// +/// @param hwnd Handle of the listbox. +/// @param indexStart Index to begin search. +/// @param szCatalog The catalog name. +/// +/// @returns INT The index of the catalog otherwise LB_ERR. +static INT ListBox_FindCatalog(HWND hwnd, INT indexStart, LPCTSTR szCatalog) +{ + INT ln = _tcslen(szCatalog) + 1; + if (ln == 1 || indexStart >= ListBox_GetCount(hwnd)) + return LB_ERR; + + for (INT i = indexStart < 0 ? 0 : indexStart; i < ListBox_GetCount(hwnd); i++) + { + LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(hwnd, i); + if (NULL != pItem && 0 == _tcsicmp(pItem->lpszCatalog, szCatalog)) + return i; + } + return LB_ERR; +} + +/// @brief Given the address of a LISTBOXITEM object of type PIT_CATALOG, +/// collapse it if it is expanded, or expand it if it is collapsed. +/// +/// @param pItem Pointer to an LISTBOXITEM object. +/// +/// @returns VOID. +static VOID ToggleCatalog(LPLISTBOXITEM pItem) +{ + if (NULL != pItem && PIT_CATALOG != pItem->iItemType) + return; + + pItem->fCollapsed = !pItem->fCollapsed; + + if (pItem->fCollapsed) + { + for (INT i = ListBox_GetCount(g_lpInst->hwndListBox) - 1; i >= 0; i--) + { + LPLISTBOXITEM p = (LPLISTBOXITEM)ListBox_GetItemDataSafe(g_lpInst->hwndListBox, i); + if (NULL != p && _tcsicmp(p->lpszCatalog, pItem->lpszCatalog) == 0) + { + if (PIT_CATALOG != p->iItemType) + { + ListBox_DeleteString(g_lpInst->hwndListBox, i); + } + } + } + } + else + { + INT idx = ListBox_FindCatalog(g_lpInst->hwndListBox, 0, pItem->lpszCatalog); + if (LB_ERR != idx) + { + LPLISTBOXITEM pp; + for (INT i = ListBox_GetCount(g_lpInst->hwndListMap) - 1; 0 <= i; i--) + { + pp = ListBox_GetItemDataSafe(g_lpInst->hwndListMap, i); + if (NULL != pp->lpszCatalog) + { + if (0 == _tcsicmp(pp->lpszCatalog, pItem->lpszCatalog)) + { + if (PIT_CATALOG != pp->iItemType) + { + ListBox_InsertItemData(g_lpInst->hwndListBox, idx + 1, pp); + } + } + } + } + } + } +} + +/// @brief Handle the begin scroll event of a listbox. +/// +/// @param hwnd Handle of a listbox. +/// +/// @par Comments: +/// This event is not raised by the listbox but is detected +/// by indirect means. +/// +/// @see ListBox_Proc() WM_NCLBUTTONDOWN for details. +/// +/// @returns VOID. +static VOID ListBox_OnBeginScroll(HWND hwnd) +{ + if (NULL == g_lpInst->lpCurrent) + return; + + switch (g_lpInst->lpCurrent->iItemType) + { + case PIT_CHECK: + g_lpInst->lpCurrent->lpszMisc = UNSELECT; + break; + case PIT_STATIC: + break; + case PIT_DATETIME: + ShowWindow(g_lpInst->hwndCtl1, SW_HIDE); + ShowWindow(g_lpInst->hwndCtl2, SW_HIDE); + break; + default: + ShowWindow(g_lpInst->hwndCtl1, SW_HIDE); + } +} + +/// @brief Handle the end scroll event of a listbox. +/// +/// @param hwnd Handle of a listbox. +/// +/// @par Comments: +/// This event is not raised by the listbox but is detected +/// by indirect means. +/// +/// @see ListBox_Proc() WM_SETCURSOR for details. +/// +/// @returns VOID. +static VOID ListBox_OnEndScroll(HWND hwnd) +{ + if (NULL == g_lpInst->lpCurrent) + return; + + RECT rect; + memset(&rect, 0, sizeof rect); + + //Caculate the grid width + ListBox_GetItemRect(hwnd, ListBox_GetCurSel(hwnd), &rect); + + rect.top += 1; + rect.left = g_lpInst->iHDivider + 1; + + switch (g_lpInst->lpCurrent->iItemType) + { + case PIT_CATALOG: + case PIT_STATIC: + break; //Don't display anything + case PIT_CHECK: + g_lpInst->lpCurrent->lpszMisc = SELECT; + RedrawWindow(hwnd, &rect, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW); + break; + case PIT_EDIT: + if (NULL == g_lpInst->hwndCtl1) + { + SetFocus(g_lpInst->hwndListBox); + } + else //Display edit box + { + MoveWindow(g_lpInst->hwndCtl1, rect.left, rect.top, WIDTH(rect), HEIGHT(rect), TRUE); + ShowWindow(g_lpInst->hwndCtl1, SW_SHOW); + SetFocus(g_lpInst->hwndCtl1); + } + break; + case PIT_COMBO: + case PIT_EDITCOMBO: + case PIT_CHECKCOMBO: + case PIT_IP: + case PIT_DATE: + case PIT_TIME: + if (NULL == g_lpInst->hwndCtl1) + SetFocus(g_lpInst->hwndListBox); + else //Display editable combobox + { + MoveWindow(g_lpInst->hwndCtl1, rect.left, rect.top, WIDTH(rect), HEIGHT(rect), TRUE); + ShowWindow(g_lpInst->hwndCtl1, SW_SHOW); + SetFocus(g_lpInst->hwndCtl1); + } + break; + case PIT_DATETIME: + if (NULL == g_lpInst->hwndCtl1 || NULL == g_lpInst->hwndCtl2) + SetFocus(g_lpInst->hwndListBox); + else //Display date and time + { + RECT rect0, rect1; + rect0 = rect1 = rect; + rect0.right = rect0.left + (rect0.right - rect0.left) / 2; + rect1.left = rect1.left + (rect1.right - rect1.left) / 2; + MoveWindow(g_lpInst->hwndCtl1, rect0.left, rect0.top, + WIDTH(rect0), HEIGHT(rect0), TRUE); + MoveWindow(g_lpInst->hwndCtl2, rect1.left, rect1.top, + WIDTH(rect1), HEIGHT(rect1), TRUE); + ShowWindow(g_lpInst->hwndCtl2, SW_SHOW); + ShowWindow(g_lpInst->hwndCtl1, SW_SHOW); + SetFocus(g_lpInst->hwndCtl1); + } + break; + default: //Button control + if (NULL == g_lpInst->hwndCtl1) + { + SetFocus(g_lpInst->hwndListBox); + } + else //Display Button + { + if (WIDTH(rect) > 19) + { + rect.left = rect.right - 19; + rect.right -= 2; + } + + rect.top += 2; + rect.bottom -= 2; + + MoveWindow(g_lpInst->hwndCtl1, rect.left, rect.top, + WIDTH(rect), HEIGHT(rect), TRUE); + + ShowWindow(g_lpInst->hwndCtl1, SW_SHOW); + SetFocus(g_lpInst->hwndCtl1); + } + break; + } +} + +/// @brief Handles WM_LBUTTONDOWN message in the listbox. +/// +/// @param hwnd Handle of listbox. +/// @param fDoubleClick TRUE if this is a double click event. +/// @param x The xpos of the mouse. +/// @param y The ypos of the mouse. +/// @param keyFlags Set if certain keys down at time of click. +/// +/// @returns VOID. +static VOID ListBox_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, INT x, INT y, UINT keyFlags) +{ + if ((x >= g_lpInst->iHDivider - 5) && (x <= g_lpInst->iHDivider + 5)) + { + //If mouse clicked on divider line, then start resizing + SetCursor(LoadCursor(NULL, IDC_SIZEWE)); + + RECT rcWindow; + GetWindowRect(hwnd, &rcWindow); + rcWindow.left += WIDTH_PART0; + rcWindow.right -= 10; + //Do not let mouse leave the list box boundary + ClipCursor(&rcWindow); + + RECT rcClient; + GetClientRect(hwnd, &rcClient); + + g_lpInst->fTracking = TRUE; + g_lpInst->nDivTop = rcClient.top; + g_lpInst->nDivBtm = rcClient.bottom; + g_lpInst->nOldDivX = x; + + HDC hdc = GetDC(hwnd); + InvertLine(hdc, x, rcClient.top, x, rcClient.bottom); + ReleaseDC(hwnd, hdc); + + if (NULL != g_lpInst->lpCurrent) + { + switch (g_lpInst->lpCurrent->iItemType) + { + case PIT_CATALOG: + case PIT_STATIC: + break; //Ignore + case PIT_CHECK: + g_lpInst->lpCurrent->lpszMisc = UNSELECT; //Prevent toggle + break; + case PIT_DATE: + case PIT_TIME: + case PIT_DATETIME: + { + DatePicker_DoUpdate(g_lpInst->lpCurrent->iItemType); + } + break; + default: + Editor_DoUpdate(hwnd, g_lpInst->hwndCtl1, TRUE); + break; + } + } + + //Capture the mouse + SetCapture(hwnd); + } + else + { + if (fDoubleClick) + { + INT i = LOWORD(ListBox_ItemFromPoint(hwnd, x, y)); + LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(hwnd, i); + if (NULL != pItem) + { + if (PIT_CATALOG == pItem->iItemType) + ToggleCatalog(pItem); + } + } + g_lpInst->fTracking = FALSE; + } +} + +/// @brief Handles WM_LBUTTONUP message in the listbox. +/// +/// @param hwnd Handle of listbox. +/// @param x The xpos of the mouse. +/// @param y The ypos of the mouse. +/// @param keyFlags Set if certain keys down at time of click. +/// +/// @returns VOID. +static VOID ListBox_OnLButtonUp(HWND hwnd, INT x, INT y, UINT keyFlags) +{ + if (g_lpInst->fTracking) + { + //If columns were being resized then this indicates + // that mouse is up so resizing is done. Need to redraw + // columns to reflect their new widths. + + g_lpInst->fTracking = FALSE; + //If mouse was captured then release it + if (hwnd == GetCapture()) + ReleaseCapture(); + + ClipCursor(NULL); + + HDC hdc = GetDC(hwnd); + InvertLine(hdc, x, g_lpInst->nDivTop, x, g_lpInst->nDivBtm); + ReleaseDC(hwnd, hdc); + + //Set the divider position to the new value + g_lpInst->iHDivider = x; + Refresh(hwnd); + + if (NULL != g_lpInst->lpCurrent) + { + switch (g_lpInst->lpCurrent->iItemType) + { + case PIT_CATALOG: + case PIT_STATIC: + case PIT_CHECK: + break; //Ignore + case PIT_DATETIME: + ShowWindow(g_lpInst->hwndCtl2, SW_SHOW); + //Fall through + default: + ShowWindow(g_lpInst->hwndCtl1, SW_SHOW); + break; + } + } + } + else + { + INT i = LOWORD(ListBox_ItemFromPoint(hwnd, x, y)); + + if (ListBox_GetCurSel(hwnd) != i) + { + ListBox_SetCurSel(hwnd, i); + } + } + //Update the fields + FORWARD_WM_CHAR(g_lpInst->hwndCtl1, VK_RETURN, 0, SNDMSG); + FORWARD_WM_CHAR(g_lpInst->hwndCtl2, VK_RETURN, 0, SNDMSG); + FORWARD_WM_COMMAND(GetParent(hwnd), GetDlgCtrlID(hwnd), hwnd, LBN_SELCHANGE, SNDMSG); + + if (NULL != g_lpInst->lpCurrent) + { + switch (g_lpInst->lpCurrent->iItemType) + { + case PIT_CATALOG: + case PIT_STATIC: + return; + case PIT_CHECK: + if (0 == _tcsicmp(g_lpInst->lpCurrent->lpszMisc, SELECT)) + { + FORWARD_WM_KEYDOWN(hwnd, VK_SPACE, 0, 0, SNDMSG); + break; + } // else fallthrough + default: + FORWARD_WM_KEYDOWN(hwnd, VK_TAB, 0, 0, SNDMSG); //Focus to editor + } + } +} + +/// @brief Handles WM_MOUSEMOVE message in the listbox. +/// +/// @param hwnd Handle of listbox. +/// @param x The xpos of the mouse. +/// @param y The ypos of the mouse. +/// @param keyFlags Set if certain keys down at time of move. +/// +/// @returns VOID. +static VOID ListBox_OnMouseMove(HWND hwnd, INT x, INT y, UINT keyFlags) +{ + if (g_lpInst->fTracking) + { + //Move divider line to the mouse pos. if columns are + // currently being resized + HDC hdc = GetDC(hwnd); + //Remove old divider line + InvertLine(hdc, g_lpInst->nOldDivX, g_lpInst->nDivTop, + g_lpInst->nOldDivX, g_lpInst->nDivBtm); + //Draw new divider line + InvertLine(hdc, x, g_lpInst->nDivTop, x, g_lpInst->nDivBtm); + ReleaseDC(hwnd, hdc); + + g_lpInst->nOldDivX = x; + } + else if ((x >= g_lpInst->iHDivider - 5) && (x <= g_lpInst->iHDivider + 5)) + { + //if the cursor is over the row divider + SetCursor(LoadCursor(NULL, IDC_SIZEWE)); + } + // + //Set the tool tip text + // + if (NULL != g_lpInst->hwndToolTip) + { + TOOLINFO tiToolInfo; + TCHAR buf[80]; + TCHAR newText[80] = { 0 }; + tiToolInfo.lpszText = buf; + tiToolInfo.cbSize = sizeof(TOOLINFO); + SendMessage(g_lpInst->hwndToolTip, 0, (WPARAM)0, (LPARAM)(LPTOOLINFO)&tiToolInfo); + + if ((x >= g_lpInst->iHDivider + 5) && !g_lpInst->fTracking) + { + INT i = LOWORD(ListBox_ItemFromPoint(hwnd, x, y)); + LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(hwnd, i); + if (NULL != pItem) + { + switch (pItem->iItemType) + { + case PIT_FONT: + { + PROPGRIDFONTITEM pgfi; + LONG PointSize = 0; + LogFontItem_FromString(&pgfi, pItem->lpszCurValue); + HDC hDC = GetDC(hwnd); + PointSize = -MulDiv(pgfi.logFont.lfHeight, 72, + GetDeviceCaps(hDC, LOGPIXELSY)); + ReleaseDC(hwnd, hDC); + _stprintf(newText, NELEMS(buf), + _T("%")_T(STR_SPEC)_T("%d"), + pgfi.logFont.lfFaceName, PointSize); + } + break; + case PIT_CHECK: //Skip this item + break; + default: + //Replace the text in the buf and update the tool + _tcsncpy(newText, pItem->lpszCurValue, NELEMS(newText) - 1); + break; + } + } + } + if (0 != _tcsncmp(buf, newText, _tcslen(newText))) + //In order to reduce flicker, Do not update unless text changed + { + _tmemset(buf, (TCHAR)0, NELEMS(buf) - 1); + _tcsncpy(buf, newText, NELEMS(buf) - 1); + SendMessage(g_lpInst->hwndToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM)(LPTOOLINFO)&tiToolInfo); + } + } +} + +/// @brief Handle the selection of a listbox item of type PIT_EDIT. +/// +/// @param hwnd The handle of the listbox. +/// @param rc RECT containing desired coordinates for the edit control. +/// @param pItem Pointer to a LISTBOXITEM object. +/// +/// @returns VOID. +static VOID ListBox_OnSelectEdit(HWND hwnd, RECT rc, LPLISTBOXITEM pItem) +{ + rc.top += 1; + + //display edit box + if (NULL == g_lpInst->hwndCtl1) + g_lpInst->hwndCtl1 = CreateEdit(g_lpInst->hInstance, hwnd, ID_EDIT); + + MoveWindow(g_lpInst->hwndCtl1, rc.left, rc.top, WIDTH(rc), HEIGHT(rc), TRUE); + + //Set the text in the edit box to the property's current value + Edit_SetText(g_lpInst->hwndCtl1, pItem->lpszCurValue); + ShowWindow(g_lpInst->hwndCtl1, SW_SHOW); + SetFocus(g_lpInst->hwndCtl1); + Edit_SetSel(g_lpInst->hwndCtl1, 0, -1); +} + +/// @brief Handle the selection of a listbox item of type PIT_IP. +/// +/// @param hwnd The handle of the listbox. +/// @param rc RECT containing desired coordinates for the ipedit control. +/// @param pItem Pointer to a LISTBOXITEM object. +/// +/// @returns VOID. +static VOID ListBox_OnSelectIP(HWND hwnd, RECT rc, LPLISTBOXITEM pItem) +{ + rc.top += 1; + if (NULL == g_lpInst->hwndCtl1) + g_lpInst->hwndCtl1 = CreateIpEdit(g_lpInst->hInstance, hwnd, ID_IPEDIT, &rc); + MoveWindow(g_lpInst->hwndCtl1, rc.left, rc.top, WIDTH(rc), HEIGHT(rc), TRUE); + DWORD ip[4] = { 0, 0, 0, 0 }; + _stscanf(pItem->lpszCurValue, + _T("%hhu.%hhu.%hhu.%hhu"), + &ip[0], &ip[1], &ip[2], &ip[3]); + SNDMSG(g_lpInst->hwndCtl1, IPM_SETADDRESS, 0, MAKEIPADDRESS(ip[0], ip[1], ip[2], ip[3])); + ShowWindow(g_lpInst->hwndCtl1, SW_SHOW); + SetFocus(g_lpInst->hwndCtl1); +} + +/// @brief Handle the selection of a listbox item of the following types: +/// PIT_COLOR, PIT_FONT, PIT_FILE, and PIT_FOLDER. +/// +/// @param hwnd The handle of the listbox. +/// @param rc RECT containing desired coordinates for the button control. +/// +/// @returns VOID. +static VOID ListBox_OnDisplayButton(HWND hwnd, RECT rc) +{ + if (WIDTH(rc) > 19) + { + rc.left = rc.right - 19; + rc.right -= 2; + } + + rc.top += 2; + rc.bottom -= 2; + + if (NULL == g_lpInst->hwndCtl1) + g_lpInst->hwndCtl1 = CreateButton(g_lpInst->hInstance, hwnd, ID_BUTTON); + + MoveWindow(g_lpInst->hwndCtl1, rc.left, rc.top, WIDTH(rc), HEIGHT(rc), TRUE); + + ShowWindow(g_lpInst->hwndCtl1, SW_SHOW); + SetFocus(g_lpInst->hwndCtl1); +} + +/// @brief Handle the selection of a listbox item of the following types: +/// PIT_DATE, PIT_TIME, and PIT_DATETIME. +/// +/// @param hwnd The handle of the listbox. +/// @param rc RECT containing desired coordinates for the date or timepicker control(s). +/// @param pItem Pointer to a LISTBOXITEM object. +/// +/// @returns VOID. +static VOID ListBox_OnSelectDateTime(HWND hwnd, RECT rc, LPLISTBOXITEM pItem) +{ + rc.top += 1; + + TCHAR buf[3]; + _tmemset(buf, (TCHAR)0, 3); + + SYSTEMTIME st; + memset(&st, 0, sizeof(SYSTEMTIME)); + + if (PIT_DATE == pItem->iItemType) + { + if (NULL == g_lpInst->hwndCtl1) + g_lpInst->hwndCtl1 = CreateDatePicker(g_lpInst->hInstance, hwnd, ID_DATE, TRUE); + + MoveWindow(g_lpInst->hwndCtl1, rc.left, rc.top, WIDTH(rc), HEIGHT(rc), TRUE); + + if (NULL != pItem) + _stscanf(pItem->lpszCurValue, + _T("%hd/%hd/%hd"), + &st.wMonth, &st.wDay, &st.wYear); + + DateTime_SetSystemtime(g_lpInst->hwndCtl1, GDT_VALID, &st); + + ShowWindow(g_lpInst->hwndCtl1, SW_SHOW); + SetFocus(g_lpInst->hwndCtl1); + } + else if (PIT_TIME == pItem->iItemType) + { + if (NULL == g_lpInst->hwndCtl1) + g_lpInst->hwndCtl1 = CreateDatePicker(g_lpInst->hInstance, hwnd, ID_TIME, FALSE); + + MoveWindow(g_lpInst->hwndCtl1, rc.left, rc.top, WIDTH(rc), HEIGHT(rc), TRUE); + + if (NULL != pItem) + { + //Initialize unused date portion so DTP doesn't default to current date time + GetLocalTime(&st); + _stscanf(pItem->lpszCurValue, + _T("%hd:%hd:%hd %2")_T(STR_SPEC), + &st.wHour, &st.wMinute, &st.wSecond, &buf); + } + if ((0 == _tcsicmp(_T("PM"), buf)) && st.wHour != 12) + st.wHour += 12; + + DateTime_SetSystemtime(g_lpInst->hwndCtl1, GDT_VALID, &st); + + ShowWindow(g_lpInst->hwndCtl1, SW_SHOW); + SetFocus(g_lpInst->hwndCtl1); + } + else if (PIT_DATETIME == pItem->iItemType) + { + RECT rect0, rect1; + rect0 = rect1 = rc; + rect0.right = rect0.left + (rect0.right - rect0.left) / 2; + rect1.left = rect1.left + (rect1.right - rect1.left) / 2; + + if (NULL == g_lpInst->hwndCtl1) + g_lpInst->hwndCtl1 = CreateDatePicker(g_lpInst->hInstance, hwnd, ID_DATE, TRUE); + + MoveWindow(g_lpInst->hwndCtl1, rect0.left, rect0.top, WIDTH(rect0), HEIGHT(rect0), TRUE); + + if (NULL == g_lpInst->hwndCtl2) + g_lpInst->hwndCtl2 = CreateDatePicker(g_lpInst->hInstance, hwnd, ID_TIME, FALSE); + + MoveWindow(g_lpInst->hwndCtl2, rect1.left, rect1.top, WIDTH(rect1), HEIGHT(rect1), TRUE); + + if (NULL != pItem) + _stscanf(pItem->lpszCurValue, + _T("%hd/%hd/%hd %hd:%hd:%hd %2")_T(STR_SPEC), + &st.wMonth, &st.wDay, &st.wYear, &st.wHour, + &st.wMinute, &st.wSecond, &buf); + + if ((0 == _tcsicmp(_T("PM"), buf)) && st.wHour != 12) + st.wHour += 12; + + DateTime_SetSystemtime(g_lpInst->hwndCtl1, GDT_VALID, &st); + DateTime_SetSystemtime(g_lpInst->hwndCtl2, GDT_VALID, &st); + + ShowWindow(g_lpInst->hwndCtl1, SW_SHOW); + ShowWindow(g_lpInst->hwndCtl2, SW_SHOW); + SetFocus(g_lpInst->hwndCtl1); + } +} + +/// @brief Handle the selection of a listbox item of the following types: +/// PIT_COMBO, PIT_EDITCOMBO, PIT_CHECKCOMBO. +/// +/// @param hwnd The handle of the listbox. +/// @param rc RECT containing desired coordinates for the combobox control. +/// @param pItem Pointer to a LISTBOXITEM object. +/// +/// @returns VOID. +static VOID ListBox_OnSelectComboBox(HWND hwnd, RECT rc, LPLISTBOXITEM pItem) +{ + HWND hCombo = NULL; + + rc.top += 1; + rc.bottom += 100; + + if (NULL == g_lpInst->hwndCtl1) + { + g_lpInst->hwndCtl1 = CreateComboBox(g_lpInst->hInstance, hwnd, pItem->iItemType); + } + hCombo = g_lpInst->hwndCtl1; + ComboBox_ResetContent(hCombo); // In case this property item re-edited + MoveWindow(hCombo, rc.left, rc.top, WIDTH(rc), HEIGHT(rc), TRUE); + ShowWindow(hCombo, SW_SHOW); + SetFocus(hCombo); + + INT iIndex; + LPTSTR selectedItem, temp, sep; + selectedItem = temp = sep = NULL; + if (PIT_CHECKCOMBO == pItem->iItemType) + { + selectedItem = _tcstok(temp = NewString(pItem->lpszCurValue), sep = DefaultSeperator()); + } + //Walk the item list and add each string until the empty string + for (LPTSTR p = pItem->lpszMisc; *p; p += _tcslen(p) + 1) + { + iIndex = ComboBox_AddString(hCombo, p); + if (NULL != selectedItem && 0 == _tcsncmp(selectedItem, p, _tcslen(p))) + { + CheckedComboBox_SetCheckState(hCombo, iIndex, TRUE); + selectedItem = _tcstok(NULL, sep); + } + } + if (NULL != temp) + free(temp); + + //Jump to the property's current value in the combo box + INT itm = ComboBox_FindStringExact(hCombo, 0, pItem->lpszCurValue); + if (itm != CB_ERR) + ComboBox_SetCurSel(hCombo, itm); + else + { + ComboBox_SetCurSel(hCombo, 0); + ComboBox_SetText(hCombo, pItem->lpszCurValue); + ComboBox_SetEditSel(hCombo, 0, -1); + } +} + +/// @brief Handles WM_KEYDOWN messages sent to the listbox. +/// +/// @param hwnd Handle of the listbox. +/// @param vk The virtual key code. +/// @param fDown TRUE for keydown (always TRUE). +/// @param cRepeat The number of times the keystroke is repeated +/// as a result of the user holding down the key. +/// @param flags Indicate OEM scan codes etc. +/// +/// @returns VOID. +static VOID ListBox_OnKeyDown(HWND hwnd, UINT vk, BOOL fDown, INT cRepeat, UINT flags) +{ + BOOL fHandled = FALSE; + + if (NULL != g_lpInst->lpCurrent) + { + RECT rc; + memset(&rc, 0, sizeof rc); + ListBox_GetItemRect(hwnd, ListBox_GetCurSel(hwnd), &rc); + + if (PIT_CATALOG == g_lpInst->lpCurrent->iItemType) + { + if (VK_RIGHT == vk && g_lpInst->lpCurrent->fCollapsed || + VK_LEFT == vk && !g_lpInst->lpCurrent->fCollapsed) + { + ToggleCatalog(g_lpInst->lpCurrent); + fHandled = TRUE; + } + else if ((VK_ADD == vk || VK_OEM_PLUS == vk) && g_lpInst->lpCurrent->fCollapsed || + (VK_SUBTRACT == vk || VK_OEM_MINUS == vk) && !g_lpInst->lpCurrent->fCollapsed) + { + ToggleCatalog(g_lpInst->lpCurrent); + fHandled = TRUE; + } + else if (VK_SPACE == vk) + { + ToggleCatalog(g_lpInst->lpCurrent); + fHandled = TRUE; + } + } + if (PIT_CHECK == g_lpInst->lpCurrent->iItemType && (VK_SPACE == vk || VK_RETURN == vk)) + { + if (0 == _tcsicmp(g_lpInst->lpCurrent->lpszMisc, SELECT)) + { + g_lpInst->lpCurrent->lpszCurValue = + (0 == _tcsicmp(g_lpInst->lpCurrent->lpszCurValue, CHECKED) ? + UNCHECKED : CHECKED); + RedrawWindow(hwnd, &rc, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW); + Grid_NotifyParent(); //Notify of check change + } + } + else if (PIT_CHECK == g_lpInst->lpCurrent->iItemType && VK_ESCAPE == vk) + { + g_lpInst->lpCurrent->lpszMisc = UNSELECT; + RedrawWindow(hwnd, &rc, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW); + } + else if (PIT_CHECK == g_lpInst->lpCurrent->iItemType && + VK_LEFT <= vk && vk <= VK_DOWN && 0 == _tcsicmp(g_lpInst->lpCurrent->lpszMisc, SELECT)) + { + fHandled = TRUE; + } + else + { + if (VK_TAB == vk) + { + if (PIT_COLOR == g_lpInst->lpCurrent->iItemType) + { + rc.left = g_lpInst->iHDivider + 1 + WIDTH_PART2; + } + else + { + rc.left = g_lpInst->iHDivider + 1; + } + switch (g_lpInst->lpCurrent->iItemType) + { + case PIT_CATALOG: //Ignore this + case PIT_STATIC: + SetFocusToParent(); + break; + case PIT_EDIT: + ListBox_OnSelectEdit(hwnd, rc, g_lpInst->lpCurrent); + break; + case PIT_COMBO: + case PIT_EDITCOMBO: + case PIT_CHECKCOMBO: + ListBox_OnSelectComboBox(hwnd, rc, g_lpInst->lpCurrent); + break; + case PIT_IP: + ListBox_OnSelectIP(hwnd, rc, g_lpInst->lpCurrent); + break; + case PIT_CHECK: + if (GetKeyState(VK_SHIFT) & 0x8000) + { + FORWARD_WM_KEYDOWN(hwnd, VK_ESCAPE, cRepeat, flags, SNDMSG); + } + else if (0 == _tcsicmp(g_lpInst->lpCurrent->lpszMisc, SELECT)) + { + g_lpInst->lpCurrent->lpszMisc = UNSELECT; + SetFocusToParent(); + } + else + { + g_lpInst->lpCurrent->lpszMisc = SELECT; + } + RedrawWindow(hwnd, &rc, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW); + break; + case PIT_DATE: + case PIT_TIME: + case PIT_DATETIME: + ListBox_OnSelectDateTime(hwnd, rc, g_lpInst->lpCurrent); + break; + default: + ListBox_OnDisplayButton(hwnd, rc); + break; + } + } + else if (VK_PRIOR == vk) + { + FORWARD_WM_KEYDOWN(hwnd, VK_HOME, cRepeat, flags, SNDMSG); + fHandled = TRUE; + } + else if (VK_NEXT == vk) + { + FORWARD_WM_KEYDOWN(hwnd, VK_END, cRepeat, flags, SNDMSG); + fHandled = TRUE; + } + } + } + if (!fHandled) //Not fully handled so follow up with default handler + FORWARD_WM_KEYDOWN(hwnd, vk, cRepeat, flags, DefProc); +} + +/// @brief Handles WM_COMMAND messages sent to the listbox. +/// +/// @param hwnd Handle of listbox. +/// @param id The id of the sender. +/// @param hwndCtl The hwnd of the sender. +/// @param codeNotify The notification code sent. +/// +/// @returns VOID. +static VOID ListBox_OnCommand(HWND hwnd, INT id, HWND hwndCtl, UINT codeNotify) +{ + switch(id) + { + case ID_COMBO: + case ID_EDCOMBO: + case ID_CHKCOMBO: + ComboBoxOwner_OnNotify(hwnd, id, hwndCtl, codeNotify); + break; + case ID_BUTTON: + ButtonOwner_OnNotify(hwnd, id, hwndCtl, codeNotify); + break; + case ID_EDIT: + EditOwner_OnNotify(hwnd, id, hwndCtl, codeNotify); + break; + } +} + +/// @brief Handles WM_NOTIFY messages sent to the listbox. +/// +/// @param hwnd Handle of listbox. +/// @param idFrom The id of the sender. +/// @param pnmhdr A Pointer to NMHDR structure that contains +/// additional information about the notification. +/// +/// @returns LRESULT depends on message. +static LRESULT ListBox_OnNotify(HWND hwnd,INT idFrom, LPNMHDR pnmhdr) +{ + switch(idFrom) + { + case ID_DATE: + case ID_TIME: + if(NM_KILLFOCUS == pnmhdr->code) + { + DatePicker_DoUpdate(g_lpInst->lpCurrent->iItemType); + return TRUE; + } + case ID_IPEDIT: + if(IPN_FIELDCHANGED == pnmhdr->code) + { + IpEdit_OnFieldChanged(hwnd, pnmhdr->hwndFrom); + return TRUE; + } + default: + return FALSE; + } +} + +#pragma endregion list box message handlers + +#pragma region grid message handlers + +/// @brief Handles WM_PAINT message. +/// +/// @param hwnd Handle of grid. +/// +/// @returns VOID. +static void Grid_OnPaint(HWND hwnd) +{ + // Ensure that active editors repaint after the Grid + // receives a paint message + if (g_lpInst->hwndCtl1) + InvalidateRect(g_lpInst->hwndCtl1,NULL, TRUE); + if (g_lpInst->hwndCtl2) + InvalidateRect(g_lpInst->hwndCtl2,NULL, TRUE); + + FORWARD_WM_PAINT(hwnd,DefWindowProc); +} + +/// @brief Handles WM_SIZE message. +/// +/// @param hwnd Handle of grid. +/// @param state Specifies the type of resizing requested. +/// @param cx The width of client area. +/// @param cy The height of client area. +/// +/// @returns VOID. +static VOID Grid_OnSize(HWND hwnd, UINT state, INT cx, INT cy) +{ + //Added to fix possible crash on application exit + // when grid is inside a splitter window - Jakob + if (NULL == g_lpInst) + return; + + g_lpInst->iVDivider = cy - g_lpInst->iDescHeight; + + if (NULL != g_lpInst->hwndPropDesc) + { + //Size listbox component + SetWindowPos(g_lpInst->hwndListBox, NULL, 0, 0, cx, g_lpInst->iVDivider - 2, + SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + + SetWindowPos(g_lpInst->hwndPropDesc, NULL, 0, g_lpInst->iVDivider, + cx, g_lpInst->iDescHeight, SWP_NOZORDER | SWP_NOACTIVATE); + } + else + { + //Size listbox component to fill parent + SetWindowPos(g_lpInst->hwndListBox, NULL, 0, 0, cx, cy, + SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + } + if (NULL != g_lpInst->hwndToolTip) //Resize ToolTips + { + TOOLINFO ti = { sizeof(ti) }; + ti.hwnd = g_lpInst->hwndListBox; + ti.uId = 0; + GetClientRect(hwnd, &ti.rect); + SendMessage(g_lpInst->hwndToolTip, TTM_NEWTOOLRECT, 0, (LPARAM)(LPTOOLINFO)&ti); + } + if (NULL != g_lpInst->lpCurrent) + { + if (PIT_CHECK == g_lpInst->lpCurrent->iItemType) + g_lpInst->lpCurrent->lpszMisc = UNSELECT; //Prevent toggle + } + + FORWARD_WM_COMMAND(hwnd, GetDlgCtrlID(g_lpInst->hwndListBox), + g_lpInst->hwndListBox, LBN_SELCHANGE, SNDMSG); +} + +/// @brief Handles WM_SETCURSOR message. +/// +/// @param hwnd Handle of grid. +/// @param hwndCursor The handle of the cursor. +/// @param codeHitTest The hit test code. +/// @param msg The windows mouse message related to hit test. +/// +/// @par Comments: +/// We are only concerned with detecting the completion of a sizing operation +/// consequently return false and let the default behavior stand. +/// +/// @returns BOOL TRUE if handled. +static BOOL Grid_OnSetCursor(HWND hwnd, HWND hwndCursor, UINT codeHitTest, UINT msg) +{ + if (NULL != g_lpInst->lpCurrent) + { + if (g_lpInst->fTracking) //Sizing operation concluded + { + if (PIT_CHECK == g_lpInst->lpCurrent->iItemType) + g_lpInst->lpCurrent->lpszMisc = SELECT; + + g_lpInst->fTracking = FALSE; + FORWARD_WM_KEYDOWN(g_lpInst->hwndListBox, VK_TAB, 0, 0, SNDMSG); //Focus to editor + } + } + return FALSE; +} + +/// @brief Handles WM_LBUTTONDOWN message. +/// +/// @param hwnd Handle of grid. +/// @param fDoubleClick TRUE if this is a double click event. +/// @param x The xpos of the mouse. +/// @param y The ypos of the mouse. +/// @param keyFlags Set if certain keys down at time of click. +/// +/// @returns VOID. +static VOID Grid_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, INT x, INT y, UINT keyFlags) +{ + if ((y >= g_lpInst->iVDivider - 5) && (y <= g_lpInst->iVDivider + 5)) + { + //If mouse clicked on divider line, then start resizing + SetCursor(LoadCursor(NULL, IDC_SIZENS)); + + RECT rc; + GetWindowRect(hwnd, &rc); + rc.top += 10; + rc.bottom -= 10; + //Do not let mouse leave the list box boundary + ClipCursor(&rc); + + g_lpInst->fTracking = TRUE; + g_lpInst->nDivLft = 0; + g_lpInst->nDivRht = WIDTH(rc); + g_lpInst->nOldDivY = y; + + HDC hdc = GetDC(hwnd); + HPEN hOld = (HPEN)SelectObject(hdc, CreatePen(PS_SOLID, 3, 0)); + + InvertLine(hdc, g_lpInst->nDivLft, g_lpInst->nOldDivY, + g_lpInst->nDivRht, g_lpInst->nOldDivY); + + DeleteObject(SelectObject(hdc, hOld)); + ReleaseDC(hwnd, hdc); + + //Capture the mouse + SetCapture(hwnd); + } +} + +/// @brief Handles WM_LBUTTONUP message. +/// +/// @param hwnd Handle of grid. +/// @param x The xpos of the mouse. +/// @param y The ypos of the mouse. +/// @param keyFlags Set if certain keys down at time of click. +/// +/// @returns VOID. +static VOID Grid_OnLButtonUp(HWND hwnd, INT x, INT y, UINT keyFlags) +{ + if (g_lpInst->fTracking) + { + g_lpInst->fTracking = FALSE; + //If mouse was captured then release it + if (hwnd == GetCapture()) + ReleaseCapture(); + + ClipCursor(NULL); + + //Set the divider position to the new value + g_lpInst->nOldDivY = y; + + HDC hdc = GetDC(hwnd); + HPEN hOld = (HPEN)SelectObject(hdc, CreatePen(PS_SOLID, 3, 0)); + + InvertLine(hdc, g_lpInst->nDivLft, g_lpInst->nOldDivY, + g_lpInst->nDivRht, g_lpInst->nOldDivY); + + DeleteObject(SelectObject(hdc, hOld)); + ReleaseDC(hwnd, hdc); + + //Trigger a resize + RECT rc; + GetClientRect(hwnd, &rc); + g_lpInst->iDescHeight = HEIGHT(rc) - y; + Grid_OnSize(hwnd, 0, WIDTH(rc), HEIGHT(rc)); + } +} + +/// @brief Handles WM_MOUSEMOVE message. +/// +/// @param hwnd Handle of grid. +/// @param x The xpos of the mouse. +/// @param y The ypos of the mouse. +/// @param keyFlags Set if certain keys down at time of move. +/// +/// @returns VOID. +static VOID Grid_OnMouseMove(HWND hwnd, INT x, INT y, UINT keyFlags) +{ + if ((y >= g_lpInst->iVDivider - 5) && (y <= g_lpInst->iVDivider + 5)) + { + SetCursor(LoadCursor(NULL, IDC_SIZENS)); + } + else if (g_lpInst->fTracking) + { + //Move divider line to the mouse pos. if columns are + // currently being resized + HDC hdc = GetDC(hwnd); + HPEN hOld = (HPEN)SelectObject(hdc, CreatePen(PS_SOLID, 3, 0)); + + //Remove old divider line + InvertLine(hdc, g_lpInst->nDivLft, g_lpInst->nOldDivY, + g_lpInst->nDivRht, g_lpInst->nOldDivY); + //Draw new divider line + g_lpInst->nOldDivY = y; + + InvertLine(hdc, g_lpInst->nDivLft, g_lpInst->nOldDivY, + g_lpInst->nDivRht, g_lpInst->nOldDivY); + + DeleteObject(SelectObject(hdc, hOld)); + ReleaseDC(hwnd, hdc); + } + else + { + SetCursor(LoadCursor(NULL, IDC_ARROW)); + } +} + +/// @brief Handles WM_CTLCOLORLISTBOX message sent to the grid. +/// +/// @param hwnd Handle of grid. +/// @param hdc The handle of the device context. +/// @param hwndChild The handle of the listbox. +/// @param type CTLCOLOR_LISTBOX. +/// +/// @returns HBRUSH The handle of the brush used to paint the +/// listbox's background. +static HBRUSH Grid_OnCtlColorListbox(HWND hwnd, HDC hdc, HWND hwndChild, INT type) +{ + return SetColor(hdc, GetSysColor(COLOR_MENUTEXT), GetSysColor(COLOR_3DFACE)); +} + +/// @brief Handles WM_CTLCOLORSTATIC message sent to the grid. +/// +/// @param hwnd Handle of grid. +/// @param hdc The handle of the device context. +/// @param hwndChild The handle of the static. +/// @param type CTLCOLOR_STATIC. +/// +/// @returns HBRUSH The handle of the brush used to paint the +/// static's background. +static HBRUSH Grid_OnCtlColorStatic(HWND hwnd, HDC hdc, HWND hwndChild, INT type) +{ + //Keep the area between the description and the list refreshed + if (NULL != g_lpInst->hwndPropDesc) + { + RECT rc; + GetClientRect(hwnd, &rc); + + HDC hdc = GetDC(hwnd); + FillSolidRect(hdc, MAKE_PRECT(0, g_lpInst->iVDivider - 2, WIDTH(rc), + g_lpInst->iVDivider), GetSysColor(COLOR_BTNFACE)); + ReleaseDC(hwnd, hdc); + } + return FORWARD_WM_CTLCOLORSTATIC(hwnd, hdc, hwndChild, DefWindowProc); +} + +/// @brief Handles WM_MEASUREITEM message sent to the grid when the owner-drawn +/// listbox is created. +/// +/// @param hwnd Handle of grid. +/// @param lpMeasureItem The structure that contains the dimensions of the +/// owner-drawn listbox. +/// +/// @returns VOID. +static VOID Grid_OnMeasureItem(HWND hwnd, LPMEASUREITEMSTRUCT lpMeasureItem) +{ + lpMeasureItem->itemHeight = MINIMUM_ITEM_HEIGHT; //pixels +} + +/// @brief Ensure that a catalog item matching the given catalog name is expanded. +/// +/// @param szCatalog The catalog name. +/// +/// @returns VOID. +static VOID Grid_ExpandCatalog(LPCTSTR szCatalog) +{ + if (NULL != szCatalog) + { + INT ln = _tcslen(szCatalog) + 1; //Check for empty string + if (ln == 1) + return; + } + + for (INT i = 0; i < ListBox_GetCount(g_lpInst->hwndListBox); i++) + { + LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListBox, i); + if (NULL != pItem && PIT_CATALOG == pItem->iItemType && + (NULL == szCatalog || 0 == _tcsicmp(pItem->lpszCatalog, szCatalog))) + { + if (pItem->fCollapsed) + ToggleCatalog(pItem); + } + } +} + +/// @brief Ensure that a catalog item matching the given catalog name is collapsed. +/// +/// @param szCatalog The catalog name. +/// +/// @returns VOID. +static VOID Grid_CollapseCatalog(LPCTSTR szCatalog) +{ + if (NULL != szCatalog) + { + INT ln = _tcslen(szCatalog) + 1; + if (ln == 1) + return; + } + for (INT i = 0; i < ListBox_GetCount(g_lpInst->hwndListBox); i++) + { + LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListBox, i); + if (NULL != pItem && PIT_CATALOG == pItem->iItemType && (NULL == szCatalog || + 0 == _tcsicmp(pItem->lpszCatalog, szCatalog))) + { + if (!pItem->fCollapsed) + ToggleCatalog(pItem); + } + } +} + +/// @brief Handles WM_COMMAND message. +/// +/// @param hwnd Handle of grid. +/// @param id The id of the sender. +/// @param hwndCtl The hwnd of the sender. +/// @param codeNotify The notification code sent. +/// +/// @returns VOID. +static VOID Grid_OnCommand(HWND hwnd, INT id, HWND hwndCtl, UINT codeNotify) +{ + if (g_lpInst->hwndListBox == hwndCtl) + { + switch (codeNotify) + { + case LBN_SELCHANGE: + { + SetFocus(hwndCtl); + INT iCurSel = ListBox_GetCurSel(hwndCtl); + + //If this is a checkbox, notify of the previous item's change + LPLISTBOXITEM pItem = (LPLISTBOXITEM)ListBox_GetItemDataSafe(hwndCtl, + g_lpInst->iPrevSel); + if (NULL != pItem) + { + if (PIT_CHECK == pItem->iItemType) + { + if (iCurSel != g_lpInst->iPrevSel && 0 == _tcsicmp(pItem->lpszMisc, SELECT)) + pItem->lpszMisc = UNSELECT; + } + } + g_lpInst->lpCurrent = (LPLISTBOXITEM)ListBox_GetItemDataSafe(hwndCtl, iCurSel); + if (NULL == g_lpInst->lpCurrent) + return; + + //Display the property description if desired + if (NULL != g_lpInst->hwndPropDesc) + Static_SetText(g_lpInst->hwndPropDesc, g_lpInst->lpCurrent->lpszPropDesc); + + //Destroy previous editor + if (NULL != g_lpInst->hwndCtl1) + { + DestroyWindow(g_lpInst->hwndCtl1); + g_lpInst->hwndCtl1 = NULL; + } + if (NULL != g_lpInst->hwndCtl2) + { + DestroyWindow(g_lpInst->hwndCtl2); + g_lpInst->hwndCtl2 = NULL; + } + + //Redraw the selection + if (iCurSel != g_lpInst->iPrevSel) + { + RECT rc; + memset(&rc, 0, sizeof rc); + + ListBox_GetItemRect(hwndCtl, g_lpInst->iPrevSel, &rc); + RedrawWindow(hwndCtl, &rc, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW); + + ListBox_GetItemRect(hwndCtl, iCurSel, &rc); + RedrawWindow(hwndCtl, &rc, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW); + + g_lpInst->iPrevSel = iCurSel; + } + } + } + } +} + +/// @brief Handles WM_DRAWITEM message sent to the grid when a visual aspect of +/// the owner-drawn listbox has changed. +/// +/// @param hwnd Handle of grid. +/// @param lpDIS The structure that contains information about the item +/// to be drawn and the type of drawing required. +/// +/// @returns VOID. +static VOID Grid_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT *lpDIS) +{ + UINT nIndex = lpDIS->itemID; + + if (lpDIS->hwndItem != g_lpInst->hwndListBox) + return; + + if (nIndex == (UINT) - 1 || !(lpDIS->itemAction & ODA_DRAWENTIRE)) + return; + + //Get the item for the current row + LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(lpDIS->hwndItem, nIndex); + if (NULL == pItem) + return; + + RECT rectFullItem, rectPart0, rectCatPart1, rectPart1, rectPart2, rectPart3; + + rectFullItem = lpDIS->rcItem; + + if (g_lpInst->iHDivider == 0) + g_lpInst->iHDivider = WIDTH(rectFullItem) / 2; + + rectPart0 = rectFullItem; + rectPart0.right = rectPart0.left + WIDTH_PART0; + + rectCatPart1 = rectFullItem; + rectCatPart1.left = rectPart0.right; + + rectPart1 = rectCatPart1; + rectPart1.right = g_lpInst->iHDivider; + + rectPart2 = rectFullItem; + rectPart2.left = g_lpInst->iHDivider + (PIT_CATALOG == pItem->iItemType ? 0 : 1); + if (PIT_COLOR == pItem->iItemType || PIT_CHECK == pItem->iItemType) + { + rectPart2.right = rectPart2.left + WIDTH_PART2; + } + else + rectPart2.right = rectPart2.left; + + rectPart3 = rectFullItem; + rectPart3.left = rectPart2.right; + + // + //First part of item + // + FillRect(lpDIS->hDC, &rectPart0, (HBRUSH)GetStockObject(HOLLOW_BRUSH)); + + if (PIT_CATALOG == pItem->iItemType) //Draw expand / collapse box + { + RECT rect2; + rect2.left = rectPart0.left + 4; + rect2.top = rectPart0.top + 4; + rect2.right = rect2.left + 9; + rect2.bottom = rect2.top + 9; + + FillRect(lpDIS->hDC, &rect2, GetSysColorBrush(COLOR_WINDOW)); + FrameRect(lpDIS->hDC, &rect2, (HBRUSH)GetStockObject(BLACK_BRUSH)); + + POINT ptCtr; + ptCtr.x = (LONG) (rect2.left + (WIDTH(rect2) * 0.5)); + ptCtr.y = (LONG) (rect2.top + (HEIGHT(rect2) * 0.5)); + InflateRect(&rect2, -2, -2); + + DrawLine(lpDIS->hDC, rect2.left, ptCtr.y, rect2.right, ptCtr.y); //Make a - + if (pItem->fCollapsed) //Convert to + + DrawLine(lpDIS->hDC, ptCtr.x, rect2.top, ptCtr.x, rect2.bottom); + } + + // + //Second part of item + // + + //If it is the selected item, set its background-color + HFONT oldFont; + if (PIT_CATALOG == pItem->iItemType) + { + FillRect(lpDIS->hDC, &rectCatPart1, (HBRUSH)GetStockObject(HOLLOW_BRUSH)); + + if (nIndex == (UINT)ListBox_GetCurSel(lpDIS->hwndItem) && g_lpInst->fGotFocus) + { + InflateRect(&rectCatPart1, -2, -2); + FrameRect(lpDIS->hDC, &rectCatPart1, GetSysColorBrush(COLOR_BTNSHADOW)); + InflateRect(&rectCatPart1, 2, 2); + } + + //Write the property name (bold font, Visual Studio style) + oldFont = (HFONT)SelectObject(lpDIS->hDC, Font_SetBold(lpDIS->hwndItem, TRUE)); + SetTextColor(lpDIS->hDC, GetSysColor(COLOR_BTNTEXT)); + DrawText(lpDIS->hDC, pItem->lpszCatalog, _tcslen(pItem->lpszCatalog), + MAKE_PRECT(rectCatPart1.left + 6, rectCatPart1.top + 3, + rectCatPart1.right - 6, rectCatPart1.bottom + 3), + DT_NOCLIP | DT_LEFT | DT_SINGLELINE); + } + else + { + SetBkMode(lpDIS->hDC, TRANSPARENT); + + FillRect(lpDIS->hDC, &rectPart1, + GetSysColorBrush(nIndex == (UINT)ListBox_GetCurSel(lpDIS->hwndItem) ? + (g_lpInst->fGotFocus ? COLOR_HIGHLIGHT : COLOR_BTNFACE) : COLOR_WINDOW)); + + //Write the property name + oldFont = (HFONT)SelectObject(lpDIS->hDC, Font_SetBold(lpDIS->hwndItem, FALSE)); + SetTextColor(lpDIS->hDC, GetSysColor(nIndex == (UINT)ListBox_GetCurSel(lpDIS->hwndItem) ? + (g_lpInst->fGotFocus ? COLOR_HIGHLIGHTTEXT : COLOR_BTNTEXT) : COLOR_WINDOWTEXT)); + DrawText(lpDIS->hDC, pItem->lpszPropName, _tcslen(pItem->lpszPropName), + MAKE_PRECT(rectPart1.left + 3, rectPart1.top + 3, + rectPart1.right - 3, rectPart1.bottom + 3), + DT_NOCLIP | DT_LEFT | DT_SINGLELINE); + + DrawBorder(lpDIS->hDC, &rectPart1, BF_TOPRIGHT, GetSysColor(COLOR_BTNFACE)); + } + DeleteObject(oldFont); + + // + //Third part - Draw color box or check box + // + if (PIT_CATALOG != pItem->iItemType) + { + FillRect(lpDIS->hDC, &rectPart2, GetSysColorBrush(COLOR_WINDOW)); + + RECT rect3 = rectPart2; + rect3.left += 2; + rect3.top += 3; + rect3.bottom = rect3.top + 15; + rect3.right = rect3.left + 15; + + if (PIT_COLOR == pItem->iItemType) + { + FillSolidRect(lpDIS->hDC, &rect3, GetColor(pItem->lpszCurValue)); + FrameRect(lpDIS->hDC, &rect3, (HBRUSH)GetStockObject(BLACK_BRUSH)); + } + else if (PIT_CHECK == pItem->iItemType) + { + if (0 == _tcsicmp(pItem->lpszMisc, SELECT)) + { + FillRect(lpDIS->hDC, &rectPart2, GetSysColorBrush(COLOR_HIGHLIGHT)); + } + DrawFrameControl(lpDIS->hDC, &rect3, DFC_BUTTON, DFCS_BUTTONCHECK | + (_tcsicmp(pItem->lpszCurValue, CHECKED) == 0 ? DFCS_CHECKED : 0)); + + if (FLATCHECKS & (DWORD)GetWindowLongPtr(lpDIS->hwndItem, GWLP_USERDATA)) + { + //Make border thin + FrameRect(lpDIS->hDC, &rect3, GetSysColorBrush(COLOR_BTNFACE)); + InflateRect(&rect3, -1, -1); + FrameRect(lpDIS->hDC, &rect3, GetSysColorBrush(COLOR_WINDOW)); + } + } + } + DrawBorder(lpDIS->hDC, &rectPart2, BF_TOP, GetSysColor(COLOR_BTNFACE)); + + // + //Fourth part - Write the initial property value in the second rectangle + // + if (PIT_CATALOG != pItem->iItemType) + { + FillRect(lpDIS->hDC, &rectPart3, GetSysColorBrush(COLOR_WINDOW)); + } + if (PIT_CHECK != pItem->iItemType) + { + SetBkMode(lpDIS->hDC, TRANSPARENT); + + if (PIT_FONT == pItem->iItemType) + { + TCHAR buf[MAX_PATH] = { 0 }; + PROPGRIDFONTITEM pgfi; + TEXTMETRIC tm; + HFONT hf, hfOld; + LONG PointSize = 0; + + LogFontItem_FromString(&pgfi, pItem->lpszCurValue); + PointSize = -MulDiv(pgfi.logFont.lfHeight, 72, GetDeviceCaps(lpDIS->hDC, LOGPIXELSY)); + + if (0 < PointSize) + { + if (GetTextMetrics(lpDIS->hDC, &tm)) + pgfi.logFont.lfHeight = tm.tmHeight + 2; //So displayed text is not oversized + + hf = (HFONT)CreateFontIndirect(&pgfi.logFont); + hfOld = (HFONT)SelectObject(lpDIS->hDC, hf); + + SetTextColor(lpDIS->hDC, pgfi.crFont); + _stprintf(buf, MAX_PATH, _T("%")_T(STR_SPEC)_T(" %d"), + pgfi.logFont.lfFaceName, PointSize); + DrawText(lpDIS->hDC, buf, _tcslen(buf), + MAKE_PRECT(rectPart3.left + 3, rectPart3.top + 3, + rectPart3.right + 3, rectPart3.bottom + 3), + DT_NOCLIP | DT_LEFT | DT_SINGLELINE); + + DeleteObject(SelectObject(lpDIS->hDC, hfOld)); + } + } + else + { + SetTextColor(lpDIS->hDC, GetSysColor(COLOR_WINDOWTEXT)); + DrawText(lpDIS->hDC, pItem->lpszCurValue, _tcslen(pItem->lpszCurValue), + MAKE_PRECT(rectPart3.left + 3, rectPart3.top + 3, + rectPart3.right + 3, rectPart3.bottom + 3), + DT_NOCLIP | DT_LEFT | DT_SINGLELINE); + } + } + DrawBorder(lpDIS->hDC, &rectPart3, BF_TOP, GetSysColor(COLOR_BTNFACE)); +} + +/// @brief Handles WM_SHOWWINDOW message. +/// +/// @param hwnd Handle of grid. +/// @param fShow Show/hide flag TRUE for show FALSE for hide. +/// @param status Status flag. +/// +/// @returns VOID. +static VOID Grid_OnShowWindow(HWND hwnd, BOOL fShow, UINT status) +{ + if (!fShow) //Hiding so make sure we update the fields + { + FORWARD_WM_CHAR(g_lpInst->hwndCtl1, VK_RETURN, 0, SNDMSG); + FORWARD_WM_CHAR(g_lpInst->hwndCtl2, VK_RETURN, 0, SNDMSG); + } + FORWARD_WM_SHOWWINDOW(hwnd, fShow, status, DefWindowProc); +} + +#pragma endregion grid message handlers + +#pragma region public interface handlers + +/// @brief Handles LB_ADDSTRING message sent to the grid. +/// +/// @param pgi A pointer to a PROPGRIDITEM object. +/// +/// @returns INT The index of the item added to the grid. +static INT Grid_OnAddString(LPPROPGRIDITEM pgi) +{ + LPLISTBOXITEM lpi; + TCHAR buf[MAX_PATH]; + _tmemset(buf, (TCHAR)0, MAX_PATH); + LPTSTR lpszCurValue = _T(""); + + INT idx = ListBox_FindCatalog(g_lpInst->hwndListBox, 0, pgi->lpszCatalog); + if (LB_ERR == idx) // Must add catalog to the listBox + { + lpi = NewItem(pgi->lpszCatalog, _T(""), _T(""), _T(""), _T(""), PIT_CATALOG, NULL); + lpi->fCollapsed = TRUE; + ListBox_AddItemData(g_lpInst->hwndListBox, lpi); + } + switch (pgi->iItemType) + { + case PIT_CATALOG: //We explicitly added a catalog item to the listbox + return -2; // catalogs are not added to the list map so skip the rest + case PIT_EDIT: + case PIT_STATIC: + case PIT_COMBO: + case PIT_EDITCOMBO: + case PIT_CHECKCOMBO: + case PIT_FOLDER: + lpszCurValue = (LPTSTR)pgi->lpCurValue; + break; + case PIT_COLOR: + _stprintf(buf, MAX_PATH, + _T("%d,%d,%d"), + GetRValue((COLORREF)pgi->lpCurValue), + GetGValue((COLORREF)pgi->lpCurValue), + GetBValue((COLORREF)pgi->lpCurValue)); + lpszCurValue = buf; + break; + case PIT_FONT: + lpszCurValue = LogFontItem_ToString((LPPROPGRIDFONTITEM)pgi->lpCurValue); + break; + case PIT_CHECK: + lpszCurValue = (BOOL)pgi->lpCurValue ? CHECKED : UNCHECKED; + break; + case PIT_FILE: + pgi->lpszzCmbItems = FileDialogItem_ToString((LPPROPGRIDFDITEM)pgi->lpCurValue); + lpszCurValue = ((LPPROPGRIDFDITEM)pgi->lpCurValue)->lpszFilePath; + break; + case PIT_IP: + _stprintf(buf, MAX_PATH, + _T("%d.%d.%d.%d"), + FIRST_IPADDRESS((DWORD)pgi->lpCurValue), + SECOND_IPADDRESS((DWORD)pgi->lpCurValue), + THIRD_IPADDRESS((DWORD)pgi->lpCurValue), + FOURTH_IPADDRESS((DWORD)pgi->lpCurValue)); + lpszCurValue = buf; + break; + case PIT_DATE: + GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, + (LPSYSTEMTIME)pgi->lpCurValue, NULL, buf, MAX_PATH); + lpszCurValue = buf; + break; + case PIT_TIME: + GetTimeFormat(LOCALE_USER_DEFAULT, 0, (LPSYSTEMTIME)pgi->lpCurValue, + _T("hh':'mm':'ss tt"), buf, MAX_PATH); + lpszCurValue = buf; + break; + case PIT_DATETIME: + GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, (LPSYSTEMTIME)pgi->lpCurValue, + NULL, buf, MAX_PATH); + _tcscat(buf, _T(" ")); + GetTimeFormat(LOCALE_USER_DEFAULT, 0, (LPSYSTEMTIME)pgi->lpCurValue, + _T("hh':'mm':'ss tt"), (LPTSTR) (buf + _tcslen(buf)), MAX_PATH - _tcslen(buf)); + lpszCurValue = buf; + break; + } + lpi = NewItem(pgi->lpszCatalog, pgi->lpszPropName, lpszCurValue, + pgi->lpszzCmbItems, pgi->lpszPropDesc, pgi->iItemType, pgi->lpUserData); + + return ListBox_AddItemData(g_lpInst->hwndListMap, lpi); +} + +/// @brief Handles LB_DELETESTRING message sent to the grid. +/// +/// @param nIndex The index of the item to delete. +/// +/// @returns INT A count of the items remaining in the grid. The return value is +/// LB_ERR if the index parameter specifies an index greater than +/// the number of items in the grid. +static INT Grid_OnDeleteString(INT nIndex) +{ + LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListMap, nIndex); + if (NULL == pItem) + return LB_ERR; + + INT iItem = ListBox_FindItemData(g_lpInst->hwndListBox, 0, pItem); + if (LB_ERR != iItem) + ListBox_DeleteString(g_lpInst->hwndListBox, iItem); + + return ListBox_DeleteString(g_lpInst->hwndListMap, nIndex); +} + +/// @brief Handles LB_GETCURSEL message sent to the grid. +/// +/// @returns INT The zero-based index of the selected item. If there is no +/// selection, the return value is LB_ERR. +static INT Grid_OnGetCurSel(VOID) +{ + INT iItem = ListBox_GetCurSel(g_lpInst->hwndListBox); + if (LB_ERR != iItem) + { + LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListBox, iItem); + if (NULL != pItem) + { + if (PIT_CATALOG != pItem->iItemType) + return ListBox_FindItemData(g_lpInst->hwndListMap, 0, pItem); + } + } + return LB_ERR; +} + +/// @brief Handles LB_GETITEMDATA message sent to the grid. +/// +/// @param iItem The zero-based index of the item. +/// +/// @returns LRESULT In this case a pointer to a PROPGRIDITEM object. +static LRESULT Grid_OnGetItemData(INT iItem) +{ + static PROPGRIDITEM pgi; + static PROPGRIDFONTITEM pgfi; + static PROPGRIDFDITEM pgfdi; + static SYSTEMTIME st = { 0 }; + static TCHAR outbuf[MAX_PATH]; + + LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListMap, iItem); + if (NULL != pItem) + { + if (PIT_CATALOG != pItem->iItemType) + { + pgi.lpszCatalog = pItem->lpszCatalog; + pgi.lpszPropName = pItem->lpszPropName; + pgi.lpszzCmbItems = pItem->lpszMisc; + pgi.lpszPropDesc = pItem->lpszPropDesc; + pgi.iItemType = pItem->iItemType; + pgi.lpUserData = pItem->lpUserData; + + switch (pgi.iItemType) + { + case PIT_EDIT: + case PIT_COMBO: + case PIT_EDITCOMBO: + case PIT_CHECKCOMBO: + case PIT_STATIC: + case PIT_FOLDER: + pgi.lpCurValue = (LPARAM)pItem->lpszCurValue; + break; + case PIT_COLOR: + pgi.lpCurValue = (LPARAM)GetColor(pItem->lpszCurValue); + break; + case PIT_FONT: + LogFontItem_FromString(&pgfi, pItem->lpszCurValue); + pgi.lpCurValue = (LPARAM) & pgfi; + break; + case PIT_CHECK: + pgi.lpCurValue = (LPARAM) (0 == _tcsicmp(CHECKED, pItem->lpszCurValue)); + break; + case PIT_FILE: + FileDialogItem_FromString(&pgfdi, pgi.lpszzCmbItems); + pgi.lpCurValue = (LPARAM) & pgfdi; + break; + case PIT_IP: + { + DWORD ip[4] = { 0, 0, 0, 0 }; + _stscanf(pItem->lpszCurValue, + _T("%hhd.%hhd.%hhd.%hhd"), + &ip[0], &ip[1], &ip[2], &ip[3]); + pgi.lpCurValue = (LPARAM)MAKEIPADDRESS(ip[0], ip[1], ip[2], ip[3]); + } + break; + case PIT_DATE: + memset(&st, 0, sizeof(SYSTEMTIME)); + _stscanf(pItem->lpszCurValue, + _T("%hd/%hd/%hd"), + &st.wMonth, &st.wDay, &st.wYear); + pgi.lpCurValue = (LPARAM) &st; + break; + case PIT_TIME: + memset(&st, 0, sizeof(SYSTEMTIME)); + _tmemset(outbuf, (TCHAR)0, MAX_PATH); + _stscanf(pItem->lpszCurValue, + _T("%hd:%hd:%hd %2")_T(STR_SPEC), + &st.wHour, &st.wMinute, &st.wSecond, (LPTSTR) &outbuf); + if ((0 == _tcsicmp(_T("PM"), outbuf)) && st.wHour != 12) + st.wHour += 12; + + pgi.lpCurValue = (LPARAM) & st; + break; + case PIT_DATETIME: + memset(&st, 0, sizeof(SYSTEMTIME)); + _tmemset(outbuf, (TCHAR)0, MAX_PATH); + _stscanf(pItem->lpszCurValue, + _T("%hd/%hd/%hd %hd:%hd:%hd %2")_T(STR_SPEC), + &st.wMonth, &st.wDay, &st.wYear, &st.wHour, + &st.wMinute, &st.wSecond, (LPTSTR) &outbuf); + if ((0 == _tcsicmp(_T("PM"), outbuf)) && st.wHour != 12) + st.wHour += 12; + + pgi.lpCurValue = (LPARAM) & st; + break; + } + } + return (LRESULT) & pgi; + } + else + return LB_ERR; +} + +/// @brief Handles LB_GETSEL message sent to the grid. +/// +/// @param iItem The zero-based index of the item. +/// +/// @returns BOOL TRUE if the item is selected, FALSE if not, or LB_ERR +/// if an error occurs. +static BOOL Grid_OnGetSel(INT iItem) +{ + LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListMap, iItem); + if (NULL != pItem) + { + if (PIT_CATALOG != pItem->iItemType) + { + INT i = ListBox_FindItemData(g_lpInst->hwndListBox, 0, pItem); + if (LB_ERR != i) + return ListBox_GetSel(g_lpInst->hwndListBox, i); + else + return FALSE; //Collapsed Not selected + } + } + return LB_ERR; +} + +/// @brief Handles LB_RESETCONTENT message sent to the grid. +/// +/// @returns VOID. +static VOID Grid_OnResetContent(VOID) +{ + //Collapse all open catalogs to avoid freeing memory twice. + Grid_CollapseCatalog(NULL); + ListBox_ResetContent(g_lpInst->hwndListBox); + ListBox_ResetContent(g_lpInst->hwndListMap); + g_lpInst->lpCurrent = NULL; + + if (NULL != g_lpInst->hwndCtl1) + { + DestroyWindow(g_lpInst->hwndCtl1); + g_lpInst->hwndCtl1 = NULL; + } + if (NULL != g_lpInst->hwndCtl2) + { + DestroyWindow(g_lpInst->hwndCtl2); + g_lpInst->hwndCtl2 = NULL; + } + //Clear the property pane + Static_SetText(g_lpInst->hwndPropDesc, _T("")); +} + +/// @brief Handles LB_SETCURSEL message sent to the grid. +/// +/// @param iItem The zero-based index of the item to select, or –1 to clear the selection. +/// +/// @returns LRESULT If an error occurs, the return value is LB_ERR. +/// If the index parameter is –1, the return value is LB_ERR +/// even though no error occurred. +static LRESULT Grid_OnSetCurSel(INT iItem) +{ + LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListMap, iItem); + if (NULL != pItem) + { + if (PIT_CATALOG != pItem->iItemType) + { + INT i = ListBox_FindItemData(g_lpInst->hwndListBox, 0, pItem); + if (LB_ERR != i) + { + INT ret = ListBox_SetCurSel(g_lpInst->hwndListBox, i); + FORWARD_WM_COMMAND(GetParent(g_lpInst->hwndListBox), + GetDlgCtrlID(g_lpInst->hwndListBox), g_lpInst->hwndListBox, + LBN_SELCHANGE, SNDMSG); + + if (PIT_CHECK == pItem->iItemType) + pItem->lpszMisc = SELECT; + + Refresh(g_lpInst->hwndListBox); + return ret; + } + } + } + return LB_ERR; +} + +/// @brief Handles LB_ADDSTRING message sent to the grid. +/// +/// @param iItem The zero-based index of the item. +/// @param pgi A pointer to a PROPGRIDITEM object. +/// +/// @returns LRESULT The return value is the zero-based index of the item in +/// the list. If an error occurs, the return value is LB_ERR. +/// If there is insufficient space to store the data, +/// the return value is LB_ERRSPACE. +static LRESULT Grid_OnSetItemData(INT iItem, LPPROPGRIDITEM pgi) +{ + if (PIT_CATALOG == pgi->iItemType) + return LB_ERR; //Can't change an item to a catalog + + LRESULT lrtn = LB_ERR; + LPLISTBOXITEM lpiCurrent, lpiNew; + TCHAR buf[MAX_PATH]; + _tmemset(buf, (TCHAR)0, MAX_PATH); + + LPTSTR lpszCurValue = _T(""); + + lpiCurrent = ListBox_GetItemDataSafe(g_lpInst->hwndListMap, iItem); + if (NULL != lpiCurrent) + { + //Do not allow the catalog to change; to do so would necessitate moving + // the item to a different index throwing off the indexing for items. + switch (pgi->iItemType) + { + case PIT_EDIT: + case PIT_STATIC: + case PIT_COMBO: + case PIT_EDITCOMBO: + case PIT_CHECKCOMBO: + case PIT_FOLDER: + lpszCurValue = (LPTSTR)pgi->lpCurValue; + break; + case PIT_COLOR: + _stprintf(buf, MAX_PATH, + _T("%d,%d,%d"), + GetRValue((COLORREF)pgi->lpCurValue), + GetGValue((COLORREF)pgi->lpCurValue), + GetBValue((COLORREF)pgi->lpCurValue)); + lpszCurValue = buf; + break; + case PIT_FONT: + lpszCurValue = LogFontItem_ToString((LPPROPGRIDFONTITEM)pgi->lpCurValue); + break; + case PIT_CHECK: + lpszCurValue = (BOOL)pgi->lpCurValue ? CHECKED : UNCHECKED; + break; + case PIT_FILE: + pgi->lpszzCmbItems = FileDialogItem_ToString((LPPROPGRIDFDITEM)pgi->lpCurValue); + lpszCurValue = ((LPPROPGRIDFDITEM)pgi->lpCurValue)->lpszFilePath; + break; + case PIT_IP: + _stprintf(buf, MAX_PATH, + _T("%d.%d.%d.%d"), + FIRST_IPADDRESS((DWORD)pgi->lpCurValue), + SECOND_IPADDRESS((DWORD)pgi->lpCurValue), + THIRD_IPADDRESS((DWORD)pgi->lpCurValue), + FOURTH_IPADDRESS((DWORD)pgi->lpCurValue)); + lpszCurValue = buf; + break; + case PIT_DATE: + GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, (LPSYSTEMTIME)pgi->lpCurValue, + NULL, buf, MAX_PATH); + lpszCurValue = buf; + break; + case PIT_TIME: + GetTimeFormat(LOCALE_USER_DEFAULT, 0, (LPSYSTEMTIME)pgi->lpCurValue, + _T("hh':'mm':'ss tt"), buf, MAX_PATH); + lpszCurValue = buf; + break; + case PIT_DATETIME: + GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, (LPSYSTEMTIME)pgi->lpCurValue, + NULL, buf, MAX_PATH); + _tcscat(buf, _T(" ")); + GetTimeFormat(LOCALE_USER_DEFAULT, 0, (LPSYSTEMTIME)pgi->lpCurValue, + _T("hh':'mm':'ss tt"), (LPTSTR) (buf + _tcslen(buf)), MAX_PATH - _tcslen(buf)); + lpszCurValue = buf; + break; + } + lpiNew = NewItem(lpiCurrent->lpszCatalog, pgi->lpszPropName, lpszCurValue, + pgi->lpszzCmbItems, pgi->lpszPropDesc, pgi->iItemType, pgi->lpUserData); + + lrtn = ListBox_SetItemData(g_lpInst->hwndListMap, iItem, lpiNew); + if (LB_ERR != lrtn) + { + lrtn = ListBox_FindItemData(g_lpInst->hwndListBox, 0, lpiCurrent); + if (LB_ERR != lrtn) + { + lrtn = ListBox_SetItemData(g_lpInst->hwndListBox, lrtn, lpiNew); + Refresh(g_lpInst->hwndListBox); + } + } + if (LB_ERR == lrtn) + return LB_ERR; + + //Reset lpCurrent if necessary + if (lpiCurrent == g_lpInst->lpCurrent) + g_lpInst->lpCurrent = lpiNew; + + DeleteItem(lpiCurrent); + } + return lrtn; +} + +#pragma endregion public interface handlers + +#pragma region create destroy + +/// @brief Handles WM_CREATE message. +/// +/// @param hwnd Handle of grid. +/// @param lpCreateStruct Pointer to a structure with creation data. +/// +/// @returns BOOL If an application processes this message, +/// it should return TRUE to continue creation of the window. +static BOOL Grid_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) +{ + INSTANCEDATA inst; + memset(&inst, 0, sizeof(INSTANCEDATA)); + + inst.hInstance = lpCreateStruct->hInstance; + inst.hwndParent = lpCreateStruct->hwndParent; + inst.fXpOrLower = FALSE; + inst.fGotFocus = FALSE; + inst.fScrolling = FALSE; + inst.fTracking = FALSE; + inst.iDescHeight = HEIGHT_DESC; + inst.iPrevSel = LB_ERR; //Nothing selected + + //Create the listbox control + inst.hwndListBox = CreateListBox(lpCreateStruct->hInstance, hwnd, ID_LISTBOX); + if (NULL == inst.hwndListBox) + return FALSE; + + //And the hidden list map + inst.hwndListMap = CreateListMap(lpCreateStruct->hInstance, hwnd, ID_LISTMAP); + if (NULL == inst.hwndListMap) + return FALSE; + + return Control_CreateInstanceData(hwnd, &inst); +} + +/// @brief Handles WM_DESTROY message. +/// +/// @param hwnd Handle of Grid. +/// +/// @returns VOID. +static VOID Grid_OnDestroy(HWND hwnd) +{ + if (NULL != g_lpInst) + { + PropGrid_ResetContent(hwnd); + DestroyWindow(g_lpInst->hwndListMap); + DestroyWindow(g_lpInst->hwndListBox); + + if (NULL != g_lpInst->hwndToolTip) + DestroyWindow(g_lpInst->hwndToolTip); + if (NULL != g_lpInst->hwndPropDesc) + DestroyWindow(g_lpInst->hwndPropDesc); + if (NULL != g_lpInst->hwndCtl1) + DestroyWindow(g_lpInst->hwndCtl1); + if (NULL != g_lpInst->hwndCtl2) + DestroyWindow(g_lpInst->hwndCtl2); + + Control_FreeInstanceData(hwnd); + } +} + +/// @brief Initialize and register the property grid class. +/// +/// @param hInstance Handle of application instance. +/// +/// @returns ATOM If the function succeeds, the atom that uniquely identifies +/// the class being registered, otherwise 0. +ATOM InitPropertyGrid(HINSTANCE hInstance) +{ + WNDCLASSEX wcex; + wcex.cbSize = sizeof(wcex); + if (!GetClassInfoEx(NULL, WC_LISTBOX, &wcex)) + return 0; + + wcex.lpfnWndProc = (WNDPROC)Grid_Proc;; + wcex.hInstance = hInstance; + wcex.lpszClassName = WC_PROPERTYGRIDCTL; + return RegisterClassEx(&wcex); +} + +/// @brief Create an new instance of the property grid. +/// +/// @param hParent Handle of the grid's parent. +/// @param dwID The ID for this control. +/// +/// @returns HWND If the function succeeds, the grid handle, otherwise NULL. +HWND New_PropertyGrid(HWND hParent, DWORD dwID) +{ + static ATOM aPropertyGrid = 0; + static HWND hPropertyGrid; + + HINSTANCE hInstance = (HINSTANCE)GetWindowLongPtr(hParent, GWLP_HINSTANCE); + DWORD dwStyle, dwExStyle; + + //Only need to register the property grid once + if (!aPropertyGrid) + aPropertyGrid = InitPropertyGrid(hInstance); + + dwStyle = WS_CHILD | WS_TABSTOP | WS_VISIBLE; + dwExStyle = WS_EX_LEFT; + + hPropertyGrid = CreateWindowEx( + dwExStyle, + WC_PROPERTYGRIDCTL, + NULL, + dwStyle, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + hParent, + (HMENU)dwID, + hInstance, + NULL); + + return hPropertyGrid; +} + +#pragma endregion create destroy + +/// @brief Send a PGN_PROPERTYCHANGE notification to grid's parent. +/// +/// @returns VOID. +static VOID Grid_NotifyParent(VOID) +{ + static NMPROPGRID nmPropGrid; + + if (NULL != g_lpInst->lpCurrent && + PIT_CATALOG != g_lpInst->lpCurrent->iItemType && + PIT_STATIC != g_lpInst->lpCurrent->iItemType) + { + nmPropGrid.hdr.hwndFrom = GetParent(g_lpInst->hwndListBox); + nmPropGrid.hdr.idFrom = GetDlgCtrlID(nmPropGrid.hdr.hwndFrom); + nmPropGrid.hdr.code = PGN_PROPERTYCHANGE; + + LRESULT lres = ListBox_FindItemData(g_lpInst->hwndListMap, 0, g_lpInst->lpCurrent); + nmPropGrid.iIndex = LB_ERR == lres ? -1 : lres; + FORWARD_WM_NOTIFY(g_lpInst->hwndParent, nmPropGrid.hdr.idFrom, &nmPropGrid, SNDMSG); + } +} + +/// @brief Window procedure for the visible owner-drawn listbox control. +/// +/// @param hList Handle of listbox. +/// @param msg Which message? +/// @param wParam Message parameter. +/// @param lParam Message parameter. +/// +/// @returns LRESULT depends on message. +static LRESULT CALLBACK ListBox_Proc(HWND hList, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HWND hParent = GetParent(hList); + + // Note: Instance data is attached to listbox's parent + Control_GetInstanceData(hParent, &g_lpInst); + + switch (msg) + { + HANDLE_MSG(hList, WM_COMMAND, ListBox_OnCommand); + HANDLE_MSG(hList, WM_LBUTTONDOWN, ListBox_OnLButtonDown); + HANDLE_MSG(hList, WM_LBUTTONDBLCLK, ListBox_OnLButtonDown); + HANDLE_MSG(hList, WM_LBUTTONUP, ListBox_OnLButtonUp); + HANDLE_MSG(hList, WM_MOUSEMOVE, ListBox_OnMouseMove); + HANDLE_MSG(hList, WM_NOTIFY, ListBox_OnNotify); + HANDLE_MSG(hList, WM_KEYDOWN, ListBox_OnKeyDown); + HANDLE_MSG(hList, WM_KILLFOCUS, ListBox_OnKillFocus); + HANDLE_MSG(hList, WM_DRAWITEM, ComboBoxOwner_OnDrawItem); + HANDLE_MSG(hList, WM_MEASUREITEM, ComboBoxOwner_OnMeasureItem); + + case WM_SETFOCUS: + g_lpInst->fGotFocus = TRUE; + Refresh(g_lpInst->hwndListBox); + return 0; + + case WM_MBUTTONDOWN: + case WM_NCLBUTTONDOWN: + //The listbox doesn't have a scrollbar component, it draws a scroll + // bar in the non-client area of the control. A mouse click in the + // non-client area then, equals clicking on a scroll bar. A click + // on the middle mouse button equals pan, we'll handle that as if + // it were a scroll event. + ListBox_OnBeginScroll(hList); + g_lpInst->fScrolling = TRUE; + break; + + case WM_SETCURSOR: + //Whenever the mouse leaves the non-client area of a listbox, it + // fires a WM_SETCURSOR message. The same happens when the middle + // mouse button is released. We can use this behavior to detect the + // completion of a scrolling operation. + if (g_lpInst->fScrolling) + { + ListBox_OnEndScroll(hList); + g_lpInst->fScrolling = FALSE; + } + break; + + case WM_MOUSEWHEEL: + ListBox_OnBeginScroll(hList);// Hide editors + //Update the fields + FORWARD_WM_CHAR(g_lpInst->hwndCtl1, VK_RETURN, 0, SNDMSG); + FORWARD_WM_CHAR(g_lpInst->hwndCtl2, VK_RETURN, 0, SNDMSG); + break; + + case WM_GETDLGCODE: + return DLGC_WANTALLKEYS; + + case WM_DESTROY: //Unsubclass the listbox Control + { + SetWindowLongPtr(hList, GWLP_WNDPROC, (DWORD_PTR)GetProp(hList, WPRC)); + RemoveProp(hList, WPRC); + return 0; + } + } + return DefProc(hList, msg, wParam, lParam); +} + +/// @brief Window procedure and public interface for the property grid. +/// +/// @param hwnd Handle of grid. +/// @param msg Which message? +/// @param wParam Message parameter. +/// @param lParam Message parameter. +/// +/// @returns LRESULT depends on message. +static LRESULT CALLBACK Grid_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + Control_GetInstanceData(hwnd, &g_lpInst); //Update the instance pointer + + switch (msg) + { + HANDLE_MSG(hwnd, WM_COMMAND, Grid_OnCommand); + HANDLE_MSG(hwnd, WM_CREATE, Grid_OnCreate); + HANDLE_MSG(hwnd, WM_CTLCOLORLISTBOX, Grid_OnCtlColorListbox); + HANDLE_MSG(hwnd, WM_CTLCOLORSTATIC, Grid_OnCtlColorStatic); + HANDLE_MSG(hwnd, WM_DESTROY, Grid_OnDestroy); + HANDLE_MSG(hwnd, WM_DELETEITEM, Grid_OnDeleteItem); + HANDLE_MSG(hwnd, WM_DRAWITEM, Grid_OnDrawItem); + HANDLE_MSG(hwnd, WM_LBUTTONDOWN, Grid_OnLButtonDown); + HANDLE_MSG(hwnd, WM_LBUTTONUP, Grid_OnLButtonUp); + HANDLE_MSG(hwnd, WM_MEASUREITEM, Grid_OnMeasureItem); + HANDLE_MSG(hwnd, WM_MOUSEMOVE, Grid_OnMouseMove); + HANDLE_MSG(hwnd, WM_SETCURSOR, Grid_OnSetCursor); + HANDLE_MSG(hwnd, WM_SHOWWINDOW, Grid_OnShowWindow); + HANDLE_MSG(hwnd, WM_SIZE, Grid_OnSize); + HANDLE_MSG(hwnd, WM_PAINT, Grid_OnPaint); + + case WM_SETFOCUS: + SetFocus(g_lpInst->hwndListBox); + return 0; + case LB_ADDSTRING: //PropGrid_AddItem + return Grid_OnAddString((LPPROPGRIDITEM)lParam); + case LB_DELETESTRING: //PropGrid_DeleteItem + return Grid_OnDeleteString((INT)wParam); + case LB_GETCOUNT: //PropGrid_GetCount + return ListBox_GetCount(g_lpInst->hwndListMap); + case LB_GETCURSEL: //PropGrid_GetCurSel + return Grid_OnGetCurSel(); + case LB_GETHORIZONTALEXTENT: //PropGrid_GetHorizontalExtent + return ListBox_GetHorizontalExtent(g_lpInst->hwndListBox); + case LB_GETITEMDATA: //PropGrid_GetItemData + return Grid_OnGetItemData((INT)wParam); + case LB_GETITEMHEIGHT: //PropGrid_GetItemHeight + return ListBox_GetItemHeight(g_lpInst->hwndListBox, wParam); + case LB_GETITEMRECT: //PropGrid_GetItemRect + return ListBox_GetItemRect(g_lpInst->hwndListBox, wParam, lParam); + case LB_GETSEL: //PropGrid_GetSel + return Grid_OnGetSel((INT)wParam); + case LB_RESETCONTENT: //PropGrid_ResetContent + Grid_OnResetContent(); + break; + case LB_SETCURSEL: //PropGrid_SetCurSel + return Grid_OnSetCurSel((INT)wParam); + case LB_SETHORIZONTALEXTENT: //PropGrid_SetHorizontalExtent + ListBox_SetHorizontalExtent(g_lpInst->hwndListBox, wParam); + break; + case LB_SETITEMDATA: //PropGrid_SetItemData + return Grid_OnSetItemData((INT)wParam, (LPPROPGRIDITEM)lParam); + case LB_SETITEMHEIGHT: //PropGrid_SetItemHeight + { + if (MINIMUM_ITEM_HEIGHT > LOWORD(lParam)) + return LB_ERR; + LRESULT lres = SNDMSG(g_lpInst->hwndListBox, msg, wParam, lParam); + Refresh(g_lpInst->hwndListBox); + return lres; + } + case PG_FLATCHECKS: + { + DWORD dwUserData = (DWORD)GetWindowLongPtr(g_lpInst->hwndListBox, GWLP_USERDATA); + if (FALSE != (BOOL)wParam) + dwUserData |= FLATCHECKS; + else + dwUserData &= ~FLATCHECKS; + + return SetWindowLongPtr(g_lpInst->hwndListBox, GWLP_USERDATA, (LONG_PTR)dwUserData); + } + case PG_EXPANDCATALOGS: + { + if (NULL == (LPTSTR)lParam) //Expand all + { + Grid_ExpandCatalog(NULL); + } + else + { + //Walk the catalog list and handle each string until the empty string + for (LPTSTR p = (LPTSTR)lParam; *p; p += _tcslen(p) + 1) + Grid_ExpandCatalog((LPCTSTR)p); + } + } + break; + case PG_COLLAPSECATALOGS: + { + if (NULL == (LPTSTR)lParam) //Collapse all + { + Grid_CollapseCatalog(NULL); + } + else + { + for (LPTSTR p = (LPTSTR)lParam; *p; p += _tcslen(p) + 1) + Grid_CollapseCatalog((LPCTSTR)p); + } + } + break; + case PG_SHOWTOOLTIPS: + { + if ((BOOL)wParam) + { + if (NULL == g_lpInst->hwndToolTip) + { + g_lpInst->hwndToolTip = CreateToolTip(g_lpInst->hInstance, + g_lpInst->hwndListBox); + + if (NULL != g_lpInst->hwndToolTip) + { + TOOLINFO ti = { sizeof(ti) }; + ti.hwnd = g_lpInst->hwndListBox; + ti.uId = 0; + GetClientRect(hwnd, &ti.rect); + SendMessage(g_lpInst->hwndToolTip, TTM_NEWTOOLRECT, 0, (LPARAM)(LPTOOLINFO)&ti); + + ShowWindow(g_lpInst->hwndToolTip, SW_SHOW); + } + } + } + else + { + DestroyWindow(g_lpInst->hwndToolTip); + g_lpInst->hwndToolTip = NULL; + } + } + break; + case PG_SHOWPROPERTYDESC: + { + if ((BOOL)wParam) + { + if (NULL == g_lpInst->hwndPropDesc) + g_lpInst->hwndPropDesc = CreateStatic(g_lpInst->hInstance, hwnd, ID_PROPDESC); + + ShowWindow(g_lpInst->hwndPropDesc, SW_SHOW); + } + else + { + DestroyWindow(g_lpInst->hwndPropDesc); + g_lpInst->hwndPropDesc = NULL; + } + //Position and display the control + RECT rc; + GetClientRect(hwnd, &rc); + Grid_OnSize(hwnd, 0, WIDTH(rc), HEIGHT(rc)); + } + break; + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} diff --git a/src/win32/propertygridimpl.h b/src/win32/propertygridimpl.h new file mode 100644 index 0000000..ef61e43 --- /dev/null +++ b/src/win32/propertygridimpl.h @@ -0,0 +1,414 @@ +////////////////////////////////////////////////////////////////////////////// +/// +/// @file propertyGrid.h +/// +/// @brief A property grid control in Win32 SDK C. +/// +/// @author David MacDermot +/// +/// @par Comments: +/// This source is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +/// +/// @date 4-16-21 +/// +/// @version 2.3 +/// +/// @todo +/// +/// @bug +/// +////////////////////////////////////////////////////////////////////////////// + +#ifndef PROPERTYGRID_H +#define PROPERTYGRID_H + +#define WC_PROPERTYGRIDCTLA "PropGridCtl" +#define WC_PROPERTYGRIDCTLW L"PropGridCtl" + +#ifdef UNICODE +#define WC_PROPERTYGRIDCTL WC_PROPERTYGRIDCTLW +#else +#define WC_PROPERTYGRIDCTL WC_PROPERTYGRIDCTLA +#endif + +/****************************************************************************/ +// Public Messages + +// List box message subset handled by the property grid. + +// LB_ADDSTRING - PropGrid_AddItem() +// LB_DELETESTRING - PropGrid_DeleteItem() +// LB_GETCOUNT - PropGrid_GetCount() +// LB_GETCURSEL - PropGrid_GetCurSel() +// LB_GETHORIZONTALEXTENT - PropGrid_GetHorizontalExtent() +// LB_GETITEMDATA - PropGrid_GetItemData() +// LB_GETITEMHEIGHT - PropGrid_GetItemHeight() +// LB_GETITEMRECT - PropGrid_GetItemRect() +// LB_GETSEL - PropGrid_GetSel() +// LB_RESETCONTENT - PropGrid_ResetContent() +// LB_SETCURSEL - PropGrid_SetCurSel() +// LB_SETHORIZONTALEXTENT - PropGrid_SetHorizontalExtent() +// LB_SETITEMDATA - PropGrid_SetItemData() +// LB_SETITEMHEIGHT - PropGrid_SetItemHeight() + +/// @name Property grid specific messages. +/// @{ +#define PG_EXPANDCATALOGS WM_USER + 0x01 ///= (left) && (pt).y >= (top) && (pt).x < (right) && (pt).y < (bottom)) + +#define RECTWIDTH(prc) ((prc)->right - (prc)->left) +#define RECTHEIGHT(prc) ((prc)->bottom - (prc)->top) + void Win32_FetchUIFont(); HFONT Win32_GetUIFont(); void Win32_ApplyUIFont(HWND hWnd); @@ -13,3 +18,10 @@ void swapf(float* a, float* b); #define COLORREF_WHITE RGB(0xFF, 0xFF, 0xFF) #define COLORREF_ORANGE RGB(0xFF, 0x88, 0x00) #define COLORREF_RED RGB(0xFF, 0x00, 0x00) +#define COLORREF_MAGENTA RGB(0xFF, 0x00, 0xFF) + +typedef struct Window Window; + +void Win32_FitChild(Window* pChildWindow, Window* pParentWindow); + +ULONG64 Win32_GetSystemTimeAsUnixTime(); diff --git a/src/win32/window.c b/src/win32/window.c index b367777..68ee0e7 100644 --- a/src/win32/window.c +++ b/src/win32/window.c @@ -4,8 +4,6 @@ static const WCHAR szClassName[] = L"DummyWindowClass"; -extern HashMap g_hWndMap; - Window* Window_Create(struct Application*); void Window_Init(Window*, struct Application*); @@ -17,7 +15,7 @@ void Window_OnPaint(Window* pWindow); BOOL Window_OnClose(Window* pWindow); void Window_OnDestroy(Window* pWindow); BOOL Window_OnCommand(Window* pWindow, WPARAM wParam, LPARAM lParam); -void Window_OnSize(Window* window, int cx, int cy); +void Window_OnSize(Window* pWindow, int cx, int cy); void Window_Subclass(Window* pWindow, HWND hWnd); LRESULT CALLBACK Window_UserProc(Window* pWindow, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); @@ -27,92 +25,95 @@ HWND Window_CreateWindow(Window*, HWND); Window* Window_Create(struct Application* app) { - Window* window = calloc(1, sizeof(Window)); + Window* window = (Window*)malloc(sizeof(Window)); + - if (window) { - Window_Init(window, app); - } + if (window) + { + memset(window, 0, sizeof(Window)); + Window_Init(window, app); + } - return window; + return window; } -void Window_Init(Window* window, struct Application* app) +void Window_Init(Window* pWindow, struct Application* app) { - window->app = app; + pWindow->app = app; - window->szClassName = szClassName; + pWindow->szClassName = szClassName; - window->PreRegister = (FnWindowPreRegister)Window_PreRegister; - window->PreCreate = (FnWindowPreCreate)Window_PreCreate; + pWindow->PreRegister = (FnWindowPreRegister)Window_PreRegister; + pWindow->PreCreate = (FnWindowPreCreate)Window_PreCreate; - window->OnCreate = (FnWindowPostCreate)Window_OnCreate; - window->PostCreate = (FnWindowPostCreate)Window_PostCreate; - window->OnPaint = (FnWindowOnPaint)Window_OnPaint; - window->OnClose = (FnWindowOnClose)Window_OnClose; - window->OnDestroy = (FnWindowOnDestroy)Window_OnDestroy; - window->OnCommand = (FnWindowOnCommand)Window_OnCommand; - window->OnSize = (FnWindowOnSize)Window_OnSize; + pWindow->OnCreate = (FnWindowPostCreate)Window_OnCreate; + pWindow->PostCreate = (FnWindowPostCreate)Window_PostCreate; + pWindow->OnPaint = (FnWindowOnPaint)Window_OnPaint; + pWindow->OnClose = (FnWindowOnClose)Window_OnClose; + pWindow->OnDestroy = (FnWindowOnDestroy)Window_OnDestroy; + pWindow->OnCommand = (FnWindowOnCommand)Window_OnCommand; + pWindow->OnSize = (FnWindowOnSize)Window_OnSize; - window->UserProc = (FnWindowUserProc)Window_UserProc; + pWindow->UserProc = (FnWindowUserProc)Window_UserProc; } void Window_PreRegister(LPWNDCLASSEX lpwcex) { - lpwcex->cbSize = sizeof(WNDCLASSEX); - lpwcex->style = CS_HREDRAW | CS_VREDRAW; - lpwcex->lpfnWndProc = (WNDPROC)Window_WndProc; - lpwcex->cbWndExtra = sizeof(Window*); - lpwcex->hInstance = GetModuleHandle(NULL); - lpwcex->hIcon = LoadIcon(NULL, IDI_APPLICATION); - lpwcex->hCursor = LoadCursor(NULL, IDC_ARROW); - lpwcex->hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); - lpwcex->lpszClassName = szClassName; + lpwcex->cbSize = sizeof(WNDCLASSEX); + lpwcex->style = CS_HREDRAW | CS_VREDRAW; + lpwcex->lpfnWndProc = (WNDPROC)Window_WndProc; + lpwcex->cbWndExtra = sizeof(Window*); + lpwcex->hInstance = GetModuleHandle(NULL); + lpwcex->hIcon = LoadIcon(NULL, IDI_APPLICATION); + lpwcex->hCursor = LoadCursor(NULL, IDC_ARROW); + lpwcex->hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + lpwcex->lpszClassName = szClassName; } void Window_PreCreate(LPCREATESTRUCT lpcs) { - lpcs->dwExStyle = 0; - lpcs->lpszClass = szClassName; - lpcs->lpszName = L"Dummy Window"; - lpcs->style = WS_OVERLAPPEDWINDOW; - lpcs->x = CW_USEDEFAULT; - lpcs->y = CW_USEDEFAULT; - lpcs->cx = CW_USEDEFAULT; - lpcs->cy = CW_USEDEFAULT; - lpcs->hwndParent = NULL; - lpcs->hInstance = GetModuleHandle(NULL); - lpcs->lpCreateParams = (LPVOID)NULL; + lpcs->dwExStyle = 0; + lpcs->lpszClass = szClassName; + lpcs->lpszName = L"Dummy Window"; + lpcs->style = WS_OVERLAPPEDWINDOW; + lpcs->x = CW_USEDEFAULT; + lpcs->y = CW_USEDEFAULT; + lpcs->cx = CW_USEDEFAULT; + lpcs->cy = CW_USEDEFAULT; + lpcs->hwndParent = NULL; + lpcs->hInstance = GetModuleHandle(NULL); + lpcs->lpCreateParams = (LPVOID)NULL; } -BOOL Window_OnCreate(Window* window, LPCREATESTRUCT lpcs) +BOOL Window_OnCreate(Window* pWindow, LPCREATESTRUCT lpcs) { - return TRUE; + return TRUE; } -void Window_PostCreate(Window* window) +void Window_PostCreate(Window* pWindow) { } -void Window_OnPaint(Window* window) +void Window_OnPaint(Window* pWindow) { /* - PAINTSTRUCT ps; - HDC hdc; - - hdc = BeginPaint(window->hWnd, &ps); - - RECT rcClient = { 0 }; - GetClientRect(window->hWnd, &rcClient); - - SetBkMode(hdc, TRANSPARENT); - - const WCHAR szDummyText[] = L"This is a dummy window created by passing default overloads"; - size_t nDummyTextLen = wcslen(szDummyText); - - DrawTextEx(hdc, szDummyText, nDummyTextLen, &rcClient, DT_SINGLELINE | DT_CENTER | DT_VCENTER, NULL); - - EndPaint(window->hWnd, &ps); - */ + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(window->hWnd, &ps); + + RECT rcClient = { 0 }; + GetClientRect(window->hWnd, &rcClient); + + SetBkMode(hdc, TRANSPARENT); + + const WCHAR szDummyText[] = L"This is a dummy window created by passing default overloads"; + size_t nDummyTextLen = wcslen(szDummyText); + + DrawTextEx(hdc, szDummyText, nDummyTextLen, &rcClient, DT_SINGLELINE | DT_CENTER | DT_VCENTER, NULL); + + EndPaint(window->hWnd, &ps); + */ } BOOL Window_OnClose(Window* pWindow) @@ -121,17 +122,17 @@ BOOL Window_OnClose(Window* pWindow) return FALSE; } -void Window_OnDestroy(Window* window) +void Window_OnDestroy(Window* pWindow) { - PostQuitMessage(0); + PostQuitMessage(0); } -BOOL Window_OnCommand(Window* window, WPARAM wParam, LPARAM lParam) +BOOL Window_OnCommand(Window* pWindow, WPARAM wParam, LPARAM lParam) { return FALSE; /* pass trough */ } -void Window_OnSize(Window* window, int cx, int cy) +void Window_OnSize(Window* pWindow, int cx, int cy) { /* Do nothing */ } @@ -193,12 +194,12 @@ LRESULT Window_UserProc(Window* pWindow, HWND hWnd, UINT message, WPARAM wParam, LRESULT CALLBACK Window_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { Window* pWindow = NULL; - pWindow = (Window*)GetFromHashMap(&g_hWndMap, (void*)hWnd); + pWindow = WindowMap_Get(hWnd); if (!pWindow) { if (pWndCreating) { - InsertIntoHashMap(&g_hWndMap, (void*)hWnd, (void*)pWndCreating); + WindowMap_Insert(hWnd, pWndCreating); pWndCreating->hWnd = hWnd; } } @@ -212,79 +213,79 @@ LRESULT CALLBACK Window_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l } } -BOOL Window_Register(Window* window) +BOOL Window_Register(Window* pWindow) { - WNDCLASSEX wcex = { 0 }; - BOOL bRegistered = FALSE; + WNDCLASSEX wcex = { 0 }; + BOOL bRegistered = FALSE; - bRegistered = GetClassInfoEx(window->app->hInstance, window->szClassName, &wcex); + bRegistered = GetClassInfoEx(GetModuleHandle(NULL), pWindow->szClassName, &wcex); - if (!bRegistered && window->PreRegister) - { - window->PreRegister(&window->wcex); - window->wcex.cbSize = sizeof(WNDCLASSEX); - window->wcex.lpfnWndProc = (WNDPROC)Window_WndProc; - window->wcex.cbWndExtra = sizeof(Window*); - window->wcex.hInstance = window->app->hInstance; - return (BOOL)RegisterClassEx(&window->wcex); - } + if (!bRegistered && pWindow->PreRegister) + { + pWindow->PreRegister(&pWindow->wcex); + pWindow->wcex.cbSize = sizeof(WNDCLASSEX); + pWindow->wcex.lpfnWndProc = (WNDPROC)Window_WndProc; + pWindow->wcex.cbWndExtra = sizeof(Window*); + pWindow->wcex.hInstance = GetModuleHandle(NULL); + return (BOOL)RegisterClassEx(&pWindow->wcex); + } - return TRUE; + return TRUE; } -HWND Window_CreateWindow(Window* window, HWND hParent) +HWND Window_CreateWindow(Window* pWindow, HWND hParent) { - Window_Register(window); - - LPWNDCLASSEX lpwcex = &window->wcex; - LPCREATESTRUCT cs = &window->cs; - - window->PreCreate(cs); - if (lpwcex->lpszClassName) - { - cs->lpszClass = window->wcex.lpszClassName; - } + Window_Register(pWindow); - if (hParent) - { - cs->style &= ~(WS_CAPTION | WS_THICKFRAME); - cs->style |= WS_CHILD; - } + LPWNDCLASSEX lpwcex = &pWindow->wcex; + LPCREATESTRUCT cs = &pWindow->cs; - pWndCreating = window; + pWindow->PreCreate(cs); + if (lpwcex->lpszClassName) + { + cs->lpszClass = pWindow->wcex.lpszClassName; + } - HWND hWindow = CreateWindowEx( - cs->dwExStyle, - cs->lpszClass, - cs->lpszName, - cs->style, - cs->x, - cs->y, - cs->cx, - cs->cy, - hParent, - cs->hMenu, - lpwcex->hInstance, - (LPVOID)NULL); + if (hParent) + { + cs->style &= ~(WS_CAPTION | WS_THICKFRAME); + cs->style |= WS_CHILD; + } - WNDCLASSEX wcex = { 0 }; - GetClassInfoEx(lpwcex->hInstance, cs->lpszClass, &wcex); - if (wcex.lpfnWndProc != (WNDPROC)Window_WndProc) - { - Window_Subclass(window, hWindow); - window->OnCreate(window, &window->cs); - } + pWndCreating = pWindow; + + HWND hWindow = CreateWindowEx( + cs->dwExStyle, + cs->lpszClass, + cs->lpszName, + cs->style, + cs->x, + cs->y, + cs->cx, + cs->cy, + hParent, + cs->hMenu, + lpwcex->hInstance, + (LPVOID)NULL); + + WNDCLASSEX wcex = { 0 }; + GetClassInfoEx(lpwcex->hInstance, cs->lpszClass, &wcex); + if (wcex.lpfnWndProc != (WNDPROC)Window_WndProc) + { + Window_Subclass(pWindow, hWindow); + pWindow->OnCreate(pWindow, &pWindow->cs); + } - pWndCreating = NULL; + pWndCreating = NULL; - window->hWnd = hWindow; + pWindow->hWnd = hWindow; - window->PostCreate(window); + pWindow->PostCreate(pWindow); - ShowWindow(hWindow, SW_NORMAL); - UpdateWindow(hWindow); + ShowWindow(hWindow, SW_NORMAL); + UpdateWindow(hWindow); - return hWindow; + return hWindow; } void Window_Subclass(Window* pWindow, HWND hWnd) @@ -318,8 +319,8 @@ HWND Window_Detach(Window* pWindow) Window_UnSubclass(pWindow); } - RemoveFromHashMapByValue(&g_hWndMap, pWindow); - + // RemoveFromHashMapByValue(&g_hWndMap, pWindow); + pWindow->hWnd = NULL; return hWnd; @@ -331,9 +332,9 @@ void Window_Attach(Window* pWindow, HWND hWnd) if (IsWindow(hWnd)) { - if (!GetFromHashMap(&g_hWndMap, hWnd)) + if (!WindowMap_Get(hWnd)) { - InsertIntoHashMap(&g_hWndMap, hWnd, pWindow); + WindowMap_Insert(hWnd, pWindow); Window_Subclass(pWindow, hWnd); } } @@ -344,49 +345,54 @@ LRESULT Window_SendMessage(Window* pWindow, UINT message, WPARAM wParam, LPARAM SendMessage(pWindow->hWnd, message, wParam, lParam); } -void Window_SetTheme(Window* window, PCWSTR lpszThemeName) +void Window_SetTheme(Window* pWindow, PCWSTR lpszThemeName) { - SetWindowTheme(window->hWnd, lpszThemeName, NULL); + SetWindowTheme(pWindow->hWnd, lpszThemeName, NULL); } -DWORD Window_GetStyle(Window* window) +DWORD Window_GetStyle(Window* pWindow) { - return (DWORD)GetWindowLongPtr(window->hWnd, GWL_STYLE); + return (DWORD)GetWindowLongPtr(pWindow->hWnd, GWL_STYLE); } -void Window_SetStyle(Window* window, DWORD dwStyle) +void Window_SetStyle(Window* pWindow, DWORD dwStyle) { - SetWindowLongPtr(window->hWnd, GWL_STYLE, dwStyle); + SetWindowLongPtr(pWindow->hWnd, GWL_STYLE, dwStyle); } -HWND Window_GetHWND(Window* window) +HWND Window_GetHWND(Window* pWindow) { - return window->hWnd; + return pWindow->hWnd; } -void Window_GetClientRect(Window* window, LPRECT lprc) +void Window_GetClientRect(Window* pWindow, LPRECT lprc) { - GetClientRect(Window_GetHWND(window), lprc); + GetClientRect(Window_GetHWND(pWindow), lprc); } -void Window_Invalidate(Window* window) +void Window_Invalidate(Window* pWindow) { - InvalidateRgn(Window_GetHWND(window), NULL, TRUE); + InvalidateRgn(Window_GetHWND(pWindow), NULL, TRUE); } -void _WindowInitHelper_SetPreRegisterRoutine(Window* window, void(*pfnPreRegister)(LPWNDCLASSEX)) +BOOL Window_Show(Window* pWindow, int nCmdShow) { - window->PreRegister = pfnPreRegister; + return ShowWindow(Window_GetHWND(pWindow), nCmdShow); } -void _WindowInitHelper_SetPreCreateRoutine(Window* window, void(*pfnPreCreate)(LPCREATESTRUCT)) +void _WindowInitHelper_SetPreRegisterRoutine(Window* pWindow, void(*pfnPreRegister)(LPWNDCLASSEX lpwcex)) { - window->PreCreate = pfnPreCreate; + pWindow->PreRegister = pfnPreRegister; } -void _WindowInitHelper_SetUserProcRoutine(Window* window, void(*pfnUserProc)(Window*, UINT, WPARAM, LPARAM)) +void _WindowInitHelper_SetPreCreateRoutine(Window* pWindow, void(*pfnPreCreate)(LPCREATESTRUCT lpcs)) { - window->UserProc = pfnUserProc; + pWindow->PreCreate = pfnPreCreate; +} + +void _WindowInitHelper_SetUserProcRoutine(Window* pWindow, void(*pfnUserProc)(Window* pWindow, UINT message, WPARAM wParam, LPARAM lParam)) +{ + pWindow->UserProc = pfnUserProc; } int Win32_Rect_GetWidth(LPRECT lprc) @@ -397,4 +403,4 @@ int Win32_Rect_GetWidth(LPRECT lprc) int Win32_Rect_GetHeight(LPRECT lprc) { return lprc->bottom - lprc->top; -} \ No newline at end of file +} diff --git a/src/win32/window.h b/src/win32/window.h index b336e4b..8e38db6 100644 --- a/src/win32/window.h +++ b/src/win32/window.h @@ -66,6 +66,7 @@ HWND Window_GetHWND(Window* pWindow); void Window_ApplyUIFont(Window* pWindow); void Window_GetClientRect(Window* pWindow, LPRECT lpcs); void Window_Invalidate(Window* pWindow); +BOOL Window_Show(Window* pWindow, int nCmdShow); void _WindowInitHelper_SetPreRegisterRoutine(Window* pWindow, void(*pfnPreRegister)(LPWNDCLASSEX lpwcex)); void _WindowInitHelper_SetPreCreateRoutine(Window* pWindow, void(*pfnPreCreate)(LPCREATESTRUCT lpcs)); diff --git a/src/win32/windowmap.c b/src/win32/windowmap.c index 46b503f..2f5afde 100644 --- a/src/win32/windowmap.c +++ b/src/win32/windowmap.c @@ -1,85 +1,51 @@ #include "common.h" #include "window.h" +#include "../util/hashmap.h" -struct KeyValuePair { - void* key; - void* value; -}; +HashMap* g_pHWNDMap; -struct HashMap { - struct KeyValuePair* pairs; - size_t capacity; - size_t size; -}; - -void InitHashMap(struct HashMap* map, size_t capacity) -{ - map->pairs = (struct KeyValuePair*)malloc(capacity * sizeof(struct KeyValuePair)); - map->capacity = capacity; - map->size = 0; -} - -void InsertIntoHashMap(struct HashMap* map, void* key, void* value) +void WindowMap_Insert(HWND hWnd, Window* pWindow) { - if (map->size < map->capacity) + if (g_pHWNDMap) { - size_t index = map->size; - map->pairs[index].key = key; - map->pairs[index].value = value; - map->size++; + HashMap_Put(g_pHWNDMap, (void*)hWnd, (void*)pWindow); } else { + MessageBox(NULL, L"WindowMap not initialized", NULL, MB_OK | MB_ICONERROR); } } -void* GetFromHashMap(struct HashMap* map, void* key) +Window* WindowMap_Get(HWND hWnd) { - for (size_t i = 0; i < map->size; ++i) - { - if (map->pairs[i].key == key) - { - return map->pairs[i].value; - } - } - return NULL; -} + Window* pWindow = NULL; -void RemoveFromHashMapByKey(struct HashMap* map, void* key) -{ - for (size_t i = 0; i < map->size; ++i) + if (g_pHWNDMap) { - if (map->pairs[i].key == key) - { - // Move the last element to the position being removed - map->pairs[i] = map->pairs[map->size - 1]; - map->size--; - return; - } + pWindow = HashMap_Get(g_pHWNDMap, (void*)hWnd); + } + else { + MessageBox(NULL, L"WindowMap not initialized", NULL, MB_OK | MB_ICONERROR); } + + return pWindow; } -void RemoveFromHashMapByValue(struct HashMap* map, void* value) +/** + * [PRIVATE] + * + * Window system descriptor key comparison callback for global window hashmap + * + * @return CMP_GREATER (1), CMP_LOWER (-1) or CMP_EQUAL (0) + */ +static int WindowMap_KeyCompare(void* pKey1, void* pKey2) { - for (size_t i = 0; i < map->size; ++i) - { - if (map->pairs[i].value == value) - { - // Move the last element to the position being removed - map->pairs[i] = map->pairs[map->size - 1]; - map->size--; - return; - } - } + return (HWND)pKey1 > (HWND)pKey2 ? CMP_GREATER : (HWND)pKey1 < (HWND)pKey2 ? CMP_LOWER : CMP_EQUAL; } -void FreeHashMap(struct HashMap* map) +void WindowMap_GlobalInit() { - free(map->pairs); - map->capacity = 0; - map->size = 0; + g_pHWNDMap = HashMap_Create(16, &WindowMap_KeyCompare); } -struct HashMap g_hWndMap; - Window* pWndCreating; diff --git a/src/win32/windowmap.h b/src/win32/windowmap.h index bf7567d..07c6dbd 100644 --- a/src/win32/windowmap.h +++ b/src/win32/windowmap.h @@ -5,14 +5,14 @@ typedef struct Window Window; typedef struct HashMap HashMap; -void InitHashMap(struct HashMap* map, size_t capacity); -void InsertIntoHashMap(struct HashMap* map, void* key, void* value); -void* GetFromHashMap(struct HashMap* map, void* key); -void FreeHashMap(struct HashMap* map); void WindowingInit(); -void RemoveFromHashMapByKey(struct HashMap* map, void* key); -void RemoveFromHashMapByValue(struct HashMap* map, void* value); + +void WindowMap_Insert(HWND hWnd, Window* pWindow); +Window* WindowMap_Get(HWND hWnd); + +void WindowMap_GlobalInit(); extern Window* pWndCreating; +extern HashMap* g_pHWNDMap; #endif // _WIN32_WINDOWMAP_H_INCLUDED diff --git a/src/windowstub.c b/src/windowstub.c index fc17620..02ef421 100644 --- a/src/windowstub.c +++ b/src/windowstub.c @@ -7,8 +7,8 @@ static const WCHAR szClassName[] = L"__WindowStub"; /* Private forward declarations */ -WindowStub* WindowStub_Create(struct Application* app); -void WindowStub_Init(WindowStub*, struct Application* app); +WindowStub* WindowStub_Create(Application* app); +void WindowStub_Init(WindowStub*, Application* app); void WindowStub_PreRegister(LPWNDCLASSEX lpwcex); void WindowStub_PreCreate(LPCREATESTRUCT lpcs); @@ -21,10 +21,9 @@ void WindowStub_OnContextMenu(WindowStub* pWindowStub, int x, int y); void WindowStub_OnDestroy(WindowStub* pWindowStub); LRESULT WindowStub_UserProc(WindowStub* pWindowStub, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); -WindowStub* WindowStub_Create(struct Application* app) +WindowStub* WindowStub_Create(Application* app) { - WindowStub* pWindowStub = (WindowStub *)calloc(1, sizeof(WindowStub)); - + WindowStub* pWindowStub = (WindowStub *)malloc(sizeof(WindowStub)); if (pWindowStub) { WindowStub_Init(pWindowStub, app); @@ -33,8 +32,10 @@ WindowStub* WindowStub_Create(struct Application* app) return pWindowStub; } -void WindowStub_Init(WindowStub* pWindowStub, struct Application* app) +void WindowStub_Init(WindowStub* pWindowStub, Application* app) { + memset(pWindowStub, 0, sizeof(WindowStub)); + Window_Init(&pWindowStub->base, app); pWindowStub->base.szClassName = szClassName; diff --git a/src/workspacecontainer.c b/src/workspacecontainer.c index 92a7b97..5638b66 100644 --- a/src/workspacecontainer.c +++ b/src/workspacecontainer.c @@ -34,7 +34,8 @@ void ViewportVector_Init(ViewportVector* pViewportVector); ViewportVector* ViewportVector_Create() { - ViewportVector* pViewportVector = (ViewportVector *)calloc(1, sizeof(ViewportVector)); + ViewportVector* pViewportVector = (ViewportVector *)malloc(sizeof(ViewportVector)); + memset(pViewportVector, 0, sizeof(ViewportVector)); ViewportVector_Init(pViewportVector); return pViewportVector; } @@ -45,7 +46,8 @@ void ViewportVector_Init(ViewportVector* pViewportVector) pViewportVector->m_size = 0; pViewportVector->pData = NULL; - ViewportWindow** pVectorData = (ViewportWindow**)calloc(10, sizeof(ViewportWindow*)); + ViewportWindow** pVectorData = (ViewportWindow**)malloc(10 * sizeof(ViewportWindow*)); + memset(pVectorData, 0, 10 * sizeof(ViewportWindow*)); if (pVectorData) { pViewportVector->pData = pVectorData; @@ -78,7 +80,8 @@ ViewportWindow* ViewportVector_Get(ViewportVector* pViewportVector, int idx) WorkspaceContainer* WorkspaceContainer_Create(struct Application* app) { - WorkspaceContainer* pWorkspaceContainer = calloc(1, sizeof(WorkspaceContainer)); + WorkspaceContainer* pWorkspaceContainer = (WorkspaceContainer*)malloc(sizeof(WorkspaceContainer)); + memset(pWorkspaceContainer, 0, sizeof(WorkspaceContainer)); if (pWorkspaceContainer) { diff --git a/src/xml.c b/src/xml.c new file mode 100644 index 0000000..ec1ed90 --- /dev/null +++ b/src/xml.c @@ -0,0 +1,1132 @@ +/** + * Copyright (c) 2012 ooxi/xml.c + * https://github.com/ooxi/xml.c + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + */ + +#define _CRT_SECURE_NO_WARNINGS + +#include "xml.h" + +#ifdef XML_PARSER_VERBOSE +#include +#endif + +#include + +#ifndef __MACH__ +#include +#endif + +#include +#include +#include +#include + + + + + +/* + * public domain strtok_r() by Charlie Gordon + * + * from comp.lang.c 9/14/2007 + * + * http://groups.google.com/group/comp.lang.c/msg/2ab1ecbb86646684 + * + * (Declaration that it's public domain): + * http://groups.google.com/group/comp.lang.c/msg/7c7b39328fefab9c + */ +static char* xml_strtok_r(char *str, const char *delim, char **nextp) { + char *ret; + + if (str == NULL) { + str = *nextp; + } + + str += strspn(str, delim); + + if (*str == '\0') { + return NULL; + } + + ret = str; + + str += strcspn(str, delim); + + if (*str) { + *str++ = '\0'; + } + + *nextp = str; + + return ret; +} + + + + + + +/** + * [OPAQUE API] + * + * UTF-8 text + */ +struct xml_string { + uint8_t const* buffer; + size_t length; +}; + +/** + * [OPAQUE API] + * + * An xml_attribute may contain text content. + */ +struct xml_attribute { + struct xml_string* name; + struct xml_string* content; +}; + +/** + * [OPAQUE API] + * + * An xml_node will always contain a tag name, a 0-terminated list of attributes + * and a 0-terminated list of children. Moreover it may contain text content. + */ +struct xml_node { + struct xml_string* name; + struct xml_string* content; + struct xml_attribute** attributes; + struct xml_node** children; +}; + +/** + * [OPAQUE API] + * + * An xml_document simply contains the root node and the underlying buffer + */ +struct xml_document { + struct { + uint8_t* buffer; + size_t length; + } buffer; + + struct xml_node* root; +}; + + + + + +/** + * [PRIVATE] + * + * Parser context + */ +struct xml_parser { + uint8_t* buffer; + size_t position; + size_t length; +}; + +/** + * [PRIVATE] + * + * Character offsets + */ +enum xml_parser_offset { + NO_CHARACTER = -1, + CURRENT_CHARACTER = 0, + NEXT_CHARACTER = 1, +}; + + + + + +/** + * [PRIVATE] + * + * @return Number of attributes in 0-terminated array + */ +static size_t get_zero_terminated_array_attributes(struct xml_attribute** attributes) { + size_t elements = 0; + + while (attributes[elements]) { + ++elements; + } + + return elements; +} + + + +/** + * [PRIVATE] + * + * @return Number of nodes in 0-terminated array + */ +static size_t get_zero_terminated_array_nodes(struct xml_node** nodes) { + size_t elements = 0; + + while (nodes[elements]) { + ++elements; + } + + return elements; +} + + + +/** + * [PRIVATE] + * + * @warning No UTF conversions will be attempted + * + * @return true iff a == b + */ +static _Bool xml_string_equals(struct xml_string* a, struct xml_string* b) { + + if (a->length != b->length) { + return false; + } + + size_t i = 0; for (; i < a->length; ++i) { + if (a->buffer[i] != b->buffer[i]) { + return false; + } + } + + return true; +} + + + +/** + * [PRIVATE] + */ +static uint8_t* xml_string_clone(struct xml_string* s) { + if (!s) { + return 0; + } + + uint8_t* clone = calloc(s->length + 1, sizeof(uint8_t)); + + xml_string_copy(s, clone, s->length); + clone[s->length] = 0; + + return clone; +} + + + +/** + * [PRIVATE] + * + * Frees the resources allocated by the string + * + * @warning `buffer` must _not_ be freed, since it is a reference to the + * document's buffer + */ +static void xml_string_free(struct xml_string* string) { + free(string); +} + + + +/** + * [PRIVATE] + * + * Frees the resources allocated by the attribute + */ +static void xml_attribute_free(struct xml_attribute* attribute) { + if(attribute->name) { + xml_string_free(attribute->name); + } + if(attribute->content) { + xml_string_free(attribute->content); + } + free(attribute); +} + +/** + * [PRIVATE] + * + * Frees the resources allocated by the node + */ +static void xml_node_free(struct xml_node* node) { + xml_string_free(node->name); + + if (node->content) { + xml_string_free(node->content); + } + + struct xml_attribute** at = node->attributes; + while(*at) { + xml_attribute_free(*at); + ++at; + } + free(node->attributes); + + struct xml_node** it = node->children; + while (*it) { + xml_node_free(*it); + ++it; + } + free(node->children); + + free(node); +} + + + +/** + * [PRIVATE] + * + * Echos the parsers call stack for debugging purposes + */ +#ifdef XML_PARSER_VERBOSE +static void xml_parser_info(struct xml_parser* parser, char const* message) { + fprintf(stdout, "xml_parser_info %s\n", message); +} +#else +#define xml_parser_info(parser, message) {} +#endif + + + +/** + * [PRIVATE] + * + * Echos an error regarding the parser's source to the console + */ +static void xml_parser_error(struct xml_parser* parser, enum xml_parser_offset offset, char const* message) { + int row = 0; + int column = 0; + + #define min(X,Y) ((X) < (Y) ? (X) : (Y)) + #define max(X,Y) ((X) > (Y) ? (X) : (Y)) + size_t character = max(0, min(parser->length, parser->position + offset)); + #undef min + #undef max + + size_t position = 0; for (; position < character; ++position) { + column++; + + if ('\n' == parser->buffer[position]) { + row++; + column = 0; + } + } + + if (NO_CHARACTER != offset) { + fprintf(stderr, "xml_parser_error at %i:%i (is %c): %s\n", + row + 1, column, parser->buffer[character], message + ); + } else { + fprintf(stderr, "xml_parser_error at %i:%i: %s\n", + row + 1, column, message + ); + } +} + + + +/** + * [PRIVATE] + * + * Returns the n-th not-whitespace byte in parser and 0 if such a byte does not + * exist + */ +static uint8_t xml_parser_peek(struct xml_parser* parser, size_t n) { + size_t position = parser->position; + + while (position < parser->length) { + if (!isspace(parser->buffer[position])) { + if (n == 0) { + return parser->buffer[position]; + } else { + --n; + } + } + + position++; + } + + return 0; +} + + + +/** + * [PRIVATE] + * + * Moves the parser's position n bytes. If the new position would be out of + * bounds, it will be converted to the bounds itself + */ +static void xml_parser_consume(struct xml_parser* parser, size_t n) { + + /* Debug information + */ + #ifdef XML_PARSER_VERBOSE + #define min(X,Y) ((X) < (Y) ? (X) : (Y)) + char* consumed = alloca((n + 1) * sizeof(char)); + memcpy(consumed, &parser->buffer[parser->position], min(n, parser->length - parser->position)); + consumed[n] = 0; + #undef min + + size_t message_buffer_length = 512; + char* message_buffer = alloca(512 * sizeof(char)); + snprintf(message_buffer, message_buffer_length, "Consuming %li bytes \"%s\"", (long)n, consumed); + message_buffer[message_buffer_length - 1] = 0; + + xml_parser_info(parser, message_buffer); + #endif + + + /* Move the position forward + */ + parser->position += n; + + /* Don't go too far + * + * @warning Valid because parser->length must be greater than 0 + */ + if (parser->position >= parser->length) { + parser->position = parser->length - 1; + } +} + + + +/** + * [PRIVATE] + * + * Skips to the next non-whitespace character + */ +static void xml_skip_whitespace(struct xml_parser* parser) { + xml_parser_info(parser, "whitespace"); + + while (isspace(parser->buffer[parser->position])) { + if (parser->position + 1 >= parser->length) { + return; + } else { + parser->position++; + } + } +} + + + +/** + * [PRIVATE] + * + * Finds and creates all attributes on the given node. + * + * @author Blake Felt + * @see https://github.com/Molorius + */ +static struct xml_attribute** xml_find_attributes(struct xml_parser* parser, struct xml_string* tag_open) { + xml_parser_info(parser, "find_attributes"); + char* tmp; + char* rest = NULL; + char* token; + char* str_name; + char* str_content; + const unsigned char* start_name; + const unsigned char* start_content; + size_t old_elements; + size_t new_elements; + struct xml_attribute* new_attribute; + struct xml_attribute** attributes; + int position; + + attributes = calloc(1, sizeof(struct xml_attribute*)); + attributes[0] = 0; + + tmp = (char*) xml_string_clone(tag_open); + + token = xml_strtok_r(tmp, " ", &rest); // skip the first value + if(token == NULL) { + goto cleanup; + } + tag_open->length = strlen(token); + + for(token=xml_strtok_r(NULL," ", &rest); token!=NULL; token=xml_strtok_r(NULL," ", &rest)) { + str_name = malloc(strlen(token)+1); + str_content = malloc(strlen(token)+1); + // %s=\"%s\" wasn't working for some reason, ugly hack to make it work + if(sscanf(token, "%[^=]=\"%[^\"]", str_name, str_content) != 2) { + if(sscanf(token, "%[^=]=\'%[^\']", str_name, str_content) != 2) { + free(str_name); + free(str_content); + continue; + } + } + position = token-tmp; + start_name = &tag_open->buffer[position]; + start_content = &tag_open->buffer[position + strlen(str_name) + 2]; + + new_attribute = malloc(sizeof(struct xml_attribute)); + new_attribute->name = malloc(sizeof(struct xml_string)); + new_attribute->name->buffer = (unsigned char*)start_name; + new_attribute->name->length = strlen(str_name); + new_attribute->content = malloc(sizeof(struct xml_string)); + new_attribute->content->buffer = (unsigned char*)start_content; + new_attribute->content->length = strlen(str_content); + + old_elements = get_zero_terminated_array_attributes(attributes); + new_elements = old_elements + 1; + attributes = realloc(attributes, (new_elements+1)*sizeof(struct xml_attribute*)); + + attributes[new_elements-1] = new_attribute; + attributes[new_elements] = 0; + + + free(str_name); + free(str_content); + } + +cleanup: + free(tmp); + return attributes; +} + + + +/** + * [PRIVATE] + * + * Parses the name out of the an XML tag's ending + * + * ---( Example )--- + * tag_name> + * --- + */ +static struct xml_string* xml_parse_tag_end(struct xml_parser* parser) { + xml_parser_info(parser, "tag_end"); + size_t start = parser->position; + size_t length = 0; + + /* Parse until `>' or a whitespace is reached + */ + while (start + length < parser->length) { + uint8_t current = xml_parser_peek(parser, CURRENT_CHARACTER); + + if (('>' == current) || isspace(current)) { + break; + } else { + xml_parser_consume(parser, 1); + length++; + } + } + + /* Consume `>' + */ + if ('>' != xml_parser_peek(parser, CURRENT_CHARACTER)) { + xml_parser_error(parser, CURRENT_CHARACTER, "xml_parse_tag_end::expected tag end"); + return 0; + } + xml_parser_consume(parser, 1); + + /* Return parsed tag name + */ + struct xml_string* name = malloc(sizeof(struct xml_string)); + name->buffer = &parser->buffer[start]; + name->length = length; + return name; +} + + + +/** + * [PRIVATE] + * + * Parses an opening XML tag without attributes + * + * ---( Example )--- + * + * --- + */ +static struct xml_string* xml_parse_tag_open(struct xml_parser* parser) { + xml_parser_info(parser, "tag_open"); + xml_skip_whitespace(parser); + + /* Consume `<' + */ + if ('<' != xml_parser_peek(parser, CURRENT_CHARACTER)) { + xml_parser_error(parser, CURRENT_CHARACTER, "xml_parse_tag_open::expected opening tag"); + return 0; + } + xml_parser_consume(parser, 1); + + /* Consume tag name + */ + return xml_parse_tag_end(parser); +} + + + +/** + * [PRIVATE] + * + * Parses an closing XML tag without attributes + * + * ---( Example )--- + * + * --- + */ +static struct xml_string* xml_parse_tag_close(struct xml_parser* parser) { + xml_parser_info(parser, "tag_close"); + xml_skip_whitespace(parser); + + /* Consume `position; + size_t length = 0; + + /* Consume until `<' is reached + */ + while (start + length < parser->length) { + uint8_t current = xml_parser_peek(parser, CURRENT_CHARACTER); + + if ('<' == current) { + break; + } else { + xml_parser_consume(parser, 1); + length++; + } + } + + /* Next character must be an `<' or we have reached end of file + */ + if ('<' != xml_parser_peek(parser, CURRENT_CHARACTER)) { + xml_parser_error(parser, CURRENT_CHARACTER, "xml_parse_content::expected <"); + return 0; + } + + /* Ignore tailing whitespace + */ + while ((length > 0) && isspace(parser->buffer[start + length - 1])) { + length--; + } + + /* Return text + */ + struct xml_string* content = malloc(sizeof(struct xml_string)); + content->buffer = &parser->buffer[start]; + content->length = length; + return content; +} + + + +/** + * [PRIVATE] + * + * Parses an XML fragment node + * + * ---( Example without children )--- + * Text + * --- + * + * ---( Example with children )--- + * + * Text + * Text + * Content + * + * --- + */ +static struct xml_node* xml_parse_node(struct xml_parser* parser) { + xml_parser_info(parser, "node"); + + /* Setup variables + */ + struct xml_string* tag_open = 0; + struct xml_string* tag_close = 0; + struct xml_string* content = 0; + + size_t original_length; + struct xml_attribute** attributes; + + struct xml_node** children = calloc(1, sizeof(struct xml_node*)); + children[0] = 0; + + + /* Parse open tag + */ + tag_open = xml_parse_tag_open(parser); + if (!tag_open) { + xml_parser_error(parser, NO_CHARACTER, "xml_parse_node::tag_open"); + goto exit_failure; + } + + original_length = tag_open->length; + attributes = xml_find_attributes(parser, tag_open); + + /* If tag ends with `/' it's self closing, skip content lookup */ + if (tag_open->length > 0 && '/' == tag_open->buffer[original_length - 1]) { + /* Drop `/' + */ + goto node_creation; + } + + /* If the content does not start with '<', a text content is assumed + */ + if ('<' != xml_parser_peek(parser, CURRENT_CHARACTER)) { + content = xml_parse_content(parser); + + if (!content) { + xml_parser_error(parser, 0, "xml_parse_node::content"); + goto exit_failure; + } + + + /* Otherwise children are to be expected + */ + } else while ('/' != xml_parser_peek(parser, NEXT_CHARACTER)) { + + /* Parse child node + */ + struct xml_node* child = xml_parse_node(parser); + if (!child) { + xml_parser_error(parser, NEXT_CHARACTER, "xml_parse_node::child"); + goto exit_failure; + } + + /* Grow child array :) + */ + size_t old_elements = get_zero_terminated_array_nodes(children); + size_t new_elements = old_elements + 1; + children = realloc(children, (new_elements + 1) * sizeof(struct xml_node*)); + + /* Save child + */ + children[new_elements - 1] = child; + children[new_elements] = 0; + } + + + /* Parse close tag + */ + tag_close = xml_parse_tag_close(parser); + if (!tag_close) { + xml_parser_error(parser, NO_CHARACTER, "xml_parse_node::tag_close"); + goto exit_failure; + } + + + /* Close tag has to match open tag + */ + if (!xml_string_equals(tag_open, tag_close)) { + xml_parser_error(parser, NO_CHARACTER, "xml_parse_node::tag missmatch"); + goto exit_failure; + } + + + /* Return parsed node + */ + xml_string_free(tag_close); + +node_creation:; + struct xml_node* node = malloc(sizeof(struct xml_node)); + node->name = tag_open; + node->content = content; + node->attributes = attributes; + node->children = children; + return node; + + + /* A failure occured, so free all allocalted resources + */ +exit_failure: + if (tag_open) { + xml_string_free(tag_open); + } + if (tag_close) { + xml_string_free(tag_close); + } + if (content) { + xml_string_free(content); + } + + struct xml_node** it = children; + while (*it) { + xml_node_free(*it); + ++it; + } + free(children); + + return 0; +} + + + + + +/** + * [PUBLIC API] + */ +struct xml_document* xml_parse_document(uint8_t* buffer, size_t length) { + + /* Initialize parser + */ + struct xml_parser parser = { + .buffer = buffer, + .position = 0, + .length = length + }; + + /* An empty buffer can never contain a valid document + */ + if (!length) { + xml_parser_error(&parser, NO_CHARACTER, "xml_parse_document::length equals zero"); + return 0; + } + + /* Parse the root node + */ + struct xml_node* root = xml_parse_node(&parser); + if (!root) { + xml_parser_error(&parser, NO_CHARACTER, "xml_parse_document::parsing document failed"); + return 0; + } + + /* Return parsed document + */ + struct xml_document* document = malloc(sizeof(struct xml_document)); + document->buffer.buffer = buffer; + document->buffer.length = length; + document->root = root; + + return document; +} + + + +/** + * [PUBLIC API] + */ +struct xml_document* xml_open_document(FILE* source) { + + /* Prepare buffer + */ + size_t const read_chunk = 1; // TODO 4096; + + size_t document_length = 0; + size_t buffer_size = 1; // TODO 4069 + uint8_t* buffer = malloc(buffer_size * sizeof(uint8_t)); + + /* Read hole file into buffer + */ + while (!feof(source)) { + + /* Reallocate buffer + */ + if (buffer_size - document_length < read_chunk) { + buffer = realloc(buffer, buffer_size + 2 * read_chunk); + buffer_size += 2 * read_chunk; + } + + size_t read = fread( + &buffer[document_length], + sizeof(uint8_t), read_chunk, + source + ); + + document_length += read; + } + fclose(source); + + /* Try to parse buffer + */ + struct xml_document* document = xml_parse_document(buffer, document_length); + + if (!document) { + free(buffer); + return 0; + } + return document; +} + + + +/** + * [PUBLIC API] + */ +void xml_document_free(struct xml_document* document, bool free_buffer) { + xml_node_free(document->root); + + if (free_buffer) { + free(document->buffer.buffer); + } + free(document); +} + + + +/** + * [PUBLIC API] + */ +struct xml_node* xml_document_root(struct xml_document* document) { + return document->root; +} + + + +/** + * [PUBLIC API] + */ +struct xml_string* xml_node_name(struct xml_node* node) { + return node->name; +} + + + +/** + * [PUBLIC API] + */ +struct xml_string* xml_node_content(struct xml_node* node) { + return node->content; +} + + + +/** + * [PUBLIC API] + * + * @warning O(n) + */ +size_t xml_node_children(struct xml_node* node) { + return get_zero_terminated_array_nodes(node->children); +} + + + +/** + * [PUBLIC API] + */ +struct xml_node* xml_node_child(struct xml_node* node, size_t child) { + if (child >= xml_node_children(node)) { + return 0; + } + + return node->children[child]; +} + + + +/** + * [PUBLIC API] + */ +size_t xml_node_attributes(struct xml_node* node) { + return get_zero_terminated_array_attributes(node->attributes); +} + + + +/** + * [PUBLIC API] + */ +struct xml_string* xml_node_attribute_name(struct xml_node* node, size_t attribute) { + if(attribute >= xml_node_attributes(node)) { + return 0; + } + + return node->attributes[attribute]->name; +} + + + +/** + * [PUBLIC API] + */ +struct xml_string* xml_node_attribute_content(struct xml_node* node, size_t attribute) { + if(attribute >= xml_node_attributes(node)) { + return 0; + } + + return node->attributes[attribute]->content; +} + + + +/** + * [PUBLIC API] + */ +struct xml_node* xml_easy_child(struct xml_node* node, uint8_t const* child_name, ...) { + + /* Find children, one by one + */ + struct xml_node* current = node; + + va_list arguments; + va_start(arguments, child_name); + + + /* Descent to current.child + */ + while (child_name) { + + /* Convert child_name to xml_string for easy comparison + */ + struct xml_string cn = { + .buffer = child_name, + .length = strlen(child_name) + }; + + /* Interate through all children + */ + struct xml_node* next = 0; + + size_t i = 0; for (; i < xml_node_children(current); ++i) { + struct xml_node* child = xml_node_child(current, i); + + if (xml_string_equals(xml_node_name(child), &cn)) { + if (!next) { + next = child; + + /* Two children with the same name + */ + } else { + va_end(arguments); + return 0; + } + } + } + + /* No child with that name found + */ + if (!next) { + va_end(arguments); + return 0; + } + current = next; + + /* Find name of next child + */ + child_name = va_arg(arguments, uint8_t const*); + } + va_end(arguments); + + + /* Return current element + */ + return current; +} + + + +/** + * [PUBLIC API] + */ +uint8_t* xml_easy_name(struct xml_node* node) { + if (!node) { + return 0; + } + + return xml_string_clone(xml_node_name(node)); +} + + + +/** + * [PUBLIC API] + */ +uint8_t* xml_easy_content(struct xml_node* node) { + if (!node) { + return 0; + } + + return xml_string_clone(xml_node_content(node)); +} + + + +/** + * [PUBLIC API] + */ +size_t xml_string_length(struct xml_string* string) { + if (!string) { + return 0; + } + return string->length; +} + + + +/** + * [PUBLIC API] + */ +void xml_string_copy(struct xml_string* string, uint8_t* buffer, size_t length) { + if (!string) { + return; + } + + #define min(X,Y) ((X) < (Y) ? (X) : (Y)) + length = min(length, string->length); + #undef min + + memcpy(buffer, string->buffer, length); +} + diff --git a/src/xml.h b/src/xml.h new file mode 100644 index 0000000..688a4be --- /dev/null +++ b/src/xml.h @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2012 ooxi/xml.c + * https://github.com/ooxi/xml.c + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + */ +#ifndef HEADER_XML +#define HEADER_XML + + +/** + * Includes + */ +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Opaque structure holding the parsed xml document + */ +struct xml_document; +struct xml_node; +struct xml_attribute; + +/** + * Internal character sequence representation + */ +struct xml_string; + + + +/** + * Tries to parse the XML fragment in buffer + * + * @param buffer Chunk to parse + * @param length Size of the buffer + * + * @warning `buffer` will be referenced by the document, you may not free it + * until you free the xml_document + * @warning You have to call xml_document_free after you finished using the + * document + * + * @return The parsed xml fragment iff parsing was successful, 0 otherwise + */ +struct xml_document* xml_parse_document(uint8_t* buffer, size_t length); + + + +/** + * Tries to read an XML document from disk + * + * @param source File that will be read into an xml document. Will be closed + * + * @warning You have to call xml_document_free with free_buffer = true after you + * finished using the document + * + * @return The parsed xml fragment iff parsing was successful, 0 otherwise + */ +struct xml_document* xml_open_document(FILE* source); + + + +/** + * Frees all resources associated with the document. All xml_node and xml_string + * references obtained through the document will be invalidated + * + * @param document xml_document to free + * @param free_buffer iff true the internal buffer supplied via xml_parse_buffer + * will be freed with the `free` system call + */ +void xml_document_free(struct xml_document* document, bool free_buffer); + + +/** + * @return xml_node representing the document root + */ +struct xml_node* xml_document_root(struct xml_document* document); + + + +/** + * @return The xml_node's tag name + */ +struct xml_string* xml_node_name(struct xml_node* node); + + + +/** + * @return The xml_node's string content (if available, otherwise NULL) + */ +struct xml_string* xml_node_content(struct xml_node* node); + + + +/** + * @return Number of child nodes + */ +size_t xml_node_children(struct xml_node* node); + + + +/** + * @return The n-th child or 0 if out of range + */ +struct xml_node* xml_node_child(struct xml_node* node, size_t child); + + + +/** + * @return Number of attribute nodes + */ +size_t xml_node_attributes(struct xml_node* node); + + + +/** + * @return the n-th attribute name or 0 if out of range + */ +struct xml_string* xml_node_attribute_name(struct xml_node* node, size_t attribute); + + + +/** + * @return the n-th attribute content or 0 if out of range + */ +struct xml_string* xml_node_attribute_content(struct xml_node* node, size_t attribute); + + + +/** + * @return The node described by the path or 0 if child cannot be found + * @warning Each element on the way must be unique + * @warning Last argument must be 0 + */ +struct xml_node* xml_easy_child(struct xml_node* node, uint8_t const* child, ...); + + + +/** + * @return 0-terminated copy of node name + * @warning User must free the result + */ +uint8_t* xml_easy_name(struct xml_node* node); + + + +/** + * @return 0-terminated copy of node content + * @warning User must free the result + */ +uint8_t* xml_easy_content(struct xml_node* node); + + + +/** + * @return Length of the string + */ +size_t xml_string_length(struct xml_string* string); + + + +/** + * Copies the string into the supplied buffer + * + * @warning String will not be 0-terminated + * @warning Will write at most length bytes, even if the string is longer + */ +void xml_string_copy(struct xml_string* string, uint8_t* buffer, size_t length); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/win32fw.vcxproj b/win32fw.vcxproj index 76b9c44..90cee2b 100644 --- a/win32fw.vcxproj +++ b/win32fw.vcxproj @@ -156,6 +156,8 @@ + + @@ -167,6 +169,8 @@ + + diff --git a/win32fw_test/test_application.c b/win32fw_test/test_application.c index 8c14587..b06ccf8 100644 --- a/win32fw_test/test_application.c +++ b/win32fw_test/test_application.c @@ -13,14 +13,15 @@ void TestApplication_Init(TestApplication* app) TestApplication* TestApplication_Create() { - TestApplication* app = calloc(1, sizeof(TestApplication)); + TestApplication* pTestApplication = (TestApplication*)malloc(sizeof(TestApplication)); + memset(pTestApplication, 0, sizeof(TestApplication)); - if (app) + if (pTestApplication) { - TestApplication_Init(app); + TestApplication_Init(pTestApplication); } - return app; + return pTestApplication; } int TestApplication_Run(TestApplication* app) diff --git a/win32fw_test/test_window.c b/win32fw_test/test_window.c index 87ab717..992a27c 100644 --- a/win32fw_test/test_window.c +++ b/win32fw_test/test_window.c @@ -18,14 +18,15 @@ LRESULT CALLBACK TestWindow_UserProc(struct Window*, HWND hWnd, UINT message, WP TestWindow* TestWindow_Create(Application* app) { - TestWindow* window = calloc(1, sizeof(TestWindow)); + TestWindow* pTestWindow = (TestWindow*)malloc(sizeof(TestWindow)); + memset(pTestWindow, 0, sizeof(TestWindow)); - if (window) + if (pTestWindow) { - TestWindow_Init(window, app); + TestWindow_Init(pTestWindow, app); } - return window; + return pTestWindow; } void TestWindow_Init(TestWindow* window, Application* app)