From 29b7c618a395db524e60b5c5fd0f3d512ba188ca Mon Sep 17 00:00:00 2001 From: fawdlstty Date: Sun, 9 Dec 2018 18:01:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DuiLib/3rd/CxImage/tif_xfile.cpp | 221 + DuiLib/3rd/CxImage/xfile.h | 79 + DuiLib/3rd/CxImage/ximabmp.cpp | 448 ++ DuiLib/3rd/CxImage/ximabmp.h | 79 + DuiLib/3rd/CxImage/ximacfg.h | 59 + DuiLib/3rd/CxImage/ximadef.h | 211 + DuiLib/3rd/CxImage/ximadsp.cpp | 3767 ++++++++++ DuiLib/3rd/CxImage/ximaenc.cpp | 1159 +++ DuiLib/3rd/CxImage/ximaexif.cpp | 877 +++ DuiLib/3rd/CxImage/ximage.cpp | 537 ++ DuiLib/3rd/CxImage/ximage.h | 807 +++ DuiLib/3rd/CxImage/ximagif.cpp | 1683 +++++ DuiLib/3rd/CxImage/ximagif.h | 244 + DuiLib/3rd/CxImage/ximahist.cpp | 627 ++ DuiLib/3rd/CxImage/ximaico.cpp | 470 ++ DuiLib/3rd/CxImage/ximaico.h | 58 + DuiLib/3rd/CxImage/ximainfo.cpp | 958 +++ DuiLib/3rd/CxImage/ximaint.cpp | 1045 +++ DuiLib/3rd/CxImage/ximaiter.h | 253 + DuiLib/3rd/CxImage/ximajas.cpp | 325 + DuiLib/3rd/CxImage/ximajas.h | 88 + DuiLib/3rd/CxImage/ximajbg.cpp | 174 + DuiLib/3rd/CxImage/ximajbg.h | 44 + DuiLib/3rd/CxImage/ximajpg.cpp | 542 ++ DuiLib/3rd/CxImage/ximajpg.h | 283 + DuiLib/3rd/CxImage/ximalpha.cpp | 367 + DuiLib/3rd/CxImage/ximalyr.cpp | 116 + DuiLib/3rd/CxImage/ximamng.cpp | 430 ++ DuiLib/3rd/CxImage/ximamng.h | 88 + DuiLib/3rd/CxImage/ximapal.cpp | 834 +++ DuiLib/3rd/CxImage/ximapcx.cpp | 479 ++ DuiLib/3rd/CxImage/ximapcx.h | 64 + DuiLib/3rd/CxImage/ximapng.cpp | 553 ++ DuiLib/3rd/CxImage/ximapng.h | 94 + DuiLib/3rd/CxImage/ximapsd.cpp | 1310 ++++ DuiLib/3rd/CxImage/ximapsd.h | 110 + DuiLib/3rd/CxImage/ximaraw.cpp | 333 + DuiLib/3rd/CxImage/ximaraw.h | 112 + DuiLib/3rd/CxImage/ximasel.cpp | 698 ++ DuiLib/3rd/CxImage/ximaska.cpp | 126 + DuiLib/3rd/CxImage/ximaska.h | 44 + DuiLib/3rd/CxImage/ximatga.cpp | 320 + DuiLib/3rd/CxImage/ximatga.h | 61 + DuiLib/3rd/CxImage/ximath.cpp | 97 + DuiLib/3rd/CxImage/ximath.h | 39 + DuiLib/3rd/CxImage/ximatif.cpp | 982 +++ DuiLib/3rd/CxImage/ximatif.h | 62 + DuiLib/3rd/CxImage/ximatran.cpp | 2728 +++++++ DuiLib/3rd/CxImage/ximawbmp.cpp | 134 + DuiLib/3rd/CxImage/ximawbmp.h | 49 + DuiLib/3rd/CxImage/ximawmf.cpp | 483 ++ DuiLib/3rd/CxImage/ximawmf.h | 154 + DuiLib/3rd/CxImage/ximawnd.cpp | 1900 +++++ DuiLib/3rd/CxImage/xiofile.h | 125 + DuiLib/3rd/CxImage/xmemfile.cpp | 213 + DuiLib/3rd/CxImage/xmemfile.h | 42 + DuiLib/Bind/BindBase.h | 45 + DuiLib/Bind/BindCtrls.hpp | 95 + DuiLib/Bind/StdAfx.h | 1 + DuiLib/Control/StdAfx.h | 1 + DuiLib/Control/UIActiveX.cpp | 1115 +++ DuiLib/Control/UIActiveX.h | 79 + DuiLib/Control/UIAnimation.cpp | 142 + DuiLib/Control/UIAnimation.h | 77 + DuiLib/Control/UIButton.cpp | 472 ++ DuiLib/Control/UIButton.h | 102 + DuiLib/Control/UIColorPalette.cpp | 337 + DuiLib/Control/UIColorPalette.h | 64 + DuiLib/Control/UICombo.cpp | 1048 +++ DuiLib/Control/UICombo.h | 150 + DuiLib/Control/UIComboBox.cpp | 95 + DuiLib/Control/UIComboBox.h | 27 + DuiLib/Control/UIDateTime.cpp | 236 + DuiLib/Control/UIDateTime.h | 36 + DuiLib/Control/UIEdit.cpp | 639 ++ DuiLib/Control/UIEdit.h | 88 + DuiLib/Control/UIFadeButton.cpp | 126 + DuiLib/Control/UIFadeButton.h | 44 + DuiLib/Control/UIFlash.cpp | 217 + DuiLib/Control/UIFlash.h | 63 + DuiLib/Control/UIGifAnim.cpp | 318 + DuiLib/Control/UIGifAnim.h | 55 + DuiLib/Control/UIGifAnimEx.cpp | 164 + DuiLib/Control/UIGifAnimEx.h | 37 + DuiLib/Control/UIGroupBox.cpp | 156 + DuiLib/Control/UIGroupBox.h | 38 + DuiLib/Control/UIHotKey.cpp | 448 ++ DuiLib/Control/UIHotKey.h | 85 + DuiLib/Control/UIIPAddress.cpp | 234 + DuiLib/Control/UIIPAddress.h | 41 + DuiLib/Control/UIIPAddressEx.cpp | Bin 0 -> 21936 bytes DuiLib/Control/UIIPAddressEx.h | 42 + DuiLib/Control/UILabel.cpp | 271 + DuiLib/Control/UILabel.h | 58 + DuiLib/Control/UIList.cpp | 2581 +++++++ DuiLib/Control/UIList.h | 514 ++ DuiLib/Control/UIListEx.cpp | 1318 ++++ DuiLib/Control/UIListEx.h | 313 + DuiLib/Control/UIMenu.cpp | 1095 +++ DuiLib/Control/UIMenu.h | 356 + DuiLib/Control/UIOption.cpp | 383 + DuiLib/Control/UIOption.h | 100 + DuiLib/Control/UIProgress.cpp | 151 + DuiLib/Control/UIProgress.h | 45 + DuiLib/Control/UIRichEdit.cpp | 2424 +++++++ DuiLib/Control/UIRichEdit.h | 194 + DuiLib/Control/UIRing.cpp | 78 + DuiLib/Control/UIRing.h | 33 + DuiLib/Control/UIRollText.cpp | 132 + DuiLib/Control/UIRollText.h | 48 + DuiLib/Control/UIScrollBar.cpp | 863 +++ DuiLib/Control/UIScrollBar.h | 146 + DuiLib/Control/UISlider.cpp | 269 + DuiLib/Control/UISlider.h | 50 + DuiLib/Control/UIText.cpp | 149 + DuiLib/Control/UIText.h | 34 + DuiLib/Control/UITreeView.cpp | 1119 +++ DuiLib/Control/UITreeView.h | 149 + DuiLib/Control/UIWebBrowser.cpp | 632 ++ DuiLib/Control/UIWebBrowser.h | 185 + DuiLib/Core/ControlFactory.cpp | 82 + DuiLib/Core/ControlFactory.h | 36 + DuiLib/Core/StdAfx.h | 1 + DuiLib/Core/UIBase.cpp | 437 ++ DuiLib/Core/UIBase.h | 102 + DuiLib/Core/UIContainer.cpp | 993 +++ DuiLib/Core/UIContainer.h | 137 + DuiLib/Core/UIControl.cpp | 1145 +++ DuiLib/Core/UIControl.h | 267 + DuiLib/Core/UIDefine.h | 313 + DuiLib/Core/UIDlgBuilder.cpp | 401 ++ DuiLib/Core/UIDlgBuilder.h | 36 + DuiLib/Core/UIManager.cpp | 3732 ++++++++++ DuiLib/Core/UIManager.h | 551 ++ DuiLib/Core/UIMarkup.cpp | 485 ++ DuiLib/Core/UIMarkup.h | 112 + DuiLib/Core/UIRender.cpp | 2320 ++++++ DuiLib/Core/UIRender.h | 81 + DuiLib/Core/UIResourceManager.cpp | 203 + DuiLib/Core/UIResourceManager.h | 58 + DuiLib/DuiLib.vcxproj | 1251 ++++ DuiLib/DuiLib.vcxproj.filters | 404 ++ DuiLib/Layout/StdAfx.h | 1 + DuiLib/Layout/UIAnimationTabLayout.cpp | 113 + DuiLib/Layout/UIAnimationTabLayout.h | 39 + DuiLib/Layout/UIChildLayout.cpp | 44 + DuiLib/Layout/UIChildLayout.h | 23 + DuiLib/Layout/UIHorizontalLayout.cpp | 314 + DuiLib/Layout/UIHorizontalLayout.h | 37 + DuiLib/Layout/UITabLayout.cpp | 156 + DuiLib/Layout/UITabLayout.h | 31 + DuiLib/Layout/UITileLayout.cpp | 165 + DuiLib/Layout/UITileLayout.h | 29 + DuiLib/Layout/UIVerticalLayout.cpp | 319 + DuiLib/Layout/UIVerticalLayout.h | 37 + DuiLib/StdAfx.cpp | 8 + DuiLib/StdAfx.h | 37 + DuiLib/UIlib.cpp | 64 + DuiLib/UIlib.h | 141 + DuiLib/Utils/DPI.cpp | 208 + DuiLib/Utils/DPI.h | 56 + DuiLib/Utils/DragDropImpl.cpp | 540 ++ DuiLib/Utils/DragDropImpl.h | 248 + DuiLib/Utils/FawTools.hpp | 312 + DuiLib/Utils/Flash11.tlb | Bin 0 -> 14600 bytes DuiLib/Utils/FlashEventHandler.h | 38 + DuiLib/Utils/StdAfx.h | 1 + DuiLib/Utils/TrayIcon.cpp | 99 + DuiLib/Utils/TrayIcon.h | 44 + DuiLib/Utils/UIDelegate.cpp | 87 + DuiLib/Utils/UIDelegate.h | 92 + DuiLib/Utils/UIShadow.cpp | 621 ++ DuiLib/Utils/UIShadow.h | 106 + DuiLib/Utils/Utils.cpp | 703 ++ DuiLib/Utils/Utils.h | 223 + DuiLib/Utils/VersionHelpers.h | 89 + DuiLib/Utils/WebBrowserEventHandler.h | 136 + DuiLib/Utils/WinImplBase.cpp | 385 + DuiLib/Utils/WinImplBase.h | 79 + DuiLib/Utils/downloadmgr.h | 209 + DuiLib/Utils/flash11.tlh | 384 + DuiLib/Utils/observer_impl_base.h | 116 + DuiLib/Utils/stb_image.h | 6360 +++++++++++++++++ DuiLib/Utils/unzip.cpp | 4142 +++++++++++ DuiLib/Utils/unzip.h | 214 + DuiLib/VC-LTL helper for Visual Studio.props | 70 + NetToolbox.sln | 65 + NetToolbox/3rdparty/Simple-Web-Server | 1 + NetToolbox/3rdparty/serial/list_ports_win.cc | 152 + NetToolbox/3rdparty/serial/serial.cc | 414 ++ NetToolbox/3rdparty/serial/serial.h | 775 ++ NetToolbox/3rdparty/serial/v8stdint.h | 57 + NetToolbox/3rdparty/serial/win.cc | 646 ++ NetToolbox/3rdparty/serial/win.h | 207 + NetToolbox/NetToolbox.vcxproj | 415 ++ NetToolbox/NetToolbox.vcxproj.filters | 263 + NetToolbox/NetToolbox.vcxproj.user | 4 + NetToolbox/NetToolboxWnd.cpp | 298 + NetToolbox/NetToolboxWnd.h | 80 + NetToolbox/Resource.aps | Bin 0 -> 358316 bytes NetToolbox/Resource.rc | Bin 0 -> 6428 bytes NetToolbox/StdAfx.cpp | 1 + NetToolbox/StdAfx.h | 14 + .../VC-LTL helper for Visual Studio.props | 70 + NetToolbox/db/db_Sqlite.hpp | 119 + NetToolbox/hanAnim.hpp | 108 + NetToolbox/main.cpp | 249 + NetToolbox/pages/page_EncDec.hpp | 72 + NetToolbox/pages/page_Example.hpp | 14 + NetToolbox/pages/page_File.hpp | 183 + NetToolbox/pages/page_Gif.hpp | 262 + NetToolbox/pages/page_Http.hpp | 246 + NetToolbox/pages/page_IPScan.hpp | 106 + NetToolbox/pages/page_LocalConn.hpp | 284 + NetToolbox/pages/page_LocalNet.hpp | 76 + NetToolbox/pages/page_Qps.hpp | 305 + NetToolbox/pages/page_Record.hpp | 64 + NetToolbox/pages/page_Regex.hpp | 69 + NetToolbox/pages/page_Rsa.hpp | 61 + NetToolbox/pages/page_SerialPort.hpp | 290 + NetToolbox/pages/page_SysInfo.hpp | 53 + NetToolbox/pages/page_Tracert.hpp | 102 + NetToolbox/pages/page_Window.hpp | 346 + NetToolbox/pages/page_base.hpp | 34 + NetToolbox/res/LICENSE_OpenSSL | 125 + NetToolbox/res/LICENSE_directui | 14 + NetToolbox/res/LICENSE_duilib | 9 + NetToolbox/res/LICENSE_serial | 9 + NetToolbox/res/find_color.cur | Bin 0 -> 4286 bytes NetToolbox/res/find_pos.cur | Bin 0 -> 4286 bytes NetToolbox/res/logo.ico | Bin 0 -> 16958 bytes NetToolbox/res/res.zip | Bin 0 -> 322791 bytes NetToolbox/res/xml.manifest | 22 + NetToolbox/res/zoom_size.cur | Bin 0 -> 4286 bytes NetToolbox/resource.h | 23 + NetToolbox/settings.hpp | 50 + NetToolbox/tools/tool_Base64.hpp | 99 + NetToolbox/tools/tool_DnsLookup.hpp | 136 + NetToolbox/tools/tool_Encoding.hpp | 317 + NetToolbox/tools/tool_Formatting.hpp | 50 + NetToolbox/tools/tool_Gdip.hpp | 158 + NetToolbox/tools/tool_Gzip.hpp | 73 + NetToolbox/tools/tool_MakeAvi.hpp | 118 + NetToolbox/tools/tool_Mutex.hpp | 46 + NetToolbox/tools/tool_NetInfo.hpp | 174 + NetToolbox/tools/tool_PE.hpp | 174 + NetToolbox/tools/tool_Path.hpp | 107 + NetToolbox/tools/tool_Priv.hpp | 39 + NetToolbox/tools/tool_Process.hpp | 100 + NetToolbox/tools/tool_Register.hpp | 194 + NetToolbox/tools/tool_Rsa.hpp | 65 + NetToolbox/tools/tool_SerialPort.hpp | 120 + NetToolbox/tools/tool_String.hpp | 302 + NetToolbox/tools/tool_SysInfo.hpp | 220 + NetToolbox/tools/tool_Tracert.hpp | 116 + NetToolbox/tools/tool_Utils.hpp | 57 + NetToolbox/tools/tool_VP9.hpp | 139 + NetToolbox/tools/tool_WMI.hpp | 86 + NetToolbox/tools/tool_WebRequest.hpp | 68 + NetToolbox/tools/tool_Zoomer.hpp | 96 + NetToolbox_publish.exe | Bin 0 -> 334848 bytes NetToolbox_publish32.exe | Bin 0 -> 334848 bytes README.md | 80 + capture.jpg | Bin 0 -> 98704 bytes res/CtrlButton.png | Bin 0 -> 1077 bytes res/color.png | Bin 0 -> 234948 bytes res/cur_findcolor.xml | 8 + res/find_color.png | Bin 0 -> 529 bytes res/find_pos.png | Bin 0 -> 696 bytes res/findcolor.png | Bin 0 -> 462 bytes res/list_bg.jpg | Bin 0 -> 12561 bytes res/list_ico_0.png | Bin 0 -> 919 bytes res/list_ico_1.png | Bin 0 -> 548 bytes res/list_ico_2.png | Bin 0 -> 837 bytes res/list_sep.png | Bin 0 -> 2801 bytes res/logo.ico | Bin 0 -> 16958 bytes res/logo.png | Bin 0 -> 4902 bytes res/main.xml | 579 ++ res/menu_bk.png | Bin 0 -> 3406 bytes res/menu_expand.png | Bin 0 -> 41111 bytes res/menu_localnet.xml | 11 + res/qb0.png | Bin 0 -> 391 bytes res/qb3.png | Bin 0 -> 468 bytes res/qb4.png | Bin 0 -> 201 bytes res/qb5.png | Bin 0 -> 263 bytes res/qb6.png | Bin 0 -> 332 bytes res/res.xml | 7 + res/scr2gif.xml | 30 + res/scrollbar.png | Bin 0 -> 3606 bytes res/selbox.png | Bin 0 -> 5557 bytes res/status_big.png | Bin 0 -> 22293 bytes res/status_small.png | Bin 0 -> 17142 bytes res/wait.png | Bin 0 -> 3328 bytes res/zoom_size.png | Bin 0 -> 752 bytes 294 files changed, 89585 insertions(+) create mode 100644 DuiLib/3rd/CxImage/tif_xfile.cpp create mode 100644 DuiLib/3rd/CxImage/xfile.h create mode 100644 DuiLib/3rd/CxImage/ximabmp.cpp create mode 100644 DuiLib/3rd/CxImage/ximabmp.h create mode 100644 DuiLib/3rd/CxImage/ximacfg.h create mode 100644 DuiLib/3rd/CxImage/ximadef.h create mode 100644 DuiLib/3rd/CxImage/ximadsp.cpp create mode 100644 DuiLib/3rd/CxImage/ximaenc.cpp create mode 100644 DuiLib/3rd/CxImage/ximaexif.cpp create mode 100644 DuiLib/3rd/CxImage/ximage.cpp create mode 100644 DuiLib/3rd/CxImage/ximage.h create mode 100644 DuiLib/3rd/CxImage/ximagif.cpp create mode 100644 DuiLib/3rd/CxImage/ximagif.h create mode 100644 DuiLib/3rd/CxImage/ximahist.cpp create mode 100644 DuiLib/3rd/CxImage/ximaico.cpp create mode 100644 DuiLib/3rd/CxImage/ximaico.h create mode 100644 DuiLib/3rd/CxImage/ximainfo.cpp create mode 100644 DuiLib/3rd/CxImage/ximaint.cpp create mode 100644 DuiLib/3rd/CxImage/ximaiter.h create mode 100644 DuiLib/3rd/CxImage/ximajas.cpp create mode 100644 DuiLib/3rd/CxImage/ximajas.h create mode 100644 DuiLib/3rd/CxImage/ximajbg.cpp create mode 100644 DuiLib/3rd/CxImage/ximajbg.h create mode 100644 DuiLib/3rd/CxImage/ximajpg.cpp create mode 100644 DuiLib/3rd/CxImage/ximajpg.h create mode 100644 DuiLib/3rd/CxImage/ximalpha.cpp create mode 100644 DuiLib/3rd/CxImage/ximalyr.cpp create mode 100644 DuiLib/3rd/CxImage/ximamng.cpp create mode 100644 DuiLib/3rd/CxImage/ximamng.h create mode 100644 DuiLib/3rd/CxImage/ximapal.cpp create mode 100644 DuiLib/3rd/CxImage/ximapcx.cpp create mode 100644 DuiLib/3rd/CxImage/ximapcx.h create mode 100644 DuiLib/3rd/CxImage/ximapng.cpp create mode 100644 DuiLib/3rd/CxImage/ximapng.h create mode 100644 DuiLib/3rd/CxImage/ximapsd.cpp create mode 100644 DuiLib/3rd/CxImage/ximapsd.h create mode 100644 DuiLib/3rd/CxImage/ximaraw.cpp create mode 100644 DuiLib/3rd/CxImage/ximaraw.h create mode 100644 DuiLib/3rd/CxImage/ximasel.cpp create mode 100644 DuiLib/3rd/CxImage/ximaska.cpp create mode 100644 DuiLib/3rd/CxImage/ximaska.h create mode 100644 DuiLib/3rd/CxImage/ximatga.cpp create mode 100644 DuiLib/3rd/CxImage/ximatga.h create mode 100644 DuiLib/3rd/CxImage/ximath.cpp create mode 100644 DuiLib/3rd/CxImage/ximath.h create mode 100644 DuiLib/3rd/CxImage/ximatif.cpp create mode 100644 DuiLib/3rd/CxImage/ximatif.h create mode 100644 DuiLib/3rd/CxImage/ximatran.cpp create mode 100644 DuiLib/3rd/CxImage/ximawbmp.cpp create mode 100644 DuiLib/3rd/CxImage/ximawbmp.h create mode 100644 DuiLib/3rd/CxImage/ximawmf.cpp create mode 100644 DuiLib/3rd/CxImage/ximawmf.h create mode 100644 DuiLib/3rd/CxImage/ximawnd.cpp create mode 100644 DuiLib/3rd/CxImage/xiofile.h create mode 100644 DuiLib/3rd/CxImage/xmemfile.cpp create mode 100644 DuiLib/3rd/CxImage/xmemfile.h create mode 100644 DuiLib/Bind/BindBase.h create mode 100644 DuiLib/Bind/BindCtrls.hpp create mode 100644 DuiLib/Bind/StdAfx.h create mode 100644 DuiLib/Control/StdAfx.h create mode 100644 DuiLib/Control/UIActiveX.cpp create mode 100644 DuiLib/Control/UIActiveX.h create mode 100644 DuiLib/Control/UIAnimation.cpp create mode 100644 DuiLib/Control/UIAnimation.h create mode 100644 DuiLib/Control/UIButton.cpp create mode 100644 DuiLib/Control/UIButton.h create mode 100644 DuiLib/Control/UIColorPalette.cpp create mode 100644 DuiLib/Control/UIColorPalette.h create mode 100644 DuiLib/Control/UICombo.cpp create mode 100644 DuiLib/Control/UICombo.h create mode 100644 DuiLib/Control/UIComboBox.cpp create mode 100644 DuiLib/Control/UIComboBox.h create mode 100644 DuiLib/Control/UIDateTime.cpp create mode 100644 DuiLib/Control/UIDateTime.h create mode 100644 DuiLib/Control/UIEdit.cpp create mode 100644 DuiLib/Control/UIEdit.h create mode 100644 DuiLib/Control/UIFadeButton.cpp create mode 100644 DuiLib/Control/UIFadeButton.h create mode 100644 DuiLib/Control/UIFlash.cpp create mode 100644 DuiLib/Control/UIFlash.h create mode 100644 DuiLib/Control/UIGifAnim.cpp create mode 100644 DuiLib/Control/UIGifAnim.h create mode 100644 DuiLib/Control/UIGifAnimEx.cpp create mode 100644 DuiLib/Control/UIGifAnimEx.h create mode 100644 DuiLib/Control/UIGroupBox.cpp create mode 100644 DuiLib/Control/UIGroupBox.h create mode 100644 DuiLib/Control/UIHotKey.cpp create mode 100644 DuiLib/Control/UIHotKey.h create mode 100644 DuiLib/Control/UIIPAddress.cpp create mode 100644 DuiLib/Control/UIIPAddress.h create mode 100644 DuiLib/Control/UIIPAddressEx.cpp create mode 100644 DuiLib/Control/UIIPAddressEx.h create mode 100644 DuiLib/Control/UILabel.cpp create mode 100644 DuiLib/Control/UILabel.h create mode 100644 DuiLib/Control/UIList.cpp create mode 100644 DuiLib/Control/UIList.h create mode 100644 DuiLib/Control/UIListEx.cpp create mode 100644 DuiLib/Control/UIListEx.h create mode 100644 DuiLib/Control/UIMenu.cpp create mode 100644 DuiLib/Control/UIMenu.h create mode 100644 DuiLib/Control/UIOption.cpp create mode 100644 DuiLib/Control/UIOption.h create mode 100644 DuiLib/Control/UIProgress.cpp create mode 100644 DuiLib/Control/UIProgress.h create mode 100644 DuiLib/Control/UIRichEdit.cpp create mode 100644 DuiLib/Control/UIRichEdit.h create mode 100644 DuiLib/Control/UIRing.cpp create mode 100644 DuiLib/Control/UIRing.h create mode 100644 DuiLib/Control/UIRollText.cpp create mode 100644 DuiLib/Control/UIRollText.h create mode 100644 DuiLib/Control/UIScrollBar.cpp create mode 100644 DuiLib/Control/UIScrollBar.h create mode 100644 DuiLib/Control/UISlider.cpp create mode 100644 DuiLib/Control/UISlider.h create mode 100644 DuiLib/Control/UIText.cpp create mode 100644 DuiLib/Control/UIText.h create mode 100644 DuiLib/Control/UITreeView.cpp create mode 100644 DuiLib/Control/UITreeView.h create mode 100644 DuiLib/Control/UIWebBrowser.cpp create mode 100644 DuiLib/Control/UIWebBrowser.h create mode 100644 DuiLib/Core/ControlFactory.cpp create mode 100644 DuiLib/Core/ControlFactory.h create mode 100644 DuiLib/Core/StdAfx.h create mode 100644 DuiLib/Core/UIBase.cpp create mode 100644 DuiLib/Core/UIBase.h create mode 100644 DuiLib/Core/UIContainer.cpp create mode 100644 DuiLib/Core/UIContainer.h create mode 100644 DuiLib/Core/UIControl.cpp create mode 100644 DuiLib/Core/UIControl.h create mode 100644 DuiLib/Core/UIDefine.h create mode 100644 DuiLib/Core/UIDlgBuilder.cpp create mode 100644 DuiLib/Core/UIDlgBuilder.h create mode 100644 DuiLib/Core/UIManager.cpp create mode 100644 DuiLib/Core/UIManager.h create mode 100644 DuiLib/Core/UIMarkup.cpp create mode 100644 DuiLib/Core/UIMarkup.h create mode 100644 DuiLib/Core/UIRender.cpp create mode 100644 DuiLib/Core/UIRender.h create mode 100644 DuiLib/Core/UIResourceManager.cpp create mode 100644 DuiLib/Core/UIResourceManager.h create mode 100644 DuiLib/DuiLib.vcxproj create mode 100644 DuiLib/DuiLib.vcxproj.filters create mode 100644 DuiLib/Layout/StdAfx.h create mode 100644 DuiLib/Layout/UIAnimationTabLayout.cpp create mode 100644 DuiLib/Layout/UIAnimationTabLayout.h create mode 100644 DuiLib/Layout/UIChildLayout.cpp create mode 100644 DuiLib/Layout/UIChildLayout.h create mode 100644 DuiLib/Layout/UIHorizontalLayout.cpp create mode 100644 DuiLib/Layout/UIHorizontalLayout.h create mode 100644 DuiLib/Layout/UITabLayout.cpp create mode 100644 DuiLib/Layout/UITabLayout.h create mode 100644 DuiLib/Layout/UITileLayout.cpp create mode 100644 DuiLib/Layout/UITileLayout.h create mode 100644 DuiLib/Layout/UIVerticalLayout.cpp create mode 100644 DuiLib/Layout/UIVerticalLayout.h create mode 100644 DuiLib/StdAfx.cpp create mode 100644 DuiLib/StdAfx.h create mode 100644 DuiLib/UIlib.cpp create mode 100644 DuiLib/UIlib.h create mode 100644 DuiLib/Utils/DPI.cpp create mode 100644 DuiLib/Utils/DPI.h create mode 100644 DuiLib/Utils/DragDropImpl.cpp create mode 100644 DuiLib/Utils/DragDropImpl.h create mode 100644 DuiLib/Utils/FawTools.hpp create mode 100644 DuiLib/Utils/Flash11.tlb create mode 100644 DuiLib/Utils/FlashEventHandler.h create mode 100644 DuiLib/Utils/StdAfx.h create mode 100644 DuiLib/Utils/TrayIcon.cpp create mode 100644 DuiLib/Utils/TrayIcon.h create mode 100644 DuiLib/Utils/UIDelegate.cpp create mode 100644 DuiLib/Utils/UIDelegate.h create mode 100644 DuiLib/Utils/UIShadow.cpp create mode 100644 DuiLib/Utils/UIShadow.h create mode 100644 DuiLib/Utils/Utils.cpp create mode 100644 DuiLib/Utils/Utils.h create mode 100644 DuiLib/Utils/VersionHelpers.h create mode 100644 DuiLib/Utils/WebBrowserEventHandler.h create mode 100644 DuiLib/Utils/WinImplBase.cpp create mode 100644 DuiLib/Utils/WinImplBase.h create mode 100644 DuiLib/Utils/downloadmgr.h create mode 100644 DuiLib/Utils/flash11.tlh create mode 100644 DuiLib/Utils/observer_impl_base.h create mode 100644 DuiLib/Utils/stb_image.h create mode 100644 DuiLib/Utils/unzip.cpp create mode 100644 DuiLib/Utils/unzip.h create mode 100644 DuiLib/VC-LTL helper for Visual Studio.props create mode 100644 NetToolbox.sln create mode 160000 NetToolbox/3rdparty/Simple-Web-Server create mode 100644 NetToolbox/3rdparty/serial/list_ports_win.cc create mode 100644 NetToolbox/3rdparty/serial/serial.cc create mode 100644 NetToolbox/3rdparty/serial/serial.h create mode 100644 NetToolbox/3rdparty/serial/v8stdint.h create mode 100644 NetToolbox/3rdparty/serial/win.cc create mode 100644 NetToolbox/3rdparty/serial/win.h create mode 100644 NetToolbox/NetToolbox.vcxproj create mode 100644 NetToolbox/NetToolbox.vcxproj.filters create mode 100644 NetToolbox/NetToolbox.vcxproj.user create mode 100644 NetToolbox/NetToolboxWnd.cpp create mode 100644 NetToolbox/NetToolboxWnd.h create mode 100644 NetToolbox/Resource.aps create mode 100644 NetToolbox/Resource.rc create mode 100644 NetToolbox/StdAfx.cpp create mode 100644 NetToolbox/StdAfx.h create mode 100644 NetToolbox/VC-LTL helper for Visual Studio.props create mode 100644 NetToolbox/db/db_Sqlite.hpp create mode 100644 NetToolbox/hanAnim.hpp create mode 100644 NetToolbox/main.cpp create mode 100644 NetToolbox/pages/page_EncDec.hpp create mode 100644 NetToolbox/pages/page_Example.hpp create mode 100644 NetToolbox/pages/page_File.hpp create mode 100644 NetToolbox/pages/page_Gif.hpp create mode 100644 NetToolbox/pages/page_Http.hpp create mode 100644 NetToolbox/pages/page_IPScan.hpp create mode 100644 NetToolbox/pages/page_LocalConn.hpp create mode 100644 NetToolbox/pages/page_LocalNet.hpp create mode 100644 NetToolbox/pages/page_Qps.hpp create mode 100644 NetToolbox/pages/page_Record.hpp create mode 100644 NetToolbox/pages/page_Regex.hpp create mode 100644 NetToolbox/pages/page_Rsa.hpp create mode 100644 NetToolbox/pages/page_SerialPort.hpp create mode 100644 NetToolbox/pages/page_SysInfo.hpp create mode 100644 NetToolbox/pages/page_Tracert.hpp create mode 100644 NetToolbox/pages/page_Window.hpp create mode 100644 NetToolbox/pages/page_base.hpp create mode 100644 NetToolbox/res/LICENSE_OpenSSL create mode 100644 NetToolbox/res/LICENSE_directui create mode 100644 NetToolbox/res/LICENSE_duilib create mode 100644 NetToolbox/res/LICENSE_serial create mode 100644 NetToolbox/res/find_color.cur create mode 100644 NetToolbox/res/find_pos.cur create mode 100644 NetToolbox/res/logo.ico create mode 100644 NetToolbox/res/res.zip create mode 100644 NetToolbox/res/xml.manifest create mode 100644 NetToolbox/res/zoom_size.cur create mode 100644 NetToolbox/resource.h create mode 100644 NetToolbox/settings.hpp create mode 100644 NetToolbox/tools/tool_Base64.hpp create mode 100644 NetToolbox/tools/tool_DnsLookup.hpp create mode 100644 NetToolbox/tools/tool_Encoding.hpp create mode 100644 NetToolbox/tools/tool_Formatting.hpp create mode 100644 NetToolbox/tools/tool_Gdip.hpp create mode 100644 NetToolbox/tools/tool_Gzip.hpp create mode 100644 NetToolbox/tools/tool_MakeAvi.hpp create mode 100644 NetToolbox/tools/tool_Mutex.hpp create mode 100644 NetToolbox/tools/tool_NetInfo.hpp create mode 100644 NetToolbox/tools/tool_PE.hpp create mode 100644 NetToolbox/tools/tool_Path.hpp create mode 100644 NetToolbox/tools/tool_Priv.hpp create mode 100644 NetToolbox/tools/tool_Process.hpp create mode 100644 NetToolbox/tools/tool_Register.hpp create mode 100644 NetToolbox/tools/tool_Rsa.hpp create mode 100644 NetToolbox/tools/tool_SerialPort.hpp create mode 100644 NetToolbox/tools/tool_String.hpp create mode 100644 NetToolbox/tools/tool_SysInfo.hpp create mode 100644 NetToolbox/tools/tool_Tracert.hpp create mode 100644 NetToolbox/tools/tool_Utils.hpp create mode 100644 NetToolbox/tools/tool_VP9.hpp create mode 100644 NetToolbox/tools/tool_WMI.hpp create mode 100644 NetToolbox/tools/tool_WebRequest.hpp create mode 100644 NetToolbox/tools/tool_Zoomer.hpp create mode 100644 NetToolbox_publish.exe create mode 100644 NetToolbox_publish32.exe create mode 100644 capture.jpg create mode 100644 res/CtrlButton.png create mode 100644 res/color.png create mode 100644 res/cur_findcolor.xml create mode 100644 res/find_color.png create mode 100644 res/find_pos.png create mode 100644 res/findcolor.png create mode 100644 res/list_bg.jpg create mode 100644 res/list_ico_0.png create mode 100644 res/list_ico_1.png create mode 100644 res/list_ico_2.png create mode 100644 res/list_sep.png create mode 100644 res/logo.ico create mode 100644 res/logo.png create mode 100644 res/main.xml create mode 100644 res/menu_bk.png create mode 100644 res/menu_expand.png create mode 100644 res/menu_localnet.xml create mode 100644 res/qb0.png create mode 100644 res/qb3.png create mode 100644 res/qb4.png create mode 100644 res/qb5.png create mode 100644 res/qb6.png create mode 100644 res/res.xml create mode 100644 res/scr2gif.xml create mode 100644 res/scrollbar.png create mode 100644 res/selbox.png create mode 100644 res/status_big.png create mode 100644 res/status_small.png create mode 100644 res/wait.png create mode 100644 res/zoom_size.png diff --git a/DuiLib/3rd/CxImage/tif_xfile.cpp b/DuiLib/3rd/CxImage/tif_xfile.cpp new file mode 100644 index 0000000..1caea61 --- /dev/null +++ b/DuiLib/3rd/CxImage/tif_xfile.cpp @@ -0,0 +1,221 @@ +/* + * TIFF file IO, using CxFile. + */ + +#ifdef WIN32 + #include +#endif +#include + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_TIF + +#include "../tiff/tiffiop.h" +#include "../tiff/tiffvers.h" + +#include "xfile.h" + +static tsize_t +_tiffReadProcEx(thandle_t fd, tdata_t buf, tsize_t size) +{ + return (tsize_t)((CxFile*)fd)->Read(buf, 1, size); +} + +static tsize_t +_tiffWriteProcEx(thandle_t fd, tdata_t buf, tsize_t size) +{ + return (tsize_t)((CxFile*)fd)->Write(buf, 1, size); +} + +static toff_t +_tiffSeekProcEx(thandle_t fd, toff_t off, int whence) +{ + if ( off == 0xFFFFFFFF ) + return 0xFFFFFFFF; + if (!((CxFile*)fd)->Seek(off, whence)) + return 0xFFFFFFFF; + if (whence == SEEK_SET) + return off; + + return (toff_t)((CxFile*)fd)->Tell(); +} + +// Return nonzero if error +static int +_tiffCloseProcEx(thandle_t /*fd*/) +{ +// return !((CxFile*)fd)->Close(); // "//" needed for memory files + return 0; +} + +#include + +static toff_t +_tiffSizeProcEx(thandle_t fd) +{ + return ((CxFile*)fd)->Size(); +} + +static int +_tiffMapProcEx(thandle_t /*fd*/, tdata_t* /*pbase*/, toff_t* /*psize*/) +{ + return (0); +} + +static void +_tiffUnmapProcEx(thandle_t /*fd*/, tdata_t /*base*/, toff_t /*size*/) +{ +} + +// Open a TIFF file descriptor for read/writing. +/* +TIFF* +TIFFOpen(const char* name, const char* mode) +{ + static const char module[] = "TIFFOpen"; + FILE* stream = fopen(name, mode); + if (stream == NULL) + { + TIFFError(module, "%s: Cannot open", name); + return NULL; + } + return (TIFFFdOpen((int)stream, name, mode)); +} +*/ + +TIFF* +_TIFFFdOpen(void* fd, const char* name, const char* mode) +{ + TIFF* tif; + + tif = TIFFClientOpen(name, mode, + (thandle_t) fd, + _tiffReadProcEx, _tiffWriteProcEx, _tiffSeekProcEx, _tiffCloseProcEx, + _tiffSizeProcEx, _tiffMapProcEx, _tiffUnmapProcEx); + if (tif) + { + tif->tif_fd = (int)fd; + } + return (tif); +} + +extern "C" TIFF* _TIFFOpenEx(CxFile* stream, const char* mode) +{ + return (_TIFFFdOpen(stream, "TIFF IMAGE", mode)); +} + +#ifdef __GNUC__ +extern char* malloc(); +extern char* realloc(); +#else +#include +#endif + +tdata_t +_TIFFmalloc(tsize_t s) +{ + return (malloc((size_t) s)); +} + +void +_TIFFfree(tdata_t p) +{ + free(p); +} + +tdata_t +_TIFFrealloc(tdata_t p, tsize_t s) +{ + return (realloc(p, (size_t) s)); +} + +void +_TIFFmemset(tdata_t p, int v, tsize_t c) +{ + memset(p, v, (size_t) c); +} + +void +_TIFFmemcpy(tdata_t d, const tdata_t s, tsize_t c) +{ + memcpy(d, s, (size_t) c); +} + +int +_TIFFmemcmp(const tdata_t p1, const tdata_t p2, tsize_t c) +{ + return (memcmp(p1, p2, (size_t) c)); +} + +#ifndef UNICODE +#define DbgPrint wvsprintf +#define DbgPrint2 wsprintf +#define DbgMsgBox MessageBox +#else +#define DbgPrint wvsprintfA +#define DbgPrint2 wsprintfA +#define DbgMsgBox MessageBoxA +#endif + +static void +Win32WarningHandler(const char* module, const char* fmt, va_list ap) +{ +#ifdef _DEBUG +#if (!defined(_CONSOLE) && !defined(_WIN32_WCE) && defined(WIN32)) + LPSTR szTitle; + LPSTR szTmp; + LPCSTR szTitleText = "%s Warning"; + LPCSTR szDefaultModule = "TIFFLIB"; + szTmp = (module == NULL) ? (LPSTR)szDefaultModule : (LPSTR)module; + if ((szTitle = (LPSTR)LocalAlloc(LMEM_FIXED, (strlen(szTmp) + + strlen(szTitleText) + strlen(fmt) + 128))) == NULL) + return; + DbgPrint2(szTitle, szTitleText, szTmp); + szTmp = szTitle + (strlen(szTitle)+2); + DbgPrint(szTmp, fmt, ap); + DbgMsgBox(GetFocus(), szTmp, szTitle, MB_OK | MB_ICONINFORMATION); + LocalFree(szTitle); + return; +#else + if (module != NULL) + fprintf(stderr, "%s: ", module); + fprintf(stderr, "Warning, "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, ".\n"); +#endif +#endif +} +TIFFErrorHandler _TIFFwarningHandler = Win32WarningHandler; + +static void +Win32ErrorHandler(const char* module, const char* fmt, va_list ap) +{ +#ifdef _DEBUG +#if (!defined(_CONSOLE) && !defined(_WIN32_WCE) && defined(WIN32)) + LPSTR szTitle; + LPSTR szTmp; + LPCSTR szTitleText = "%s Error"; + LPCSTR szDefaultModule = "TIFFLIB"; + szTmp = (module == NULL) ? (LPSTR)szDefaultModule : (LPSTR)module; + if ((szTitle = (LPSTR)LocalAlloc(LMEM_FIXED, (strlen(szTmp) + + strlen(szTitleText) + strlen(fmt) + 128))) == NULL) + return; + DbgPrint2(szTitle, szTitleText, szTmp); + szTmp = szTitle + (strlen(szTitle)+2); + DbgPrint(szTmp, fmt, ap); + DbgMsgBox(GetFocus(), szTmp, szTitle, MB_OK | MB_ICONEXCLAMATION); + LocalFree(szTitle); + return; +#else + if (module != NULL) + fprintf(stderr, "%s: ", module); + vfprintf(stderr, fmt, ap); + fprintf(stderr, ".\n"); +#endif +#endif +} +TIFFErrorHandler _TIFFerrorHandler = Win32ErrorHandler; + +#endif + diff --git a/DuiLib/3rd/CxImage/xfile.h b/DuiLib/3rd/CxImage/xfile.h new file mode 100644 index 0000000..75e022e --- /dev/null +++ b/DuiLib/3rd/CxImage/xfile.h @@ -0,0 +1,79 @@ +/* + * File: xfile.h + * Purpose: General Purpose File Class + */ +/* + -------------------------------------------------------------------------------- + + COPYRIGHT NOTICE, DISCLAIMER, and LICENSE: + + CxFile (c) 11/May/2002 Davide Pizzolato - www.xdp.it + CxFile version 2.00 23/Aug/2002 + CxFile version 2.10 16/Dec/2007 + + Special thanks to Chris Shearer Cooper for new features, enhancements and bugfixes + + Covered code is provided under this license on an "as is" basis, without warranty + of any kind, either expressed or implied, including, without limitation, warranties + that the covered code is free of defects, merchantable, fit for a particular purpose + or non-infringing. The entire risk as to the quality and performance of the covered + code is with you. Should any covered code prove defective in any respect, you (not + the initial developer or any other contributor) assume the cost of any necessary + servicing, repair or correction. This disclaimer of warranty constitutes an essential + part of this license. No use of any covered code is authorized hereunder except under + this disclaimer. + + Permission is hereby granted to use, copy, modify, and distribute this + source code, or portions hereof, for any purpose, including commercial applications, + freely and without fee, 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. + -------------------------------------------------------------------------------- + */ +#if !defined(__xfile_h) +#define __xfile_h + +#if defined (WIN32) || defined (_WIN32_WCE) + #include +#endif + +#include +#include + +#include "ximadef.h" + +class DLL_EXP CxFile +{ +public: + CxFile(void) { }; + virtual ~CxFile() { }; + + virtual bool Close() = 0; + virtual size_t Read(void *buffer, size_t size, size_t count) = 0; + virtual size_t Write(const void *buffer, size_t size, size_t count) = 0; + virtual bool Seek(int32_t offset, int32_t origin) = 0; + virtual int32_t Tell() = 0; + virtual int32_t Size() = 0; + virtual bool Flush() = 0; + virtual bool Eof() = 0; + virtual int32_t Error() = 0; + virtual bool PutC(uint8_t c) + { + // Default implementation + size_t nWrote = Write(&c, 1, 1); + return (bool)(nWrote == 1); + } + virtual int32_t GetC() = 0; + virtual char * GetS(char *string, int32_t n) = 0; + virtual int32_t Scanf(const char *format, void* output) = 0; +}; + +#endif //__xfile_h diff --git a/DuiLib/3rd/CxImage/ximabmp.cpp b/DuiLib/3rd/CxImage/ximabmp.cpp new file mode 100644 index 0000000..3a4c19b --- /dev/null +++ b/DuiLib/3rd/CxImage/ximabmp.cpp @@ -0,0 +1,448 @@ +/* + * File: ximabmp.cpp + * Purpose: Platform Independent BMP Image Class Loader and Writer + * 07/Aug/2001 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximabmp.h" + +#if CXIMAGE_SUPPORT_BMP + +#include "ximaiter.h" + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageBMP::Encode(CxFile * hFile) +{ + + if (EncodeSafeCheck(hFile)) return false; + + BITMAPFILEHEADER hdr; + + hdr.bfType = 0x4d42; // 'BM' WINDOWS_BITMAP_SIGNATURE + hdr.bfSize = GetSize() + 14 /*sizeof(BITMAPFILEHEADER)*/; + hdr.bfReserved1 = hdr.bfReserved2 = 0; + hdr.bfOffBits = 14 /*sizeof(BITMAPFILEHEADER)*/ + head.biSize + GetPaletteSize(); + + hdr.bfType = m_ntohs(hdr.bfType); + hdr.bfSize = m_ntohl(hdr.bfSize); + hdr.bfOffBits = m_ntohl(hdr.bfOffBits); + +#if CXIMAGE_SUPPORT_ALPHA + if (GetNumColors()==0 && AlphaIsValid()){ + + BITMAPINFOHEADER infohdr; + memcpy(&infohdr,&head,sizeof(BITMAPINFOHEADER)); + infohdr.biCompression = BI_RGB; + infohdr.biBitCount = 32; + uint32_t dwEffWidth = ((((infohdr.biBitCount * infohdr.biWidth) + 31) / 32) * 4); + infohdr.biSizeImage = dwEffWidth * infohdr.biHeight; + + hdr.bfSize = infohdr.biSize + infohdr.biSizeImage + 14 /*sizeof(BITMAPFILEHEADER)*/; + + hdr.bfSize = m_ntohl(hdr.bfSize); + bihtoh(&infohdr); + + // Write the file header + hFile->Write(&hdr,min(14,sizeof(BITMAPFILEHEADER)),1); + hFile->Write(&infohdr,sizeof(BITMAPINFOHEADER),1); + //and DIB+ALPHA interlaced + uint8_t *srcalpha = AlphaGetPointer(); + for(int32_t y = 0; y < infohdr.biHeight; ++y){ + uint8_t *srcdib = GetBits(y); + for(int32_t x = 0; x < infohdr.biWidth; ++x){ + hFile->Write(srcdib,3,1); + hFile->Write(srcalpha,1,1); + srcdib += 3; + ++srcalpha; + } + } + + } else +#endif //CXIMAGE_SUPPORT_ALPHA + { + // Write the file header + hFile->Write(&hdr,min(14,sizeof(BITMAPFILEHEADER)),1); + //copy attributes + memcpy(pDib,&head,sizeof(BITMAPINFOHEADER)); + bihtoh((BITMAPINFOHEADER*)pDib); + // Write the DIB header and the pixels + hFile->Write(pDib,GetSize(),1); + bihtoh((BITMAPINFOHEADER*)pDib); + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageBMP::Decode(CxFile * hFile) +{ + if (hFile == NULL) return false; + + BITMAPFILEHEADER bf; + uint32_t off = hFile->Tell(); // + cx_try { + if (hFile->Read(&bf,min(14,sizeof(bf)),1)==0) cx_throw("Not a BMP"); + + bf.bfSize = m_ntohl(bf.bfSize); + bf.bfOffBits = m_ntohl(bf.bfOffBits); + + if (m_ntohs(bf.bfType) != BFT_BITMAP) { //do we have a RC HEADER? + bf.bfOffBits = 0L; + hFile->Seek(off,SEEK_SET); + } + + BITMAPINFOHEADER bmpHeader; + if (!DibReadBitmapInfo(hFile,&bmpHeader)) cx_throw("Error reading BMP info"); + uint32_t dwCompression=bmpHeader.biCompression; + uint32_t dwBitCount=bmpHeader.biBitCount; //preserve for BI_BITFIELDS compression + bool bIsOldBmp = bmpHeader.biSize == sizeof(BITMAPCOREHEADER); + + bool bTopDownDib = bmpHeader.biHeight<0; // check if it's a top-down bitmap + if (bTopDownDib) bmpHeader.biHeight=-bmpHeader.biHeight; + + if (info.nEscape == -1) { + // Return output dimensions only + head.biWidth = bmpHeader.biWidth; + head.biHeight = bmpHeader.biHeight; + info.dwType = CXIMAGE_FORMAT_BMP; + cx_throw("output dimensions returned"); + } + + if (!Create(bmpHeader.biWidth,bmpHeader.biHeight,bmpHeader.biBitCount,CXIMAGE_FORMAT_BMP)) + cx_throw(""); + + SetXDPI((int32_t) floor(bmpHeader.biXPelsPerMeter * 254.0 / 10000.0 + 0.5)); + SetYDPI((int32_t) floor(bmpHeader.biYPelsPerMeter * 254.0 / 10000.0 + 0.5)); + + if (info.nEscape) cx_throw("Cancelled"); // - cancel decoding + + RGBQUAD *pRgb = GetPalette(); + if (pRgb){ + if (bIsOldBmp){ + // convert a old color table (3 byte entries) to a new + // color table (4 byte entries) + hFile->Read((void*)pRgb,DibNumColors(&bmpHeader) * sizeof(RGBTRIPLE),1); + for (int32_t i=DibNumColors(&head)-1; i>=0; i--){ + pRgb[i].rgbRed = ((RGBTRIPLE *)pRgb)[i].rgbtRed; + pRgb[i].rgbBlue = ((RGBTRIPLE *)pRgb)[i].rgbtBlue; + pRgb[i].rgbGreen = ((RGBTRIPLE *)pRgb)[i].rgbtGreen; + pRgb[i].rgbReserved = (uint8_t)0; + } + } else { + hFile->Read((void*)pRgb,DibNumColors(&bmpHeader) * sizeof(RGBQUAD),1); + //force rgbReserved=0, to avoid problems with some WinXp bitmaps + for (uint32_t i=0; i - cancel decoding + + switch (dwBitCount) { + case 32 : + uint32_t bfmask[3]; + if (dwCompression == BI_BITFIELDS) + { + hFile->Read(bfmask, 12, 1); + } else { + bfmask[0]=0x00FF0000; + bfmask[1]=0x0000FF00; + bfmask[2]=0x000000FF; + } + if (bf.bfOffBits != 0L) hFile->Seek(off + bf.bfOffBits,SEEK_SET); + if (dwCompression == BI_BITFIELDS || dwCompression == BI_RGB){ + int32_t imagesize=4*head.biHeight*head.biWidth; + uint8_t* buff32=(uint8_t*)malloc(imagesize); + if (buff32){ + hFile->Read(buff32, imagesize,1); // read in the pixels + +#if CXIMAGE_SUPPORT_ALPHA + if (dwCompression == BI_RGB){ + AlphaCreate(); + if (AlphaIsValid()){ + bool bAlphaOk = false; + uint8_t* p; + for (int32_t y=0; ySeek(off + bf.bfOffBits,SEEK_SET); + if (dwCompression == BI_RGB){ + hFile->Read(info.pImage, head.biSizeImage,1); // read in the pixels + } else cx_throw("unknown compression"); + break; + case 16 : + { + uint32_t bfmask[3]; + if (dwCompression == BI_BITFIELDS) + { + hFile->Read(bfmask, 12, 1); + } else { + bfmask[0]=0x7C00; bfmask[1]=0x3E0; bfmask[2]=0x1F; //RGB555 + } + // bf.bfOffBits required after the bitfield mask + if (bf.bfOffBits != 0L) hFile->Seek(off + bf.bfOffBits,SEEK_SET); + // read in the pixels + hFile->Read(info.pImage, head.biHeight*((head.biWidth+1)/2)*4,1); + // transform into RGB + Bitfield2RGB(info.pImage,bfmask[0],bfmask[1],bfmask[2],16); + break; + } + case 8 : + case 4 : + case 1 : + if (bf.bfOffBits != 0L) hFile->Seek(off + bf.bfOffBits,SEEK_SET); + switch (dwCompression) { + case BI_RGB : + hFile->Read(info.pImage, head.biSizeImage,1); // read in the pixels + break; + case BI_RLE4 : + { + uint8_t status_byte = 0; + uint8_t second_byte = 0; + int32_t scanline = 0; + int32_t bits = 0; + BOOL low_nibble = FALSE; + CImageIterator iter(this); + + for (BOOL bContinue = TRUE; bContinue && hFile->Read(&status_byte, sizeof(uint8_t), 1);) { + + switch (status_byte) { + case RLE_COMMAND : + hFile->Read(&status_byte, sizeof(uint8_t), 1); + switch (status_byte) { + case RLE_ENDOFLINE : + bits = 0; + scanline++; + low_nibble = FALSE; + break; + case RLE_ENDOFBITMAP : + bContinue=FALSE; + break; + case RLE_DELTA : + { + // read the delta values + uint8_t delta_x; + uint8_t delta_y; + hFile->Read(&delta_x, sizeof(uint8_t), 1); + hFile->Read(&delta_y, sizeof(uint8_t), 1); + // apply them + bits += delta_x / 2; + scanline += delta_y; + break; + } + default : + hFile->Read(&second_byte, sizeof(uint8_t), 1); + uint8_t *sline = iter.GetRow(scanline); + for (int32_t i = 0; i < status_byte; i++) { + if ((uint8_t*)(sline+bits) < (uint8_t*)(info.pImage+head.biSizeImage)){ + if (low_nibble) { + if (i&1) + *(sline + bits) |= (second_byte & 0x0f); + else + *(sline + bits) |= (second_byte & 0xf0)>>4; + bits++; + } else { + if (i&1) + *(sline + bits) = (uint8_t)(second_byte & 0x0f)<<4; + else + *(sline + bits) = (uint8_t)(second_byte & 0xf0); + } + } + + if ((i & 1) && (i != (status_byte - 1))) + hFile->Read(&second_byte, sizeof(uint8_t), 1); + + low_nibble = !low_nibble; + } + if ((((status_byte+1) >> 1) & 1 ) == 1) + hFile->Read(&second_byte, sizeof(uint8_t), 1); + break; + }; + break; + default : + { + uint8_t *sline = iter.GetRow(scanline); + hFile->Read(&second_byte, sizeof(uint8_t), 1); + for (unsigned i = 0; i < status_byte; i++) { + if ((uint8_t*)(sline+bits) < (uint8_t*)(info.pImage+head.biSizeImage)){ + if (low_nibble) { + if (i&1) + *(sline + bits) |= (second_byte & 0x0f); + else + *(sline + bits) |= (second_byte & 0xf0)>>4; + bits++; + } else { + if (i&1) + *(sline + bits) = (uint8_t)(second_byte & 0x0f)<<4; + else + *(sline + bits) = (uint8_t)(second_byte & 0xf0); + } + } + low_nibble = !low_nibble; + } + } + break; + }; + } + break; + } + case BI_RLE8 : + { + uint8_t status_byte = 0; + uint8_t second_byte = 0; + int32_t scanline = 0; + int32_t bits = 0; + CImageIterator iter(this); + + for (BOOL bContinue = TRUE; bContinue && hFile->Read(&status_byte, sizeof(uint8_t), 1);) { + switch (status_byte) { + case RLE_COMMAND : + hFile->Read(&status_byte, sizeof(uint8_t), 1); + switch (status_byte) { + case RLE_ENDOFLINE : + bits = 0; + scanline++; + break; + case RLE_ENDOFBITMAP : + bContinue=FALSE; + break; + case RLE_DELTA : + { + // read the delta values + uint8_t delta_x; + uint8_t delta_y; + hFile->Read(&delta_x, sizeof(uint8_t), 1); + hFile->Read(&delta_y, sizeof(uint8_t), 1); + // apply them + bits += delta_x; + scanline += delta_y; + break; + } + default : + hFile->Read((void *)(iter.GetRow(scanline) + bits), sizeof(uint8_t) * status_byte, 1); + // align run length to even number of bytes + if ((status_byte & 1) == 1) + hFile->Read(&second_byte, sizeof(uint8_t), 1); + bits += status_byte; + break; + }; + break; + default : + uint8_t *sline = iter.GetRow(scanline); + hFile->Read(&second_byte, sizeof(uint8_t), 1); + for (unsigned i = 0; i < status_byte; i++) { + if ((uint8_t*)(sline+bits) < (uint8_t*)(info.pImage+head.biSizeImage)){ + *(sline + bits) = second_byte; + bits++; + } else { + break; + } + } + break; + }; + } + break; + } + default : + cx_throw("compression type not supported"); + } + } + + if (bTopDownDib) Flip(); // + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + if (info.nEscape == -1 && info.dwType == CXIMAGE_FORMAT_BMP) return true; + return false; + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/* ReadDibBitmapInfo() + * + * Will read a file in DIB format and return a global HANDLE to its + * BITMAPINFO. This function will work with both "old" and "new" + * bitmap formats, but will always return a "new" BITMAPINFO. + */ +bool CxImageBMP::DibReadBitmapInfo(CxFile* fh, BITMAPINFOHEADER *pdib) +{ + if ((fh==NULL)||(pdib==NULL)) return false; + + if (fh->Read(pdib,sizeof(BITMAPINFOHEADER),1)==0) return false; + + bihtoh(pdib); + + switch (pdib->biSize) // what type of bitmap info is this? + { + case sizeof(BITMAPINFOHEADER): + break; + + case 64: //sizeof(OS2_BMP_HEADER): + fh->Seek((int32_t)(64 - sizeof(BITMAPINFOHEADER)),SEEK_CUR); + break; + + case 124: //sizeof(BITMAPV5HEADER): + fh->Seek((long)(124-sizeof(BITMAPINFOHEADER)), SEEK_CUR); + break; + + case sizeof(BITMAPCOREHEADER): + { + BITMAPCOREHEADER bc = *(BITMAPCOREHEADER*)pdib; + pdib->biSize = bc.bcSize; + pdib->biWidth = (uint32_t)bc.bcWidth; + pdib->biHeight = (uint32_t)bc.bcHeight; + pdib->biPlanes = bc.bcPlanes; + pdib->biBitCount = bc.bcBitCount; + pdib->biCompression = BI_RGB; + pdib->biSizeImage = 0; + pdib->biXPelsPerMeter = 0; + pdib->biYPelsPerMeter = 0; + pdib->biClrUsed = 0; + pdib->biClrImportant = 0; + + fh->Seek((int32_t)(sizeof(BITMAPCOREHEADER)-sizeof(BITMAPINFOHEADER)), SEEK_CUR); + } + break; + default: + //give a last chance + if (pdib->biSize>(sizeof(BITMAPINFOHEADER))&& + (pdib->biSizeImage>=(uint32_t)(pdib->biHeight*((((pdib->biBitCount*pdib->biWidth)+31)/32)*4)))&& + (pdib->biPlanes==1)&&(pdib->biClrUsed==0)) + { + if (pdib->biCompression==BI_RGB) + fh->Seek((int32_t)(pdib->biSize - sizeof(BITMAPINFOHEADER)),SEEK_CUR); + break; + } + return false; + } + + FixBitmapInfo(pdib); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_BMP +//////////////////////////////////////////////////////////////////////////////// diff --git a/DuiLib/3rd/CxImage/ximabmp.h b/DuiLib/3rd/CxImage/ximabmp.h new file mode 100644 index 0000000..d0f137b --- /dev/null +++ b/DuiLib/3rd/CxImage/ximabmp.h @@ -0,0 +1,79 @@ +/* + * File: ximabmp.h + * Purpose: BMP Image Class Loader and Writer + */ +/* ========================================================== + * CxImageBMP (c) 07/Aug/2001 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * + * Special thanks to Troels Knakkergaard for new features, enhancements and bugfixes + * + * original CImageBMP and CImageIterator implementation are: + * Copyright: (c) 1995, Alejandro Aguilar Sierra + * + * ========================================================== + */ + +#if !defined(__ximaBMP_h) +#define __ximaBMP_h + +#include "ximage.h" + +const int32_t RLE_COMMAND = 0; +const int32_t RLE_ENDOFLINE = 0; +const int32_t RLE_ENDOFBITMAP = 1; +const int32_t RLE_DELTA = 2; + +#if !defined(BI_RLE8) + #define BI_RLE8 1L +#endif +#if !defined(BI_RLE4) + #define BI_RLE4 2L +#endif + +#if CXIMAGE_SUPPORT_BMP + +class CxImageBMP: public CxImage +{ +public: + CxImageBMP(): CxImage(CXIMAGE_FORMAT_BMP) {}; + + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +#endif // CXIMAGE_SUPPORT_ENCODE + +protected: + bool DibReadBitmapInfo(CxFile* fh, BITMAPINFOHEADER *pdib); +}; + +#define BFT_ICON 0x4349 /* 'IC' */ +#define BFT_BITMAP 0x4d42 /* 'BM' */ +#define BFT_CURSOR 0x5450 /* 'PT' */ + +#ifndef WIDTHBYTES +#define WIDTHBYTES(i) ((unsigned)((i+31)&(~31))/8) /* ULONG aligned ! */ +#endif + +#endif + +#define DibWidthBytesN(lpbi, n) (uint32_t)WIDTHBYTES((uint32_t)(lpbi)->biWidth * (uint32_t)(n)) +#define DibWidthBytes(lpbi) DibWidthBytesN(lpbi, (lpbi)->biBitCount) + +#define DibSizeImage(lpbi) ((lpbi)->biSizeImage == 0 \ + ? ((uint32_t)(uint32_t)DibWidthBytes(lpbi) * (uint32_t)(uint32_t)(lpbi)->biHeight) \ + : (lpbi)->biSizeImage) + +#define DibNumColors(lpbi) ((lpbi)->biClrUsed == 0 && (lpbi)->biBitCount <= 8 \ + ? (int32_t)(1 << (int32_t)(lpbi)->biBitCount) \ + : (int32_t)(lpbi)->biClrUsed) + +#define FixBitmapInfo(lpbi) if ((lpbi)->biSizeImage == 0) \ + (lpbi)->biSizeImage = DibSizeImage(lpbi); \ + if ((lpbi)->biClrUsed == 0) \ + (lpbi)->biClrUsed = DibNumColors(lpbi); \ + +#endif diff --git a/DuiLib/3rd/CxImage/ximacfg.h b/DuiLib/3rd/CxImage/ximacfg.h new file mode 100644 index 0000000..4c17ba3 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximacfg.h @@ -0,0 +1,59 @@ +#if !defined(__ximaCFG_h) +#define __ximaCFG_h + +///////////////////////////////////////////////////////////////////////////// +// CxImage supported features +#define CXIMAGE_SUPPORT_ALPHA 0 +#define CXIMAGE_SUPPORT_SELECTION 0 +#define CXIMAGE_SUPPORT_TRANSFORMATION 0 +#define CXIMAGE_SUPPORT_DSP 0 +#define CXIMAGE_SUPPORT_LAYERS 0 +#define CXIMAGE_SUPPORT_INTERPOLATION 0 + +#define CXIMAGE_SUPPORT_DECODE 1 +#define CXIMAGE_SUPPORT_ENCODE 0 // +#define CXIMAGE_SUPPORT_WINDOWS 1 +#define CXIMAGE_SUPPORT_EXIF 0 + +///////////////////////////////////////////////////////////////////////////// +// CxImage supported formats +#define CXIMAGE_SUPPORT_BMP 0 +#define CXIMAGE_SUPPORT_GIF 1 +#define CXIMAGE_SUPPORT_JPG 0 +#define CXIMAGE_SUPPORT_PNG 0 +#define CXIMAGE_SUPPORT_ICO 0 +#define CXIMAGE_SUPPORT_TIF 0 +#define CXIMAGE_SUPPORT_TGA 0 +#define CXIMAGE_SUPPORT_PCX 0 +#define CXIMAGE_SUPPORT_WBMP 0 +#define CXIMAGE_SUPPORT_WMF 0 + +#define CXIMAGE_SUPPORT_JP2 0 +#define CXIMAGE_SUPPORT_JPC 0 +#define CXIMAGE_SUPPORT_PGX 0 +#define CXIMAGE_SUPPORT_PNM 0 +#define CXIMAGE_SUPPORT_RAS 0 + +#define CXIMAGE_SUPPORT_JBG 0 // GPL'd see ../jbig/copying.txt & ../jbig/patents.htm + +#define CXIMAGE_SUPPORT_MNG 0 +#define CXIMAGE_SUPPORT_SKA 0 +#define CXIMAGE_SUPPORT_RAW 0 +#define CXIMAGE_SUPPORT_PSD 0 + +///////////////////////////////////////////////////////////////////////////// +#define CXIMAGE_MAX_MEMORY 268435456 + +#define CXIMAGE_DEFAULT_DPI 96 + +#define CXIMAGE_ERR_NOFILE "null file handler" +#define CXIMAGE_ERR_NOIMAGE "null image!!!" + +#define CXIMAGE_SUPPORT_EXCEPTION_HANDLING 1 + +///////////////////////////////////////////////////////////////////////////// +//color to grey mapping +//#define RGB2GRAY(r,g,b) (((b)*114 + (g)*587 + (r)*299)/1000) +#define RGB2GRAY(r,g,b) (((b)*117 + (g)*601 + (r)*306) >> 10) + +#endif diff --git a/DuiLib/3rd/CxImage/ximadef.h b/DuiLib/3rd/CxImage/ximadef.h new file mode 100644 index 0000000..2cf9393 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximadef.h @@ -0,0 +1,211 @@ +#if !defined(__ximadefs_h) +#define __ximadefs_h + +#include "ximacfg.h" + +// #if /*defined(_AFXDLL)||*/defined(_USRDLL) +// #define DLL_EXP __declspec(dllexport) +// #elif defined(_MSC_VER)&&(_MSC_VER<1200) +// #define DLL_EXP __declspec(dllimport) +// #else +// #define DLL_EXP +// #endif + +#define DLL_EXP + +#if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + #define cx_try try + #define cx_throw(message) throw(message) + #define cx_catch catch (const char *message) +#else + #define cx_try bool cx_error=false; + #define cx_throw(message) {cx_error=true; if(strcmp(message,"")) strncpy(info.szLastError,message,255); goto cx_error_catch;} + #define cx_catch cx_error_catch: char message[]=""; if(cx_error) +#endif + + +#if CXIMAGE_SUPPORT_JP2 || CXIMAGE_SUPPORT_JPC || CXIMAGE_SUPPORT_PGX || CXIMAGE_SUPPORT_PNM || CXIMAGE_SUPPORT_RAS + #define CXIMAGE_SUPPORT_JASPER 1 +#else + #define CXIMAGE_SUPPORT_JASPER 0 +#endif + +#if CXIMAGE_SUPPORT_DSP +#undef CXIMAGE_SUPPORT_TRANSFORMATION + #define CXIMAGE_SUPPORT_TRANSFORMATION 1 +#endif + +#if CXIMAGE_SUPPORT_TRANSFORMATION || CXIMAGE_SUPPORT_TIF || CXIMAGE_SUPPORT_TGA || CXIMAGE_SUPPORT_BMP || CXIMAGE_SUPPORT_WINDOWS + #define CXIMAGE_SUPPORT_BASICTRANSFORMATIONS 1 +#endif + +#if CXIMAGE_SUPPORT_DSP || CXIMAGE_SUPPORT_TRANSFORMATION +#undef CXIMAGE_SUPPORT_INTERPOLATION + #define CXIMAGE_SUPPORT_INTERPOLATION 1 +#endif + +#if (CXIMAGE_SUPPORT_DECODE == 0) +#undef CXIMAGE_SUPPORT_EXIF + #define CXIMAGE_SUPPORT_EXIF 0 +#endif + +#if defined (_WIN32_WCE) + #undef CXIMAGE_SUPPORT_WMF + #define CXIMAGE_SUPPORT_WMF 0 +#endif + +#if !defined(WIN32) && !defined(_WIN32_WCE) + #undef CXIMAGE_SUPPORT_WINDOWS + #define CXIMAGE_SUPPORT_WINDOWS 0 +#endif + +#ifndef min +#define min(a,b) (((a)<(b))?(a):(b)) +#endif +#ifndef max +#define max(a,b) (((a)>(b))?(a):(b)) +#endif + +#ifndef PI + #define PI 3.141592653589793f +#endif + + +#if defined(WIN32) || defined(_WIN32_WCE) +#include +#include +#endif + +#include +#include + +#ifdef __BORLANDC__ + +#ifndef _COMPLEX_DEFINED + +typedef struct tagcomplex { + double x,y; +} _complex; + +#endif + +#define _cabs(c) sqrt(c.x*c.x+c.y*c.y) + +#endif + +#if defined(WIN32) || defined(_WIN32_WCE) + #include "stdint.h" +#endif + +#if !defined(WIN32) && !defined(_WIN32_WCE) + +#include +#include +#include +#include + +typedef uint32_t COLORREF; +typedef void* HANDLE; +typedef void* HRGN; + +#ifndef BOOL +#define BOOL bool +#endif + +#ifndef TRUE +#define TRUE true +#endif + +#ifndef FALSE +#define FALSE false +#endif + +#ifndef TCHAR +#define TCHAR char +#define _T +#endif + +typedef struct tagRECT +{ + int32_t left; + int32_t top; + int32_t right; + int32_t bottom; +} RECT; + +typedef struct tagPOINT +{ + int32_t x; + int32_t y; +} POINT; + +typedef struct tagRGBQUAD { + uint8_t rgbBlue; + uint8_t rgbGreen; + uint8_t rgbRed; + uint8_t rgbReserved; +} RGBQUAD; + +#pragma pack(1) + +typedef struct tagBITMAPINFOHEADER{ + uint32_t biSize; + int32_t biWidth; + int32_t biHeight; + uint16_t biPlanes; + uint16_t biBitCount; + uint32_t biCompression; + uint32_t biSizeImage; + int32_t biXPelsPerMeter; + int32_t biYPelsPerMeter; + uint32_t biClrUsed; + uint32_t biClrImportant; +} BITMAPINFOHEADER; + +typedef struct tagBITMAPFILEHEADER { + uint16_t bfType; + uint32_t bfSize; + uint16_t bfReserved1; + uint16_t bfReserved2; + uint32_t bfOffBits; +} BITMAPFILEHEADER; + +typedef struct tagBITMAPCOREHEADER { + uint32_t bcSize; + uint16_t bcWidth; + uint16_t bcHeight; + uint16_t bcPlanes; + uint16_t bcBitCount; +} BITMAPCOREHEADER; + +typedef struct tagRGBTRIPLE { + uint8_t rgbtBlue; + uint8_t rgbtGreen; + uint8_t rgbtRed; +} RGBTRIPLE; + +#pragma pack() + +#define BI_RGB 0L +#define BI_RLE8 1L +#define BI_RLE4 2L +#define BI_BITFIELDS 3L + +#define GetRValue(rgb) ((uint8_t)(rgb)) +#define GetGValue(rgb) ((uint8_t)(((uint16_t)(rgb)) >> 8)) +#define GetBValue(rgb) ((uint8_t)((rgb)>>16)) +#define RGB(r,g,b) ((COLORREF)(((uint8_t)(r)|((uint16_t)((uint8_t)(g))<<8))|(((uint32_t)(uint8_t)(b))<<16))) + +#ifndef _COMPLEX_DEFINED + +typedef struct tagcomplex { + double x,y; +} _complex; + +#endif + +#define _cabs(c) sqrt(c.x*c.x+c.y*c.y) + +#endif + +#endif //__ximadefs diff --git a/DuiLib/3rd/CxImage/ximadsp.cpp b/DuiLib/3rd/CxImage/ximadsp.cpp new file mode 100644 index 0000000..9019275 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximadsp.cpp @@ -0,0 +1,3767 @@ +// xImaDsp.cpp : DSP functions +/* 07/08/2001 v1.00 - Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximage.h" + +#include "ximaiter.h" + +#if CXIMAGE_SUPPORT_DSP + +//////////////////////////////////////////////////////////////////////////////// +/** + * Converts the image to B&W. + * The OptimalThreshold() function can be used for calculating the optimal threshold. + * \param level: the lightness threshold. + * \return true if everything is ok + */ +bool CxImage::Threshold(uint8_t level) +{ + if (!pDib) return false; + if (head.biBitCount == 1) return true; + + GrayScale(); + + CxImage tmp(head.biWidth,head.biHeight,1); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + for (int32_t y=0;ylevel) + tmp.BlindSetPixelIndex(x,y,1); + else + tmp.BlindSetPixelIndex(x,y,0); + } + } + tmp.SetPaletteColor(0,0,0,0); + tmp.SetPaletteColor(1,255,255,255); + Transfer(tmp); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Converts the image to B&W, using a threshold mask + * \param pThresholdMask: the lightness threshold mask. + * the pThresholdMask image must be grayscale with same with and height of the current image + * \return true if everything is ok + */ +bool CxImage::Threshold(CxImage* pThresholdMask) +{ + if (!pDib) return false; + if (head.biBitCount == 1) return true; + + if (!pThresholdMask) return false; + + if (!pThresholdMask->IsValid() || + !pThresholdMask->IsGrayScale() || + pThresholdMask->GetWidth() != GetWidth() || + pThresholdMask->GetHeight() != GetHeight()){ + strcpy(info.szLastError,"invalid ThresholdMask"); + return false; + } + + GrayScale(); + + CxImage tmp(head.biWidth,head.biHeight,1); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + for (int32_t y=0;ypThresholdMask->BlindGetPixelIndex(x,y)) + tmp.BlindSetPixelIndex(x,y,1); + else + tmp.BlindSetPixelIndex(x,y,0); + } + } + tmp.SetPaletteColor(0,0,0,0); + tmp.SetPaletteColor(1,255,255,255); + Transfer(tmp); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Filters only the pixels with a lightness less (or more) than the threshold level, + * and preserves the colors for the unfiltered pixels. + * \param level = the lightness threshold. + * \param bDirection = false: filter dark pixels, true: filter light pixels + * \param nBkgndColor = filtered pixels are set to nBkgndColor color + * \param bSetAlpha = if true, sets also the alpha component for the filtered pixels, with nBkgndColor.rgbReserved + * \return true if everything is ok + * \author [DP], [wangsongtao] + */ +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Threshold2(uint8_t level, bool bDirection, RGBQUAD nBkgndColor, bool bSetAlpha) +{ + if (!pDib) return false; + if (head.biBitCount == 1) return true; + + CxImage tmp(*this, true, false, false); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + tmp.GrayScale(); + + int32_t xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + for(int32_t y=ymin; y=level) BlindSetPixelColor(x,y,nBkgndColor,bSetAlpha); + } + } + } + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Extract RGB channels from the image. Each channel is an 8 bit grayscale image. + * \param r,g,b: pointers to CxImage objects, to store the splited channels + * \return true if everything is ok + */ +bool CxImage::SplitRGB(CxImage* r,CxImage* g,CxImage* b) +{ + if (!pDib) return false; + if (r==NULL && g==NULL && b==NULL) return false; + + CxImage tmpr(head.biWidth,head.biHeight,8); + CxImage tmpg(head.biWidth,head.biHeight,8); + CxImage tmpb(head.biWidth,head.biHeight,8); + + RGBQUAD color; + for(int32_t y=0; yTransfer(tmpr); + if (g) g->Transfer(tmpg); + if (b) b->Transfer(tmpb); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Extract CMYK channels from the image. Each channel is an 8 bit grayscale image. + * \param c,m,y,k: pointers to CxImage objects, to store the splited channels + * \return true if everything is ok + */ +bool CxImage::SplitCMYK(CxImage* c,CxImage* m,CxImage* y,CxImage* k) +{ + if (!pDib) return false; + if (c==NULL && m==NULL && y==NULL && k==NULL) return false; + + CxImage tmpc(head.biWidth,head.biHeight,8); + CxImage tmpm(head.biWidth,head.biHeight,8); + CxImage tmpy(head.biWidth,head.biHeight,8); + CxImage tmpk(head.biWidth,head.biHeight,8); + + RGBQUAD color; + for(int32_t yy=0; yyTransfer(tmpc); + if (m) m->Transfer(tmpm); + if (y) y->Transfer(tmpy); + if (k) k->Transfer(tmpk); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Extract YUV channels from the image. Each channel is an 8 bit grayscale image. + * \param y,u,v: pointers to CxImage objects, to store the splited channels + * \return true if everything is ok + */ +bool CxImage::SplitYUV(CxImage* y,CxImage* u,CxImage* v) +{ + if (!pDib) return false; + if (y==NULL && u==NULL && v==NULL) return false; + + CxImage tmpy(head.biWidth,head.biHeight,8); + CxImage tmpu(head.biWidth,head.biHeight,8); + CxImage tmpv(head.biWidth,head.biHeight,8); + + RGBQUAD color; + for(int32_t yy=0; yyTransfer(tmpy); + if (u) u->Transfer(tmpu); + if (v) v->Transfer(tmpv); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Extract YIQ channels from the image. Each channel is an 8 bit grayscale image. + * \param y,i,q: pointers to CxImage objects, to store the splited channels + * \return true if everything is ok + */ +bool CxImage::SplitYIQ(CxImage* y,CxImage* i,CxImage* q) +{ + if (!pDib) return false; + if (y==NULL && i==NULL && q==NULL) return false; + + CxImage tmpy(head.biWidth,head.biHeight,8); + CxImage tmpi(head.biWidth,head.biHeight,8); + CxImage tmpq(head.biWidth,head.biHeight,8); + + RGBQUAD color; + for(int32_t yy=0; yyTransfer(tmpy); + if (i) i->Transfer(tmpi); + if (q) q->Transfer(tmpq); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Extract XYZ channels from the image. Each channel is an 8 bit grayscale image. + * \param x,y,z: pointers to CxImage objects, to store the splited channels + * \return true if everything is ok + */ +bool CxImage::SplitXYZ(CxImage* x,CxImage* y,CxImage* z) +{ + if (!pDib) return false; + if (x==NULL && y==NULL && z==NULL) return false; + + CxImage tmpx(head.biWidth,head.biHeight,8); + CxImage tmpy(head.biWidth,head.biHeight,8); + CxImage tmpz(head.biWidth,head.biHeight,8); + + RGBQUAD color; + for(int32_t yy=0; yyTransfer(tmpx); + if (y) y->Transfer(tmpy); + if (z) z->Transfer(tmpz); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Extract HSL channels from the image. Each channel is an 8 bit grayscale image. + * \param h,s,l: pointers to CxImage objects, to store the splited channels + * \return true if everything is ok + */ +bool CxImage::SplitHSL(CxImage* h,CxImage* s,CxImage* l) +{ + if (!pDib) return false; + if (h==NULL && s==NULL && l==NULL) return false; + + CxImage tmph(head.biWidth,head.biHeight,8); + CxImage tmps(head.biWidth,head.biHeight,8); + CxImage tmpl(head.biWidth,head.biHeight,8); + + RGBQUAD color; + for(int32_t y=0; yTransfer(tmph); + if (s) s->Transfer(tmps); + if (l) l->Transfer(tmpl); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#define HSLMAX 255 /* H,L, and S vary over 0-HSLMAX */ +#define RGBMAX 255 /* R,G, and B vary over 0-RGBMAX */ + /* HSLMAX BEST IF DIVISIBLE BY 6 */ + /* RGBMAX, HSLMAX must each fit in a uint8_t. */ +/* Hue is undefined if Saturation is 0 (grey-scale) */ +/* This value determines where the Hue scrollbar is */ +/* initially set for achromatic colors */ +#define HSLUNDEFINED (HSLMAX*2/3) +//////////////////////////////////////////////////////////////////////////////// +RGBQUAD CxImage::RGBtoHSL(RGBQUAD lRGBColor) +{ + uint8_t R,G,B; /* input RGB values */ + uint8_t H,L,S; /* output HSL values */ + uint8_t cMax,cMin; /* max and min RGB values */ + uint16_t Rdelta,Gdelta,Bdelta; /* intermediate value: % of spread from max*/ + + R = lRGBColor.rgbRed; /* get R, G, and B out of uint32_t */ + G = lRGBColor.rgbGreen; + B = lRGBColor.rgbBlue; + + cMax = max( max(R,G), B); /* calculate lightness */ + cMin = min( min(R,G), B); + L = (uint8_t)((((cMax+cMin)*HSLMAX)+RGBMAX)/(2*RGBMAX)); + + if (cMax==cMin){ /* r=g=b --> achromatic case */ + S = 0; /* saturation */ + H = HSLUNDEFINED; /* hue */ + } else { /* chromatic case */ + if (L <= (HSLMAX/2)) /* saturation */ + S = (uint8_t)((((cMax-cMin)*HSLMAX)+((cMax+cMin)/2))/(cMax+cMin)); + else + S = (uint8_t)((((cMax-cMin)*HSLMAX)+((2*RGBMAX-cMax-cMin)/2))/(2*RGBMAX-cMax-cMin)); + /* hue */ + Rdelta = (uint16_t)((((cMax-R)*(HSLMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin)); + Gdelta = (uint16_t)((((cMax-G)*(HSLMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin)); + Bdelta = (uint16_t)((((cMax-B)*(HSLMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin)); + + if (R == cMax) + H = (uint8_t)(Bdelta - Gdelta); + else if (G == cMax) + H = (uint8_t)((HSLMAX/3) + Rdelta - Bdelta); + else /* B == cMax */ + H = (uint8_t)(((2*HSLMAX)/3) + Gdelta - Rdelta); + +// if (H < 0) H += HSLMAX; //always false + if (H > HSLMAX) H -= HSLMAX; + } + RGBQUAD hsl={L,S,H,0}; + return hsl; +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::HueToRGB(float n1,float n2, float hue) +{ + // fixed implementation for HSL2RGB routine + float rValue; + + if (hue > 360) + hue = hue - 360; + else if (hue < 0) + hue = hue + 360; + + if (hue < 60) + rValue = n1 + (n2-n1)*hue/60.0f; + else if (hue < 180) + rValue = n2; + else if (hue < 240) + rValue = n1+(n2-n1)*(240-hue)/60; + else + rValue = n1; + + return rValue; +} +//////////////////////////////////////////////////////////////////////////////// +RGBQUAD CxImage::HSLtoRGB(COLORREF cHSLColor) +{ + return HSLtoRGB(RGBtoRGBQUAD(cHSLColor)); +} +//////////////////////////////////////////////////////////////////////////////// +RGBQUAD CxImage::HSLtoRGB(RGBQUAD lHSLColor) +{ + // fixed implementation for HSL2RGB routine + float h,s,l; + float m1,m2; + uint8_t r,g,b; + + h = (float)lHSLColor.rgbRed * 360.0f/255.0f; + s = (float)lHSLColor.rgbGreen/255.0f; + l = (float)lHSLColor.rgbBlue/255.0f; + + if (l <= 0.5) m2 = l * (1+s); + else m2 = l + s - l*s; + + m1 = 2 * l - m2; + + if (s == 0) { + r=g=b=(uint8_t)(l*255.0f); + } else { + r = (uint8_t)(HueToRGB(m1,m2,h+120) * 255.0f); + g = (uint8_t)(HueToRGB(m1,m2,h) * 255.0f); + b = (uint8_t)(HueToRGB(m1,m2,h-120) * 255.0f); + } + + RGBQUAD rgb = {b,g,r,0}; + return rgb; +} +//////////////////////////////////////////////////////////////////////////////// +RGBQUAD CxImage::YUVtoRGB(RGBQUAD lYUVColor) +{ + int32_t U,V,R,G,B; + float Y = lYUVColor.rgbRed; + U = lYUVColor.rgbGreen - 128; + V = lYUVColor.rgbBlue - 128; + +// R = (int32_t)(1.164 * Y + 2.018 * U); +// G = (int32_t)(1.164 * Y - 0.813 * V - 0.391 * U); +// B = (int32_t)(1.164 * Y + 1.596 * V); + R = (int32_t)( Y + 1.403f * V); + G = (int32_t)( Y - 0.344f * U - 0.714f * V); + B = (int32_t)( Y + 1.770f * U); + + R= min(255,max(0,R)); + G= min(255,max(0,G)); + B= min(255,max(0,B)); + RGBQUAD rgb={(uint8_t)B,(uint8_t)G,(uint8_t)R,0}; + return rgb; +} +//////////////////////////////////////////////////////////////////////////////// +RGBQUAD CxImage::RGBtoYUV(RGBQUAD lRGBColor) +{ + int32_t Y,U,V,R,G,B; + R = lRGBColor.rgbRed; + G = lRGBColor.rgbGreen; + B = lRGBColor.rgbBlue; + +// Y = (int32_t)( 0.257 * R + 0.504 * G + 0.098 * B); +// U = (int32_t)( 0.439 * R - 0.368 * G - 0.071 * B + 128); +// V = (int32_t)(-0.148 * R - 0.291 * G + 0.439 * B + 128); + Y = (int32_t)(0.299f * R + 0.587f * G + 0.114f * B); + U = (int32_t)((B-Y) * 0.565f + 128); + V = (int32_t)((R-Y) * 0.713f + 128); + + Y= min(255,max(0,Y)); + U= min(255,max(0,U)); + V= min(255,max(0,V)); + RGBQUAD yuv={(uint8_t)V,(uint8_t)U,(uint8_t)Y,0}; + return yuv; +} +//////////////////////////////////////////////////////////////////////////////// +RGBQUAD CxImage::YIQtoRGB(RGBQUAD lYIQColor) +{ + int32_t I,Q,R,G,B; + float Y = lYIQColor.rgbRed; + I = lYIQColor.rgbGreen - 128; + Q = lYIQColor.rgbBlue - 128; + + R = (int32_t)( Y + 0.956f * I + 0.621f * Q); + G = (int32_t)( Y - 0.273f * I - 0.647f * Q); + B = (int32_t)( Y - 1.104f * I + 1.701f * Q); + + R= min(255,max(0,R)); + G= min(255,max(0,G)); + B= min(255,max(0,B)); + RGBQUAD rgb={(uint8_t)B,(uint8_t)G,(uint8_t)R,0}; + return rgb; +} +//////////////////////////////////////////////////////////////////////////////// +RGBQUAD CxImage::RGBtoYIQ(RGBQUAD lRGBColor) +{ + int32_t Y,I,Q,R,G,B; + R = lRGBColor.rgbRed; + G = lRGBColor.rgbGreen; + B = lRGBColor.rgbBlue; + + Y = (int32_t)( 0.2992f * R + 0.5868f * G + 0.1140f * B); + I = (int32_t)( 0.5960f * R - 0.2742f * G - 0.3219f * B + 128); + Q = (int32_t)( 0.2109f * R - 0.5229f * G + 0.3120f * B + 128); + + Y= min(255,max(0,Y)); + I= min(255,max(0,I)); + Q= min(255,max(0,Q)); + RGBQUAD yiq={(uint8_t)Q,(uint8_t)I,(uint8_t)Y,0}; + return yiq; +} +//////////////////////////////////////////////////////////////////////////////// +RGBQUAD CxImage::XYZtoRGB(RGBQUAD lXYZColor) +{ + int32_t X,Y,Z,R,G,B; + X = lXYZColor.rgbRed; + Y = lXYZColor.rgbGreen; + Z = lXYZColor.rgbBlue; + double k=1.088751; + + R = (int32_t)( 3.240479f * X - 1.537150f * Y - 0.498535f * Z * k); + G = (int32_t)( -0.969256f * X + 1.875992f * Y + 0.041556f * Z * k); + B = (int32_t)( 0.055648f * X - 0.204043f * Y + 1.057311f * Z * k); + + R= min(255,max(0,R)); + G= min(255,max(0,G)); + B= min(255,max(0,B)); + RGBQUAD rgb={(uint8_t)B,(uint8_t)G,(uint8_t)R,0}; + return rgb; +} +//////////////////////////////////////////////////////////////////////////////// +RGBQUAD CxImage::RGBtoXYZ(RGBQUAD lRGBColor) +{ + int32_t X,Y,Z,R,G,B; + R = lRGBColor.rgbRed; + G = lRGBColor.rgbGreen; + B = lRGBColor.rgbBlue; + + X = (int32_t)( 0.412453f * R + 0.357580f * G + 0.180423f * B); + Y = (int32_t)( 0.212671f * R + 0.715160f * G + 0.072169f * B); + Z = (int32_t)((0.019334f * R + 0.119193f * G + 0.950227f * B)*0.918483657f); + + //X= min(255,max(0,X)); + //Y= min(255,max(0,Y)); + //Z= min(255,max(0,Z)); + RGBQUAD xyz={(uint8_t)Z,(uint8_t)Y,(uint8_t)X,0}; + return xyz; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Generates a "rainbow" palette with saturated colors + * \param correction: 1 generates a single hue spectrum. 0.75 is nice for scientific applications. + */ +void CxImage::HuePalette(float correction) +{ + if (head.biClrUsed==0) return; + + for(uint32_t j=0; j 1.0f) blend = 1.0f; + int32_t a0 = (int32_t)(256*blend); + int32_t a1 = 256 - a0; + + bool bFullBlend = false; + if (blend > 0.999f) bFullBlend = true; + + RGBQUAD color,hsl; + if (head.biClrUsed==0){ + + int32_t xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + for(int32_t y=ymin; y>8); + color.rgbBlue = (uint8_t)((hsl.rgbBlue * a0 + color.rgbBlue * a1)>>8); + color.rgbGreen = (uint8_t)((hsl.rgbGreen * a0 + color.rgbGreen * a1)>>8); + BlindSetPixelColor(x,y,color); + } + } + } + } + } else { + for(uint32_t j=0; j + for (int32_t i=0;i<256;i++) { + cTable[i] = (uint8_t)max(0,min(255,(int32_t)((i-128)*c + brightness + 0.5f))); + } + + return Lut(cTable); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return mean lightness of the image. Useful with Threshold() and Light() + */ +float CxImage::Mean() +{ + if (!pDib) return 0; + + CxImage tmp(*this,true); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + tmp.GrayScale(); + float sum=0; + + int32_t xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + if (xmin==xmax || ymin==ymax) return (float)0.0; + + uint8_t *iSrc=tmp.info.pImage; + iSrc += tmp.info.dwEffWidth*ymin; // necessary for selections + + for(int32_t y=ymin; y + for(int32_t x=xmin; x(y+j) || (y+j)>=head.biHeight) continue; + iY = iY2+x; + for(int32_t k=-k2;k(x+k) || (x+k)>=head.biWidth) continue; + i=kernel[iCount]; + b += cPtr[iY+k] * i; + ksumcur += i; + } + } + if (Kfactor==0 || ksumcur==0){ + cPtr2[iY1] = (uint8_t)min(255, max(0,(int32_t)(b + Koffset))); + } else if (ksumtot == ksumcur) { + cPtr2[iY1] = (uint8_t)min(255, max(0,(int32_t)(b/Kfactor + Koffset))); + } else { + cPtr2[iY1] = (uint8_t)min(255, max(0,(int32_t)((b*ksumtot)/(ksumcur*Kfactor) + Koffset))); + } + } + } + } + } + else + { + for(int32_t y=ymin; y r) r=c.rgbRed; + if (c.rgbGreen > g) g=c.rgbGreen; + if (c.rgbBlue > b) b=c.rgbBlue; + } + } + c.rgbRed = r; + c.rgbGreen = g; + c.rgbBlue = b; + tmp.BlindSetPixelColor(x,y,c); + } + } + } + Transfer(tmp); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Enhance the variations between adjacent pixels. + * Similar results can be achieved using Filter(), + * but the algorithms are different both in Edge() and in Contour(). + * \param Ksize: size of the kernel. + * \return true if everything is ok + */ +bool CxImage::Edge(int32_t Ksize) +{ + if (!pDib) return false; + + int32_t k2 = Ksize/2; + int32_t kmax= Ksize-k2; + uint8_t r,g,b,rr,gg,bb; + RGBQUAD c; + + CxImage tmp(*this); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + int32_t xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + for(int32_t y=ymin; y r) r=c.rgbRed; + if (c.rgbGreen > g) g=c.rgbGreen; + if (c.rgbBlue > b) b=c.rgbBlue; + + if (c.rgbRed < rr) rr=c.rgbRed; + if (c.rgbGreen < gg) gg=c.rgbGreen; + if (c.rgbBlue < bb) bb=c.rgbBlue; + } + } + c.rgbRed = (uint8_t)(255-abs(r-rr)); + c.rgbGreen = (uint8_t)(255-abs(g-gg)); + c.rgbBlue = (uint8_t)(255-abs(b-bb)); + tmp.BlindSetPixelColor(x,y,c); + } + } + } + Transfer(tmp); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Blends two images + * \param imgsrc2: image to be mixed with this + * \param op: blending method; see ImageOpType + * \param lXOffset, lYOffset: image displacement + * \param bMixAlpha: if true and imgsrc2 has a valid alpha layer, it will be mixed in the destination image. + * \return true if everything is ok + * \author [Mwolski],[brunom] + */ +void CxImage::Mix(CxImage & imgsrc2, ImageOpType op, int32_t lXOffset, int32_t lYOffset, bool bMixAlpha) +{ + int32_t lWide = min(GetWidth(),imgsrc2.GetWidth()-lXOffset); + int32_t lHeight = min(GetHeight(),imgsrc2.GetHeight()-lYOffset); + + bool bEditAlpha = false; + +#if CXIMAGE_SUPPORT_ALPHA + bEditAlpha = imgsrc2.AlphaIsValid() & bMixAlpha; + if (bEditAlpha && AlphaIsValid()==false){ + AlphaCreate(); + } +#endif //CXIMAGE_SUPPORT_ALPHA + + RGBQUAD rgbBackgrnd1 = GetTransColor(); + RGBQUAD rgb1, rgb2, rgbDest; + + for(int32_t lY=0;lY 250) ){ + rgbDest = rgb2; + } else { + // Alpha Blending with associative calculation merge + // (http://en.wikipedia.org/wiki/Alpha_compositing) + int32_t a0,a1,a2; + // Transparency of the superimposed image + a2 = rgb2.rgbReserved; + // Calculation transparency of the underlying image + a1 = (rgb1.rgbReserved * (255 - a2)) >> 8; + // total transparency of the new pixel + a0 = a2 + a1; + // New transparency assume (a0 == 0 is the restriction s.o. (range 5-250) intercepted) + if (bEditAlpha) rgbDest.rgbReserved = a0; + // each color channel to calculate + rgbDest.rgbBlue = (BYTE)((rgb2.rgbBlue * a2 + a1 * rgb1.rgbBlue )/a0); + rgbDest.rgbGreen = (BYTE)((rgb2.rgbGreen * a2 + a1 * rgb1.rgbGreen)/a0); + rgbDest.rgbRed = (BYTE)((rgb2.rgbRed * a2 + a1 * rgb1.rgbRed )/a0); + } + } else { + rgbDest = rgb1; + rgbDest.rgbReserved = 0; + } + break; + default: + return; + } + SetPixelColor(lX,lY,rgbDest,bEditAlpha); + } + } + } +} +//////////////////////////////////////////////////////////////////////////////// +// thanks to Kenneth Ballard +void CxImage::MixFrom(CxImage & imagesrc2, int32_t lXOffset, int32_t lYOffset) +{ + int32_t width = imagesrc2.GetWidth(); + int32_t height = imagesrc2.GetHeight(); + + int32_t x, y; + + if (imagesrc2.IsTransparent()) { + for(x = 0; x < width; x++) { + for(y = 0; y < height; y++) { + if(!imagesrc2.IsTransparent(x,y)){ + SetPixelColor(x + lXOffset, y + lYOffset, imagesrc2.BlindGetPixelColor(x, y)); + } + } + } + } else { //no transparency so just set it + for(x = 0; x < width; x++) { + for(y = 0; y < height; y++) { + SetPixelColor(x + lXOffset, y + lYOffset, imagesrc2.BlindGetPixelColor(x, y)); + } + } + } +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Adjusts separately the red, green, and blue values in the image. + * \param r, g, b: can be from -255 to +255. + * \return true if everything is ok + */ +bool CxImage::ShiftRGB(int32_t r, int32_t g, int32_t b) +{ + if (!pDib) return false; + RGBQUAD color; + if (head.biClrUsed==0){ + + int32_t xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + for(int32_t y=ymin; y + for (int32_t i=0;i<256;i++) { + cTable[i] = (uint8_t)max(0,min(255,(int32_t)( pow((double)i, dinvgamma) / dMax))); + } + + return Lut(cTable); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Adjusts the color balance indipendent for each color channel + * \param gammaR, gammaG, gammaB can be from 0.1 to 5. + * \return true if everything is ok + * \sa Gamma + */ +bool CxImage::GammaRGB(float gammaR, float gammaG, float gammaB) +{ + if (!pDib) return false; + + if (gammaR <= 0.0f) return false; + if (gammaG <= 0.0f) return false; + if (gammaB <= 0.0f) return false; + + double dinvgamma, dMax; + int32_t i; + + dinvgamma = 1/gammaR; + dMax = pow(255.0, dinvgamma) / 255.0; + uint8_t cTableR[256]; + for (i=0;i<256;i++) { + cTableR[i] = (uint8_t)max(0,min(255,(int32_t)( pow((double)i, dinvgamma) / dMax))); + } + + dinvgamma = 1/gammaG; + dMax = pow(255.0, dinvgamma) / 255.0; + uint8_t cTableG[256]; + for (i=0;i<256;i++) { + cTableG[i] = (uint8_t)max(0,min(255,(int32_t)( pow((double)i, dinvgamma) / dMax))); + } + + dinvgamma = 1/gammaB; + dMax = pow(255.0, dinvgamma) / 255.0; + uint8_t cTableB[256]; + for (i=0;i<256;i++) { + cTableB[i] = (uint8_t)max(0,min(255,(int32_t)( pow((double)i, dinvgamma) / dMax))); + } + + return Lut(cTableR, cTableG, cTableB); +} +//////////////////////////////////////////////////////////////////////////////// + +//#if !defined (_WIN32_WCE) +/** + * Adjusts the intensity of each pixel to the median intensity of its surrounding pixels. + * \param Ksize: size of the kernel. + * \return true if everything is ok + */ +bool CxImage::Median(int32_t Ksize) +{ + if (!pDib) return false; + + int32_t k2 = Ksize/2; + int32_t kmax= Ksize-k2; + int32_t i,j,k; + + RGBQUAD* kernel = (RGBQUAD*)malloc(Ksize*Ksize*sizeof(RGBQUAD)); + + CxImage tmp(*this); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + int32_t xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + for(int32_t y=ymin; y + for(int32_t x=xmin; xGetWidth(); + h=srcReal->GetHeight(); + } else { + w=srcImag->GetWidth(); + h=srcImag->GetHeight(); + } + + bool bXpow2 = IsPowerof2(w); + bool bYpow2 = IsPowerof2(h); + //if bForceFFT, width AND height must be powers of 2 + if (bForceFFT && !(bXpow2 && bYpow2)) { + int32_t i; + + i=0; + while((1< copy the image + if (srcReal && dstReal) tmpReal->Copy(*srcReal,true,false,false); + if (srcImag && dstImag) tmpImag->Copy(*srcImag,true,false,false); + + // dst&&src are empty -> create new one, else turn to GrayScale + if (srcReal==0 && dstReal==0){ + tmpReal = new CxImage(w,h,8); + tmpReal->Clear(0); + tmpReal->SetGrayPalette(); + } else { + if (!tmpReal->IsGrayScale()) tmpReal->GrayScale(); + } + if (srcImag==0 && dstImag==0){ + tmpImag = new CxImage(w,h,8); + tmpImag->Clear(0); + tmpImag->SetGrayPalette(); + } else { + if (!tmpImag->IsGrayScale()) tmpImag->GrayScale(); + } + + if (!(tmpReal->IsValid() && tmpImag->IsValid())){ + if (srcReal==0 && dstReal==0) delete tmpReal; + if (srcImag==0 && dstImag==0) delete tmpImag; + return false; + } + + //resample for FFT, if necessary + tmpReal->Resample(w,h,0); + tmpImag->Resample(w,h,0); + + //ok, here we have 2 (w x h), grayscale images ready for a FFT + + double* real; + double* imag; + int32_t j,k,m; + + _complex **grid; + //double mean = tmpReal->Mean(); + /* Allocate memory for the grid */ + grid = (_complex **)malloc(w * sizeof(_complex)); + for (k=0;kGetPixelIndex(k,j)-128; + grid[k][j].y = tmpImag->GetPixelIndex(k,j)-128; + } + } + + //DFT buffers + double *real2,*imag2; + real2 = (double*)malloc(max(w,h) * sizeof(double)); + imag2 = (double*)malloc(max(w,h) * sizeof(double)); + + /* Transform the rows */ + real = (double *)malloc(w * sizeof(double)); + imag = (double *)malloc(w * sizeof(double)); + + m=0; + while((1<SetPixelIndex(k,j,(uint8_t)max(0,min(255,(nn*(3+log(_cabs(grid[k][j]))))))); + if (grid[k][j].x==0){ + tmpImag->SetPixelIndex(k,j,(uint8_t)max(0,min(255,(128+(atan(grid[k][j].y/0.0000000001)*nn))))); + } else { + tmpImag->SetPixelIndex(k,j,(uint8_t)max(0,min(255,(128+(atan(grid[k][j].y/grid[k][j].x)*nn))))); + } + } else { + tmpReal->SetPixelIndex(k,j,(uint8_t)max(0,min(255,(128 + grid[k][j].x*nn)))); + tmpImag->SetPixelIndex(k,j,(uint8_t)max(0,min(255,(128 + grid[k][j].y*nn)))); + } + } + } + + for (k=0;k> 1; + j = 0; + for (i=0;i>= 1; + } + j += k; + } + + /* Compute the FFT */ + c1 = -1.0; + c2 = 0.0; + l2 = 1; + for (l=0;lGetWidth(); + int32_t h = r->GetHeight(); + + Create(w,h,24); + + g->Resample(w,h); + b->Resample(w,h); + + if (a) { + a->Resample(w,h); +#if CXIMAGE_SUPPORT_ALPHA + AlphaCreate(); +#endif //CXIMAGE_SUPPORT_ALPHA + } + + RGBQUAD c; + for (int32_t y=0;y + for (int32_t x=0;xGetPixelIndex(x,y); + c.rgbGreen=g->GetPixelIndex(x,y); + c.rgbBlue=b->GetPixelIndex(x,y); + switch (colorspace){ + case 1: + BlindSetPixelColor(x,y,HSLtoRGB(c)); + break; + case 2: + BlindSetPixelColor(x,y,YUVtoRGB(c)); + break; + case 3: + BlindSetPixelColor(x,y,YIQtoRGB(c)); + break; + case 4: + BlindSetPixelColor(x,y,XYZtoRGB(c)); + break; + default: + BlindSetPixelColor(x,y,c); + } +#if CXIMAGE_SUPPORT_ALPHA + if (a) AlphaSet(x,y,a->GetPixelIndex(x,y)); +#endif //CXIMAGE_SUPPORT_ALPHA + } + } + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Smart blurring to remove small defects, dithering or artifacts. + * \param radius: normally between 0.01 and 0.5 + * \param niterations: should be trimmed with radius, to avoid blurring should be (radius*niterations)<1 + * \param colorspace: 0 = RGB, 1 = HSL, 2 = YUV, 3 = YIQ, 4 = XYZ + * \return true if everything is ok + */ +bool CxImage::Repair(float radius, int32_t niterations, int32_t colorspace) +{ + if (!IsValid()) return false; + + int32_t w = GetWidth(); + int32_t h = GetHeight(); + + CxImage r,g,b; + + r.Create(w,h,8); + g.Create(w,h,8); + b.Create(w,h,8); + + switch (colorspace){ + case 1: + SplitHSL(&r,&g,&b); + break; + case 2: + SplitYUV(&r,&g,&b); + break; + case 3: + SplitYIQ(&r,&g,&b); + break; + case 4: + SplitXYZ(&r,&g,&b); + break; + default: + SplitRGB(&r,&g,&b); + } + + for (int32_t i=0; iGetWidth()-1; + int32_t h = ch->GetHeight()-1; + + double correction,ix,iy,ixx,ixy,iyy; + int32_t x,y,xy0,xp1,xm1,yp1,ym1; + + for(x=1; xBlindGetPixelIndex(x,y); + xm1 = ch->BlindGetPixelIndex(x-1,y); + xp1 = ch->BlindGetPixelIndex(x+1,y); + ym1 = ch->BlindGetPixelIndex(x,y-1); + yp1 = ch->BlindGetPixelIndex(x,y+1); + + ix= (xp1-xm1)/2.0; + iy= (yp1-ym1)/2.0; + ixx= xp1 - 2.0 * xy0 + xm1; + iyy= yp1 - 2.0 * xy0 + ym1; + ixy=(ch->BlindGetPixelIndex(x+1,y+1) + ch->BlindGetPixelIndex(x-1,y-1) - + ch->BlindGetPixelIndex(x-1,y+1) - ch->BlindGetPixelIndex(x+1,y-1))/4.0; + + correction = ((1.0+iy*iy)*ixx - ix*iy*ixy + (1.0+ix*ix)*iyy)/(1.0+ix*ix+iy*iy); + + tmp.BlindSetPixelIndex(x,y,(uint8_t)min(255,max(0,(xy0 + radius * correction + 0.5)))); + } + } + + for (x=0;x<=w;x++){ + for(y=0; y<=h; y+=h){ + xy0 = ch->BlindGetPixelIndex(x,y); + xm1 = ch->GetPixelIndex(x-1,y); + xp1 = ch->GetPixelIndex(x+1,y); + ym1 = ch->GetPixelIndex(x,y-1); + yp1 = ch->GetPixelIndex(x,y+1); + + ix= (xp1-xm1)/2.0; + iy= (yp1-ym1)/2.0; + ixx= xp1 - 2.0 * xy0 + xm1; + iyy= yp1 - 2.0 * xy0 + ym1; + ixy=(ch->GetPixelIndex(x+1,y+1) + ch->GetPixelIndex(x-1,y-1) - + ch->GetPixelIndex(x-1,y+1) - ch->GetPixelIndex(x+1,y-1))/4.0; + + correction = ((1.0+iy*iy)*ixx - ix*iy*ixy + (1.0+ix*ix)*iyy)/(1.0+ix*ix+iy*iy); + + tmp.BlindSetPixelIndex(x,y,(uint8_t)min(255,max(0,(xy0 + radius * correction + 0.5)))); + } + } + for (x=0;x<=w;x+=w){ + for (y=0;y<=h;y++){ + xy0 = ch->BlindGetPixelIndex(x,y); + xm1 = ch->GetPixelIndex(x-1,y); + xp1 = ch->GetPixelIndex(x+1,y); + ym1 = ch->GetPixelIndex(x,y-1); + yp1 = ch->GetPixelIndex(x,y+1); + + ix= (xp1-xm1)/2.0; + iy= (yp1-ym1)/2.0; + ixx= xp1 - 2.0 * xy0 + xm1; + iyy= yp1 - 2.0 * xy0 + ym1; + ixy=(ch->GetPixelIndex(x+1,y+1) + ch->GetPixelIndex(x-1,y-1) - + ch->GetPixelIndex(x-1,y+1) - ch->GetPixelIndex(x+1,y-1))/4.0; + + correction = ((1.0+iy*iy)*ixx - ix*iy*ixy + (1.0+ix*ix)*iyy)/(1.0+ix*ix+iy*iy); + + tmp.BlindSetPixelIndex(x,y,(uint8_t)min(255,max(0,(xy0 + radius * correction + 0.5)))); + } + } + + ch->Transfer(tmp); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Enhance the variations between adjacent pixels. + * Similar results can be achieved using Filter(), + * but the algorithms are different both in Edge() and in Contour(). + * \return true if everything is ok + */ +bool CxImage::Contour() +{ + if (!pDib) return false; + + int32_t Ksize = 3; + int32_t k2 = Ksize/2; + int32_t kmax= Ksize-k2; + int32_t i,j,k; + uint8_t maxr,maxg,maxb; + RGBQUAD pix1,pix2; + + CxImage tmp(*this); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + int32_t xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + for(int32_t y=ymin; ymaxb) maxb = pix2.rgbBlue; + if ((pix2.rgbGreen-pix1.rgbGreen)>maxg) maxg = pix2.rgbGreen; + if ((pix2.rgbRed-pix1.rgbRed)>maxr) maxr = pix2.rgbRed; + } + } + pix1.rgbBlue=(uint8_t)(255-maxb); + pix1.rgbGreen=(uint8_t)(255-maxg); + pix1.rgbRed=(uint8_t)(255-maxr); + tmp.BlindSetPixelColor(x,y,pix1); + } + } + } + Transfer(tmp); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Adds a random offset to each pixel in the image + * \param radius: maximum pixel displacement + * \return true if everything is ok + */ +bool CxImage::Jitter(int32_t radius) +{ + if (!pDib) return false; + + int32_t nx,ny; + + CxImage tmp(*this); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + int32_t xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + for(int32_t y=ymin; y modified scaling, so that matrix_lenght = 1+2*radius parameter + */ + radius = (float)fabs(0.5*radius) + 0.25f; + + std_dev = radius; + radius = std_dev * 2; + + /* go out 'radius' in each direction */ + matrix_length = int32_t (2 * ceil(radius-0.5) + 1); + if (matrix_length <= 0) matrix_length = 1; + matrix_midpoint = matrix_length/2 + 1; + *cmatrix_p = new float[matrix_length]; + cmatrix = *cmatrix_p; + + /* Now we fill the matrix by doing a numeric integration approximation + * from -2*std_dev to 2*std_dev, sampling 50 points per pixel. + * We do the bottom half, mirror it to the top half, then compute the + * center point. Otherwise asymmetric quantization errors will occur. + * The formula to integrate is e^-(x^2/2s^2). + */ + + /* first we do the top (right) half of matrix */ + for (i = matrix_length/2 + 1; i < matrix_length; i++) + { + float base_x = i - (float)floor((float)(matrix_length/2)) - 0.5f; + sum = 0; + for (j = 1; j <= 50; j++) + { + if ( base_x+0.02*j <= radius ) + sum += (float)exp (-(base_x+0.02*j)*(base_x+0.02*j) / + (2*std_dev*std_dev)); + } + cmatrix[i] = sum/50; + } + + /* mirror the thing to the bottom half */ + for (i=0; i<=matrix_length/2; i++) { + cmatrix[i] = cmatrix[matrix_length-1-i]; + } + + /* find center val -- calculate an odd number of quanta to make it symmetric, + * even if the center point is weighted slightly higher than others. */ + sum = 0; + for (j=0; j<=50; j++) + { + sum += (float)exp (-(0.5+0.02*j)*(0.5+0.02*j) / + (2*std_dev*std_dev)); + } + cmatrix[matrix_length/2] = sum/51; + + /* normalize the distribution by scaling the total sum to one */ + sum=0; + for (i=0; i y) + { + for (row = 0; row < y ; row++) + { + scale=0; + /* find the scale factor */ + for (j = 0; j < y ; j++) + { + /* if the index is in bounds, add it to the scale counter */ + if ((j + cmatrix_middle - row >= 0) && + (j + cmatrix_middle - row < cmatrix_length)) + scale += cmatrix[j + cmatrix_middle - row]; + } + for (i = 0; i= row - cmatrix_middle) && + (j <= row + cmatrix_middle)) + sum += cur_col[j*bytes + i] * cmatrix[j]; + } + dest_col[row*bytes + i] = (uint8_t)(0.5f + sum / scale); + } + } + } + else + { + /* for the edge condition, we only use available info and scale to one */ + for (row = 0; row < cmatrix_middle; row++) + { + /* find scale factor */ + scale=0; + for (j = cmatrix_middle - row; j0; j--) + { + sum += *(ctable_p + *cur_col_p1); + cur_col_p1 += bytes; + ctable_p += 256; + } + cur_col_p++; + *(dest_col_p++) = (uint8_t)(0.5f + sum); + } + } + + /* for the edge condition , we only use available info, and scale to one */ + for (; row < y; row++) + { + /* find scale factor */ + scale=0; + for (j = 0; j< y-row + cmatrix_middle; j++) + scale += cmatrix[j]; + for (i = 0; ihead.biWidth; + ymax = iSrc->head.biHeight; + + if (xmin==xmax || ymin==ymax) return; + + nmin = xmin * bytes; + nmax = xmax * bytes; + + CImageIterator itSrc(iSrc); + CImageIterator itTmp(iDst); + + double dbScaler = 100.0f/(ymax-ymin)/bytes; + + for (n=0; n=pivot){ + while (z1) ? ((m/bytes)/decay+1) : m/bytes; + if (m>max_depth) m = max_depth; + step = (uint8_t)((pSrc[x+bytes]-pSrc[x])/(m+1)); + while (m-->1){ + pDst[x+m*bytes] = (uint8_t)(pDst[x]+(step*(m+1))); + } + } + //find lower corner + z=x+bytes; + if (pSrc[x]=pivot){ + while (z1) ? ((m/bytes)/decay+1) : m/bytes; + if (m>max_depth) m = max_depth; + step = (uint8_t)((pSrc[x+bytes]-pSrc[x])/(m+1)); + while (m-->1){ + pDst[x+m*bytes] = (uint8_t)(pDst[x]+(step*(m+1))); + } + } + } + //scan right to left + for (x=nmax-1-n /*,i=(xmax-1)*/; x>0; x-=bytes /*,i--*/) + { + z=x-bytes; + pivot = pSrc[z]-threshold; + //find upper corner + if (pSrc[x]=pivot){ + while (z>n && pSrc2[z]1) ? ((m/bytes)/decay+1) : m/bytes; + if (m>max_depth) m = max_depth; + step = (uint8_t)((pSrc[x-bytes]-pSrc[x])/(m+1)); + while (m-->1){ + pDst[x-m*bytes] = (uint8_t)(pDst[x]+(step*(m+1))); + } + } + //find lower corner + z=x-bytes; + if (pSrc[x]=pivot){ + while (z>n && pSrc3[z]1) ? ((m/bytes)/decay+1) : m/bytes; + if (m>max_depth) m = max_depth; + step = (uint8_t)((pSrc[x-bytes]-pSrc[x])/(m+1)); + while (m-->1){ + pDst[x-m*bytes] = (uint8_t)(pDst[x]+(step*(m+1))); + } + } + } + } + } +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \author [DP] + */ +bool CxImage::TextBlur(uint8_t threshold, uint8_t decay, uint8_t max_depth, bool bBlurHorizontal, bool bBlurVertical, CxImage* iDst) +{ + if (!pDib) return false; + + RGBQUAD* pPalette=NULL; + uint16_t bpp = GetBpp(); + + //the routine is optimized for RGB or GrayScale images + if (!(head.biBitCount == 24 || IsGrayScale())){ + pPalette = new RGBQUAD[head.biClrUsed]; + memcpy(pPalette, GetPalette(),GetPaletteSize()); + if (!IncreaseBpp(24)) + return false; + } + + CxImage tmp(*this); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + if (bBlurHorizontal) + blur_text(threshold, decay, max_depth, this, &tmp, head.biBitCount>>3); + + if (bBlurVertical){ + CxImage src2(*this); + src2.RotateLeft(); + tmp.RotateLeft(); + blur_text(threshold, decay, max_depth, &src2, &tmp, head.biBitCount>>3); + tmp.RotateRight(); + } + +#if CXIMAGE_SUPPORT_SELECTION + //restore the non selected region + if (pSelection){ + for(int32_t y=0; yTransfer(tmp); + else Transfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \author [nipper]; changes [DP] + */ +bool CxImage::GaussianBlur(float radius /*= 1.0f*/, CxImage* iDst /*= 0*/) +{ + if (!pDib) return false; + + RGBQUAD* pPalette=NULL; + uint16_t bpp = GetBpp(); + + //the routine is optimized for RGB or GrayScale images + if (!(head.biBitCount == 24 || IsGrayScale())){ + pPalette = new RGBQUAD[head.biClrUsed]; + memcpy(pPalette, GetPalette(),GetPaletteSize()); + if (!IncreaseBpp(24)) + return false; + } + + CxImage tmp_x(*this, false, true, true); + if (!tmp_x.IsValid()){ + strcpy(info.szLastError,tmp_x.GetLastError()); + return false; + } + + // generate convolution matrix and make sure it's smaller than each dimension + float *cmatrix = NULL; + int32_t cmatrix_length = gen_convolve_matrix(radius, &cmatrix); + // generate lookup table + float *ctable = gen_lookup_table(cmatrix, cmatrix_length); + + int32_t x,y; + int32_t bypp = head.biBitCount>>3; + + CImageIterator itSrc(this); + CImageIterator itTmp(&tmp_x); + + double dbScaler = 50.0f/head.biHeight; + + // blur the rows + for (y=0;yTransfer(tmp_y); + else Transfer(tmp_y); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \author [DP],[nipper] + */ +bool CxImage::SelectiveBlur(float radius, uint8_t threshold, CxImage* iDst) +{ + if (!pDib) return false; + + RGBQUAD* pPalette=NULL; + uint16_t bpp = GetBpp(); + + CxImage Tmp(*this, true, true, true); + if (!Tmp.IsValid()){ + strcpy(info.szLastError,Tmp.GetLastError()); + return false; + } + + //the routine is optimized for RGB or GrayScale images + if (!(head.biBitCount == 24 || IsGrayScale())){ + pPalette = new RGBQUAD[head.biClrUsed]; + memcpy(pPalette, GetPalette(),GetPaletteSize()); + if (!Tmp.IncreaseBpp(24)){ + delete [] pPalette; + return false; + } + } + + CxImage Dst(Tmp, true, true, true); + if (!Dst.IsValid()){ + strcpy(info.szLastError,Dst.GetLastError()); + delete [] pPalette; + return false; + } + + //build the difference mask + uint8_t thresh_dw = (uint8_t)max( 0 ,(int32_t)(128 - threshold)); + uint8_t thresh_up = (uint8_t)min(255,(int32_t)(128 + threshold)); + int32_t kernel[]={-100,-100,-100,-100,801,-100,-100,-100,-100}; + if (!Tmp.Filter(kernel,3,800,128)){ + strcpy(info.szLastError,Tmp.GetLastError()); + delete [] pPalette; + return false; + } + + //if the image has no selection, build a selection for the whole image +#if CXIMAGE_SUPPORT_SELECTION + if (!Tmp.SelectionIsValid()){ + Tmp.SelectionCreate(); + Tmp.SelectionClear(255); + } + + int32_t xmin,xmax,ymin,ymax; + xmin = Tmp.info.rSelectionBox.left; + xmax = Tmp.info.rSelectionBox.right; + ymin = Tmp.info.rSelectionBox.bottom; + ymax = Tmp.info.rSelectionBox.top; + + //modify the selection where the difference mask is over the threshold + for(int32_t y=ymin; y thresh_up) || + (c.rgbGreen < thresh_dw || c.rgbGreen > thresh_up) || + (c.rgbBlue < thresh_dw || c.rgbBlue > thresh_up)) + { + Tmp.SelectionSet(x,y,0); + } + } + } + } + + //blur the image (only in the selected pixels) + Dst.SelectionCopy(Tmp); + if (!Dst.GaussianBlur(radius)){ + strcpy(info.szLastError,Dst.GetLastError()); + delete [] pPalette; + return false; + } + + //restore the original selection + Dst.SelectionCopy(*this); +#endif //CXIMAGE_SUPPORT_SELECTION + + //if necessary, restore the original BPP and palette + if (pPalette){ + Dst.DecreaseBpp(bpp, false, pPalette); + delete [] pPalette; + } + + if (iDst) iDst->Transfer(Dst); + else Transfer(Dst); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * sharpen the image by subtracting a blurred copy from the original image. + * \param radius: width in pixels of the blurring effect. Range: >0; default = 5. + * \param amount: strength of the filter. Range: 0.0 (none) to 1.0 (max); default = 0.5 + * \param threshold: difference, between blurred and original pixel, to trigger the filter + * Range: 0 (always triggered) to 255 (never triggered); default = 0. + * \return true if everything is ok + * \author [nipper]; changes [DP] + */ +bool CxImage::UnsharpMask(float radius /*= 5.0*/, float amount /*= 0.5*/, int32_t threshold /*= 0*/) +{ + if (!pDib) return false; + + RGBQUAD* pPalette=NULL; + uint16_t bpp = GetBpp(); + + //the routine is optimized for RGB or GrayScale images + if (!(head.biBitCount == 24 || IsGrayScale())){ + pPalette = new RGBQUAD[head.biClrUsed]; + memcpy(pPalette, GetPalette(),GetPaletteSize()); + if (!IncreaseBpp(24)) + return false; + } + + CxImage iDst; + if (!GaussianBlur(radius,&iDst)) + return false; + + CImageIterator itSrc(this); + CImageIterator itDst(&iDst); + + int32_t xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + if (xmin==xmax || ymin==ymax) + return false; + + double dbScaler = 100.0/(ymax-ymin); + int32_t bypp = head.biBitCount>>3; + + // merge the source and destination (which currently contains + // the blurred version) images + for (int32_t y=ymin; y + for(int32_t x=xmin; x1.0f) strength = 1.0f; + + for(int32_t y=ymin; ylevel){ + BlindSetPixelIndex(x,y,255-index); + } + } + } + } + } else { //PALETTE, full image + RGBQUAD* ppal=GetPalette(); + for(uint32_t i=0;ilevel){ + ppal[i].rgbBlue =(uint8_t)(255-ppal[i].rgbBlue); + ppal[i].rgbGreen =(uint8_t)(255-ppal[i].rgbGreen); + ppal[i].rgbRed =(uint8_t)(255-ppal[i].rgbRed); + } + } else { + if (color.rgbBlue>level) ppal[i].rgbBlue =(uint8_t)(255-ppal[i].rgbBlue); + if (color.rgbGreen>level) ppal[i].rgbGreen =(uint8_t)(255-ppal[i].rgbGreen); + if (color.rgbRed>level) ppal[i].rgbRed =(uint8_t)(255-ppal[i].rgbRed); + } + } + } + } else { //RGB, selection + for(int32_t y=ymin; ylevel){ + color.rgbRed = (uint8_t)(255-color.rgbRed); + color.rgbGreen = (uint8_t)(255-color.rgbGreen); + color.rgbBlue = (uint8_t)(255-color.rgbBlue); + } + } else { + if (color.rgbBlue>level) color.rgbBlue =(uint8_t)(255-color.rgbBlue); + if (color.rgbGreen>level) color.rgbGreen =(uint8_t)(255-color.rgbGreen); + if (color.rgbRed>level) color.rgbRed =(uint8_t)(255-color.rgbRed); + } + BlindSetPixelColor(x,y,color); + } + } + } + } + + //invert transparent color only in case of full image processing + if (pSelection==0 || (!IsGrayScale() && IsIndexed())){ + if (bLinkedChannels){ + if ((uint8_t)RGB2GRAY(info.nBkgndColor.rgbRed,info.nBkgndColor.rgbGreen,info.nBkgndColor.rgbBlue)>level){ + info.nBkgndColor.rgbBlue = (uint8_t)(255-info.nBkgndColor.rgbBlue); + info.nBkgndColor.rgbGreen = (uint8_t)(255-info.nBkgndColor.rgbGreen); + info.nBkgndColor.rgbRed = (uint8_t)(255-info.nBkgndColor.rgbRed); + } + } else { + if (info.nBkgndColor.rgbBlue>level) info.nBkgndColor.rgbBlue = (uint8_t)(255-info.nBkgndColor.rgbBlue); + if (info.nBkgndColor.rgbGreen>level) info.nBkgndColor.rgbGreen = (uint8_t)(255-info.nBkgndColor.rgbGreen); + if (info.nBkgndColor.rgbRed>level) info.nBkgndColor.rgbRed = (uint8_t)(255-info.nBkgndColor.rgbRed); + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Converts the RGB triplets to and from different colorspace + * \param dstColorSpace: destination colorspace; 0 = RGB, 1 = HSL, 2 = YUV, 3 = YIQ, 4 = XYZ + * \param srcColorSpace: source colorspace; 0 = RGB, 1 = HSL, 2 = YUV, 3 = YIQ, 4 = XYZ + * \return true if everything is ok + */ +bool CxImage::ConvertColorSpace(const int32_t dstColorSpace, const int32_t srcColorSpace) +{ + if (!pDib) + return false; + + if (dstColorSpace == srcColorSpace) + return true; + + int32_t w = GetWidth(); + int32_t h = GetHeight(); + + for (int32_t y=0;yIsValid() || + !pContrastMask->IsGrayScale() || + pContrastMask->GetWidth() != GetWidth() || + pContrastMask->GetHeight() != GetHeight()){ + strcpy(info.szLastError,"OptimalThreshold invalid ContrastMask"); + return -1; + } + } + + int32_t xmin,xmax,ymin,ymax; + if (pBox){ + xmin = max(pBox->left,0); + xmax = min(pBox->right,head.biWidth); + ymin = max(pBox->bottom,0); + ymax = min(pBox->top,head.biHeight); + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + if (xmin>=xmax || ymin>=ymax) + return -1; + + double p[256]; + memset(p, 0, 256*sizeof(double)); + //build histogram + for (int32_t y = ymin; yGetBits(y) + xmin; + for (int32_t x = xmin; x0 && p[gray_max]==0) gray_max--; + if (gray_min > gray_max) + return -1; + if (gray_min == gray_max){ + if (gray_min == 0) + return 0; + else + return gray_max-1; + } + + //compute total moments 0th,1st,2nd order + int32_t i,k; + double w_tot = 0; + double m_tot = 0; + double q_tot = 0; + for (i = gray_min; i <= gray_max; i++){ + w_tot += p[i]; + m_tot += i*p[i]; + q_tot += i*i*p[i]; + } + + double L, L1max, L2max, L3max, L4max; //objective functions + int32_t th1,th2,th3,th4; //optimal thresholds + L1max = L2max = L3max = L4max = 0; + th1 = th2 = th3 = th4 = -1; + + double w1, w2, m1, m2, q1, q2, s1, s2; + w1 = m1 = q1 = 0; + for (i = gray_min; i < gray_max; i++){ + w1 += p[i]; + w2 = w_tot - w1; + m1 += i*p[i]; + m2 = m_tot - m1; + q1 += i*i*p[i]; + q2 = q_tot - q1; + s1 = q1/w1-m1*m1/w1/w1; //s1 = q1/w1-pow(m1/w1,2); + s2 = q2/w2-m2*m2/w2/w2; //s2 = q2/w2-pow(m2/w2,2); + + //Otsu + L = -(s1*w1 + s2*w2); //implemented as definition + //L = w1 * w2 * (m2/w2 - m1/w1)*(m2/w2 - m1/w1); //implementation that doesn't need s1 & s2 + if (L1max < L || th1<0){ + L1max = L; + th1 = i; + } + + //Kittler and Illingworth + if (s1>0 && s2>0){ + L = w1*log(w1/sqrt(s1))+w2*log(w2/sqrt(s2)); + //L = w1*log(w1*w1/s1)+w2*log(w2*w2/s2); + if (L2max < L || th2<0){ + L2max = L; + th2 = i; + } + } + + //max entropy + L = 0; + for (k=gray_min;k<=i;k++) if (p[k] > 0) L -= p[k]*log(p[k]/w1)/w1; + for (k;k<=gray_max;k++) if (p[k] > 0) L -= p[k]*log(p[k]/w2)/w2; + if (L3max < L || th3<0){ + L3max = L; + th3 = i; + } + + //potential difference (based on Electrostatic Binarization method by J. Acharya & G. Sreechakra) + // L=-fabs(vdiff/vsum); molto selettivo, sembra che L=-fabs(vdiff) o L=-(vsum) + // abbiano lo stesso valore di soglia... il che semplificherebbe molto la routine + double vdiff = 0; + for (k=gray_min;k<=i;k++) + vdiff += p[k]*(i-k)*(i-k); + double vsum = vdiff; + for (k;k<=gray_max;k++){ + double dv = p[k]*(k-i)*(k-i); + vdiff -= dv; + vsum += dv; + } + if (vsum>0) L = -fabs(vdiff/vsum); else L = 0; + if (L4max < L || th4<0){ + L4max = L; + th4 = i; + } + } + + int32_t threshold; + switch (method){ + case 1: //Otsu + threshold = th1; + break; + case 2: //Kittler and Illingworth + threshold = th2; + break; + case 3: //max entropy + threshold = th3; + break; + case 4: //potential difference + threshold = th4; + break; + default: //auto + { + int32_t nt = 0; + threshold = 0; + if (th1>=0) { threshold += th1; nt++;} + if (th2>=0) { threshold += th2; nt++;} + if (th3>=0) { threshold += th3; nt++;} + if (th4>=0) { threshold += th4; nt++;} + if (nt) + threshold /= nt; + else + threshold = (gray_min+gray_max)/2; + + /*better(?) but really expensive alternative: + n = 0:255; + pth1 = c1(th1)/sqrt(2*pi*s1(th1))*exp(-((n - m1(th1)).^2)/2/s1(th1)) + c2(th1)/sqrt(2*pi*s2(th1))*exp(-((n - m2(th1)).^2)/2/s2(th1)); + pth2 = c1(th2)/sqrt(2*pi*s1(th2))*exp(-((n - m1(th2)).^2)/2/s1(th2)) + c2(th2)/sqrt(2*pi*s2(th2))*exp(-((n - m2(th2)).^2)/2/s2(th2)); + ... + mse_th1 = sum((p-pth1).^2); + mse_th2 = sum((p-pth2).^2); + ... + select th# that gives minimum mse_th# + */ + + } + } + + if (threshold <= gray_min || threshold >= gray_max) + threshold = (gray_min+gray_max)/2; + + return threshold; +} +/////////////////////////////////////////////////////////////////////////////// +/** + * Converts the image to B&W, using an optimal threshold mask + * \param method: 0 = average all methods (default); 1 = Otsu; 2 = Kittler & Illingworth; 3 = max entropy; 4 = potential difference; + * \param nBoxSize: the image is divided into "nBoxSize x nBoxSize" blocks, from where the threshold is computed; min = 8; default = 64. + * \param pContrastMask: limit the computation only in regions with contrasted (!=0) pixels; default = 0. + * \param nBias: global offset added to the threshold mask; default = 0. + * \param fGlobalLocalBalance: balance between local and global threshold. default = 0.5 + * fGlobalLocalBalance can be from 0.0 (use only local threshold) to 1.0 (use only global threshold) + * the pContrastMask image must be grayscale with same with and height of the current image, + * \return true if everything is ok. + * \sa OptimalThreshold + */ +bool CxImage::AdaptiveThreshold(int32_t method, int32_t nBoxSize, CxImage* pContrastMask, int32_t nBias, float fGlobalLocalBalance) +{ + if (!pDib) + return false; + + if (pContrastMask){ + if (!pContrastMask->IsValid() || + !pContrastMask->IsGrayScale() || + pContrastMask->GetWidth() != GetWidth() || + pContrastMask->GetHeight() != GetHeight()){ + strcpy(info.szLastError,"AdaptiveThreshold invalid ContrastMask"); + return false; + } + } + + if (nBoxSize<8) nBoxSize = 8; + if (fGlobalLocalBalance<0.0f) fGlobalLocalBalance = 0.0f; + if (fGlobalLocalBalance>1.0f) fGlobalLocalBalance = 1.0f; + + int32_t mw = (head.biWidth + nBoxSize - 1)/nBoxSize; + int32_t mh = (head.biHeight + nBoxSize - 1)/nBoxSize; + + CxImage mask(mw,mh,8); + if(!mask.GrayScale()) + return false; + + if(!GrayScale()) + return false; + + int32_t globalthreshold = OptimalThreshold(method, 0, pContrastMask); + if (globalthreshold <0) + return false; + + for (int32_t y=0; y=0 && !bFindStartPoint;y--){ + info.nProgress = (int32_t)(100*y/head.biHeight); + if (info.nEscape) break; + for (x=0;x +//////////////////////////////////////////////////////////////////////////////// +/** + * Flood Fill + * \param xStart, yStart: starting point + * \param cFillColor: filling color + * \param nTolerance: deviation from the starting point color + * \param nOpacity: can be from 0 (transparent) to 255 (opaque, default) + * \param bSelectFilledArea: if true, the pixels in the region are also set in the selection layer; default = false + * \param nSelectionLevel: if bSelectFilledArea is true, the selected pixels are set to nSelectionLevel; default = 255 + * Note: nOpacity=0 && bSelectFilledArea=true act as a "magic wand" + * \return true if everything is ok + */ +bool CxImage::FloodFill(const int32_t xStart, const int32_t yStart, const RGBQUAD cFillColor, const uint8_t nTolerance, + uint8_t nOpacity, const bool bSelectFilledArea, const uint8_t nSelectionLevel) +{ + if (!pDib) + return false; + + if (!IsInside(xStart,yStart)) + return true; + +#if CXIMAGE_SUPPORT_SELECTION + if (!SelectionIsInside(xStart,yStart)) + return true; +#endif //CXIMAGE_SUPPORT_SELECTION + + RGBQUAD* pPalette=NULL; + uint16_t bpp = GetBpp(); + //nTolerance or nOpacity implemented only for grayscale or 24bpp images + if ((nTolerance || nOpacity != 255) && !(head.biBitCount == 24 || IsGrayScale())){ + pPalette = new RGBQUAD[head.biClrUsed]; + memcpy(pPalette, GetPalette(),GetPaletteSize()); + if (!IncreaseBpp(24)) + return false; + } + + uint8_t* pFillMask = (uint8_t*)calloc(head.biWidth * head.biHeight,1); + if (!pFillMask) + return false; + +//------------------------------------- Begin of Flood Fill + POINT offset[4] = {{-1,0},{0,-1},{1,0},{0,1}}; + std::queue q; + POINT point = {xStart,yStart}; + q.push(point); + + if (IsIndexed()){ //--- Generic indexed image, no tolerance OR Grayscale image with tolerance + uint8_t idxRef = GetPixelIndex(xStart,yStart); + uint8_t idxFill = GetNearestIndex(cFillColor); + uint8_t idxMin = (uint8_t)min(255, max(0,(int32_t)(idxRef - nTolerance))); + uint8_t idxMax = (uint8_t)min(255, max(0,(int32_t)(idxRef + nTolerance))); + + while(!q.empty()) + { + point = q.front(); + q.pop(); + + for (int32_t z=0; z<4; z++){ + int32_t x = point.x + offset[z].x; + int32_t y = point.y + offset[z].y; + if(IsInside(x,y)){ +#if CXIMAGE_SUPPORT_SELECTION + if (BlindSelectionIsInside(x,y)) +#endif //CXIMAGE_SUPPORT_SELECTION + { + uint8_t idx = BlindGetPixelIndex(x, y); + uint8_t* pFill = pFillMask + x + y * head.biWidth; + if (*pFill==0 && idxMin <= idx && idx <= idxMax ) + { + if (nOpacity>0){ + if (nOpacity == 255) + BlindSetPixelIndex(x, y, idxFill); + else + BlindSetPixelIndex(x, y, (uint8_t)((idxFill * nOpacity + idx * (255-nOpacity))>>8)); + } + POINT pt = {x,y}; + q.push(pt); + *pFill = 1; + } + } + } + } + } + } else { //--- RGB image + RGBQUAD cRef = GetPixelColor(xStart,yStart); + RGBQUAD cRefMin, cRefMax; + cRefMin.rgbRed = (uint8_t)min(255, max(0,(int32_t)(cRef.rgbRed - nTolerance))); + cRefMin.rgbGreen = (uint8_t)min(255, max(0,(int32_t)(cRef.rgbGreen - nTolerance))); + cRefMin.rgbBlue = (uint8_t)min(255, max(0,(int32_t)(cRef.rgbBlue - nTolerance))); + cRefMax.rgbRed = (uint8_t)min(255, max(0,(int32_t)(cRef.rgbRed + nTolerance))); + cRefMax.rgbGreen = (uint8_t)min(255, max(0,(int32_t)(cRef.rgbGreen + nTolerance))); + cRefMax.rgbBlue = (uint8_t)min(255, max(0,(int32_t)(cRef.rgbBlue + nTolerance))); + + while(!q.empty()) + { + point = q.front(); + q.pop(); + + for (int32_t z=0; z<4; z++){ + int32_t x = point.x + offset[z].x; + int32_t y = point.y + offset[z].y; + if(IsInside(x,y)){ +#if CXIMAGE_SUPPORT_SELECTION + if (BlindSelectionIsInside(x,y)) +#endif //CXIMAGE_SUPPORT_SELECTION + { + RGBQUAD cc = BlindGetPixelColor(x, y); + uint8_t* pFill = pFillMask + x + y * head.biWidth; + if (*pFill==0 && + cRefMin.rgbRed <= cc.rgbRed && cc.rgbRed <= cRefMax.rgbRed && + cRefMin.rgbGreen <= cc.rgbGreen && cc.rgbGreen <= cRefMax.rgbGreen && + cRefMin.rgbBlue <= cc.rgbBlue && cc.rgbBlue <= cRefMax.rgbBlue ) + { + if (nOpacity>0){ + if (nOpacity == 255) + BlindSetPixelColor(x, y, cFillColor); + else + { + cc.rgbRed = (uint8_t)((cFillColor.rgbRed * nOpacity + cc.rgbRed * (255-nOpacity))>>8); + cc.rgbGreen = (uint8_t)((cFillColor.rgbGreen * nOpacity + cc.rgbGreen * (255-nOpacity))>>8); + cc.rgbBlue = (uint8_t)((cFillColor.rgbBlue * nOpacity + cc.rgbBlue * (255-nOpacity))>>8); + BlindSetPixelColor(x, y, cc); + } + } + POINT pt = {x,y}; + q.push(pt); + *pFill = 1; + } + } + } + } + } + } + if (pFillMask[xStart+yStart*head.biWidth] == 0 && nOpacity>0){ + if (nOpacity == 255) + BlindSetPixelColor(xStart, yStart, cFillColor); + else + { + RGBQUAD cc = BlindGetPixelColor(xStart, yStart); + cc.rgbRed = (uint8_t)((cFillColor.rgbRed * nOpacity + cc.rgbRed * (255-nOpacity))>>8); + cc.rgbGreen = (uint8_t)((cFillColor.rgbGreen * nOpacity + cc.rgbGreen * (255-nOpacity))>>8); + cc.rgbBlue = (uint8_t)((cFillColor.rgbBlue * nOpacity + cc.rgbBlue * (255-nOpacity))>>8); + BlindSetPixelColor(xStart, yStart, cc); + } + } + pFillMask[xStart+yStart*head.biWidth] = 1; +//------------------------------------- End of Flood Fill + + //if necessary, restore the original BPP and palette + if (pPalette){ + DecreaseBpp(bpp, false, pPalette); + delete [] pPalette; + } + +#if CXIMAGE_SUPPORT_SELECTION + if (bSelectFilledArea){ + if (!SelectionIsValid()){ + if (!SelectionCreate()){ + return false; + } + SelectionClear(); + info.rSelectionBox.right = head.biWidth; + info.rSelectionBox.top = head.biHeight; + info.rSelectionBox.left = info.rSelectionBox.bottom = 0; + } + RECT r; + SelectionGetBox(r); + for (int32_t y = r.bottom; y < r.top; y++){ + uint8_t* pFill = pFillMask + r.left + y * head.biWidth; + for (int32_t x = r.left; x - WMF/EMF support +#endif + +#if CXIMAGE_SUPPORT_JBG +#include "ximajbg.h" +#endif + +#if CXIMAGE_SUPPORT_JASPER +#include "ximajas.h" +#endif + +#if CXIMAGE_SUPPORT_SKA +#include "ximaska.h" +#endif + +#if CXIMAGE_SUPPORT_RAW +#include "ximaraw.h" +#endif + +#if CXIMAGE_SUPPORT_PSD +#include "ximapsd.h" +#endif + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::EncodeSafeCheck(CxFile *hFile) +{ + if (hFile==NULL) { + strcpy(info.szLastError,CXIMAGE_ERR_NOFILE); + return true; + } + + if (pDib==NULL){ + strcpy(info.szLastError,CXIMAGE_ERR_NOIMAGE); + return true; + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +//#ifdef WIN32 +//bool CxImage::Save(LPCWSTR filename, uint32_t imagetype) +//{ +// FILE* hFile; //file handle to write the image +// if ((hFile=_wfopen(filename,L"wb"))==NULL) return false; +// bool bOK = Encode(hFile,imagetype); +// fclose(hFile); +// return bOK; +//} +//#endif //WIN32 +//////////////////////////////////////////////////////////////////////////////// +// For UNICODE support: char -> TCHAR +/** + * Saves to disk the image in a specific format. + * \param filename: file name + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + */ +bool CxImage::Save(const TCHAR * filename, uint32_t imagetype) +{ + FILE* hFile; //file handle to write the image + +#ifdef WIN32 + if ((hFile=_tfopen(filename,_T("wb")))==NULL) return false; // For UNICODE support +#else + if ((hFile=fopen(filename,"wb"))==NULL) return false; +#endif + + bool bOK = Encode(hFile,imagetype); + fclose(hFile); + return bOK; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Saves to disk the image in a specific format. + * \param hFile: file handle, open and enabled for writing. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + */ +bool CxImage::Encode(FILE *hFile, uint32_t imagetype) +{ + CxIOFile file(hFile); + return Encode(&file,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Saves to memory buffer the image in a specific format. + * \param buffer: output memory buffer pointer. Must be NULL, + * the function allocates and fill the memory, + * the application must free the buffer, see also FreeMemory(). + * \param size: output memory buffer size. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + */ +bool CxImage::Encode(uint8_t * &buffer, int32_t &size, uint32_t imagetype) +{ + if (buffer!=NULL){ + strcpy(info.szLastError,"the buffer must be empty"); + return false; + } + CxMemFile file; + file.Open(); + if(Encode(&file,imagetype)){ + buffer=file.GetBuffer(); + size=file.Size(); + return true; + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Saves to disk the image in a specific format. + * \param hFile: file handle (CxMemFile or CxIOFile), with write access. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + * \sa ENUM_CXIMAGE_FORMATS + */ +bool CxImage::Encode(CxFile *hFile, uint32_t imagetype) +{ + +#if CXIMAGE_SUPPORT_BMP + if (CXIMAGE_FORMAT_BMP==imagetype){ + CxImageBMP *newima = new CxImageBMP; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_ICO + if (CXIMAGE_FORMAT_ICO==imagetype){ + CxImageICO *newima = new CxImageICO; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_TIF + if (CXIMAGE_FORMAT_TIF==imagetype){ + CxImageTIF *newima = new CxImageTIF; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_JPG + if (CXIMAGE_FORMAT_JPG==imagetype){ + CxImageJPG *newima = new CxImageJPG; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_GIF + if (CXIMAGE_FORMAT_GIF==imagetype){ + CxImageGIF *newima = new CxImageGIF; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_PNG + if (CXIMAGE_FORMAT_PNG==imagetype){ + CxImagePNG *newima = new CxImagePNG; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_MNG + if (CXIMAGE_FORMAT_MNG==imagetype){ + CxImageMNG *newima = new CxImageMNG; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_TGA + if (CXIMAGE_FORMAT_TGA==imagetype){ + CxImageTGA *newima = new CxImageTGA; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_PCX + if (CXIMAGE_FORMAT_PCX==imagetype){ + CxImagePCX *newima = new CxImagePCX; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_WBMP + if (CXIMAGE_FORMAT_WBMP==imagetype){ + CxImageWBMP *newima = new CxImageWBMP; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_WMF && CXIMAGE_SUPPORT_WINDOWS // - WMF/EMF support + if (CXIMAGE_FORMAT_WMF==imagetype){ + CxImageWMF *newima = new CxImageWMF; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_JBG + if (CXIMAGE_FORMAT_JBG==imagetype){ + CxImageJBG *newima = new CxImageJBG; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_JASPER + if ( + #if CXIMAGE_SUPPORT_JP2 + CXIMAGE_FORMAT_JP2==imagetype || + #endif + #if CXIMAGE_SUPPORT_JPC + CXIMAGE_FORMAT_JPC==imagetype || + #endif + #if CXIMAGE_SUPPORT_PGX + CXIMAGE_FORMAT_PGX==imagetype || + #endif + #if CXIMAGE_SUPPORT_PNM + CXIMAGE_FORMAT_PNM==imagetype || + #endif + #if CXIMAGE_SUPPORT_RAS + CXIMAGE_FORMAT_RAS==imagetype || + #endif + false ){ + CxImageJAS *newima = new CxImageJAS; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile,imagetype)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif + +#if CXIMAGE_SUPPORT_SKA + if (CXIMAGE_FORMAT_SKA==imagetype){ + CxImageSKA *newima = new CxImageSKA; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif + +#if CXIMAGE_SUPPORT_RAW + if (CXIMAGE_FORMAT_RAW==imagetype){ + CxImageRAW *newima = new CxImageRAW; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif + +#if CXIMAGE_SUPPORT_PSD + if (CXIMAGE_FORMAT_PSD==imagetype){ + CxImagePSD *newima = new CxImagePSD; + if (!newima) return false; + newima->Ghost(this); + if (newima->Encode(hFile)){ + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + delete newima; + return false; + } + } +#endif + + strcpy(info.szLastError,"Encode: Unknown format"); + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Saves to disk or memory pagecount images, referenced by an array of CxImage pointers. + * \param hFile: file handle. + * \param pImages: array of CxImage pointers. + * \param pagecount: number of images. + * \param imagetype: can be CXIMAGE_FORMAT_TIF or CXIMAGE_FORMAT_GIF. + * \return true if everything is ok + */ +bool CxImage::Encode(FILE * hFile, CxImage ** pImages, int32_t pagecount, uint32_t imagetype) +{ + CxIOFile file(hFile); + return Encode(&file, pImages, pagecount,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Saves to disk or memory pagecount images, referenced by an array of CxImage pointers. + * \param hFile: file handle (CxMemFile or CxIOFile), with write access. + * \param pImages: array of CxImage pointers. + * \param pagecount: number of images. + * \param imagetype: can be CXIMAGE_FORMAT_TIF, CXIMAGE_FORMAT_GIF or CXIMAGE_FORMAT_ICO. + * \return true if everything is ok + */ +bool CxImage::Encode(CxFile * hFile, CxImage ** pImages, int32_t pagecount, uint32_t imagetype) +{ +#if CXIMAGE_SUPPORT_TIF + if (imagetype==CXIMAGE_FORMAT_TIF){ + CxImageTIF newima; + newima.Ghost(this); + if (newima.Encode(hFile,pImages,pagecount)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_GIF + if (imagetype==CXIMAGE_FORMAT_GIF){ + CxImageGIF newima; + newima.Ghost(this); + if (newima.Encode(hFile,pImages,pagecount)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_ICO + if (imagetype==CXIMAGE_FORMAT_ICO){ + CxImageICO newima; + newima.Ghost(this); + if (newima.Encode(hFile,pImages,pagecount)){ + return true; + } else { + strcpy(info.szLastError,newima.GetLastError()); + return false; + } + } +#endif + strcpy(info.szLastError,"Multipage Encode, Unsupported operation for this format"); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * exports the image into a RGBA buffer, Useful for OpenGL applications. + * \param buffer: output memory buffer pointer. Must be NULL, + * the function allocates and fill the memory, + * the application must free the buffer, see also FreeMemory(). + * \param size: output memory buffer size. + * \param bFlipY: direction of Y axis. default = false. + * \return true if everything is ok + */ +bool CxImage::Encode2RGBA(uint8_t * &buffer, int32_t &size, bool bFlipY) +{ + if (buffer!=NULL){ + strcpy(info.szLastError,"the buffer must be empty"); + return false; + } + CxMemFile file; + file.Open(); + if(Encode2RGBA(&file,bFlipY)){ + buffer=file.GetBuffer(); + size=file.Size(); + return true; + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * exports the image into a RGBA buffer, Useful for OpenGL applications. + * \param hFile: file handle (CxMemFile or CxIOFile), with write access. + * \param bFlipY: direction of Y axis. default = false. + * \return true if everything is ok + */ +bool CxImage::Encode2RGBA(CxFile *hFile, bool bFlipY) +{ + if (EncodeSafeCheck(hFile)) return false; + + for (int32_t y1 = 0; y1 < head.biHeight; y1++) { + int32_t y = bFlipY ? head.biHeight - 1 - y1 : y1; + for(int32_t x = 0; x < head.biWidth; x++) { + RGBQUAD color = BlindGetPixelColor(x,y); + hFile->PutC(color.rgbRed); + hFile->PutC(color.rgbGreen); + hFile->PutC(color.rgbBlue); + hFile->PutC(color.rgbReserved); + } + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +// For UNICODE support: char -> TCHAR +/** + * Reads from disk the image in a specific format. + * - If decoding fails using the specified image format, + * the function will try the automatic file format recognition. + * + * \param filename: file name + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + */ +bool CxImage::Load(const TCHAR * filename, uint32_t imagetype) +//bool CxImage::Load(const char * filename, uint32_t imagetype) +{ + /*FILE* hFile; //file handle to read the image + if ((hFile=fopen(filename,"rb"))==NULL) return false; + bool bOK = Decode(hFile,imagetype); + fclose(hFile);*/ + + /* automatic file type recognition */ + bool bOK = false; + if ( GetTypeIndexFromId(imagetype) ){ + FILE* hFile; //file handle to read the image + +#ifdef WIN32 + if ((hFile=_tfopen(filename,_T("rb")))==NULL) return false; // For UNICODE support +#else + if ((hFile=fopen(filename,"rb"))==NULL) return false; +#endif + + bOK = Decode(hFile,imagetype); + fclose(hFile); + if (bOK) return bOK; + } + + char szError[256]; + strcpy(szError,info.szLastError); //save the first error + + // if failed, try automatic recognition of the file... + FILE* hFile; + +#ifdef WIN32 + if ((hFile=_tfopen(filename,_T("rb")))==NULL) return false; // For UNICODE support +#else + if ((hFile=fopen(filename,"rb"))==NULL) return false; +#endif + + bOK = Decode(hFile,CXIMAGE_FORMAT_UNKNOWN); + fclose(hFile); + + if (!bOK && imagetype > 0) strcpy(info.szLastError,szError); //restore the first error + + return bOK; +} +//////////////////////////////////////////////////////////////////////////////// +#ifdef WIN32 +//bool CxImage::Load(LPCWSTR filename, uint32_t imagetype) +//{ +// /*FILE* hFile; //file handle to read the image +// if ((hFile=_wfopen(filename, L"rb"))==NULL) return false; +// bool bOK = Decode(hFile,imagetype); +// fclose(hFile);*/ +// +// /* automatic file type recognition */ +// bool bOK = false; +// if ( GetTypeIndexFromId(imagetype) ){ +// FILE* hFile; //file handle to read the image +// if ((hFile=_wfopen(filename,L"rb"))==NULL) return false; +// bOK = Decode(hFile,imagetype); +// fclose(hFile); +// if (bOK) return bOK; +// } +// +// char szError[256]; +// strcpy(szError,info.szLastError); //save the first error +// +// // if failed, try automatic recognition of the file... +// FILE* hFile; //file handle to read the image +// if ((hFile=_wfopen(filename,L"rb"))==NULL) return false; +// bOK = Decode(hFile,CXIMAGE_FORMAT_UNKNOWN); +// fclose(hFile); +// +// if (!bOK && imagetype > 0) strcpy(info.szLastError,szError); //restore the first error +// +// return bOK; +//} +//////////////////////////////////////////////////////////////////////////////// +/** + * Loads an image from the application resources. + * \param hRes: the resource handle returned by FindResource(). + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS. + * \param hModule: NULL for internal resource, or external application/DLL hinstance returned by LoadLibray. + * \return true if everything is ok + */ +bool CxImage::LoadResource(HRSRC hRes, uint32_t imagetype, HMODULE hModule) +{ + uint32_t rsize=SizeofResource(hModule,hRes); + HGLOBAL hMem=::LoadResource(hModule,hRes); + if (hMem){ + char* lpVoid=(char*)LockResource(hMem); + if (lpVoid){ + // FILE* fTmp=tmpfile(); doesn't work with network + /*char tmpPath[MAX_PATH] = {0}; + char tmpFile[MAX_PATH] = {0}; + GetTempPath(MAX_PATH,tmpPath); + GetTempFileName(tmpPath,"IMG",0,tmpFile); + FILE* fTmp=fopen(tmpFile,"w+b"); + if (fTmp){ + fwrite(lpVoid,rsize,1,fTmp); + fseek(fTmp,0,SEEK_SET); + bool bOK = Decode(fTmp,imagetype); + fclose(fTmp); + DeleteFile(tmpFile); + return bOK; + }*/ + + CxMemFile fTmp((uint8_t*)lpVoid,rsize); + return Decode(&fTmp,imagetype); + } + } else strcpy(info.szLastError,"Unable to load resource!"); + return false; +} +#endif //WIN32 +//////////////////////////////////////////////////////////////////////////////// +/** + * Constructor from file name, see Load() + * \param filename: file name + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + */ +// +// > filename: file name +// > imagetype: specify the image format (CXIMAGE_FORMAT_BMP,...) +// For UNICODE support: char -> TCHAR +CxImage::CxImage(const TCHAR * filename, uint32_t imagetype) +//CxImage::CxImage(const char * filename, uint32_t imagetype) +{ + Startup(imagetype); + Load(filename,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Constructor from file handle, see Decode() + * \param stream: file handle, with read access. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + */ +CxImage::CxImage(FILE * stream, uint32_t imagetype) +{ + Startup(imagetype); + Decode(stream,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Constructor from CxFile object, see Decode() + * \param stream: file handle (CxMemFile or CxIOFile), with read access. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + */ +CxImage::CxImage(CxFile * stream, uint32_t imagetype) +{ + Startup(imagetype); + Decode(stream,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Constructor from memory buffer, see Decode() + * \param buffer: memory buffer + * \param size: size of buffer + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + */ +CxImage::CxImage(uint8_t * buffer, uint32_t size, uint32_t imagetype) +{ + Startup(imagetype); + CxMemFile stream(buffer,size); + Decode(&stream,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Loads an image from memory buffer + * \param buffer: memory buffer + * \param size: size of buffer + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + */ +bool CxImage::Decode(uint8_t * buffer, uint32_t size, uint32_t imagetype) +{ + CxMemFile file(buffer,size); + return Decode(&file,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Loads an image from file handle. + * \param hFile: file handle, with read access. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + */ +bool CxImage::Decode(FILE *hFile, uint32_t imagetype) +{ + CxIOFile file(hFile); + return Decode(&file,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Loads an image from CxFile object + * \param hFile: file handle (CxMemFile or CxIOFile), with read access. + * \param imagetype: file format, see ENUM_CXIMAGE_FORMATS + * \return true if everything is ok + * \sa ENUM_CXIMAGE_FORMATS + */ +bool CxImage::Decode(CxFile *hFile, uint32_t imagetype) +{ + if (hFile == NULL){ + strcpy(info.szLastError,CXIMAGE_ERR_NOFILE); + return false; + } + + uint32_t pos = hFile->Tell(); + +#if CXIMAGE_SUPPORT_BMP + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_BMP==imagetype){ + CxImageBMP *newima = new CxImageBMP; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_JPG + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_JPG==imagetype){ + CxImageJPG *newima = new CxImageJPG; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_ICO + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_ICO==imagetype){ + CxImageICO *newima = new CxImageICO; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + info.nNumFrames = newima->info.nNumFrames; + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_GIF + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_GIF==imagetype){ + CxImageGIF *newima = new CxImageGIF; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + info.nNumFrames = newima->info.nNumFrames; + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_PNG + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_PNG==imagetype){ + CxImagePNG *newima = new CxImagePNG; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_TIF + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_TIF==imagetype){ + CxImageTIF *newima = new CxImageTIF; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + info.nNumFrames = newima->info.nNumFrames; + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_MNG + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_MNG==imagetype){ + CxImageMNG *newima = new CxImageMNG; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + info.nNumFrames = newima->info.nNumFrames; + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_TGA + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_TGA==imagetype){ + CxImageTGA *newima = new CxImageTGA; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_PCX + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_PCX==imagetype){ + CxImagePCX *newima = new CxImagePCX; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_WBMP + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_WBMP==imagetype){ + CxImageWBMP *newima = new CxImageWBMP; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_WMF && CXIMAGE_SUPPORT_WINDOWS + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_WMF==imagetype){ + CxImageWMF *newima = new CxImageWMF; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_JBG + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_JBG==imagetype){ + CxImageJBG *newima = new CxImageJBG; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_JASPER + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || +#if CXIMAGE_SUPPORT_JP2 + CXIMAGE_FORMAT_JP2==imagetype || +#endif +#if CXIMAGE_SUPPORT_JPC + CXIMAGE_FORMAT_JPC==imagetype || +#endif +#if CXIMAGE_SUPPORT_PGX + CXIMAGE_FORMAT_PGX==imagetype || +#endif +#if CXIMAGE_SUPPORT_PNM + CXIMAGE_FORMAT_PNM==imagetype || +#endif +#if CXIMAGE_SUPPORT_RAS + CXIMAGE_FORMAT_RAS==imagetype || +#endif + false ){ + CxImageJAS *newima = new CxImageJAS; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_SKA + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_SKA==imagetype){ + CxImageSKA *newima = new CxImageSKA; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_RAW + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_RAW==imagetype){ + CxImageRAW *newima = new CxImageRAW; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif +#if CXIMAGE_SUPPORT_PSD + if (CXIMAGE_FORMAT_UNKNOWN==imagetype || CXIMAGE_FORMAT_PSD==imagetype){ + CxImagePSD *newima = new CxImagePSD; + if (!newima) + return false; + newima->CopyInfo(*this); + if (newima->Decode(hFile)) { + Transfer(*newima); + delete newima; + return true; + } else { + strcpy(info.szLastError,newima->GetLastError()); + hFile->Seek(pos,SEEK_SET); + delete newima; + if (CXIMAGE_FORMAT_UNKNOWN!=imagetype) + return false; + } + } +#endif + + strcpy(info.szLastError,"Decode: Unknown or wrong format"); + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Loads an image from CxFile object + * \param hFile: file handle (CxMemFile or CxIOFile), with read access. + * \param imagetype: file format, default = 0 (CXIMAGE_FORMAT_UNKNOWN) + * \return : if imagetype is not 0, the function returns true when imagetype + * matches the file image format. If imagetype is 0, the function returns true + * when the file image format is recognized as a supported format. + * If the returned value is true, use GetHeight(), GetWidth() or GetType() + * to retrieve the basic image information. + * \sa ENUM_CXIMAGE_FORMATS + */ +bool CxImage::CheckFormat(CxFile * hFile, uint32_t imagetype) +{ + SetType(CXIMAGE_FORMAT_UNKNOWN); + SetEscape(-1); + + if (!Decode(hFile,imagetype)) + return false; + + if (GetType() == CXIMAGE_FORMAT_UNKNOWN || + ((imagetype!=CXIMAGE_FORMAT_UNKNOWN)&&(GetType() != imagetype))) + return false; + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::CheckFormat(uint8_t * buffer, uint32_t size, uint32_t imagetype) +{ + if (buffer==NULL || size==NULL){ + strcpy(info.szLastError,"invalid or empty buffer"); + return false; + } + CxMemFile file(buffer,size); + return CheckFormat(&file,imagetype); +} +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_EXIF +bool CxImage::GetExifThumbnail(const TCHAR *filename, const TCHAR *outname, int32_t type) +{ + switch (type){ +#if CXIMAGE_SUPPORT_RAW + case CXIMAGE_FORMAT_RAW: + { + CxImageRAW image; + return image.GetExifThumbnail(filename, outname, type); + } +#endif //CXIMAGE_SUPPORT_RAW +#if CXIMAGE_SUPPORT_JPG + case CXIMAGE_FORMAT_JPG: + { + CxImageJPG image; + return image.GetExifThumbnail(filename, outname, type); + } +#endif //CXIMAGE_SUPPORT_JPG + default: + return false; + } +} +#endif //CXIMAGE_SUPPORT_EXIF + +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// diff --git a/DuiLib/3rd/CxImage/ximaexif.cpp b/DuiLib/3rd/CxImage/ximaexif.cpp new file mode 100644 index 0000000..6d14374 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximaexif.cpp @@ -0,0 +1,877 @@ +/* + * File: ximaexif.cpp + * Purpose: EXIF reader + * 18/Aug/2002 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + * based on jhead-1.8 by Matthias Wandel + */ + +#include "ximajpg.h" + +#if CXIMAGEJPG_SUPPORT_EXIF + +//////////////////////////////////////////////////////////////////////////////// +CxImageJPG::CxExifInfo::CxExifInfo(EXIFINFO* info) +{ + if (info) { + m_exifinfo = info; + freeinfo = false; + } else { + m_exifinfo = new EXIFINFO; + memset(m_exifinfo,0,sizeof(EXIFINFO)); + freeinfo = true; + } + + m_szLastError[0]='\0'; + ExifImageWidth = MotorolaOrder = 0; + SectionsRead=0; + memset(&Sections, 0, MAX_SECTIONS * sizeof(Section_t)); +} +//////////////////////////////////////////////////////////////////////////////// +CxImageJPG::CxExifInfo::~CxExifInfo() +{ + for(int32_t i=0;iGetC(); + + if (a != 0xff || hFile->GetC() != M_SOI){ + return FALSE; + } + + for(;;){ + int32_t itemlen; + int32_t marker = 0; + int32_t ll,lh, got; + uint8_t * Data; + + if (SectionsRead >= MAX_SECTIONS){ + strcpy(m_szLastError,"Too many sections in jpg file"); + return false; + } + + for (a=0;a<7;a++){ + marker = hFile->GetC(); + if (marker != 0xff) break; + + if (a >= 6){ + printf("too many padding bytes\n"); + return false; + } + } + + if (marker == 0xff){ + // 0xff is legal padding, but if we get that many, something's wrong. + strcpy(m_szLastError,"too many padding bytes!"); + return false; + } + + Sections[SectionsRead].Type = marker; + + // Read the length of the section. + lh = hFile->GetC(); + ll = hFile->GetC(); + + itemlen = (lh << 8) | ll; + + if (itemlen < 2){ + strcpy(m_szLastError,"invalid marker"); + return false; + } + + Sections[SectionsRead].Size = itemlen; + + Data = (uint8_t *)malloc(itemlen); + if (Data == NULL){ + strcpy(m_szLastError,"Could not allocate memory"); + return false; + } + Sections[SectionsRead].Data = Data; + + // Store first two pre-read bytes. + Data[0] = (uint8_t)lh; + Data[1] = (uint8_t)ll; + + got = hFile->Read(Data+2, 1, itemlen-2); // Read the whole section. + if (got != itemlen-2){ + strcpy(m_szLastError,"Premature end of file?"); + return false; + } + SectionsRead += 1; + + switch(marker){ + + case M_SOS: // stop before hitting compressed data + // If reading entire image is requested, read the rest of the data. + if (nReadMode & EXIF_READ_IMAGE){ + int32_t cp, ep, size; + // Determine how much file is left. + cp = hFile->Tell(); + hFile->Seek(0, SEEK_END); + ep = hFile->Tell(); + hFile->Seek(cp, SEEK_SET); + + size = ep-cp; + Data = (uint8_t *)malloc(size); + if (Data == NULL){ + strcpy(m_szLastError,"could not allocate data for entire image"); + return false; + } + + got = hFile->Read(Data, 1, size); + if (got != size){ + strcpy(m_szLastError,"could not read the rest of the image"); + return false; + } + + Sections[SectionsRead].Data = Data; + Sections[SectionsRead].Size = size; + Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER; + SectionsRead ++; + } + return true; + + case M_EOI: // in case it's a tables-only JPEG stream + printf("No image in jpeg!\n"); + return FALSE; + + case M_COM: // Comment section + if (HaveCom || ((nReadMode & EXIF_READ_EXIF) == 0)){ + // Discard this section. + free(Sections[--SectionsRead].Data); + Sections[SectionsRead].Data=0; + }else{ + process_COM(Data, itemlen); + HaveCom = TRUE; + } + break; + + case M_JFIF: + // Regular jpegs always have this tag, exif images have the exif + // marker instead, althogh ACDsee will write images with both markers. + // this program will re-create this marker on absence of exif marker. + // hence no need to keep the copy from the file. + free(Sections[--SectionsRead].Data); + Sections[SectionsRead].Data=0; + break; + + case M_EXIF: + // Seen files from some 'U-lead' software with Vivitar scanner + // that uses marker 31 for non exif stuff. Thus make sure + // it says 'Exif' in the section before treating it as exif. + if ((nReadMode & EXIF_READ_EXIF) && memcmp(Data+2, "Exif", 4) == 0){ + m_exifinfo->IsExif = process_EXIF((uint8_t *)Data+2, itemlen); + }else{ + // Discard this section. + free(Sections[--SectionsRead].Data); + Sections[SectionsRead].Data=0; + } + break; + + case M_SOF0: + case M_SOF1: + case M_SOF2: + case M_SOF3: + case M_SOF5: + case M_SOF6: + case M_SOF7: + case M_SOF9: + case M_SOF10: + case M_SOF11: + case M_SOF13: + case M_SOF14: + case M_SOF15: + process_SOFn(Data, marker); + break; + default: + // Skip any other sections. + //if (ShowTags) printf("Jpeg section marker 0x%02x size %d\n",marker, itemlen); + break; + } + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/*-------------------------------------------------------------------------- + Process a EXIF marker + Describes all the drivel that most digital cameras include... +--------------------------------------------------------------------------*/ +bool CxImageJPG::CxExifInfo::process_EXIF(uint8_t * CharBuf, uint32_t length) +{ + m_exifinfo->FlashUsed = 0; + /* If it's from a digicam, and it used flash, it says so. */ + m_exifinfo->Comments[0] = '\0'; /* Initial value - null string */ + + ExifImageWidth = 0; + + { /* Check the EXIF header component */ + static const uint8_t ExifHeader[] = "Exif\0\0"; + if (memcmp(CharBuf+0, ExifHeader,6)){ + strcpy(m_szLastError,"Incorrect Exif header"); + return false; + } + } + + if (memcmp(CharBuf+6,"II",2) == 0){ + MotorolaOrder = 0; + }else{ + if (memcmp(CharBuf+6,"MM",2) == 0){ + MotorolaOrder = 1; + }else{ + strcpy(m_szLastError,"Invalid Exif alignment marker."); + return false; + } + } + + /* Check the next two values for correctness. */ + if (Get16u(CharBuf+8) != 0x2a){ + strcpy(m_szLastError,"Invalid Exif start (1)"); + return false; + } + + int32_t FirstOffset = Get32u(CharBuf+10); + /* + if (FirstOffset < 8 || FirstOffset > 16){ + // I used to ensure this was set to 8 (website I used indicated its 8) + // but PENTAX Optio 230 has it set differently, and uses it as offset. (Sept 11 2002) + strcpy(m_szLastError,"Suspicious offset of first IFD value"); + return false; + }*/ + + uint8_t * LastExifRefd = CharBuf; + + /* First directory starts 16 bytes in. Offsets start at 8 bytes in. */ + if (!ProcessExifDir(CharBuf+14, CharBuf+6, length-6, m_exifinfo, &LastExifRefd)) + return false; + + /* give a chance for a second directory */ + if (FirstOffset > 8) { + if (!ProcessExifDir(CharBuf+14+FirstOffset-8, CharBuf+6, length-6, m_exifinfo, &LastExifRefd)) + return false; + } + + /* This is how far the interesting (non thumbnail) part of the exif went. */ + // int32_t ExifSettingsLength = LastExifRefd - CharBuf; + + /* Compute the CCD width, in milimeters. */ + if (m_exifinfo->FocalplaneXRes != 0){ + m_exifinfo->CCDWidth = (float)(ExifImageWidth * m_exifinfo->FocalplaneUnits / m_exifinfo->FocalplaneXRes); + } + + return true; +} +//-------------------------------------------------------------------------- +// Get 16 bits motorola order (always) for jpeg header stuff. +//-------------------------------------------------------------------------- +int32_t CxImageJPG::CxExifInfo::Get16m(void * Short) +{ + return (((uint8_t *)Short)[0] << 8) | ((uint8_t *)Short)[1]; +} +//////////////////////////////////////////////////////////////////////////////// +/*-------------------------------------------------------------------------- + Convert a 16 bit unsigned value from file's native byte order +--------------------------------------------------------------------------*/ +int32_t CxImageJPG::CxExifInfo::Get16u(void * Short) +{ + if (MotorolaOrder){ + return (((uint8_t *)Short)[0] << 8) | ((uint8_t *)Short)[1]; + }else{ + return (((uint8_t *)Short)[1] << 8) | ((uint8_t *)Short)[0]; + } +} +//////////////////////////////////////////////////////////////////////////////// +/*-------------------------------------------------------------------------- + Convert a 32 bit signed value from file's native byte order +--------------------------------------------------------------------------*/ +int32_t CxImageJPG::CxExifInfo::Get32s(void * Long) +{ + if (MotorolaOrder){ + return ((( char *)Long)[0] << 24) | (((uint8_t *)Long)[1] << 16) + | (((uint8_t *)Long)[2] << 8 ) | (((uint8_t *)Long)[3] << 0 ); + }else{ + return ((( char *)Long)[3] << 24) | (((uint8_t *)Long)[2] << 16) + | (((uint8_t *)Long)[1] << 8 ) | (((uint8_t *)Long)[0] << 0 ); + } +} +//////////////////////////////////////////////////////////////////////////////// +/*-------------------------------------------------------------------------- + Convert a 32 bit unsigned value from file's native byte order +--------------------------------------------------------------------------*/ +uint32_t CxImageJPG::CxExifInfo::Get32u(void * Long) +{ + return (uint32_t)Get32s(Long) & 0xffffffff; +} +//////////////////////////////////////////////////////////////////////////////// + +/* Describes format descriptor */ +static const int32_t BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8}; +#define NUM_FORMATS 12 + +#define FMT_BYTE 1 +#define FMT_STRING 2 +#define FMT_USHORT 3 +#define FMT_ULONG 4 +#define FMT_URATIONAL 5 +#define FMT_SBYTE 6 +#define FMT_UNDEFINED 7 +#define FMT_SSHORT 8 +#define FMT_SLONG 9 +#define FMT_SRATIONAL 10 +#define FMT_SINGLE 11 +#define FMT_DOUBLE 12 + +/* Describes tag values */ + +#define TAG_EXIF_VERSION 0x9000 +#define TAG_EXIF_OFFSET 0x8769 +#define TAG_INTEROP_OFFSET 0xa005 + +#define TAG_MAKE 0x010F +#define TAG_MODEL 0x0110 + +#define TAG_ORIENTATION 0x0112 +#define TAG_XRESOLUTION 0x011A +#define TAG_YRESOLUTION 0x011B +#define TAG_RESOLUTIONUNIT 0x0128 + +#define TAG_EXPOSURETIME 0x829A +#define TAG_FNUMBER 0x829D + +#define TAG_SHUTTERSPEED 0x9201 +#define TAG_APERTURE 0x9202 +#define TAG_BRIGHTNESS 0x9203 +#define TAG_MAXAPERTURE 0x9205 +#define TAG_FOCALLENGTH 0x920A + +#define TAG_DATETIME_ORIGINAL 0x9003 +#define TAG_USERCOMMENT 0x9286 + +#define TAG_SUBJECT_DISTANCE 0x9206 +#define TAG_FLASH 0x9209 + +#define TAG_FOCALPLANEXRES 0xa20E +#define TAG_FOCALPLANEYRES 0xa20F +#define TAG_FOCALPLANEUNITS 0xa210 +#define TAG_EXIF_IMAGEWIDTH 0xA002 +#define TAG_EXIF_IMAGELENGTH 0xA003 + +/* the following is added 05-jan-2001 vcs */ +#define TAG_EXPOSURE_BIAS 0x9204 +#define TAG_WHITEBALANCE 0x9208 +#define TAG_METERING_MODE 0x9207 +#define TAG_EXPOSURE_PROGRAM 0x8822 +#define TAG_ISO_EQUIVALENT 0x8827 +#define TAG_COMPRESSION_LEVEL 0x9102 + +#define TAG_THUMBNAIL_OFFSET 0x0201 +#define TAG_THUMBNAIL_LENGTH 0x0202 + + +/*-------------------------------------------------------------------------- + Process one of the nested EXIF directories. +--------------------------------------------------------------------------*/ +bool CxImageJPG::CxExifInfo::ProcessExifDir(uint8_t * DirStart, uint8_t * OffsetBase, unsigned ExifLength, + EXIFINFO * const m_exifinfo, uint8_t ** const LastExifRefdP, int32_t NestingLevel) +{ + int32_t de; + int32_t a; + int32_t NumDirEntries; + unsigned ThumbnailOffset = 0; + unsigned ThumbnailSize = 0; + + if (NestingLevel > 4){ + strcpy(m_szLastError,"Maximum directory nesting exceeded (corrupt exif header)"); + return false; + } + + NumDirEntries = Get16u(DirStart); + + if ((DirStart+2+NumDirEntries*12) > (OffsetBase+ExifLength)){ + strcpy(m_szLastError,"Illegally sized directory"); + return false; + } + + for (de=0;de= NUM_FORMATS) { + /* (-1) catches illegal zero case as unsigned underflows to positive large */ + strcpy(m_szLastError,"Illegal format code in EXIF dir"); + return false; + } + + ByteCount = Components * BytesPerFormat[Format]; + + if (ByteCount > 4){ + unsigned OffsetVal; + OffsetVal = Get32u(DirEntry+8); + /* If its bigger than 4 bytes, the dir entry contains an offset.*/ + if (OffsetVal+ByteCount > ExifLength){ + /* Bogus pointer offset and / or bytecount value */ + strcpy(m_szLastError,"Illegal pointer offset value in EXIF."); + return false; + } + ValuePtr = OffsetBase+OffsetVal; + }else{ + /* 4 bytes or less and value is in the dir entry itself */ + ValuePtr = DirEntry+8; + } + + if (*LastExifRefdP < ValuePtr+ByteCount){ + /* Keep track of last byte in the exif header that was + actually referenced. That way, we know where the + discardable thumbnail data begins. + */ + *LastExifRefdP = ValuePtr+ByteCount; + } + + /* Extract useful components of tag */ + switch(Tag){ + + case TAG_MAKE: + strncpy(m_exifinfo->CameraMake, (char*)ValuePtr, 31); + break; + + case TAG_MODEL: + strncpy(m_exifinfo->CameraModel, (char*)ValuePtr, 39); + break; + + case TAG_EXIF_VERSION: + strncpy(m_exifinfo->Version,(char*)ValuePtr, 4); + break; + + case TAG_DATETIME_ORIGINAL: + strncpy(m_exifinfo->DateTime, (char*)ValuePtr, 19); + break; + + case TAG_USERCOMMENT: + // Olympus has this padded with trailing spaces. Remove these first. + for (a=ByteCount;;){ + a--; + if (((char*)ValuePtr)[a] == ' '){ + ((char*)ValuePtr)[a] = '\0'; + }else{ + break; + } + if (a == 0) break; + } + + /* Copy the comment */ + if (memcmp(ValuePtr, "ASCII",5) == 0){ + for (a=5;a<10;a++){ + char c; + c = ((char*)ValuePtr)[a]; + if (c != '\0' && c != ' '){ + strncpy(m_exifinfo->Comments, (char*)ValuePtr+a, 199); + break; + } + } + + }else{ + strncpy(m_exifinfo->Comments, (char*)ValuePtr, 199); + } + break; + + case TAG_FNUMBER: + /* Simplest way of expressing aperture, so I trust it the most. + (overwrite previously computd value if there is one) + */ + m_exifinfo->ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_APERTURE: + case TAG_MAXAPERTURE: + /* More relevant info always comes earlier, so only + use this field if we don't have appropriate aperture + information yet. + */ + if (m_exifinfo->ApertureFNumber == 0){ + m_exifinfo->ApertureFNumber = (float)exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0f)*0.5); + } + break; + + case TAG_BRIGHTNESS: + m_exifinfo->Brightness = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_FOCALLENGTH: + /* Nice digital cameras actually save the focal length + as a function of how farthey are zoomed in. + */ + + m_exifinfo->FocalLength = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_SUBJECT_DISTANCE: + /* Inidcates the distacne the autofocus camera is focused to. + Tends to be less accurate as distance increases. + */ + m_exifinfo->Distance = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXPOSURETIME: + /* Simplest way of expressing exposure time, so I + trust it most. (overwrite previously computd value + if there is one) + */ + m_exifinfo->ExposureTime = + (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_SHUTTERSPEED: + /* More complicated way of expressing exposure time, + so only use this value if we don't already have it + from somewhere else. + */ + if (m_exifinfo->ExposureTime == 0){ + m_exifinfo->ExposureTime = (float) + (1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0f))); + } + break; + + case TAG_FLASH: + if ((int32_t)ConvertAnyFormat(ValuePtr, Format) & 7){ + m_exifinfo->FlashUsed = 1; + }else{ + m_exifinfo->FlashUsed = 0; + } + break; + + case TAG_ORIENTATION: + m_exifinfo->Orientation = (int32_t)ConvertAnyFormat(ValuePtr, Format); + if (m_exifinfo->Orientation < 1 || m_exifinfo->Orientation > 8){ + strcpy(m_szLastError,"Undefined rotation value"); + m_exifinfo->Orientation = 0; + } + break; + + case TAG_EXIF_IMAGELENGTH: + case TAG_EXIF_IMAGEWIDTH: + /* Use largest of height and width to deal with images + that have been rotated to portrait format. + */ + a = (int32_t)ConvertAnyFormat(ValuePtr, Format); + if (ExifImageWidth < a) ExifImageWidth = a; + break; + + case TAG_FOCALPLANEXRES: + m_exifinfo->FocalplaneXRes = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_FOCALPLANEYRES: + m_exifinfo->FocalplaneYRes = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_RESOLUTIONUNIT: + switch((int32_t)ConvertAnyFormat(ValuePtr, Format)){ + case 1: m_exifinfo->ResolutionUnit = 1.0f; break; /* 1 inch */ + case 2: m_exifinfo->ResolutionUnit = 1.0f; break; + case 3: m_exifinfo->ResolutionUnit = 0.3937007874f; break; /* 1 centimeter*/ + case 4: m_exifinfo->ResolutionUnit = 0.03937007874f; break; /* 1 millimeter*/ + case 5: m_exifinfo->ResolutionUnit = 0.00003937007874f; /* 1 micrometer*/ + } + break; + + case TAG_FOCALPLANEUNITS: + switch((int32_t)ConvertAnyFormat(ValuePtr, Format)){ + case 1: m_exifinfo->FocalplaneUnits = 1.0f; break; /* 1 inch */ + case 2: m_exifinfo->FocalplaneUnits = 1.0f; break; + case 3: m_exifinfo->FocalplaneUnits = 0.3937007874f; break; /* 1 centimeter*/ + case 4: m_exifinfo->FocalplaneUnits = 0.03937007874f; break; /* 1 millimeter*/ + case 5: m_exifinfo->FocalplaneUnits = 0.00003937007874f; /* 1 micrometer*/ + } + break; + + // Remaining cases contributed by: Volker C. Schoech + + case TAG_EXPOSURE_BIAS: + m_exifinfo->ExposureBias = (float) ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_WHITEBALANCE: + m_exifinfo->Whitebalance = (int32_t)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_METERING_MODE: + m_exifinfo->MeteringMode = (int32_t)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXPOSURE_PROGRAM: + m_exifinfo->ExposureProgram = (int32_t)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_ISO_EQUIVALENT: + m_exifinfo->ISOequivalent = (int32_t)ConvertAnyFormat(ValuePtr, Format); + if ( m_exifinfo->ISOequivalent < 50 ) m_exifinfo->ISOequivalent *= 200; + break; + + case TAG_COMPRESSION_LEVEL: + m_exifinfo->CompressionLevel = (int32_t)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_XRESOLUTION: + m_exifinfo->Xresolution = (float)ConvertAnyFormat(ValuePtr, Format); + break; + case TAG_YRESOLUTION: + m_exifinfo->Yresolution = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_THUMBNAIL_OFFSET: + ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_THUMBNAIL_LENGTH: + ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format); + break; + + } + + if (Tag == TAG_EXIF_OFFSET || Tag == TAG_INTEROP_OFFSET){ + uint8_t * SubdirStart; + unsigned Offset = Get32u(ValuePtr); + if (Offset>8){ + SubdirStart = OffsetBase + Offset; + if (SubdirStart < OffsetBase || + SubdirStart > OffsetBase+ExifLength){ + strcpy(m_szLastError,"Illegal subdirectory link"); + return false; + } + ProcessExifDir(SubdirStart, OffsetBase, ExifLength, m_exifinfo, LastExifRefdP, NestingLevel+1); + } + continue; + } + } + + + { + /* In addition to linking to subdirectories via exif tags, + there's also a potential link to another directory at the end + of each directory. This has got to be the result of a + committee! + */ + uint8_t * SubdirStart; + unsigned Offset; + Offset = Get16u(DirStart+2+12*NumDirEntries); + if (Offset){ + SubdirStart = OffsetBase + Offset; + if (SubdirStart < OffsetBase + || SubdirStart > OffsetBase+ExifLength){ + strcpy(m_szLastError,"Illegal subdirectory link"); + return false; + } + ProcessExifDir(SubdirStart, OffsetBase, ExifLength, m_exifinfo, LastExifRefdP, NestingLevel+1); + } + } + + + if (ThumbnailSize && ThumbnailOffset){ + if (ThumbnailSize + ThumbnailOffset <= ExifLength){ + /* The thumbnail pointer appears to be valid. Store it. */ + m_exifinfo->ThumbnailPointer = OffsetBase + ThumbnailOffset; + m_exifinfo->ThumbnailSize = ThumbnailSize; + } + } + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/*-------------------------------------------------------------------------- + Evaluate number, be it int32_t, rational, or float from directory. +--------------------------------------------------------------------------*/ +double CxImageJPG::CxExifInfo::ConvertAnyFormat(void * ValuePtr, int32_t Format) +{ + double Value; + Value = 0; + + switch(Format){ + case FMT_SBYTE: Value = *(signed char *)ValuePtr; break; + case FMT_BYTE: Value = *(uint8_t *)ValuePtr; break; + + case FMT_USHORT: Value = Get16u(ValuePtr); break; + case FMT_ULONG: Value = Get32u(ValuePtr); break; + + case FMT_URATIONAL: + case FMT_SRATIONAL: + { + int32_t Num,Den; + Num = Get32s(ValuePtr); + Den = Get32s(4+(char *)ValuePtr); + if (Den == 0){ + Value = 0; + }else{ + Value = (double)Num/Den; + } + break; + } + + case FMT_SSHORT: Value = (int16_t)Get16u(ValuePtr); break; + case FMT_SLONG: Value = Get32s(ValuePtr); break; + + /* Not sure if this is correct (never seen float used in Exif format) + */ + case FMT_SINGLE: Value = (double)*(float *)ValuePtr; break; + case FMT_DOUBLE: Value = *(double *)ValuePtr; break; + } + return Value; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageJPG::CxExifInfo::process_COM (const uint8_t * Data, int32_t length) +{ + int32_t ch; + char Comment[MAX_COMMENT+1]; + int32_t nch; + int32_t a; + + nch = 0; + + if (length > MAX_COMMENT) length = MAX_COMMENT; // Truncate if it won't fit in our structure. + + for (a=2;aComments,Comment); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageJPG::CxExifInfo::process_SOFn (const uint8_t * Data, int32_t marker) +{ + int32_t data_precision, num_components; + + data_precision = Data[2]; + m_exifinfo->Height = Get16m((void*)(Data+3)); + m_exifinfo->Width = Get16m((void*)(Data+5)); + num_components = Data[7]; + + if (num_components == 3){ + m_exifinfo->IsColor = 1; + }else{ + m_exifinfo->IsColor = 0; + } + + m_exifinfo->Process = marker; + + //if (ShowTags) printf("JPEG image is %uw * %uh, %d color components, %d bits per sample\n", + // ImageInfo.Width, ImageInfo.Height, num_components, data_precision); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * this will work only on a CxImageJPG object, if the image originally has valid EXIF data + \verbatim + CxImageJPG jpg; + CxIOFile in,out; + in.Open("D:\\exif_in.jpg","rb"); + out.Open("D:\\exif_out.jpg","w+b"); + jpg.Decode(&in); + if (jpg.IsValid()){ + jpg.RotateLeft(); + jpg.Encode(&out); + } + \endverbatim +*/ +bool CxImageJPG::CxExifInfo::EncodeExif(CxFile * hFile) +{ + int32_t a; + + if (FindSection(M_SOS)==NULL){ + strcpy(m_szLastError,"Can't write exif : didn't read all"); + return false; + } + + // Initial static jpeg marker. + hFile->PutC(0xff); + hFile->PutC(0xd8); + + if (Sections[0].Type != M_EXIF && Sections[0].Type != M_JFIF){ + // The image must start with an exif or jfif marker. If we threw those away, create one. + static uint8_t JfifHead[18] = { + 0xff, M_JFIF, + 0x00, 0x10, 'J' , 'F' , 'I' , 'F' , 0x00, 0x01, + 0x01, 0x01, 0x01, 0x2C, 0x01, 0x2C, 0x00, 0x00 + }; + hFile->Write(JfifHead, 18, 1); + } + + // Write all the misc sections + for (a=0;aPutC(0xff); + hFile->PutC((uint8_t)(Sections[a].Type)); + hFile->Write(Sections[a].Data, Sections[a].Size, 1); + } + + // Write the remaining image data. + hFile->Write(Sections[a].Data, Sections[a].Size, 1); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageJPG::CxExifInfo::DiscardAllButExif() +{ + Section_t ExifKeeper; + Section_t CommentKeeper; + int32_t a; + + memset(&ExifKeeper, 0, sizeof(ExifKeeper)); + memset(&CommentKeeper, 0, sizeof(ExifKeeper)); + + for (a=0;a Use it before Create() + */ +void CxImage::CopyInfo(const CxImage &src) +{ + if (pDib==NULL) memcpy(&info,&src.info,sizeof(CXIMAGEINFO)); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa Copy + */ +CxImage& CxImage::operator = (const CxImage& isrc) +{ + if (this != &isrc) Copy(isrc); + return *this; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Initializes or rebuilds the image. + * \param dwWidth: width + * \param dwHeight: height + * \param wBpp: bit per pixel, can be 1, 4, 8, 24 + * \param imagetype: (optional) set the image format, see ENUM_CXIMAGE_FORMATS + * \return pointer to the internal pDib object; NULL if an error occurs. + */ +void* CxImage::Create(uint32_t dwWidth, uint32_t dwHeight, uint32_t wBpp, uint32_t imagetype) +{ + // destroy the existing image (if any) + if (!Destroy()) + return NULL; + + // prevent further actions if width or height are not vaild + if ((dwWidth == 0) || (dwHeight == 0)){ + strcpy(info.szLastError,"CxImage::Create : width and height must be greater than zero"); + return NULL; + } + + // Make sure bits per pixel is valid + if (wBpp <= 1) wBpp = 1; + else if (wBpp <= 4) wBpp = 4; + else if (wBpp <= 8) wBpp = 8; + else wBpp = 24; + + // limit memory requirements + if ((((float)dwWidth*(float)dwHeight*(float)wBpp)/8.0f) > (float)CXIMAGE_MAX_MEMORY) + { + strcpy(info.szLastError,"CXIMAGE_MAX_MEMORY exceeded"); + return NULL; + } + + // set the correct bpp value + switch (wBpp){ + case 1: + head.biClrUsed = 2; break; + case 4: + head.biClrUsed = 16; break; + case 8: + head.biClrUsed = 256; break; + default: + head.biClrUsed = 0; + } + + //set the common image informations + info.dwEffWidth = ((((wBpp * dwWidth) + 31) / 32) * 4); + info.dwType = imagetype; + + // initialize BITMAPINFOHEADER + head.biSize = sizeof(BITMAPINFOHEADER); // + head.biWidth = dwWidth; // fill in width from parameter + head.biHeight = dwHeight; // fill in height from parameter + head.biPlanes = 1; // must be 1 + head.biBitCount = (uint16_t)wBpp; // from parameter + head.biCompression = BI_RGB; + head.biSizeImage = info.dwEffWidth * dwHeight; +// head.biXPelsPerMeter = 0; See SetXDPI +// head.biYPelsPerMeter = 0; See SetYDPI +// head.biClrImportant = 0; See SetClrImportant + + pDib = malloc(GetSize()); // alloc memory block to store our bitmap + if (!pDib){ + strcpy(info.szLastError,"CxImage::Create can't allocate memory"); + return NULL; + } + + //clear the palette + RGBQUAD* pal=GetPalette(); + if (pal) memset(pal,0,GetPaletteSize()); + //Destroy the existing selection +#if CXIMAGE_SUPPORT_SELECTION + if (pSelection) SelectionDelete(); +#endif //CXIMAGE_SUPPORT_SELECTION + //Destroy the existing alpha channel +#if CXIMAGE_SUPPORT_ALPHA + if (pAlpha) AlphaDelete(); +#endif //CXIMAGE_SUPPORT_ALPHA + + // use our bitmap info structure to fill in first part of + // our DIB with the BITMAPINFOHEADER + BITMAPINFOHEADER* lpbi; + lpbi = (BITMAPINFOHEADER*)(pDib); + *lpbi = head; + + info.pImage=GetBits(); + + return pDib; //return handle to the DIB +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return pointer to the image pixels. USE CAREFULLY + */ +uint8_t* CxImage::GetBits(uint32_t row) +{ + if (pDib){ + if (row) { + if (row<(uint32_t)head.biHeight){ + return ((uint8_t*)pDib + *(uint32_t*)pDib + GetPaletteSize() + (info.dwEffWidth * row)); + } else { + return NULL; + } + } else { + return ((uint8_t*)pDib + *(uint32_t*)pDib + GetPaletteSize()); + } + } + return NULL; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return the size in bytes of the internal pDib object + */ +int32_t CxImage::GetSize() +{ + return head.biSize + head.biSizeImage + GetPaletteSize(); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Checks if the coordinates are inside the image + * \return true if x and y are both inside the image + */ +bool CxImage::IsInside(int32_t x, int32_t y) +{ + return (0<=y && y 0) bval = 255; + } + if (GetBpp() == 4){ + bval = (uint8_t)(17*(0x0F & bval)); + } + + memset(info.pImage,bval,head.biSizeImage); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Transfers the image from an existing source image. The source becomes empty. + * \return true if everything is ok + */ +bool CxImage::Transfer(CxImage &from, bool bTransferFrames /*=true*/) +{ + if (!Destroy()) + return false; + + memcpy(&head,&from.head,sizeof(BITMAPINFOHEADER)); + memcpy(&info,&from.info,sizeof(CXIMAGEINFO)); + + pDib = from.pDib; + pSelection = from.pSelection; + pAlpha = from.pAlpha; + ppLayers = from.ppLayers; + + memset(&from.head,0,sizeof(BITMAPINFOHEADER)); + memset(&from.info,0,sizeof(CXIMAGEINFO)); + from.pDib = from.pSelection = from.pAlpha = NULL; + from.ppLayers = NULL; + + if (bTransferFrames){ + DestroyFrames(); + ppFrames = from.ppFrames; + from.ppFrames = NULL; + } + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * (this) points to the same pDib owned by (*from), the image remains in (*from) + * but (this) has the access to the pixels. Use carefully !!! + */ +void CxImage::Ghost(const CxImage *from) +{ + if (from){ + memcpy(&head,&from->head,sizeof(BITMAPINFOHEADER)); + memcpy(&info,&from->info,sizeof(CXIMAGEINFO)); + pDib = from->pDib; + pSelection = from->pSelection; + pAlpha = from->pAlpha; + ppLayers = from->ppLayers; + ppFrames = from->ppFrames; + info.pGhost=(CxImage *)from; + } +} +//////////////////////////////////////////////////////////////////////////////// +/** + * turns a 16 or 32 bit bitfield image into a RGB image + */ +void CxImage::Bitfield2RGB(uint8_t *src, uint32_t redmask, uint32_t greenmask, uint32_t bluemask, uint8_t bpp) +{ + switch (bpp){ + case 16: + { + uint32_t ns[3]={0,0,0}; + // compute the number of shift for each mask + for (int32_t i=0;i<16;i++){ + if ((redmask>>i)&0x01) ns[0]++; + if ((greenmask>>i)&0x01) ns[1]++; + if ((bluemask>>i)&0x01) ns[2]++; + } + ns[1]+=ns[0]; ns[2]+=ns[1]; ns[0]=8-ns[0]; ns[1]-=8; ns[2]-=8; + // dword aligned width for 16 bit image + int32_t effwidth2=(((head.biWidth + 1) / 2) * 4); + uint16_t w; + int32_t y2,y3,x2,x3; + uint8_t *p=info.pImage; + // scan the buffer in reverse direction to avoid reallocations + for (int32_t y=head.biHeight-1; y>=0; y--){ + y2=effwidth2*y; + y3=info.dwEffWidth*y; + for (int32_t x=head.biWidth-1; x>=0; x--){ + x2 = 2*x+y2; + x3 = 3*x+y3; + w = (uint16_t)(src[x2]+256*src[1+x2]); + p[ x3]=(uint8_t)((w & bluemask)<>ns[1]); + p[2+x3]=(uint8_t)((w & redmask)>>ns[2]); + } + } + break; + } + case 32: + { + uint32_t ns[3]={0,0,0}; + // compute the number of shift for each mask + for (int32_t i=8;i<32;i+=8){ + if (redmask>>i) ns[0]++; + if (greenmask>>i) ns[1]++; + if (bluemask>>i) ns[2]++; + } + // dword aligned width for 32 bit image + int32_t effwidth4 = head.biWidth * 4; + int32_t y4,y3,x4,x3; + uint8_t *p=info.pImage; + // scan the buffer in reverse direction to avoid reallocations + for (int32_t y=head.biHeight-1; y>=0; y--){ + y4=effwidth4*y; + y3=info.dwEffWidth*y; + for (int32_t x=head.biWidth-1; x>=0; x--){ + x4 = 4*x+y4; + x3 = 3*x+y3; + p[ x3]=src[ns[2]+x4]; + p[1+x3]=src[ns[1]+x4]; + p[2+x3]=src[ns[0]+x4]; + } + } + } + + } + return; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Creates an image from a generic buffer + * \param pArray: source memory buffer + * \param dwWidth: image width + * \param dwHeight: image height + * \param dwBitsperpixel: can be 1,4,8,24,32 + * \param dwBytesperline: line alignment, in bytes, for a single row stored in pArray + * \param bFlipImage: tune this parameter if the image is upsidedown + * \return true if everything is ok + */ +bool CxImage::CreateFromArray(uint8_t* pArray,uint32_t dwWidth,uint32_t dwHeight,uint32_t dwBitsperpixel, uint32_t dwBytesperline, bool bFlipImage) +{ + if (pArray==NULL) return false; + if (!((dwBitsperpixel==1)||(dwBitsperpixel==4)||(dwBitsperpixel==8)|| + (dwBitsperpixel==24)||(dwBitsperpixel==32))) return false; + + if (!Create(dwWidth,dwHeight,dwBitsperpixel)) return false; + + if (dwBitsperpixel<24) SetGrayPalette(); + +#if CXIMAGE_SUPPORT_ALPHA + if (dwBitsperpixel==32) AlphaCreate(); +#endif //CXIMAGE_SUPPORT_ALPHA + + uint8_t *dst,*src; + + for (uint32_t y = 0; yrgbRed,c1->rgbGreen,c1->rgbBlue); + int32_t g2 = (int32_t)RGB2GRAY(c2->rgbRed,c2->rgbGreen,c2->rgbBlue); + + return (g1-g2); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * simply calls "if (memblock) free(memblock);". + * Useful when calling Encode for a memory buffer, + * from a DLL compiled with different memory management options. + * CxImage::FreeMemory will use the same memory environment used by Encode. + * \author [livecn] + */ +void CxImage::FreeMemory(void* memblock) +{ + if (memblock) + free(memblock); +} +//////////////////////////////////////////////////////////////////////////////// +//EOF diff --git a/DuiLib/3rd/CxImage/ximage.h b/DuiLib/3rd/CxImage/ximage.h new file mode 100644 index 0000000..f54dc9d --- /dev/null +++ b/DuiLib/3rd/CxImage/ximage.h @@ -0,0 +1,807 @@ +/* + * File: ximage.h + * Purpose: General Purpose Image Class + */ +/* + -------------------------------------------------------------------------------- + + COPYRIGHT NOTICE, DISCLAIMER, and LICENSE: + + CxImage version 7.0.1 07/Jan/2011 + + CxImage : Copyright (C) 2001 - 2010, Davide Pizzolato + + Original CImage and CImageIterator implementation are: + Copyright (C) 1995, Alejandro Aguilar Sierra (asierra(at)servidor(dot)unam(dot)mx) + + Covered code is provided under this license on an "as is" basis, without warranty + of any kind, either expressed or implied, including, without limitation, warranties + that the covered code is free of defects, merchantable, fit for a particular purpose + or non-infringing. The entire risk as to the quality and performance of the covered + code is with you. Should any covered code prove defective in any respect, you (not + the initial developer or any other contributor) assume the cost of any necessary + servicing, repair or correction. This disclaimer of warranty constitutes an essential + part of this license. No use of any covered code is authorized hereunder except under + this disclaimer. + + Permission is hereby granted to use, copy, modify, and distribute this + source code, or portions hereof, for any purpose, including commercial applications, + freely and without fee, 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. + + -------------------------------------------------------------------------------- + + Other information about CxImage, and the latest version, can be found at the + CxImage home page: http://www.xdp.it/cximage/ + + -------------------------------------------------------------------------------- + */ +#if !defined(__CXIMAGE_H) +#define __CXIMAGE_H + +#if _MSC_VER > 1000 +#pragma once +#endif + +#ifdef _LINUX + #define _XOPEN_SOURCE + #include + #include +#endif + +///////////////////////////////////////////////////////////////////////////// +#include "xfile.h" +#include "xiofile.h" +#include "xmemfile.h" +#include "ximadef.h" // adjust some #define + +/* see "ximacfg.h" for CxImage configuration options */ + +///////////////////////////////////////////////////////////////////////////// +// CxImage formats enumerator +enum ENUM_CXIMAGE_FORMATS{ +CXIMAGE_FORMAT_UNKNOWN = 0, +#if CXIMAGE_SUPPORT_BMP +CXIMAGE_FORMAT_BMP = 1, +#endif +#if CXIMAGE_SUPPORT_GIF +CXIMAGE_FORMAT_GIF = 2, +#endif +#if CXIMAGE_SUPPORT_JPG +CXIMAGE_FORMAT_JPG = 3, +#endif +#if CXIMAGE_SUPPORT_PNG +CXIMAGE_FORMAT_PNG = 4, +#endif +#if CXIMAGE_SUPPORT_ICO +CXIMAGE_FORMAT_ICO = 5, +#endif +#if CXIMAGE_SUPPORT_TIF +CXIMAGE_FORMAT_TIF = 6, +#endif +#if CXIMAGE_SUPPORT_TGA +CXIMAGE_FORMAT_TGA = 7, +#endif +#if CXIMAGE_SUPPORT_PCX +CXIMAGE_FORMAT_PCX = 8, +#endif +#if CXIMAGE_SUPPORT_WBMP +CXIMAGE_FORMAT_WBMP = 9, +#endif +#if CXIMAGE_SUPPORT_WMF +CXIMAGE_FORMAT_WMF = 10, +#endif +#if CXIMAGE_SUPPORT_JP2 +CXIMAGE_FORMAT_JP2 = 11, +#endif +#if CXIMAGE_SUPPORT_JPC +CXIMAGE_FORMAT_JPC = 12, +#endif +#if CXIMAGE_SUPPORT_PGX +CXIMAGE_FORMAT_PGX = 13, +#endif +#if CXIMAGE_SUPPORT_PNM +CXIMAGE_FORMAT_PNM = 14, +#endif +#if CXIMAGE_SUPPORT_RAS +CXIMAGE_FORMAT_RAS = 15, +#endif +#if CXIMAGE_SUPPORT_JBG +CXIMAGE_FORMAT_JBG = 16, +#endif +#if CXIMAGE_SUPPORT_MNG +CXIMAGE_FORMAT_MNG = 17, +#endif +#if CXIMAGE_SUPPORT_SKA +CXIMAGE_FORMAT_SKA = 18, +#endif +#if CXIMAGE_SUPPORT_RAW +CXIMAGE_FORMAT_RAW = 19, +#endif +#if CXIMAGE_SUPPORT_PSD +CXIMAGE_FORMAT_PSD = 20, +#endif +CMAX_IMAGE_FORMATS = CXIMAGE_SUPPORT_BMP + CXIMAGE_SUPPORT_GIF + CXIMAGE_SUPPORT_JPG + + CXIMAGE_SUPPORT_PNG + CXIMAGE_SUPPORT_MNG + CXIMAGE_SUPPORT_ICO + + CXIMAGE_SUPPORT_TIF + CXIMAGE_SUPPORT_TGA + CXIMAGE_SUPPORT_PCX + + CXIMAGE_SUPPORT_WBMP+ CXIMAGE_SUPPORT_WMF + + CXIMAGE_SUPPORT_JBG + CXIMAGE_SUPPORT_JP2 + CXIMAGE_SUPPORT_JPC + + CXIMAGE_SUPPORT_PGX + CXIMAGE_SUPPORT_PNM + CXIMAGE_SUPPORT_RAS + + CXIMAGE_SUPPORT_SKA + CXIMAGE_SUPPORT_RAW + CXIMAGE_SUPPORT_PSD + 1 +}; + +#if CXIMAGE_SUPPORT_EXIF + +#define MAX_COMMENT 255 +#define MAX_SECTIONS 20 + +typedef struct tag_ExifInfo { + char Version [5]; + char CameraMake [32]; + char CameraModel [40]; + char DateTime [20]; + int32_t Height, Width; + int32_t Orientation; + int32_t IsColor; + int32_t Process; + int32_t FlashUsed; + float FocalLength; + float ExposureTime; + float ApertureFNumber; + float Distance; + float CCDWidth; + float ExposureBias; + int32_t Whitebalance; + int32_t MeteringMode; + int32_t ExposureProgram; + int32_t ISOequivalent; + int32_t CompressionLevel; + float FocalplaneXRes; + float FocalplaneYRes; + float FocalplaneUnits; + float Xresolution; + float Yresolution; + float ResolutionUnit; + float Brightness; + char Comments[MAX_COMMENT+1]; + + uint8_t * ThumbnailPointer; /* Pointer at the thumbnail */ + unsigned ThumbnailSize; /* Size of thumbnail. */ + + bool IsExif; +} EXIFINFO; + +#endif //CXIMAGE_SUPPORT_EXIF + +///////////////////////////////////////////////////////////////////////////// +// CxImage class +///////////////////////////////////////////////////////////////////////////// +class DLL_EXP CxImage +{ +//extensible information collector +typedef struct tagCxImageInfo { + uint32_t dwEffWidth; ///< uint32_t aligned scan line width + uint8_t* pImage; ///< THE IMAGE BITS + CxImage* pGhost; ///< if this is a ghost, pGhost points to the body + CxImage* pParent; ///< if this is a layer, pParent points to the body + uint32_t dwType; ///< original image format + char szLastError[256]; ///< debugging + int32_t nProgress; ///< monitor + int32_t nEscape; ///< escape + int32_t nBkgndIndex; ///< used for GIF, PNG, MNG + RGBQUAD nBkgndColor; ///< used for RGB transparency + float fQuality; ///< used for JPEG, JPEG2000 (0.0f ... 100.0f) + uint8_t nJpegScale; ///< used for JPEG [ignacio] + int32_t nFrame; ///< used for TIF, GIF, MNG : actual frame + int32_t nNumFrames; ///< used for TIF, GIF, MNG : total number of frames + uint32_t dwFrameDelay; ///< used for GIF, MNG + int32_t xDPI; ///< horizontal resolution + int32_t yDPI; ///< vertical resolution + RECT rSelectionBox; ///< bounding rectangle + uint8_t nAlphaMax; ///< max opacity (fade) + bool bAlphaPaletteEnabled; ///< true if alpha values in the palette are enabled. + bool bEnabled; ///< enables the painting functions + int32_t xOffset; + int32_t yOffset; + uint32_t dwCodecOpt[CMAX_IMAGE_FORMATS]; ///< for GIF, TIF : 0=def.1=unc,2=fax3,3=fax4,4=pack,5=jpg + RGBQUAD last_c; ///< for GetNearestIndex optimization + uint8_t last_c_index; + bool last_c_isvalid; + int32_t nNumLayers; + uint32_t dwFlags; ///< 0x??00000 = reserved, 0x00??0000 = blend mode, 0x0000???? = layer id - user flags + uint8_t dispmeth; + bool bGetAllFrames; + bool bLittleEndianHost; + +#if CXIMAGE_SUPPORT_EXIF + EXIFINFO ExifInfo; +#endif + +} CXIMAGEINFO; + +public: + //public structures +struct rgb_color { uint8_t r,g,b; }; + +#if CXIMAGE_SUPPORT_WINDOWS +// text placement data +// members must be initialized with the InitTextInfo(&this) function. +typedef struct tagCxTextInfo +{ +#if defined (_WIN32_WCE) + TCHAR text[256]; ///< text for windows CE +#else + TCHAR text[4096]; ///< text (char -> TCHAR for UNICODE [Cesar M]) +#endif + LOGFONT lfont; ///< font and codepage data + COLORREF fcolor; ///< foreground color + int32_t align; ///< DT_CENTER, DT_RIGHT, DT_LEFT aligment for multiline text + uint8_t smooth; ///< text smoothing option. Default is false. + uint8_t opaque; ///< text has background or hasn't. Default is true. + ///< data for background (ignored if .opaque==FALSE) + COLORREF bcolor; ///< background color + float b_opacity; ///< opacity value for background between 0.0-1.0 Default is 0. (opaque) + uint8_t b_outline; ///< outline width for background (zero: no outline) + uint8_t b_round; ///< rounding radius for background rectangle. % of the height, between 0-50. Default is 10. + ///< (backgr. always has a frame: width = 3 pixel + 10% of height by default.) +} CXTEXTINFO; +#endif + +public: +/** \addtogroup Constructors */ //@{ + CxImage(uint32_t imagetype = 0); + CxImage(uint32_t dwWidth, uint32_t dwHeight, uint32_t wBpp, uint32_t imagetype = 0); + CxImage(const CxImage &src, bool copypixels = true, bool copyselection = true, bool copyalpha = true); +#if CXIMAGE_SUPPORT_DECODE + CxImage(const TCHAR * filename, uint32_t imagetype); // For UNICODE support: char -> TCHAR + CxImage(FILE * stream, uint32_t imagetype); + CxImage(CxFile * stream, uint32_t imagetype); + CxImage(uint8_t * buffer, uint32_t size, uint32_t imagetype); +#endif + virtual ~CxImage() { DestroyFrames(); Destroy(); }; + CxImage& operator = (const CxImage&); +//@} + +/** \addtogroup Initialization */ //@{ + void* Create(uint32_t dwWidth, uint32_t dwHeight, uint32_t wBpp, uint32_t imagetype = 0); + bool Destroy(); + bool DestroyFrames(); + void Clear(uint8_t bval=0); + void Copy(const CxImage &src, bool copypixels = true, bool copyselection = true, bool copyalpha = true); + bool Transfer(CxImage &from, bool bTransferFrames = true); + bool CreateFromArray(uint8_t* pArray,uint32_t dwWidth,uint32_t dwHeight,uint32_t dwBitsperpixel, uint32_t dwBytesperline, bool bFlipImage); + bool CreateFromMatrix(uint8_t** ppMatrix,uint32_t dwWidth,uint32_t dwHeight,uint32_t dwBitsperpixel, uint32_t dwBytesperline, bool bFlipImage); + void FreeMemory(void* memblock); + + uint32_t Dump(uint8_t * dst); + uint32_t UnDump(const uint8_t * src); + uint32_t DumpSize(); + +//@} + +/** \addtogroup Attributes */ //@{ + int32_t GetSize(); + uint8_t* GetBits(uint32_t row = 0); + uint8_t GetColorType(); + void* GetDIB() const; + uint32_t GetHeight() const; + uint32_t GetWidth() const; + uint32_t GetEffWidth() const; + uint32_t GetNumColors() const; + uint16_t GetBpp() const; + uint32_t GetType() const; + const char* GetLastError(); + static const TCHAR* GetVersion(); + static const float GetVersionNumber(); + + uint32_t GetFrameDelay() const; + void SetFrameDelay(uint32_t d); + + void GetOffset(int32_t *x,int32_t *y); + void SetOffset(int32_t x,int32_t y); + + uint8_t GetJpegQuality() const; + void SetJpegQuality(uint8_t q); + float GetJpegQualityF() const; + void SetJpegQualityF(float q); + + uint8_t GetJpegScale() const; + void SetJpegScale(uint8_t q); + +#if CXIMAGE_SUPPORT_EXIF + EXIFINFO *GetExifInfo() {return &info.ExifInfo;}; + bool GetExifThumbnail(const TCHAR *filename, const TCHAR *outname, int32_t imageType); + #if CXIMAGE_SUPPORT_TRANSFORMATION + bool RotateExif(int32_t orientation = 0); + #endif +#endif + + int32_t GetXDPI() const; + int32_t GetYDPI() const; + void SetXDPI(int32_t dpi); + void SetYDPI(int32_t dpi); + + uint32_t GetClrImportant() const; + void SetClrImportant(uint32_t ncolors = 0); + + int32_t GetProgress() const; + int32_t GetEscape() const; + void SetProgress(int32_t p); + void SetEscape(int32_t i); + + int32_t GetTransIndex() const; + RGBQUAD GetTransColor(); + void SetTransIndex(int32_t idx); + void SetTransColor(RGBQUAD rgb); + bool IsTransparent() const; + + uint32_t GetCodecOption(uint32_t imagetype = 0); + bool SetCodecOption(uint32_t opt, uint32_t imagetype = 0); + + uint32_t GetFlags() const; + void SetFlags(uint32_t flags, bool bLockReservedFlags = true); + + uint8_t GetDisposalMethod() const; + void SetDisposalMethod(uint8_t dm); + + bool SetType(uint32_t type); + + static uint32_t GetNumTypes(); + static uint32_t GetTypeIdFromName(const TCHAR* ext); + static uint32_t GetTypeIdFromIndex(const uint32_t index); + static uint32_t GetTypeIndexFromId(const uint32_t id); + + bool GetRetreiveAllFrames() const; + void SetRetreiveAllFrames(bool flag); + CxImage * GetFrame(int32_t nFrame) const; + + //void* GetUserData() const {return info.pUserData;} + //void SetUserData(void* pUserData) {info.pUserData = pUserData;} +//@} + +/** \addtogroup Palette + * These functions have no effects on RGB images and in this case the returned value is always 0. + * @{ */ + bool IsGrayScale(); + bool IsIndexed() const; + bool IsSamePalette(CxImage &img, bool bCheckAlpha = true); + uint32_t GetPaletteSize(); + RGBQUAD* GetPalette() const; + RGBQUAD GetPaletteColor(uint8_t idx); + bool GetPaletteColor(uint8_t i, uint8_t* r, uint8_t* g, uint8_t* b); + uint8_t GetNearestIndex(RGBQUAD c); + void BlendPalette(COLORREF cr,int32_t perc); + void SetGrayPalette(); + void SetPalette(uint32_t n, uint8_t *r, uint8_t *g, uint8_t *b); + void SetPalette(RGBQUAD* pPal,uint32_t nColors=256); + void SetPalette(rgb_color *rgb,uint32_t nColors=256); + void SetPaletteColor(uint8_t idx, uint8_t r, uint8_t g, uint8_t b, uint8_t alpha=0); + void SetPaletteColor(uint8_t idx, RGBQUAD c); + void SetPaletteColor(uint8_t idx, COLORREF cr); + void SwapIndex(uint8_t idx1, uint8_t idx2); + void SwapRGB2BGR(); + void SetStdPalette(); +//@} + +/** \addtogroup Pixel */ //@{ + bool IsInside(int32_t x, int32_t y); + bool IsTransparent(int32_t x,int32_t y); + bool GetTransparentMask(CxImage* iDst = 0); + RGBQUAD GetPixelColor(int32_t x,int32_t y, bool bGetAlpha = true); + uint8_t GetPixelIndex(int32_t x,int32_t y); + uint8_t GetPixelGray(int32_t x, int32_t y); + void SetPixelColor(int32_t x,int32_t y,RGBQUAD c, bool bSetAlpha = false); + void SetPixelColor(int32_t x,int32_t y,COLORREF cr); + void SetPixelIndex(int32_t x,int32_t y,uint8_t i); + void DrawLine(int32_t StartX, int32_t EndX, int32_t StartY, int32_t EndY, RGBQUAD color, bool bSetAlpha=false); + void DrawLine(int32_t StartX, int32_t EndX, int32_t StartY, int32_t EndY, COLORREF cr); + void BlendPixelColor(int32_t x,int32_t y,RGBQUAD c, float blend, bool bSetAlpha = false); +//@} + +protected: +/** \addtogroup Protected */ //@{ + uint8_t BlindGetPixelIndex(const int32_t x,const int32_t y); + RGBQUAD BlindGetPixelColor(const int32_t x,const int32_t y, bool bGetAlpha = true); + void *BlindGetPixelPointer(const int32_t x,const int32_t y); + void BlindSetPixelColor(int32_t x,int32_t y,RGBQUAD c, bool bSetAlpha = false); + void BlindSetPixelIndex(int32_t x,int32_t y,uint8_t i); +//@} + +public: + +#if CXIMAGE_SUPPORT_INTERPOLATION +/** \addtogroup Interpolation */ //@{ + //overflow methods: + enum OverflowMethod { + OM_COLOR=1, + OM_BACKGROUND=2, + OM_TRANSPARENT=3, + OM_WRAP=4, + OM_REPEAT=5, + OM_MIRROR=6 + }; + void OverflowCoordinates(float &x, float &y, OverflowMethod const ofMethod); + void OverflowCoordinates(int32_t &x, int32_t &y, OverflowMethod const ofMethod); + RGBQUAD GetPixelColorWithOverflow(int32_t x, int32_t y, OverflowMethod const ofMethod=OM_BACKGROUND, RGBQUAD* const rplColor=0); + //interpolation methods: + enum InterpolationMethod { + IM_NEAREST_NEIGHBOUR=1, + IM_BILINEAR =2, + IM_BSPLINE =3, + IM_BICUBIC =4, + IM_BICUBIC2 =5, + IM_LANCZOS =6, + IM_BOX =7, + IM_HERMITE =8, + IM_HAMMING =9, + IM_SINC =10, + IM_BLACKMAN =11, + IM_BESSEL =12, + IM_GAUSSIAN =13, + IM_QUADRATIC =14, + IM_MITCHELL =15, + IM_CATROM =16, + IM_HANNING =17, + IM_POWER =18 + }; + RGBQUAD GetPixelColorInterpolated(float x,float y, InterpolationMethod const inMethod=IM_BILINEAR, OverflowMethod const ofMethod=OM_BACKGROUND, RGBQUAD* const rplColor=0); + RGBQUAD GetAreaColorInterpolated(float const xc, float const yc, float const w, float const h, InterpolationMethod const inMethod, OverflowMethod const ofMethod=OM_BACKGROUND, RGBQUAD* const rplColor=0); +//@} + +protected: +/** \addtogroup Protected */ //@{ + void AddAveragingCont(RGBQUAD const &color, float const surf, float &rr, float &gg, float &bb, float &aa); +//@} + +/** \addtogroup Kernels */ //@{ +public: + static float KernelBSpline(const float x); + static float KernelLinear(const float t); + static float KernelCubic(const float t); + static float KernelGeneralizedCubic(const float t, const float a=-1); + static float KernelLanczosSinc(const float t, const float r = 3); + static float KernelBox(const float x); + static float KernelHermite(const float x); + static float KernelHamming(const float x); + static float KernelSinc(const float x); + static float KernelBlackman(const float x); + static float KernelBessel_J1(const float x); + static float KernelBessel_P1(const float x); + static float KernelBessel_Q1(const float x); + static float KernelBessel_Order1(float x); + static float KernelBessel(const float x); + static float KernelGaussian(const float x); + static float KernelQuadratic(const float x); + static float KernelMitchell(const float x); + static float KernelCatrom(const float x); + static float KernelHanning(const float x); + static float KernelPower(const float x, const float a = 2); +//@} +#endif //CXIMAGE_SUPPORT_INTERPOLATION + +/** \addtogroup Painting */ //@{ +#if CXIMAGE_SUPPORT_WINDOWS + int32_t Blt(HDC pDC, int32_t x=0, int32_t y=0); + HBITMAP Draw2HBITMAP(HDC hdc, int32_t x, int32_t y, int32_t cx, int32_t cy, RECT* pClipRect, bool bSmooth); + HBITMAP MakeBitmap(HDC hdc = NULL, bool bTransparency = false); + HICON MakeIcon(HDC hdc = NULL, bool bTransparency = false); + HANDLE CopyToHandle(); + bool CreateFromHANDLE(HANDLE hMem); //Windows objects (clipboard) + bool CreateFromHBITMAP(HBITMAP hbmp, HPALETTE hpal=0, bool bTransparency = false); //Windows resource + bool CreateFromHICON(HICON hico, bool bTransparency = false); + int32_t Draw(HDC hdc, int32_t x=0, int32_t y=0, int32_t cx = -1, int32_t cy = -1, RECT* pClipRect = 0, bool bSmooth = false, bool bFlipY = false); + int32_t Draw(HDC hdc, const RECT& rect, RECT* pClipRect=NULL, bool bSmooth = false, bool bFlipY = false); + int32_t Stretch(HDC hdc, int32_t xoffset, int32_t yoffset, int32_t xsize, int32_t ysize, uint32_t dwRop = SRCCOPY); + int32_t Stretch(HDC hdc, const RECT& rect, uint32_t dwRop = SRCCOPY); + int32_t Tile(HDC hdc, RECT *rc); + int32_t Draw2(HDC hdc, int32_t x=0, int32_t y=0, int32_t cx = -1, int32_t cy = -1); + int32_t Draw2(HDC hdc, const RECT& rect); + //int32_t DrawString(HDC hdc, int32_t x, int32_t y, const char* text, RGBQUAD color, const char* font, int32_t lSize=0, int32_t lWeight=400, uint8_t bItalic=0, uint8_t bUnderline=0, bool bSetAlpha=false); + int32_t DrawString(HDC hdc, int32_t x, int32_t y, const TCHAR* text, RGBQUAD color, const TCHAR* font, int32_t lSize=0, int32_t lWeight=400, uint8_t bItalic=0, uint8_t bUnderline=0, bool bSetAlpha=false); + // extensions + int32_t DrawStringEx(HDC hdc, int32_t x, int32_t y, CXTEXTINFO *pTextType, bool bSetAlpha=false ); + void InitTextInfo( CXTEXTINFO *txt ); +protected: + bool IsHBITMAPAlphaValid( HBITMAP hbmp ); +public: +#endif //CXIMAGE_SUPPORT_WINDOWS +//@} + + // file operations +#if CXIMAGE_SUPPORT_DECODE +/** \addtogroup Decode */ //@{ +#ifdef WIN32 + //bool Load(LPCWSTR filename, uint32_t imagetype=0); + bool LoadResource(HRSRC hRes, uint32_t imagetype, HMODULE hModule=NULL); +#endif + // For UNICODE support: char -> TCHAR + bool Load(const TCHAR* filename, uint32_t imagetype=0); + //bool Load(const char * filename, uint32_t imagetype=0); + bool Decode(FILE * hFile, uint32_t imagetype); + bool Decode(CxFile * hFile, uint32_t imagetype); + bool Decode(uint8_t * buffer, uint32_t size, uint32_t imagetype); + + bool CheckFormat(CxFile * hFile, uint32_t imagetype = 0); + bool CheckFormat(uint8_t * buffer, uint32_t size, uint32_t imagetype = 0); +//@} +#endif //CXIMAGE_SUPPORT_DECODE + +#if CXIMAGE_SUPPORT_ENCODE +protected: +/** \addtogroup Protected */ //@{ + bool EncodeSafeCheck(CxFile *hFile); +//@} + +public: +/** \addtogroup Encode */ //@{ +#ifdef WIN32 + //bool Save(LPCWSTR filename, uint32_t imagetype=0); +#endif + // For UNICODE support: char -> TCHAR + bool Save(const TCHAR* filename, uint32_t imagetype); + //bool Save(const char * filename, uint32_t imagetype=0); + bool Encode(FILE * hFile, uint32_t imagetype); + bool Encode(CxFile * hFile, uint32_t imagetype); + bool Encode(CxFile * hFile, CxImage ** pImages, int32_t pagecount, uint32_t imagetype); + bool Encode(FILE *hFile, CxImage ** pImages, int32_t pagecount, uint32_t imagetype); + bool Encode(uint8_t * &buffer, int32_t &size, uint32_t imagetype); + + bool Encode2RGBA(CxFile *hFile, bool bFlipY = false); + bool Encode2RGBA(uint8_t * &buffer, int32_t &size, bool bFlipY = false); +//@} +#endif //CXIMAGE_SUPPORT_ENCODE + +/** \addtogroup Attributes */ //@{ + //misc. + bool IsValid() const; + bool IsEnabled() const; + void Enable(bool enable=true); + + // frame operations + int32_t GetNumFrames() const; + int32_t GetFrame() const; + void SetFrame(int32_t nFrame); +//@} + +#if CXIMAGE_SUPPORT_BASICTRANSFORMATIONS +/** \addtogroup BasicTransformations */ //@{ + bool GrayScale(); + bool Flip(bool bFlipSelection = false, bool bFlipAlpha = true); + bool Mirror(bool bMirrorSelection = false, bool bMirrorAlpha = true); + bool Negative(); + bool RotateLeft(CxImage* iDst = NULL); + bool RotateRight(CxImage* iDst = NULL); + bool IncreaseBpp(uint32_t nbit); +//@} +#endif //CXIMAGE_SUPPORT_BASICTRANSFORMATIONS + +#if CXIMAGE_SUPPORT_TRANSFORMATION +/** \addtogroup Transformations */ //@{ + // image operations + bool Rotate(float angle, CxImage* iDst = NULL); + bool Rotate2(float angle, CxImage *iDst = NULL, InterpolationMethod inMethod=IM_BILINEAR, + OverflowMethod ofMethod=OM_BACKGROUND, RGBQUAD *replColor=0, + bool const optimizeRightAngles=true, bool const bKeepOriginalSize=false); + bool Rotate180(CxImage* iDst = NULL); + bool Resample(int32_t newx, int32_t newy, int32_t mode = 1, CxImage* iDst = NULL); + bool Resample2(int32_t newx, int32_t newy, InterpolationMethod const inMethod=IM_BICUBIC2, + OverflowMethod const ofMethod=OM_REPEAT, CxImage* const iDst = NULL, + bool const disableAveraging=false); + bool DecreaseBpp(uint32_t nbit, bool errordiffusion, RGBQUAD* ppal = 0, uint32_t clrimportant = 0); + bool Dither(int32_t method = 0); + bool Crop(int32_t left, int32_t top, int32_t right, int32_t bottom, CxImage* iDst = NULL); + bool Crop(const RECT& rect, CxImage* iDst = NULL); + bool CropRotatedRectangle( int32_t topx, int32_t topy, int32_t width, int32_t height, float angle, CxImage* iDst = NULL); + bool Skew(float xgain, float ygain, int32_t xpivot=0, int32_t ypivot=0, bool bEnableInterpolation = false); + bool Expand(int32_t left, int32_t top, int32_t right, int32_t bottom, RGBQUAD canvascolor, CxImage* iDst = 0); + bool Expand(int32_t newx, int32_t newy, RGBQUAD canvascolor, CxImage* iDst = 0); + bool Thumbnail(int32_t newx, int32_t newy, RGBQUAD canvascolor, CxImage* iDst = 0); + bool CircleTransform(int32_t type,int32_t rmax=0,float Koeff=1.0f); + bool QIShrink(int32_t newx, int32_t newy, CxImage* const iDst = NULL, bool bChangeBpp = false); + +//@} +#endif //CXIMAGE_SUPPORT_TRANSFORMATION + +#if CXIMAGE_SUPPORT_DSP +/** \addtogroup DSP */ //@{ + bool Contour(); + bool HistogramStretch(int32_t method = 0, double threshold = 0); + bool HistogramEqualize(); + bool HistogramNormalize(); + bool HistogramRoot(); + bool HistogramLog(); + int32_t Histogram(int32_t* red, int32_t* green = 0, int32_t* blue = 0, int32_t* gray = 0, int32_t colorspace = 0); + bool Jitter(int32_t radius=2); + bool Repair(float radius = 0.25f, int32_t niterations = 1, int32_t colorspace = 0); + bool Combine(CxImage* r,CxImage* g,CxImage* b,CxImage* a, int32_t colorspace = 0); + bool FFT2(CxImage* srcReal, CxImage* srcImag, CxImage* dstReal, CxImage* dstImag, int32_t direction = 1, bool bForceFFT = true, bool bMagnitude = true); + bool Noise(int32_t level); + bool Median(int32_t Ksize=3); + bool Gamma(float gamma); + bool GammaRGB(float gammaR, float gammaG, float gammaB); + bool ShiftRGB(int32_t r, int32_t g, int32_t b); + bool Threshold(uint8_t level); + bool Threshold(CxImage* pThresholdMask); + bool Threshold2(uint8_t level, bool bDirection, RGBQUAD nBkgndColor, bool bSetAlpha = false); + bool Colorize(uint8_t hue, uint8_t sat, float blend = 1.0f); + bool Light(int32_t brightness, int32_t contrast = 0); + float Mean(); + bool Filter(int32_t* kernel, int32_t Ksize, int32_t Kfactor, int32_t Koffset); + bool Erode(int32_t Ksize=2); + bool Dilate(int32_t Ksize=2); + bool Edge(int32_t Ksize=2); + void HuePalette(float correction=1); + enum ImageOpType { OpAdd, OpAnd, OpXor, OpOr, OpMask, OpSrcCopy, OpDstCopy, OpSub, OpSrcBlend, OpScreen, OpAvg, OpBlendAlpha }; + void Mix(CxImage & imgsrc2, ImageOpType op, int32_t lXOffset = 0, int32_t lYOffset = 0, bool bMixAlpha = false); + void MixFrom(CxImage & imagesrc2, int32_t lXOffset, int32_t lYOffset); + bool UnsharpMask(float radius = 5.0f, float amount = 0.5f, int32_t threshold = 0); + bool Lut(uint8_t* pLut); + bool Lut(uint8_t* pLutR, uint8_t* pLutG, uint8_t* pLutB, uint8_t* pLutA = 0); + bool GaussianBlur(float radius = 1.0f, CxImage* iDst = 0); + bool TextBlur(uint8_t threshold = 100, uint8_t decay = 2, uint8_t max_depth = 5, bool bBlurHorizontal = true, bool bBlurVertical = true, CxImage* iDst = 0); + bool SelectiveBlur(float radius = 1.0f, uint8_t threshold = 25, CxImage* iDst = 0); + bool Solarize(uint8_t level = 128, bool bLinkedChannels = true); + bool FloodFill(const int32_t xStart, const int32_t yStart, const RGBQUAD cFillColor, const uint8_t tolerance = 0, + uint8_t nOpacity = 255, const bool bSelectFilledArea = false, const uint8_t nSelectionLevel = 255); + bool Saturate(const int32_t saturation, const int32_t colorspace = 1); + bool ConvertColorSpace(const int32_t dstColorSpace, const int32_t srcColorSpace); + int32_t OptimalThreshold(int32_t method = 0, RECT * pBox = 0, CxImage* pContrastMask = 0); + bool AdaptiveThreshold(int32_t method = 0, int32_t nBoxSize = 64, CxImage* pContrastMask = 0, int32_t nBias = 0, float fGlobalLocalBalance = 0.5f); + bool RedEyeRemove(float strength = 0.8f); + bool Trace(RGBQUAD color_target, RGBQUAD color_trace); + +//@} + +protected: +/** \addtogroup Protected */ //@{ + bool IsPowerof2(int32_t x); + bool FFT(int32_t dir,int32_t m,double *x,double *y); + bool DFT(int32_t dir,int32_t m,double *x1,double *y1,double *x2,double *y2); + bool RepairChannel(CxImage *ch, float radius); + // + int32_t gen_convolve_matrix (float radius, float **cmatrix_p); + float* gen_lookup_table (float *cmatrix, int32_t cmatrix_length); + void blur_line (float *ctable, float *cmatrix, int32_t cmatrix_length, uint8_t* cur_col, uint8_t* dest_col, int32_t y, int32_t bytes); + void blur_text (uint8_t threshold, uint8_t decay, uint8_t max_depth, CxImage* iSrc, CxImage* iDst, uint8_t bytes); +//@} + +public: +/** \addtogroup ColorSpace */ //@{ + bool SplitRGB(CxImage* r,CxImage* g,CxImage* b); + bool SplitYUV(CxImage* y,CxImage* u,CxImage* v); + bool SplitHSL(CxImage* h,CxImage* s,CxImage* l); + bool SplitYIQ(CxImage* y,CxImage* i,CxImage* q); + bool SplitXYZ(CxImage* x,CxImage* y,CxImage* z); + bool SplitCMYK(CxImage* c,CxImage* m,CxImage* y,CxImage* k); + static RGBQUAD HSLtoRGB(COLORREF cHSLColor); + static RGBQUAD RGBtoHSL(RGBQUAD lRGBColor); + static RGBQUAD HSLtoRGB(RGBQUAD lHSLColor); + static RGBQUAD YUVtoRGB(RGBQUAD lYUVColor); + static RGBQUAD RGBtoYUV(RGBQUAD lRGBColor); + static RGBQUAD YIQtoRGB(RGBQUAD lYIQColor); + static RGBQUAD RGBtoYIQ(RGBQUAD lRGBColor); + static RGBQUAD XYZtoRGB(RGBQUAD lXYZColor); + static RGBQUAD RGBtoXYZ(RGBQUAD lRGBColor); +#endif //CXIMAGE_SUPPORT_DSP + static RGBQUAD RGBtoRGBQUAD(COLORREF cr); + static COLORREF RGBQUADtoRGB (RGBQUAD c); +//@} + +/** \addtogroup Selection */ //@{ + bool SelectionIsValid(); +#if CXIMAGE_SUPPORT_SELECTION + bool SelectionClear(uint8_t level = 0); + bool SelectionCreate(); + bool SelectionDelete(); + bool SelectionInvert(); + bool SelectionMirror(); + bool SelectionFlip(); + bool SelectionAddRect(RECT r, uint8_t level = 255); + bool SelectionAddEllipse(RECT r, uint8_t level = 255); + bool SelectionAddPolygon(POINT *points, int32_t npoints, uint8_t level = 255); + bool SelectionAddColor(RGBQUAD c, uint8_t level = 255); + bool SelectionAddPixel(int32_t x, int32_t y, uint8_t level = 255); + bool SelectionCopy(CxImage &from); + bool SelectionIsInside(int32_t x, int32_t y); + void SelectionGetBox(RECT& r); + bool SelectionToHRGN(HRGN& region); + bool SelectionSplit(CxImage *dest); + uint8_t SelectionGet(const int32_t x,const int32_t y); + bool SelectionSet(CxImage &from); + void SelectionRebuildBox(); + uint8_t* SelectionGetPointer(const int32_t x = 0,const int32_t y = 0); +//@} + +protected: +/** \addtogroup Protected */ //@{ + bool BlindSelectionIsInside(int32_t x, int32_t y); + uint8_t BlindSelectionGet(const int32_t x,const int32_t y); + void SelectionSet(const int32_t x,const int32_t y,const uint8_t level); + +public: + +#endif //CXIMAGE_SUPPORT_SELECTION +//@} + +#if CXIMAGE_SUPPORT_ALPHA +/** \addtogroup Alpha */ //@{ + void AlphaClear(); + bool AlphaCreate(); + void AlphaDelete(); + void AlphaInvert(); + bool AlphaMirror(); + bool AlphaFlip(); + bool AlphaCopy(CxImage &from); + bool AlphaSplit(CxImage *dest); + void AlphaStrip(); + void AlphaSet(uint8_t level); + bool AlphaSet(CxImage &from); + void AlphaSet(const int32_t x,const int32_t y,const uint8_t level); + uint8_t AlphaGet(const int32_t x,const int32_t y); + uint8_t AlphaGetMax() const; + void AlphaSetMax(uint8_t nAlphaMax); + bool AlphaIsValid(); + uint8_t* AlphaGetPointer(const int32_t x = 0,const int32_t y = 0); + bool AlphaFromTransparency(); + + void AlphaPaletteClear(); + void AlphaPaletteEnable(bool enable=true); + bool AlphaPaletteIsEnabled(); + bool AlphaPaletteIsValid(); + bool AlphaPaletteSplit(CxImage *dest); +//@} + +protected: +/** \addtogroup Protected */ //@{ + uint8_t BlindAlphaGet(const int32_t x,const int32_t y); +//@} +#endif //CXIMAGE_SUPPORT_ALPHA + +public: +#if CXIMAGE_SUPPORT_LAYERS +/** \addtogroup Layers */ //@{ + bool LayerCreate(int32_t position = -1); + bool LayerDelete(int32_t position = -1); + void LayerDeleteAll(); + CxImage* GetLayer(int32_t position); + CxImage* GetParent() const; + int32_t GetNumLayers() const; + int32_t LayerDrawAll(HDC hdc, int32_t x=0, int32_t y=0, int32_t cx = -1, int32_t cy = -1, RECT* pClipRect = 0, bool bSmooth = false); + int32_t LayerDrawAll(HDC hdc, const RECT& rect, RECT* pClipRect=NULL, bool bSmooth = false); +//@} +#endif //CXIMAGE_SUPPORT_LAYERS + +protected: +/** \addtogroup Protected */ //@{ + void Startup(uint32_t imagetype = 0); + void CopyInfo(const CxImage &src); + void Ghost(const CxImage *src); + void RGBtoBGR(uint8_t *buffer, int32_t length); + static float HueToRGB(float n1,float n2, float hue); + void Bitfield2RGB(uint8_t *src, uint32_t redmask, uint32_t greenmask, uint32_t bluemask, uint8_t bpp); + static int32_t CompareColors(const void *elem1, const void *elem2); + int16_t m_ntohs(const int16_t word); + int32_t m_ntohl(const int32_t dword); + void bihtoh(BITMAPINFOHEADER* bih); + + void* pDib; //contains the header, the palette, the pixels + BITMAPINFOHEADER head; //standard header + CXIMAGEINFO info; //extended information + uint8_t* pSelection; //selected region + uint8_t* pAlpha; //alpha channel + CxImage** ppLayers; //generic layers + CxImage** ppFrames; +//@} +}; + +//////////////////////////////////////////////////////////////////////////// +#endif // !defined(__CXIMAGE_H) diff --git a/DuiLib/3rd/CxImage/ximagif.cpp b/DuiLib/3rd/CxImage/ximagif.cpp new file mode 100644 index 0000000..74d2592 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximagif.cpp @@ -0,0 +1,1683 @@ +/* + * File: ximagif.cpp + * Purpose: Platform Independent GIF Image Class Loader and Writer + * 07/Aug/2001 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximagif.h" + +#if CXIMAGE_SUPPORT_GIF + +#include "ximaiter.h" + +#if defined (_WIN32_WCE) + #define assert(s) +#else + #include +#endif + +//////////////////////////////////////////////////////////////////////////////// +CxImageGIF::CxImageGIF(): CxImage(CXIMAGE_FORMAT_GIF) +{ + buf = new uint8_t [GIFBUFTAM + 1]; + + stack = new uint8_t [MAX_CODES + 1]; + suffix = new uint8_t [MAX_CODES + 1]; + prefix = new uint16_t [MAX_CODES + 1]; + + htab = new int32_t [HSIZE]; + codetab = new uint16_t [HSIZE]; + + byte_buff = new uint8_t [257]; + accum = new char [256]; + m_comment = new char [256]; + + m_loops=0; + info.dispmeth=0; + m_comment[0]='\0'; + +} +//////////////////////////////////////////////////////////////////////////////// +CxImageGIF::~CxImageGIF() +{ + delete [] buf; + + delete [] stack; + delete [] suffix; + delete [] prefix; + + delete [] htab; + delete [] codetab; + + delete [] byte_buff; + delete [] accum; + delete [] m_comment; +} +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageGIF::Decode(CxFile *fp) +{ + /* AD - for transparency */ + struct_dscgif dscgif; + struct_image image; + struct_TabCol TabCol; + + if (fp == NULL) return false; + + fp->Read(&dscgif,/*sizeof(dscgif)*/13,1); + //if (strncmp(dscgif.header,"GIF8",3)!=0) { + if (strncmp(dscgif.header,"GIF8",4)!=0) return FALSE; + + // Avoid Byte order problem with Mac + dscgif.scrheight = m_ntohs(dscgif.scrheight); + dscgif.scrwidth = m_ntohs(dscgif.scrwidth); + + if (info.nEscape == -1) { + // Return output dimensions only + head.biWidth = dscgif.scrwidth; + head.biHeight = dscgif.scrheight; + info.dwType = CXIMAGE_FORMAT_GIF; + return true; + } + + /* AD - for interlace */ + TabCol.sogct = (int16_t)(1 << ((dscgif.pflds & 0x07)+1)); + TabCol.colres = (int16_t)(((dscgif.pflds & 0x70) >> 4) + 1); + + // assume that the image is a truecolor-gif if + // 1) no global color map found + // 2) (image.w, image.h) of the 1st image != (dscgif.scrwidth, dscgif.scrheight) + int32_t bTrueColor=0; + CxImage* imaRGB=NULL; + + // Global colour map? + if (dscgif.pflds & 0x80) + fp->Read(TabCol.paleta,sizeof(struct rgb_color)*TabCol.sogct,1); + else + bTrueColor++; //first chance for a truecolor gif + + int32_t first_transparent_index = 0; + + int32_t iImage = 0; + info.nNumFrames=get_num_frames(fp,&TabCol,&dscgif); + + if ((info.nFrame<0)||(info.nFrame>=info.nNumFrames)) return false; + + //it cannot be a true color GIF with only one frame + if (info.nNumFrames == 1) + bTrueColor=0; + + char ch; + bool bPreviousWasNull = true; + int32_t prevdispmeth = 0; + CxImage *previousFrame = NULL; + + for (BOOL bContinue = TRUE; bContinue; ) + { + if (fp->Read(&ch, sizeof(ch), 1) != 1) {break;} + + if (info.nEscape > 0) return false; // - cancel decoding + if (bPreviousWasNull || ch==0) + { + switch (ch) + { + case '!': // extension + { + bContinue = DecodeExtension(fp); + break; + } + case ',': // image + { + assert(sizeof(image) == 9); + fp->Read(&image,sizeof(image),1); + //avoid byte order problems with Solaris + image.l = m_ntohs(image.l); + image.t = m_ntohs(image.t); + image.w = m_ntohs(image.w); + image.h = m_ntohs(image.h); + + if (((image.l + image.w) > dscgif.scrwidth)||((image.t + image.h) > dscgif.scrheight)) + break; + + // check if it could be a truecolor gif + if ((iImage==0) && (image.w != dscgif.scrwidth) && (image.h != dscgif.scrheight)) + bTrueColor++; + + rgb_color locpal[256]; //Local Palette + rgb_color* pcurpal = TabCol.paleta; //Current Palette + int16_t palcount = TabCol.sogct; //Current Palette color count + + // Local colour map? + if (image.pf & 0x80) { + palcount = (int16_t)(1 << ((image.pf & 0x07) +1)); + assert(3 == sizeof(struct rgb_color)); + fp->Read(locpal,sizeof(struct rgb_color)*palcount,1); + pcurpal = locpal; + } + + int32_t bpp; // select the correct bit per pixel value + if (palcount <= 2) bpp = 1; + else if (palcount <= 16) bpp = 4; + else bpp = 8; + + CxImageGIF backimage; + backimage.CopyInfo(*this); + if (iImage==0){ + //first frame: build image background + backimage.Create(dscgif.scrwidth, dscgif.scrheight, bpp, CXIMAGE_FORMAT_GIF); + first_transparent_index = info.nBkgndIndex; + backimage.Clear((uint8_t)gifgce.transpcolindex); + previousFrame = new CxImage(backimage); + previousFrame->SetRetreiveAllFrames(false); + } else { + //generic frame: handle disposal method from previous one + /*Values : 0 - No disposal specified. The decoder is + not required to take any action. + 1 - Do not dispose. The graphic is to be left + in place. + 2 - Restore to background color. The area used by the + graphic must be restored to the background color. + 3 - Restore to previous. The decoder is required to + restore the area overwritten by the graphic with + what was there prior to rendering the graphic. + */ + /* backimage.Copy(*this); + if (prevdispmeth==2){ + backimage.Clear((uint8_t)first_transparent_index); + }*/ + if (prevdispmeth==2){ + backimage.Copy(*this,false,false,false); + backimage.Clear((uint8_t)first_transparent_index); + } else if (prevdispmeth==3) { + backimage.Copy(*this,false,false,false); + backimage.Create(previousFrame->GetWidth(), + previousFrame->GetHeight(), + previousFrame->GetBpp(),CXIMAGE_FORMAT_GIF); + memcpy(backimage.GetDIB(),previousFrame->GetDIB(), + backimage.GetSize()); + //backimage.AlphaSet(*previousFrame); + } else { + backimage.Copy(*this); + } + } + + //active frame + Create(image.w, image.h, bpp, CXIMAGE_FORMAT_GIF); + + if ((image.pf & 0x80) || (dscgif.pflds & 0x80)) { + uint8_t r[256], g[256], b[256]; + int32_t i, has_white = 0; + + for (i=0; i < palcount; i++) { + r[i] = pcurpal[i].r; + g[i] = pcurpal[i].g; + b[i] = pcurpal[i].b; + if (RGB(r[i],g[i],b[i]) == 0xFFFFFF) has_white = 1; + } + + // Force transparency colour white... + //if (0) if (info.nBkgndIndex >= 0) + // r[info.nBkgndIndex] = g[info.nBkgndIndex] = b[info.nBkgndIndex] = 255; + // Fill in with white // AD + if (info.nBkgndIndex >= 0) { + while (i < 256) { + has_white = 1; + r[i] = g[i] = b[i] = 255; + i++; + } + } + + // Force last colour to white... // AD + //if ((info.nBkgndIndex >= 0) && !has_white) { + // r[255] = g[255] = b[255] = 255; + //} + + SetPalette((info.nBkgndIndex >= 0 ? 256 : palcount), r, g, b); + } + + CImageIterator* iter = new CImageIterator(this); + iter->Upset(); + int32_t badcode=0; + ibf = GIFBUFTAM+1; + + interlaced = image.pf & 0x40; + iheight = image.h; + istep = 8; + iypos = 0; + ipass = 0; + + int32_t pos_start = fp->Tell(); + //if (interlaced) log << "Interlaced" << endl; + decoder(fp, iter, image.w, badcode); + delete iter; + + if (info.nEscape) return false; // - cancel decoding + + if (bTrueColor<2 ){ //standard GIF: mix frame with background + backimage.IncreaseBpp(bpp); + backimage.GifMix(*this,image); + backimage.SetTransIndex(first_transparent_index); + backimage.SetPalette(GetPalette()); + Transfer(backimage,false); + } else { //it's a truecolor gif! + //force full image decoding + info.nFrame=info.nNumFrames-1; + //build the RGB image + if (imaRGB==NULL) imaRGB = new CxImage(dscgif.scrwidth,dscgif.scrheight,24,CXIMAGE_FORMAT_GIF); + //copy the partial image into the full RGB image + for(int32_t y=0;ySetPixelColor(x+image.l,dscgif.scrheight-1-image.t-y,GetPixelColor(x,image.h-y-1)); + } + } + } + + prevdispmeth = (gifgce.flags >> 2) & 0x7; + + //restore the correct position in the file for the next image + if (badcode){ + seek_next_image(fp,pos_start); + } else { + fp->Seek(-(ibfmax - ibf - 1), SEEK_CUR); + } + + if (info.bGetAllFrames && imaRGB == NULL) { + if (iImage == 0) { + DestroyFrames(); + ppFrames = new CxImage*[info.nNumFrames]; + for(int32_t frameIdx = 0; frameIdx < info.nNumFrames; frameIdx++){ + ppFrames[frameIdx] = NULL; + } + } + ppFrames[iImage] = new CxImage(*this); + ppFrames[iImage]->SetRetreiveAllFrames(false); + } + if (prevdispmeth <= 1) { + delete previousFrame; + previousFrame = new CxImage(*this); + previousFrame->SetRetreiveAllFrames(false); + } + + if ((info.nFrame==iImage) && (info.bGetAllFrames==false)) bContinue=false; else iImage++; + + break; + } + case ';': //terminator + bContinue=false; + break; + default: + bPreviousWasNull = (ch==0); + break; + } + } + } + + if (bTrueColor>=2 && imaRGB){ + if (gifgce.flags & 0x1){ + imaRGB->SetTransColor(GetPaletteColor((uint8_t)info.nBkgndIndex)); + imaRGB->SetTransIndex(0); + } + Transfer(*imaRGB); + } + delete imaRGB; + + delete previousFrame; + + return true; + +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImageGIF::DecodeExtension(CxFile *fp) +{ + bool bContinue; + uint8_t count; + uint8_t fc; + + bContinue = (1 == fp->Read(&fc, sizeof(fc), 1)); + if (bContinue) { + /* AD - for transparency */ + if (fc == 0xF9) { + bContinue = (1 == fp->Read(&count, sizeof(count), 1)); + if (bContinue) { + assert(sizeof(gifgce) == 4); + bContinue = (count == fp->Read(&gifgce, 1, sizeof(gifgce))); + gifgce.delaytime = m_ntohs(gifgce.delaytime); // Avoid Byte order problem with Mac + if (bContinue) { + info.nBkgndIndex = (gifgce.flags & 0x1) ? gifgce.transpcolindex : -1; + info.dwFrameDelay = gifgce.delaytime; + SetDisposalMethod((gifgce.flags >> 2) & 0x7); + } } } + + if (fc == 0xFE) { // Comment block + bContinue = (1 == fp->Read(&count, sizeof(count), 1)); + if (bContinue) { + bContinue = (1 == fp->Read(m_comment, count, 1)); + m_comment[count]='\0'; + } } + + if (fc == 0xFF) { // Application Extension block + bContinue = (1 == fp->Read(&count, sizeof(count), 1)); + if (bContinue) { + bContinue = (count==11); + if (bContinue){ + char AppID[11]; + bContinue = (1 == fp->Read(AppID, count, 1)); + if (bContinue) { + bContinue = (1 == fp->Read(&count, sizeof(count), 1)); + if (bContinue) { + uint8_t* dati = (uint8_t*)malloc(count); + bContinue = (dati!=NULL); + if (bContinue){ + bContinue = (1 == fp->Read(dati, count, 1)); + if (count>2){ + m_loops = dati[1]+256*dati[2]; + } + } + free(dati); + } } } } } + + while (bContinue && fp->Read(&count, sizeof(count), 1) && count) { + //log << "Skipping " << count << " bytes" << endl; + fp->Seek(count, SEEK_CUR); + } + } + return bContinue; + +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// + +// - This external (machine specific) function is expected to return +// either the next uint8_t from the GIF file, or a negative error number. +int32_t CxImageGIF::get_byte(CxFile* file) +{ + if (ibf>=GIFBUFTAM){ + // FW 06/02/98 >>> + ibfmax = (int32_t)file->Read( buf , 1 , GIFBUFTAM) ; + if( ibfmax < GIFBUFTAM ) buf[ ibfmax ] = 255 ; + // FW 06/02/98 <<< + ibf = 0; + } + if (ibf>=ibfmax) return -1; // avoid overflows + return buf[ibf++]; +} +//////////////////////////////////////////////////////////////////////////////// +/* - This function takes a full line of pixels (one uint8_t per pixel) and + * displays them (or does whatever your program wants with them...). It + * should return zero, or negative if an error or some other event occurs + * which would require aborting the decode process... Note that the length + * passed will almost always be equal to the line length passed to the + * decoder function, with the sole exception occurring when an ending code + * occurs in an odd place in the GIF file... In any case, linelen will be + * equal to the number of pixels passed... +*/ +int32_t CxImageGIF::out_line(CImageIterator* iter, uint8_t *pixels, int32_t linelen) +{ + if (iter == NULL || pixels == NULL) + return -1; + + // for 1 & 4 bpp images, the pixels are compressed + if (head.biBitCount < 8){ + for(int32_t x=0;x> 3); + if (head.biBitCount==4){ + pos = (uint8_t)(4*(1-x%2)); + *iDst &= ~(0x0F<SetY(iheight-iypos-1); + iter->SetRow(pixels, linelen); + + if ((iypos += istep) >= iheight) { + do { + if (ipass++ > 0) istep /= 2; + iypos = istep / 2; + } + while (iypos > iheight); + } + return 0; + } else { + if (iter->ItOK()) { + iter->SetRow(pixels, linelen); + (void)iter->PrevRow(); + return 0; + } else { + // puts("chafeo"); + return -1; + } + } +} +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +// SaveFile - writes GIF87a gif file +// Randy Spann 6/15/97 +// R.Spann@ConnRiver.net +bool CxImageGIF::Encode(CxFile * fp) +{ + if (EncodeSafeCheck(fp)) return false; + + if(head.biBitCount > 8) { + //strcpy(info.szLastError,"GIF Images must be 8 bit or less"); + //return FALSE; + return EncodeRGB(fp); + } + + if ( GetNumFrames()>1 && ppFrames ) { + return Encode(fp, ppFrames, GetNumFrames() ); + } + + EncodeHeader(fp); + + EncodeExtension(fp); + + EncodeComment(fp); + + EncodeBody(fp); + + fp->PutC(';'); // Write the GIF file terminator + + return true; // done! +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImageGIF::Encode(CxFile * fp, CxImage ** pImages, int32_t pagecount, bool bLocalColorMap, bool bLocalDispMeth) +{ + cx_try { + if (fp==NULL) cx_throw("invalid file pointer"); + if (pImages==NULL || pagecount<=0 || pImages[0]==NULL) cx_throw("multipage GIF, no images!"); + + int32_t i; + for (i=0; iIsValid())) + cx_throw("Empty image"); + if (pImages[i]->GetNumColors()==0) + cx_throw("CxImageGIF::Encode cannot create animated GIFs with a true color frame. Use DecreaseBpp before"); + } + + CxImageGIF ghost; + + //write the first image + ghost.Ghost(pImages[0]); + ghost.EncodeHeader(fp); + + if (m_loops!=1){ + ghost.SetLoops(max(0,m_loops-1)); + ghost.EncodeLoopExtension(fp); + } + + if (bLocalDispMeth) { + ghost.EncodeExtension(fp); + } else { + uint8_t dm = ghost.GetDisposalMethod(); + ghost.SetDisposalMethod(GetDisposalMethod()); + ghost.EncodeExtension(fp); + ghost.SetDisposalMethod(dm); + } + + EncodeComment(fp); + + ghost.EncodeBody(fp); + + for (i=1; iPutC(';'); // Write the GIF file terminator + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + return false; + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::EncodeHeader(CxFile *fp) +{ + fp->Write("GIF89a",1,6); //GIF Header + + Putword(head.biWidth,fp); //Logical screen descriptor + Putword(head.biHeight,fp); + + uint8_t Flags; + if (head.biClrUsed==0){ + Flags=0x11; + } else { + Flags = 0x80; + Flags |=(head.biBitCount - 1) << 5; + Flags |=(head.biBitCount - 1); + } + + fp->PutC(Flags); //GIF "packed fields" + fp->PutC(0); //GIF "BackGround" + fp->PutC(0); //GIF "pixel aspect ratio" + + if (head.biClrUsed!=0){ + RGBQUAD* pPal = GetPalette(); + for(uint32_t i=0; iPutC(pPal[i].rgbRed); + fp->PutC(pPal[i].rgbGreen); + fp->PutC(pPal[i].rgbBlue); + } + } +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::EncodeExtension(CxFile *fp) +{ + // TRK BEGIN : transparency + fp->PutC('!'); + fp->PutC(TRANSPARENCY_CODE); + + gifgce.flags = 0; + gifgce.flags |= ((info.nBkgndIndex != -1) ? 1 : 0); + gifgce.flags |= ((GetDisposalMethod() & 0x7) << 2); + gifgce.delaytime = (uint16_t)info.dwFrameDelay; + gifgce.transpcolindex = (uint8_t)info.nBkgndIndex; + + //Invert byte order in case we use a byte order arch, then set it back + gifgce.delaytime = m_ntohs(gifgce.delaytime); + fp->PutC(sizeof(gifgce)); + fp->Write(&gifgce, sizeof(gifgce), 1); + gifgce.delaytime = m_ntohs(gifgce.delaytime); + + fp->PutC(0); + // TRK END +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::EncodeLoopExtension(CxFile *fp) +{ + fp->PutC('!'); //byte 1 : 33 (hex 0x21) GIF Extension code + fp->PutC(255); //byte 2 : 255 (hex 0xFF) Application Extension Label + fp->PutC(11); //byte 3 : 11 (hex (0x0B) Length of Application Block (eleven bytes of data to follow) + fp->Write("NETSCAPE2.0",11,1); + fp->PutC(3); //byte 15 : 3 (hex 0x03) Length of Data Sub-Block (three bytes of data to follow) + fp->PutC(1); //byte 16 : 1 (hex 0x01) + Putword(m_loops,fp); //bytes 17 to 18 : 0 to 65535, an unsigned integer in lo-hi byte format. + //This indicate the number of iterations the loop should be executed. + fp->PutC(0); //bytes 19 : 0 (hex 0x00) a Data Sub-block Terminator. +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::EncodeBody(CxFile *fp, bool bLocalColorMap) +{ + curx = 0; + cury = head.biHeight - 1; //because we read the image bottom to top + CountDown = (int32_t)head.biWidth * (int32_t)head.biHeight; + + fp->PutC(','); + + Putword(info.xOffset,fp); + Putword(info.yOffset,fp); + Putword(head.biWidth,fp); + Putword(head.biHeight,fp); + + uint8_t Flags=0x00; //non-interlaced (0x40 = interlaced) (0x80 = LocalColorMap) + if (bLocalColorMap) { Flags|=0x80; Flags|=head.biBitCount-1; } + fp->PutC(Flags); + + if (bLocalColorMap){ + Flags|=0x87; + RGBQUAD* pPal = GetPalette(); + for(uint32_t i=0; iPutC(pPal[i].rgbRed); + fp->PutC(pPal[i].rgbGreen); + fp->PutC(pPal[i].rgbBlue); + } + } + + int32_t InitCodeSize = head.biBitCount <=1 ? 2 : head.biBitCount; + // Write out the initial code size + fp->PutC((uint8_t)InitCodeSize); + + // Go and actually compress the data + switch (GetCodecOption(CXIMAGE_FORMAT_GIF)) + { + case 1: //uncompressed + compressNONE(InitCodeSize+1, fp); + break; + case 2: //RLE + compressRLE(InitCodeSize+1, fp); + break; + default: //LZW + compressLZW(InitCodeSize+1, fp); + } + + // Write out a Zero-length packet (to end the series) + fp->PutC(0); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::EncodeComment(CxFile *fp) +{ + uint32_t n = (uint32_t) strlen(m_comment); + if (n>255) n=255; + if (n) { + fp->PutC('!'); //extension code: + fp->PutC(254); //comment extension + fp->PutC((uint8_t)n); //size of comment + fp->Write(m_comment,n,1); + fp->PutC(0); //block terminator + } +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImageGIF::EncodeRGB(CxFile *fp) +{ + EncodeHeader(fp); + +// EncodeLoopExtension(fp); + + EncodeComment(fp); + + uint32_t w,h; + w=h=0; + const int32_t cellw = 17; + const int32_t cellh = 15; + CxImageGIF tmp; + for (int32_t y=0;yPutC(';'); // Write the GIF file terminator + + return true; // done! +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +// Return the next pixel from the image +// fix for 1 & 4 bpp images +int32_t CxImageGIF::GifNextPixel( ) +{ + if( CountDown == 0 ) return EOF; + --CountDown; + int32_t r = GetPixelIndex(curx,cury); + // Bump the current X position + ++curx; + if( curx == head.biWidth ){ + curx = 0; + cury--; //bottom to top + } + return r; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::Putword(int32_t w, CxFile *fp ) +{ + fp->PutC((uint8_t)(w & 0xff)); + fp->PutC((uint8_t)((w >> 8) & 0xff)); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::compressNONE( int32_t init_bits, CxFile* outfile) +{ + register int32_t c; + register int32_t ent; + + // g_init_bits - initial number of bits + // g_outfile - pointer to output file + g_init_bits = init_bits; + g_outfile = outfile; + + // Set up the necessary values + cur_accum = cur_bits = clear_flg = 0; + maxcode = (int16_t)MAXCODE(n_bits = g_init_bits); + code_int maxmaxcode = (code_int)1 << MAXBITSCODES; + + ClearCode = (1 << (init_bits - 1)); + EOFCode = ClearCode + 1; + free_ent = (int16_t)(ClearCode + 2); + + a_count=0; + ent = GifNextPixel( ); + + output( (code_int)ClearCode ); + + while ( ent != EOF ) { + c = GifNextPixel(); + + output ( (code_int) ent ); + ent = c; + if ( free_ent < maxmaxcode ) { + free_ent++; + } else { + free_ent=(int16_t)(ClearCode+2); + clear_flg=1; + output((code_int)ClearCode); + } + } + // Put out the final code. + output( (code_int) EOFCode ); +} +//////////////////////////////////////////////////////////////////////////////// + +/*************************************************************************** + * + * GIFCOMPR.C - LZW GIF Image compression routines + * + ***************************************************************************/ + +void CxImageGIF::compressLZW( int32_t init_bits, CxFile* outfile) +{ + register int32_t fcode; + register int32_t c; + register int32_t ent; + register int32_t hshift; + register int32_t disp; + register int32_t i; + + // g_init_bits - initial number of bits + // g_outfile - pointer to output file + g_init_bits = init_bits; + g_outfile = outfile; + + // Set up the necessary values + cur_accum = cur_bits = clear_flg = 0; + maxcode = (int16_t)MAXCODE(n_bits = g_init_bits); + code_int maxmaxcode = (code_int)1 << MAXBITSCODES; + + ClearCode = (1 << (init_bits - 1)); + EOFCode = ClearCode + 1; + free_ent = (int16_t)(ClearCode + 2); + + a_count=0; + ent = GifNextPixel( ); + + hshift = 0; + for ( fcode = (int32_t) HSIZE; fcode < 65536L; fcode *= 2L ) ++hshift; + hshift = 8 - hshift; /* set hash code range bound */ + cl_hash((int32_t)HSIZE); /* clear hash table */ + output( (code_int)ClearCode ); + + while ( (c = GifNextPixel( )) != EOF ) { + + fcode = (int32_t) (((int32_t) c << MAXBITSCODES) + ent); + i = (((code_int)c << hshift) ^ ent); /* xor hashing */ + + if ( HashTabOf (i) == fcode ) { + ent = CodeTabOf (i); + continue; + } else if ( (int32_t)HashTabOf (i) < 0 ) /* empty slot */ + goto nomatch; + disp = HSIZE - i; /* secondary hash (after G. Knott) */ + if ( i == 0 ) disp = 1; +probe: + if ( (i -= disp) < 0 ) i += HSIZE; + if ( HashTabOf (i) == fcode ) { ent = CodeTabOf (i); continue; } + if ( (int32_t)HashTabOf (i) > 0 ) goto probe; +nomatch: + output ( (code_int) ent ); + ent = c; + if ( free_ent < maxmaxcode ) { + CodeTabOf (i) = free_ent++; /* code -> hashtable */ + HashTabOf (i) = fcode; + } else { + cl_hash((int32_t)HSIZE); + free_ent=(int16_t)(ClearCode+2); + clear_flg=1; + output((code_int)ClearCode); + } + } + // Put out the final code. + output( (code_int)ent ); + output( (code_int) EOFCode ); +} +//////////////////////////////////////////////////////////////////////////////// + +static const uint32_t code_mask[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, + 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, + 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::output( code_int code) +{ + cur_accum &= code_mask[ cur_bits ]; + + if( cur_bits > 0 ) + cur_accum |= ((int32_t)code << cur_bits); + else + cur_accum = code; + + cur_bits += n_bits; + + while( cur_bits >= 8 ) { + char_out( (uint32_t)(cur_accum & 0xff) ); + cur_accum >>= 8; + cur_bits -= 8; + } + + /* + * If the next entry is going to be too big for the code size, + * then increase it, if possible. + */ + + if ( free_ent > maxcode || clear_flg ) { + if( clear_flg ) { + maxcode = (int16_t)MAXCODE(n_bits = g_init_bits); + clear_flg = 0; + } else { + ++n_bits; + if ( n_bits == MAXBITSCODES ) + maxcode = (code_int)1 << MAXBITSCODES; /* should NEVER generate this code */ + else + maxcode = (int16_t)MAXCODE(n_bits); + } + } + + if( code == EOFCode ) { + // At EOF, write the rest of the buffer. + while( cur_bits > 0 ) { + char_out( (uint32_t)(cur_accum & 0xff) ); + cur_accum >>= 8; + cur_bits -= 8; + } + + flush_char(); + g_outfile->Flush(); + + if(g_outfile->Error()) strcpy(info.szLastError,"Write Error in GIF file"); + } +} +//////////////////////////////////////////////////////////////////////////////// + +void CxImageGIF::cl_hash(int32_t hsize) + +{ + register int32_t *htab_p = htab+hsize; + + register int32_t i; + register int32_t m1 = -1L; + + i = hsize - 16; + + do { + *(htab_p-16)=m1; + *(htab_p-15)=m1; + *(htab_p-14)=m1; + *(htab_p-13)=m1; + *(htab_p-12)=m1; + *(htab_p-11)=m1; + *(htab_p-10)=m1; + *(htab_p-9)=m1; + *(htab_p-8)=m1; + *(htab_p-7)=m1; + *(htab_p-6)=m1; + *(htab_p-5)=m1; + *(htab_p-4)=m1; + *(htab_p-3)=m1; + *(htab_p-2)=m1; + *(htab_p-1)=m1; + + htab_p-=16; + } while ((i-=16) >=0); + + for (i+=16;i>0;--i) + *--htab_p=m1; +} + +/******************************************************************************* +* GIF specific +*******************************************************************************/ + +void CxImageGIF::char_out(int32_t c) +{ + accum[a_count++]=(char)c; + if (a_count >=254) + flush_char(); +} + +void CxImageGIF::flush_char() +{ + if (a_count > 0) { + g_outfile->PutC((uint8_t)a_count); + g_outfile->Write(accum,1,a_count); + a_count=0; + } +} + +/******************************************************************************* +* GIF decoder +*******************************************************************************/ +/* DECODE.C - An LZW decoder for GIF + * Copyright (C) 1987, by Steven A. Bennett + * Copyright (C) 1994, C++ version by Alejandro Aguilar Sierra +* + * Permission is given by the author to freely redistribute and include + * this code in any program as int32_t as this credit is given where due. + * + * In accordance with the above, I want to credit Steve Wilhite who wrote + * the code which this is heavily inspired by... + * + * GIF and 'Graphics Interchange Format' are trademarks (tm) of + * Compuserve, Incorporated, an H&R Block Company. + * + * Release Notes: This file contains a decoder routine for GIF images + * which is similar, structurally, to the original routine by Steve Wilhite. + * It is, however, somewhat noticably faster in most cases. + * + */ + +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// + +int16_t CxImageGIF::init_exp(int16_t size) +{ + curr_size = (int16_t)(size + 1); + top_slot = (int16_t)(1 << curr_size); + clear = (int16_t)(1 << size); + ending = (int16_t)(clear + 1); + slot = newcodes = (int16_t)(ending + 1); + navail_bytes = nbits_left = 0; + + memset(stack,0,MAX_CODES + 1); + memset(prefix,0,MAX_CODES + 1); + memset(suffix,0,MAX_CODES + 1); + return(0); +} +//////////////////////////////////////////////////////////////////////////////// + +/* get_next_code() + * - gets the next code from the GIF file. Returns the code, or else + * a negative number in case of file errors... + */ +int16_t CxImageGIF::get_next_code(CxFile* file) +{ + int16_t i, x; + uint32_t ret; + + if (nbits_left == 0) { + if (navail_bytes <= 0) { + /* Out of bytes in current block, so read next block */ + pbytes = byte_buff; + if ((navail_bytes = (int16_t)get_byte(file)) < 0) + return(navail_bytes); + else if (navail_bytes) { + for (i = 0; i < navail_bytes; ++i) { + if ((x = (int16_t)get_byte(file)) < 0) return(x); + byte_buff[i] = (uint8_t)x; + } + } + } + b1 = *pbytes++; + nbits_left = 8; + --navail_bytes; + } + + if (navail_bytes<0) return ending; // prevent deadlocks (thanks to Mike Melnikov) + + ret = b1 >> (8 - nbits_left); + while (curr_size > nbits_left){ + if (navail_bytes <= 0){ + /* Out of bytes in current block, so read next block*/ + pbytes = byte_buff; + if ((navail_bytes = (int16_t)get_byte(file)) < 0) + return(navail_bytes); + else if (navail_bytes){ + for (i = 0; i < navail_bytes; ++i){ + if ((x = (int16_t)get_byte(file)) < 0) return(x); + byte_buff[i] = (uint8_t)x; + } + } + } + b1 = *pbytes++; + ret |= b1 << nbits_left; + nbits_left += 8; + --navail_bytes; + } + nbits_left = (int16_t)(nbits_left-curr_size); + ret &= code_mask[curr_size]; + return((int16_t)(ret)); +} +//////////////////////////////////////////////////////////////////////////////// + +/* int16_t decoder(linewidth) + * int16_t linewidth; * Pixels per line of image * + * + * - This function decodes an LZW image, according to the method used + * in the GIF spec. Every *linewidth* "characters" (ie. pixels) decoded + * will generate a call to out_line(), which is a user specific function + * to display a line of pixels. The function gets it's codes from + * get_next_code() which is responsible for reading blocks of data and + * seperating them into the proper size codes. Finally, get_byte() is + * the global routine to read the next uint8_t from the GIF file. + * + * It is generally a good idea to have linewidth correspond to the actual + * width of a line (as specified in the Image header) to make your own + * code a bit simpler, but it isn't absolutely necessary. + * + * Returns: 0 if successful, else negative. (See ERRS.H) + * + */ +/* bad_code_count is incremented each time an out of range code is read. + * When this value is non-zero after a decode, your GIF file is probably + * corrupt in some way... + */ +int16_t CxImageGIF::decoder(CxFile* file, CImageIterator* iter, int16_t linewidth, int32_t &bad_code_count) +{ + register uint8_t *sp, *bufptr; + uint8_t *buf; + register int16_t code, fc, oc, bufcnt; + int16_t c, size, ret; + + if (linewidth<=0) + return BAD_LINE_WIDTH; + + /* Initialize for decoding a new image... */ + bad_code_count = 0; + if ((size = (int16_t)get_byte(file)) < 0) return(size); + if (size < 2 || 9 < size) return(BAD_CODE_SIZE); + // out_line = outline; + init_exp(size); + //printf("L %d %x\n",linewidth,size); + + /* Initialize in case they forgot to put in a clear code. + * (This shouldn't happen, but we'll try and decode it anyway...) + */ + oc = fc = 0; + + /* Allocate space for the decode buffer */ + if ((buf = new uint8_t[linewidth + 1]) == NULL) return(OUT_OF_MEMORY); + + /* Set up the stack pointer and decode buffer pointer */ + sp = stack; + bufptr = buf; + bufcnt = linewidth; + + /* This is the main loop. For each code we get we pass through the + * linked list of prefix codes, pushing the corresponding "character" for + * each code onto the stack. When the list reaches a single "character" + * we push that on the stack too, and then start unstacking each + * character for output in the correct order. Special handling is + * included for the clear code, and the whole thing ends when we get + * an ending code. + */ + while ((c = get_next_code(file)) != ending) { + /* If we had a file error, return without completing the decode*/ + if (c < 0){ + delete [] buf; + return(0); + } + /* If the code is a clear code, reinitialize all necessary items.*/ + if (c == clear){ + curr_size = (int16_t)(size + 1); + slot = newcodes; + top_slot = (int16_t)(1 << curr_size); + + /* Continue reading codes until we get a non-clear code + * (Another unlikely, but possible case...) + */ + while ((c = get_next_code(file)) == clear); + + /* If we get an ending code immediately after a clear code + * (Yet another unlikely case), then break out of the loop. + */ + if (c == ending) break; + + /* Finally, if the code is beyond the range of already set codes, + * (This one had better NOT happen... I have no idea what will + * result from this, but I doubt it will look good...) then set it + * to color zero. + */ + if (c >= slot) c = 0; + oc = fc = c; + + /* And let us not forget to put the char into the buffer... And + * if, on the off chance, we were exactly one pixel from the end + * of the line, we have to send the buffer to the out_line() + * routine... + */ + *bufptr++ = (uint8_t)c; + if (--bufcnt == 0) { + if (iter) { + if ((ret = (int16_t)out_line(iter, buf, linewidth)) < 0) { + delete [] buf; + return(ret); + } + } + bufptr = buf; + bufcnt = linewidth; + } + } else { + /* In this case, it's not a clear code or an ending code, so + * it must be a code code... So we can now decode the code into + * a stack of character codes. (Clear as mud, right?) + */ + code = c; + + /* Here we go again with one of those off chances... If, on the + * off chance, the code we got is beyond the range of those already + * set up (Another thing which had better NOT happen...) we trick + * the decoder into thinking it actually got the last code read. + * (Hmmn... I'm not sure why this works... But it does...) + */ + if (code >= slot && sp<(stack+MAX_CODES-1)) { + if (code > slot) + ++bad_code_count; + code = oc; + *sp++ = (uint8_t)fc; + } + + /* Here we scan back along the linked list of prefixes, pushing + * helpless characters (ie. suffixes) onto the stack as we do so. + */ + while (code >= newcodes && sp<(stack+MAX_CODES-1)) { + *sp++ = suffix[code]; + code = prefix[code]; + } + + /* Push the last character on the stack, and set up the new + * prefix and suffix, and if the required slot number is greater + * than that allowed by the current bit size, increase the bit + * size. (NOTE - If we are all full, we *don't* save the new + * suffix and prefix... I'm not certain if this is correct... + * it might be more proper to overwrite the last code... + */ + *sp++ = (uint8_t)code; + if (slot < top_slot){ + suffix[slot] = (uint8_t)(fc = (uint8_t)code); + prefix[slot++] = oc; + oc = c; + } + if (slot >= top_slot){ + if (curr_size < 12) { + top_slot <<= 1; + ++curr_size; + } + } + + /* Now that we've pushed the decoded string (in reverse order) + * onto the stack, lets pop it off and put it into our decode + * buffer... And when the decode buffer is full, write another + * line... + */ + while (sp > stack) { + *bufptr++ = *(--sp); + if (--bufcnt == 0) { + if (iter) { + if ((ret = (int16_t)out_line(iter, buf, linewidth)) < 0) { + delete [] buf; + return(ret); + } + } + bufptr = buf; + bufcnt = linewidth; + } + } + } + } + ret = 0; + if (bufcnt != linewidth && iter) + ret = (int16_t)out_line(iter, buf, (linewidth - bufcnt)); + delete [] buf; + return(ret); +} +//////////////////////////////////////////////////////////////////////////////// +int32_t CxImageGIF::get_num_frames(CxFile *fp,struct_TabCol* TabColSrc,struct_dscgif* dscgif) +{ + struct_image image; + + int32_t pos=fp->Tell(); + int32_t nframes=0; + + struct_TabCol TempTabCol; + memcpy(&TempTabCol,TabColSrc,sizeof(struct_TabCol)); + + char ch; + bool bPreviousWasNull = true; + + for (BOOL bContinue = TRUE; bContinue; ) + { + if (fp->Read(&ch, sizeof(ch), 1) != 1) {break;} + + if (bPreviousWasNull || ch==0) + { + switch (ch) + { + case '!': // extension + { + DecodeExtension(fp); + break; + } + case ',': // image + { + + assert(sizeof(image) == 9); + //log << "Image header" << endl; + fp->Read(&image,sizeof(image),1); + + //avoid byte order problems with Solaris + image.l = m_ntohs(image.l); + image.t = m_ntohs(image.t); + image.w = m_ntohs(image.w); + image.h = m_ntohs(image.h); + + // in case of images with empty screen descriptor, give a last chance + if (dscgif->scrwidth==0 && dscgif->scrheight==0){ + dscgif->scrwidth = image.w; + dscgif->scrheight = image.h; + } + + if (((image.l + image.w) > dscgif->scrwidth)||((image.t + image.h) > dscgif->scrheight)) + break; + + nframes++; + + // Local colour map? + if (image.pf & 0x80) { + TempTabCol.sogct = (int16_t)(1 << ((image.pf & 0x07) +1)); + assert(3 == sizeof(struct rgb_color)); + fp->Read(TempTabCol.paleta,sizeof(struct rgb_color)*TempTabCol.sogct,1); + //log << "Local colour map" << endl; + } + + int32_t badcode=0; + ibf = GIFBUFTAM+1; + + interlaced = image.pf & 0x40; + iheight = image.h; + istep = 8; + iypos = 0; + ipass = 0; + + int32_t pos_start = fp->Tell(); + + //if (interlaced) log << "Interlaced" << endl; + decoder(fp, 0, image.w, badcode); + + if (badcode){ + seek_next_image(fp,pos_start); + } else { + fp->Seek(-(ibfmax - ibf - 1), SEEK_CUR); + } + + break; + } + case ';': //terminator + bContinue=false; + break; + default: + bPreviousWasNull = (ch==0); + break; + } + } + } + + fp->Seek(pos,SEEK_SET); + return nframes; +} +//////////////////////////////////////////////////////////////////////////////// +int32_t CxImageGIF::seek_next_image(CxFile* fp, int32_t position) +{ + fp->Seek(position, SEEK_SET); + char ch1,ch2; + ch1=ch2=0; + while(fp->Read(&ch2,sizeof(char),1)>0){ + if (ch1 == 0 && ch2 == ','){ + fp->Seek(-1,SEEK_CUR); + return fp->Tell(); + } else { + ch1 = ch2; + } + } + return -1; +} +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::SetLoops(int32_t loops) +{ m_loops=loops; } +//////////////////////////////////////////////////////////////////////////////// +int32_t CxImageGIF::GetLoops() +{ return m_loops; } +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::SetComment(const char* sz_comment_in) +{ if (sz_comment_in) strncpy(m_comment,sz_comment_in,255); } +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::GetComment(char* sz_comment_out) +{ if (sz_comment_out) strncpy(sz_comment_out,m_comment,255); } +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::GifMix(CxImage & imgsrc2, struct_image & imgdesc) +{ + int32_t ymin = max(0,(int32_t)(GetHeight()-imgdesc.t - imgdesc.h)); + int32_t ymax = GetHeight()-imgdesc.t; + int32_t xmin = imgdesc.l; + int32_t xmax = min(GetWidth(), (uint32_t)(imgdesc.l + imgdesc.w)); + + int32_t ibg2= imgsrc2.GetTransIndex(); + uint8_t i2; + + for(int32_t y = ymin; y < ymax; y++){ + for(int32_t x = xmin; x < xmax; x++){ + i2 = imgsrc2.GetPixelIndex(x-xmin,y-ymin); + if(i2!=ibg2) SetPixelIndex(x,y,i2); + } + } +} +//////////////////////////////////////////////////////////////////////////////// +/*----------------------------------------------------------------------- + * + * miGIF Compression - mouse and ivo's GIF-compatible compression + * + * -run length encoding compression routines- + * + * Copyright (C) 1998 Hutchison Avenue Software Corporation + * http://www.hasc.com + * info@hasc.com + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. This software is provided "AS IS." The Hutchison Avenue + * Software Corporation disclaims all warranties, either express or implied, + * including but not limited to implied warranties of merchantability and + * fitness for a particular purpose, with respect to this code and accompanying + * documentation. + * + * The miGIF compression routines do not, strictly speaking, generate files + * conforming to the GIF spec, since the image data is not LZW-compressed + * (this is the point: in order to avoid transgression of the Unisys patent + * on the LZW algorithm.) However, miGIF generates data streams that any + * reasonably sane LZW decompresser will decompress to what we want. + * + * miGIF compression uses run length encoding. It compresses horizontal runs + * of pixels of the same color. This type of compression gives good results + * on images with many runs, for example images with lines, text and solid + * shapes on a solid-colored background. It gives little or no compression + * on images with few runs, for example digital or scanned photos. + * + * der Mouse + * mouse@rodents.montreal.qc.ca + * 7D C8 61 52 5D E7 2D 39 4E F1 31 3E E8 B3 27 4B + * + * ivo@hasc.com + * + * The Graphics Interchange Format(c) is the Copyright property of + * CompuServe Incorporated. GIF(sm) is a Service Mark property of + * CompuServe Incorporated. + * + */ +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::rle_clear(struct_RLE* rle) +{ + rle->out_bits = rle->out_bits_init; + rle->out_bump = rle->out_bump_init; + rle->out_clear = rle->out_clear_init; + rle->out_count = 0; + rle->rl_table_max = 0; + rle->just_cleared = 1; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::rle_flush(struct_RLE* rle) +{ + if (rle->rl_count == 1){ + rle_output_plain(rle->rl_pixel,rle); + rle->rl_count = 0; + return; + } + if (rle->just_cleared){ + rle_flush_fromclear(rle->rl_count,rle); + } else if ((rle->rl_table_max < 2) || (rle->rl_table_pixel != rle->rl_pixel)) { + rle_flush_clearorrep(rle->rl_count,rle); + } else { + rle_flush_withtable(rle->rl_count,rle); + } + rle->rl_count = 0; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::rle_output_plain(int32_t c,struct_RLE* rle) +{ + rle->just_cleared = 0; + rle_output(c,rle); + rle->out_count++; + if (rle->out_count >= rle->out_bump){ + rle->out_bits ++; + rle->out_bump += 1 << (rle->out_bits - 1); + } + if (rle->out_count >= rle->out_clear){ + rle_output(rle->code_clear,rle); + rle_clear(rle); + } +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::rle_flush_fromclear(int32_t count,struct_RLE* rle) +{ + int32_t n; + + rle->out_clear = rle->max_ocodes; + rle->rl_table_pixel = rle->rl_pixel; + n = 1; + while (count > 0){ + if (n == 1){ + rle->rl_table_max = 1; + rle_output_plain(rle->rl_pixel,rle); + count --; + } else if (count >= n){ + rle->rl_table_max = n; + rle_output_plain(rle->rl_basecode+n-2,rle); + count -= n; + } else if (count == 1){ + rle->rl_table_max ++; + rle_output_plain(rle->rl_pixel,rle); + count = 0; + } else { + rle->rl_table_max ++; + rle_output_plain(rle->rl_basecode+count-2,rle); + count = 0; + } + if (rle->out_count == 0) n = 1; else n ++; + } + rle_reset_out_clear(rle); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::rle_reset_out_clear(struct_RLE* rle) +{ + rle->out_clear = rle->out_clear_init; + if (rle->out_count >= rle->out_clear){ + rle_output(rle->code_clear,rle); + rle_clear(rle); + } +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::rle_flush_withtable(int32_t count, struct_RLE* rle) +{ + int32_t repmax; + int32_t repleft; + int32_t leftover; + + repmax = count / rle->rl_table_max; + leftover = count % rle->rl_table_max; + repleft = (leftover ? 1 : 0); + if (rle->out_count+repmax+repleft > rle->max_ocodes){ + repmax = rle->max_ocodes - rle->out_count; + leftover = count - (repmax * rle->rl_table_max); + repleft = 1 + rle_compute_triangle_count(leftover,rle->max_ocodes); + } + if (1+rle_compute_triangle_count(count,rle->max_ocodes) < (uint32_t)(repmax+repleft)){ + rle_output(rle->code_clear,rle); + rle_clear(rle); + rle_flush_fromclear(count,rle); + return; + } + rle->out_clear = rle->max_ocodes; + for (;repmax>0;repmax--) rle_output_plain(rle->rl_basecode+rle->rl_table_max-2,rle); + if (leftover){ + if (rle->just_cleared){ + rle_flush_fromclear(leftover,rle); + } else if (leftover == 1){ + rle_output_plain(rle->rl_pixel,rle); + } else { + rle_output_plain(rle->rl_basecode+leftover-2,rle); + } + } + rle_reset_out_clear(rle); +} +//////////////////////////////////////////////////////////////////////////////// +uint32_t CxImageGIF::rle_compute_triangle_count(uint32_t count, uint32_t nrepcodes) +{ + uint32_t perrep; + uint32_t cost; + + cost = 0; + perrep = (nrepcodes * (nrepcodes+1)) / 2; + while (count >= perrep){ + cost += nrepcodes; + count -= perrep; + } + if (count > 0){ + uint32_t n; + n = rle_isqrt(count); + while ((n*(n+1)) >= 2*count) n --; + while ((n*(n+1)) < 2*count) n ++; + cost += n; + } + return(cost); +} +//////////////////////////////////////////////////////////////////////////////// +uint32_t CxImageGIF::rle_isqrt(uint32_t x) +{ + uint32_t r; + uint32_t v; + + if (x < 2) return(x); + for (v=x,r=1;v;v>>=2,r<<=1) ; + for( ;; ) + { + v = ((x / r) + r) / 2; + if ((v == r) || (v == r+1)) return(r); + r = v; + } +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::rle_flush_clearorrep(int32_t count, struct_RLE* rle) +{ + int32_t withclr; + withclr = 1 + rle_compute_triangle_count(count,rle->max_ocodes); + if (withclr < count) { + rle_output(rle->code_clear,rle); + rle_clear(rle); + rle_flush_fromclear(count,rle); + } else { + for (;count>0;count--) rle_output_plain(rle->rl_pixel,rle); + } +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::rle_write_block(struct_RLE* rle) +{ + g_outfile->PutC((uint8_t)rle->oblen); + g_outfile->Write(rle->oblock,1,rle->oblen); + rle->oblen = 0; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::rle_block_out(uint8_t c, struct_RLE* rle) +{ + rle->oblock[rle->oblen++] = c; + if (rle->oblen >= 255) rle_write_block(rle); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::rle_block_flush(struct_RLE* rle) +{ + if (rle->oblen > 0) rle_write_block(rle); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::rle_output(int32_t val, struct_RLE* rle) +{ + rle->obuf |= val << rle->obits; + rle->obits += rle->out_bits; + while (rle->obits >= 8){ + rle_block_out((uint8_t)(rle->obuf&0xff),rle); + rle->obuf >>= 8; + rle->obits -= 8; + } +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::rle_output_flush(struct_RLE* rle) +{ + if (rle->obits > 0) rle_block_out((uint8_t)(rle->obuf),rle); + rle_block_flush(rle); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageGIF::compressRLE( int32_t init_bits, CxFile* outfile) +{ + g_init_bits = init_bits; + g_outfile = outfile; + + struct_RLE rle; + rle.code_clear = 1 << (init_bits - 1); + rle.code_eof = rle.code_clear + 1; + rle.rl_basecode = rle.code_eof + 1; + rle.out_bump_init = (1 << (init_bits - 1)) - 1; + rle.out_clear_init = (init_bits <= 3) ? 9 : (rle.out_bump_init-1); + rle.out_bits_init = init_bits; + rle.max_ocodes = (1 << MAXBITSCODES) - ((1 << (rle.out_bits_init - 1)) + 3); + rle.rl_count = 0; + rle_clear(&rle); + rle.obuf = 0; + rle.obits = 0; + rle.oblen = 0; + + rle_output(rle.code_clear,&rle); + + int32_t c; + for( ;; ) + { + c = GifNextPixel(); + if ((rle.rl_count > 0) && (c != rle.rl_pixel)) rle_flush(&rle); + if (c == EOF) break; + if (rle.rl_pixel == c){ + rle.rl_count++; + } else { + rle.rl_pixel = c; + rle.rl_count = 1; + } + } + rle_output(rle.code_eof,&rle); + rle_output_flush(&rle); +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_GIF diff --git a/DuiLib/3rd/CxImage/ximagif.h b/DuiLib/3rd/CxImage/ximagif.h new file mode 100644 index 0000000..347afff --- /dev/null +++ b/DuiLib/3rd/CxImage/ximagif.h @@ -0,0 +1,244 @@ +/* + * File: ximagif.h + * Purpose: GIF Image Class Loader and Writer + */ +/* ========================================================== + * CxImageGIF (c) 07/Aug/2001 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * + * Special thanks to Troels Knakkergaard for new features, enhancements and bugfixes + * + * original CImageGIF and CImageIterator implementation are: + * Copyright: (c) 1995, Alejandro Aguilar Sierra + * + * 6/15/97 Randy Spann: Added GIF87a writing support + * R.Spann@ConnRiver.net + * + * DECODE.C - An LZW decoder for GIF + * Copyright (C) 1987, by Steven A. Bennett + * Copyright (C) 1994, C++ version by Alejandro Aguilar Sierra + * + * In accordance with the above, I want to credit Steve Wilhite who wrote + * the code which this is heavily inspired by... + * + * GIF and 'Graphics Interchange Format' are trademarks (tm) of + * Compuserve, Incorporated, an H&R Block Company. + * + * Release Notes: This file contains a decoder routine for GIF images + * which is similar, structurally, to the original routine by Steve Wilhite. + * It is, however, somewhat noticably faster in most cases. + * + * ========================================================== + */ + +#if !defined(__ximaGIF_h) +#define __ximaGIF_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_GIF + +typedef int16_t code_int; + +/* Various error codes used by decoder */ +#define OUT_OF_MEMORY -10 +#define BAD_CODE_SIZE -20 +#define READ_ERROR -1 +#define WRITE_ERROR -2 +#define OPEN_ERROR -3 +#define CREATE_ERROR -4 +#define BAD_LINE_WIDTH -5 +#define MAX_CODES 4095 +#define GIFBUFTAM 16383 +#define TRANSPARENCY_CODE 0xF9 + +//LZW GIF Image compression +#define MAXBITSCODES 12 +#define HSIZE 5003 /* 80% occupancy */ +#define MAXCODE(n_bits) (((code_int) 1 << (n_bits)) - 1) +#define HashTabOf(i) htab[i] +#define CodeTabOf(i) codetab[i] + + +class CImageIterator; +class DLL_EXP CxImageGIF: public CxImage +{ +#pragma pack(1) + +typedef struct tag_gifgce{ + uint8_t flags; /*res:3|dispmeth:3|userinputflag:1|transpcolflag:1*/ + uint16_t delaytime; + uint8_t transpcolindex; +} struct_gifgce; + +typedef struct tag_dscgif{ /* Logic Screen Descriptor */ + char header[6]; /* Firma and version */ + uint16_t scrwidth; + uint16_t scrheight; + char pflds; + char bcindx; + char pxasrat; +} struct_dscgif; + +typedef struct tag_image{ /* Image Descriptor */ + uint16_t l; + uint16_t t; + uint16_t w; + uint16_t h; + uint8_t pf; +} struct_image; + +typedef struct tag_TabCol{ /* Tabla de colores */ + int16_t colres; /* color resolution */ + int16_t sogct; /* size of global color table */ + rgb_color paleta[256]; /* paleta */ +} struct_TabCol; + +typedef struct tag_RLE{ + int32_t rl_pixel; + int32_t rl_basecode; + int32_t rl_count; + int32_t rl_table_pixel; + int32_t rl_table_max; + int32_t just_cleared; + int32_t out_bits; + int32_t out_bits_init; + int32_t out_count; + int32_t out_bump; + int32_t out_bump_init; + int32_t out_clear; + int32_t out_clear_init; + int32_t max_ocodes; + int32_t code_clear; + int32_t code_eof; + uint32_t obuf; + int32_t obits; + uint8_t oblock[256]; + int32_t oblen; +} struct_RLE; +#pragma pack() + +public: + CxImageGIF(); + ~CxImageGIF(); + +// bool Load(const TCHAR * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_GIF);} +// bool Save(const TCHAR * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_GIF);} + + bool Decode(CxFile * fp); + bool Decode(FILE *fp) { CxIOFile file(fp); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * fp); + bool Encode(CxFile * fp, CxImage ** pImages, int32_t pagecount, bool bLocalColorMap = false, bool bLocalDispMeth = false); + bool Encode(FILE *fp) { CxIOFile file(fp); return Encode(&file); } + bool Encode(FILE *fp, CxImage ** pImages, int32_t pagecount, bool bLocalColorMap = false) + { CxIOFile file(fp); return Encode(&file, pImages, pagecount, bLocalColorMap); } +#endif // CXIMAGE_SUPPORT_ENCODE + + void SetLoops(int32_t loops); + int32_t GetLoops(); + void SetComment(const char* sz_comment_in); + void GetComment(char* sz_comment_out); + +protected: + bool DecodeExtension(CxFile *fp); + void EncodeHeader(CxFile *fp); + void EncodeLoopExtension(CxFile *fp); + void EncodeExtension(CxFile *fp); + void EncodeBody(CxFile *fp, bool bLocalColorMap = false); + void EncodeComment(CxFile *fp); + bool EncodeRGB(CxFile *fp); + void GifMix(CxImage & imgsrc2, struct_image & imgdesc); + + struct_gifgce gifgce; + + int32_t curx, cury; + int32_t CountDown; + uint32_t cur_accum; + int32_t cur_bits; + int32_t interlaced, iypos, istep, iheight, ipass; + int32_t ibf; + int32_t ibfmax; + uint8_t * buf; +// Implementation + int32_t GifNextPixel (); + void Putword (int32_t w, CxFile* fp ); + void compressNONE (int32_t init_bits, CxFile* outfile); + void compressLZW (int32_t init_bits, CxFile* outfile); + void output (code_int code ); + void cl_hash (int32_t hsize); + void char_out (int32_t c); + void flush_char (); + int16_t init_exp(int16_t size); + int16_t get_next_code(CxFile*); + int16_t decoder(CxFile*, CImageIterator* iter, int16_t linewidth, int32_t &bad_code_count); + int32_t get_byte(CxFile*); + int32_t out_line(CImageIterator* iter, uint8_t *pixels, int32_t linelen); + int32_t get_num_frames(CxFile *f,struct_TabCol* TabColSrc,struct_dscgif* dscgif); + int32_t seek_next_image(CxFile* fp, int32_t position); + + int16_t curr_size; /* The current code size */ + int16_t clear; /* Value for a clear code */ + int16_t ending; /* Value for a ending code */ + int16_t newcodes; /* First available code */ + int16_t top_slot; /* Highest code for current size */ + int16_t slot; /* Last read code */ + + /* The following static variables are used + * for seperating out codes */ + int16_t navail_bytes; /* # bytes left in block */ + int16_t nbits_left; /* # bits left in current uint8_t */ + uint8_t b1; /* Current uint8_t */ + uint8_t * byte_buff; /* Current block */ + uint8_t *pbytes; /* Pointer to next uint8_t in block */ + /* The reason we have these seperated like this instead of using + * a structure like the original Wilhite code did, is because this + * stuff generally produces significantly faster code when compiled... + * This code is full of similar speedups... (For a good book on writing + * C for speed or for space optomisation, see Efficient C by Tom Plum, + * published by Plum-Hall Associates...) + */ + uint8_t * stack; /* Stack for storing pixels */ + uint8_t * suffix; /* Suffix table */ + uint16_t * prefix; /* Prefix linked list */ + +//LZW GIF Image compression routines + int32_t * htab; + uint16_t * codetab; + int32_t n_bits; /* number of bits/code */ + code_int maxcode; /* maximum code, given n_bits */ + code_int free_ent; /* first unused entry */ + int32_t clear_flg; + int32_t g_init_bits; + CxFile* g_outfile; + int32_t ClearCode; + int32_t EOFCode; + + int32_t a_count; + char * accum; + + char * m_comment; + int32_t m_loops; + +//RLE compression routines + void compressRLE( int32_t init_bits, CxFile* outfile); + void rle_clear(struct_RLE* rle); + void rle_flush(struct_RLE* rle); + void rle_flush_withtable(int32_t count, struct_RLE* rle); + void rle_flush_clearorrep(int32_t count, struct_RLE* rle); + void rle_flush_fromclear(int32_t count,struct_RLE* rle); + void rle_output_plain(int32_t c,struct_RLE* rle); + void rle_reset_out_clear(struct_RLE* rle); + uint32_t rle_compute_triangle_count(uint32_t count, uint32_t nrepcodes); + uint32_t rle_isqrt(uint32_t x); + void rle_write_block(struct_RLE* rle); + void rle_block_out(uint8_t c, struct_RLE* rle); + void rle_block_flush(struct_RLE* rle); + void rle_output(int32_t val, struct_RLE* rle); + void rle_output_flush(struct_RLE* rle); +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximahist.cpp b/DuiLib/3rd/CxImage/ximahist.cpp new file mode 100644 index 0000000..cfda039 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximahist.cpp @@ -0,0 +1,627 @@ +// xImaHist.cpp : histogram functions +/* 28/01/2004 v1.00 - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_DSP + +//////////////////////////////////////////////////////////////////////////////// +int32_t CxImage::Histogram(int32_t* red, int32_t* green, int32_t* blue, int32_t* gray, int32_t colorspace) +{ + if (!pDib) return 0; + RGBQUAD color; + + if (red) memset(red,0,256*sizeof(int32_t)); + if (green) memset(green,0,256*sizeof(int32_t)); + if (blue) memset(blue,0,256*sizeof(int32_t)); + if (gray) memset(gray,0,256*sizeof(int32_t)); + + int32_t xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + for(int32_t y=ymin; yn) n=red[i]; + if (green && green[i]>n) n=green[i]; + if (blue && blue[i]>n) n=blue[i]; + if (gray && gray[i]>n) n=gray[i]; + } + + return n; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * HistogramStretch + * \param method: 0 = luminance (default), 1 = linked channels , 2 = independent channels. + * \param threshold: minimum percentage level in the histogram to recognize it as meaningful. Range: 0.0 to 1.0; default = 0; typical = 0.005 (0.5%); + * \return true if everything is ok + * \author [dave] and [nipper]; changes [DP] + */ +bool CxImage::HistogramStretch(int32_t method, double threshold) +{ + if (!pDib) return false; + + double dbScaler = 50.0/head.biHeight; + int32_t x,y; + + if ((head.biBitCount==8) && IsGrayScale()){ + + double p[256]; + memset(p, 0, 256*sizeof(double)); + for (y=0; y0 && p[maxc]<=threshold) maxc--; + + if (minc == 0 && maxc == 255) return true; + if (minc >= maxc) return true; + + // calculate LUT + uint8_t lut[256]; + for (x = 0; x <256; x++){ + lut[x] = (uint8_t)max(0,min(255,(255 * (x - minc) / (maxc - minc)))); + } + + for (y=0; y + double p[256]; + memset(p, 0, 256*sizeof(double)); + for (y=0; y0 && p[maxc]<=threshold) maxc--; + + if (minc == 0 && maxc == 255) return true; + if (minc >= maxc) return true; + + // calculate LUT + uint8_t lut[256]; + for (x = 0; x <256; x++){ + lut[x] = (uint8_t)max(0,min(255,(255 * (x - minc) / (maxc - minc)))); + } + + // normalize image + for (y=0; y + double pR[256]; + memset(pR, 0, 256*sizeof(double)); + double pG[256]; + memset(pG, 0, 256*sizeof(double)); + double pB[256]; + memset(pB, 0, 256*sizeof(double)); + for (y=0; y0 && pR[maxR]<=threshold2) maxR--; + + maxh = 0; + for (y=0; y<255; y++) if (maxh < pG[y]) maxh = pG[y]; + threshold2 = threshold*maxh; + int32_t minG = 0; + while (minG<255 && pG[minG]<=threshold2) minG++; + int32_t maxG = 255; + while (maxG>0 && pG[maxG]<=threshold2) maxG--; + + maxh = 0; + for (y=0; y<255; y++) if (maxh < pB[y]) maxh = pB[y]; + threshold2 = threshold*maxh; + int32_t minB = 0; + while (minB<255 && pB[minB]<=threshold2) minB++; + int32_t maxB = 255; + while (maxB>0 && pB[maxB]<=threshold2) maxB--; + + if (minR == 0 && maxR == 255 && minG == 0 && maxG == 255 && minB == 0 && maxB == 255) + return true; + + // calculate LUT + uint8_t lutR[256]; + uint8_t range = maxR - minR; + if (range != 0) { + for (x = 0; x <256; x++){ + lutR[x] = (uint8_t)max(0,min(255,(255 * (x - minR) / range))); + } + } else lutR[minR] = minR; + + uint8_t lutG[256]; + range = maxG - minG; + if (range != 0) { + for (x = 0; x <256; x++){ + lutG[x] = (uint8_t)max(0,min(255,(255 * (x - minG) / range))); + } + } else lutG[minG] = minG; + + uint8_t lutB[256]; + range = maxB - minB; + if (range != 0) { + for (x = 0; x <256; x++){ + lutB[x] = (uint8_t)max(0,min(255,(255 * (x - minB) / range))); + } + } else lutB[minB] = minB; + + // normalize image + for (y=0; y + double p[256]; + memset(p, 0, 256*sizeof(double)); + for (y=0; y0 && p[maxc]<=threshold) maxc--; + + if (minc == 0 && maxc == 255) return true; + if (minc >= maxc) return true; + + // calculate LUT + uint8_t lut[256]; + for (x = 0; x <256; x++){ + lut[x] = (uint8_t)max(0,min(255,(255 * (x - minc) / (maxc - minc)))); + } + + for(y=0; y : dave(at)posortho(dot)com +bool CxImage::HistogramEqualize() +{ + if (!pDib) return false; + + int32_t histogram[256]; + int32_t map[256]; + int32_t equalize_map[256]; + int32_t x, y, i, j; + RGBQUAD color; + RGBQUAD yuvClr; + uint32_t YVal, high, low; + + memset( &histogram, 0, sizeof(int32_t) * 256 ); + memset( &map, 0, sizeof(int32_t) * 256 ); + memset( &equalize_map, 0, sizeof(int32_t) * 256 ); + + // form histogram + for(y=0; y < head.biHeight; y++){ + info.nProgress = (int32_t)(50*y/head.biHeight); + if (info.nEscape) break; + for(x=0; x < head.biWidth; x++){ + color = BlindGetPixelColor( x, y ); + YVal = (uint32_t)RGB2GRAY(color.rgbRed, color.rgbGreen, color.rgbBlue); + histogram[YVal]++; + } + } + + // integrate the histogram to get the equalization map. + j = 0; + for(i=0; i <= 255; i++){ + j += histogram[i]; + map[i] = j; + } + + // equalize + low = map[0]; + high = map[255]; + if (low == high) return false; + for( i = 0; i <= 255; i++ ){ + equalize_map[i] = (uint32_t)((((double)( map[i] - low ) ) * 255) / ( high - low ) ); + } + + // stretch the histogram + if(head.biClrUsed == 0){ // No Palette + for( y = 0; y < head.biHeight; y++ ){ + info.nProgress = (int32_t)(50+50*y/head.biHeight); + if (info.nEscape) break; + for( x = 0; x < head.biWidth; x++ ){ + + color = BlindGetPixelColor( x, y ); + yuvClr = RGBtoYUV(color); + + yuvClr.rgbRed = (uint8_t)equalize_map[yuvClr.rgbRed]; + + color = YUVtoRGB(yuvClr); + BlindSetPixelColor( x, y, color ); + } + } + } else { // Palette + for( i = 0; i < (int32_t)head.biClrUsed; i++ ){ + + color = GetPaletteColor((uint8_t)i); + yuvClr = RGBtoYUV(color); + + yuvClr.rgbRed = (uint8_t)equalize_map[yuvClr.rgbRed]; + + color = YUVtoRGB(yuvClr); + SetPaletteColor( (uint8_t)i, color ); + } + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +// HistogramNormalize function by : dave(at)posortho(dot)com +bool CxImage::HistogramNormalize() +{ + if (!pDib) return false; + + int32_t histogram[256]; + int32_t threshold_intensity, intense; + int32_t x, y, i; + uint32_t normalize_map[256]; + uint32_t high, low, YVal; + + RGBQUAD color; + RGBQUAD yuvClr; + + memset( &histogram, 0, sizeof( int32_t ) * 256 ); + memset( &normalize_map, 0, sizeof( uint32_t ) * 256 ); + + // form histogram + for(y=0; y < head.biHeight; y++){ + info.nProgress = (int32_t)(50*y/head.biHeight); + if (info.nEscape) break; + for(x=0; x < head.biWidth; x++){ + color = BlindGetPixelColor( x, y ); + YVal = (uint32_t)RGB2GRAY(color.rgbRed, color.rgbGreen, color.rgbBlue); + histogram[YVal]++; + } + } + + // find histogram boundaries by locating the 1 percent levels + threshold_intensity = ( head.biWidth * head.biHeight) / 100; + + intense = 0; + for( low = 0; low < 255; low++ ){ + intense += histogram[low]; + if( intense > threshold_intensity ) break; + } + + intense = 0; + for( high = 255; high != 0; high--){ + intense += histogram[ high ]; + if( intense > threshold_intensity ) break; + } + + if ( low == high ){ + // Unreasonable contrast; use zero threshold to determine boundaries. + threshold_intensity = 0; + intense = 0; + for( low = 0; low < 255; low++){ + intense += histogram[low]; + if( intense > threshold_intensity ) break; + } + intense = 0; + for( high = 255; high != 0; high-- ){ + intense += histogram [high ]; + if( intense > threshold_intensity ) break; + } + } + if( low == high ) return false; // zero span bound + + // Stretch the histogram to create the normalized image mapping. + for(i = 0; i <= 255; i++){ + if ( i < (int32_t) low ){ + normalize_map[i] = 0; + } else { + if(i > (int32_t) high) + normalize_map[i] = 255; + else + normalize_map[i] = ( 255 - 1) * ( i - low) / ( high - low ); + } + } + + // Normalize + if( head.biClrUsed == 0 ){ + for( y = 0; y < head.biHeight; y++ ){ + info.nProgress = (int32_t)(50+50*y/head.biHeight); + if (info.nEscape) break; + for( x = 0; x < head.biWidth; x++ ){ + + color = BlindGetPixelColor( x, y ); + yuvClr = RGBtoYUV( color ); + + yuvClr.rgbRed = (uint8_t)normalize_map[yuvClr.rgbRed]; + + color = YUVtoRGB( yuvClr ); + BlindSetPixelColor( x, y, color ); + } + } + } else { + for(i = 0; i < (int32_t)head.biClrUsed; i++){ + + color = GetPaletteColor( (uint8_t)i ); + yuvClr = RGBtoYUV( color ); + + yuvClr.rgbRed = (uint8_t)normalize_map[yuvClr.rgbRed]; + + color = YUVtoRGB( yuvClr ); + SetPaletteColor( (uint8_t)i, color ); + } + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +// HistogramLog function by : dave(at)posortho(dot)com +bool CxImage::HistogramLog() +{ + if (!pDib) return false; + + //q(i,j) = 255/log(1 + |high|) * log(1 + |p(i,j)|); + int32_t x, y, i; + RGBQUAD color; + RGBQUAD yuvClr; + + uint32_t YVal, high = 1; + + // Find Highest Luminance Value in the Image + if( head.biClrUsed == 0 ){ // No Palette + for(y=0; y < head.biHeight; y++){ + info.nProgress = (int32_t)(50*y/head.biHeight); + if (info.nEscape) break; + for(x=0; x < head.biWidth; x++){ + color = BlindGetPixelColor( x, y ); + YVal = (uint32_t)RGB2GRAY(color.rgbRed, color.rgbGreen, color.rgbBlue); + if (YVal > high ) high = YVal; + } + } + } else { // Palette + for(i = 0; i < (int32_t)head.biClrUsed; i++){ + color = GetPaletteColor((uint8_t)i); + YVal = (uint32_t)RGB2GRAY(color.rgbRed, color.rgbGreen, color.rgbBlue); + if (YVal > high ) high = YVal; + } + } + + // Logarithm Operator + double k = 255.0 / ::log( 1.0 + (double)high ); + if( head.biClrUsed == 0 ){ + for( y = 0; y < head.biHeight; y++ ){ + info.nProgress = (int32_t)(50+50*y/head.biHeight); + if (info.nEscape) break; + for( x = 0; x < head.biWidth; x++ ){ + + color = BlindGetPixelColor( x, y ); + yuvClr = RGBtoYUV( color ); + + yuvClr.rgbRed = (uint8_t)(k * ::log( 1.0 + (double)yuvClr.rgbRed ) ); + + color = YUVtoRGB( yuvClr ); + BlindSetPixelColor( x, y, color ); + } + } + } else { + for(i = 0; i < (int32_t)head.biClrUsed; i++){ + + color = GetPaletteColor( (uint8_t)i ); + yuvClr = RGBtoYUV( color ); + + yuvClr.rgbRed = (uint8_t)(k * ::log( 1.0 + (double)yuvClr.rgbRed ) ); + + color = YUVtoRGB( yuvClr ); + SetPaletteColor( (uint8_t)i, color ); + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// HistogramRoot function by : dave(at)posortho(dot)com +bool CxImage::HistogramRoot() +{ + if (!pDib) return false; + //q(i,j) = sqrt(|p(i,j)|); + + int32_t x, y, i; + RGBQUAD color; + RGBQUAD yuvClr; + double dtmp; + uint32_t YVal, high = 1; + + // Find Highest Luminance Value in the Image + if( head.biClrUsed == 0 ){ // No Palette + for(y=0; y < head.biHeight; y++){ + info.nProgress = (int32_t)(50*y/head.biHeight); + if (info.nEscape) break; + for(x=0; x < head.biWidth; x++){ + color = BlindGetPixelColor( x, y ); + YVal = (uint32_t)RGB2GRAY(color.rgbRed, color.rgbGreen, color.rgbBlue); + if (YVal > high ) high = YVal; + } + } + } else { // Palette + for(i = 0; i < (int32_t)head.biClrUsed; i++){ + color = GetPaletteColor((uint8_t)i); + YVal = (uint32_t)RGB2GRAY(color.rgbRed, color.rgbGreen, color.rgbBlue); + if (YVal > high ) high = YVal; + } + } + + // Root Operator + double k = 256.0 / ::sqrt( 1.0 + (double)high ); + if( head.biClrUsed == 0 ){ + for( y = 0; y < head.biHeight; y++ ){ + info.nProgress = (int32_t)(50+50*y/head.biHeight); + if (info.nEscape) break; + for( x = 0; x < head.biWidth; x++ ){ + + color = BlindGetPixelColor( x, y ); + yuvClr = RGBtoYUV( color ); + + dtmp = k * ::sqrt( (double)yuvClr.rgbRed ); + if ( dtmp > 255.0 ) dtmp = 255.0; + if ( dtmp < 0 ) dtmp = 0; + yuvClr.rgbRed = (uint8_t)dtmp; + + color = YUVtoRGB( yuvClr ); + BlindSetPixelColor( x, y, color ); + } + } + } else { + for(i = 0; i < (int32_t)head.biClrUsed; i++){ + + color = GetPaletteColor( (uint8_t)i ); + yuvClr = RGBtoYUV( color ); + + dtmp = k * ::sqrt( (double)yuvClr.rgbRed ); + if ( dtmp > 255.0 ) dtmp = 255.0; + if ( dtmp < 0 ) dtmp = 0; + yuvClr.rgbRed = (uint8_t)dtmp; + + color = YUVtoRGB( yuvClr ); + SetPaletteColor( (uint8_t)i, color ); + } + } + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif diff --git a/DuiLib/3rd/CxImage/ximaico.cpp b/DuiLib/3rd/CxImage/ximaico.cpp new file mode 100644 index 0000000..2050ef2 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximaico.cpp @@ -0,0 +1,470 @@ +/* + * File: ximaico.cpp + * Purpose: Platform Independent ICON Image Class Loader and Writer (MS version) + * 07/Aug/2001 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximaico.h" + +#if CXIMAGE_SUPPORT_ICO + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageICO::Decode(CxFile *hFile) +{ + if (hFile==NULL) return false; + + uint32_t off = hFile->Tell(); // + int32_t page=info.nFrame; //internal icon structure indexes + + // read the first part of the header + ICONHEADER icon_header; + hFile->Read(&icon_header,sizeof(ICONHEADER),1); + + icon_header.idType = m_ntohs(icon_header.idType); + icon_header.idCount = m_ntohs(icon_header.idCount); + + // check if it's an icon or a cursor + if ((icon_header.idReserved == 0) && ((icon_header.idType == 1)||(icon_header.idType == 2))) { + + info.nNumFrames = icon_header.idCount; + + // load the icon descriptions + ICONDIRENTRY *icon_list = (ICONDIRENTRY *)malloc(icon_header.idCount * sizeof(ICONDIRENTRY)); + int32_t c; + for (c = 0; c < icon_header.idCount; c++) { + hFile->Read(icon_list + c, sizeof(ICONDIRENTRY), 1); + + icon_list[c].wPlanes = m_ntohs(icon_list[c].wPlanes); + icon_list[c].wBitCount = m_ntohs(icon_list[c].wBitCount); + icon_list[c].dwBytesInRes = m_ntohl(icon_list[c].dwBytesInRes); + icon_list[c].dwImageOffset = m_ntohl(icon_list[c].dwImageOffset); + } + + if ((page>=0)&&(pageSeek(off + icon_list[page].dwImageOffset, SEEK_SET); + CxImage png; + png.SetEscape(-1); + if (png.Decode(hFile,CXIMAGE_FORMAT_PNG)){ + Transfer(png); + info.nNumFrames = icon_header.idCount; + } + } +#endif //CXIMAGE_SUPPORT_PNG + free(icon_list); + info.dwType = CXIMAGE_FORMAT_ICO; + return true; + } + + // get the bit count for the colors in the icon + BITMAPINFOHEADER bih; + hFile->Seek(off + icon_list[page].dwImageOffset, SEEK_SET); + + if (icon_list[page].bWidth==0 && icon_list[page].bHeight==0) + { // Vista icon support +#if CXIMAGE_SUPPORT_PNG + CxImage png; + if (png.Decode(hFile,CXIMAGE_FORMAT_PNG)){ + Transfer(png); + info.nNumFrames = icon_header.idCount; + } + SetType(CXIMAGE_FORMAT_ICO); +#endif //CXIMAGE_SUPPORT_PNG + } + else + { // standard icon + hFile->Read(&bih,sizeof(BITMAPINFOHEADER),1); + + bihtoh(&bih); + + c = bih.biBitCount; + + // allocate memory for one icon + Create(icon_list[page].bWidth,icon_list[page].bHeight, c, CXIMAGE_FORMAT_ICO); //image creation + + // read the palette + RGBQUAD pal[256]; + if (bih.biClrUsed) + hFile->Read(pal,bih.biClrUsed*sizeof(RGBQUAD), 1); + else + hFile->Read(pal,head.biClrUsed*sizeof(RGBQUAD), 1); + + SetPalette(pal,head.biClrUsed); //palette assign + + //read the icon + if (c<=24){ + hFile->Read(info.pImage, head.biSizeImage, 1); + } else { // 32 bit icon + uint8_t* buf=(uint8_t*)malloc(4*head.biHeight*head.biWidth); + uint8_t* src = buf; + hFile->Read(buf, 4*head.biHeight*head.biWidth, 1); +#if CXIMAGE_SUPPORT_ALPHA + if (!AlphaIsValid()) AlphaCreate(); +#endif //CXIMAGE_SUPPORT_ALPHA + for (int32_t y = 0; y < head.biHeight; y++) { + uint8_t* dst = GetBits(y); + for(int32_t x=0;xRead(mask, masksize, 1)){ + + bool bGoodMask=false; + for (int32_t im=0;im>3)]>>(7-x%8))&0x01)){ + AlphaSet(x,y,0); + bNeedAlpha=true; + } + } + } + if (!bNeedAlpha) AlphaDelete(); +#endif //CXIMAGE_SUPPORT_ALPHA + + //check if there is only one transparent color + RGBQUAD cc,ct; + int32_t nTransColors=0; + int32_t nTransIndex=0; + for (y = 0; y < head.biHeight; y++){ + for (x = 0; x < head.biWidth; x++){ + if (((mask[y*maskwdt+(x>>3)] >> (7-x%8)) & 0x01)){ + cc = GetPixelColor(x,y,false); + if (nTransColors==0){ + nTransIndex = GetPixelIndex(x,y); + nTransColors++; + ct = cc; + } else { + if (memcmp(&cc, &ct, sizeof(RGBQUAD)) != 0){ + nTransColors++; + } + } + } + } + } + if (nTransColors==1 && c<=8){ + SetTransColor(ct); + SetTransIndex(nTransIndex); +#if CXIMAGE_SUPPORT_ALPHA + AlphaDelete(); //because we have a unique transparent color in the image +#endif //CXIMAGE_SUPPORT_ALPHA + } + + // - Transparency support w/o Alpha support + if (c <= 8){ // only for icons with less than 256 colors (XP icons need alpha). + + // find a color index, which is not used in the image + // it is almost sure to find one, bcs. nobody uses all possible colors for an icon + + uint8_t colorsUsed[256]; + memset(colorsUsed, 0, sizeof(colorsUsed)); + + for (y = 0; y < head.biHeight; y++){ + for (x = 0; x < head.biWidth; x++){ + colorsUsed[BlindGetPixelIndex(x,y)] = 1; + } + } + + int32_t iTransIdx = -1; + for (x = (int32_t)(head.biClrUsed-1); x>=0 ; x--){ + if (colorsUsed[x] == 0){ + iTransIdx = x; // this one is not in use. we may use it as transparent color + break; + } + } + + // Go thru image and set unused color as transparent index if needed + if (iTransIdx >= 0){ + bool bNeedTrans = false; + for (y = 0; y < head.biHeight; y++){ + for (x = 0; x < head.biWidth; x++){ + // AND mask (Each Byte represents 8 Pixels) + if (((mask[y*maskwdt+(x>>3)] >> (7-x%8)) & 0x01)){ + // AND mask is set (!=0). This is a transparent part + SetPixelIndex(x, y, (uint8_t)iTransIdx); + bNeedTrans = true; + } + } + } + // set transparent index if needed + if (bNeedTrans) SetTransIndex(iTransIdx); +#if CXIMAGE_SUPPORT_ALPHA + AlphaDelete(); //because we have a transparent color in the palette +#endif //CXIMAGE_SUPPORT_ALPHA + } + } + } else { + SetTransIndex(0); //empty mask, set black as transparent color + Negative(); + } + } + free(mask); + } + free(icon_list); + // icon has been loaded successfully! + return true; + } + free(icon_list); + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +// Thanks to +bool CxImageICO::Encode(CxFile * hFile, CxImage ** pImages, int32_t nPageCount) +{ + cx_try + { + if (hFile==NULL) cx_throw("invalid file pointer"); + if (pImages==NULL || nPageCount<=0) cx_throw("multipage ICO, no images!"); + + int32_t i; + for (i=0; iIsValid())) + cx_throw("Empty image"); + } + + CxImageICO ghost; + for (i=0; i255)||(head.biHeight>255)){ + strcpy(info.szLastError,"Can't save this image as icon"); + return false; + } +#endif + + //prepare the palette struct + RGBQUAD* pal=GetPalette(); + if (head.biBitCount<=8 && pal==NULL) return false; + + int32_t maskwdt=((head.biWidth+31)/32)*4; //mask line width + int32_t masksize=head.biHeight * maskwdt; //size of mask + int32_t bitcount=head.biBitCount; + int32_t imagesize=head.biSizeImage; +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid() && head.biClrUsed==0){ + bitcount=32; + imagesize=4*head.biHeight*head.biWidth; + } +#endif + + //fill the icon headers + int32_t nPages = nPageCount; + if (nPages<1) nPages = 1; + + ICONHEADER icon_header={0,1,nPages}; + + if (!bAppend) + m_dwImageOffset = sizeof(ICONHEADER) + nPages * sizeof(ICONDIRENTRY); + + uint32_t dwBytesInRes = sizeof(BITMAPINFOHEADER)+head.biClrUsed*sizeof(RGBQUAD)+imagesize+masksize; + + ICONDIRENTRY icon_list={ + (uint8_t)head.biWidth, + (uint8_t)head.biHeight, + (uint8_t)head.biClrUsed, + 0, 0, + (uint16_t)bitcount, + dwBytesInRes, + m_dwImageOffset + }; + + BITMAPINFOHEADER bi={ + sizeof(BITMAPINFOHEADER), + head.biWidth, + 2*head.biHeight, + 1, + (uint16_t)bitcount, + 0, imagesize, + 0, 0, 0, 0 + }; + +#if CXIMAGE_SUPPORT_PNG // Vista icon support + CxImage png(*this); + CxMemFile memfile; + if (head.biWidth>255 || head.biHeight>255){ + icon_list.bWidth = icon_list.bHeight = 0; + memfile.Open(); + png.Encode(&memfile,CXIMAGE_FORMAT_PNG); + icon_list.dwBytesInRes = dwBytesInRes = memfile.Size(); + } +#endif //CXIMAGE_SUPPORT_PNG + + if (!bAppend){ + icon_header.idType = m_ntohs(icon_header.idType); + icon_header.idCount = m_ntohs(icon_header.idCount); + hFile->Write(&icon_header,sizeof(ICONHEADER),1); //write the file header + icon_header.idType = m_ntohs(icon_header.idType); + icon_header.idCount = m_ntohs(icon_header.idCount); + } + + + if ((bAppend && nPageCount==info.nNumFrames) || (!bAppend && nPageCount==0)){ + icon_list.wPlanes = m_ntohs(icon_list.wPlanes); + icon_list.wBitCount = m_ntohs(icon_list.wBitCount); + icon_list.dwBytesInRes = m_ntohl(icon_list.dwBytesInRes); + icon_list.dwImageOffset = m_ntohl(icon_list.dwImageOffset); + hFile->Write(&icon_list,sizeof(ICONDIRENTRY),1); //write the image entry + icon_list.wPlanes = m_ntohs(icon_list.wPlanes); + icon_list.wBitCount = m_ntohs(icon_list.wBitCount); + icon_list.dwBytesInRes = m_ntohl(icon_list.dwBytesInRes); + icon_list.dwImageOffset = m_ntohl(icon_list.dwImageOffset); + + m_dwImageOffset += dwBytesInRes; //update offset for next header + } + + if ((bAppend && nPageCountWrite(memfile.GetBuffer(false),dwBytesInRes,1); + } else +#endif //CXIMAGE_SUPPORT_PNG + { // standard icon + bihtoh(&bi); + hFile->Write(&bi,sizeof(BITMAPINFOHEADER),1); //write the image header + bihtoh(&bi); + + bool bTransparent = info.nBkgndIndex >= 0; + RGBQUAD ct = GetTransColor(); + if (pal){ + if (bTransparent) SetPaletteColor((uint8_t)info.nBkgndIndex,0,0,0,0); + hFile->Write(pal,head.biClrUsed*sizeof(RGBQUAD),1); //write palette + if (bTransparent) SetPaletteColor((uint8_t)info.nBkgndIndex,ct); + } + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid() && head.biClrUsed==0){ + uint8_t* buf=(uint8_t*)malloc(imagesize); + uint8_t* dst = buf; + for (int32_t y = 0; y < head.biHeight; y++) { + uint8_t* src = GetBits(y); + for(int32_t x=0;xWrite(buf,imagesize, 1); + free(buf); + } else { + hFile->Write(info.pImage,imagesize,1); //write image + } +#else + hFile->Write(info.pImage,imagesize,1); //write image +#endif + + //save transparency mask + uint8_t* mask=(uint8_t*)calloc(masksize,1); //create empty AND/XOR masks + if (!mask) return false; + + //prepare the variables to build the mask + uint8_t* iDst; + int32_t pos,i; + RGBQUAD c={0,0,0,0}; + int32_t* pc = (int32_t*)&c; + int32_t* pct= (int32_t*)&ct; +#if CXIMAGE_SUPPORT_ALPHA + bool bAlphaPaletteIsValid = AlphaPaletteIsValid(); + bool bAlphaIsValid = AlphaIsValid(); +#endif + //build the mask + for (int32_t y = 0; y < head.biHeight; y++) { + for (int32_t x = 0; x < head.biWidth; x++) { + i=0; +#if CXIMAGE_SUPPORT_ALPHA + if (bAlphaIsValid && AlphaGet(x,y)==0) i=1; + if (bAlphaPaletteIsValid && BlindGetPixelColor(x,y).rgbReserved==0) i=1; +#endif + c=GetPixelColor(x,y,false); + if (bTransparent && *pc==*pct) i=1; + iDst = mask + y*maskwdt + (x>>3); + pos = 7-x%8; + *iDst &= ~(0x01<Write(mask,masksize,1); + free(mask); + } + } + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ICO + diff --git a/DuiLib/3rd/CxImage/ximaico.h b/DuiLib/3rd/CxImage/ximaico.h new file mode 100644 index 0000000..023ce67 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximaico.h @@ -0,0 +1,58 @@ +/* + * File: ximaico.h + * Purpose: ICON Image Class Loader and Writer + */ +/* ========================================================== + * CxImageICO (c) 07/Aug/2001 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * ========================================================== + */ +#if !defined(__ximaICO_h) +#define __ximaICO_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_ICO + +class CxImageICO: public CxImage +{ +typedef struct tagIconDirectoryEntry { + uint8_t bWidth; + uint8_t bHeight; + uint8_t bColorCount; + uint8_t bReserved; + uint16_t wPlanes; + uint16_t wBitCount; + uint32_t dwBytesInRes; + uint32_t dwImageOffset; +} ICONDIRENTRY; + +typedef struct tagIconDir { + uint16_t idReserved; + uint16_t idType; + uint16_t idCount; +} ICONHEADER; + +public: + CxImageICO(): CxImage(CXIMAGE_FORMAT_ICO) {m_dwImageOffset=0;} + +// bool Load(const TCHAR * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_ICO);} +// bool Save(const TCHAR * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_ICO);} + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile, bool bAppend=false, int32_t nPageCount=0); + bool Encode(CxFile * hFile, CxImage ** pImages, int32_t nPageCount); + bool Encode(FILE *hFile, bool bAppend=false, int32_t nPageCount=0) + { CxIOFile file(hFile); return Encode(&file,bAppend,nPageCount); } + bool Encode(FILE *hFile, CxImage ** pImages, int32_t nPageCount) + { CxIOFile file(hFile); return Encode(&file, pImages, nPageCount); } +#endif // CXIMAGE_SUPPORT_ENCODE +protected: + uint32_t m_dwImageOffset; +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximainfo.cpp b/DuiLib/3rd/CxImage/ximainfo.cpp new file mode 100644 index 0000000..b079eee --- /dev/null +++ b/DuiLib/3rd/CxImage/ximainfo.cpp @@ -0,0 +1,958 @@ +// ximainfo.cpp : main attributes +/* 03/10/2004 v1.00 - Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximage.h" + +#if defined(_LINUX) || defined(__APPLE__) + #define _tcsnicmp(a,b,c) strcasecmp(a,b) +#endif + +//////////////////////////////////////////////////////////////////////////////// +/** + * \return the color used for transparency, and/or for background color + */ +RGBQUAD CxImage::GetTransColor() +{ + if (head.biBitCount<24 && info.nBkgndIndex>=0) return GetPaletteColor((uint8_t)info.nBkgndIndex); + return info.nBkgndColor; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Gets the index used for transparency. Returns -1 for no transparancy. + */ +int32_t CxImage::GetTransIndex() const +{ + return info.nBkgndIndex; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets the index used for transparency with 1, 4 and 8 bpp images. Set to -1 to remove the effect. + */ +void CxImage::SetTransIndex(int32_t idx) +{ + if (idx<(int32_t)head.biClrUsed) + info.nBkgndIndex = idx; + else + info.nBkgndIndex = 0; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets the color used for transparency with 24 bpp images. + * You must call SetTransIndex(0) to enable the effect, SetTransIndex(-1) to disable it. + */ +void CxImage::SetTransColor(RGBQUAD rgb) +{ + rgb.rgbReserved=0; + info.nBkgndColor = rgb; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::IsTransparent() const +{ + return info.nBkgndIndex>=0; // +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Returns true if the image has 256 colors or less. + */ +bool CxImage::IsIndexed() const +{ + return head.biClrUsed!=0; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return 1 = indexed, 2 = RGB, 4 = RGBA + */ +uint8_t CxImage::GetColorType() +{ + uint8_t b = (uint8_t)((head.biBitCount>8) ? 2 /*COLORTYPE_COLOR*/ : 1 /*COLORTYPE_PALETTE*/); +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) b = 4 /*COLORTYPE_ALPHA*/; +#endif //CXIMAGE_SUPPORT_ALPHA + return b; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return Resolution for TIFF, JPEG, PNG and BMP formats. + */ +int32_t CxImage::GetXDPI() const +{ + return info.xDPI; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return Resolution for TIFF, JPEG, PNG and BMP formats. + */ +int32_t CxImage::GetYDPI() const +{ + return info.yDPI; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Set resolution for TIFF, JPEG, PNG and BMP formats. + */ +void CxImage::SetXDPI(int32_t dpi) +{ + if (dpi<=0) dpi = CXIMAGE_DEFAULT_DPI; + info.xDPI = dpi; + head.biXPelsPerMeter = (int32_t) floor(dpi * 10000.0 / 254.0 + 0.5); + if (pDib) ((BITMAPINFOHEADER*)pDib)->biXPelsPerMeter = head.biXPelsPerMeter; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Set resolution for TIFF, JPEG, PNG and BMP formats. + */ +void CxImage::SetYDPI(int32_t dpi) +{ + if (dpi<=0) dpi = CXIMAGE_DEFAULT_DPI; + info.yDPI = dpi; + head.biYPelsPerMeter = (int32_t) floor(dpi * 10000.0 / 254.0 + 0.5); + if (pDib) ((BITMAPINFOHEADER*)pDib)->biYPelsPerMeter = head.biYPelsPerMeter; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa SetFlags + */ +uint32_t CxImage::GetFlags() const +{ + return info.dwFlags; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Image flags, for future use + * \param flags + * - 0x??00000 = reserved for 16 bit, CMYK, multilayer + * - 0x00??0000 = blend modes + * - 0x0000???? = layer id or user flags + * + * \param bLockReservedFlags protects the "reserved" and "blend modes" flags + */ +void CxImage::SetFlags(uint32_t flags, bool bLockReservedFlags) +{ + if (bLockReservedFlags) info.dwFlags = flags & 0x0000ffff; + else info.dwFlags = flags; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa SetCodecOption + */ +uint32_t CxImage::GetCodecOption(uint32_t imagetype) +{ + imagetype = GetTypeIndexFromId(imagetype); + if (imagetype==0){ + imagetype = GetTypeIndexFromId(GetType()); + } + return info.dwCodecOpt[imagetype]; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Encode option for GIF, TIF, JPG, PNG and RAW + * - GIF : 0 = LZW (default), 1 = none, 2 = RLE. + * - TIF : 0 = automatic (default), or a valid compression code as defined in "tiff.h" (COMPRESSION_NONE = 1, COMPRESSION_CCITTRLE = 2, ...) + * - JPG : valid values stored in enum CODEC_OPTION ( ENCODE_BASELINE = 0x01, ENCODE_PROGRESSIVE = 0x10, ...) + * - PNG : combination of interlace option and compression option + * interlace option : 1 = interlace, 0 = no interlace + * compression option : 2 = no compression, 4 = best speed, 6 = best compression, 8 = default compression + * default is no interlace and default compression + * example : 5 = 1+4 = interlace + best speed + * - RAW : valid values stored in enum CODEC_OPTION ( DECODE_QUALITY_LIN = 0x00, DECODE_QUALITY_VNG = 0x01, ...) + * + * \return true if everything is ok + */ +bool CxImage::SetCodecOption(uint32_t opt, uint32_t imagetype) +{ + imagetype = GetTypeIndexFromId(imagetype); + if (imagetype==0){ + imagetype = GetTypeIndexFromId(GetType()); + } + info.dwCodecOpt[imagetype] = opt; + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return internal hDib object.. + */ +void* CxImage::GetDIB() const +{ + return pDib; +} +//////////////////////////////////////////////////////////////////////////////// +uint32_t CxImage::GetHeight() const +{ + return head.biHeight; +} +//////////////////////////////////////////////////////////////////////////////// +uint32_t CxImage::GetWidth() const +{ + return head.biWidth; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return uint32_t aligned width of the image. + */ +uint32_t CxImage::GetEffWidth() const +{ + return info.dwEffWidth; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return 2, 16, 256; 0 for RGB images. + */ +uint32_t CxImage::GetNumColors() const +{ + return head.biClrUsed; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return: 1, 4, 8, 24. + */ +uint16_t CxImage::GetBpp() const +{ + return head.biBitCount; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return original image format + * \sa ENUM_CXIMAGE_FORMATS. + */ +uint32_t CxImage::GetType() const +{ + return info.dwType; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * change image format identifier + * \sa ENUM_CXIMAGE_FORMATS. + */ +bool CxImage::SetType(uint32_t type) +{ + switch (type){ +#if CXIMAGE_SUPPORT_BMP + case CXIMAGE_FORMAT_BMP: +#endif +#if CXIMAGE_SUPPORT_GIF + case CXIMAGE_FORMAT_GIF: +#endif +#if CXIMAGE_SUPPORT_JPG + case CXIMAGE_FORMAT_JPG: +#endif +#if CXIMAGE_SUPPORT_PNG + case CXIMAGE_FORMAT_PNG: +#endif +#if CXIMAGE_SUPPORT_MNG + case CXIMAGE_FORMAT_MNG: +#endif +#if CXIMAGE_SUPPORT_ICO + case CXIMAGE_FORMAT_ICO: +#endif +#if CXIMAGE_SUPPORT_TIF + case CXIMAGE_FORMAT_TIF: +#endif +#if CXIMAGE_SUPPORT_TGA + case CXIMAGE_FORMAT_TGA: +#endif +#if CXIMAGE_SUPPORT_PCX + case CXIMAGE_FORMAT_PCX: +#endif +#if CXIMAGE_SUPPORT_WBMP + case CXIMAGE_FORMAT_WBMP: +#endif +#if CXIMAGE_SUPPORT_WMF + case CXIMAGE_FORMAT_WMF: +#endif +#if CXIMAGE_SUPPORT_JBG + case CXIMAGE_FORMAT_JBG: +#endif +#if CXIMAGE_SUPPORT_JP2 + case CXIMAGE_FORMAT_JP2: +#endif +#if CXIMAGE_SUPPORT_JPC + case CXIMAGE_FORMAT_JPC: +#endif +#if CXIMAGE_SUPPORT_PGX + case CXIMAGE_FORMAT_PGX: +#endif +#if CXIMAGE_SUPPORT_PNM + case CXIMAGE_FORMAT_PNM: +#endif +#if CXIMAGE_SUPPORT_RAS + case CXIMAGE_FORMAT_RAS: +#endif +#if CXIMAGE_SUPPORT_SKA + case CXIMAGE_FORMAT_SKA: +#endif +#if CXIMAGE_SUPPORT_RAW + case CXIMAGE_FORMAT_RAW: +#endif +#if CXIMAGE_SUPPORT_PSD + case CXIMAGE_FORMAT_PSD: +#endif + info.dwType = type; + return true; + case CXIMAGE_FORMAT_UNKNOWN: + default: + info.dwType = CXIMAGE_FORMAT_UNKNOWN; + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +uint32_t CxImage::GetNumTypes() +{ + return CMAX_IMAGE_FORMATS-1; +} +//////////////////////////////////////////////////////////////////////////////// +uint32_t CxImage::GetTypeIdFromName(const TCHAR* ext) +{ +#if CXIMAGE_SUPPORT_BMP + if (_tcsnicmp(ext,_T("bmp"),3)==0 ) return CXIMAGE_FORMAT_BMP; +#endif +#if CXIMAGE_SUPPORT_JPG + if (_tcsnicmp(ext,_T("jpg"),3)==0 || + _tcsnicmp(ext,_T("jpe"),3)==0 || + _tcsnicmp(ext,_T("jfi"),3)==0 ) return CXIMAGE_FORMAT_JPG; +#endif +#if CXIMAGE_SUPPORT_GIF + if (_tcsnicmp(ext,_T("gif"),3)==0 ) return CXIMAGE_FORMAT_GIF; +#endif +#if CXIMAGE_SUPPORT_PNG + if (_tcsnicmp(ext,_T("png"),3)==0 ) return CXIMAGE_FORMAT_PNG; +#endif +#if CXIMAGE_SUPPORT_ICO + if (_tcsnicmp(ext,_T("ico"),3)==0 || + _tcsnicmp(ext,_T("cur"),3)==0 ) return CXIMAGE_FORMAT_ICO; +#endif +#if CXIMAGE_SUPPORT_TIF + if (_tcsnicmp(ext,_T("tif"),3)==0 ) return CXIMAGE_FORMAT_TIF; +#endif +#if CXIMAGE_SUPPORT_TGA + if (_tcsnicmp(ext,_T("tga"),3)==0 ) return CXIMAGE_FORMAT_TGA; +#endif +#if CXIMAGE_SUPPORT_PCX + if (_tcsnicmp(ext,_T("pcx"),3)==0 ) return CXIMAGE_FORMAT_PCX; +#endif +#if CXIMAGE_SUPPORT_WBMP + if (_tcsnicmp(ext,_T("wbm"),3)==0 ) return CXIMAGE_FORMAT_WBMP; +#endif +#if CXIMAGE_SUPPORT_WMF + if (_tcsnicmp(ext,_T("wmf"),3)==0 || + _tcsnicmp(ext,_T("emf"),3)==0 ) return CXIMAGE_FORMAT_WMF; +#endif +#if CXIMAGE_SUPPORT_JP2 + if (_tcsnicmp(ext,_T("jp2"),3)==0 || + _tcsnicmp(ext,_T("j2k"),3)==0 ) return CXIMAGE_FORMAT_JP2; +#endif +#if CXIMAGE_SUPPORT_JPC + if (_tcsnicmp(ext,_T("jpc"),3)==0 || + _tcsnicmp(ext,_T("j2c"),3)==0 ) return CXIMAGE_FORMAT_JPC; +#endif +#if CXIMAGE_SUPPORT_PGX + if (_tcsnicmp(ext,_T("pgx"),3)==0 ) return CXIMAGE_FORMAT_PGX; +#endif +#if CXIMAGE_SUPPORT_RAS + if (_tcsnicmp(ext,_T("ras"),3)==0 ) return CXIMAGE_FORMAT_RAS; +#endif +#if CXIMAGE_SUPPORT_PNM + if (_tcsnicmp(ext,_T("pnm"),3)==0 || + _tcsnicmp(ext,_T("pgm"),3)==0 || + _tcsnicmp(ext,_T("ppm"),3)==0 ) return CXIMAGE_FORMAT_PNM; +#endif +#if CXIMAGE_SUPPORT_JBG + if (_tcsnicmp(ext,_T("jbg"),3)==0 ) return CXIMAGE_FORMAT_JBG; +#endif +#if CXIMAGE_SUPPORT_MNG + if (_tcsnicmp(ext,_T("mng"),3)==0 || + _tcsnicmp(ext,_T("jng"),3)==0 ) return CXIMAGE_FORMAT_MNG; +#endif +#if CXIMAGE_SUPPORT_SKA + if (_tcsnicmp(ext,_T("ska"),3)==0 ) return CXIMAGE_FORMAT_SKA; +#endif +#if CXIMAGE_SUPPORT_PSD + if (_tcsnicmp(ext,_T("psd"),3)==0 ) return CXIMAGE_FORMAT_PSD; +#endif +#if CXIMAGE_SUPPORT_RAW + if (_tcsnicmp(ext,_T("nef"),3)==0 || + _tcsnicmp(ext,_T("crw"),3)==0 || + _tcsnicmp(ext,_T("cr2"),3)==0 || + _tcsnicmp(ext,_T("dng"),3)==0 || + _tcsnicmp(ext,_T("arw"),3)==0 || + _tcsnicmp(ext,_T("erf"),3)==0 || + _tcsnicmp(ext,_T("3fr"),3)==0 || + _tcsnicmp(ext,_T("dcr"),3)==0 || + _tcsnicmp(ext,_T("raw"),3)==0 || + _tcsnicmp(ext,_T("x3f"),3)==0 || + _tcsnicmp(ext,_T("mef"),3)==0 || + _tcsnicmp(ext,_T("raf"),3)==0 || + _tcsnicmp(ext,_T("mrw"),3)==0 || + _tcsnicmp(ext,_T("pef"),3)==0 || + _tcsnicmp(ext,_T("sr2"),3)==0 || + _tcsnicmp(ext,_T("orf"),3)==0 ) return CXIMAGE_FORMAT_RAW; +#endif + + return CXIMAGE_FORMAT_UNKNOWN; +} +//////////////////////////////////////////////////////////////////////////////// +uint32_t CxImage::GetTypeIdFromIndex(const uint32_t index) +{ + uint32_t n; + + n=0; if (index == n) return CXIMAGE_FORMAT_UNKNOWN; +#if CXIMAGE_SUPPORT_BMP + n++; if (index == n) return CXIMAGE_FORMAT_BMP; +#endif +#if CXIMAGE_SUPPORT_GIF + n++; if (index == n) return CXIMAGE_FORMAT_GIF; +#endif +#if CXIMAGE_SUPPORT_JPG + n++; if (index == n) return CXIMAGE_FORMAT_JPG; +#endif +#if CXIMAGE_SUPPORT_PNG + n++; if (index == n) return CXIMAGE_FORMAT_PNG; +#endif +#if CXIMAGE_SUPPORT_ICO + n++; if (index == n) return CXIMAGE_FORMAT_ICO; +#endif +#if CXIMAGE_SUPPORT_TIF + n++; if (index == n) return CXIMAGE_FORMAT_TIF; +#endif +#if CXIMAGE_SUPPORT_TGA + n++; if (index == n) return CXIMAGE_FORMAT_TGA; +#endif +#if CXIMAGE_SUPPORT_PCX + n++; if (index == n) return CXIMAGE_FORMAT_PCX; +#endif +#if CXIMAGE_SUPPORT_WBMP + n++; if (index == n) return CXIMAGE_FORMAT_WBMP; +#endif +#if CXIMAGE_SUPPORT_WMF + n++; if (index == n) return CXIMAGE_FORMAT_WMF; +#endif +#if CXIMAGE_SUPPORT_JP2 + n++; if (index == n) return CXIMAGE_FORMAT_JP2; +#endif +#if CXIMAGE_SUPPORT_JPC + n++; if (index == n) return CXIMAGE_FORMAT_JPC; +#endif +#if CXIMAGE_SUPPORT_PGX + n++; if (index == n) return CXIMAGE_FORMAT_PGX; +#endif +#if CXIMAGE_SUPPORT_PNM + n++; if (index == n) return CXIMAGE_FORMAT_PNM; +#endif +#if CXIMAGE_SUPPORT_RAS + n++; if (index == n) return CXIMAGE_FORMAT_RAS; +#endif +#if CXIMAGE_SUPPORT_JBG + n++; if (index == n) return CXIMAGE_FORMAT_JBG; +#endif +#if CXIMAGE_SUPPORT_MNG + n++; if (index == n) return CXIMAGE_FORMAT_MNG; +#endif +#if CXIMAGE_SUPPORT_SKA + n++; if (index == n) return CXIMAGE_FORMAT_SKA; +#endif +#if CXIMAGE_SUPPORT_RAW + n++; if (index == n) return CXIMAGE_FORMAT_RAW; +#endif +#if CXIMAGE_SUPPORT_PSD + n++; if (index == n) return CXIMAGE_FORMAT_PSD; +#endif + + return CXIMAGE_FORMAT_UNKNOWN; +} +//////////////////////////////////////////////////////////////////////////////// +uint32_t CxImage::GetTypeIndexFromId(const uint32_t id) +{ + uint32_t n; + + n=0; if (id == CXIMAGE_FORMAT_UNKNOWN) return n; +#if CXIMAGE_SUPPORT_BMP + n++; if (id == CXIMAGE_FORMAT_BMP) return n; +#endif +#if CXIMAGE_SUPPORT_GIF + n++; if (id == CXIMAGE_FORMAT_GIF) return n; +#endif +#if CXIMAGE_SUPPORT_JPG + n++; if (id == CXIMAGE_FORMAT_JPG) return n; +#endif +#if CXIMAGE_SUPPORT_PNG + n++; if (id == CXIMAGE_FORMAT_PNG) return n; +#endif +#if CXIMAGE_SUPPORT_ICO + n++; if (id == CXIMAGE_FORMAT_ICO) return n; +#endif +#if CXIMAGE_SUPPORT_TIF + n++; if (id == CXIMAGE_FORMAT_TIF) return n; +#endif +#if CXIMAGE_SUPPORT_TGA + n++; if (id == CXIMAGE_FORMAT_TGA) return n; +#endif +#if CXIMAGE_SUPPORT_PCX + n++; if (id == CXIMAGE_FORMAT_PCX) return n; +#endif +#if CXIMAGE_SUPPORT_WBMP + n++; if (id == CXIMAGE_FORMAT_WBMP) return n; +#endif +#if CXIMAGE_SUPPORT_WMF + n++; if (id == CXIMAGE_FORMAT_WMF) return n; +#endif +#if CXIMAGE_SUPPORT_JP2 + n++; if (id == CXIMAGE_FORMAT_JP2) return n; +#endif +#if CXIMAGE_SUPPORT_JPC + n++; if (id == CXIMAGE_FORMAT_JPC) return n; +#endif +#if CXIMAGE_SUPPORT_PGX + n++; if (id == CXIMAGE_FORMAT_PGX) return n; +#endif +#if CXIMAGE_SUPPORT_PNM + n++; if (id == CXIMAGE_FORMAT_PNM) return n; +#endif +#if CXIMAGE_SUPPORT_RAS + n++; if (id == CXIMAGE_FORMAT_RAS) return n; +#endif +#if CXIMAGE_SUPPORT_JBG + n++; if (id == CXIMAGE_FORMAT_JBG) return n; +#endif +#if CXIMAGE_SUPPORT_MNG + n++; if (id == CXIMAGE_FORMAT_MNG) return n; +#endif +#if CXIMAGE_SUPPORT_SKA + n++; if (id == CXIMAGE_FORMAT_SKA) return n; +#endif +#if CXIMAGE_SUPPORT_RAW + n++; if (id == CXIMAGE_FORMAT_RAW) return n; +#endif +#if CXIMAGE_SUPPORT_PSD + n++; if (id == CXIMAGE_FORMAT_PSD) return n; +#endif + + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return current frame delay in milliseconds. Only for GIF and MNG formats. + */ +uint32_t CxImage::GetFrameDelay() const +{ + return info.dwFrameDelay; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets current frame delay. Only for GIF format. + * \param d = delay in milliseconds + */ +void CxImage::SetFrameDelay(uint32_t d) +{ + info.dwFrameDelay=d; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::GetOffset(int32_t *x,int32_t *y) +{ + *x=info.xOffset; + *y=info.yOffset; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::SetOffset(int32_t x,int32_t y) +{ + info.xOffset=x; + info.yOffset=y; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa SetJpegQuality, GetJpegQualityF + * \author [DP]; changes [Stefan Schrmans] + */ +uint8_t CxImage::GetJpegQuality() const +{ + return (uint8_t)(info.fQuality + 0.5f); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa SetJpegQuality, GetJpegQuality + * \author [Stefan Schrmans] + */ +float CxImage::GetJpegQualityF() const +{ + return info.fQuality; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * quality level for JPEG and JPEG2000 + * \param q: can be from 0 to 100 + * \author [DP]; changes [Stefan Schrmans] + */ +void CxImage::SetJpegQuality(uint8_t q){ + info.fQuality = (float)q; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * quality level for JPEG and JPEG2000 + * necessary for JPEG2000 when quality is between 0.0 and 1.0 + * \param q: can be from 0.0 to 100.0 + * \author [Stefan Schrmans] + */ +void CxImage::SetJpegQualityF(float q){ + if (q>0) info.fQuality = q; + else info.fQuality = 0.0f; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa SetJpegScale + */ +uint8_t CxImage::GetJpegScale() const +{ + return info.nJpegScale; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * scaling down during JPEG decoding valid numbers are 1, 2, 4, 8 + * \author [ignacio] + */ +void CxImage::SetJpegScale(uint8_t q){ + info.nJpegScale = q; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Used to monitor the slow loops. + * \return value is from 0 to 100. + * \sa SetProgress + */ +int32_t CxImage::GetProgress() const +{ + return info.nProgress; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return the escape code. + * \sa SetEscape + */ +int32_t CxImage::GetEscape() const +{ + return info.nEscape; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Forces the value of the internal progress variable. + * \param p should be from 0 to 100. + * \sa GetProgress + */ +void CxImage::SetProgress(int32_t p) +{ + info.nProgress = p; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Used to quit the slow loops or the codecs. + * - SetEscape(-1) before Decode forces the function to exit, right after + * the image width and height are available ( for bmp, jpg, gif, tif ) + */ +void CxImage::SetEscape(int32_t i) +{ + info.nEscape = i; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Checks if the image is correctly initializated. + */ +bool CxImage::IsValid() const +{ + return pDib!=0; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * True if the image is enabled for painting. + */ +bool CxImage::IsEnabled() const +{ + return info.bEnabled; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Enables/disables the image. + */ +void CxImage::Enable(bool enable) +{ + info.bEnabled=enable; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * This function must be used after a Decode() / Load() call. + * Use the sequence SetFrame(-1); Load(...); GetNumFrames(); + * to get the number of images without loading the first image. + * \return the number of images in the file. + */ +int32_t CxImage::GetNumFrames() const +{ + return info.nNumFrames; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return the current selected image (zero-based index). + */ +int32_t CxImage::GetFrame() const +{ + return info.nFrame; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets the image number that the next Decode() / Load() call will load + */ +void CxImage::SetFrame(int32_t nFrame){ + info.nFrame=nFrame; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets the method for drawing the frame related to others + * \sa GetDisposalMethod + */ +void CxImage::SetDisposalMethod(uint8_t dm) +{ info.dispmeth=dm; } +//////////////////////////////////////////////////////////////////////////////// +/** + * Gets the method for drawing the frame related to others + * Values : 0 - No disposal specified. The decoder is + * not required to take any action. + * 1 - Do not dispose. The graphic is to be left + * in place. + * 2 - Restore to background color. The area used by the + * graphic must be restored to the background color. + * 3 - Restore to previous. The decoder is required to + * restore the area overwritten by the graphic with + * what was there prior to rendering the graphic. + * 4-7 - To be defined. + */ +uint8_t CxImage::GetDisposalMethod() const +{ return info.dispmeth; } +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::GetRetreiveAllFrames() const +{ return info.bGetAllFrames; } +//////////////////////////////////////////////////////////////////////////////// +void CxImage::SetRetreiveAllFrames(bool flag) +{ info.bGetAllFrames = flag; } +//////////////////////////////////////////////////////////////////////////////// +CxImage * CxImage::GetFrame(int32_t nFrame) const +{ + if ( ppFrames == NULL) return NULL; + if ( info.nNumFrames == 0) return NULL; + if ( nFrame >= info.nNumFrames ) return NULL; + if ( nFrame < 0) nFrame = info.nNumFrames - 1; + return ppFrames[nFrame]; +} +//////////////////////////////////////////////////////////////////////////////// +int16_t CxImage::m_ntohs(const int16_t word) +{ + if (info.bLittleEndianHost) return word; + return ( (word & 0xff) << 8 ) | ( (word >> 8) & 0xff ); +} +//////////////////////////////////////////////////////////////////////////////// +int32_t CxImage::m_ntohl(const int32_t dword) +{ + if (info.bLittleEndianHost) return dword; + return ((dword & 0xff) << 24 ) | ((dword & 0xff00) << 8 ) | + ((dword >> 8) & 0xff00) | ((dword >> 24) & 0xff); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::bihtoh(BITMAPINFOHEADER* bih) +{ + bih->biSize = m_ntohl(bih->biSize); + bih->biWidth = m_ntohl(bih->biWidth); + bih->biHeight = m_ntohl(bih->biHeight); + bih->biPlanes = m_ntohs(bih->biPlanes); + bih->biBitCount = m_ntohs(bih->biBitCount); + bih->biCompression = m_ntohl(bih->biCompression); + bih->biSizeImage = m_ntohl(bih->biSizeImage); + bih->biXPelsPerMeter = m_ntohl(bih->biXPelsPerMeter); + bih->biYPelsPerMeter = m_ntohl(bih->biYPelsPerMeter); + bih->biClrUsed = m_ntohl(bih->biClrUsed); + bih->biClrImportant = m_ntohl(bih->biClrImportant); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Returns the last reported error. + */ +const char* CxImage::GetLastError() +{ + return info.szLastError; +} +//////////////////////////////////////////////////////////////////////////////// +uint32_t CxImage::DumpSize() +{ + uint32_t n; + n = sizeof(BITMAPINFOHEADER) + sizeof(CXIMAGEINFO) + GetSize(); + +#if CXIMAGE_SUPPORT_ALPHA + if (pAlpha){ + n += 1 + head.biWidth * head.biHeight; + } else n++; +#endif + +#if CXIMAGE_SUPPORT_SELECTION + if (pSelection){ + n += 1 + head.biWidth * head.biHeight; + } else n++; +#endif + +#if CXIMAGE_SUPPORT_LAYERS + if (ppLayers){ + for (int32_t m=0; mDumpSize(); + } + } + } else n++; +#endif + + if (ppFrames){ + for (int32_t m=0; mDumpSize(); + } + } + } else n++; + + return n; +} +//////////////////////////////////////////////////////////////////////////////// +uint32_t CxImage::Dump(uint8_t * dst) +{ + if (!dst) return 0; + + memcpy(dst,&head,sizeof(BITMAPINFOHEADER)); + dst += sizeof(BITMAPINFOHEADER); + + memcpy(dst,&info,sizeof(CXIMAGEINFO)); + dst += sizeof(CXIMAGEINFO); + + memcpy(dst,pDib,GetSize()); + dst += GetSize(); + +#if CXIMAGE_SUPPORT_ALPHA + if (pAlpha){ + memset(dst++, 1, 1); + memcpy(dst,pAlpha,head.biWidth * head.biHeight); + dst += head.biWidth * head.biHeight; + } else { + memset(dst++, 0, 1); + } +#endif + +#if CXIMAGE_SUPPORT_SELECTION + if (pSelection){ + memset(dst++, 1, 1); + memcpy(dst,pSelection,head.biWidth * head.biHeight); + dst += head.biWidth * head.biHeight; + } else { + memset(dst++, 0, 1); + } +#endif + +#if CXIMAGE_SUPPORT_LAYERS + if (ppLayers){ + memset(dst++, 1, 1); + for (int32_t m=0; mDump(dst); + } + } + } else { + memset(dst++, 0, 1); + } +#endif + + if (ppFrames){ + memset(dst++, 1, 1); + for (int32_t m=0; mDump(dst); + } + } + } else { + memset(dst++, 0, 1); + } + + return DumpSize(); +} +//////////////////////////////////////////////////////////////////////////////// +uint32_t CxImage::UnDump(const uint8_t * src) +{ + if (!src) + return 0; + if (!Destroy()) + return 0; + if (!DestroyFrames()) + return 0; + + uint32_t n = 0; + + memcpy(&head,src,sizeof(BITMAPINFOHEADER)); + n += sizeof(BITMAPINFOHEADER); + + memcpy(&info,&src[n],sizeof(CXIMAGEINFO)); + n += sizeof(CXIMAGEINFO); + + if (!Create(head.biWidth, head.biHeight, head.biBitCount, info.dwType)) + return 0; + + memcpy(pDib,&src[n],GetSize()); + n += GetSize(); + +#if CXIMAGE_SUPPORT_ALPHA + if (src[n++]){ + if (AlphaCreate()){ + memcpy(pAlpha, &src[n], head.biWidth * head.biHeight); + } + n += head.biWidth * head.biHeight; + } +#endif + +#if CXIMAGE_SUPPORT_SELECTION + if (src[n++]){ + RECT box = info.rSelectionBox; + if (SelectionCreate()){ + info.rSelectionBox = box; + memcpy(pSelection, &src[n], head.biWidth * head.biHeight); + } + n += head.biWidth * head.biHeight; + } +#endif + +#if CXIMAGE_SUPPORT_LAYERS + if (src[n++]){ + ppLayers = new CxImage*[info.nNumLayers]; + for (int32_t m=0; mUnDump(&src[n]); + } + } +#endif + + if (src[n++]){ + ppFrames = new CxImage*[info.nNumFrames]; + for (int32_t m=0; mUnDump(&src[n]); + } + } + + return n; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \return A.BBCCCDDDD + * - A = main version + * - BB = main revision + * - CCC = minor revision (letter) + * - DDDD = experimental revision + */ +const float CxImage::GetVersionNumber() +{ + return 7.000010000f; +} +//////////////////////////////////////////////////////////////////////////////// +const TCHAR* CxImage::GetVersion() +{ + static const TCHAR CxImageVersion[] = _T("CxImage 7.0.1"); + return (CxImageVersion); +} +//////////////////////////////////////////////////////////////////////////////// diff --git a/DuiLib/3rd/CxImage/ximaint.cpp b/DuiLib/3rd/CxImage/ximaint.cpp new file mode 100644 index 0000000..fd0f64e --- /dev/null +++ b/DuiLib/3rd/CxImage/ximaint.cpp @@ -0,0 +1,1045 @@ +// xImaInt.cpp : interpolation functions +/* 02/2004 - Branko Brevensek + * CxImage version 7.0.1 07/Jan/2011 - Davide Pizzolato - www.xdp.it + */ + +#include "ximage.h" +#include "ximath.h" + +#if CXIMAGE_SUPPORT_INTERPOLATION + +//////////////////////////////////////////////////////////////////////////////// +/** + * Recalculates coordinates according to specified overflow method. + * If pixel (x,y) lies within image, nothing changes. + * + * \param x, y - coordinates of pixel + * \param ofMethod - overflow method + * + * \return x, y - new coordinates (pixel (x,y) now lies inside image) + * + * \author ***bd*** 2.2004 + */ +void CxImage::OverflowCoordinates(int32_t &x, int32_t &y, OverflowMethod const ofMethod) +{ + if (IsInside(x,y)) return; //if pixel is within bounds, no change + switch (ofMethod) { + case OM_REPEAT: + //clip coordinates + x=max(x,0); x=min(x, head.biWidth-1); + y=max(y,0); y=min(y, head.biHeight-1); + break; + case OM_WRAP: + //wrap coordinates + x = x % head.biWidth; + y = y % head.biHeight; + if (x<0) x = head.biWidth + x; + if (y<0) y = head.biHeight + y; + break; + case OM_MIRROR: + //mirror pixels near border + if (x<0) x=((-x) % head.biWidth); + else if (x>=head.biWidth) x=head.biWidth-(x % head.biWidth + 1); + if (y<0) y=((-y) % head.biHeight); + else if (y>=head.biHeight) y=head.biHeight-(y % head.biHeight + 1); + break; + default: + return; + }//switch +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * See OverflowCoordinates for integer version + * \author ***bd*** 2.2004 + */ +void CxImage::OverflowCoordinates(float &x, float &y, OverflowMethod const ofMethod) +{ + if (x>=0 && x=0 && y=head.biWidth) x=head.biWidth-((float)fmod(x, (float) head.biWidth) + 1); + if (y<0) y=(float)fmod(-y, (float) head.biHeight); + else if (y>=head.biHeight) y=head.biHeight-((float)fmod(y, (float) head.biHeight) + 1); + break; + default: + return; + }//switch +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Method return pixel color. Different methods are implemented for out of bounds pixels. + * If an image has alpha channel, alpha value is returned in .RGBReserved. + * + * \param x,y : pixel coordinates + * \param ofMethod : out-of-bounds method: + * - OF_WRAP - wrap over to pixels on other side of the image + * - OF_REPEAT - repeat last pixel on the edge + * - OF_COLOR - return input value of color + * - OF_BACKGROUND - return background color (if not set, return input color) + * - OF_TRANSPARENT - return transparent pixel + * + * \param rplColor : input color (returned for out-of-bound coordinates in OF_COLOR mode and if other mode is not applicable) + * + * \return color : color of pixel + * \author ***bd*** 2.2004 + */ +RGBQUAD CxImage::GetPixelColorWithOverflow(int32_t x, int32_t y, OverflowMethod const ofMethod, RGBQUAD* const rplColor) +{ + RGBQUAD color; //color to return + if ((!IsInside(x,y)) || pDib==NULL) { //is pixel within bouns?: + //pixel is out of bounds or no DIB + if (rplColor!=NULL) + color=*rplColor; + else { + color.rgbRed=color.rgbGreen=color.rgbBlue=255; color.rgbReserved=0; //default replacement colour: white transparent + }//if + if (pDib==NULL) return color; + //pixel is out of bounds: + switch (ofMethod) { + case OM_TRANSPARENT: +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) { + //alpha transparency is supported and image has alpha layer + color.rgbReserved=0; + } else { +#endif //CXIMAGE_SUPPORT_ALPHA + //no alpha transparency + if (GetTransIndex()>=0) { + color=GetTransColor(); //single color transparency enabled (return transparent color) + }//if +#if CXIMAGE_SUPPORT_ALPHA + }//if +#endif //CXIMAGE_SUPPORT_ALPHA + return color; + case OM_BACKGROUND: + //return background color (if it exists, otherwise input value) + if (info.nBkgndIndex >= 0) { + if (head.biBitCount<24) color = GetPaletteColor((uint8_t)info.nBkgndIndex); + else color = info.nBkgndColor; + }//if + return color; + case OM_REPEAT: + case OM_WRAP: + case OM_MIRROR: + OverflowCoordinates(x,y,ofMethod); + break; + default: + //simply return replacement color (OM_COLOR and others) + return color; + }//switch + }//if + //just return specified pixel (it's within bounds) + return BlindGetPixelColor(x,y); +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * This method reconstructs image according to chosen interpolation method and then returns pixel (x,y). + * (x,y) can lie between actual image pixels. If (x,y) lies outside of image, method returns value + * according to overflow method. + * This method is very useful for geometrical image transformations, where destination pixel + * can often assume color value lying between source pixels. + * + * \param (x,y) - coordinates of pixel to return + * GPCI method recreates "analogue" image back from digital data, so x and y + * are float values and color value of point (1.1,1) will generally not be same + * as (1,1). Center of first pixel is at (0,0) and center of pixel right to it is (1,0). + * (0.5,0) is half way between these two pixels. + * \param inMethod - interpolation (reconstruction) method (kernel) to use: + * - IM_NEAREST_NEIGHBOUR - returns colour of nearest lying pixel (causes stairy look of + * processed images) + * - IM_BILINEAR - interpolates colour from four neighbouring pixels (softens image a bit) + * - IM_BICUBIC - interpolates from 16 neighbouring pixels (can produce "halo" artifacts) + * - IM_BICUBIC2 - interpolates from 16 neighbouring pixels (perhaps a bit less halo artifacts + than IM_BICUBIC) + * - IM_BSPLINE - interpolates from 16 neighbouring pixels (softens image, washes colours) + * (As far as I know, image should be prefiltered for this method to give + * good results... some other time :) ) + * This method uses bicubic interpolation kernel from CXImage 5.99a and older + * versions. + * - IM_LANCZOS - interpolates from 12*12 pixels (slow, ringing artifacts) + * + * \param ofMethod - overflow method (see comments at GetPixelColorWithOverflow) + * \param rplColor - pointer to color used for out of borders pixels in OM_COLOR mode + * (and other modes if colour can't calculated in a specified way) + * + * \return interpolated color value (including interpolated alpha value, if image has alpha layer) + * + * \author ***bd*** 2.2004 + */ +RGBQUAD CxImage::GetPixelColorInterpolated( + float x,float y, + InterpolationMethod const inMethod, + OverflowMethod const ofMethod, + RGBQUAD* const rplColor) +{ + //calculate nearest pixel + int32_t xi=(int32_t)(x); if (x<0) xi--; //these replace (incredibly slow) floor (Visual c++ 2003, AMD Athlon) + int32_t yi=(int32_t)(y); if (y<0) yi--; + RGBQUAD color; //calculated colour + + switch (inMethod) { + case IM_NEAREST_NEIGHBOUR: + return GetPixelColorWithOverflow((int32_t)(x+0.5f), (int32_t)(y+0.5f), ofMethod, rplColor); + default: { + //IM_BILINEAR: bilinear interpolation + if (xi<-1 || xi>=head.biWidth || yi<-1 || yi>=head.biHeight) { //all 4 points are outside bounds?: + switch (ofMethod) { + case OM_COLOR: case OM_TRANSPARENT: case OM_BACKGROUND: + //we don't need to interpolate anything with all points outside in this case + return GetPixelColorWithOverflow(-999, -999, ofMethod, rplColor); + default: + //recalculate coordinates and use faster method later on + OverflowCoordinates(x,y,ofMethod); + xi=(int32_t)(x); if (x<0) xi--; //x and/or y have changed ... recalculate xi and yi + yi=(int32_t)(y); if (y<0) yi--; + }//switch + }//if + //get four neighbouring pixels + if ((xi+1)=0 && (yi+1)=0 && head.biClrUsed==0) { + //all pixels are inside RGB24 image... optimize reading (and use fixed point arithmetic) + uint16_t wt1=(uint16_t)((x-xi)*256.0f), wt2=(uint16_t)((y-yi)*256.0f); + uint16_t wd=wt1*wt2>>8; + uint16_t wb=wt1-wd; + uint16_t wc=wt2-wd; + uint16_t wa=256-wt1-wc; + uint16_t wrr,wgg,wbb; + uint8_t *pxptr=(uint8_t*)info.pImage+yi*info.dwEffWidth+xi*3; + wbb=wa*(*pxptr++); wgg=wa*(*pxptr++); wrr=wa*(*pxptr++); + wbb+=wb*(*pxptr++); wgg+=wb*(*pxptr++); wrr+=wb*(*pxptr); + pxptr+=(info.dwEffWidth-5); //move to next row + wbb+=wc*(*pxptr++); wgg+=wc*(*pxptr++); wrr+=wc*(*pxptr++); + wbb+=wd*(*pxptr++); wgg+=wd*(*pxptr++); wrr+=wd*(*pxptr); + color.rgbRed=(uint8_t) (wrr>>8); color.rgbGreen=(uint8_t) (wgg>>8); color.rgbBlue=(uint8_t) (wbb>>8); +#if CXIMAGE_SUPPORT_ALPHA + if (pAlpha) { + uint16_t waa; + //image has alpha layer... we have to do the same for alpha data + pxptr=AlphaGetPointer(xi,yi); //pointer to first byte + waa=wa*(*pxptr++); waa+=wb*(*pxptr); //first two pixels + pxptr+=(head.biWidth-1); //move to next row + waa+=wc*(*pxptr++); waa+=wd*(*pxptr); //and second row pixels + color.rgbReserved=(uint8_t) (waa>>8); + } else +#endif + { //Alpha not supported or no alpha at all + color.rgbReserved = 0; + } + return color; + } else { + //default (slower) way to get pixels (not RGB24 or some pixels out of borders) + float t1=x-xi, t2=y-yi; + float d=t1*t2; + float b=t1-d; + float c=t2-d; + float a=1-t1-c; + RGBQUAD rgb11,rgb21,rgb12,rgb22; + rgb11=GetPixelColorWithOverflow(xi, yi, ofMethod, rplColor); + rgb21=GetPixelColorWithOverflow(xi+1, yi, ofMethod, rplColor); + rgb12=GetPixelColorWithOverflow(xi, yi+1, ofMethod, rplColor); + rgb22=GetPixelColorWithOverflow(xi+1, yi+1, ofMethod, rplColor); + //calculate linear interpolation + color.rgbRed=(uint8_t) (a*rgb11.rgbRed+b*rgb21.rgbRed+c*rgb12.rgbRed+d*rgb22.rgbRed); + color.rgbGreen=(uint8_t) (a*rgb11.rgbGreen+b*rgb21.rgbGreen+c*rgb12.rgbGreen+d*rgb22.rgbGreen); + color.rgbBlue=(uint8_t) (a*rgb11.rgbBlue+b*rgb21.rgbBlue+c*rgb12.rgbBlue+d*rgb22.rgbBlue); +#if CXIMAGE_SUPPORT_ALPHA + color.rgbReserved=(uint8_t) (a*rgb11.rgbReserved+b*rgb21.rgbReserved+c*rgb12.rgbReserved+d*rgb22.rgbReserved); +#else + color.rgbReserved = 0; +#endif + return color; + }//if + }//default + case IM_BICUBIC: + case IM_BICUBIC2: + case IM_BSPLINE: + case IM_BOX: + case IM_HERMITE: + case IM_HAMMING: + case IM_SINC: + case IM_BLACKMAN: + case IM_BESSEL: + case IM_GAUSSIAN: + case IM_QUADRATIC: + case IM_MITCHELL: + case IM_CATROM: + case IM_HANNING: + case IM_POWER: + //bicubic interpolation(s) + if (((xi+2)<0) || ((xi-1)>=head.biWidth) || ((yi+2)<0) || ((yi-1)>=head.biHeight)) { //all points are outside bounds?: + switch (ofMethod) { + case OM_COLOR: case OM_TRANSPARENT: case OM_BACKGROUND: + //we don't need to interpolate anything with all points outside in this case + return GetPixelColorWithOverflow(-999, -999, ofMethod, rplColor); + break; + default: + //recalculate coordinates and use faster method later on + OverflowCoordinates(x,y,ofMethod); + xi=(int32_t)(x); if (x<0) xi--; //x and/or y have changed ... recalculate xi and yi + yi=(int32_t)(y); if (y<0) yi--; + }//switch + }//if + + //some variables needed from here on + int32_t xii,yii; //x any y integer indexes for loops + float kernel, kernelyc; //kernel cache + float kernelx[12], kernely[4]; //precalculated kernel values + float rr,gg,bb,aa; //accumulated color values + //calculate multiplication factors for all pixels + int32_t i; + switch (inMethod) { + case IM_BICUBIC: + for (i=0; i<4; i++) { + kernelx[i]=KernelCubic((float)(xi+i-1-x)); + kernely[i]=KernelCubic((float)(yi+i-1-y)); + }//for i + break; + case IM_BICUBIC2: + for (i=0; i<4; i++) { + kernelx[i]=KernelGeneralizedCubic((float)(xi+i-1-x), -0.5); + kernely[i]=KernelGeneralizedCubic((float)(yi+i-1-y), -0.5); + }//for i + break; + case IM_BSPLINE: + for (i=0; i<4; i++) { + kernelx[i]=KernelBSpline((float)(xi+i-1-x)); + kernely[i]=KernelBSpline((float)(yi+i-1-y)); + }//for i + break; + case IM_BOX: + for (i=0; i<4; i++) { + kernelx[i]=KernelBox((float)(xi+i-1-x)); + kernely[i]=KernelBox((float)(yi+i-1-y)); + }//for i + break; + case IM_HERMITE: + for (i=0; i<4; i++) { + kernelx[i]=KernelHermite((float)(xi+i-1-x)); + kernely[i]=KernelHermite((float)(yi+i-1-y)); + }//for i + break; + case IM_HAMMING: + for (i=0; i<4; i++) { + kernelx[i]=KernelHamming((float)(xi+i-1-x)); + kernely[i]=KernelHamming((float)(yi+i-1-y)); + }//for i + break; + case IM_SINC: + for (i=0; i<4; i++) { + kernelx[i]=KernelSinc((float)(xi+i-1-x)); + kernely[i]=KernelSinc((float)(yi+i-1-y)); + }//for i + break; + case IM_BLACKMAN: + for (i=0; i<4; i++) { + kernelx[i]=KernelBlackman((float)(xi+i-1-x)); + kernely[i]=KernelBlackman((float)(yi+i-1-y)); + }//for i + break; + case IM_BESSEL: + for (i=0; i<4; i++) { + kernelx[i]=KernelBessel((float)(xi+i-1-x)); + kernely[i]=KernelBessel((float)(yi+i-1-y)); + }//for i + break; + case IM_GAUSSIAN: + for (i=0; i<4; i++) { + kernelx[i]=KernelGaussian((float)(xi+i-1-x)); + kernely[i]=KernelGaussian((float)(yi+i-1-y)); + }//for i + break; + case IM_QUADRATIC: + for (i=0; i<4; i++) { + kernelx[i]=KernelQuadratic((float)(xi+i-1-x)); + kernely[i]=KernelQuadratic((float)(yi+i-1-y)); + }//for i + break; + case IM_MITCHELL: + for (i=0; i<4; i++) { + kernelx[i]=KernelMitchell((float)(xi+i-1-x)); + kernely[i]=KernelMitchell((float)(yi+i-1-y)); + }//for i + break; + case IM_CATROM: + for (i=0; i<4; i++) { + kernelx[i]=KernelCatrom((float)(xi+i-1-x)); + kernely[i]=KernelCatrom((float)(yi+i-1-y)); + }//for i + break; + case IM_HANNING: + for (i=0; i<4; i++) { + kernelx[i]=KernelHanning((float)(xi+i-1-x)); + kernely[i]=KernelHanning((float)(yi+i-1-y)); + }//for i + break; + case IM_POWER: + for (i=0; i<4; i++) { + kernelx[i]=KernelPower((float)(xi+i-1-x)); + kernely[i]=KernelPower((float)(yi+i-1-y)); + }//for i + break; + }//switch + rr=gg=bb=aa=0; + if (((xi+2)=1 && ((yi+2)=1) && !IsIndexed()) { + //optimized interpolation (faster pixel reads) for RGB24 images with all pixels inside bounds + for (yii=yi-1; yii255) rr=255; if (rr<0) rr=0; color.rgbRed=(uint8_t) rr; + if (gg>255) gg=255; if (gg<0) gg=0; color.rgbGreen=(uint8_t) gg; + if (bb>255) bb=255; if (bb<0) bb=0; color.rgbBlue=(uint8_t) bb; +#if CXIMAGE_SUPPORT_ALPHA + if (aa>255) aa=255; if (aa<0) aa=0; color.rgbReserved=(uint8_t) aa; +#else + color.rgbReserved = 0; +#endif + return color; + case IM_LANCZOS: + //lanczos window (16*16) sinc interpolation + if (((xi+6)<0) || ((xi-5)>=head.biWidth) || ((yi+6)<0) || ((yi-5)>=head.biHeight)) { + //all points are outside bounds + switch (ofMethod) { + case OM_COLOR: case OM_TRANSPARENT: case OM_BACKGROUND: + //we don't need to interpolate anything with all points outside in this case + return GetPixelColorWithOverflow(-999, -999, ofMethod, rplColor); + break; + default: + //recalculate coordinates and use faster method later on + OverflowCoordinates(x,y,ofMethod); + xi=(int32_t)(x); if (x<0) xi--; //x and/or y have changed ... recalculate xi and yi + yi=(int32_t)(y); if (y<0) yi--; + }//switch + }//if + + for (xii=xi-5; xii=0) && ((yi+6)=0) && !IsIndexed()) { + //optimized interpolation (faster pixel reads) for RGB24 images with all pixels inside bounds + for (yii=yi-5; yii255) rr=255; if (rr<0) rr=0; color.rgbRed=(uint8_t) rr; + if (gg>255) gg=255; if (gg<0) gg=0; color.rgbGreen=(uint8_t) gg; + if (bb>255) bb=255; if (bb<0) bb=0; color.rgbBlue=(uint8_t) bb; +#if CXIMAGE_SUPPORT_ALPHA + if (aa>255) aa=255; if (aa<0) aa=0; color.rgbReserved=(uint8_t) aa; +#else + color.rgbReserved = 0; +#endif + return color; + }//switch +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Helper function for GetAreaColorInterpolated. + * Adds 'surf' portion of image pixel with color 'color' to (rr,gg,bb,aa). + */ +void CxImage::AddAveragingCont(RGBQUAD const &color, float const surf, float &rr, float &gg, float &bb, float &aa) +{ + rr+=color.rgbRed*surf; + gg+=color.rgbGreen*surf; + bb+=color.rgbBlue*surf; +#if CXIMAGE_SUPPORT_ALPHA + aa+=color.rgbReserved*surf; +#endif +} +//////////////////////////////////////////////////////////////////////////////// +/** + * This method is similar to GetPixelColorInterpolated, but this method also properly handles + * subsampling. + * If you need to sample original image with interval of more than 1 pixel (as when shrinking an image), + * you should use this method instead of GetPixelColorInterpolated or aliasing will occur. + * When area width and height are both less than pixel, this method gets pixel color by interpolating + * color of frame center with selected (inMethod) interpolation by calling GetPixelColorInterpolated. + * If width and height are more than 1, method calculates color by averaging color of pixels within area. + * Interpolation method is not used in this case. Pixel color is interpolated by averaging instead. + * If only one of both is more than 1, method uses combination of interpolation and averaging. + * Chosen interpolation method is used, but since it is averaged later on, there is little difference + * between IM_BILINEAR (perhaps best for this case) and better methods. IM_NEAREST_NEIGHBOUR again + * leads to aliasing artifacts. + * This method is a bit slower than GetPixelColorInterpolated and when aliasing is not a problem, you should + * simply use the later. + * + * \param xc, yc - center of (rectangular) area + * \param w, h - width and height of area + * \param inMethod - interpolation method that is used, when interpolation is used (see above) + * \param ofMethod - overflow method used when retrieving individual pixel colors + * \param rplColor - replacement colour to use, in OM_COLOR + * + * \author ***bd*** 2.2004 + */ +RGBQUAD CxImage::GetAreaColorInterpolated( + float const xc, float const yc, float const w, float const h, + InterpolationMethod const inMethod, + OverflowMethod const ofMethod, + RGBQUAD* const rplColor) +{ + RGBQUAD color; //calculated colour + + if (h<=1 && w<=1) { + //both width and height are less than one... we will use interpolation of center point + return GetPixelColorInterpolated(xc, yc, inMethod, ofMethod, rplColor); + } else { + //area is wider and/or taller than one pixel: + CxRect2 area(xc-w/2.0f, yc-h/2.0f, xc+w/2.0f, yc+h/2.0f); //area + int32_t xi1=(int32_t)(area.botLeft.x+0.49999999f); //low x + int32_t yi1=(int32_t)(area.botLeft.y+0.49999999f); //low y + + + int32_t xi2=(int32_t)(area.topRight.x+0.5f); //top x + int32_t yi2=(int32_t)(area.topRight.y+0.5f); //top y (for loops) + + float rr,gg,bb,aa; //red, green, blue and alpha components + rr=gg=bb=aa=0; + int32_t x,y; //loop counters + float s=0; //surface of all pixels + float cps; //surface of current crosssection + if (h>1 && w>1) { + //width and height of area are greater than one pixel, so we can employ "ordinary" averaging + CxRect2 intBL, intTR; //bottom left and top right intersection + intBL=area.CrossSection(CxRect2(((float)xi1)-0.5f, ((float)yi1)-0.5f, ((float)xi1)+0.5f, ((float)yi1)+0.5f)); + intTR=area.CrossSection(CxRect2(((float)xi2)-0.5f, ((float)yi2)-0.5f, ((float)xi2)+0.5f, ((float)yi2)+0.5f)); + float wBL, wTR, hBL, hTR; + wBL=intBL.Width(); //width of bottom left pixel-area intersection + hBL=intBL.Height(); //height of bottom left... + wTR=intTR.Width(); //width of top right... + hTR=intTR.Height(); //height of top right... + + AddAveragingCont(GetPixelColorWithOverflow(xi1,yi1,ofMethod,rplColor), wBL*hBL, rr, gg, bb, aa); //bottom left pixel + AddAveragingCont(GetPixelColorWithOverflow(xi2,yi1,ofMethod,rplColor), wTR*hBL, rr, gg, bb, aa); //bottom right pixel + AddAveragingCont(GetPixelColorWithOverflow(xi1,yi2,ofMethod,rplColor), wBL*hTR, rr, gg, bb, aa); //top left pixel + AddAveragingCont(GetPixelColorWithOverflow(xi2,yi2,ofMethod,rplColor), wTR*hTR, rr, gg, bb, aa); //top right pixel + //bottom and top row + for (x=xi1+1; x255) rr=255; if (rr<0) rr=0; color.rgbRed=(uint8_t) rr; + if (gg>255) gg=255; if (gg<0) gg=0; color.rgbGreen=(uint8_t) gg; + if (bb>255) bb=255; if (bb<0) bb=0; color.rgbBlue=(uint8_t) bb; +#if CXIMAGE_SUPPORT_ALPHA + if (aa>255) aa=255; if (aa<0) aa=0; color.rgbReserved=(uint8_t) aa; +#else + color.rgbReserved = 0; +#endif + }//if + return color; +} + +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBSpline(const float x) +{ + if (x>2.0f) return 0.0f; + // thanks to Kristian Kratzenstein + float a, b, c, d; + float xm1 = x - 1.0f; // Was calculatet anyway cause the "if((x-1.0f) < 0)" + float xp1 = x + 1.0f; + float xp2 = x + 2.0f; + + if ((xp2) <= 0.0f) a = 0.0f; else a = xp2*xp2*xp2; // Only float, not float -> double -> float + if ((xp1) <= 0.0f) b = 0.0f; else b = xp1*xp1*xp1; + if (x <= 0) c = 0.0f; else c = x*x*x; + if ((xm1) <= 0.0f) d = 0.0f; else d = xm1*xm1*xm1; + + return (0.16666666666666666667f * (a - (4.0f * b) + (6.0f * c) - (4.0f * d))); + + /* equivalent + if (x < -2.0) + return(0.0f); + if (x < -1.0) + return((2.0f+x)*(2.0f+x)*(2.0f+x)*0.16666666666666666667f); + if (x < 0.0) + return((4.0f+x*x*(-6.0f-3.0f*x))*0.16666666666666666667f); + if (x < 1.0) + return((4.0f+x*x*(-6.0f+3.0f*x))*0.16666666666666666667f); + if (x < 2.0) + return((2.0f-x)*(2.0f-x)*(2.0f-x)*0.16666666666666666667f); + return(0.0f); + */ +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Bilinear interpolation kernel: + \verbatim + / + | 1-t , if 0 <= t <= 1 + h(t) = | t+1 , if -1 <= t < 0 + | 0 , otherwise + \ + \endverbatim + * ***bd*** 2.2004 + */ +float CxImage::KernelLinear(const float t) +{ +// if (0<=t && t<=1) return 1-t; +// if (-1<=t && t<0) return 1+t; +// return 0; + + // + if (t < -1.0f) + return 0.0f; + if (t < 0.0f) + return 1.0f+t; + if (t < 1.0f) + return 1.0f-t; + return 0.0f; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Bicubic interpolation kernel (a=-1): + \verbatim + / + | 1-2|t|**2+|t|**3 , if |t| < 1 + h(t) = | 4-8|t|+5|t|**2-|t|**3 , if 1<=|t|<2 + | 0 , otherwise + \ + \endverbatim + * ***bd*** 2.2004 + */ +float CxImage::KernelCubic(const float t) +{ + float abs_t = (float)fabs(t); + float abs_t_sq = abs_t * abs_t; + if (abs_t<1) return 1-2*abs_t_sq+abs_t_sq*abs_t; + if (abs_t<2) return 4 - 8*abs_t +5*abs_t_sq - abs_t_sq*abs_t; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Bicubic kernel (for a=-1 it is the same as BicubicKernel): + \verbatim + / + | (a+2)|t|**3 - (a+3)|t|**2 + 1 , |t| <= 1 + h(t) = | a|t|**3 - 5a|t|**2 + 8a|t| - 4a , 1 < |t| <= 2 + | 0 , otherwise + \ + \endverbatim + * Often used values for a are -1 and -1/2. + */ +float CxImage::KernelGeneralizedCubic(const float t, const float a) +{ + float abs_t = (float)fabs(t); + float abs_t_sq = abs_t * abs_t; + if (abs_t<1) return (a+2)*abs_t_sq*abs_t - (a+3)*abs_t_sq + 1; + if (abs_t<2) return a*abs_t_sq*abs_t - 5*a*abs_t_sq + 8*a*abs_t - 4*a; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Lanczos windowed sinc interpolation kernel with radius r. + \verbatim + / + h(t) = | sinc(t)*sinc(t/r) , if |t| r) return 0; + if (t==0) return 1; + float pit=PI*t; + float pitd=pit/r; + return (float)((sin(pit)/pit) * (sin(pitd)/pitd)); +} + +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBox(const float x) +{ + if (x < -0.5f) + return 0.0f; + if (x < 0.5f) + return 1.0f; + return 0.0f; +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelHermite(const float x) +{ + if (x < -1.0f) + return 0.0f; + if (x < 0.0f) + return (-2.0f*x-3.0f)*x*x+1.0f; + if (x < 1.0f) + return (2.0f*x-3.0f)*x*x+1.0f; + return 0.0f; +// if (fabs(x)>1) return 0.0f; +// return(0.5f+0.5f*(float)cos(PI*x)); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelHanning(const float x) +{ + if (fabs(x)>1) return 0.0f; + return (0.5f+0.5f*(float)cos(PI*x))*((float)sin(PI*x)/(PI*x)); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelHamming(const float x) +{ + if (x < -1.0f) + return 0.0f; + if (x < 0.0f) + return 0.92f*(-2.0f*x-3.0f)*x*x+1.0f; + if (x < 1.0f) + return 0.92f*(2.0f*x-3.0f)*x*x+1.0f; + return 0.0f; +// if (fabs(x)>1) return 0.0f; +// return(0.54f+0.46f*(float)cos(PI*x)); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelSinc(const float x) +{ + if (x == 0.0) + return(1.0); + return((float)sin(PI*x)/(PI*x)); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBlackman(const float x) +{ + //if (fabs(x)>1) return 0.0f; + return (0.42f+0.5f*(float)cos(PI*x)+0.08f*(float)cos(2.0f*PI*x)); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBessel_J1(const float x) +{ + double p, q; + + register int32_t i; + + static const double + Pone[] = + { + 0.581199354001606143928050809e+21, + -0.6672106568924916298020941484e+20, + 0.2316433580634002297931815435e+19, + -0.3588817569910106050743641413e+17, + 0.2908795263834775409737601689e+15, + -0.1322983480332126453125473247e+13, + 0.3413234182301700539091292655e+10, + -0.4695753530642995859767162166e+7, + 0.270112271089232341485679099e+4 + }, + Qone[] = + { + 0.11623987080032122878585294e+22, + 0.1185770712190320999837113348e+20, + 0.6092061398917521746105196863e+17, + 0.2081661221307607351240184229e+15, + 0.5243710262167649715406728642e+12, + 0.1013863514358673989967045588e+10, + 0.1501793594998585505921097578e+7, + 0.1606931573481487801970916749e+4, + 0.1e+1 + }; + + p = Pone[8]; + q = Qone[8]; + for (i=7; i >= 0; i--) + { + p = p*x*x+Pone[i]; + q = q*x*x+Qone[i]; + } + return (float)(p/q); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBessel_P1(const float x) +{ + double p, q; + + register int32_t i; + + static const double + Pone[] = + { + 0.352246649133679798341724373e+5, + 0.62758845247161281269005675e+5, + 0.313539631109159574238669888e+5, + 0.49854832060594338434500455e+4, + 0.2111529182853962382105718e+3, + 0.12571716929145341558495e+1 + }, + Qone[] = + { + 0.352246649133679798068390431e+5, + 0.626943469593560511888833731e+5, + 0.312404063819041039923015703e+5, + 0.4930396490181088979386097e+4, + 0.2030775189134759322293574e+3, + 0.1e+1 + }; + + p = Pone[5]; + q = Qone[5]; + for (i=4; i >= 0; i--) + { + p = p*(8.0/x)*(8.0/x)+Pone[i]; + q = q*(8.0/x)*(8.0/x)+Qone[i]; + } + return (float)(p/q); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBessel_Q1(const float x) +{ + double p, q; + + register int32_t i; + + static const double + Pone[] = + { + 0.3511751914303552822533318e+3, + 0.7210391804904475039280863e+3, + 0.4259873011654442389886993e+3, + 0.831898957673850827325226e+2, + 0.45681716295512267064405e+1, + 0.3532840052740123642735e-1 + }, + Qone[] = + { + 0.74917374171809127714519505e+4, + 0.154141773392650970499848051e+5, + 0.91522317015169922705904727e+4, + 0.18111867005523513506724158e+4, + 0.1038187585462133728776636e+3, + 0.1e+1 + }; + + p = Pone[5]; + q = Qone[5]; + for (i=4; i >= 0; i--) + { + p = p*(8.0/x)*(8.0/x)+Pone[i]; + q = q*(8.0/x)*(8.0/x)+Qone[i]; + } + return (float)(p/q); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBessel_Order1(float x) +{ + float p, q; + + if (x == 0.0) + return (0.0f); + p = x; + if (x < 0.0) + x=(-x); + if (x < 8.0) + return(p*KernelBessel_J1(x)); + q = (float)sqrt(2.0f/(PI*x))*(float)(KernelBessel_P1(x)*(1.0f/sqrt(2.0f)*(sin(x)-cos(x)))-8.0f/x*KernelBessel_Q1(x)* + (-1.0f/sqrt(2.0f)*(sin(x)+cos(x)))); + if (p < 0.0f) + q = (-q); + return (q); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelBessel(const float x) +{ + if (x == 0.0f) + return(PI/4.0f); + return(KernelBessel_Order1(PI*x)/(2.0f*x)); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelGaussian(const float x) +{ + return (float)(exp(-2.0f*x*x)*0.79788456080287f/*sqrt(2.0f/PI)*/); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelQuadratic(const float x) +{ + if (x < -1.5f) + return(0.0f); + if (x < -0.5f) + return(0.5f*(x+1.5f)*(x+1.5f)); + if (x < 0.5f) + return(0.75f-x*x); + if (x < 1.5f) + return(0.5f*(x-1.5f)*(x-1.5f)); + return(0.0f); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelMitchell(const float x) +{ +#define KM_B (1.0f/3.0f) +#define KM_C (1.0f/3.0f) +#define KM_P0 (( 6.0f - 2.0f * KM_B ) / 6.0f) +#define KM_P2 ((-18.0f + 12.0f * KM_B + 6.0f * KM_C) / 6.0f) +#define KM_P3 (( 12.0f - 9.0f * KM_B - 6.0f * KM_C) / 6.0f) +#define KM_Q0 (( 8.0f * KM_B + 24.0f * KM_C) / 6.0f) +#define KM_Q1 ((-12.0f * KM_B - 48.0f * KM_C) / 6.0f) +#define KM_Q2 (( 6.0f * KM_B + 30.0f * KM_C) / 6.0f) +#define KM_Q3 (( -1.0f * KM_B - 6.0f * KM_C) / 6.0f) + + if (x < -2.0) + return(0.0f); + if (x < -1.0) + return(KM_Q0-x*(KM_Q1-x*(KM_Q2-x*KM_Q3))); + if (x < 0.0f) + return(KM_P0+x*x*(KM_P2-x*KM_P3)); + if (x < 1.0f) + return(KM_P0+x*x*(KM_P2+x*KM_P3)); + if (x < 2.0f) + return(KM_Q0+x*(KM_Q1+x*(KM_Q2+x*KM_Q3))); + return(0.0f); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelCatrom(const float x) +{ + if (x < -2.0) + return(0.0f); + if (x < -1.0) + return(0.5f*(4.0f+x*(8.0f+x*(5.0f+x)))); + if (x < 0.0) + return(0.5f*(2.0f+x*x*(-5.0f-3.0f*x))); + if (x < 1.0) + return(0.5f*(2.0f+x*x*(-5.0f+3.0f*x))); + if (x < 2.0) + return(0.5f*(4.0f+x*(-8.0f+x*(5.0f-x)))); + return(0.0f); +} +//////////////////////////////////////////////////////////////////////////////// +float CxImage::KernelPower(const float x, const float a) +{ + if (fabs(x)>1) return 0.0f; + return (1.0f - (float)fabs(pow(x,a))); +} +//////////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/DuiLib/3rd/CxImage/ximaiter.h b/DuiLib/3rd/CxImage/ximaiter.h new file mode 100644 index 0000000..b160b20 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximaiter.h @@ -0,0 +1,253 @@ +/* + * File: ImaIter.h + * Purpose: Declaration of the Platform Independent Image Base Class + * Author: Alejandro Aguilar Sierra + * Created: 1995 + * Copyright: (c) 1995, Alejandro Aguilar Sierra + * + * 07/08/2001 Davide Pizzolato - www.xdp.it + * - removed slow loops + * - added safe checks + * + * Permission is given by the author to freely redistribute and include + * this code in any program as int32_t as this credit is given where due. + * + * COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY + * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES + * THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE + * OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED + * CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT + * THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY + * SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL + * PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER + * THIS DISCLAIMER. + * + * Use at your own risk! + * ========================================================== + */ + +#if !defined(__ImaIter_h) +#define __ImaIter_h + +#include "ximage.h" +#include "ximadef.h" + +class CImageIterator +{ +friend class CxImage; +protected: + int32_t Itx, Ity; // Counters + int32_t Stepx, Stepy; + uint8_t* IterImage; // Image pointer + CxImage *ima; +public: + // Constructors + CImageIterator ( void ); + CImageIterator ( CxImage *image ); + operator CxImage* (); + + // Iterators + BOOL ItOK (); + void Reset (); + void Upset (); + void SetRow(uint8_t *buf, int32_t n); + void GetRow(uint8_t *buf, int32_t n); + uint8_t GetByte( ) { return IterImage[Itx]; } + void SetByte(uint8_t b) { IterImage[Itx] = b; } + uint8_t* GetRow(void); + uint8_t* GetRow(int32_t n); + BOOL NextRow(); + BOOL PrevRow(); + BOOL NextByte(); + BOOL PrevByte(); + + void SetSteps(int32_t x, int32_t y=0) { Stepx = x; Stepy = y; } + void GetSteps(int32_t *x, int32_t *y) { *x = Stepx; *y = Stepy; } + BOOL NextStep(); + BOOL PrevStep(); + + void SetY(int32_t y); /* AD - for interlace */ + int32_t GetY() {return Ity;} + BOOL GetCol(uint8_t* pCol, uint32_t x); + BOOL SetCol(uint8_t* pCol, uint32_t x); +}; + +///////////////////////////////////////////////////////////////////// +inline +CImageIterator::CImageIterator(void) +{ + ima = 0; + IterImage = 0; + Itx = Ity = 0; + Stepx = Stepy = 0; +} +///////////////////////////////////////////////////////////////////// +inline +CImageIterator::CImageIterator(CxImage *imageImpl): ima(imageImpl) +{ + if (ima) IterImage = ima->GetBits(); + Itx = Ity = 0; + Stepx = Stepy = 0; +} +///////////////////////////////////////////////////////////////////// +inline +CImageIterator::operator CxImage* () +{ + return ima; +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::ItOK () +{ + if (ima) return ima->IsInside(Itx, Ity); + else return FALSE; +} +///////////////////////////////////////////////////////////////////// +inline void CImageIterator::Reset() +{ + if (ima) IterImage = ima->GetBits(); + else IterImage=0; + Itx = Ity = 0; +} +///////////////////////////////////////////////////////////////////// +inline void CImageIterator::Upset() +{ + Itx = 0; + Ity = ima->GetHeight()-1; + IterImage = ima->GetBits() + ima->GetEffWidth()*(ima->GetHeight()-1); +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::NextRow() +{ + if (++Ity >= (int32_t)ima->GetHeight()) return 0; + IterImage += ima->GetEffWidth(); + return 1; +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::PrevRow() +{ + if (--Ity < 0) return 0; + IterImage -= ima->GetEffWidth(); + return 1; +} +/* AD - for interlace */ +inline void CImageIterator::SetY(int32_t y) +{ + if ((y < 0) || (y > (int32_t)ima->GetHeight())) return; + Ity = y; + IterImage = ima->GetBits() + ima->GetEffWidth()*y; +} +///////////////////////////////////////////////////////////////////// +inline void CImageIterator::SetRow(uint8_t *buf, int32_t n) +{ + if (n<0) n = (int32_t)ima->GetEffWidth(); + else n = min(n,(int32_t)ima->GetEffWidth()); + + if ((IterImage!=NULL)&&(buf!=NULL)&&(n>0)) memcpy(IterImage,buf,n); +} +///////////////////////////////////////////////////////////////////// +inline void CImageIterator::GetRow(uint8_t *buf, int32_t n) +{ + if ((IterImage!=NULL)&&(buf!=NULL)&&(n>0)) + memcpy(buf,IterImage,min(n,(int32_t)ima->GetEffWidth())); +} +///////////////////////////////////////////////////////////////////// +inline uint8_t* CImageIterator::GetRow() +{ + return IterImage; +} +///////////////////////////////////////////////////////////////////// +inline uint8_t* CImageIterator::GetRow(int32_t n) +{ + SetY(n); + return IterImage; +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::NextByte() +{ + if (++Itx < (int32_t)ima->GetEffWidth()) return 1; + else + if (++Ity < (int32_t)ima->GetHeight()){ + IterImage += ima->GetEffWidth(); + Itx = 0; + return 1; + } else + return 0; +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::PrevByte() +{ + if (--Itx >= 0) return 1; + else + if (--Ity >= 0){ + IterImage -= ima->GetEffWidth(); + Itx = 0; + return 1; + } else + return 0; +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::NextStep() +{ + Itx += Stepx; + if (Itx < (int32_t)ima->GetEffWidth()) return 1; + else { + Ity += Stepy; + if (Ity < (int32_t)ima->GetHeight()){ + IterImage += ima->GetEffWidth(); + Itx = 0; + return 1; + } else + return 0; + } +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::PrevStep() +{ + Itx -= Stepx; + if (Itx >= 0) return 1; + else { + Ity -= Stepy; + if (Ity >= 0 && Ity < (int32_t)ima->GetHeight()) { + IterImage -= ima->GetEffWidth(); + Itx = 0; + return 1; + } else + return 0; + } +} +///////////////////////////////////////////////////////////////////// +inline BOOL CImageIterator::GetCol(uint8_t* pCol, uint32_t x) +{ + if ((pCol==0)||(ima->GetBpp()<8)||(x>=ima->GetWidth())) + return 0; + uint32_t h = ima->GetHeight(); + //uint32_t line = ima->GetEffWidth(); + uint8_t bytes = (uint8_t)(ima->GetBpp()>>3); + uint8_t* pSrc; + for (uint32_t y=0;yGetBits(y) + x*bytes; + for (uint8_t w=0;wGetBpp()<8)||(x>=ima->GetWidth())) + return 0; + uint32_t h = ima->GetHeight(); + //uint32_t line = ima->GetEffWidth(); + uint8_t bytes = (uint8_t)(ima->GetBpp()>>3); + uint8_t* pSrc; + for (uint32_t y=0;yGetBits(y) + x*bytes; + for (uint8_t w=0;wnumcmpts_ > 64 || image->numcmpts_ < 0) + cx_throw("error: too many components"); + + // 01/Jan/2005: Always force conversion to sRGB. Seems to be required for many types of JPEG2000 file. + // if (depth!=1 && depth!=4 && depth!=8) + if (image->numcmpts_>=3 && depth <=8) + { + jas_image_t *newimage; + jas_cmprof_t *outprof; + //jas_eprintf("forcing conversion to sRGB\n"); + outprof = jas_cmprof_createfromclrspc(JAS_CLRSPC_SRGB); + if (!outprof) { + cx_throw("cannot create sRGB profile"); + } + newimage = jas_image_chclrspc(image, outprof, JAS_CMXFORM_INTENT_PER); + if (!newimage) { + jas_cmprof_destroy(outprof); // 01/Jan/2005: Destroy color profile on error. + cx_throw("cannot convert to sRGB"); + } + jas_image_destroy(image); + jas_cmprof_destroy(outprof); + image = newimage; + } + + bufs = (jas_matrix_t **)calloc(image->numcmpts_, sizeof(jas_matrix_t**)); + for (i = 0; i < image->numcmpts_; ++i) { + bufs[i] = jas_matrix_create(1, w); + if (!bufs[i]) { + cx_throw("error: cannot allocate memory"); + } + } + + int32_t nshift = (depth>8) ? (depth-8) : 0; + + if (image->numcmpts_==3 && + image->cmpts_[0]->width_ == image->cmpts_[1]->width_ && + image->cmpts_[1]->width_ == image->cmpts_[2]->width_ && + image->cmpts_[0]->height_ == image->cmpts_[1]->height_ && + image->cmpts_[1]->height_ == image->cmpts_[2]->height_ && + image->cmpts_[0]->prec_ == image->cmpts_[1]->prec_ && + image->cmpts_[1]->prec_ == image->cmpts_[2]->prec_ ) + { + + if(!Create(w,h,24,fmt)) + cx_throw(""); + + RGBQUAD c; + for (y=0; ynumcmpts_; ++cmptno) { + jas_image_readcmpt(image, cmptno, 0, y, w, 1, bufs[cmptno]); + } + + for (x=0; x>nshift)); + c.rgbGreen = (uint8_t)((jas_matrix_getv(bufs[1], x)>>nshift)); + c.rgbBlue = (uint8_t)((jas_matrix_getv(bufs[2], x)>>nshift)); + SetPixelColor(x,h-1-y,c); + } + } + } else { + info.nNumFrames = image->numcmpts_; + if ((info.nFrame<0)||(info.nFrame>=info.nNumFrames)){ + cx_throw("wrong frame!"); + } + for (cmptno=0; cmptno<=info.nFrame; cmptno++) { + w = jas_image_cmptwidth(image,cmptno); + h = jas_image_cmptheight(image,cmptno); + depth = jas_image_cmptprec(image,cmptno); + if (depth>8) depth=8; + if(!Create(w,h,depth,imagetype)) + cx_throw(""); + SetGrayPalette(); + for (y=0; y>nshift))); + } + } + } + } + + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + if (info.nEscape == -1 && fmt>0){ + error = 0; + } else { + error = 1; + } + } + + if (bufs) { + for (i = 0; i < image->numcmpts_; ++i){ if (bufs[i]) jas_matrix_destroy(bufs[i]);} + free(bufs); + } + jas_cleanup(); + if (image) jas_image_destroy(image); + if (in) jas_stream_close(in); + return (error==0); +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageJAS::Encode(CxFile * hFile, uint32_t imagetype) +{ + if (EncodeSafeCheck(hFile)) return false; + + if (head.biClrUsed!=0 && !IsGrayScale()){ + strcpy(info.szLastError,"JasPer can save only RGB or GrayScale images"); + return false; + } + + jas_image_t *image=0; + jas_stream_t *out=0; + jas_matrix_t *cmpts[3]; + int32_t x,y,yflip,error=0; + uint_fast16_t cmptno, numcmpts=0; + jas_image_cmptparm_t cmptparms[3], *cmptparm; + + cx_try { + + if (jas_init()) + cx_throw("cannot initialize jasper"); + + out = jas_stream_fdopen(0, "wb"); + if (!out) + cx_throw("error: cannot open standard output"); + + CxFileJas src(hFile,out); + + numcmpts = head.biClrUsed==0 ? 3 : 1; + + for (cmptno = 0, cmptparm = cmptparms; cmptno < numcmpts; ++cmptno, ++cmptparm) { + cmptparm->tlx = 0; + cmptparm->tly = 0; + cmptparm->hstep = 1; + cmptparm->vstep = 1; + cmptparm->width = head.biWidth; + cmptparm->height = head.biHeight; + cmptparm->prec = 8; + cmptparm->sgnd = false; + } + + /* Create image object. */ + image = jas_image_create(numcmpts, cmptparms, JAS_CLRSPC_UNKNOWN); + if (!image) + cx_throw("error : jas_image_create"); + + if (numcmpts == 3) { + jas_image_setclrspc(image, JAS_CLRSPC_SRGB); + jas_image_setcmpttype(image, 0, + JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R)); + jas_image_setcmpttype(image, 1, + JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G)); + jas_image_setcmpttype(image, 2, + JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B)); + } else { + jas_image_setclrspc(image, JAS_CLRSPC_SGRAY); + jas_image_setcmpttype(image, 0, + JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_GRAY_Y)); + } + + + for (x = 0; x < numcmpts; ++x) { cmpts[x] = 0; } + /* Create temporary matrices to hold component data. */ + for (x = 0; x < numcmpts; ++x) { + cmpts[x] = jas_matrix_create(1, head.biWidth); + if (!cmpts[x]) { + cx_throw("error : can't allocate memory"); + } + } + + RGBQUAD c; + for (y = 0; y < head.biHeight; ++y) { + for (x = 0; x < head.biWidth; ++x) { + if (head.biClrUsed==0){ + c = GetPixelColor(x,y); + jas_matrix_setv(cmpts[0], x, c.rgbRed); + jas_matrix_setv(cmpts[1], x, c.rgbGreen); + jas_matrix_setv(cmpts[2], x, c.rgbBlue); + } else { + jas_matrix_setv(cmpts[0], x, GetPixelIndex(x,y)); + } + } + yflip = head.biHeight - 1 - y; + for (cmptno = 0; cmptno < numcmpts; ++cmptno) { + if (jas_image_writecmpt(image, cmptno, 0, yflip, head.biWidth, 1, cmpts[cmptno])) { + cx_throw("error : jas_image_writecmpt"); + } + } + } + + char szfmt[4]; + *szfmt = '\0'; +#if CXIMAGE_SUPPORT_JP2 + if (imagetype == CXIMAGE_FORMAT_JP2) strcpy(szfmt,"jp2"); +#endif +#if CXIMAGE_SUPPORT_JPC + if (imagetype == CXIMAGE_FORMAT_JPC) strcpy(szfmt,"jpc"); +#endif +#if CXIMAGE_SUPPORT_RAS + if (imagetype == CXIMAGE_FORMAT_RAS) strcpy(szfmt,"ras"); +#endif +#if CXIMAGE_SUPPORT_PNM + if (imagetype == CXIMAGE_FORMAT_PNM) strcpy(szfmt,"pnm"); +#endif +#if CXIMAGE_SUPPORT_PGX + if (imagetype == CXIMAGE_FORMAT_PGX){ + strcpy(szfmt,"pgx"); + if (head.biClrUsed==0) cx_throw("PGX can save only GrayScale images"); + } +#endif + int32_t outfmt = jas_image_strtofmt(szfmt); + + char szoutopts[32]; + sprintf(szoutopts,"rate=%.3f", info.fQuality/100.0f); + + if (jas_image_encode(image, out, outfmt, szoutopts)) { + cx_throw("error: cannot encode image"); + } + jas_stream_flush(out); + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + error = 1; + } + + for (x = 0; x < numcmpts; ++x) { if (cmpts[x]) { jas_matrix_destroy(cmpts[x]); } } + jas_cleanup(); + if (image) jas_image_destroy(image); + if (out) jas_stream_close(out); + + return (error==0); +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_JASPER + diff --git a/DuiLib/3rd/CxImage/ximajas.h b/DuiLib/3rd/CxImage/ximajas.h new file mode 100644 index 0000000..d4a4e39 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximajas.h @@ -0,0 +1,88 @@ +/* + * File: ximajas.h + * Purpose: Jasper Image Class Loader and Writer + */ +/* ========================================================== + * CxImageJAS (c) 12/Apr/2003 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * + * based on JasPer Copyright (c) 2001-2003 Michael David Adams - All rights reserved. + * ========================================================== + */ +#if !defined(__ximaJAS_h) +#define __ximaJAS_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_JASPER + +#ifdef _LINUX + #include +#else + #include "../jasper/include/jasper/jasper.h" +#endif + +class CxImageJAS: public CxImage +{ +public: + CxImageJAS(): CxImage((uint32_t)0) {} // cast to uint32_t + +// bool Load(const TCHAR * imageFileName){ return CxImage::Load(imageFileName,0);} +// bool Save(const TCHAR * imageFileName){ return CxImage::Save(imageFileName,0);} + bool Decode(CxFile * hFile, uint32_t imagetype = 0); + bool Decode(FILE *hFile, uint32_t imagetype = 0) { CxIOFile file(hFile); return Decode(&file,imagetype); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile, uint32_t imagetype = 0); + bool Encode(FILE *hFile, uint32_t imagetype = 0) { CxIOFile file(hFile); return Encode(&file,imagetype); } +#endif // CXIMAGE_SUPPORT_ENCODE +protected: + + class CxFileJas + { + public: + CxFileJas(CxFile* pFile,jas_stream_t *stream) + { + if (stream->obj_) jas_free(stream->obj_); + stream->obj_ = pFile; + + // - cannot set the stream->ops_->functions here, + // because this overwrites a static structure in the Jasper library. + // This structure is used by Jasper for internal operations too, e.g. tempfile. + // However the ops_ pointer in the stream can be overwritten. + + //stream->ops_->close_ = JasClose; + //stream->ops_->read_ = JasRead; + //stream->ops_->seek_ = JasSeek; + //stream->ops_->write_ = JasWrite; + + jas_stream_CxFile.close_ = JasClose; + jas_stream_CxFile.read_ = JasRead; + jas_stream_CxFile.seek_ = JasSeek; + jas_stream_CxFile.write_ = JasWrite; + + stream->ops_ = &jas_stream_CxFile; + + // - end + } + static int32_t JasRead(jas_stream_obj_t *obj, char *buf, int32_t cnt) + { return ((CxFile*)obj)->Read(buf,1,cnt); } + static int32_t JasWrite(jas_stream_obj_t *obj, char *buf, int32_t cnt) + { return ((CxFile*)obj)->Write(buf,1,cnt); } + static long JasSeek(jas_stream_obj_t *obj, long offset, int32_t origin) + { return ((CxFile*)obj)->Seek(offset,origin); } + static int32_t JasClose(jas_stream_obj_t * /*obj*/) + { return 1; } + + // +private: + jas_stream_ops_t jas_stream_CxFile; + // - end + + }; + +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximajbg.cpp b/DuiLib/3rd/CxImage/ximajbg.cpp new file mode 100644 index 0000000..0b441d0 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximajbg.cpp @@ -0,0 +1,174 @@ +/* + * File: ximajbg.cpp + * Purpose: Platform Independent JBG Image Class Loader and Writer + * 18/Aug/2002 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximajbg.h" + +#if CXIMAGE_SUPPORT_JBG + +#include "ximaiter.h" + +#define JBIG_BUFSIZE 8192 + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageJBG::Decode(CxFile *hFile) +{ + if (hFile == NULL) return false; + + struct jbg_dec_state jbig_state; + uint32_t xmax = 4294967295UL, ymax = 4294967295UL; + uint32_t len, cnt; + uint8_t *buffer=0,*p; + int32_t result; + + cx_try + { + jbg_dec_init(&jbig_state); + jbg_dec_maxsize(&jbig_state, xmax, ymax); + + buffer = (uint8_t*)malloc(JBIG_BUFSIZE); + if (!buffer) cx_throw("Sorry, not enough memory available!"); + + result = JBG_EAGAIN; + do { + len = hFile->Read(buffer, 1, JBIG_BUFSIZE); + if (!len) break; + cnt = 0; + p = buffer; + while (len > 0 && (result == JBG_EAGAIN || result == JBG_EOK)) { + result = jbg_dec_in(&jbig_state, p, len, &cnt); + p += cnt; + len -= cnt; + } + } while (result == JBG_EAGAIN || result == JBG_EOK); + + if (hFile->Error()) + cx_throw("Problem while reading input file"); + if (result != JBG_EOK && result != JBG_EOK_INTR) + cx_throw("Problem with input file"); + + int32_t w, h, bpp, planes, ew; + + w = jbg_dec_getwidth(&jbig_state); + h = jbg_dec_getheight(&jbig_state); + planes = jbg_dec_getplanes(&jbig_state); + bpp = (planes+7)>>3; + ew = (w + 7)>>3; + + if (info.nEscape == -1){ + head.biWidth = w; + head.biHeight= h; + info.dwType = CXIMAGE_FORMAT_JBG; + cx_throw("output dimensions returned"); + } + + switch (planes){ + case 1: + { + uint8_t* binary_image = jbg_dec_getimage(&jbig_state, 0); + + if (!Create(w,h,1,CXIMAGE_FORMAT_JBG)) + cx_throw(""); + + SetPaletteColor(0,255,255,255); + SetPaletteColor(1,0,0,0); + + CImageIterator iter(this); + iter.Upset(); + for (int32_t i=0;i>3; + ew = (w + 7)>>3; + + uint8_t mask; + RGBQUAD *rgb = GetPalette(); + if (CompareColors(&rgb[0],&rgb[1])<0) mask=255; else mask=0; + + uint8_t *buffer = (uint8_t*)malloc(ew*h*2); + if (!buffer) { + strcpy(info.szLastError,"Sorry, not enough memory available!"); + return false; + } + + for (y=0; yError()){ + strcpy(info.szLastError,"Problem while writing JBG file"); + return false; + } + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_JBG + diff --git a/DuiLib/3rd/CxImage/ximajbg.h b/DuiLib/3rd/CxImage/ximajbg.h new file mode 100644 index 0000000..55a6d76 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximajbg.h @@ -0,0 +1,44 @@ +/* + * File: ximajbg.h + * Purpose: JBG Image Class Loader and Writer + */ +/* ========================================================== + * CxImageJBG (c) 18/Aug/2002 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * + * based on LIBJBG Copyright (c) 2002, Markus Kuhn - All rights reserved. + * ========================================================== + */ +#if !defined(__ximaJBG_h) +#define __ximaJBG_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_JBG + +extern "C" { +#include "../jbig/jbig.h" +}; + +class CxImageJBG: public CxImage +{ +public: + CxImageJBG(): CxImage(CXIMAGE_FORMAT_JBG) {} + +// bool Load(const TCHAR * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_JBG);} +// bool Save(const TCHAR * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_JBG);} + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +#endif // CXIMAGE_SUPPORT_ENCODE +protected: + static void jbig_data_out(uint8_t *buffer, uint32_t len, void *file) + {((CxFile*)file)->Write(buffer,len,1);} +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximajpg.cpp b/DuiLib/3rd/CxImage/ximajpg.cpp new file mode 100644 index 0000000..3b3668b --- /dev/null +++ b/DuiLib/3rd/CxImage/ximajpg.cpp @@ -0,0 +1,542 @@ +/* + * File: ximajpg.cpp + * Purpose: Platform Independent JPEG Image Class Loader and Writer + * 07/Aug/2001 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximajpg.h" + +#if CXIMAGE_SUPPORT_JPG + +#ifdef _LINUX + #include +#else + #include "../jpeg/jmorecfg.h" +#endif + +#include "ximaiter.h" + +#include + +struct jpg_error_mgr { + struct jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ + char* buffer; /* error message */ +}; +typedef jpg_error_mgr *jpg_error_ptr; + +//////////////////////////////////////////////////////////////////////////////// +// Here's the routine that will replace the standard error_exit method: +//////////////////////////////////////////////////////////////////////////////// +static void +ima_jpeg_error_exit (j_common_ptr cinfo) +{ + /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */ + jpg_error_ptr myerr = (jpg_error_ptr) cinfo->err; + /* Create the message */ + myerr->pub.format_message (cinfo, myerr->buffer); + /* Send it to stderr, adding a newline */ + /* Return control to the setjmp point */ + longjmp(myerr->setjmp_buffer, 1); +} +//////////////////////////////////////////////////////////////////////////////// +CxImageJPG::CxImageJPG(): CxImage(CXIMAGE_FORMAT_JPG) +{ +#if CXIMAGEJPG_SUPPORT_EXIF + m_exif = NULL; + memset(&info.ExifInfo, 0, sizeof(EXIFINFO)); +#endif +} +//////////////////////////////////////////////////////////////////////////////// +CxImageJPG::~CxImageJPG() +{ +#if CXIMAGEJPG_SUPPORT_EXIF + if (m_exif) delete m_exif; +#endif +} +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGEJPG_SUPPORT_EXIF +bool CxImageJPG::DecodeExif(CxFile * hFile) +{ + m_exif = new CxExifInfo(&info.ExifInfo); + if (m_exif){ + int32_t pos=hFile->Tell(); + m_exif->DecodeExif(hFile); + hFile->Seek(pos,SEEK_SET); + return m_exif->m_exifinfo->IsExif; + } else { + return false; + } +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImageJPG::GetExifThumbnail(const TCHAR *filename, const TCHAR *outname, int32_t type) +{ + CxIOFile file; + if (!file.Open(filename, _T("rb"))) return false; + CxExifInfo exif(&info.ExifInfo); + exif.DecodeExif(&file); + if (info.ExifInfo.IsExif && info.ExifInfo.ThumbnailPointer && info.ExifInfo.ThumbnailSize > 0) + { // have a thumbnail - check whether it needs rotating or resizing + // TODO: Write a fast routine to read the jpeg header to get the width and height + CxImage image(info.ExifInfo.ThumbnailPointer, info.ExifInfo.ThumbnailSize, CXIMAGE_FORMAT_JPG); + if (image.IsValid()) + { + if (image.GetWidth() > 256 || image.GetHeight() > 256) + { // resize the image +// float amount = 256.0f / max(image.GetWidth(), image.GetHeight()); +// image.Resample((int32_t)(image.GetWidth() * amount), (int32_t)(image.GetHeight() * amount), 0); + } +#if CXIMAGE_SUPPORT_TRANSFORMATION + if (info.ExifInfo.Orientation != 1) + image.RotateExif(info.ExifInfo.Orientation); +#endif +#if CXIMAGE_SUPPORT_ENCODE + return image.Save(outname, CXIMAGE_FORMAT_JPG); +#endif + } + // nice and fast, but we can't resize :( + /* + FILE *hFileWrite; + if ((hFileWrite=fopen(outname, "wb")) != NULL) + { + fwrite(m_exifinfo.ThumbnailPointer, m_exifinfo.ThumbnailSize, 1, hFileWrite); + fclose(hFileWrite); + return true; + }*/ + } + return false; +} +#endif //CXIMAGEJPG_SUPPORT_EXIF +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageJPG::Decode(CxFile * hFile) +{ + + bool is_exif = false; +#if CXIMAGEJPG_SUPPORT_EXIF + is_exif = DecodeExif(hFile); +#endif + + CImageIterator iter(this); + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. */ + struct jpg_error_mgr jerr; + jerr.buffer=info.szLastError; + /* More stuff */ + JSAMPARRAY buffer; /* Output row buffer */ + int32_t row_stride; /* physical row width in output buffer */ + + /* In this example we want to open the input file before doing anything else, + * so that the setjmp() error recovery below can assume the file is open. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to read binary files. + */ + + /* Step 1: allocate and initialize JPEG decompression object */ + /* We set up the normal JPEG error routines, then override error_exit. */ + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = ima_jpeg_error_exit; + + CxFileJpg src(hFile); + + /* Establish the setjmp return context for my_error_exit to use. */ + if (setjmp(jerr.setjmp_buffer)) { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + jpeg_destroy_decompress(&cinfo); + return 0; + } + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&cinfo); + + /* Step 2: specify data source (eg, a file) */ + //jpeg_stdio_src(&cinfo, infile); + cinfo.src = &src; + + /* Step 3: read file parameters with jpeg_read_header() */ + (void) jpeg_read_header(&cinfo, TRUE); + + /* Step 4 handle decoder options*/ + uint32_t dwCodecOptions = GetCodecOption(CXIMAGE_FORMAT_JPG); //[nm_114] + if ((dwCodecOptions & DECODE_GRAYSCALE) != 0) + cinfo.out_color_space = JCS_GRAYSCALE; + if ((dwCodecOptions & DECODE_QUANTIZE) != 0) { + cinfo.quantize_colors = TRUE; + cinfo.desired_number_of_colors = GetJpegQuality(); + } + if ((dwCodecOptions & DECODE_DITHER) != 0) + cinfo.dither_mode = m_nDither; + if ((dwCodecOptions & DECODE_ONEPASS) != 0) + cinfo.two_pass_quantize = FALSE; + if ((dwCodecOptions & DECODE_NOSMOOTH) != 0) + cinfo.do_fancy_upsampling = FALSE; + +//: Load true color images as RGB (no quantize) +/* Step 4: set parameters for decompression */ +/* if (cinfo.jpeg_color_space!=JCS_GRAYSCALE) { + * cinfo.quantize_colors = TRUE; + * cinfo.desired_number_of_colors = 128; + *} + */ // + + cinfo.scale_num = 1; + // Set the scale + cinfo.scale_denom = GetJpegScale(); + + // Borrowed the idea from GIF implementation + if (info.nEscape == -1) { + // Return output dimensions only + jpeg_calc_output_dimensions(&cinfo); + head.biWidth = cinfo.output_width; + head.biHeight = cinfo.output_height; + info.dwType = CXIMAGE_FORMAT_JPG; + jpeg_destroy_decompress(&cinfo); + return true; + } + + /* Step 5: Start decompressor */ + jpeg_start_decompress(&cinfo); + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + */ + //Create the image using output dimensions + //Create(cinfo.image_width, cinfo.image_height, 8*cinfo.output_components, CXIMAGE_FORMAT_JPG); + Create(cinfo.output_width, cinfo.output_height, 8*cinfo.output_components, CXIMAGE_FORMAT_JPG); + + if (!pDib) longjmp(jerr.setjmp_buffer, 1); // check if the image has been created + + if (is_exif){ +#if CXIMAGEJPG_SUPPORT_EXIF + if ((info.ExifInfo.Xresolution != 0.0) && (info.ExifInfo.ResolutionUnit != 0)) + SetXDPI((int32_t)(info.ExifInfo.Xresolution/info.ExifInfo.ResolutionUnit)); + if ((info.ExifInfo.Yresolution != 0.0) && (info.ExifInfo.ResolutionUnit != 0)) + SetYDPI((int32_t)(info.ExifInfo.Yresolution/info.ExifInfo.ResolutionUnit)); +#endif + } else { + switch (cinfo.density_unit) { + case 0: // [andy] fix for aspect ratio... + if((cinfo.Y_density > 0) && (cinfo.X_density > 0)){ + SetYDPI((int32_t)(GetXDPI()*(float(cinfo.Y_density)/float(cinfo.X_density)))); + } + break; + case 2: // [andy] fix: cinfo.X/Y_density is pixels per centimeter + SetXDPI((int32_t)floor(cinfo.X_density * 2.54 + 0.5)); + SetYDPI((int32_t)floor(cinfo.Y_density * 2.54 + 0.5)); + break; + default: + SetXDPI(cinfo.X_density); + SetYDPI(cinfo.Y_density); + } + } + + if (cinfo.out_color_space==JCS_GRAYSCALE){ + SetGrayPalette(); + head.biClrUsed =256; + } else { + if (cinfo.quantize_colors){ + SetPalette(cinfo.actual_number_of_colors, cinfo.colormap[0], cinfo.colormap[1], cinfo.colormap[2]); + head.biClrUsed=cinfo.actual_number_of_colors; + } else { + head.biClrUsed=0; + } + } + + /* JSAMPLEs per row in output buffer */ + row_stride = cinfo.output_width * cinfo.output_components; + + /* Make a one-row-high sample array that will go away when done with image */ + buffer = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + iter.Upset(); + while (cinfo.output_scanline < cinfo.output_height) { + + if (info.nEscape) longjmp(jerr.setjmp_buffer, 1); // - cancel decoding + + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + // info.nProgress = (int32_t)(100*cinfo.output_scanline/cinfo.output_height); + // Step 6a: CMYK->RGB */ + if ((cinfo.num_components==4)&&(cinfo.quantize_colors==FALSE)){ + uint8_t k,*dst,*src; + dst=iter.GetRow(); + src=buffer[0]; + for(int32_t x3=0,x4=0; x3<(int32_t)info.dwEffWidth && x4 Step 7A: Swap red and blue components + // not necessary if swapped red and blue definition in jmorecfg.h;ln322 + if ((cinfo.num_components==3)&&(cinfo.quantize_colors==FALSE)){ + uint8_t* r0=GetBits(); + for(int32_t y=0;y - cancel decoding + RGBtoBGR(r0,3*head.biWidth); + r0+=info.dwEffWidth; + } + } + + /* Step 8: Release JPEG decompression object */ + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_decompress(&cinfo); + + /* At this point you may want to check to see whether any corrupt-data + * warnings occurred (test whether jerr.pub.num_warnings is nonzero). + */ + + /* And we're done! */ + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageJPG::Encode(CxFile * hFile) +{ + if (EncodeSafeCheck(hFile)) return false; + + if (head.biClrUsed!=0 && !IsGrayScale()){ + strcpy(info.szLastError,"JPEG can save only RGB or GreyScale images"); + return false; + } + + // necessary for EXIF, and for roll backs + int32_t pos=hFile->Tell(); + + /* This struct contains the JPEG compression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + * It is possible to have several such structures, representing multiple + * compression/decompression processes, in existence at once. We refer + * to any one struct (and its associated working data) as a "JPEG object". + */ + struct jpeg_compress_struct cinfo; + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as int32_t as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + //struct jpeg_error_mgr jerr; + /* We use our private extension JPEG error handler. */ + struct jpg_error_mgr jerr; + jerr.buffer=info.szLastError; + /* More stuff */ + int32_t row_stride; /* physical row width in image buffer */ + JSAMPARRAY buffer; /* Output row buffer */ + + /* Step 1: allocate and initialize JPEG compression object */ + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + //cinfo.err = jpeg_std_error(&jerr); + /* We set up the normal JPEG error routines, then override error_exit. */ + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = ima_jpeg_error_exit; + + /* Establish the setjmp return context for my_error_exit to use. */ + if (setjmp(jerr.setjmp_buffer)) { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + strcpy(info.szLastError, jerr.buffer); // + jpeg_destroy_compress(&cinfo); + return 0; + } + + /* Now we can initialize the JPEG compression object. */ + jpeg_create_compress(&cinfo); + /* Step 2: specify data destination (eg, a file) */ + /* Note: steps 2 and 3 can be done in either order. */ + /* Here we use the library-supplied code to send compressed data to a + * stdio stream. You can also write your own code to do something else. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to write binary files. + */ + + //jpeg_stdio_dest(&cinfo, outfile); + CxFileJpg dest(hFile); + cinfo.dest = &dest; + + /* Step 3: set parameters for compression */ + /* First we supply a description of the input image. + * Four fields of the cinfo struct must be filled in: + */ + cinfo.image_width = GetWidth(); // image width and height, in pixels + cinfo.image_height = GetHeight(); + + if (IsGrayScale()){ + cinfo.input_components = 1; // # of color components per pixel + cinfo.in_color_space = JCS_GRAYSCALE; /* colorspace of input image */ + } else { + cinfo.input_components = 3; // # of color components per pixel + cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ + } + + /* Now use the library's routine to set default compression parameters. + * (You must set at least cinfo.in_color_space before calling this, + * since the defaults depend on the source color space.) + */ + jpeg_set_defaults(&cinfo); + /* Now you can set any non-default parameters you wish to. + * Here we just illustrate the use of quality (quantization table) scaling: + */ + + uint32_t dwCodecOptions = GetCodecOption(CXIMAGE_FORMAT_JPG); //[nm_114] +//#ifdef C_ARITH_CODING_SUPPORTED + if ((dwCodecOptions & ENCODE_ARITHMETIC) != 0) + cinfo.arith_code = TRUE; +//#endif + +//#ifdef ENTROPY_OPT_SUPPORTED + if ((dwCodecOptions & ENCODE_OPTIMIZE) != 0) + cinfo.optimize_coding = TRUE; +//#endif + + if ((dwCodecOptions & ENCODE_GRAYSCALE) != 0) + jpeg_set_colorspace(&cinfo, JCS_GRAYSCALE); + + if ((dwCodecOptions & ENCODE_SMOOTHING) != 0) + cinfo.smoothing_factor = m_nSmoothing; + + jpeg_set_quality(&cinfo, GetJpegQuality(), (dwCodecOptions & ENCODE_BASELINE) != 0); + +//#ifdef C_PROGRESSIVE_SUPPORTED + if ((dwCodecOptions & ENCODE_PROGRESSIVE) != 0) + jpeg_simple_progression(&cinfo); +//#endif + +#ifdef C_LOSSLESS_SUPPORTED + if ((dwCodecOptions & ENCODE_LOSSLESS) != 0) + jpeg_simple_lossless(&cinfo, m_nPredictor, m_nPointTransform); +#endif + + //SetCodecOption(ENCODE_SUBSAMPLE_444 | GetCodecOption(CXIMAGE_FORMAT_JPG),CXIMAGE_FORMAT_JPG); + + // 2x2, 1x1, 1x1 (4:1:1) : High (default sub sampling) + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 2; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + + if ((dwCodecOptions & ENCODE_SUBSAMPLE_422) != 0){ + // 2x1, 1x1, 1x1 (4:2:2) : Medium + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 1; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + } + + if ((dwCodecOptions & ENCODE_SUBSAMPLE_444) != 0){ + // 1x1 1x1 1x1 (4:4:4) : None + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + } + + cinfo.density_unit=1; + cinfo.X_density=(uint16_t)GetXDPI(); + cinfo.Y_density=(uint16_t)GetYDPI(); + + /* Step 4: Start compressor */ + /* TRUE ensures that we will write a complete interchange-JPEG file. + * Pass TRUE unless you are very sure of what you're doing. + */ + jpeg_start_compress(&cinfo, TRUE); + + /* Step 5: while (scan lines remain to be written) */ + /* jpeg_write_scanlines(...); */ + /* Here we use the library's state variable cinfo.next_scanline as the + * loop counter, so that we don't have to keep track ourselves. + * To keep things simple, we pass one scanline per call; you can pass + * more if you wish, though. + */ + row_stride = info.dwEffWidth; /* JSAMPLEs per row in image_buffer */ + + // "8+row_stride" fix heap deallocation problem during debug??? + buffer = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) &cinfo, JPOOL_IMAGE, 8+row_stride, 1); + + CImageIterator iter(this); + + iter.Upset(); + while (cinfo.next_scanline < cinfo.image_height) { + // info.nProgress = (int32_t)(100*cinfo.next_scanline/cinfo.image_height); + iter.GetRow(buffer[0], row_stride); + // not necessary if swapped red and blue definition in jmorecfg.h;ln322 + if (head.biClrUsed==0){ // swap R & B for RGB images + RGBtoBGR(buffer[0], row_stride); // Lance : 1998/09/01 : Bug ID: EXP-2.1.1-9 + } + iter.PrevRow(); + (void) jpeg_write_scanlines(&cinfo, buffer, 1); + } + + /* Step 6: Finish compression */ + jpeg_finish_compress(&cinfo); + + /* Step 7: release JPEG compression object */ + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_compress(&cinfo); + + +#if CXIMAGEJPG_SUPPORT_EXIF + if (m_exif && m_exif->m_exifinfo->IsExif){ + // discard useless sections (if any) read from original image + m_exif->DiscardAllButExif(); + // read new created image, to split the sections + hFile->Seek(pos,SEEK_SET); + m_exif->DecodeExif(hFile,EXIF_READ_IMAGE); + // save back the image, adding EXIF section + hFile->Seek(pos,SEEK_SET); + m_exif->EncodeExif(hFile); + } +#endif + + + /* And we're done! */ + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_JPG + diff --git a/DuiLib/3rd/CxImage/ximajpg.h b/DuiLib/3rd/CxImage/ximajpg.h new file mode 100644 index 0000000..4b2fe54 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximajpg.h @@ -0,0 +1,283 @@ +/* + * File: ximajpg.h + * Purpose: JPG Image Class Loader and Writer + */ +/* ========================================================== + * CxImageJPG (c) 07/Aug/2001 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * + * Special thanks to Troels Knakkergaard for new features, enhancements and bugfixes + * + * Special thanks to Chris Shearer Cooper for CxFileJpg tips & code + * + * EXIF support based on jhead-1.8 by Matthias Wandel + * + * original CImageJPG and CImageIterator implementation are: + * Copyright: (c) 1995, Alejandro Aguilar Sierra + * + * This software is based in part on the work of the Independent JPEG Group. + * Copyright (C) 1991-1998, Thomas G. Lane. + * ========================================================== + */ +#if !defined(__ximaJPEG_h) +#define __ximaJPEG_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_JPG + +#define CXIMAGEJPG_SUPPORT_EXIF CXIMAGE_SUPPORT_EXIF + +extern "C" { +#ifdef _LINUX + #include + #include +#else + #include "../jpeg/jpeglib.h" + #include "../jpeg/jerror.h" +#endif +} + +class DLL_EXP CxImageJPG: public CxImage +{ +public: + CxImageJPG(); + ~CxImageJPG(); + +// bool Load(const TCHAR * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_JPG);} +// bool Save(const TCHAR * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_JPG);} + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +#endif // CXIMAGE_SUPPORT_ENCODE + +/* + * EXIF support based on jhead-1.8 by Matthias Wandel + */ + +#if CXIMAGEJPG_SUPPORT_EXIF + +//-------------------------------------------------------------------------- +// JPEG markers consist of one or more 0xFF bytes, followed by a marker +// code byte (which is not an FF). Here are the marker codes of interest +// in this program. (See jdmarker.c for a more complete list.) +//-------------------------------------------------------------------------- + +#define M_SOF0 0xC0 // Start Of Frame N +#define M_SOF1 0xC1 // N indicates which compression process +#define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use +#define M_SOF3 0xC3 +#define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers +#define M_SOF6 0xC6 +#define M_SOF7 0xC7 +#define M_SOF9 0xC9 +#define M_SOF10 0xCA +#define M_SOF11 0xCB +#define M_SOF13 0xCD +#define M_SOF14 0xCE +#define M_SOF15 0xCF +#define M_SOI 0xD8 // Start Of Image (beginning of datastream) +#define M_EOI 0xD9 // End Of Image (end of datastream) +#define M_SOS 0xDA // Start Of Scan (begins compressed data) +#define M_JFIF 0xE0 // Jfif marker +#define M_EXIF 0xE1 // Exif marker +#define M_COM 0xFE // COMment + +#define PSEUDO_IMAGE_MARKER 0x123; // Extra value. + +#define EXIF_READ_EXIF 0x01 +#define EXIF_READ_IMAGE 0x02 +#define EXIF_READ_ALL 0x03 + +class DLL_EXP CxExifInfo +{ + +typedef struct tag_Section_t{ + uint8_t* Data; + int32_t Type; + unsigned Size; +} Section_t; + +public: + EXIFINFO* m_exifinfo; + char m_szLastError[256]; + CxExifInfo(EXIFINFO* info = NULL); + ~CxExifInfo(); + bool DecodeExif(CxFile * hFile, int32_t nReadMode = EXIF_READ_EXIF); + bool EncodeExif(CxFile * hFile); + void DiscardAllButExif(); +protected: + bool process_EXIF(uint8_t * CharBuf, uint32_t length); + void process_COM (const uint8_t * Data, int32_t length); + void process_SOFn (const uint8_t * Data, int32_t marker); + int32_t Get16u(void * Short); + int32_t Get16m(void * Short); + int32_t Get32s(void * Long); + uint32_t Get32u(void * Long); + double ConvertAnyFormat(void * ValuePtr, int32_t Format); + void* FindSection(int32_t SectionType); + bool ProcessExifDir(uint8_t * DirStart, uint8_t * OffsetBase, unsigned ExifLength, + EXIFINFO * const pInfo, uint8_t ** const LastExifRefdP, int32_t NestingLevel=0); + int32_t ExifImageWidth; + int32_t MotorolaOrder; + Section_t Sections[MAX_SECTIONS]; + int32_t SectionsRead; + bool freeinfo; +}; + + CxExifInfo* m_exif; + bool DecodeExif(CxFile * hFile); + bool DecodeExif(FILE * hFile) { CxIOFile file(hFile); return DecodeExif(&file); } + bool GetExifThumbnail(const TCHAR *filename, const TCHAR *outname, int32_t type); + +#endif //CXIMAGEJPG_SUPPORT_EXIF + +//////////////////////////////////////////////////////////////////////////////////////// +////////////////////// C x F i l e J p g //////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////// + +// thanks to Chris Shearer Cooper +class CxFileJpg : public jpeg_destination_mgr, public jpeg_source_mgr + { +public: + enum { eBufSize = 4096 }; + + CxFileJpg(CxFile* pFile) + { + m_pFile = pFile; + + init_destination = InitDestination; + empty_output_buffer = EmptyOutputBuffer; + term_destination = TermDestination; + + init_source = InitSource; + fill_input_buffer = FillInputBuffer; + skip_input_data = SkipInputData; + resync_to_restart = jpeg_resync_to_restart; // use default method + term_source = TermSource; + next_input_byte = NULL; //* => next byte to read from buffer + bytes_in_buffer = 0; //* # of bytes remaining in buffer + + m_pBuffer = new uint8_t[eBufSize]; + } + ~CxFileJpg() + { + delete [] m_pBuffer; + } + + static void InitDestination(j_compress_ptr cinfo) + { + CxFileJpg* pDest = (CxFileJpg*)cinfo->dest; + pDest->next_output_byte = pDest->m_pBuffer; + pDest->free_in_buffer = eBufSize; + } + + static boolean EmptyOutputBuffer(j_compress_ptr cinfo) + { + CxFileJpg* pDest = (CxFileJpg*)cinfo->dest; + if (pDest->m_pFile->Write(pDest->m_pBuffer,1,eBufSize)!=(size_t)eBufSize) + ERREXIT(cinfo, JERR_FILE_WRITE); + pDest->next_output_byte = pDest->m_pBuffer; + pDest->free_in_buffer = eBufSize; + return TRUE; + } + + static void TermDestination(j_compress_ptr cinfo) + { + CxFileJpg* pDest = (CxFileJpg*)cinfo->dest; + size_t datacount = eBufSize - pDest->free_in_buffer; + /* Write any data remaining in the buffer */ + if (datacount > 0) { + if (!pDest->m_pFile->Write(pDest->m_pBuffer,1,datacount)) + ERREXIT(cinfo, JERR_FILE_WRITE); + } + pDest->m_pFile->Flush(); + /* Make sure we wrote the output file OK */ + if (pDest->m_pFile->Error()) ERREXIT(cinfo, JERR_FILE_WRITE); + return; + } + + static void InitSource(j_decompress_ptr cinfo) + { + CxFileJpg* pSource = (CxFileJpg*)cinfo->src; + pSource->m_bStartOfFile = TRUE; + } + + static boolean FillInputBuffer(j_decompress_ptr cinfo) + { + size_t nbytes; + CxFileJpg* pSource = (CxFileJpg*)cinfo->src; + nbytes = pSource->m_pFile->Read(pSource->m_pBuffer,1,eBufSize); + if (nbytes <= 0){ + if (pSource->m_bStartOfFile) //* Treat empty input file as fatal error + ERREXIT(cinfo, JERR_INPUT_EMPTY); + WARNMS(cinfo, JWRN_JPEG_EOF); + // Insert a fake EOI marker + pSource->m_pBuffer[0] = (JOCTET) 0xFF; + pSource->m_pBuffer[1] = (JOCTET) JPEG_EOI; + nbytes = 2; + } + pSource->next_input_byte = pSource->m_pBuffer; + pSource->bytes_in_buffer = nbytes; + pSource->m_bStartOfFile = FALSE; + return TRUE; + } + + static void SkipInputData(j_decompress_ptr cinfo, long num_bytes) + { + CxFileJpg* pSource = (CxFileJpg*)cinfo->src; + if (num_bytes > 0){ + while (num_bytes > (int32_t)pSource->bytes_in_buffer){ + num_bytes -= (int32_t)pSource->bytes_in_buffer; + FillInputBuffer(cinfo); + // note we assume that fill_input_buffer will never return FALSE, + // so suspension need not be handled. + } + pSource->next_input_byte += (size_t) num_bytes; + pSource->bytes_in_buffer -= (size_t) num_bytes; + } + } + + static void TermSource(j_decompress_ptr /*cinfo*/) + { + return; + } +protected: + CxFile *m_pFile; + uint8_t *m_pBuffer; + bool m_bStartOfFile; +}; + +public: + enum CODEC_OPTION + { + ENCODE_BASELINE = 0x1, + ENCODE_ARITHMETIC = 0x2, + ENCODE_GRAYSCALE = 0x4, + ENCODE_OPTIMIZE = 0x8, + ENCODE_PROGRESSIVE = 0x10, + ENCODE_LOSSLESS = 0x20, + ENCODE_SMOOTHING = 0x40, + DECODE_GRAYSCALE = 0x80, + DECODE_QUANTIZE = 0x100, + DECODE_DITHER = 0x200, + DECODE_ONEPASS = 0x400, + DECODE_NOSMOOTH = 0x800, + ENCODE_SUBSAMPLE_422 = 0x1000, + ENCODE_SUBSAMPLE_444 = 0x2000 + }; + + int32_t m_nPredictor; + int32_t m_nPointTransform; + int32_t m_nSmoothing; + int32_t m_nQuantize; + J_DITHER_MODE m_nDither; + +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximalpha.cpp b/DuiLib/3rd/CxImage/ximalpha.cpp new file mode 100644 index 0000000..3a870f7 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximalpha.cpp @@ -0,0 +1,367 @@ +// xImalpha.cpp : Alpha channel functions +/* 07/08/2001 v1.00 - Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_ALPHA + +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa AlphaSetMax + */ +uint8_t CxImage::AlphaGetMax() const +{ + return info.nAlphaMax; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets global Alpha (opacity) value applied to the whole image, + * valid only for painting functions. + * \param nAlphaMax: can be from 0 to 255 + */ +void CxImage::AlphaSetMax(uint8_t nAlphaMax) +{ + info.nAlphaMax=nAlphaMax; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Checks if the image has a valid alpha channel. + */ +bool CxImage::AlphaIsValid() +{ + return pAlpha!=0; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Enables the alpha palette, so the Draw() function changes its behavior. + */ +void CxImage::AlphaPaletteEnable(bool enable) +{ + info.bAlphaPaletteEnabled=enable; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * True if the alpha palette is enabled for painting. + */ +bool CxImage::AlphaPaletteIsEnabled() +{ + return info.bAlphaPaletteEnabled; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets the alpha channel to full transparent. AlphaSet(0) has the same effect + */ +void CxImage::AlphaClear() +{ + if (pAlpha) memset(pAlpha,0,head.biWidth * head.biHeight); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets the alpha level for the whole image. + * \param level : from 0 (transparent) to 255 (opaque) + */ +void CxImage::AlphaSet(uint8_t level) +{ + if (pAlpha) memset(pAlpha,level,head.biWidth * head.biHeight); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Allocates an empty (opaque) alpha channel. + */ +bool CxImage::AlphaCreate() +{ + if (pAlpha==NULL) { + pAlpha = (uint8_t*)malloc(head.biWidth * head.biHeight); + if (pAlpha) memset(pAlpha,255,head.biWidth * head.biHeight); + } + return (pAlpha!=0); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::AlphaDelete() +{ + if (pAlpha) { free(pAlpha); pAlpha=0; } +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::AlphaInvert() +{ + if (pAlpha) { + uint8_t *iSrc=pAlpha; + int32_t n=head.biHeight*head.biWidth; + for(int32_t i=0; i < n; i++){ + *iSrc=(uint8_t)~(*(iSrc)); + iSrc++; + } + } +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Imports an existing alpa channel from another image with the same width and height. + */ +bool CxImage::AlphaCopy(CxImage &from) +{ + if (from.pAlpha == NULL || head.biWidth != from.head.biWidth || head.biHeight != from.head.biHeight) return false; + if (pAlpha==NULL) pAlpha = (uint8_t*)malloc(head.biWidth * head.biHeight); + if (pAlpha==NULL) return false; + memcpy(pAlpha,from.pAlpha,head.biWidth * head.biHeight); + info.nAlphaMax=from.info.nAlphaMax; + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Creates the alpha channel from a gray scale image. + */ +bool CxImage::AlphaSet(CxImage &from) +{ + if (!from.IsGrayScale() || head.biWidth != from.head.biWidth || head.biHeight != from.head.biHeight) return false; + if (pAlpha==NULL) pAlpha = (uint8_t*)malloc(head.biWidth * head.biHeight); + uint8_t* src = from.info.pImage; + uint8_t* dst = pAlpha; + if (src==NULL || dst==NULL) return false; + for (int32_t y=0; y>8); + c.rgbGreen = (uint8_t)((c.rgbGreen * a + a1 * info.nBkgndColor.rgbGreen)>>8); + c.rgbRed = (uint8_t)((c.rgbRed * a + a1 * info.nBkgndColor.rgbRed)>>8); + BlindSetPixelColor(x,y,c); + } + } + AlphaDelete(); + } else { + CxImage tmp(head.biWidth,head.biHeight,24); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return; + } + + for(int32_t y=0; y>8); + c.rgbGreen = (uint8_t)((c.rgbGreen * a + a1 * info.nBkgndColor.rgbGreen)>>8); + c.rgbRed = (uint8_t)((c.rgbRed * a + a1 * info.nBkgndColor.rgbRed)>>8); + tmp.BlindSetPixelColor(x,y,c); + } + } + Transfer(tmp); + } + return; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::AlphaFlip() +{ + if (!pAlpha) return false; + + uint8_t *buff = (uint8_t*)malloc(head.biWidth); + if (!buff) return false; + + uint8_t *iSrc,*iDst; + iSrc = pAlpha + (head.biHeight-1)*head.biWidth; + iDst = pAlpha; + for (int32_t i=0; i<(head.biHeight/2); ++i) + { + memcpy(buff, iSrc, head.biWidth); + memcpy(iSrc, iDst, head.biWidth); + memcpy(iDst, buff, head.biWidth); + iSrc-=head.biWidth; + iDst+=head.biWidth; + } + + free(buff); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::AlphaMirror() +{ + if (!pAlpha) return false; + uint8_t* pAlpha2 = (uint8_t*)malloc(head.biWidth * head.biHeight); + if (!pAlpha2) return false; + uint8_t *iSrc,*iDst; + int32_t wdt=head.biWidth-1; + iSrc=pAlpha + wdt; + iDst=pAlpha2; + for(int32_t y=0; y < head.biHeight; y++){ + for(int32_t x=0; x <= wdt; x++) + *(iDst+x)=*(iSrc-x); + iSrc+=head.biWidth; + iDst+=head.biWidth; + } + free(pAlpha); + pAlpha=pAlpha2; + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Exports the alpha channel in a 8bpp grayscale image. + */ +bool CxImage::AlphaSplit(CxImage *dest) +{ + if (!pAlpha || !dest) return false; + + CxImage tmp(head.biWidth,head.biHeight,8); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + uint8_t* src = pAlpha; + uint8_t* dst = tmp.info.pImage; + for (int32_t y=0; yTransfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Exports the alpha palette channel in a 8bpp grayscale image. + */ +bool CxImage::AlphaPaletteSplit(CxImage *dest) +{ + if (!AlphaPaletteIsValid() || !dest) return false; + + CxImage tmp(head.biWidth,head.biHeight,8); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + for(int32_t y=0; yTransfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Merge in the alpha layer the transparent color mask + * (previously set with SetTransColor or SetTransIndex) + */ +bool CxImage::AlphaFromTransparency() +{ + if (!IsValid() || !IsTransparent()) + return false; + + AlphaCreate(); + + for(int32_t y=0; y info.nNumLayers ) position = info.nNumLayers; + + CxImage** ptmp = new CxImage*[info.nNumLayers + 1]; + if (ptmp==0) return false; + + int32_t i=0; + for (int32_t n=0; ninfo.pParent = this; + } else { + free(ptmp); + return false; + } + + info.nNumLayers++; + delete [] ppLayers; + ppLayers = ptmp; + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Deletes a layer. If position is less than 0, the last layer will be deleted + */ +bool CxImage::LayerDelete(int32_t position) +{ + if ( position >= info.nNumLayers ) return false; + if ( position < 0) position = info.nNumLayers - 1; + if ( position < 0) return false; + + if (info.nNumLayers>1){ + + CxImage** ptmp = new CxImage*[info.nNumLayers - 1]; + if (ptmp==0) return false; + + int32_t i=0; + for (int32_t n=0; n= info.nNumLayers ) return NULL; + if ( position < 0) position = info.nNumLayers - 1; + return ppLayers[position]; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_LAYERS diff --git a/DuiLib/3rd/CxImage/ximamng.cpp b/DuiLib/3rd/CxImage/ximamng.cpp new file mode 100644 index 0000000..bdfb05c --- /dev/null +++ b/DuiLib/3rd/CxImage/ximamng.cpp @@ -0,0 +1,430 @@ +/* + * File: ximamng.cpp + * Purpose: Platform Independent MNG Image Class Loader and Writer + * Author: 07/Aug/2001 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximamng.h" + +#if CXIMAGE_SUPPORT_MNG + +//////////////////////////////////////////////////////////////////////////////// +// callbacks for the mng decoder: +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// memory allocation; data must be zeroed +static mng_ptr +mymngalloc( mng_size_t size ) +{ + return (mng_ptr)calloc(1, size); +} + +//////////////////////////////////////////////////////////////////////////////// +// memory deallocation +static void mymngfree(mng_ptr p, mng_size_t size) +{ + free(p); +} + +//////////////////////////////////////////////////////////////////////////////// +// Stream open/close: +// since the user is responsible for opening and closing the file, +// we leave the default implementation open +static mng_bool mymngopenstream(mng_handle mng) { return MNG_TRUE; } +static mng_bool mymngopenstreamwrite(mng_handle mng) { return MNG_TRUE; } +static mng_bool mymngclosestream(mng_handle mng) { return MNG_TRUE; } + +//////////////////////////////////////////////////////////////////////////////// +// feed data to the decoder +static mng_bool mymngreadstream(mng_handle mng, mng_ptr buffer, mng_uint32 size, mng_uint32 *bytesread) +{ + mngstuff *mymng = (mngstuff *)mng_get_userdata(mng); + // read the requested amount of data from the file + *bytesread = mymng->file->Read( buffer, sizeof(uint8_t), size); + return MNG_TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +static mng_bool mymngwritestream (mng_handle mng, mng_ptr pBuf, mng_uint32 iSize, mng_uint32 *iWritten) +{ + mngstuff *mymng = (mngstuff *)mng_get_userdata(mng); + // write it + *iWritten = mymng->file->Write (pBuf, 1, iSize); + return MNG_TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +// the header's been read. set up the display stuff +static mng_bool mymngprocessheader( mng_handle mng, mng_uint32 width, mng_uint32 height ) +{ + // normally the image buffer is allocated here, + // but in this module we don't know nothing about + // the final environment. + + mngstuff *mymng = (mngstuff *)mng_get_userdata(mng); + + mymng->width = width; + mymng->height = height; + mymng->bpp = 24; + mymng->effwdt = ((((width * mymng->bpp) + 31) >> 5) << 2); + + if (mng->bUseBKGD){ + mymng->nBkgndIndex = 0; + mymng->nBkgndColor.rgbRed = mng->iBGred >> 8; + mymng->nBkgndColor.rgbGreen =mng->iBGgreen >> 8; + mymng->nBkgndColor.rgbBlue = mng->iBGblue >> 8; + } + + mymng->image = (uint8_t*)malloc(height * mymng->effwdt); + + // tell the mng decoder about our bit-depth choice +#if CXIMAGE_SUPPORT_ALPHA + mng_set_canvasstyle( mng, MNG_CANVAS_RGB8_A8 ); + mymng->alpha = (uint8_t*)malloc(height * width); +#else + mng_set_canvasstyle( mng, MNG_CANVAS_BGR8); + mymng->alpha = NULL; +#endif + return MNG_TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +// return a row pointer for the decoder to fill +static mng_ptr mymnggetcanvasline( mng_handle mng, mng_uint32 line ) +{ + mngstuff *mymng = (mngstuff *)mng_get_userdata(mng); + return (mng_ptr)(mymng->image + (mymng->effwdt * (mymng->height - 1 - line))); +} +//////////////////////////////////////////////////////////////////////////////// +// return a row pointer for the decoder to fill for alpha channel +static mng_ptr mymnggetalphaline( mng_handle mng, mng_uint32 line ) +{ + mngstuff *mymng = (mngstuff *)mng_get_userdata(mng); + return (mng_ptr)(mymng->alpha + (mymng->width * (mymng->height - 1 - line))); +} + +//////////////////////////////////////////////////////////////////////////////// +// timer +static mng_uint32 mymnggetticks(mng_handle mng) +{ +#ifdef WIN32 + return (mng_uint32)GetTickCount(); +#else + return 0; +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +// Refresh: actual frame need to be updated (Invalidate) +static mng_bool mymngrefresh(mng_handle mng, mng_uint32 x, mng_uint32 y, mng_uint32 w, mng_uint32 h) +{ +// mngstuff *mymng = (mngstuff *)mng_get_userdata(mng); + return MNG_TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +// interframe delay callback +static mng_bool mymngsettimer(mng_handle mng, mng_uint32 msecs) +{ + mngstuff *mymng = (mngstuff *)mng_get_userdata(mng); + mymng->delay = msecs; // set the timer for when the decoder wants to be woken + return MNG_TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +static mng_bool mymngerror(mng_handle mng, mng_int32 code, mng_int8 severity, mng_chunkid chunktype, mng_uint32 chunkseq, mng_int32 extra1, mng_int32 extra2, mng_pchar text) +{ + return mng_cleanup(&mng); // +} + +//////////////////////////////////////////////////////////////////////////////// +// CxImage members +//////////////////////////////////////////////////////////////////////////////// +CxImageMNG::CxImageMNG(): CxImage(CXIMAGE_FORMAT_MNG) +{ + hmng = NULL; + memset(&mnginfo,0,sizeof(mngstuff)); + mnginfo.nBkgndIndex = -1; + mnginfo.speed = 1.0f; +} +//////////////////////////////////////////////////////////////////////////////// +CxImageMNG::~CxImageMNG() +{ + // cleanup and return + if (mnginfo.thread){ //close the animation thread + mnginfo.animation_enabled=0; + ResumeThread(mnginfo.thread); + WaitForSingleObject(mnginfo.thread,500); + CloseHandle(mnginfo.thread); + } + // free objects + if (mnginfo.image) free(mnginfo.image); + if (mnginfo.alpha) free(mnginfo.alpha); + if (hmng) mng_cleanup(&hmng); //be sure it's not needed any more. (active timers ?) +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageMNG::SetCallbacks(mng_handle mng) +{ + // set the callbacks + mng_setcb_errorproc(mng, mymngerror); + mng_setcb_openstream(mng, mymngopenstream); + mng_setcb_closestream(mng, mymngclosestream); + mng_setcb_readdata(mng, mymngreadstream); + mng_setcb_processheader(mng, mymngprocessheader); + mng_setcb_getcanvasline(mng, mymnggetcanvasline); + mng_setcb_refresh(mng, mymngrefresh); + mng_setcb_gettickcount(mng, mymnggetticks); + mng_setcb_settimer(mng, mymngsettimer); + mng_setcb_refresh(mng, mymngrefresh); + mng_setcb_getalphaline(mng, mymnggetalphaline); +} +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +// can't use the CxImage implementation because it looses mnginfo +bool CxImageMNG::Load(const TCHAR * imageFileName){ + FILE* hFile; //file handle to read the image +#ifdef WIN32 + if ((hFile=_tfopen(imageFileName,_T("rb")))==NULL) return false; // For UNICODE support +#else + if ((hFile=fopen(imageFileName,"rb"))==NULL) return false; +#endif + bool bOK = Decode(hFile); + fclose(hFile); + return bOK; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImageMNG::Decode(CxFile *hFile) +{ + if (hFile == NULL) return false; + + cx_try + { + // set up the mng decoder for our stream + hmng = mng_initialize(&mnginfo, (mng_memalloc)mymngalloc, (mng_memfree)mymngfree, MNG_NULL); + if (hmng == NULL) cx_throw("could not initialize libmng"); + + // set the file we want to play + mnginfo.file = hFile; + + // Set the colorprofile, lcms uses this: + mng_set_srgb(hmng, MNG_TRUE ); + // Set white as background color: + uint16_t Red,Green,Blue; + Red = Green = Blue = (255 << 8) + 255; + mng_set_bgcolor(hmng, Red, Green, Blue ); + // If PNG Background is available, use it: + mng_set_usebkgd(hmng, MNG_TRUE ); + + // No need to store chunks: + mng_set_storechunks(hmng, MNG_FALSE); + // No need to wait: straight reading + mng_set_suspensionmode(hmng, MNG_FALSE); + + SetCallbacks(hmng); + + mng_datap pData = (mng_datap)hmng; + + // read in the image + info.nNumFrames=0; + int32_t retval=MNG_NOERROR; + + retval = mng_readdisplay(hmng); + + if (retval != MNG_NOERROR && retval != MNG_NEEDTIMERWAIT){ + mng_store_error(hmng,retval,0,0); + if (hmng->zErrortext){ + cx_throw(hmng->zErrortext); + } else { + cx_throw("Error in MNG file"); + } + } + + if (info.nEscape == -1) { + // Return output dimensions only + head.biWidth = hmng->iWidth; + head.biHeight = hmng->iHeight; + info.dwType = CXIMAGE_FORMAT_MNG; + return true; + } + + // read all + while(pData->bReading){ + retval = mng_display_resume(hmng); + info.nNumFrames++; + } + + // single frame check: + if (retval != MNG_NEEDTIMERWAIT){ + info.nNumFrames--; + } else { + mnginfo.animation=1; + } + + if (info.nNumFrames<=0) info.nNumFrames=1; + + if (mnginfo.animation_enabled==0){ + // select the frame + if (info.nFrame>=0 && info.nFrame= 0){ + info.nBkgndIndex = mnginfo.nBkgndIndex; + info.nBkgndColor.rgbRed = mnginfo.nBkgndColor.rgbRed; + info.nBkgndColor.rgbGreen = mnginfo.nBkgndColor.rgbGreen; + info.nBkgndColor.rgbBlue = mnginfo.nBkgndColor.rgbBlue; + } + + //store the newly created image + if (Create(mnginfo.width,mnginfo.height,mnginfo.bpp, CXIMAGE_FORMAT_MNG)){ + memcpy(GetBits(), mnginfo.image, info.dwEffWidth * head.biHeight); +#if CXIMAGE_SUPPORT_ALPHA + SwapRGB2BGR(); + AlphaCreate(); + if(AlphaIsValid() && mnginfo.alpha){ + memcpy(AlphaGetPointer(),mnginfo.alpha,mnginfo.width * mnginfo.height); + } +#endif + } else cx_throw("CxImageMNG::Decode cannot create image"); + + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + return false; + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageMNG::Encode(CxFile *hFile) +{ + if (EncodeSafeCheck(hFile)) return false; + + cx_try + { + if (head.biClrUsed != 0) cx_throw("MNG encoder can save only RGB images"); + // set the file we want to play + mnginfo.file = hFile; + mnginfo.bpp = head.biBitCount; + mnginfo.effwdt = info.dwEffWidth; + mnginfo.height = head.biHeight; + mnginfo.width = head.biWidth; + + mnginfo.image = (uint8_t*)malloc(head.biSizeImage); + if (mnginfo.image == NULL) cx_throw("could not allocate memory for MNG"); + memcpy(mnginfo.image,info.pImage, head.biSizeImage); + + // set up the mng decoder for our stream + hmng = mng_initialize(&mnginfo, (mng_memalloc)mymngalloc, (mng_memfree)mymngfree, MNG_NULL); + if (hmng == NULL) cx_throw("could not initialize libmng"); + + mng_setcb_openstream(hmng, mymngopenstreamwrite ); + mng_setcb_closestream(hmng, mymngclosestream); + mng_setcb_writedata(hmng, mymngwritestream); + + // Write File: + mng_create(hmng); + // Just a single Frame (save a normal PNG): + WritePNG(hmng, 0, 1 ); + // Now write file: + mng_write(hmng); + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + return false; + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +// Writes a single PNG datastream +void CxImageMNG::WritePNG( mng_handle hMNG, int32_t Frame, int32_t FrameCount ) +{ + mngstuff *mymng = (mngstuff *)mng_get_userdata(hMNG); + + int32_t OffsetX=0,OffsetY=0,OffsetW=mymng->width,OffsetH=mymng->height; + + uint8_t *tmpbuffer = new uint8_t[ (mymng->effwdt+1) * mymng->height]; + if( tmpbuffer == 0 ) return; + + // Write DEFI chunk. + mng_putchunk_defi( hMNG, 0, 0, 0, MNG_TRUE, OffsetX, OffsetY, MNG_FALSE, 0, 0, 0, 0 ); + + // Write Header: + mng_putchunk_ihdr( + hMNG, + OffsetW, OffsetH, + MNG_BITDEPTH_8, + MNG_COLORTYPE_RGB, + MNG_COMPRESSION_DEFLATE, + MNG_FILTER_ADAPTIVE, + MNG_INTERLACE_NONE + ); + + // transfer data, add Filterbyte: + for( int32_t Row=0; Row No Filter. + tmpbuffer[Row*(mymng->effwdt+1)]=0; + // Copy the scanline: (reverse order) + memcpy(tmpbuffer+Row*(mymng->effwdt+1)+1, + mymng->image+((OffsetH-1-(OffsetY+Row))*(mymng->effwdt))+OffsetX,mymng->effwdt); + // swap red and blue components + RGBtoBGR(tmpbuffer+Row*(mymng->effwdt+1)+1,mymng->effwdt); + } + + // Compress data with ZLib (Deflate): + uint8_t *dstbuffer = new uint8_t[(mymng->effwdt+1)*OffsetH]; + if( dstbuffer == 0 ) return; + uint32_t dstbufferSize=(mymng->effwdt+1)*OffsetH; + + // Compress data: + if(Z_OK != compress2((Bytef *)dstbuffer,(ULONG *)&dstbufferSize,(const Bytef*)tmpbuffer, + (ULONG) (mymng->effwdt+1)*OffsetH,9 )) return; + + // Write Data into MNG File: + mng_putchunk_idat( hMNG, dstbufferSize, (mng_ptr*)dstbuffer); + mng_putchunk_iend(hMNG); + + // Free the stuff: + delete [] tmpbuffer; + delete [] dstbuffer; +} +//////////////////////////////////////////////////////////////////////////////// +int32_t CxImageMNG::Resume() +{ + if (MNG_NEEDTIMERWAIT == mng_display_resume(hmng)){ + if (info.pImage==NULL){ + Create(mnginfo.width,mnginfo.height,mnginfo.bpp, CXIMAGE_FORMAT_MNG); + } + if (IsValid()){ + memcpy(GetBits(), mnginfo.image, info.dwEffWidth * head.biHeight); +#if CXIMAGE_SUPPORT_ALPHA + SwapRGB2BGR(); + AlphaCreate(); + if(AlphaIsValid() && mnginfo.alpha){ + memcpy(AlphaGetPointer(),mnginfo.alpha,mnginfo.width * mnginfo.height); + } +#endif + } + } else { + mnginfo.animation_enabled = 0; + } + return mnginfo.animation_enabled; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageMNG::SetSpeed(float speed) +{ + if (speed>10.0) mnginfo.speed = 10.0f; + else if (speed<0.1) mnginfo.speed = 0.1f; + else mnginfo.speed=speed; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_MNG diff --git a/DuiLib/3rd/CxImage/ximamng.h b/DuiLib/3rd/CxImage/ximamng.h new file mode 100644 index 0000000..5142194 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximamng.h @@ -0,0 +1,88 @@ +/* + * File: ximamng.h + * Purpose: Declaration of the MNG Image Class + * Author: Davide Pizzolato - www.xdp.it + * Created: 2001 + */ +/* ========================================================== + * CxImageMNG (c) 07/Aug/2001 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * + * Special thanks to Frank Haug for suggestions and code. + * + * original mng.cpp code created by Nikolaus Brennig, November 14th, 2000. + * + * LIBMNG Copyright (c) 2000,2001 Gerard Juyn (gerard@libmng.com) + * ========================================================== + */ + +#if !defined(__ximaMNG_h) +#define __ximaMNG_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_MNG + +//#define MNG_NO_CMS +#define MNG_SUPPORT_DISPLAY +#define MNG_SUPPORT_READ +#define MNG_SUPPORT_WRITE +#define MNG_ACCESS_CHUNKS +#define MNG_STORE_CHUNKS + +extern "C" { +#include "../mng/libmng.h" +#include "../mng/libmng_data.h" +#include "../mng/libmng_error.h" +} + +//uint32_t _stdcall RunMNGThread(void *lpParam); + +typedef struct tagmngstuff +{ + CxFile *file; + uint8_t *image; + uint8_t *alpha; + HANDLE thread; + mng_uint32 delay; + mng_uint32 width; + mng_uint32 height; + mng_uint32 effwdt; + mng_int16 bpp; + mng_bool animation; + mng_bool animation_enabled; + float speed; + int32_t nBkgndIndex; + RGBQUAD nBkgndColor; +} mngstuff; + +class CxImageMNG: public CxImage +{ +public: + CxImageMNG(); + ~CxImageMNG(); + + bool Load(const TCHAR * imageFileName); + + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } + bool Save(const TCHAR * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_MNG);} +#endif // CXIMAGE_SUPPORT_ENCODE + + int32_t Resume(); + void SetSpeed(float speed); + + mng_handle hmng; + mngstuff mnginfo; +protected: + void WritePNG(mng_handle hMNG, int32_t Frame, int32_t FrameCount ); + void SetCallbacks(mng_handle mng); +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximapal.cpp b/DuiLib/3rd/CxImage/ximapal.cpp new file mode 100644 index 0000000..6b3219e --- /dev/null +++ b/DuiLib/3rd/CxImage/ximapal.cpp @@ -0,0 +1,834 @@ +// xImaPal.cpp : Palette and Pixel functions +/* 07/08/2001 v1.00 - Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximage.h" + +//////////////////////////////////////////////////////////////////////////////// +/** + * returns the palette dimension in byte + */ +uint32_t CxImage::GetPaletteSize() +{ + return (head.biClrUsed * sizeof(RGBQUAD)); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::SetPaletteColor(uint8_t idx, uint8_t r, uint8_t g, uint8_t b, uint8_t alpha) +{ + if ((pDib)&&(head.biClrUsed)){ + uint8_t* iDst = (uint8_t*)(pDib) + sizeof(BITMAPINFOHEADER); + if (idx=head.biWidth)||(y>=head.biHeight)) { + if (info.nBkgndIndex >= 0) return (uint8_t)info.nBkgndIndex; + else return *info.pImage; + } + if (head.biBitCount==8){ + return info.pImage[y*info.dwEffWidth + x]; + } else { + uint8_t pos; + uint8_t iDst= info.pImage[y*info.dwEffWidth + (x*head.biBitCount >> 3)]; + if (head.biBitCount==4){ + pos = (uint8_t)(4*(1-x%2)); + iDst &= (0x0F<> pos); + } else if (head.biBitCount==1){ + pos = (uint8_t)(7-x%8); + iDst &= (0x01<> pos); + } + } + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +uint8_t CxImage::BlindGetPixelIndex(const int32_t x,const int32_t y) +{ +#ifdef _DEBUG + if ((pDib==NULL) || (head.biClrUsed==0) || !IsInside(x,y)) + #if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + throw 0; + #else + return 0; + #endif +#endif + + if (head.biBitCount==8){ + return info.pImage[y*info.dwEffWidth + x]; + } else { + uint8_t pos; + uint8_t iDst= info.pImage[y*info.dwEffWidth + (x*head.biBitCount >> 3)]; + if (head.biBitCount==4){ + pos = (uint8_t)(4*(1-x%2)); + iDst &= (0x0F<> pos); + } else if (head.biBitCount==1){ + pos = (uint8_t)(7-x%8); + iDst &= (0x01<> pos); + } + } + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +RGBQUAD CxImage::GetPixelColor(int32_t x,int32_t y, bool bGetAlpha) +{ +// RGBQUAD rgb={0,0,0,0}; + RGBQUAD rgb=info.nBkgndColor; // + if ((pDib==NULL)||(x<0)||(y<0)|| + (x>=head.biWidth)||(y>=head.biHeight)){ + if (info.nBkgndIndex >= 0){ + if (head.biBitCount<24) return GetPaletteColor((uint8_t)info.nBkgndIndex); + else return info.nBkgndColor; + } else if (pDib) return GetPixelColor(0,0); + return rgb; + } + + if (head.biClrUsed){ + rgb = GetPaletteColor(BlindGetPixelIndex(x,y)); + } else { + uint8_t* iDst = info.pImage + y*info.dwEffWidth + x*3; + rgb.rgbBlue = *iDst++; + rgb.rgbGreen= *iDst++; + rgb.rgbRed = *iDst; + } +#if CXIMAGE_SUPPORT_ALPHA + if (pAlpha && bGetAlpha) rgb.rgbReserved = BlindAlphaGet(x,y); +#else + rgb.rgbReserved = 0; +#endif //CXIMAGE_SUPPORT_ALPHA + return rgb; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * This is (a bit) faster version of GetPixelColor. + * It tests bounds only in debug mode (_DEBUG defined). + * + * It is an error to request out-of-borders pixel with this method. + * In DEBUG mode an exception will be thrown, and data will be violated in non-DEBUG mode. + * \author ***bd*** 2.2004 + */ +RGBQUAD CxImage::BlindGetPixelColor(const int32_t x,const int32_t y, bool bGetAlpha) +{ + RGBQUAD rgb; +#ifdef _DEBUG + if ((pDib==NULL) || !IsInside(x,y)) + #if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + throw 0; + #else + {rgb.rgbReserved = 0; return rgb;} + #endif +#endif + + if (head.biClrUsed){ + rgb = GetPaletteColor(BlindGetPixelIndex(x,y)); + } else { + uint8_t* iDst = info.pImage + y*info.dwEffWidth + x*3; + rgb.rgbBlue = *iDst++; + rgb.rgbGreen= *iDst++; + rgb.rgbRed = *iDst; + rgb.rgbReserved = 0; //needed for images without alpha layer + } +#if CXIMAGE_SUPPORT_ALPHA + if (pAlpha && bGetAlpha) rgb.rgbReserved = BlindAlphaGet(x,y); +#else + rgb.rgbReserved = 0; +#endif //CXIMAGE_SUPPORT_ALPHA + return rgb; +} +//////////////////////////////////////////////////////////////////////////////// +uint8_t CxImage::GetPixelGray(int32_t x, int32_t y) +{ + RGBQUAD color = GetPixelColor(x,y); + return (uint8_t)RGB2GRAY(color.rgbRed,color.rgbGreen,color.rgbBlue); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::BlindSetPixelIndex(int32_t x,int32_t y,uint8_t i) +{ +#ifdef _DEBUG + if ((pDib==NULL)||(head.biClrUsed==0)|| + (x<0)||(y<0)||(x>=head.biWidth)||(y>=head.biHeight)) + #if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + throw 0; + #else + return; + #endif +#endif + + if (head.biBitCount==8){ + info.pImage[y*info.dwEffWidth + x]=i; + return; + } else { + uint8_t pos; + uint8_t* iDst= info.pImage + y*info.dwEffWidth + (x*head.biBitCount >> 3); + if (head.biBitCount==4){ + pos = (uint8_t)(4*(1-x%2)); + *iDst &= ~(0x0F<=head.biWidth)||(y>=head.biHeight)) return ; + + if (head.biBitCount==8){ + info.pImage[y*info.dwEffWidth + x]=i; + return; + } else { + uint8_t pos; + uint8_t* iDst= info.pImage + y*info.dwEffWidth + (x*head.biBitCount >> 3); + if (head.biBitCount==4){ + pos = (uint8_t)(4*(1-x%2)); + *iDst &= ~(0x0F<=head.biWidth)||(y>=head.biHeight)) + #if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + throw 0; + #else + return; + #endif +#endif + if (head.biClrUsed) + BlindSetPixelIndex(x,y,GetNearestIndex(c)); + else { + uint8_t* iDst = info.pImage + y*info.dwEffWidth + x*3; + *iDst++ = c.rgbBlue; + *iDst++ = c.rgbGreen; + *iDst = c.rgbRed; + } +#if CXIMAGE_SUPPORT_ALPHA + if (bSetAlpha) AlphaSet(x,y,c.rgbReserved); +#endif //CXIMAGE_SUPPORT_ALPHA +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::SetPixelColor(int32_t x,int32_t y,RGBQUAD c, bool bSetAlpha) +{ + if ((pDib==NULL)||(x<0)||(y<0)|| + (x>=head.biWidth)||(y>=head.biHeight)) return; + if (head.biClrUsed) + BlindSetPixelIndex(x,y,GetNearestIndex(c)); + else { + uint8_t* iDst = info.pImage + y*info.dwEffWidth + x*3; + *iDst++ = c.rgbBlue; + *iDst++ = c.rgbGreen; + *iDst = c.rgbRed; + } +#if CXIMAGE_SUPPORT_ALPHA + if (bSetAlpha) AlphaSet(x,y,c.rgbReserved); +#endif //CXIMAGE_SUPPORT_ALPHA +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Blends the current pixel color with a new color. + * \param x,y = pixel + * \param c = new color + * \param blend = can be from 0 (no effect) to 1 (full effect). + * \param bSetAlpha = if true, blends also the alpha component stored in c.rgbReserved + */ +void CxImage::BlendPixelColor(int32_t x,int32_t y,RGBQUAD c, float blend, bool bSetAlpha) +{ + if ((pDib==NULL)||(x<0)||(y<0)|| + (x>=head.biWidth)||(y>=head.biHeight)) return; + + int32_t a0 = (int32_t)(256*blend); + int32_t a1 = 256 - a0; + + RGBQUAD c0 = BlindGetPixelColor(x,y); + c.rgbRed = (uint8_t)((c.rgbRed * a0 + c0.rgbRed * a1)>>8); + c.rgbBlue = (uint8_t)((c.rgbBlue * a0 + c0.rgbBlue * a1)>>8); + c.rgbGreen = (uint8_t)((c.rgbGreen * a0 + c0.rgbGreen * a1)>>8); + + if (head.biClrUsed) + BlindSetPixelIndex(x,y,GetNearestIndex(c)); + else { + uint8_t* iDst = info.pImage + y*info.dwEffWidth + x*3; + *iDst++ = c.rgbBlue; + *iDst++ = c.rgbGreen; + *iDst = c.rgbRed; +#if CXIMAGE_SUPPORT_ALPHA + if (bSetAlpha) AlphaSet(x,y,c.rgbReserved); +#endif //CXIMAGE_SUPPORT_ALPHA + } +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Returns the best palette index that matches a specified color. + */ +uint8_t CxImage::GetNearestIndex(RGBQUAD c) +{ + if ((pDib==NULL)||(head.biClrUsed==0)) return 0; + + // check matching with the previous result + if (info.last_c_isvalid && (*(int32_t*)&info.last_c == *(int32_t*)&c)) return info.last_c_index; + info.last_c = c; + info.last_c_isvalid = true; + + uint8_t* iDst = (uint8_t*)(pDib) + sizeof(BITMAPINFOHEADER); + int32_t distance=200000; + int32_t i,j = 0; + int32_t k,l; + int32_t m = (int32_t)(head.biClrImportant==0 ? head.biClrUsed : head.biClrImportant); + for(i=0,l=0;i100) perc=100; + for(i=0;i=0){ + if (head.biClrUsed){ + if (GetPixelIndex(x,y) == info.nBkgndIndex) return true; + } else { + RGBQUAD ct = info.nBkgndColor; + RGBQUAD c = GetPixelColor(x,y,false); + if (*(int32_t*)&c==*(int32_t*)&ct) return true; + } + } + +#if CXIMAGE_SUPPORT_ALPHA + if (pAlpha) return AlphaGet(x,y)==0; +#endif + + return false; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::GetTransparentMask(CxImage* iDst) +{ + if (!pDib) return false; + + CxImage tmp; + tmp.Create(head.biWidth, head.biHeight, 1, GetType()); + tmp.SetStdPalette(); + tmp.Clear(0); + + for(int32_t y=0; yTransfer(tmp); + else Transfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Checks if image has the same palette, if any. + * \param img = image to compare. + * \param bCheckAlpha = check also the rgbReserved field. + */ +bool CxImage::IsSamePalette(CxImage &img, bool bCheckAlpha) +{ + if (head.biClrUsed != img.head.biClrUsed) + return false; + if (head.biClrUsed == 0) + return false; + + RGBQUAD c1,c2; + for (uint32_t n=0; n256) { + head.biClrImportant = 0; + return; + } + + switch(head.biBitCount){ + case 1: + head.biClrImportant = min(ncolors,2); + break; + case 4: + head.biClrImportant = min(ncolors,16); + break; + case 8: + head.biClrImportant = ncolors; + break; + } + return; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Returns pointer to pixel. Currently implemented only for truecolor images. + * + * \param x,y - coordinates + * + * \return pointer to first byte of pixel data + * + * \author ***bd*** 2.2004 + */ +void* CxImage::BlindGetPixelPointer(const int32_t x, const int32_t y) +{ +#ifdef _DEBUG + if ((pDib==NULL) || !IsInside(x,y)) + #if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + throw 0; + #else + return 0; + #endif +#endif + if (!IsIndexed()) + return info.pImage + y*info.dwEffWidth + x*3; + else + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::DrawLine(int32_t StartX, int32_t EndX, int32_t StartY, int32_t EndY, COLORREF cr) +{ + DrawLine(StartX, EndX, StartY, EndY, RGBtoRGBQUAD(cr)); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImage::DrawLine(int32_t StartX, int32_t EndX, int32_t StartY, int32_t EndY, RGBQUAD color, bool bSetAlpha) +{ + if (!pDib) return; + ////////////////////////////////////////////////////// + // Draws a line using the Bresenham line algorithm + // Thanks to Jordan DeLozier + ////////////////////////////////////////////////////// + int32_t x1 = StartX; + int32_t y1 = StartY; + int32_t x = x1; // Start x off at the first pixel + int32_t y = y1; // Start y off at the first pixel + int32_t x2 = EndX; + int32_t y2 = EndY; + + int32_t xinc1,xinc2,yinc1,yinc2; // Increasing values + int32_t den, num, numadd,numpixels; + int32_t deltax = abs(x2 - x1); // The difference between the x's + int32_t deltay = abs(y2 - y1); // The difference between the y's + + // Get Increasing Values + if (x2 >= x1) { // The x-values are increasing + xinc1 = 1; + xinc2 = 1; + } else { // The x-values are decreasing + xinc1 = -1; + xinc2 = -1; + } + + if (y2 >= y1) { // The y-values are increasing + yinc1 = 1; + yinc2 = 1; + } else { // The y-values are decreasing + yinc1 = -1; + yinc2 = -1; + } + + // Actually draw the line + if (deltax >= deltay) // There is at least one x-value for every y-value + { + xinc1 = 0; // Don't change the x when numerator >= denominator + yinc2 = 0; // Don't change the y for every iteration + den = deltax; + num = deltax / 2; + numadd = deltay; + numpixels = deltax; // There are more x-values than y-values + } + else // There is at least one y-value for every x-value + { + xinc2 = 0; // Don't change the x for every iteration + yinc1 = 0; // Don't change the y when numerator >= denominator + den = deltay; + num = deltay / 2; + numadd = deltax; + numpixels = deltay; // There are more y-values than x-values + } + + for (int32_t curpixel = 0; curpixel <= numpixels; curpixel++) + { + // Draw the current pixel + SetPixelColor(x,y,color,bSetAlpha); + + num += numadd; // Increase the numerator by the top of the fraction + if (num >= den) // Check if numerator >= denominator + { + num -= den; // Calculate the new numerator value + x += xinc1; // Change the x as appropriate + y += yinc1; // Change the y as appropriate + } + x += xinc2; // Change the x as appropriate + y += yinc2; // Change the y as appropriate + } +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Sets a palette with standard colors for 1, 4 and 8 bpp images. + */ +void CxImage::SetStdPalette() +{ + if (!pDib) return; + switch (head.biBitCount){ + case 8: + { + const uint8_t pal256[1024] = {0,0,0,0,0,0,128,0,0,128,0,0,0,128,128,0,128,0,0,0,128,0,128,0,128,128,0,0,192,192,192,0, + 192,220,192,0,240,202,166,0,212,240,255,0,177,226,255,0,142,212,255,0,107,198,255,0, + 72,184,255,0,37,170,255,0,0,170,255,0,0,146,220,0,0,122,185,0,0,98,150,0,0,74,115,0,0, + 50,80,0,212,227,255,0,177,199,255,0,142,171,255,0,107,143,255,0,72,115,255,0,37,87,255,0,0, + 85,255,0,0,73,220,0,0,61,185,0,0,49,150,0,0,37,115,0,0,25,80,0,212,212,255,0,177,177,255,0, + 142,142,255,0,107,107,255,0,72,72,255,0,37,37,255,0,0,0,254,0,0,0,220,0,0,0,185,0,0,0,150,0, + 0,0,115,0,0,0,80,0,227,212,255,0,199,177,255,0,171,142,255,0,143,107,255,0,115,72,255,0, + 87,37,255,0,85,0,255,0,73,0,220,0,61,0,185,0,49,0,150,0,37,0,115,0,25,0,80,0,240,212,255,0, + 226,177,255,0,212,142,255,0,198,107,255,0,184,72,255,0,170,37,255,0,170,0,255,0,146,0,220,0, + 122,0,185,0,98,0,150,0,74,0,115,0,50,0,80,0,255,212,255,0,255,177,255,0,255,142,255,0,255,107,255,0, + 255,72,255,0,255,37,255,0,254,0,254,0,220,0,220,0,185,0,185,0,150,0,150,0,115,0,115,0,80,0,80,0, + 255,212,240,0,255,177,226,0,255,142,212,0,255,107,198,0,255,72,184,0,255,37,170,0,255,0,170,0, + 220,0,146,0,185,0,122,0,150,0,98,0,115,0,74,0,80,0,50,0,255,212,227,0,255,177,199,0,255,142,171,0, + 255,107,143,0,255,72,115,0,255,37,87,0,255,0,85,0,220,0,73,0,185,0,61,0,150,0,49,0,115,0,37,0, + 80,0,25,0,255,212,212,0,255,177,177,0,255,142,142,0,255,107,107,0,255,72,72,0,255,37,37,0,254,0, + 0,0,220,0,0,0,185,0,0,0,150,0,0,0,115,0,0,0,80,0,0,0,255,227,212,0,255,199,177,0,255,171,142,0, + 255,143,107,0,255,115,72,0,255,87,37,0,255,85,0,0,220,73,0,0,185,61,0,0,150,49,0,0,115,37,0, + 0,80,25,0,0,255,240,212,0,255,226,177,0,255,212,142,0,255,198,107,0,255,184,72,0,255,170,37,0, + 255,170,0,0,220,146,0,0,185,122,0,0,150,98,0,0,115,74,0,0,80,50,0,0,255,255,212,0,255,255,177,0, + 255,255,142,0,255,255,107,0,255,255,72,0,255,255,37,0,254,254,0,0,220,220,0,0,185,185,0,0,150,150,0, + 0,115,115,0,0,80,80,0,0,240,255,212,0,226,255,177,0,212,255,142,0,198,255,107,0,184,255,72,0, + 170,255,37,0,170,255,0,0,146,220,0,0,122,185,0,0,98,150,0,0,74,115,0,0,50,80,0,0,227,255,212,0, + 199,255,177,0,171,255,142,0,143,255,107,0,115,255,72,0,87,255,37,0,85,255,0,0,73,220,0,0,61,185,0, + 0,49,150,0,0,37,115,0,0,25,80,0,0,212,255,212,0,177,255,177,0,142,255,142,0,107,255,107,0,72,255,72,0, + 37,255,37,0,0,254,0,0,0,220,0,0,0,185,0,0,0,150,0,0,0,115,0,0,0,80,0,0,212,255,227,0,177,255,199,0, + 142,255,171,0,107,255,143,0,72,255,115,0,37,255,87,0,0,255,85,0,0,220,73,0,0,185,61,0,0,150,49,0,0, + 115,37,0,0,80,25,0,212,255,240,0,177,255,226,0,142,255,212,0,107,255,198,0,72,255,184,0,37,255,170,0, + 0,255,170,0,0,220,146,0,0,185,122,0,0,150,98,0,0,115,74,0,0,80,50,0,212,255,255,0,177,255,255,0, + 142,255,255,0,107,255,255,0,72,255,255,0,37,255,255,0,0,254,254,0,0,220,220,0,0,185,185,0,0, + 150,150,0,0,115,115,0,0,80,80,0,242,242,242,0,230,230,230,0,218,218,218,0,206,206,206,0,194,194,194,0, + 182,182,182,0,170,170,170,0,158,158,158,0,146,146,146,0,134,134,134,0,122,122,122,0,110,110,110,0, + 98,98,98,0,86,86,86,0,74,74,74,0,62,62,62,0,50,50,50,0,38,38,38,0,26,26,26,0,14,14,14,0,240,251,255,0, + 164,160,160,0,128,128,128,0,0,0,255,0,0,255,0,0,0,255,255,0,255,0,0,0,255,0,255,0,255,255,0,0,255,255,255,0}; + memcpy(GetPalette(),pal256,1024); + break; + } + case 4: + { + const uint8_t pal16[64]={0,0,0,0,0,0,128,0,0,128,0,0,0,128,128,0,128,0,0,0,128,0,128,0,128,128,0,0,192,192,192,0, + 128,128,128,0,0,0,255,0,0,255,0,0,0,255,255,0,255,0,0,0,255,0,255,0,255,255,0,0,255,255,255,0}; + memcpy(GetPalette(),pal16,64); + break; + } + case 1: + { + const uint8_t pal2[8]={0,0,0,0,255,255,255,0}; + memcpy(GetPalette(),pal2,8); + break; + } + } + info.last_c_isvalid = false; + return; +} +//////////////////////////////////////////////////////////////////////////////// diff --git a/DuiLib/3rd/CxImage/ximapcx.cpp b/DuiLib/3rd/CxImage/ximapcx.cpp new file mode 100644 index 0000000..05a7241 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximapcx.cpp @@ -0,0 +1,479 @@ +/* + * File: ximapcx.cpp + * Purpose: Platform Independent PCX Image Class Loader and Writer + * 05/Jan/2002 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + * + * based on ppmtopcx.c - convert a portable pixmap to PCX + * Copyright (C) 1994 by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * based on ppmtopcx.c by Michael Davidson + */ + +#include "ximapcx.h" + +#if CXIMAGE_SUPPORT_PCX + +#include "xmemfile.h" + +#define PCX_MAGIC 0X0A // PCX magic number +#define PCX_256_COLORS 0X0C // magic number for 256 colors +#define PCX_HDR_SIZE 128 // size of PCX header +#define PCX_MAXCOLORS 256 +#define PCX_MAXPLANES 4 +#define PCX_MAXVAL 255 + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImagePCX::Decode(CxFile *hFile) +{ + if (hFile == NULL) return false; + + PCXHEADER pcxHeader; + int32_t i, x, y, y2, nbytes, count, Height, Width; + uint8_t c, ColorMap[PCX_MAXCOLORS][3]; + uint8_t *pcximage = NULL, *lpHead1 = NULL, *lpHead2 = NULL; + uint8_t *pcxplanes, *pcxpixels; + + cx_try + { + if (hFile->Read(&pcxHeader,sizeof(PCXHEADER),1)==0) cx_throw("Can't read PCX image"); + + PCX_toh(&pcxHeader); + + if (pcxHeader.Manufacturer != PCX_MAGIC) cx_throw("Error: Not a PCX file"); + // Check for PCX run length encoding + if (pcxHeader.Encoding != 1) cx_throw("PCX file has unknown encoding scheme"); + + Width = (pcxHeader.Xmax - pcxHeader.Xmin) + 1; + Height = (pcxHeader.Ymax - pcxHeader.Ymin) + 1; + info.xDPI = pcxHeader.Hres; + info.yDPI = pcxHeader.Vres; + + if (info.nEscape == -1){ + head.biWidth = Width; + head.biHeight= Height; + info.dwType = CXIMAGE_FORMAT_PCX; + return true; + } + + // Check that we can handle this image format + if (pcxHeader.ColorPlanes > 4) + cx_throw("Can't handle image with more than 4 planes"); + + // Create the image + if (pcxHeader.ColorPlanes >= 3 && pcxHeader.BitsPerPixel == 8){ + Create (Width, Height, 24, CXIMAGE_FORMAT_PCX); +#if CXIMAGE_SUPPORT_ALPHA + if (pcxHeader.ColorPlanes==4) AlphaCreate(); +#endif //CXIMAGE_SUPPORT_ALPHA + } else if (pcxHeader.ColorPlanes == 4 && pcxHeader.BitsPerPixel == 1) + Create (Width, Height, 4, CXIMAGE_FORMAT_PCX); + else + Create (Width, Height, pcxHeader.BitsPerPixel, CXIMAGE_FORMAT_PCX); + + if (info.nEscape) cx_throw("Cancelled"); // - cancel decoding + + //Read the image and check if it's ok + nbytes = pcxHeader.BytesPerLine * pcxHeader.ColorPlanes * Height; + lpHead1 = pcximage = (uint8_t*)malloc(nbytes); + while (nbytes > 0){ + if (hFile == NULL || hFile->Eof()) cx_throw("corrupted PCX"); + + hFile->Read(&c,1,1); + if ((c & 0XC0) != 0XC0){ // Repeated group + *pcximage++ = c; + --nbytes; + continue; + } + count = c & 0X3F; // extract count + hFile->Read(&c,1,1); + if (count > nbytes) cx_throw("repeat count spans end of image"); + + nbytes -= count; + while (--count >=0) *pcximage++ = c; + } + pcximage = lpHead1; + + //store the palette + for (i = 0; i < 16; i++){ + ColorMap[i][0] = pcxHeader.ColorMap[i][0]; + ColorMap[i][1] = pcxHeader.ColorMap[i][1]; + ColorMap[i][2] = pcxHeader.ColorMap[i][2]; + } + if (pcxHeader.BitsPerPixel == 8 && pcxHeader.ColorPlanes == 1){ + hFile->Read(&c,1,1); + if (c != PCX_256_COLORS) cx_throw("bad color map signature"); + + for (i = 0; i < PCX_MAXCOLORS; i++){ + hFile->Read(&ColorMap[i][0],1,1); + hFile->Read(&ColorMap[i][1],1,1); + hFile->Read(&ColorMap[i][2],1,1); + } + } + if (pcxHeader.BitsPerPixel == 1 && pcxHeader.ColorPlanes == 1){ + ColorMap[0][0] = ColorMap[0][1] = ColorMap[0][2] = 0; + ColorMap[1][0] = ColorMap[1][1] = ColorMap[1][2] = 255; + } + + for (uint32_t idx=0; idx - cancel decoding + + y2=Height-1-y; + pcxpixels = lpHead2; + pcxplanes = pcximage + (y * pcxHeader.BytesPerLine * pcxHeader.ColorPlanes); + + if (pcxHeader.ColorPlanes == 3 && pcxHeader.BitsPerPixel == 8){ + // Deal with 24 bit color image + for (x = 0; x < Width; x++){ + SetPixelColor(x,y2,RGB(pcxplanes[x],pcxplanes[pcxHeader.BytesPerLine + x],pcxplanes[2*pcxHeader.BytesPerLine + x])); + } + continue; +#if CXIMAGE_SUPPORT_ALPHA + } else if (pcxHeader.ColorPlanes == 4 && pcxHeader.BitsPerPixel == 8){ + for (x = 0; x < Width; x++){ + SetPixelColor(x,y2,RGB(pcxplanes[x],pcxplanes[pcxHeader.BytesPerLine + x],pcxplanes[2*pcxHeader.BytesPerLine + x])); + AlphaSet(x,y2,pcxplanes[3*pcxHeader.BytesPerLine + x]); + } + continue; +#endif //CXIMAGE_SUPPORT_ALPHA + } else if (pcxHeader.ColorPlanes == 1) { + if (!PCX_UnpackPixels(pcxpixels, pcxplanes, pcxHeader.BytesPerLine, pcxHeader.ColorPlanes, pcxHeader.BitsPerPixel)){ + cx_throw("PCX_UnpackPixels: Can't handle packed pixels with more than 1 plane"); + } + } else { + if (!PCX_PlanesToPixels(pcxpixels, pcxplanes, pcxHeader.BytesPerLine, pcxHeader.ColorPlanes, pcxHeader.BitsPerPixel)){ + cx_throw("PCX_PlanesToPixels: more than 4 planes or more than 1 bit per pixel"); + } + } + for (x = 0; x < Width; x++) SetPixelIndex(x,y2,pcxpixels[x]); + } + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + if (lpHead1){ free(lpHead1); lpHead1 = NULL; } + if (lpHead2){ free(lpHead2); lpHead2 = NULL; } + return false; + } + if (lpHead1){ free(lpHead1); lpHead1 = NULL; } + if (lpHead2){ free(lpHead2); lpHead2 = NULL; } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImagePCX::Encode(CxFile * hFile) +{ + if (EncodeSafeCheck(hFile)) return false; + + cx_try + { + PCXHEADER pcxHeader; + memset(&pcxHeader,0,sizeof(pcxHeader)); + pcxHeader.Manufacturer = PCX_MAGIC; + pcxHeader.Version = 5; + pcxHeader.Encoding = 1; + pcxHeader.Xmin = 0; + pcxHeader.Ymin = 0; + pcxHeader.Xmax = (uint16_t)head.biWidth-1; + pcxHeader.Ymax = (uint16_t)head.biHeight-1; + pcxHeader.Hres = (uint16_t)info.xDPI; + pcxHeader.Vres = (uint16_t)info.yDPI; + pcxHeader.Reserved = 0; + pcxHeader.PaletteType = head.biClrUsed==0; + + switch(head.biBitCount){ + case 24: + case 8: + { + pcxHeader.BitsPerPixel = 8; + pcxHeader.ColorPlanes = head.biClrUsed==0 ? 3 : 1; +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid() && head.biClrUsed==0) pcxHeader.ColorPlanes =4; +#endif //CXIMAGE_SUPPORT_ALPHA + pcxHeader.BytesPerLine = (uint16_t)head.biWidth; + break; + } + default: //(4 1) + pcxHeader.BitsPerPixel = 1; + pcxHeader.ColorPlanes = head.biClrUsed==16 ? 4 : 1; + pcxHeader.BytesPerLine = (uint16_t)((head.biWidth * pcxHeader.BitsPerPixel + 7)>>3); + } + + if (pcxHeader.BitsPerPixel == 1 && pcxHeader.ColorPlanes == 1){ + pcxHeader.ColorMap[0][0] = pcxHeader.ColorMap[0][1] = pcxHeader.ColorMap[0][2] = 0; + pcxHeader.ColorMap[1][0] = pcxHeader.ColorMap[1][1] = pcxHeader.ColorMap[1][2] = 255; + } + if (pcxHeader.BitsPerPixel == 1 && pcxHeader.ColorPlanes == 4){ + RGBQUAD c; + for (int32_t i = 0; i < 16; i++){ + c=GetPaletteColor(i); + pcxHeader.ColorMap[i][0] = c.rgbRed; + pcxHeader.ColorMap[i][1] = c.rgbGreen; + pcxHeader.ColorMap[i][2] = c.rgbBlue; + } + } + + pcxHeader.BytesPerLine = (pcxHeader.BytesPerLine + 1)&(~1); + + PCX_toh(&pcxHeader); + if (hFile->Write(&pcxHeader, sizeof(pcxHeader), 1) == 0 ) + cx_throw("cannot write PCX header"); + PCX_toh(&pcxHeader); + + CxMemFile buffer; + buffer.Open(); + + uint8_t c,n; + int32_t x,y; + if (head.biClrUsed==0){ + for (y = head.biHeight-1; y >=0 ; y--){ + for (int32_t p=0; pWrite(buffer.GetBuffer(false),buffer.Tell(),1); + + } else if (head.biBitCount==8) { + + for (y = head.biHeight-1; y >=0 ; y--){ + c=n=0; + for (x = 0; xWrite(buffer.GetBuffer(false),buffer.Tell(),1); + + if (head.biBitCount == 8){ + hFile->PutC(0x0C); + uint8_t* pal = (uint8_t*)malloc(768); + RGBQUAD c; + for (int32_t i=0;i<256;i++){ + c=GetPaletteColor(i); + pal[3*i+0] = c.rgbRed; + pal[3*i+1] = c.rgbGreen; + pal[3*i+2] = c.rgbBlue; + } + hFile->Write(pal,768,1); + free(pal); + } + } else { //(head.biBitCount==4) || (head.biBitCount==1) + + RGBQUAD *rgb = GetPalette(); + bool binvert = false; + if (CompareColors(&rgb[0],&rgb[1])>0) binvert=(head.biBitCount==1); + + uint8_t* plane = (uint8_t*)malloc(pcxHeader.BytesPerLine); + uint8_t* raw = (uint8_t*)malloc(head.biWidth); + + for(y = head.biHeight-1; y >=0 ; y--) { + + for( x = 0; x < head.biWidth; x++) raw[x] = (uint8_t)GetPixelIndex(x,y); + + if (binvert) for( x = 0; x < head.biWidth; x++) raw[x] = 1-raw[x]; + + for( x = 0; x < pcxHeader.ColorPlanes; x++ ) { + PCX_PixelsToPlanes(raw, head.biWidth, plane, x); + PCX_PackPlanes(plane, pcxHeader.BytesPerLine, buffer); + } + } + + free(plane); + free(raw); + + hFile->Write(buffer.GetBuffer(false),buffer.Tell(),1); + + } + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + return false; + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +// Convert multi-plane format into 1 pixel per byte +// from unpacked file data bitplanes[] into pixel row pixels[] +// image Height rows, with each row having planes image planes each +// bytesperline bytes +bool CxImagePCX::PCX_PlanesToPixels(uint8_t * pixels, uint8_t * bitplanes, int16_t bytesperline, int16_t planes, int16_t bitsperpixel) +{ + int32_t i, j, npixels; + uint8_t * p; + if (planes > 4) return false; + if (bitsperpixel != 1) return false; + + // Clear the pixel buffer + npixels = (bytesperline * 8) / bitsperpixel; + p = pixels; + while (--npixels >= 0) *p++ = 0; + + // Do the format conversion + for (i = 0; i < planes; i++){ + int32_t pixbit, bits, mask; + p = pixels; + pixbit = (1 << i); // pixel bit for this plane + for (j = 0; j < bytesperline; j++){ + bits = *bitplanes++; + for (mask = 0X80; mask != 0; mask >>= 1, p++) + if (bits & mask) *p |= pixbit; + } + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +// convert packed pixel format into 1 pixel per byte +// from unpacked file data bitplanes[] into pixel row pixels[] +// image Height rows, with each row having planes image planes each +// bytesperline bytes +bool CxImagePCX::PCX_UnpackPixels(uint8_t * pixels, uint8_t * bitplanes, int16_t bytesperline, int16_t planes, int16_t bitsperpixel) +{ + register int32_t bits; + if (planes != 1) return false; + + if (bitsperpixel == 8){ // 8 bits/pixels, no unpacking needed + while (bytesperline-- > 0) *pixels++ = *bitplanes++; + } else if (bitsperpixel == 4){ // 4 bits/pixel, two pixels per byte + while (bytesperline-- > 0){ + bits = *bitplanes++; + *pixels++ = (uint8_t)((bits >> 4) & 0X0F); + *pixels++ = (uint8_t)((bits) & 0X0F); + } + } else if (bitsperpixel == 2){ // 2 bits/pixel, four pixels per byte + while (bytesperline-- > 0){ + bits = *bitplanes++; + *pixels++ = (uint8_t)((bits >> 6) & 0X03); + *pixels++ = (uint8_t)((bits >> 4) & 0X03); + *pixels++ = (uint8_t)((bits >> 2) & 0X03); + *pixels++ = (uint8_t)((bits) & 0X03); + } + } else if (bitsperpixel == 1){ // 1 bits/pixel, 8 pixels per byte + while (bytesperline-- > 0){ + bits = *bitplanes++; + *pixels++ = ((bits & 0X80) != 0); + *pixels++ = ((bits & 0X40) != 0); + *pixels++ = ((bits & 0X20) != 0); + *pixels++ = ((bits & 0X10) != 0); + *pixels++ = ((bits & 0X08) != 0); + *pixels++ = ((bits & 0X04) != 0); + *pixels++ = ((bits & 0X02) != 0); + *pixels++ = ((bits & 0X01) != 0); + } + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/* PCX_PackPixels(const int32_t p,uint8_t &c, uint8_t &n, int32_t &l, CxFile &f) + * p = current pixel (-1 ends the line -2 ends odd line) + * c = previous pixel + * n = number of consecutive pixels + */ +void CxImagePCX::PCX_PackPixels(const int32_t p,uint8_t &c, uint8_t &n, CxFile &f) +{ + if (p!=c && n){ + if (n==1 && c<0xC0){ + f.PutC(c); + } else { + f.PutC(0xC0|n); + f.PutC(c); + } + n=0; + } + if (n==0x3F) { + f.PutC(0xFF); + f.PutC(c); + n=0; + } + if (p==-2) f.PutC(0); + c=(uint8_t)p; + n++; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImagePCX::PCX_PackPlanes(uint8_t* buff, const int32_t size, CxFile &f) +{ + uint8_t *start,*end; + uint8_t c, previous, count; + + start = buff; + end = buff + size; + previous = *start++; + count = 1; + + while (start < end) { + c = *start++; + if (c == previous && count < 63) { + ++count; + continue; + } + + if (count > 1 || (previous & 0xc0) == 0xc0) { + f.PutC( count | 0xc0 ); + } + f.PutC(previous); + previous = c; + count = 1; + } + + if (count > 1 || (previous & 0xc0) == 0xc0) { + count |= 0xc0; + f.PutC(count); + } + f.PutC(previous); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImagePCX::PCX_PixelsToPlanes(uint8_t* raw, int32_t width, uint8_t* buf, int32_t plane) +{ + int32_t cbit, x, mask; + uint8_t *cp = buf-1; + + mask = 1 << plane; + cbit = -1; + for( x = 0; x < width; x++ ) { + if( cbit < 0 ) { + cbit = 7; + *++cp = 0; + } + if( raw[x] & mask ) + *cp |= (1<Xmin = m_ntohs(p->Xmin); + p->Ymin = m_ntohs(p->Ymin); + p->Xmax = m_ntohs(p->Xmax); + p->Ymax = m_ntohs(p->Ymax); + p->Hres = m_ntohs(p->Hres); + p->Vres = m_ntohs(p->Vres); + p->BytesPerLine = m_ntohs(p->BytesPerLine); + p->PaletteType = m_ntohs(p->PaletteType); +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_PCX diff --git a/DuiLib/3rd/CxImage/ximapcx.h b/DuiLib/3rd/CxImage/ximapcx.h new file mode 100644 index 0000000..0463d2f --- /dev/null +++ b/DuiLib/3rd/CxImage/ximapcx.h @@ -0,0 +1,64 @@ +/* + * File: ximapcx.h + * Purpose: PCX Image Class Loader and Writer + */ +/* ========================================================== + * CxImagePCX (c) 05/Jan/2002 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * + * Parts of the code come from Paintlib: Copyright (c) 1996-1998 Ulrich von Zadow + * ========================================================== + */ +#if !defined(__ximaPCX_h) +#define __ximaPCX_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_PCX + +class CxImagePCX: public CxImage +{ +// PCX Image File +#pragma pack(1) +typedef struct tagPCXHEADER +{ + char Manufacturer; // always 0X0A + char Version; // version number + char Encoding; // always 1 + char BitsPerPixel; // color bits + uint16_t Xmin, Ymin; // image origin + uint16_t Xmax, Ymax; // image dimensions + uint16_t Hres, Vres; // resolution values + uint8_t ColorMap[16][3]; // color palette + char Reserved; + char ColorPlanes; // color planes + uint16_t BytesPerLine; // line buffer size + uint16_t PaletteType; // grey or color palette + char Filter[58]; +} PCXHEADER; +#pragma pack() + +public: + CxImagePCX(): CxImage(CXIMAGE_FORMAT_PCX) {} + +// bool Load(const TCHAR * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_PCX);} +// bool Save(const TCHAR * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_PCX);} + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +#endif // CXIMAGE_SUPPORT_ENCODE +protected: + bool PCX_PlanesToPixels(uint8_t * pixels, uint8_t * bitplanes, int16_t bytesperline, int16_t planes, int16_t bitsperpixel); + bool PCX_UnpackPixels(uint8_t * pixels, uint8_t * bitplanes, int16_t bytesperline, int16_t planes, int16_t bitsperpixel); + void PCX_PackPixels(const int32_t p,uint8_t &c, uint8_t &n, CxFile &f); + void PCX_PackPlanes(uint8_t* buff, const int32_t size, CxFile &f); + void PCX_PixelsToPlanes(uint8_t* raw, int32_t width, uint8_t* buf, int32_t plane); + void PCX_toh(PCXHEADER* p); +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximapng.cpp b/DuiLib/3rd/CxImage/ximapng.cpp new file mode 100644 index 0000000..27a916c --- /dev/null +++ b/DuiLib/3rd/CxImage/ximapng.cpp @@ -0,0 +1,553 @@ +/* + * File: ximapng.cpp + * Purpose: Platform Independent PNG Image Class Loader and Writer + * 07/Aug/2001 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximapng.h" + +#if CXIMAGE_SUPPORT_PNG + +#include "ximaiter.h" + +//////////////////////////////////////////////////////////////////////////////// +void CxImagePNG::ima_png_error(png_struct *png_ptr, char *message) +{ + strcpy(info.szLastError,message); + longjmp(png_ptr->png_jmpbuf, 1); +} +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +void CxImagePNG::expand2to4bpp(uint8_t* prow) +{ + uint8_t *psrc,*pdst; + uint8_t pos,idx; + for(int32_t x=head.biWidth-1;x>=0;x--){ + psrc = prow + ((2*x)>>3); + pdst = prow + ((4*x)>>3); + pos = (uint8_t)(2*(3-x%4)); + idx = (uint8_t)((*psrc & (0x03<>pos); + pos = (uint8_t)(4*(1-x%2)); + *pdst &= ~(0x0F<png_jmpbuf)) { + /* Free all of the memory associated with the png_ptr and info_ptr */ + delete [] row_pointers; + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + cx_throw(""); + } + + // use custom I/O functions + png_set_read_fn(png_ptr, hFile, /*(png_rw_ptr)*/user_read_data); + png_set_error_fn(png_ptr,info.szLastError,/*(png_error_ptr)*/user_error_fn,NULL); + + /* read the file information */ + png_read_info(png_ptr, info_ptr); + + if (info.nEscape == -1){ + head.biWidth = info_ptr->width; + head.biHeight= info_ptr->height; + info.dwType = CXIMAGE_FORMAT_PNG; + longjmp(png_ptr->png_jmpbuf, 1); + } + + /* calculate new number of channels */ + int32_t channels=0; + switch(info_ptr->color_type){ + case PNG_COLOR_TYPE_GRAY: + case PNG_COLOR_TYPE_PALETTE: + channels = 1; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + channels = 2; + break; + case PNG_COLOR_TYPE_RGB: + channels = 3; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + channels = 4; + break; + default: + strcpy(info.szLastError,"unknown PNG color type"); + longjmp(png_ptr->png_jmpbuf, 1); + } + + //find the right pixel depth used for cximage + int32_t pixel_depth = info_ptr->pixel_depth; + if (channels == 1 && pixel_depth>8) pixel_depth=8; + if (channels == 2) pixel_depth=8; + if (channels >= 3) pixel_depth=24; + + if (!Create(info_ptr->width, info_ptr->height, pixel_depth, CXIMAGE_FORMAT_PNG)){ + longjmp(png_ptr->png_jmpbuf, 1); + } + + /* get metrics */ + switch (info_ptr->phys_unit_type) + { + case PNG_RESOLUTION_UNKNOWN: + SetXDPI(info_ptr->x_pixels_per_unit); + SetYDPI(info_ptr->y_pixels_per_unit); + break; + case PNG_RESOLUTION_METER: + SetXDPI((int32_t)floor(info_ptr->x_pixels_per_unit * 254.0 / 10000.0 + 0.5)); + SetYDPI((int32_t)floor(info_ptr->y_pixels_per_unit * 254.0 / 10000.0 + 0.5)); + break; + } + + if (info_ptr->num_palette>0){ + SetPalette((rgb_color*)info_ptr->palette,info_ptr->num_palette); + SetClrImportant(info_ptr->num_palette); + } else if (info_ptr->bit_depth ==2) { // needed for 2 bpp grayscale PNGs + SetPaletteColor(0,0,0,0); + SetPaletteColor(1,85,85,85); + SetPaletteColor(2,170,170,170); + SetPaletteColor(3,255,255,255); + } else SetGrayPalette(); // needed for grayscale PNGs + + int32_t nshift = max(0,(info_ptr->bit_depth>>3)-1)<<3; + + if (info_ptr->num_trans!=0){ //palette transparency + if (info_ptr->num_trans==1){ + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE){ + info.nBkgndIndex = info_ptr->trans_color.index; + } else{ + info.nBkgndIndex = info_ptr->trans_color.gray>>nshift; + } + } + if (info_ptr->num_trans>1){ + RGBQUAD* pal=GetPalette(); + if (pal){ + uint32_t ip; + for (ip=0;ipnum_trans);ip++) + pal[ip].rgbReserved=info_ptr->trans_alpha[ip]; + for (ip=info_ptr->num_trans;iptrans_color.red>>nshift); + info.nBkgndColor.rgbGreen = (uint8_t)(info_ptr->trans_color.green>>nshift); + info.nBkgndColor.rgbBlue = (uint8_t)(info_ptr->trans_color.blue>>nshift); + info.nBkgndColor.rgbReserved = 0; + info.nBkgndIndex = 0; + } + } + + int32_t alpha_present = (channels - 1) % 2; + if (alpha_present){ +#if CXIMAGE_SUPPORT_ALPHA // + AlphaCreate(); +#else + png_set_strip_alpha(png_ptr); +#endif //CXIMAGE_SUPPORT_ALPHA + } + + // - flip the RGB pixels to BGR (or RGBA to BGRA) + if (info_ptr->color_type & PNG_COLOR_MASK_COLOR){ + png_set_bgr(png_ptr); + } + + // - handle cancel + if (info.nEscape) longjmp(png_ptr->png_jmpbuf, 1); + + // row_bytes is the width x number of channels x (bit-depth / 8) + row_pointers = new uint8_t[info_ptr->rowbytes + 8]; + + // turn on interlace handling + int32_t number_passes = png_set_interlace_handling(png_ptr); + + if (number_passes>1){ + SetCodecOption( (ENCODE_INTERLACE) | GetCodecOption(CXIMAGE_FORMAT_PNG)); + } else { + SetCodecOption(~(ENCODE_INTERLACE) & GetCodecOption(CXIMAGE_FORMAT_PNG)); + } + + int32_t chan_offset = info_ptr->bit_depth >> 3; + int32_t pixel_offset = info_ptr->pixel_depth >> 3; + + for (int32_t pass=0; pass < number_passes; pass++) { + iter.Upset(); + int32_t y=0; + do { + + // - handle cancel + if (info.nEscape) longjmp(png_ptr->png_jmpbuf, 1); + +#if CXIMAGE_SUPPORT_ALPHA // + if (AlphaIsValid()) { + + //compute the correct position of the line + int32_t ax,ay; + ay = head.biHeight-1-y; + uint8_t* prow= iter.GetRow(ay); + + //recover data from previous scan + if (info_ptr->interlace_type && pass>0 && pass!=7){ + for(ax=0;ax RGB + A + for(ax=0;axinterlace_type && pass>0){ + iter.GetRow(row_pointers, info_ptr->rowbytes); + //re-expand buffer for images with bit depth > 8 + if (info_ptr->bit_depth > 8){ + for(int32_t ax=(head.biWidth*channels-1);ax>=0;ax--) + row_pointers[ax*chan_offset] = row_pointers[ax]; + } + } + + //read next row + png_read_row(png_ptr, row_pointers, NULL); + + //shrink 16 bit depth images down to 8 bits + if (info_ptr->bit_depth > 8){ + for(int32_t ax=0;ax<(head.biWidth*channels);ax++) + row_pointers[ax] = row_pointers[ax*chan_offset]; + } + + //copy the pixels + iter.SetRow(row_pointers, info_ptr->rowbytes); + // expand 2 bpp images only in the last pass + if (info_ptr->bit_depth==2 && pass==(number_passes-1)) + expand2to4bpp(iter.GetRow()); + + //go on + iter.PrevRow(); + } + + y++; + } while(ypng_jmpbuf)){ + /* If we get here, we had a problem reading the file */ + if (info_ptr->palette) free(info_ptr->palette); + png_destroy_write_struct(&png_ptr, (png_infopp)&info_ptr); + cx_throw("Error saving PNG file"); + } + + /* set up the output control */ + //png_init_io(png_ptr, hFile); + + // use custom I/O functions + png_set_write_fn(png_ptr,hFile,/*(png_rw_ptr)*/user_write_data,/*(png_flush_ptr)*/user_flush_data); + + /* set the file information here */ + info_ptr->width = GetWidth(); + info_ptr->height = GetHeight(); + info_ptr->pixel_depth = (uint8_t)GetBpp(); + info_ptr->channels = (GetBpp()>8) ? (uint8_t)3: (uint8_t)1; + info_ptr->bit_depth = (uint8_t)(GetBpp()/info_ptr->channels); + info_ptr->compression_type = info_ptr->filter_type = 0; + info_ptr->valid = 0; + + // set interlace type + DWORD codec_opt = GetCodecOption(CXIMAGE_FORMAT_PNG); + if (codec_opt & CxImagePNG::ENCODE_INTERLACE) + info_ptr->interlace_type = PNG_INTERLACE_ADAM7; + else + info_ptr->interlace_type = PNG_INTERLACE_NONE; + + /* set compression level */ + int32_t compress_level; + switch (codec_opt & CxImagePNG::ENCODE_COMPRESSION_MASK) + { + case ENCODE_NO_COMPRESSION: + compress_level = Z_NO_COMPRESSION; + break; + case ENCODE_BEST_SPEED: + compress_level = Z_BEST_SPEED; + break; + case ENCODE_BEST_COMPRESSION: + compress_level = Z_BEST_COMPRESSION; + break; + default: + compress_level = Z_DEFAULT_COMPRESSION; + break; + } + png_set_compression_level(png_ptr, compress_level); + + bool bGrayScale = IsGrayScale(); + + if (GetNumColors()){ + if (bGrayScale){ + info_ptr->color_type = PNG_COLOR_TYPE_GRAY; + } else { + info_ptr->color_type = PNG_COLOR_TYPE_PALETTE; + } + } else { + info_ptr->color_type = PNG_COLOR_TYPE_RGB; + } +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()){ + info_ptr->color_type |= PNG_COLOR_MASK_ALPHA; + info_ptr->channels++; + info_ptr->bit_depth = 8; + info_ptr->pixel_depth += 8; + } +#endif + + /* set background */ + png_color_16 image_background={ 0, 255, 255, 255, 0 }; + RGBQUAD tc = GetTransColor(); + if (info.nBkgndIndex>=0) { + image_background.blue = tc.rgbBlue; + image_background.green = tc.rgbGreen; + image_background.red = tc.rgbRed; + } + png_set_bKGD(png_ptr, info_ptr, &image_background); + + /* set metrics */ + png_set_pHYs(png_ptr, info_ptr, head.biXPelsPerMeter, head.biYPelsPerMeter, PNG_RESOLUTION_METER); + + png_set_IHDR(png_ptr, info_ptr, info_ptr->width, info_ptr->height, info_ptr->bit_depth, + info_ptr->color_type, info_ptr->interlace_type, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + // simple transparency + if (info.nBkgndIndex >= 0){ + info_ptr->num_trans = 1; + info_ptr->valid |= PNG_INFO_tRNS; + info_ptr->trans_alpha = trans; + info_ptr->trans_color.index = (uint8_t)info.nBkgndIndex; + info_ptr->trans_color.red = tc.rgbRed; + info_ptr->trans_color.green = tc.rgbGreen; + info_ptr->trans_color.blue = tc.rgbBlue; + info_ptr->trans_color.gray = info_ptr->trans_color.index; + + // the transparency indexes start from 0 for non grayscale palette + if (!bGrayScale && head.biClrUsed && info.nBkgndIndex) + SwapIndex(0,(uint8_t)info.nBkgndIndex); + } + + /* set the palette if there is one */ + if (GetPalette()){ + if (!bGrayScale){ + info_ptr->valid |= PNG_INFO_PLTE; + } + + int32_t nc = GetClrImportant(); + if (nc==0) nc = GetNumColors(); + + if (info.bAlphaPaletteEnabled){ + for(uint16_t ip=0; ipnum_trans = (uint16_t)nc; + info_ptr->valid |= PNG_INFO_tRNS; + info_ptr->trans_alpha = trans; + } + + // copy the palette colors + info_ptr->palette = new png_color[nc]; + info_ptr->num_palette = (png_uint_16) nc; + for (int32_t i=0; ipalette[i].red, &info_ptr->palette[i].green, &info_ptr->palette[i].blue); + } + +#if CXIMAGE_SUPPORT_ALPHA // + //Merge the transparent color with the alpha channel + if (AlphaIsValid() && head.biBitCount==24 && info.nBkgndIndex>=0){ + for(int32_t y=0; y < head.biHeight; y++){ + for(int32_t x=0; x < head.biWidth ; x++){ + RGBQUAD c=GetPixelColor(x,y,false); + if (*(int32_t*)&c==*(int32_t*)&tc) + AlphaSet(x,y,0); + } } } +#endif // CXIMAGE_SUPPORT_ALPHA // + + int32_t row_size = max(info.dwEffWidth, info_ptr->width*info_ptr->channels*(info_ptr->bit_depth/8)); + info_ptr->rowbytes = row_size; + uint8_t *row_pointers = new uint8_t[row_size]; + + /* write the file information */ + png_write_info(png_ptr, info_ptr); + + //interlace handling + int32_t num_pass = png_set_interlace_handling(png_ptr); + for (int32_t pass = 0; pass < num_pass; pass++){ + //write image + iter.Upset(); + int32_t ay=head.biHeight-1; + do { +#if CXIMAGE_SUPPORT_ALPHA // + RGBQUAD c; + if (AlphaIsValid()){ + for (int32_t ax=head.biWidth-1; ax>=0;ax--){ + c = BlindGetPixelColor(ax,ay); + int32_t px = ax * info_ptr->channels; + if (!bGrayScale){ + row_pointers[px++]=c.rgbRed; + row_pointers[px++]=c.rgbGreen; + } + row_pointers[px++]=c.rgbBlue; + row_pointers[px] = AlphaGet(ax,ay); + } + png_write_row(png_ptr, row_pointers); + ay--; + } + else +#endif //CXIMAGE_SUPPORT_ALPHA // + { + iter.GetRow(row_pointers, row_size); + if (info_ptr->color_type == PNG_COLOR_TYPE_RGB) //HACK BY OP + RGBtoBGR(row_pointers, row_size); + png_write_row(png_ptr, row_pointers); + } + } while(iter.PrevRow()); + } + + delete [] row_pointers; + row_pointers = NULL; + + //if necessary, restore the original palette + if (!bGrayScale && head.biClrUsed && info.nBkgndIndex>0) + SwapIndex((uint8_t)info.nBkgndIndex,0); + + /* It is REQUIRED to call this to finish writing the rest of the file */ + png_write_end(png_ptr, info_ptr); + + /* if you malloced the palette, free it here */ + if (info_ptr->palette){ + delete [] (info_ptr->palette); + info_ptr->palette = NULL; + } + + /* clean up after the write, and free any memory allocated */ + png_destroy_write_struct(&png_ptr, (png_infopp)&info_ptr); + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + return FALSE; + } + /* that's it */ + return TRUE; +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_PNG diff --git a/DuiLib/3rd/CxImage/ximapng.h b/DuiLib/3rd/CxImage/ximapng.h new file mode 100644 index 0000000..369265a --- /dev/null +++ b/DuiLib/3rd/CxImage/ximapng.h @@ -0,0 +1,94 @@ +/* + * File: ximapng.h + * Purpose: PNG Image Class Loader and Writer + */ +/* ========================================================== + * CxImagePNG (c) 07/Aug/2001 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * + * Special thanks to Troels Knakkergaard for new features, enhancements and bugfixes + * + * original CImagePNG and CImageIterator implementation are: + * Copyright: (c) 1995, Alejandro Aguilar Sierra + * + * libpng Copyright (c) 1998-2003 Glenn Randers-Pehrson + * ========================================================== + */ +#if !defined(__ximaPNG_h) +#define __ximaPNG_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_PNG + +extern "C" { +#ifdef _LINUX + #undef _DLL + #include + #include + #include +#else + #include "../png/png.h" + #include "../png/pngstruct.h" + #include "../png/pnginfo.h" +#endif +} + +class CxImagePNG: public CxImage +{ +public: + CxImagePNG(): CxImage(CXIMAGE_FORMAT_PNG) {} + +// bool Load(const TCHAR * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_PNG);} +// bool Save(const TCHAR * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_PNG);} + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +#endif // CXIMAGE_SUPPORT_ENCODE + + enum CODEC_OPTION + { + ENCODE_INTERLACE = 0x01, + // Exclusive compression types : 3 bit wide field + ENCODE_COMPRESSION_MASK = 0x0E, + ENCODE_NO_COMPRESSION = 1 << 1, + ENCODE_BEST_SPEED = 2 << 1, + ENCODE_BEST_COMPRESSION = 3 << 1, + ENCODE_DEFAULT_COMPRESSION = 4 << 1 + }; + +protected: + void ima_png_error(png_struct *png_ptr, char *message); + void expand2to4bpp(uint8_t* prow); + + static void PNGAPI user_read_data(png_structp png_ptr, png_bytep data, png_size_t length) + { + CxFile* hFile = (CxFile*)png_get_io_ptr(png_ptr); + if (hFile == NULL || hFile->Read(data,1,length) != length) png_error(png_ptr, "Read Error"); + } + + static void PNGAPI user_write_data(png_structp png_ptr, png_bytep data, png_size_t length) + { + CxFile* hFile = (CxFile*)png_get_io_ptr(png_ptr); + if (hFile == NULL || hFile->Write(data,1,length) != length) png_error(png_ptr, "Write Error"); + } + + static void PNGAPI user_flush_data(png_structp png_ptr) + { + CxFile* hFile = (CxFile*)png_get_io_ptr(png_ptr); + if (hFile == NULL || !hFile->Flush()) png_error(png_ptr, "Flush Error"); + } + + static void PNGAPI user_error_fn(png_structp png_ptr,png_const_charp error_msg) + { + strncpy((char*)png_ptr->error_ptr,error_msg,255); + longjmp(png_ptr->png_jmpbuf, 1); + } +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximapsd.cpp b/DuiLib/3rd/CxImage/ximapsd.cpp new file mode 100644 index 0000000..34a2c05 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximapsd.cpp @@ -0,0 +1,1310 @@ +/* + * File: ximapsd.cpp + * Purpose: Platform Independent PSD Image Class Loader + * Dec/2010 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + * + * libpsd (c) 2004-2007 Graphest Software + * + * Based on MyPSD class by Iosif Hamlatzis + * Details: http://www.codeproject.com/KB/graphics/MyPSD.aspx + * Cleaned up a bit and ported to CxImage by Vitaly Ovchinnikov + * Send feedback to vitaly(dot)ovchinnikov(at)gmail.com + */ + +#include "ximapsd.h" + +#if CXIMAGE_SUPPORT_PSD + +enum { + PSD_FILE_HEADER, + PSD_COLOR_MODE_DATA, + PSD_IMAGE_RESOURCE, + PSD_LAYER_AND_MASK_INFORMATION, + PSD_IMAGE_DATA, + PSD_DONE +}; + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_USE_LIBPSD == 0 +// MyPSD.h ///////////////////////////////////////////////////////////////////// + +#ifndef __MyPSD_H__ +#define __MyPSD_H__ + +namespace MyPSD +{ + + class CPSD + { + struct HEADER_INFO + { + //Table 2-12: HeaderInfo Color spaces + // Color-ID Name Description + //------------------------------------------- + // 0 Bitmap // Probably means black & white + // 1 Grayscale The first value in the color data is the gray value, from 0...10000. + // 2 Indexed + // 3 RGB The first three values in the color data are red, green, and blue. + // They are full unsigned 16bit values as in Apples RGBColor data + // structure. Pure red=65535,0,0. + // 4 CMYK The four values in the color data are cyan, magenta, yellow, and + // black. They are full unsigned 16bit values. 0=100% ink. Pure + // cyan=0,65535,65535,65535. + // 7 Multichannel // Have no idea + // 8 Duotone + // 9 Lab The first three values in the color data are lightness, a chrominance, + // and b chrominance. + // Lightness is a 16bit value from 0...100. The chromanance components + // are each 16bit values from 128...127. Gray values + // are represented by chrominance components of 0. Pure + // white=100,0,0. + short nChannels; + int nHeight; + int nWidth; + short nBitsPerPixel; + short nColourMode; + HEADER_INFO(); + }; + + struct COLOUR_MODE_DATA + { + int nLength; + unsigned char* ColourData; + COLOUR_MODE_DATA(); + }; + + + struct IMAGE_RESOURCE + { + // Table 21: Image resource block + // Type Name Description + //------------------------------------------- + // OSType Type Photoshop always uses its signature, 8BIM + // int16 ID Unique identifier + // PString Name A pascal string, padded to make size even (a null name consists of two bytes of 0) + // Pascal style string where the first byte gives the length of the + // string and the content bytes follow. + // int32 Size Actual size of resource data. This does not include the + // Type, ID, Name, or Size fields. + // Variable Data Resource data, padded to make size even + int nLength; + char OSType[4]; + short nID; + unsigned char* Name; + int nSize; + IMAGE_RESOURCE(); + void Reset(); + }; + + struct RESOLUTION_INFO + { + // Table A-6: ResolutionInfo structure + // Type Name Description + //------------------------------------------- + // Fixed hRes Horizontal resolution in pixels per inch. + // int hResUnit 1=display horizontal resolution in pixels per inch; + // 2=display horizontal resolution in pixels per cm. + // short widthUnit Display width as 1=inches; 2=cm; 3=points; 4=picas; 5=columns. + // Fixed vRes Vertical resolution in pixels per inch. + // int vResUnit 1=display vertical resolution in pixels per inch; + // 2=display vertical resolution in pixels per cm. + // short heightUnit Display height as 1=inches; 2=cm; 3=points; 4=picas; 5=columns. + short hRes; + int hResUnit; + short widthUnit; + + short vRes; + int vResUnit; + short heightUnit; + RESOLUTION_INFO(); + }; + + struct RESOLUTION_INFO_v2 // Obsolete - Photoshop 2.0 + { + short nChannels; + short nRows; + short nColumns; + short nDepth; + short nMode; + RESOLUTION_INFO_v2(); + }; + + struct DISPLAY_INFO + { + // This structure contains display information about each channel. + //Table A-7: DisplayInfo Color spaces + // Color-ID Name Description + //------------------------------------------- + // 0 RGB The first three values in the color data are red, green, and blue. + // They are full unsigned 16bit values as in Apples RGBColor data + // structure. Pure red=65535,0,0. + // 1 HSB The first three values in the color data are hue, saturation, and + // brightness. They are full unsigned 16bit values as in Apples + // HSVColor data structure. Pure red=0,65535, 65535. + // 2 CMYK The four values in the color data are cyan, magenta, yellow, and + // black. They are full unsigned 16bit values. 0=100% ink. Pure + // cyan=0,65535,65535,65535. + // 7 Lab The first three values in the color data are lightness, a chrominance, + // and b chrominance. + // Lightness is a 16bit value from 0...10000. The chromanance components + // are each 16bit values from 12800...12700. Gray values + // are represented by chrominance components of 0. Pure + // white=10000,0,0. + // 8 grayscale The first value in the color data is the gray value, from 0...10000. + short ColourSpace; + short Colour[4]; + short Opacity; // 0..100 + bool kind; // selected = 0, protected = 1 + unsigned char padding; // should be zero + DISPLAY_INFO(); + }; + struct THUMBNAIL + { + // Adobe Photoshop 5.0 and later stores thumbnail information for preview + // display in an image resource block. These resource blocks consist of an + // 28 byte header, followed by a JFIF thumbnail in RGB (red, green, blue) + // for both Macintosh and Windows. Adobe Photoshop 4.0 stored the + // thumbnail information in the same format except the data section is + // (blue, green, red). The Adobe Photoshop 4.0 format is at resource ID + // and the Adobe Photoshop 5.0 format is at resource ID 1036. + // Table 25: Thumnail resource header + // Type Name Description + //------------------------------------------- + // 4 bytes format = 1 (kJpegRGB). Also supports kRawRGB (0). + // 4 bytes width Width of thumbnail in pixels. + // 4 bytes height Height of thumbnail in pixels. + // 4 bytes widthbytes Padded row bytes as (width * bitspixel + 31) / 32 * 4. + // 4 bytes size Total size as widthbytes * height * planes + // 4 bytes compressedsize Size after compression. Used for consistentcy check. + // 2 bytes bitspixel = 24. Bits per pixel. + // 2 bytes planes = 1. Number of planes. + // Variable Data JFIF data in RGB format. + // Note: For resource ID 1033 the data is in BGR format. + int nFormat; + int nWidth; + int nHeight; + int nWidthBytes; + int nSize; + int nCompressedSize; + short nBitPerPixel; + short nPlanes; + unsigned char* Data; + THUMBNAIL(); + }; + + + CxImage &m_image; + + HEADER_INFO header_info; + + COLOUR_MODE_DATA colour_mode_data; + short mnColourCount; + short mnTransparentIndex; + + IMAGE_RESOURCE image_resource; + + int mnGlobalAngle; + + RESOLUTION_INFO resolution_info; + bool mbResolutionInfoFilled; + + RESOLUTION_INFO_v2 resolution_info_v2; + bool mbResolutionInfoFilled_v2; + + DISPLAY_INFO display_info; + bool mbDisplayInfoFilled; + + THUMBNAIL thumbnail; + bool mbThumbNailFilled; + + bool mbCopyright; + + int Calculate(unsigned char* c, int nDigits); + void XYZToRGB(const double X, const double Y, const double Z, int &R, int &G, int &B); + void LabToRGB(const int L, const int a, const int b, int &R, int &G, int &B ); + void CMYKToRGB(const double C, const double M, const double Y, const double K, int &R, int &G, int &B); + + bool ReadHeader(CxFile &f, HEADER_INFO& header_info); + bool ReadColourModeData(CxFile &f, COLOUR_MODE_DATA& colour_mode_data); + bool ReadImageResource(CxFile &f, IMAGE_RESOURCE& image_resource); + bool ReadLayerAndMaskInfoSection(CxFile &f); // Actually ignore it + int ReadImageData(CxFile &f); + + int DecodeRawData(CxFile &pFile); + int DecodeRLEData(CxFile &pFile); + + void ProccessBuffer(unsigned char* pData = 0); + + public: + CPSD(CxImage &image); + ~CPSD(); + + int Load(LPCTSTR szPathName); + int Load(CxFile &file); + + bool ThumbNailIncluded() const { return mbThumbNailFilled; } + void DPI(int &x, int &y) const { x = resolution_info.hRes; y = resolution_info.vRes; } + void Dimensions(int &cx, int &cy) const { cx = header_info.nWidth; cy = header_info.nHeight; } + int BitsPerPixel() const { return header_info.nBitsPerPixel; } + int GlobalAngle() const { return mnGlobalAngle; } + bool IsCopyright() const { return mbCopyright; } + HBITMAP Detach(); + }; +} + +#endif // __MyPSD_H__ + +// MyPSD.cpp /////////////////////////////////////////////////////////////////// + + +inline int dti(double value) { return (int)floor(value+.5f); } + +#define assert(a) + +#define mypsd_fread(a, b, c, d) d.Read(a, b, c) +#define mypsd_fseek(a, b, c) a.Seek(b, c) +#define mypsd_feof(a) a.Eof() + +namespace MyPSD +{ + CPSD::CPSD(CxImage &image) : m_image(image) + { + mbThumbNailFilled = false; + mbDisplayInfoFilled = false; + mbResolutionInfoFilled = false; + mbResolutionInfoFilled_v2 = false; + mnGlobalAngle = 30; + mbCopyright = false; + mnColourCount = -1; + mnTransparentIndex = -1; + } + CPSD::~CPSD() + { + // free memory + if ( 0 < colour_mode_data.nLength ) + delete[] colour_mode_data.ColourData; + colour_mode_data.ColourData = 0; + + if ( image_resource.Name ) + delete[] image_resource.Name; + image_resource.Name = 0; + } + + int CPSD::Calculate(unsigned char* c, int nDigits) + { + int nValue = 0; + + for(int n = 0; n < nDigits; ++n) + nValue = ( nValue << 8 ) | *(c+n); + + return nValue; + }; + + void CPSD::XYZToRGB(const double X, const double Y, const double Z, int &R, int &G, int &B) + { + // Standards used Observer = 2, Illuminant = D65 + // ref_X = 95.047, ref_Y = 100.000, ref_Z = 108.883 + const double ref_X = 95.047; + const double ref_Y = 100.000; + const double ref_Z = 108.883; + + double var_X = X / 100.0; + double var_Y = Y / 100.0; + double var_Z = Z / 100.0; + + double var_R = var_X * 3.2406 + var_Y * (-1.5372) + var_Z * (-0.4986); + double var_G = var_X * (-0.9689) + var_Y * 1.8758 + var_Z * 0.0415; + double var_B = var_X * 0.0557 + var_Y * (-0.2040) + var_Z * 1.0570; + + if ( var_R > 0.0031308 ) + var_R = 1.055 * ( pow(var_R, 1/2.4) ) - 0.055; + else + var_R = 12.92 * var_R; + + if ( var_G > 0.0031308 ) + var_G = 1.055 * ( pow(var_G, 1/2.4) ) - 0.055; + else + var_G = 12.92 * var_G; + + if ( var_B > 0.0031308 ) + var_B = 1.055 * ( pow(var_B, 1/2.4) )- 0.055; + else + var_B = 12.92 * var_B; + + R = (int)(var_R * 256.0); + G = (int)(var_G * 256.0); + B = (int)(var_B * 256.0); + }; + + void CPSD::LabToRGB(const int L, const int a, const int b, int &R, int &G, int &B ) + { + // For the conversion we first convert values to XYZ and then to RGB + // Standards used Observer = 2, Illuminant = D65 + // ref_X = 95.047, ref_Y = 100.000, ref_Z = 108.883 + const double ref_X = 95.047; + const double ref_Y = 100.000; + const double ref_Z = 108.883; + + double var_Y = ( (double)L + 16.0 ) / 116.0; + double var_X = (double)a / 500.0 + var_Y; + double var_Z = var_Y - (double)b / 200.0; + + if ( pow(var_Y, 3) > 0.008856 ) + var_Y = pow(var_Y, 3); + else + var_Y = ( var_Y - 16 / 116 ) / 7.787; + + if ( pow(var_X, 3) > 0.008856 ) + var_X = pow(var_X, 3); + else + var_X = ( var_X - 16 / 116 ) / 7.787; + + if ( pow(var_Z, 3) > 0.008856 ) + var_Z = pow(var_Z, 3); + else + var_Z = ( var_Z - 16 / 116 ) / 7.787; + + double X = ref_X * var_X; + double Y = ref_Y * var_Y; + double Z = ref_Z * var_Z; + + XYZToRGB(X, Y, Z, R, G, B); + }; + + void CPSD::CMYKToRGB(const double C, const double M, const double Y, const double K, int &R, int &G, int &B ) + { + R = dti( ( 1.0f - ( C *( 1.0f - K ) + K ) ) * 255.0f ); + G = dti( ( 1.0f - ( M *( 1.0f - K ) + K ) ) * 255.0f ); + B = dti( ( 1.0f - ( Y *( 1.0f - K ) + K ) ) * 255.0f ); + }; + + bool CPSD::ReadLayerAndMaskInfoSection(CxFile &pFile) // Actually ignore it + { + bool bSuccess = false; + + unsigned char DataLength[4]; + int nBytesRead = 0; + int nItemsRead = (int)(int)mypsd_fread(&DataLength, sizeof(DataLength), 1, pFile); + + int nTotalBytes = Calculate( DataLength, sizeof(DataLength) ); + + unsigned char data[1]; + while( !mypsd_feof( pFile ) && ( nBytesRead < nTotalBytes ) ) + { + data[0] = '\0'; + nItemsRead = (int)(int)mypsd_fread(&data, sizeof(data), 1, pFile); + nBytesRead += nItemsRead * sizeof(data); + } + + assert ( nBytesRead == nTotalBytes ); + if ( nBytesRead == nTotalBytes ) + bSuccess = true; + + return bSuccess; + } + bool CPSD::ReadImageResource(CxFile &pFile, IMAGE_RESOURCE& image_resource) + { + bool bSuccess = false; + + unsigned char Length[4]; + int nItemsRead = (int)(int)mypsd_fread(&Length, sizeof(Length), 1, pFile); + + image_resource.nLength = Calculate( Length, sizeof(image_resource.nLength) ); + + int nBytesRead = 0; + int nTotalBytes = image_resource.nLength; + + while( !mypsd_feof( pFile ) && ( nBytesRead < nTotalBytes ) ) + { + nItemsRead = 0; + image_resource.Reset(); + + nItemsRead = (int)(int)mypsd_fread(&image_resource.OSType, sizeof(image_resource.OSType), 1, pFile); + nBytesRead += nItemsRead * sizeof(image_resource.OSType); + + assert ( 0 == (nBytesRead % 2) ); + if (::memcmp(image_resource.OSType, "8BIM", 4) == 0) + { + unsigned char ID[2]; + nItemsRead = (int)(int)mypsd_fread(&ID, sizeof(ID), 1, pFile); + nBytesRead += nItemsRead * sizeof(ID); + + image_resource.nID = (short)Calculate( ID, sizeof(ID) ); + + unsigned char SizeOfName; + nItemsRead = (int)(int)mypsd_fread(&SizeOfName, sizeof(SizeOfName), 1, pFile); + nBytesRead += nItemsRead * sizeof(SizeOfName); + + int nSizeOfName = Calculate( &SizeOfName, sizeof(SizeOfName) ); + if ( 0 < nSizeOfName ) + { + image_resource.Name = new unsigned char[nSizeOfName]; + nItemsRead = (int)(int)mypsd_fread(image_resource.Name, nSizeOfName, 1, pFile); + nBytesRead += nItemsRead * nSizeOfName; + } + + if ( 0 == (nSizeOfName % 2) ) + { + nItemsRead = (int)(int)mypsd_fread(&SizeOfName, sizeof(SizeOfName), 1, pFile); + nBytesRead += nItemsRead * sizeof(SizeOfName); + } + + unsigned char Size[4]; + nItemsRead = (int)(int)mypsd_fread(&Size, sizeof(Size), 1, pFile); + nBytesRead += nItemsRead * sizeof(Size); + + image_resource.nSize = Calculate( Size, sizeof(image_resource.nSize) ); + + if ( 0 != (image_resource.nSize % 2) ) // resource data must be even + image_resource.nSize++; + if ( 0 < image_resource.nSize ) + { + unsigned char IntValue[4]; + unsigned char ShortValue[2]; + + switch( image_resource.nID ) + { + case 1000: + { + // Obsolete - Photoshop 2.0 + mbResolutionInfoFilled_v2 = true; + + nItemsRead = (int)(int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + resolution_info_v2.nChannels = (short)Calculate(ShortValue, sizeof(resolution_info_v2.nChannels) ); + nItemsRead = (int)(int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + resolution_info_v2.nRows = (short)Calculate(ShortValue, sizeof(resolution_info_v2.nRows) ); + nItemsRead = (int)(int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + resolution_info_v2.nColumns = (short)Calculate(ShortValue, sizeof(resolution_info_v2.nColumns) ); + nItemsRead = (int)(int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + resolution_info_v2.nDepth = (short)Calculate(ShortValue, sizeof(resolution_info_v2.nDepth) ); + nItemsRead = (int)(int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + resolution_info_v2.nMode = (short)Calculate(ShortValue, sizeof(resolution_info_v2.nMode) ); + } + break; + case 1005: + { + mbResolutionInfoFilled = true; + + nItemsRead = (int)(int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + resolution_info.hRes = (short)Calculate(ShortValue, sizeof(resolution_info.hRes) ); + nItemsRead = (int)(int)mypsd_fread(&IntValue, sizeof(IntValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(IntValue); + resolution_info.hResUnit = Calculate(IntValue, sizeof(resolution_info.hResUnit) ); + nItemsRead = (int)(int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + resolution_info.widthUnit = (short)Calculate(ShortValue, sizeof(resolution_info.widthUnit) ); + + nItemsRead = (int)(int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + resolution_info.vRes = (short)Calculate(ShortValue, sizeof(resolution_info.vRes) ); + nItemsRead = (int)(int)mypsd_fread(&IntValue, sizeof(IntValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(IntValue); + resolution_info.vResUnit = Calculate(IntValue, sizeof(resolution_info.vResUnit) ); + nItemsRead = (int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + resolution_info.heightUnit = (short)Calculate(ShortValue, sizeof(resolution_info.heightUnit) ); + } + break; + case 1007: + { + mbDisplayInfoFilled = true; + + nItemsRead = (int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + display_info.ColourSpace = (short)Calculate(ShortValue, sizeof(display_info.ColourSpace) ); + + for ( unsigned int n = 0; n < 4; ++n ) + { + nItemsRead = (int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + display_info.Colour[n] = (short)Calculate(ShortValue, sizeof(display_info.Colour[n]) ); + } + + nItemsRead = (int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + display_info.Opacity = (short)Calculate(ShortValue, sizeof(display_info.Opacity) ); + assert ( 0 <= display_info.Opacity ); + assert ( 100 >= display_info.Opacity ); + + unsigned char c[1]; + nItemsRead = (int)mypsd_fread(&c, sizeof(c), 1, pFile); + nBytesRead += nItemsRead * sizeof(c); + ( 1 == Calculate(c, sizeof(c) ) ) ? display_info.kind = true : display_info.kind = false; + + nItemsRead = (int)mypsd_fread(&c, sizeof(c), 1, pFile); + nBytesRead += nItemsRead * sizeof(c); + display_info.padding = (unsigned int)Calculate(c, sizeof(c) ); + assert ( 0 == display_info.padding ); + } + break; + case 1034: + { + nItemsRead = (int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + ( 1 == Calculate(ShortValue, sizeof(ShortValue) ) ) ? mbCopyright = true : mbCopyright = false; + } + break; + case 1033: + case 1036: + { + mbThumbNailFilled = true; + + nItemsRead = (int)mypsd_fread(&IntValue, sizeof(IntValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(IntValue); + thumbnail.nFormat = Calculate(IntValue, sizeof(thumbnail.nFormat) ); + + nItemsRead = (int)mypsd_fread(&IntValue, sizeof(IntValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(IntValue); + thumbnail.nWidth = Calculate(IntValue, sizeof(thumbnail.nWidth) ); + + nItemsRead = (int)mypsd_fread(&IntValue, sizeof(IntValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(IntValue); + thumbnail.nHeight = Calculate(IntValue, sizeof(thumbnail.nHeight) ); + + nItemsRead = (int)mypsd_fread(&IntValue, sizeof(IntValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(IntValue); + thumbnail.nWidthBytes = Calculate(IntValue, sizeof(thumbnail.nWidthBytes) ); + + nItemsRead = (int)mypsd_fread(&IntValue, sizeof(IntValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(IntValue); + thumbnail.nSize = Calculate(IntValue, sizeof(thumbnail.nSize) ); + + nItemsRead = (int)mypsd_fread(&IntValue, sizeof(IntValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(IntValue); + thumbnail.nCompressedSize = Calculate(IntValue, sizeof(thumbnail.nCompressedSize) ); + + nItemsRead = (int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + thumbnail.nBitPerPixel = (short)Calculate(ShortValue, sizeof(thumbnail.nBitPerPixel) ); + + nItemsRead = (int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + thumbnail.nPlanes = (short)Calculate(ShortValue, sizeof(thumbnail.nPlanes) ); + + int nTotalData = image_resource.nSize - 28; // header + unsigned char* buffer = new unsigned char[nTotalData]; + unsigned char c[1]; + if ( 1033 == image_resource.nID ) + { + // In BGR format + for (int n = 0; n < nTotalData; n = n +3 ) + { + nItemsRead = (int)mypsd_fread(&c, sizeof(unsigned char), 1, pFile); + nBytesRead += nItemsRead * sizeof(unsigned char); + buffer[n+2] = (unsigned char)Calculate(c, sizeof(unsigned char) ); + nItemsRead = (int)mypsd_fread(&c, sizeof(unsigned char), 1, pFile); + nBytesRead += nItemsRead * sizeof(unsigned char); + buffer[n+1] = (unsigned char)Calculate(c, sizeof(BYTE) ); + nItemsRead = (int)mypsd_fread(&c, sizeof(unsigned char), 1, pFile); + nBytesRead += nItemsRead * sizeof(unsigned char); + buffer[n] = (unsigned char)Calculate(c, sizeof(unsigned char) ); + } + } + else if ( 1036 == image_resource.nID ) + { + // In RGB format + for (int n = 0; n < nTotalData; ++n ) + { + nItemsRead = (int)mypsd_fread(&c, sizeof(BYTE), 1, pFile); + nBytesRead += nItemsRead * sizeof(BYTE); + buffer[n] = (BYTE)Calculate(c, sizeof(BYTE) ); + } + } + + delete[] buffer; + buffer = 0; + } + break; + case 1037: + { + nItemsRead = (int)mypsd_fread(&IntValue, sizeof(IntValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(IntValue); + mnGlobalAngle = Calculate(IntValue, sizeof(mnGlobalAngle) ); + } + break; + case 1046: + { + nItemsRead = (int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + mnColourCount = (short)Calculate(ShortValue, sizeof(ShortValue) ); + } + break; + case 1047: + { + nItemsRead = (int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + nBytesRead += nItemsRead * sizeof(ShortValue); + mnTransparentIndex = (short)Calculate(ShortValue, sizeof(ShortValue) ); + } + break; + + default: + pFile.Seek(image_resource.nSize, SEEK_CUR); + nBytesRead += image_resource.nSize; + break; + } + } + } + } + + assert ( nBytesRead == nTotalBytes ); + if ( nBytesRead == nTotalBytes ) + bSuccess = true; + + return bSuccess; + } + bool CPSD::ReadColourModeData(CxFile &pFile, COLOUR_MODE_DATA& colour_mode_data) + { + // Only indexed colour and duotone have colour mode data, + // for all other modes this section is 4 bytes length, the length field is set to zero + + // For indexed color images, the length will be equal to 768, and the color + // will contain the color table for the image, in noninterleaved order. + + // For duotone images, the color data will contain the duotone specification, + // the format of which is not documented. Other applications that read + // Photoshop files can treat a duotone image as a grayscale image, and just + // preserve the contents of the duotone information when reading and writing + // the file. + + // free memory + if ( 0 < colour_mode_data.nLength ) + delete[] colour_mode_data.ColourData; + colour_mode_data.ColourData = 0; + + unsigned char Length[4]; + int nItemsRead = (int)mypsd_fread(&Length, sizeof(Length), 1, pFile); + + colour_mode_data.nLength = Calculate( Length, sizeof(colour_mode_data.nLength) ); + if ( 0 < colour_mode_data.nLength ) + { + colour_mode_data.ColourData = new unsigned char[colour_mode_data.nLength]; + nItemsRead = 0; + memset(colour_mode_data.ColourData, 254, colour_mode_data.nLength); + + nItemsRead += (int)mypsd_fread( colour_mode_data.ColourData, colour_mode_data.nLength, 1, pFile); + + } + + return true; + } + + bool CPSD::ReadHeader(CxFile &pFile, HEADER_INFO& header_info) + { + bool bSuccess = false; + + struct HEADER + { + char Signature[4]; // always equal 8BPS, do not read file if not + unsigned char Version[2]; // always equal 1, do not read file if not + char Reserved[6]; // must be zero + unsigned char Channels[2]; // numer of channels including any alpha channels, supported range 1 to 24 + unsigned char Rows[4]; // height in PIXELS, supported range 1 to 30000 + unsigned char Columns[4]; // width in PIXELS, supported range 1 to 30000 + unsigned char Depth[2]; // number of bpp + unsigned char Mode[2]; // colour mode of the file, + // Btmap=0, Grayscale=1, Indexed=2, RGB=3, + // CMYK=4, Multichannel=7, Duotone=8, Lab=9 + }; + + HEADER header; + int nItemsRead = (int)mypsd_fread(&header, sizeof(HEADER), 1, pFile); + if ( nItemsRead ) + { + if ( 0 == ::memcmp(header.Signature, "8BPS", 4)) + { + int nVersion = Calculate( header.Version, sizeof(header.Version) ); + + if ( 1 == nVersion ) + { + unsigned int n = 0; + bool bOK = true; + while ( (n < 6) && bOK ) + { + if ( '\0' != header.Reserved[n] ) + bOK = false; + n++; + } + bSuccess = bOK; + + if ( bSuccess ) + { + header_info.nChannels = (short)Calculate( header.Channels, sizeof(header.Channels) ); + header_info.nHeight = Calculate( header.Rows, sizeof(header.Rows) ); + header_info.nWidth = Calculate( header.Columns, sizeof(header.Columns) ); + header_info.nBitsPerPixel = (short)Calculate( header.Depth, sizeof(header.Depth) ); + header_info.nColourMode = (short)Calculate( header.Mode, sizeof(header.Mode) ); + } + } + } + } + + return bSuccess; + } + + + void CPSD::ProccessBuffer(unsigned char* pData ) + { + if (!pData) return; + + switch ( header_info.nColourMode ) + { + case 1: // Grayscale + case 8: // Duotone + { + bool bAlpha = header_info.nChannels > 1; + + int nPixels = header_info.nWidth * header_info.nHeight; + byte *pRGBA = new byte[nPixels * (bAlpha ? 4 : 3)]; + byte *pSrc = pData, *pDst = pRGBA; + for (int i = 0; i < nPixels; i++, pSrc += header_info.nChannels, pDst += bAlpha ? 4 : 3) + { + pDst[0] = pDst[1] = pDst[2] = pSrc[0]; + if (bAlpha) pDst[3] = pSrc[1]; + } + + m_image.CreateFromArray(pRGBA, header_info.nWidth, header_info.nHeight, bAlpha ? 32 : 24, header_info.nWidth * (bAlpha ? 4 : 3), true); + + delete [] pRGBA; + } + break; + case 2: // Indexed + { + if (!colour_mode_data.ColourData) break; + if (colour_mode_data.nLength != 768) break; + if (mnColourCount == 0) break; + + int nPixels = header_info.nWidth * header_info.nHeight; + byte *pRGB = new byte[nPixels * 3]; + ::memset(pRGB, 0, nPixels * 3); + byte *pSrc = pData, *pDst = pRGB; + for (int i = 0; i < nPixels; i++, pSrc += header_info.nChannels, pDst += 3) + { + int nIndex = *pSrc; + pDst[2] = colour_mode_data.ColourData[nIndex + 0 * 256]; + pDst[1] = colour_mode_data.ColourData[nIndex + 1 * 256]; + pDst[0] = colour_mode_data.ColourData[nIndex + 2 * 256]; + } + + m_image.CreateFromArray(pRGB, header_info.nWidth, header_info.nHeight, 24, header_info.nWidth * 3, true); + delete [] pRGB; + } + break; + case 3: // RGB + { + m_image.CreateFromArray(pData, header_info.nWidth, header_info.nHeight, header_info.nChannels == 3 ? 24 : 32, header_info.nWidth * header_info.nChannels, true); + m_image.SwapRGB2BGR(); + } + break; + case 4: // CMYK + { + bool bAlpha = header_info.nChannels > 4; + + int nPixels = header_info.nWidth * header_info.nHeight; + byte *pRGBA = new byte[nPixels * (bAlpha ? 4 : 3)]; + byte *pSrc = pData, *pDst = pRGBA; + double C, M, Y, K; + int nRed, nGreen, nBlue; + for (int i = 0; i < nPixels; i++, pSrc += header_info.nChannels, pDst += bAlpha ? 4 : 3) + { + C = (1.0 - (double)pSrc[0] / 256); + M = (1.0 - (double)pSrc[1] / 256); + Y = (1.0 - (double)pSrc[2] / 256); + K = (1.0 - (double)pSrc[3] / 256); + + CMYKToRGB(C, M, Y, K, nRed, nGreen, nBlue); + + if (0 > nRed) nRed = 0; else if (255 < nRed) nRed = 255; + if (0 > nGreen) nGreen = 0; else if (255 < nGreen) nGreen = 255; + if (0 > nBlue) nBlue = 0; else if (255 < nBlue) nBlue = 255; + + pDst[0] = nBlue; pDst[1] = nGreen; pDst[2] = nRed; + if (bAlpha) pDst[3] = pSrc[4]; + } + + m_image.CreateFromArray(pRGBA, header_info.nWidth, header_info.nHeight, bAlpha ? 32 : 24, header_info.nWidth * (bAlpha ? 4 : 3), true); + + delete [] pRGBA; + } + break; + case 7: // Multichannel + { + if (header_info.nChannels == 0 || header_info.nChannels > 4) break; // ??? + + int nPixels = header_info.nWidth * header_info.nHeight; + byte *pRGB = new byte[nPixels * 3]; + byte *pSrc = pData, *pDst = pRGB; + double C, M, Y, K; + int nRed, nGreen, nBlue; + for (int i = 0; i < nPixels; i++, pSrc += header_info.nChannels, pDst += 3) + { + C = M = Y = K = 0; + C = (1.0 - (double)pSrc[0] / 256); + if (header_info.nChannels > 1) M = (1.0 - (double)pSrc[1] / 256); + if (header_info.nChannels > 2) Y = (1.0 - (double)pSrc[2] / 256); + if (header_info.nChannels > 3) K = (1.0 - (double)pSrc[3] / 256); + + CMYKToRGB(C, M, Y, K, nRed, nGreen, nBlue); + + if (0 > nRed) nRed = 0; else if (255 < nRed) nRed = 255; + if (0 > nGreen) nGreen = 0; else if (255 < nGreen) nGreen = 255; + if (0 > nBlue) nBlue = 0; else if (255 < nBlue) nBlue = 255; + + pDst[0] = nBlue; pDst[1] = nGreen; pDst[2] = nRed; + } + + m_image.CreateFromArray(pRGB, header_info.nWidth, header_info.nHeight, 24, header_info.nWidth * 3, true); + + delete [] pRGB; + } + break; + case 9: // Lab + { + bool bAlpha = header_info.nChannels > 3; + + int nPixels = header_info.nWidth * header_info.nHeight; + byte *pRGBA = new byte[nPixels * (bAlpha ? 4 : 3)]; + byte *pSrc = pData, *pDst = pRGBA; + + double L_coef = 256.f / 100.f, a_coef = 256.f / 256.f, b_coef = 256.f / 256.f; + int L, a, b; + int nRed, nGreen, nBlue; + for (int i = 0; i < nPixels; i++, pSrc += header_info.nChannels, pDst += bAlpha ? 4 : 3) + { + L = (int)((float)pSrc[0] / L_coef); + a = (int)((float)pSrc[1] / a_coef - 128.0); + b = (int)((float)pSrc[2] / b_coef - 128.0); + + LabToRGB(L, a, b, nRed, nGreen, nBlue ); + + if (0 > nRed) nRed = 0; else if (255 < nRed) nRed = 255; + if (0 > nGreen) nGreen = 0; else if (255 < nGreen) nGreen = 255; + if (0 > nBlue) nBlue = 0; else if (255 < nBlue) nBlue = 255; + + pDst[0] = nBlue; pDst[1] = nGreen; pDst[2] = nRed; + if (bAlpha) pDst[3] = pSrc[3]; + } + + m_image.CreateFromArray(pRGBA, header_info.nWidth, header_info.nHeight, bAlpha ? 32 : 24, header_info.nWidth * (bAlpha ? 4 : 3), true); + + delete [] pRGBA; + } + break; + } + } + + int CPSD::Load(LPCTSTR szPathName) + { + CxIOFile f; + if (!f.Open(szPathName, _T("rb"))) return -1; + return Load(f); + } + + int CPSD::Load(CxFile &f) + { + if (!ReadHeader(f, header_info)) return -2; // Error in header + if (!ReadColourModeData(f, colour_mode_data)) return -3; // Error in ColourMode Data + if (!ReadImageResource(f, image_resource)) return -4; // Error in Image Resource + if (!ReadLayerAndMaskInfoSection(f)) return -5; // Error in Mask Info + if (ReadImageData(f) != 0) return -6; // Error in Image Data + return 0; // all right + } + + int CPSD::DecodeRawData( CxFile &pFile) + { + if (header_info.nBitsPerPixel != 8 && header_info.nBitsPerPixel != 16) return -7; // can't read this + + int nWidth = header_info.nWidth; + int nHeight = header_info.nHeight; + int bytesPerPixelPerChannel = header_info.nBitsPerPixel / 8; + + int nPixels = nWidth * nHeight; + int nTotalBytes = 0; + + byte* pData = NULL; + + switch ( header_info.nColourMode ) + { + case 1: // Grayscale + case 2: // Indexed + case 3: // RGB + case 4: // CMYK + case 8: // Duotone + case 9: // Lab + { + // read RRRRRRRGGGGGGGBBBBBBAAAAAA data + int nAllDataSize = nPixels * bytesPerPixelPerChannel * header_info.nChannels; + byte *pFileData = new byte[nAllDataSize]; + ::memset(pFileData, 0, nAllDataSize); + if (pFile.Read(pFileData, nAllDataSize, 1) != 1) + { + delete [] pFileData; + return -1; // bad data + } + + // and convert them to RGBARGBARGBA data (depends on number of channels) + nTotalBytes = nPixels * header_info.nChannels; + pData = new byte[nTotalBytes]; + byte *pSource = pFileData; + for (int nChannel = 0; nChannel < header_info.nChannels; nChannel++) + { + byte *pDest = pData + nChannel; + for (int pos = 0; pos < nPixels; pos++, pDest += header_info.nChannels, pSource += bytesPerPixelPerChannel) *pDest = *pSource; + } + delete [] pFileData; + } + break; + default: + return -1; // unsupported format + } + + ProccessBuffer(pData); + delete [] pData; + + // dpi related things + int ppm_x = 3780; // 96 dpi + int ppm_y = 3780; // 96 dpi + if (mbResolutionInfoFilled) + { + int nHorResolution = (int)resolution_info.hRes; + int nVertResolution = (int)resolution_info.vRes; + ppm_x = (nHorResolution * 10000) / 254; + ppm_y = (nVertResolution * 10000) / 254; + } + m_image.SetXDPI(ppm_x); + m_image.SetYDPI(ppm_y); + + return 0; + } + + + int CPSD::DecodeRLEData(CxFile & pFile) + { + if (header_info.nBitsPerPixel != 8) return -7; // can't read this + + int nWidth = header_info.nWidth; + int nHeight = header_info.nHeight; + int nPixels = nWidth * nHeight; + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data + // read them and compute size of RLE data + int nLengthDataSize = nHeight * header_info.nChannels * 2; + byte *pLengthData = new byte[nLengthDataSize]; + if (pFile.Read(pLengthData, nLengthDataSize, 1) != 1) + { + delete [] pLengthData; + return -1; // error while reading + } + int nRLEDataSize = 0; + for (int i = 0; i < nHeight * header_info.nChannels * 2; i += 2) + nRLEDataSize += Calculate(pLengthData + i, 2); + delete [] pLengthData; + + // now read RLE data to the buffer for fast access + byte *pRLEData = new byte[nRLEDataSize]; + if (pFile.Read(pRLEData, nRLEDataSize, 1) != 1) + { + delete [] pRLEData; + return -1; + } + + // allocate buffer for raw data (RRRRRRR...RRRGGGGG...GGGGGGBBBBB...BBBBBAAAAA....AAAAA) it has the same size as the final buffer + // and the perform RLE-decoding + int nTotalBytes = nPixels * header_info.nChannels; + byte* pRawData = new byte[nTotalBytes]; + byte *pRLESource = pRLEData, *pRLEDest = pRawData; + for (int channel = 0; channel < header_info.nChannels; channel++) + { + int nCount = 0; + while (nCount < nPixels) + { + int len = *pRLESource++; + if ( 128 > len ) + { // copy next (len + 1) bytes as is + len++; + nCount += len; + ::memcpy(pRLEDest, pRLESource, len); + pRLEDest += len; pRLESource += len; + } + else if ( 128 < len ) + { + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + nCount += len; + ::memset(pRLEDest, *pRLESource++, len); + pRLEDest += len; + } + else if ( 128 == len ) { /* Do nothing */ } + } + } + delete [] pRLEData; + + // transform raw data to the good one (RGBARGBARGBA...RGBA) + byte *pRawSource = pRawData; + byte *pData = new byte[nTotalBytes]; + int nPixelCounter = 0; + for( int nColour = 0; nColour < header_info.nChannels; ++nColour ) + { + nPixelCounter = nColour; + for (int nPos = 0; nPos < nPixels; nPos++, pRawSource++) + { + pData[nPixelCounter] = *pRawSource; + nPixelCounter += header_info.nChannels; + } + } + delete[] pRawData; + + // create image + ProccessBuffer(pData); + delete [] pData; + + // dpi related things + int ppm_x = 3780; // 96 dpi + int ppm_y = 3780; // 96 dpi + if (mbResolutionInfoFilled) + { + int nHorResolution = (int)resolution_info.hRes; + int nVertResolution = (int)resolution_info.vRes; + ppm_x = (nHorResolution * 10000) / 254; + ppm_y = (nVertResolution * 10000) / 254; + } + m_image.SetXDPI(ppm_x); + m_image.SetYDPI(ppm_y); + + return 0; + } + + int CPSD::ReadImageData(CxFile &pFile) + { + int nErrorCode = 0; // No Errors + + if ( !mypsd_feof(pFile) ) + { + unsigned char ShortValue[2]; + int nBytesRead = 0; + int nItemsRead = (int)mypsd_fread(&ShortValue, sizeof(ShortValue), 1, pFile); + short nCompression = (short)Calculate( ShortValue, sizeof(ShortValue) ); + + switch ( nCompression ) + { + case 0: // raw data + nErrorCode = DecodeRawData(pFile); + break; + case 1: // RLE compression + nErrorCode = DecodeRLEData(pFile); + break; + case 2: // ZIP without prediction + nErrorCode = -10; // ZIP without prediction, no specification + break; + case 3: // ZIP with prediction + nErrorCode = -11; // ZIP with prediction, no specification + break; + default: + nErrorCode = -12; // Unknown format + } + } + return nErrorCode; + } + + ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + CPSD::HEADER_INFO::HEADER_INFO() + { + nChannels = -1; + nHeight = -1; + nWidth = -1; + nBitsPerPixel = -1; + nColourMode = -1; + } + + CPSD::COLOUR_MODE_DATA::COLOUR_MODE_DATA() + { + nLength = -1; + ColourData = 0; + } + + CPSD::IMAGE_RESOURCE::IMAGE_RESOURCE() + { + Name = 0; + Reset(); + } + + void CPSD::IMAGE_RESOURCE::Reset() + { + nLength = -1; + memset( OSType, '\0', sizeof(OSType) ); + nID = -1; + if ( Name ) + delete[] Name; + Name = 0; + nSize = -1; + } + + CPSD::RESOLUTION_INFO::RESOLUTION_INFO() + { + hRes = -1; + hResUnit = -1; + widthUnit = -1; + vRes = -1; + vResUnit = -1; + heightUnit = -1; + } + + CPSD::RESOLUTION_INFO_v2::RESOLUTION_INFO_v2() + { + nChannels = -1; + nRows = -1; + nColumns = -1; + nDepth = -1; + nMode = -1; + } + + CPSD::DISPLAY_INFO::DISPLAY_INFO() + { + ColourSpace = -1; + for ( unsigned int n = 0; n < 4; ++n) + Colour[n] = 0; + Opacity = -1; + kind = false; + padding = '0'; + } + + CPSD::THUMBNAIL::THUMBNAIL() + { + nFormat = -1; + nWidth = -1; + nHeight = -1; + nWidthBytes = -1; + nSize = -1; + nCompressedSize = -1; + nBitPerPixel = -1; + nPlanes = -1; + Data = 0; + } +} // MyPSD + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_USE_LIBPSD + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImagePSD::Decode(CxFile *hFile) +{ + if (hFile==NULL) + return false; + +#if CXIMAGE_USE_LIBPSD + psd_context* context = NULL; +#endif + + cx_try + { +#if CXIMAGE_USE_LIBPSD + + psd_status status; + + context = (psd_context *)malloc(sizeof(psd_context)); + if(context == NULL){ + cx_throw("CxImagePSD: psd_status_malloc_failed"); + } + memset(context, 0, sizeof(psd_context)); + + // install file manager + CxFilePsd src(hFile,context); + + context->state = PSD_FILE_HEADER; + context->stream.file_length = hFile->Size(); + context->load_tag = psd_load_tag_all; + status = psd_main_loop(context); + + if(status != psd_status_done){ + cx_throw("CxImagePSD: psd_main_loop failed"); + } + + Create(context->width,context->height,24,CXIMAGE_FORMAT_PSD); + + uint8_t* rgba = (uint8_t*)context->merged_image_data; + uint8_t* alpha = NULL; + if (context->alpha_channel_info) + alpha = (uint8_t*)context->alpha_channel_info->channel_data; + +#if CXIMAGE_SUPPORT_ALPHA + if (alpha) + AlphaCreate(); +#endif + + int32_t x,y; + RGBQUAD c; + c.rgbReserved = 0; + if (rgba){ + for(y =context->height-1; y--;){ + for (x=0; xwidth; x++){ + c.rgbBlue = *rgba++; + c.rgbGreen = *rgba++; + c.rgbRed = *rgba++; + rgba++; + SetPixelColor(x,y,c); +#if CXIMAGE_SUPPORT_ALPHA + if (alpha) AlphaSet(x,y,*alpha++); +#endif //CXIMAGE_SUPPORT_ALPHA + } + } + } + + psd_image_free(context); + free(context); + +#else //CXIMAGE_USE_LIBPSD == 0 + + MyPSD::CPSD psd(*this); + int nErrorCode = psd.Load(*hFile); + if (nErrorCode != 0) cx_throw("error loading PSD file"); + +#endif //CXIMAGE_USE_LIBPSD + + } cx_catch { + +#if CXIMAGE_USE_LIBPSD + psd_image_free(context); + if (context) free(context); +#endif //CXIMAGE_USE_LIBPSD + + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + if (info.nEscape == -1 && info.dwType == CXIMAGE_FORMAT_PSD) return true; + return false; + } + /* that's it */ + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImagePSD::Encode(CxFile * hFile) +{ + if (hFile == NULL) return false; + strcpy(info.szLastError, "Save PSD not supported"); + return false; +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_PSD + diff --git a/DuiLib/3rd/CxImage/ximapsd.h b/DuiLib/3rd/CxImage/ximapsd.h new file mode 100644 index 0000000..aa4639e --- /dev/null +++ b/DuiLib/3rd/CxImage/ximapsd.h @@ -0,0 +1,110 @@ +/* + * File: ximapsd.h + * Purpose: PSD Image Class Loader and Writer + */ +/* ========================================================== + * CxImagePSD (c) Dec/2010 + * For conditions of distribution and use, see copyright notice in ximage.h + * + * libpsd (c) 2004-2007 Graphest Software + * + * ========================================================== + */ +#if !defined(__ximaPSD_h) +#define __ximaPSD_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_PSD + +#define CXIMAGE_USE_LIBPSD 0 + +#if CXIMAGE_USE_LIBPSD + extern "C" { + #include "../libpsd/libpsd.h" + } +#endif + +class CxImagePSD: public CxImage +{ + +public: + CxImagePSD(): CxImage(CXIMAGE_FORMAT_PSD) {} + +// bool Load(const char * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_PSD);} +// bool Save(const char * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_PSD);} + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +//#if CXIMAGE_SUPPORT_EXIF +// bool GetExifThumbnail(const TCHAR *filename, const TCHAR *outname, int32_t type); +//#endif //CXIMAGE_SUPPORT_EXIF + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +#endif // CXIMAGE_SUPPORT_ENCODE + +#if CXIMAGE_USE_LIBPSD +protected: + class CxFilePsd + { + public: + CxFilePsd(CxFile* pFile,psd_context *context) + { + context->file = pFile; + + psd_CxFile_ops.size_ = psd_file_size; + psd_CxFile_ops.seek_ = psd_file_seek; + psd_CxFile_ops.read_ = psd_file_read; +// psd_CxFile_ops.write_ = psd_file_write; +// psd_CxFile_ops.close_ = psd_file_close; +// psd_CxFile_ops.gets_ = psd_file_gets; +// psd_CxFile_ops.eof_ = psd_file_eof; +// psd_CxFile_ops.tell_ = psd_file_tell; +// psd_CxFile_ops.getc_ = psd_file_getc; +// psd_CxFile_ops.scanf_ = psd_file_scanf; + + context->ops_ = &psd_CxFile_ops; + + } + + static int32_t psd_file_size(psd_file_obj *obj) + { return ((CxFile*)obj)->Size(); } + + static int32_t psd_file_seek(psd_file_obj *obj, int32_t offset, int32_t origin) + { return ((CxFile*)obj)->Seek(offset,origin); } + + static int32_t psd_file_read(psd_file_obj *obj, void *buf, int32_t size, int32_t cnt) + { return ((CxFile*)obj)->Read(buf,size,cnt); } + +// static int32_t psd_file_write(psd_file_obj *obj, void *buf, int32_t size, int32_t cnt) +// { return ((CxFile*)obj)->Write(buf,size,cnt); } + +// static int32_t psd_file_close(psd_file_obj *obj) +// { return 1; /*((CxFile*)obj)->Close();*/ } + +// static char* psd_file_gets(psd_file_obj *obj, char *string, int32_t n) +// { return ((CxFile*)obj)->GetS(string,n); } + +// static int32_t psd_file_eof(psd_file_obj *obj) +// { return ((CxFile*)obj)->Eof(); } + +// static long psd_file_tell(psd_file_obj *obj) +// { return ((CxFile*)obj)->Tell(); } + +// static int32_t psd_file_getc(psd_file_obj *obj) +// { return ((CxFile*)obj)->GetC(); } + +// static int32_t psd_file_scanf(psd_file_obj *obj,const char *format, void* output) +// { return ((CxFile*)obj)->Scanf(format, output); } + + private: + psd_file_ops psd_CxFile_ops; + }; +#endif //CXIMAGE_USE_LIBPSD +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximaraw.cpp b/DuiLib/3rd/CxImage/ximaraw.cpp new file mode 100644 index 0000000..fe341fd --- /dev/null +++ b/DuiLib/3rd/CxImage/ximaraw.cpp @@ -0,0 +1,333 @@ +/* + * File: ximaraw.cpp + * Purpose: Platform Independent RAW Image Class Loader + * 16/Dec/2007 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + * + * CxImageRAW (c) May/2006 pdw63 + * + * based on dcraw.c -- Dave Coffin's raw photo decoder + * Copyright 1997-2007 by Dave Coffin, dcoffin a cybercom o net + */ + +#include "ximaraw.h" + +#if CXIMAGE_SUPPORT_RAW + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageRAW::Decode(CxFile *hFile) +{ + if (hFile==NULL) + return false; + + DCRAW dcr; + + cx_try + { + // initialization + dcr_init_dcraw(&dcr); + + dcr.opt.user_qual = GetCodecOption(CXIMAGE_FORMAT_RAW) & 0x03; + + // setup variables for debugging + char szClass[] = "CxImageRAW"; + dcr.ifname = szClass; + dcr.sz_error = info.szLastError; + + // setup library options, see dcr_print_manual for the available switches + // call dcr_parse_command_line_options(&dcr,0,0,0) to set default options + // if (dcr_parse_command_line_options(&dcr,argc,argv,&arg)) + if (dcr_parse_command_line_options(&dcr,0,0,0)){ + cx_throw("CxImageRAW: unknown option"); + } + + // set return point for error handling + if (setjmp (dcr.failure)) { + cx_throw(""); + } + + // install file manager + CxFileRaw src(hFile,&dcr); + + // check file header + dcr_identify(&dcr); + + if(!dcr.is_raw){ + cx_throw("CxImageRAW: not a raw image"); + } + + if (dcr.load_raw == NULL) { + cx_throw("CxImageRAW: missing raw decoder"); + } + + // verify special case + if (dcr.load_raw == dcr_kodak_ycbcr_load_raw) { + dcr.height += dcr.height & 1; + dcr.width += dcr.width & 1; + } + + if (info.nEscape == -1){ + head.biWidth = dcr.width; + head.biHeight= dcr.height; + info.dwType = CXIMAGE_FORMAT_RAW; + cx_throw("output dimensions returned"); + } + + // shrinked decoding available and requested? + dcr.shrink = dcr.filters && (dcr.opt.half_size || dcr.opt.threshold || dcr.opt.aber[0] != 1 || dcr.opt.aber[2] != 1); + dcr.iheight = (dcr.height + dcr.shrink) >> dcr.shrink; + dcr.iwidth = (dcr.width + dcr.shrink) >> dcr.shrink; + + // install custom camera matrix + if (dcr.opt.use_camera_matrix && dcr.cmatrix[0][0] > 0.25) { + memcpy (dcr.rgb_cam, dcr.cmatrix, sizeof dcr.cmatrix); + dcr.raw_color = 0; + } else { + dcr.opt.use_camera_wb = 1; + } + + // allocate memory for the image + dcr.image = (ushort (*)[4]) calloc (dcr.iheight*dcr.iwidth, sizeof *dcr.image); + dcr_merror (&dcr, dcr.image, szClass); + + if (dcr.meta_length) { + dcr.meta_data = (char *) malloc (dcr.meta_length); + dcr_merror (&dcr, dcr.meta_data, szClass); + } + + // start image decoder + hFile->Seek(dcr.data_offset, SEEK_SET); + (*dcr.load_raw)(&dcr); + + // post processing + if (dcr.zero_is_bad) dcr_remove_zeroes(&dcr); + + dcr_bad_pixels(&dcr,dcr.opt.bpfile); + + if (dcr.opt.dark_frame) dcr_subtract (&dcr,dcr.opt.dark_frame); + + dcr.quality = 2 + !dcr.fuji_width; + + if (dcr.opt.user_qual >= 0) dcr.quality = dcr.opt.user_qual; + + if (dcr.opt.user_black >= 0) dcr.black = dcr.opt.user_black; + + if (dcr.opt.user_sat >= 0) dcr.maximum = dcr.opt.user_sat; + +#ifdef COLORCHECK + dcr_colorcheck(&dcr); +#endif + +#if RESTRICTED + if (dcr.is_foveon && !dcr.opt.document_mode) dcr_foveon_interpolate(&dcr); +#endif + + if (!dcr.is_foveon && dcr.opt.document_mode < 2) dcr_scale_colors(&dcr); + + // pixel interpolation and filters + dcr_pre_interpolate(&dcr); + + if (dcr.filters && !dcr.opt.document_mode) { + if (dcr.quality == 0) + dcr_lin_interpolate(&dcr); + else if (dcr.quality == 1 || dcr.colors > 3) + dcr_vng_interpolate(&dcr); + else if (dcr.quality == 2) + dcr_ppg_interpolate(&dcr); + else + dcr_ahd_interpolate(&dcr); + } + + if (dcr.mix_green) { + int32_t i; + for (dcr.colors=3, i=0; i < dcr.height*dcr.width; i++) { + dcr.image[i][1] = (dcr.image[i][1] + dcr.image[i][3]) >> 1; + } + } + + if (!dcr.is_foveon && dcr.colors == 3) dcr_median_filter(&dcr); + + if (!dcr.is_foveon && dcr.opt.highlight == 2) dcr_blend_highlights(&dcr); + + if (!dcr.is_foveon && dcr.opt.highlight > 2) dcr_recover_highlights(&dcr); + + if (dcr.opt.use_fuji_rotate) dcr_fuji_rotate(&dcr); + +#ifndef NO_LCMS + if (dcr.opt.cam_profile) dcr_apply_profile (dcr.opt.cam_profile, dcr.opt.out_profile); +#endif + + // final conversion + dcr_convert_to_rgb(&dcr); + + if (dcr.opt.use_fuji_rotate) dcr_stretch(&dcr); + + dcr.iheight = dcr.height; + dcr.iwidth = dcr.width; + if (dcr.flip & 4) SWAP(dcr.height,dcr.width); + + // ready to transfer data from dcr.image + if (!Create(dcr.width,dcr.height,24,CXIMAGE_FORMAT_RAW)){ + cx_throw(""); + } + + uchar *ppm = (uchar *) calloc (dcr.width, dcr.colors*dcr.opt.output_bps/8); + ushort *ppm2 = (ushort *) ppm; + dcr_merror (&dcr, ppm, szClass); + + uchar lut[0x10000]; + if (dcr.opt.output_bps == 8) dcr_gamma_lut (&dcr, lut); + + int32_t c, row, col, soff, rstep, cstep; + soff = dcr_flip_index (&dcr, 0, 0); + cstep = dcr_flip_index (&dcr, 0, 1) - soff; + rstep = dcr_flip_index (&dcr, 1, 0) - dcr_flip_index (&dcr, 0, dcr.width); + for (row=0; row < dcr.height; row++, soff += rstep) { + for (col=0; col < dcr.width; col++, soff += cstep) { + if (dcr.opt.output_bps == 8) + for (c=0; c < dcr.colors; c++) ppm [col*dcr.colors+c] = lut[dcr.image[soff][c]]; + else + for (c=0; c < dcr.colors; c++) ppm2[col*dcr.colors+c] = dcr.image[soff][c]; + } + if (dcr.opt.output_bps == 16 && !dcr.opt.output_tiff && htons(0x55aa) != 0x55aa) +#if defined(_LINUX) || defined(__APPLE__) + swab ((char*)ppm2, (char*)ppm2, dcr.width*dcr.colors*2); +#else + _swab ((char*)ppm2, (char*)ppm2, dcr.width*dcr.colors*2); +#endif + + uint32_t size = dcr.width * (dcr.colors*dcr.opt.output_bps/8); + RGBtoBGR(ppm,size); + memcpy(GetBits(dcr.height - 1 - row), ppm, min(size,GetEffWidth())); + } + free (ppm); + + + dcr_cleanup_dcraw(&dcr); + + } cx_catch { + + dcr_cleanup_dcraw(&dcr); + + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + if (info.nEscape == -1 && info.dwType == CXIMAGE_FORMAT_RAW) return true; + return false; + } + /* that's it */ + return true; +} + +#if CXIMAGE_SUPPORT_EXIF +bool CxImageRAW::GetExifThumbnail(const TCHAR *filename, const TCHAR *outname, int32_t type) +{ + DCRAW dcr; + + CxIOFile file; + if (!file.Open(filename, _T("rb"))) + return false; + + cx_try + { + // initialization + dcr_init_dcraw(&dcr); + + dcr.opt.user_qual = GetCodecOption(CXIMAGE_FORMAT_RAW) & 0x03; + + // setup variables for debugging + char szClass[] = "CxImageRAW"; + dcr.ifname = szClass; + dcr.sz_error = info.szLastError; + + // setup library options, see dcr_print_manual for the available switches + // call dcr_parse_command_line_options(&dcr,0,0,0) to set default options + // if (dcr_parse_command_line_options(&dcr,argc,argv,&arg)) + if (dcr_parse_command_line_options(&dcr,0,0,0)){ + cx_throw("CxImageRAW: unknown option"); + } + + // set return point for error handling + if (setjmp (dcr.failure)) { + cx_throw(""); + } + + // install file manager + CxFileRaw src(&file,&dcr); + + // check file header + dcr_identify(&dcr); + + if(!dcr.is_raw){ + cx_throw("CxImageRAW: not a raw image"); + } + + if (dcr.load_raw == NULL) { + cx_throw("CxImageRAW: missing raw decoder"); + } + + // THUMB. + if (dcr.thumb_offset != 0) + { + FILE* file = _tfopen(outname, _T("wb")); + DCRAW* p = &dcr; + dcr_fseek(dcr.obj_, dcr.thumb_offset, SEEK_SET); + dcr.write_thumb(&dcr, file); + fclose(file); + + // Read in the thumbnail to resize and rotate. + CxImage image(outname, CXIMAGE_FORMAT_UNKNOWN); + if (image.IsValid()) + { +#if CXIMAGE_SUPPORT_TRANSFORMATION + // Resizing. + if (image.GetWidth() > 256 || image.GetHeight() > 256) + { + float amount = 256.0f / max(image.GetWidth(), image.GetHeight()); + image.Resample((int32_t)(image.GetWidth() * amount), (int32_t)(image.GetHeight() * amount), 0); + } + // Rotation. + if (p->flip != 0) + image.RotateExif(p->flip); +#endif +#if CXIMAGE_SUPPORT_ENCODE && CXIMAGE_SUPPORT_JPG + return image.Save(outname, CXIMAGE_FORMAT_JPG); +#endif + } + } + else + { + cx_throw("No thumbnail!"); + } + + dcr_cleanup_dcraw(&dcr); + + } cx_catch { + + dcr_cleanup_dcraw(&dcr); + + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + if (info.nEscape == -1 && info.dwType == CXIMAGE_FORMAT_RAW) return true; + return false; + } + /* that's it */ + return true; +} +#endif //CXIMAGE_SUPPORT_EXIF + +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageRAW::Encode(CxFile * hFile) +{ + if (hFile == NULL) return false; + strcpy(info.szLastError, "Save RAW not supported"); + return false; +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_RAW + diff --git a/DuiLib/3rd/CxImage/ximaraw.h b/DuiLib/3rd/CxImage/ximaraw.h new file mode 100644 index 0000000..465f31d --- /dev/null +++ b/DuiLib/3rd/CxImage/ximaraw.h @@ -0,0 +1,112 @@ +/* + * File: ximaraw.h + * Purpose: RAW Image Class Loader and Writer + */ +/* ========================================================== + * CxImageRAW (c) May/2006 pdw63 + * For conditions of distribution and use, see copyright notice in ximage.h + * Special thanks to David Coffin for dcraw without which this class would not exist + * + * libdcr (c) Dec/2007 Davide Pizzolato - www.xdp.it + * + * based on dcraw.c -- Dave Coffin's raw photo decoder + * Copyright 1997-2007 by Dave Coffin, dcoffin a cybercom o net + * ========================================================== + */ +#if !defined(__ximaRAW_h) +#define __ximaRAW_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_RAW + +extern "C" { + #include "../raw/libdcr.h" +} + +class CxImageRAW: public CxImage +{ + +public: + CxImageRAW(): CxImage(CXIMAGE_FORMAT_RAW) {} + +// bool Load(const char * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_ICO);} +// bool Save(const char * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_ICO);} + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_EXIF + bool GetExifThumbnail(const TCHAR *filename, const TCHAR *outname, int32_t type); +#endif //CXIMAGE_SUPPORT_EXIF + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +#endif // CXIMAGE_SUPPORT_ENCODE + + enum CODEC_OPTION + { + DECODE_QUALITY_LIN = 0x00, + DECODE_QUALITY_VNG = 0x01, + DECODE_QUALITY_PPG = 0x02, + DECODE_QUALITY_AHD = 0x03, + }; + +protected: + + class CxFileRaw + { + public: + CxFileRaw(CxFile* pFile,DCRAW *stream) + { + stream->obj_ = pFile; + + ras_stream_CxFile.read_ = raw_sfile_read; + ras_stream_CxFile.write_ = raw_sfile_write; + ras_stream_CxFile.seek_ = raw_sfile_seek; + ras_stream_CxFile.close_ = raw_sfile_close; + ras_stream_CxFile.gets_ = raw_sfile_gets; + ras_stream_CxFile.eof_ = raw_sfile_eof; + ras_stream_CxFile.tell_ = raw_sfile_tell; + ras_stream_CxFile.getc_ = raw_sfile_getc; + ras_stream_CxFile.scanf_ = raw_sfile_scanf; + + stream->ops_ = &ras_stream_CxFile; + + } + + static int32_t raw_sfile_read(dcr_stream_obj *obj, void *buf, int32_t size, int32_t cnt) + { return ((CxFile*)obj)->Read(buf,size,cnt); } + + static int32_t raw_sfile_write(dcr_stream_obj *obj, void *buf, int32_t size, int32_t cnt) + { return ((CxFile*)obj)->Write(buf,size,cnt); } + + static long raw_sfile_seek(dcr_stream_obj *obj, long offset, int32_t origin) + { return ((CxFile*)obj)->Seek(offset,origin); } + + static int32_t raw_sfile_close(dcr_stream_obj *obj) + { return 1; /*((CxFile*)obj)->Close();*/ } + + static char* raw_sfile_gets(dcr_stream_obj *obj, char *string, int32_t n) + { return ((CxFile*)obj)->GetS(string,n); } + + static int32_t raw_sfile_eof(dcr_stream_obj *obj) + { return ((CxFile*)obj)->Eof(); } + + static long raw_sfile_tell(dcr_stream_obj *obj) + { return ((CxFile*)obj)->Tell(); } + + static int32_t raw_sfile_getc(dcr_stream_obj *obj) + { return ((CxFile*)obj)->GetC(); } + + static int32_t raw_sfile_scanf(dcr_stream_obj *obj,const char *format, void* output) + { return ((CxFile*)obj)->Scanf(format, output); } + + private: + dcr_stream_ops ras_stream_CxFile; + }; +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximasel.cpp b/DuiLib/3rd/CxImage/ximasel.cpp new file mode 100644 index 0000000..f774ea2 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximasel.cpp @@ -0,0 +1,698 @@ +// xImaSel.cpp : Selection functions +/* 07/08/2001 v1.00 - Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximage.h" + +//////////////////////////////////////////////////////////////////////////////// +/** + * Checks if the image has a valid selection. + */ +bool CxImage::SelectionIsValid() +{ + return pSelection!=0; +} + +#if CXIMAGE_SUPPORT_SELECTION + +//////////////////////////////////////////////////////////////////////////////// +/** + * Gets the smallest rectangle that contains the selection + */ +void CxImage::SelectionGetBox(RECT& r) +{ + memcpy(&r,&info.rSelectionBox,sizeof(RECT)); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Empties the selection. + */ +bool CxImage::SelectionClear(uint8_t level) +{ + if (pSelection){ + if (level==0){ + memset(pSelection,0,head.biWidth * head.biHeight); + info.rSelectionBox.left = head.biWidth; + info.rSelectionBox.bottom = head.biHeight; + info.rSelectionBox.right = info.rSelectionBox.top = 0; + } else { + memset(pSelection,level,head.biWidth * head.biHeight); + info.rSelectionBox.right = head.biWidth; + info.rSelectionBox.top = head.biHeight; + info.rSelectionBox.left = info.rSelectionBox.bottom = 0; + } + return true; + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Allocates an empty selection. + */ +bool CxImage::SelectionCreate() +{ + SelectionDelete(); + pSelection = (uint8_t*)calloc(head.biWidth * head.biHeight, 1); + return (pSelection!=0); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Deallocates the selction. + */ +bool CxImage::SelectionDelete() +{ + if (pSelection){ + free(pSelection); + pSelection=NULL; + } + info.rSelectionBox.left = head.biWidth; + info.rSelectionBox.bottom = head.biHeight; + info.rSelectionBox.right = info.rSelectionBox.top = 0; + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Checks if the coordinates are inside the selection. + */ +bool CxImage::SelectionIsInside(int32_t x, int32_t y) +{ + if (IsInside(x,y)){ + if (pSelection==NULL) return true; + return pSelection[x+y*head.biWidth]!=0; + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Checks if the coordinates are inside the selection. + * "blind" version assumes that (x,y) is inside to the image. + */ +bool CxImage::BlindSelectionIsInside(int32_t x, int32_t y) +{ +#ifdef _DEBUG + if (!IsInside(x,y)) + #if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + throw 0; + #else + return 0; + #endif +#endif + if (pSelection==NULL) return true; + return pSelection[x+y*head.biWidth]!=0; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Adds a rectangle to the existing selection. + */ +bool CxImage::SelectionAddRect(RECT r, uint8_t level) +{ + if (pSelection==NULL) SelectionCreate(); + if (pSelection==NULL) return false; + + RECT r2; + if (r.left r2.left) info.rSelectionBox.left = max(0L,min(head.biWidth,r2.left)); + if (info.rSelectionBox.right <= r2.right) info.rSelectionBox.right = max(0L,min(head.biWidth,r2.right+1)); + if (info.rSelectionBox.bottom > r2.bottom) info.rSelectionBox.bottom = max(0L,min(head.biHeight,r2.bottom)); + + int32_t ymin = max(0L,min(head.biHeight,r2.bottom)); + int32_t ymax = max(0L,min(head.biHeight,r2.top+1)); + int32_t xmin = max(0L,min(head.biWidth,r2.left)); + int32_t xmax = max(0L,min(head.biWidth,r2.right+1)); + + for (int32_t y=ymin; y (xcenter - xradius)) info.rSelectionBox.left = max(0L,min(head.biWidth,(xcenter - xradius))); + if (info.rSelectionBox.right <= (xcenter + xradius)) info.rSelectionBox.right = max(0L,min(head.biWidth,(xcenter + xradius + 1))); + if (info.rSelectionBox.bottom > (ycenter - yradius)) info.rSelectionBox.bottom = max(0L,min(head.biHeight,(ycenter - yradius))); + if (info.rSelectionBox.top <= (ycenter + yradius)) info.rSelectionBox.top = max(0L,min(head.biHeight,(ycenter + yradius + 1))); + + int32_t xmin = max(0L,min(head.biWidth,xcenter - xradius)); + int32_t xmax = max(0L,min(head.biWidth,xcenter + xradius + 1)); + int32_t ymin = max(0L,min(head.biHeight,ycenter - yradius)); + int32_t ymax = max(0L,min(head.biHeight,ycenter + yradius + 1)); + + int32_t y,yo; + for (y=ymin; yy) pSelection[x + y * head.biWidth] = level; + } + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Inverts the selection. + * Note: the SelectionBox is set to "full image", call SelectionGetBox before (if necessary) + */ +bool CxImage::SelectionInvert() +{ + if (pSelection) { + uint8_t *iSrc=pSelection; + int32_t n=head.biHeight*head.biWidth; + for(int32_t i=0; i < n; i++){ + *iSrc=(uint8_t)~(*(iSrc)); + iSrc++; + } + + SelectionRebuildBox(); + + return true; + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Imports an existing region from another image with the same width and height. + */ +bool CxImage::SelectionCopy(CxImage &from) +{ + if (from.pSelection == NULL || head.biWidth != from.head.biWidth || head.biHeight != from.head.biHeight) return false; + if (pSelection==NULL) pSelection = (uint8_t*)malloc(head.biWidth * head.biHeight); + if (pSelection==NULL) return false; + memcpy(pSelection,from.pSelection,head.biWidth * head.biHeight); + memcpy(&info.rSelectionBox,&from.info.rSelectionBox,sizeof(RECT)); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Adds a polygonal region to the existing selection. points points to an array of POINT structures. + * Each structure specifies the x-coordinate and y-coordinate of one vertex of the polygon. + * npoints specifies the number of POINT structures in the array pointed to by points. + */ +bool CxImage::SelectionAddPolygon(POINT *points, int32_t npoints, uint8_t level) +{ + if (points==NULL || npoints<3) return false; + + if (pSelection==NULL) SelectionCreate(); + if (pSelection==NULL) return false; + + uint8_t* plocal = (uint8_t*)calloc(head.biWidth*head.biHeight, 1); + RECT localbox = {head.biWidth,0,0,head.biHeight}; + + int32_t x,y,i=0; + POINT *current; + POINT *next = NULL; + POINT *start = NULL; + //trace contour + while (i < npoints){ + current = &points[i]; + if (current->x!=-1){ + if (i==0 || (i>0 && points[i-1].x==-1)) start = &points[i]; + + if ((i+1)==npoints || points[i+1].x==-1) + next = start; + else + next = &points[i+1]; + + float beta; + if (current->x != next->x){ + beta = (float)(next->y - current->y)/(float)(next->x - current->x); + if (current->x < next->x){ + for (x=current->x; x<=next->x; x++){ + y = (int32_t)(current->y + (x - current->x) * beta); + if (IsInside(x,y)) plocal[x + y * head.biWidth] = 255; + } + } else { + for (x=current->x; x>=next->x; x--){ + y = (int32_t)(current->y + (x - current->x) * beta); + if (IsInside(x,y)) plocal[x + y * head.biWidth] = 255; + } + } + } + if (current->y != next->y){ + beta = (float)(next->x - current->x)/(float)(next->y - current->y); + if (current->y < next->y){ + for (y=current->y; y<=next->y; y++){ + x = (int32_t)(current->x + (y - current->y) * beta); + if (IsInside(x,y)) plocal[x + y * head.biWidth] = 255; + } + } else { + for (y=current->y; y>=next->y; y--){ + x = (int32_t)(current->x + (y - current->y) * beta); + if (IsInside(x,y)) plocal[x + y * head.biWidth] = 255; + } + } + } + } + + RECT r2; + if (current->x < next->x) {r2.left=current->x; r2.right=next->x; } else {r2.left=next->x ; r2.right=current->x; } + if (current->y < next->y) {r2.bottom=current->y; r2.top=next->y; } else {r2.bottom=next->y ; r2.top=current->y; } + if (localbox.top < r2.top) localbox.top = max(0L,min(head.biHeight-1,r2.top+1)); + if (localbox.left > r2.left) localbox.left = max(0L,min(head.biWidth-1,r2.left-1)); + if (localbox.right < r2.right) localbox.right = max(0L,min(head.biWidth-1,r2.right+1)); + if (localbox.bottom > r2.bottom) localbox.bottom = max(0L,min(head.biHeight-1,r2.bottom-1)); + + i++; + } + + //fill the outer region + int32_t npix=(localbox.right - localbox.left)*(localbox.top - localbox.bottom); + POINT* pix = (POINT*)calloc(npix,sizeof(POINT)); + uint8_t back=0, mark=1; + int32_t fx, fy, fxx, fyy, first, last; + int32_t xmin = 0; + int32_t xmax = 0; + int32_t ymin = 0; + int32_t ymax = 0; + + for (int32_t side=0; side<4; side++){ + switch(side){ + case 0: + xmin=localbox.left; xmax=localbox.right+1; ymin=localbox.bottom; ymax=localbox.bottom+1; + break; + case 1: + xmin=localbox.right; xmax=localbox.right+1; ymin=localbox.bottom; ymax=localbox.top+1; + break; + case 2: + xmin=localbox.left; xmax=localbox.right+1; ymin=localbox.top; ymax=localbox.top+1; + break; + case 3: + xmin=localbox.left; xmax=localbox.left+1; ymin=localbox.bottom; ymax=localbox.top+1; + break; + } + //fill from the border points + for(y=ymin;y=localbox.left && fxx<=localbox.right && fyy>=localbox.bottom && fyy<=localbox.top ) + { + plocal[fxx + fyy*head.biWidth] = mark; + if (fyy > 0 && plocal[fxx + (fyy - 1)*head.biWidth] == back){ + pix[last].x = fx; + pix[last].y = fy - 1; + last++; + if (last == npix) last = 0; + } + if ((fyy + 1)=localbox.left && fxx<=localbox.right && fyy>=localbox.bottom && fyy<=localbox.top ) + { + plocal[fxx + (y + fy)*head.biWidth] = mark; + if (fyy > 0 && plocal[fxx + (fyy - 1)*head.biWidth] == back){ + pix[last].x = fx; + pix[last].y = fy - 1; + last++; + if (last == npix) last = 0; + } + if ((fyy + 1) localbox.left) info.rSelectionBox.left = min(head.biWidth,localbox.left); + if (info.rSelectionBox.right <= localbox.right) info.rSelectionBox.right = min(head.biWidth,localbox.right + 1); + if (info.rSelectionBox.bottom > localbox.bottom) info.rSelectionBox.bottom = min(head.biHeight,localbox.bottom); + + free(plocal); + free(pix); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Adds to the selection all the pixels matching the specified color. + */ +bool CxImage::SelectionAddColor(RGBQUAD c, uint8_t level) +{ + if (pSelection==NULL) SelectionCreate(); + if (pSelection==NULL) return false; + + RECT localbox = {head.biWidth,0,0,head.biHeight}; + + for (int32_t y = 0; y < head.biHeight; y++){ + for (int32_t x = 0; x < head.biWidth; x++){ + RGBQUAD color = BlindGetPixelColor(x, y); + if (color.rgbRed == c.rgbRed && + color.rgbGreen == c.rgbGreen && + color.rgbBlue == c.rgbBlue) + { + pSelection[x + y * head.biWidth] = level; + + if (localbox.top < y) localbox.top = y; + if (localbox.left > x) localbox.left = x; + if (localbox.right < x) localbox.right = x; + if (localbox.bottom > y) localbox.bottom = y; + } + } + } + + if (info.rSelectionBox.top <= localbox.top) info.rSelectionBox.top = localbox.top + 1; + if (info.rSelectionBox.left > localbox.left) info.rSelectionBox.left = localbox.left; + if (info.rSelectionBox.right <= localbox.right) info.rSelectionBox.right = localbox.right + 1; + if (info.rSelectionBox.bottom > localbox.bottom) info.rSelectionBox.bottom = localbox.bottom; + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Adds a single pixel to the existing selection. + */ +bool CxImage::SelectionAddPixel(int32_t x, int32_t y, uint8_t level) +{ + if (pSelection==NULL) SelectionCreate(); + if (pSelection==NULL) return false; + + if (IsInside(x,y)) { + pSelection[x + y * head.biWidth] = level; // set the correct mask bit + + if (info.rSelectionBox.top <= y) info.rSelectionBox.top = y+1; + if (info.rSelectionBox.left > x) info.rSelectionBox.left = x; + if (info.rSelectionBox.right <= x) info.rSelectionBox.right = x+1; + if (info.rSelectionBox.bottom > y) info.rSelectionBox.bottom = y; + + return true; + } + + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Exports the selection channel in a 8bpp grayscale image. + */ +bool CxImage::SelectionSplit(CxImage *dest) +{ + if (!pSelection || !dest) return false; + + CxImage tmp(head.biWidth,head.biHeight,8); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + for(int32_t y=0; yTransfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Creates the selection channel from a gray scale image. + * black = unselected + */ +bool CxImage::SelectionSet(CxImage &from) +{ + if (!from.IsGrayScale() || head.biWidth != from.head.biWidth || head.biHeight != from.head.biHeight){ + strcpy(info.szLastError,"CxImage::SelectionSet: wrong width or height, or image is not gray scale"); + return false; + } + + if (pSelection==NULL) pSelection = (uint8_t*)malloc(head.biWidth * head.biHeight); + + uint8_t* src = from.info.pImage; + uint8_t* dst = pSelection; + if (src==NULL || dst==NULL){ + strcpy(info.szLastError,"CxImage::SelectionSet: null pointer"); + return false; + } + + for (int32_t y=0; y=info.rSelectionBox.right; x--){ + if (pSelection[x+y*head.biWidth]){ + info.rSelectionBox.right = x+1; + continue; + } + } + } + + for (x=0; x=info.rSelectionBox.top; y--){ + if (pSelection[x+y*head.biWidth]){ + info.rSelectionBox.top = y+1; + continue; + } + } + } + +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Gets the Selection level for a single pixel + * "blind" version assumes that (x,y) is inside to the image. + */ +uint8_t CxImage::BlindSelectionGet(const int32_t x,const int32_t y) +{ +#ifdef _DEBUG + if (!IsInside(x,y) || (pSelection==0)) + #if CXIMAGE_SUPPORT_EXCEPTION_HANDLING + throw 0; + #else + return 0; + #endif +#endif + return pSelection[x+y*head.biWidth]; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Returns pointer to selection data for pixel (x,y). + */ +uint8_t* CxImage::SelectionGetPointer(const int32_t x,const int32_t y) +{ + if (pSelection && IsInside(x,y)) return pSelection+x+y*head.biWidth; + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::SelectionFlip() +{ + if (!pSelection) return false; + + uint8_t *buff = (uint8_t*)malloc(head.biWidth); + if (!buff) return false; + + uint8_t *iSrc,*iDst; + iSrc = pSelection + (head.biHeight-1)*head.biWidth; + iDst = pSelection; + for (int32_t i=0; i<(head.biHeight/2); ++i) + { + memcpy(buff, iSrc, head.biWidth); + memcpy(iSrc, iDst, head.biWidth); + memcpy(iDst, buff, head.biWidth); + iSrc-=head.biWidth; + iDst+=head.biWidth; + } + + free(buff); + + int32_t top = info.rSelectionBox.top; + info.rSelectionBox.top = head.biHeight - info.rSelectionBox.bottom; + info.rSelectionBox.bottom = head.biHeight - top; + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::SelectionMirror() +{ + if (!pSelection) return false; + uint8_t* pSelection2 = (uint8_t*)malloc(head.biWidth * head.biHeight); + if (!pSelection2) return false; + + uint8_t *iSrc,*iDst; + int32_t wdt=head.biWidth-1; + iSrc=pSelection + wdt; + iDst=pSelection2; + for(int32_t y=0; y < head.biHeight; y++){ + for(int32_t x=0; x <= wdt; x++) + *(iDst+x)=*(iSrc-x); + iSrc+=head.biWidth; + iDst+=head.biWidth; + } + free(pSelection); + pSelection=pSelection2; + + int32_t left = info.rSelectionBox.left; + info.rSelectionBox.left = head.biWidth - info.rSelectionBox.right; + info.rSelectionBox.right = head.biWidth - left; + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_WINDOWS +/** + * Converts the selection in a HRGN object. + */ +bool CxImage::SelectionToHRGN(HRGN& region) +{ + if (pSelection && region){ + for(int32_t y = 0; y < head.biHeight; y++){ + HRGN hTemp = NULL; + int32_t iStart = -1; + int32_t x = 0; + for(; x < head.biWidth; x++){ + if (pSelection[x + y * head.biWidth] != 0){ + if (iStart == -1) iStart = x; + continue; + }else{ + if (iStart >= 0){ + hTemp = CreateRectRgn(iStart, y, x, y + 1); + CombineRgn(region, hTemp, region, RGN_OR); + DeleteObject(hTemp); + iStart = -1; + } + } + } + if (iStart >= 0){ + hTemp = CreateRectRgn(iStart, y, x, y + 1); + CombineRgn(region, hTemp, region, RGN_OR); + DeleteObject(hTemp); + iStart = -1; + } + } + return true; + } + return false; +} +#endif //CXIMAGE_SUPPORT_WINDOWS +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_SELECTION diff --git a/DuiLib/3rd/CxImage/ximaska.cpp b/DuiLib/3rd/CxImage/ximaska.cpp new file mode 100644 index 0000000..3c935d9 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximaska.cpp @@ -0,0 +1,126 @@ +/* + * File: ximaska.cpp + * Purpose: Platform Independent SKA Image Class Loader and Writer + * 25/Sep/2007 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximaska.h" + +#if CXIMAGE_SUPPORT_SKA + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageSKA::Decode(CxFile *hFile) +{ + if (hFile==NULL) + return false; + + // read the header + SKAHEADER ska_header; + hFile->Read(&ska_header,sizeof(SKAHEADER),1); + + ska_header.Width = m_ntohs(ska_header.Width); + ska_header.Height = m_ntohs(ska_header.Height); + ska_header.dwUnknown = m_ntohl(ska_header.dwUnknown); + + // check header + if (ska_header.dwUnknown != 0x01000000 || + ska_header.Width > 0x7FFF || ska_header.Height > 0x7FFF || + ska_header.BppExp != 3) + return false; + + if (info.nEscape == -1){ + head.biWidth = ska_header.Width ; + head.biHeight= ska_header.Height; + info.dwType = CXIMAGE_FORMAT_SKA; + return true; + } + + int32_t bpp = 1<Read(ppal,nColors*sizeof(rgb_color),1); + SetPalette(ppal,nColors); + free(ppal); + + //read the image + hFile->Read(GetBits(),ska_header.Width*ska_header.Height,1); + + //reorder rows + if (GetEffWidth() != ska_header.Width){ + uint8_t *src,*dst; + src = GetBits() + ska_header.Width*(ska_header.Height-1); + dst = GetBits(ska_header.Height-1); + for(int32_t y=0;y 8) { + strcpy(info.szLastError,"SKA Images must be 8 bit or less"); + return false; + } + + SKAHEADER ska_header; + + ska_header.Width = (uint16_t)GetWidth(); + ska_header.Height = (uint16_t)GetHeight(); + ska_header.BppExp = 3; + ska_header.dwUnknown = 0x01000000; + + ska_header.Width = m_ntohs(ska_header.Width); + ska_header.Height = m_ntohs(ska_header.Height); + ska_header.dwUnknown = m_ntohl(ska_header.dwUnknown); + + hFile->Write(&ska_header,sizeof(SKAHEADER),1); + + ska_header.Width = m_ntohs(ska_header.Width); + ska_header.Height = m_ntohs(ska_header.Height); + ska_header.dwUnknown = m_ntohl(ska_header.dwUnknown); + + if (head.biBitCount<8) IncreaseBpp(8); + + rgb_color pal[256]; + for(int32_t idx=0; idx<256; idx++){ + GetPaletteColor(idx,&(pal[idx].r),&(pal[idx].g),&(pal[idx].b)); + } + + hFile->Write(pal,256*sizeof(rgb_color),1); + + uint8_t* src = GetBits(ska_header.Height-1); + for(int32_t y=0;yWrite(src,ska_header.Width,1); + src -= GetEffWidth(); + } + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_SKA + diff --git a/DuiLib/3rd/CxImage/ximaska.h b/DuiLib/3rd/CxImage/ximaska.h new file mode 100644 index 0000000..c43d0de --- /dev/null +++ b/DuiLib/3rd/CxImage/ximaska.h @@ -0,0 +1,44 @@ +/* + * File: ximaska.h + * Purpose: SKA Image Class Loader and Writer + */ +/* ========================================================== + * CxImageSKA (c) 25/Sep/2007 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * ========================================================== + */ +#if !defined(__ximaSKA_h) +#define __ximaSKA_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_SKA + +class CxImageSKA: public CxImage +{ +#pragma pack(1) + typedef struct tagSkaHeader { + uint16_t Width; + uint16_t Height; + uint8_t BppExp; + uint32_t dwUnknown; +} SKAHEADER; +#pragma pack() + +public: + CxImageSKA(): CxImage(CXIMAGE_FORMAT_SKA) {} + +// bool Load(const char * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_ICO);} +// bool Save(const char * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_ICO);} + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +#endif // CXIMAGE_SUPPORT_ENCODE +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximatga.cpp b/DuiLib/3rd/CxImage/ximatga.cpp new file mode 100644 index 0000000..8c925b2 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximatga.cpp @@ -0,0 +1,320 @@ +/* + * File: ximatga.cpp + * Purpose: Platform Independent TGA Image Class Loader and Writer + * 05/Jan/2001 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximatga.h" + +#if CXIMAGE_SUPPORT_TGA + +#include "ximaiter.h" + +// Definitions for image types. +#define TGA_Null 0 +#define TGA_Map 1 +#define TGA_RGB 2 +#define TGA_Mono 3 +#define TGA_RLEMap 9 +#define TGA_RLERGB 10 +#define TGA_RLEMono 11 +#define TGA_CompMap 32 +#define TGA_CompMap4 33 + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageTGA::Decode(CxFile *hFile) +{ + if (hFile == NULL) return false; + + TGAHEADER tgaHead; + + cx_try + { + if (hFile->Read(&tgaHead,sizeof(tgaHead),1)==0) + cx_throw("Not a TGA"); + + tga_toh(&tgaHead); + + bool bCompressed; + switch (tgaHead.ImageType){ + case TGA_Map: + case TGA_RGB: + case TGA_Mono: + bCompressed = false; + break; + case TGA_RLEMap: + case TGA_RLERGB: + case TGA_RLEMono: + bCompressed = true; + break; + default: + cx_throw("Unknown TGA image type"); + } + + if (tgaHead.ImageWidth==0 || tgaHead.ImageHeight==0 || tgaHead.PixelDepth==0 || tgaHead.CmapLength>256) + cx_throw("bad TGA header"); + + if (tgaHead.PixelDepth!=8 && tgaHead.PixelDepth!=15 && tgaHead.PixelDepth!=16 && tgaHead.PixelDepth!=24 && tgaHead.PixelDepth!=32) + cx_throw("bad TGA header"); + + if (info.nEscape == -1){ + head.biWidth = tgaHead.ImageWidth ; + head.biHeight= tgaHead.ImageHeight; + info.dwType = CXIMAGE_FORMAT_TGA; + return true; + } + + if (tgaHead.IdLength>0) hFile->Seek(tgaHead.IdLength,SEEK_CUR); //skip descriptor + + Create(tgaHead.ImageWidth, tgaHead.ImageHeight, tgaHead.PixelDepth, CXIMAGE_FORMAT_TGA); +#if CXIMAGE_SUPPORT_ALPHA // + if (tgaHead.PixelDepth==32) AlphaCreate(); // Image has alpha channel +#endif //CXIMAGE_SUPPORT_ALPHA + + if (!IsValid()) cx_throw("TGA Create failed"); + + if (info.nEscape) cx_throw("Cancelled"); // - cancel decoding + + if (tgaHead.CmapType != 0){ // read the palette + rgb_color pal[256]; + hFile->Read(pal,tgaHead.CmapLength*sizeof(rgb_color), 1); + for (int32_t i=0;i - cancel decoding + + if (hFile == NULL || hFile->Eof()) cx_throw("corrupted TGA"); + + if (bYReversed) pDest = iter.GetRow(tgaHead.ImageHeight-y-1); + else pDest = iter.GetRow(y); + + if (bCompressed) rleLeftover = ExpandCompressedLine(pDest,&tgaHead,hFile,tgaHead.ImageWidth,y,rleLeftover); + else ExpandUncompressedLine (pDest,&tgaHead,hFile,tgaHead.ImageWidth,y,0); + } + + if (bXReversed) Mirror(); + +#if CXIMAGE_SUPPORT_ALPHA + if (bYReversed && tgaHead.PixelDepth==32) AlphaFlip(); // +#endif //CXIMAGE_SUPPORT_ALPHA + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + return false; + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageTGA::Encode(CxFile * hFile) +{ + if (EncodeSafeCheck(hFile)) return false; + + if (head.biBitCount<8){ + strcpy(info.szLastError,"Bit depth must be 8 or 24"); + return false; + } + + TGAHEADER tgaHead; + + tgaHead.IdLength = 0; // Image ID Field Length + tgaHead.CmapType = GetPalette()!=0; // Color Map Type + tgaHead.ImageType = (head.biBitCount == 8) ? (uint8_t)TGA_Map : (uint8_t)TGA_RGB; // Image Type + + tgaHead.CmapIndex=0; // First Entry Index + tgaHead.CmapLength=(head.biBitCount == 8) ? 256 : 0; // Color Map Length + tgaHead.CmapEntrySize=(head.biBitCount == 8) ? (uint8_t)24 : (uint8_t)0; // Color Map Entry Size + + tgaHead.X_Origin=0; // X-origin of Image + tgaHead.Y_Origin=0; // Y-origin of Image + tgaHead.ImageWidth=(uint16_t)head.biWidth; // Image Width + tgaHead.ImageHeight=(uint16_t)head.biHeight; // Image Height + tgaHead.PixelDepth=(uint8_t)head.biBitCount; // Pixel Depth + tgaHead.ImagDesc=0; // Image Descriptor + + if (pAlpha && head.biBitCount==24) tgaHead.PixelDepth=32; + + tga_toh(&tgaHead); + hFile->Write(&tgaHead,sizeof(TGAHEADER),1); + tga_toh(&tgaHead); + + if (head.biBitCount==8){ + rgb_color pal[256]; + RGBQUAD* ppal = GetPalette(); + for (int32_t i=0;i<256; i++){ + pal[i].r = ppal[i].rgbBlue; + pal[i].g = ppal[i].rgbGreen; + pal[i].b = ppal[i].rgbRed; + } + hFile->Write(&pal,256*sizeof(rgb_color),1); + } + + CImageIterator iter(this); + uint8_t* pDest; + if (pAlpha==0 || head.biBitCount==8){ + for (int32_t y=0; y < tgaHead.ImageHeight; y++){ + pDest = iter.GetRow(y); + hFile->Write(pDest,tgaHead.ImageWidth * (head.biBitCount >> 3),1); + } + } else { + pDest = (uint8_t*)malloc(4*tgaHead.ImageWidth); + RGBQUAD c; + for (int32_t y=0; y < tgaHead.ImageHeight; y++){ + for(int32_t x=0, x4=0;x + pDest[x4+3]=AlphaGet(x,y); +#else + pDest[x4+3]=0; +#endif //CXIMAGE_SUPPORT_ALPHA + } + hFile->Write(pDest,4*tgaHead.ImageWidth,1); + } + free(pDest); + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +uint8_t CxImageTGA::ExpandCompressedLine(uint8_t* pDest,TGAHEADER* ptgaHead,CxFile *hFile,int32_t width, int32_t y, uint8_t rleLeftover) +{ + uint8_t rle; + int32_t filePos=0; + for (int32_t x=0; xRead(&rle,1,1); + } + if (rle & 128) { // RLE-Encoded packet + rle -= 127; // Calculate real repeat count. + if ((x+rle)>width){ + rleLeftover = (uint8_t)(128 + (rle - (width - x) - 1)); + filePos = hFile->Tell(); + rle = (uint8_t)(width - x); + } + switch (ptgaHead->PixelDepth) + { + case 32: { + RGBQUAD color; + hFile->Read(&color,4,1); + for (int32_t ix = 0; ix < rle; ix++){ + memcpy(&pDest[3*ix],&color,3); +#if CXIMAGE_SUPPORT_ALPHA // + AlphaSet(ix+x,y,color.rgbReserved); +#endif //CXIMAGE_SUPPORT_ALPHA + } + break; + } + case 24: { + rgb_color triple; + hFile->Read(&triple,3,1); + for (int32_t ix = 0; ix < rle; ix++) memcpy(&pDest[3*ix],&triple,3); + break; + } + case 15: + case 16: { + uint16_t pixel; + hFile->Read(&pixel,2,1); + rgb_color triple; + triple.r = (uint8_t)(( pixel & 0x1F ) * 8); // red + triple.g = (uint8_t)(( pixel >> 2 ) & 0x0F8); // green + triple.b = (uint8_t)(( pixel >> 7 ) & 0x0F8); // blue + for (int32_t ix = 0; ix < rle; ix++){ + memcpy(&pDest[3*ix],&triple,3); + } + break; + } + case 8: { + uint8_t pixel; + hFile->Read(&pixel,1,1); + for (int32_t ix = 0; ix < rle; ix++) pDest[ix] = pixel; + } + } + if (rleLeftover!=255) hFile->Seek(filePos, SEEK_SET); + } else { // Raw packet + rle += 1; // Calculate real repeat count. + if ((x+rle)>width){ + rleLeftover = (uint8_t)(rle - (width - x) - 1); + rle = (uint8_t)(width - x); + } + ExpandUncompressedLine(pDest,ptgaHead,hFile,rle,y,x); + } + if (head.biBitCount == 24) pDest += rle*3; else pDest += rle; + x += rle; + } + return rleLeftover; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageTGA::ExpandUncompressedLine(uint8_t* pDest,TGAHEADER* ptgaHead,CxFile *hFile,int32_t width, int32_t y, int32_t xoffset) +{ + switch (ptgaHead->PixelDepth){ + case 8: + hFile->Read(pDest,width,1); + break; + case 15: + case 16:{ + uint8_t* dst=pDest; + uint16_t pixel; + for (int32_t x=0; xRead(&pixel,2,1); + *dst++ = (uint8_t)(( pixel & 0x1F ) * 8); // blue + *dst++ = (uint8_t)(( pixel >> 2 ) & 0x0F8); // green + *dst++ = (uint8_t)(( pixel >> 7 ) & 0x0F8); // red + } + break; + } + case 24: + hFile->Read(pDest,3*width,1); + break; + case 32:{ + uint8_t* dst=pDest; + for (int32_t x=0; xRead(&pixel,4,1); + *dst++ = pixel.rgbBlue; + *dst++ = pixel.rgbGreen; + *dst++ = pixel.rgbRed; +#if CXIMAGE_SUPPORT_ALPHA // + AlphaSet(x+xoffset,y,pixel.rgbReserved); //alpha +#endif //CXIMAGE_SUPPORT_ALPHA + } + break; + } + } +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageTGA::tga_toh(TGAHEADER* p) +{ + p->CmapIndex = m_ntohs(p->CmapIndex); + p->CmapLength = m_ntohs(p->CmapLength); + p->X_Origin = m_ntohs(p->X_Origin); + p->Y_Origin = m_ntohs(p->Y_Origin); + p->ImageWidth = m_ntohs(p->ImageWidth); + p->ImageHeight = m_ntohs(p->ImageHeight); +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_TGA diff --git a/DuiLib/3rd/CxImage/ximatga.h b/DuiLib/3rd/CxImage/ximatga.h new file mode 100644 index 0000000..ff9d169 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximatga.h @@ -0,0 +1,61 @@ +/* + * File: ximatga.h + * Purpose: TARGA Image Class Loader and Writer + */ +/* ========================================================== + * CxImageTGA (c) 05/Jan/2002 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * + * Parts of the code come from Paintlib : Copyright (c) 1996-1998 Ulrich von Zadow + * ========================================================== + */ +#if !defined(__ximaTGA_h) +#define __ximaTGA_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_TGA + +class CxImageTGA: public CxImage +{ +#pragma pack(1) +typedef struct tagTgaHeader +{ + uint8_t IdLength; // Image ID Field Length + uint8_t CmapType; // Color Map Type + uint8_t ImageType; // Image Type + + uint16_t CmapIndex; // First Entry Index + uint16_t CmapLength; // Color Map Length + uint8_t CmapEntrySize; // Color Map Entry Size + + uint16_t X_Origin; // X-origin of Image + uint16_t Y_Origin; // Y-origin of Image + uint16_t ImageWidth; // Image Width + uint16_t ImageHeight; // Image Height + uint8_t PixelDepth; // Pixel Depth + uint8_t ImagDesc; // Image Descriptor +} TGAHEADER; +#pragma pack() + +public: + CxImageTGA(): CxImage(CXIMAGE_FORMAT_TGA) {} + +// bool Load(const TCHAR * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_TGA);} +// bool Save(const TCHAR * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_TGA);} + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +#endif // CXIMAGE_SUPPORT_ENCODE +protected: + uint8_t ExpandCompressedLine(uint8_t* pDest,TGAHEADER* ptgaHead,CxFile *hFile,int32_t width, int32_t y, uint8_t rleLeftover); + void ExpandUncompressedLine(uint8_t* pDest,TGAHEADER* ptgaHead,CxFile *hFile,int32_t width, int32_t y, int32_t xoffset); + void tga_toh(TGAHEADER* p); +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximath.cpp b/DuiLib/3rd/CxImage/ximath.cpp new file mode 100644 index 0000000..37533e2 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximath.cpp @@ -0,0 +1,97 @@ +#include "ximage.h" +#include "ximath.h" +#include + +//this module should contain some classes for geometrical transformations +//usable with selections, etc... once it's done, that is. :) + +CxPoint2::CxPoint2() +{ + x=y=0.0f; +} + +CxPoint2::CxPoint2(float const x_, float const y_) +{ + x=x_; + y=y_; +} + +CxPoint2::CxPoint2(CxPoint2 const &p) +{ + x=p.x; + y=p.y; +} + +float CxPoint2::Distance(CxPoint2 const p2) +{ + return (float)sqrt((x-p2.x)*(x-p2.x)+(y-p2.y)*(y-p2.y)); +} + +float CxPoint2::Distance(float const x_, float const y_) +{ + return (float)sqrt((x-x_)*(x-x_)+(y-y_)*(y-y_)); +} + +CxRect2::CxRect2() +{ +} + +CxRect2::CxRect2(float const x1_, float const y1_, float const x2_, float const y2_) +{ + botLeft.x=x1_; + botLeft.y=y1_; + topRight.x=x2_; + topRight.y=y2_; +} + +CxRect2::CxRect2(CxRect2 const &p) +{ + botLeft=p.botLeft; + topRight=p.topRight; +} + +float CxRect2::Surface() const +/* + * Returns the surface of rectangle. + */ +{ + return (topRight.x-botLeft.x)*(topRight.y-botLeft.y); +} + +CxRect2 CxRect2::CrossSection(CxRect2 const &r2) const +/* + * Returns crossection with another rectangle. + */ +{ + CxRect2 cs; + cs.botLeft.x=max(botLeft.x, r2.botLeft.x); + cs.botLeft.y=max(botLeft.y, r2.botLeft.y); + cs.topRight.x=min(topRight.x, r2.topRight.x); + cs.topRight.y=min(topRight.y, r2.topRight.y); + if (cs.botLeft.x<=cs.topRight.x && cs.botLeft.y<=cs.topRight.y) { + return cs; + } else { + return CxRect2(0,0,0,0); + }//if +} + +CxPoint2 CxRect2::Center() const +/* + * Returns the center point of rectangle. + */ +{ + return CxPoint2((topRight.x+botLeft.x)/2.0f, (topRight.y+botLeft.y)/2.0f); +} + +float CxRect2::Width() const +//returns rectangle width +{ + return topRight.x-botLeft.x; +} + +float CxRect2::Height() const +//returns rectangle height +{ + return topRight.y-botLeft.y; +} + diff --git a/DuiLib/3rd/CxImage/ximath.h b/DuiLib/3rd/CxImage/ximath.h new file mode 100644 index 0000000..10b9898 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximath.h @@ -0,0 +1,39 @@ +#if !defined(__ximath_h) +#define __ximath_h + +#include "ximadef.h" + +//***bd*** simple floating point point +class DLL_EXP CxPoint2 +{ +public: + CxPoint2(); + CxPoint2(float const x_, float const y_); + CxPoint2(CxPoint2 const &p); + + float Distance(CxPoint2 const p2); + float Distance(float const x_, float const y_); + + float x,y; +}; + +//and simple rectangle +class DLL_EXP CxRect2 +{ +public: + CxRect2(); + CxRect2(float const x1_, float const y1_, float const x2_, float const y2_); + CxRect2(CxPoint2 const &bl, CxPoint2 const &tr); + CxRect2(CxRect2 const &p); + + float Surface() const; + CxRect2 CrossSection(CxRect2 const &r2) const; + CxPoint2 Center() const; + float Width() const; + float Height() const; + + CxPoint2 botLeft; + CxPoint2 topRight; +}; + +#endif diff --git a/DuiLib/3rd/CxImage/ximatif.cpp b/DuiLib/3rd/CxImage/ximatif.cpp new file mode 100644 index 0000000..44714d7 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximatif.cpp @@ -0,0 +1,982 @@ +/* + * File: ximatif.cpp + * Purpose: Platform Independent TIFF Image Class Loader and Writer + * 07/Aug/2001 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximatif.h" + +#if CXIMAGE_SUPPORT_TIF + +#define FIX_16BPP_DARKIMG // + VK: if uncomment, dark 16bpp images are fixed + +#include "../tiff/tiffio.h" + +#define CVT(x) (((x) * 255L) / ((1L<<16)-1)) +#define SCALE(x) (((x)*((1L<<16)-1))/255) +#define CalculateLine(width,bitdepth) (((width * bitdepth) + 7) / 8) +#define CalculatePitch(line) (line + 3 & ~3) + +extern "C" TIFF* _TIFFOpenEx(CxFile* stream, const char* mode); + +//////////////////////////////////////////////////////////////////////////////// +CxImageTIF::~CxImageTIF() +{ + if (m_tif2) TIFFClose(m_tif2); +} +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageTIF::Decode(CxFile * hFile) +{ + //Comment this line if you need more information on errors + // TIFFSetErrorHandler(NULL); // + + //Open file and fill the TIFF structure + // m_tif = TIFFOpen(imageFileName,"rb"); + TIFF* m_tif = _TIFFOpenEx(hFile, "rb"); + + uint32 height=0; + uint32 width=0; + uint16 bitspersample=1; + uint16 samplesperpixel=1; + uint32 rowsperstrip=(uint32_t)-1; + uint16 photometric=0; + uint16 compression=1; + uint16 orientation=ORIENTATION_TOPLEFT; // + uint16 res_unit; // + uint32 x, y; + float resolution, offset; + BOOL isRGB; + uint8_t *bits; //pointer to source data + uint8_t *bits2; //pointer to destination data + + cx_try + { + //check if it's a tiff file + if (!m_tif) + cx_throw("Error encountered while opening TIFF file"); + + // - 12/2002 : get NumFrames directly, instead of looping + // info.nNumFrames=0; + // while(TIFFSetDirectory(m_tif,(uint16)info.nNumFrames)) info.nNumFrames++; + info.nNumFrames = TIFFNumberOfDirectories(m_tif); + + if (!TIFFSetDirectory(m_tif, (uint16)info.nFrame)) + cx_throw("Error: page not present in TIFF file"); + + //get image info + TIFFGetField(m_tif, TIFFTAG_IMAGEWIDTH, &width); + TIFFGetField(m_tif, TIFFTAG_IMAGELENGTH, &height); + TIFFGetField(m_tif, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel); + TIFFGetField(m_tif, TIFFTAG_BITSPERSAMPLE, &bitspersample); + TIFFGetField(m_tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip); + TIFFGetField(m_tif, TIFFTAG_PHOTOMETRIC, &photometric); + TIFFGetField(m_tif, TIFFTAG_ORIENTATION, &orientation); + + if (info.nEscape == -1) { + // Return output dimensions only + head.biWidth = width; + head.biHeight = height; + info.dwType = CXIMAGE_FORMAT_TIF; + cx_throw("output dimensions returned"); + } + + TIFFGetFieldDefaulted(m_tif, TIFFTAG_RESOLUTIONUNIT, &res_unit); + if (TIFFGetField(m_tif, TIFFTAG_XRESOLUTION, &resolution)) + { + if (res_unit == RESUNIT_CENTIMETER) resolution = (float)(resolution*2.54f + 0.5f); + SetXDPI((int32_t)resolution); + } + if (TIFFGetField(m_tif, TIFFTAG_YRESOLUTION, &resolution)) + { + if (res_unit == RESUNIT_CENTIMETER) resolution = (float)(resolution*2.54f + 0.5f); + SetYDPI((int32_t)resolution); + } + + if (TIFFGetField(m_tif, TIFFTAG_XPOSITION, &offset)) info.xOffset = (int32_t)offset; + if (TIFFGetField(m_tif, TIFFTAG_YPOSITION, &offset)) info.yOffset = (int32_t)offset; + + head.biClrUsed=0; + info.nBkgndIndex =-1; + + if (rowsperstrip>height){ + rowsperstrip=height; + TIFFSetField(m_tif, TIFFTAG_ROWSPERSTRIP, rowsperstrip); + } + + isRGB = /*(bitspersample >= 8) && (VK: it is possible so for RGB to have < 8 bpp!)*/ + (photometric == PHOTOMETRIC_RGB) || + (photometric == PHOTOMETRIC_YCBCR) || + (photometric == PHOTOMETRIC_SEPARATED) || + (photometric == PHOTOMETRIC_LOGL) || + (photometric == PHOTOMETRIC_LOGLUV); + + if (isRGB){ + head.biBitCount=24; + }else{ + if ((photometric==PHOTOMETRIC_MINISBLACK)||(photometric==PHOTOMETRIC_MINISWHITE)||(photometric==PHOTOMETRIC_PALETTE)){ + if (bitspersample == 1){ + head.biBitCount=1; //B&W image + head.biClrUsed =2; + } else if (bitspersample == 4) { + head.biBitCount=4; //16 colors gray scale + head.biClrUsed =16; + } else { + head.biBitCount=8; //gray scale + head.biClrUsed =256; + } + } else if (bitspersample == 4) { + head.biBitCount=4; // 16 colors + head.biClrUsed=16; + } else { + head.biBitCount=8; //256 colors + head.biClrUsed=256; + } + + if ((bitspersample > 8) && (photometric==PHOTOMETRIC_PALETTE)) // + VK + (BIG palette! => convert to RGB) + { head.biBitCount=24; + head.biClrUsed =0; + } + } + + if (info.nEscape) cx_throw("Cancelled"); // - cancel decoding + + Create(width,height,head.biBitCount,CXIMAGE_FORMAT_TIF); //image creation + if (!pDib) cx_throw("CxImageTIF can't create image"); + +#if CXIMAGE_SUPPORT_ALPHA + if (samplesperpixel==4) AlphaCreate(); //add alpha support for 32bpp tiffs + if (samplesperpixel==2 && bitspersample==8) AlphaCreate(); //add alpha support for 8bpp + alpha +#endif //CXIMAGE_SUPPORT_ALPHA + + TIFFGetField(m_tif, TIFFTAG_COMPRESSION, &compression); + SetCodecOption(compression); // save original compression type + + if (isRGB) { + // Read the whole image into one big RGBA buffer using + // the traditional TIFFReadRGBAImage() API that we trust. + uint32* raster; // retrieve RGBA image + uint32 *row; + + raster = (uint32*)_TIFFmalloc(width * height * sizeof (uint32)); + if (raster == NULL) cx_throw("No space for raster buffer"); + + // Read the image in one chunk into an RGBA array + if(!TIFFReadRGBAImage(m_tif, width, height, raster, 1)) { + _TIFFfree(raster); + cx_throw("Corrupted TIFF file!"); + } + + // read the raster lines and save them in the DIB + // with RGB mode, we have to change the order of the 3 samples RGB + row = &raster[0]; + bits2 = info.pImage; + for (y = 0; y < height; y++) { + + if (info.nEscape){ // - cancel decoding + _TIFFfree(raster); + cx_throw("Cancelled"); + } + + bits = bits2; + for (x = 0; x < width; x++) { + *bits++ = (uint8_t)TIFFGetB(row[x]); + *bits++ = (uint8_t)TIFFGetG(row[x]); + *bits++ = (uint8_t)TIFFGetR(row[x]); +#if CXIMAGE_SUPPORT_ALPHA + if (samplesperpixel==4) AlphaSet(x,y,(uint8_t)TIFFGetA(row[x])); +#endif //CXIMAGE_SUPPORT_ALPHA + } + row += width; + bits2 += info.dwEffWidth; + } + _TIFFfree(raster); + } else { + int32_t BIG_palette = (bitspersample > 8) && // + VK + (photometric==PHOTOMETRIC_PALETTE); + if (BIG_palette && (bitspersample > 24)) // + VK + cx_throw("Too big palette to handle"); // + VK + + RGBQUAD *pal; + pal=(RGBQUAD*)calloc(BIG_palette ? 1< 8) + + // set up the colormap based on photometric + switch(photometric) { + case PHOTOMETRIC_MINISBLACK: // bitmap and greyscale image types + case PHOTOMETRIC_MINISWHITE: + if (bitspersample == 1) { // Monochrome image + if (photometric == PHOTOMETRIC_MINISBLACK) { + pal[1].rgbRed = pal[1].rgbGreen = pal[1].rgbBlue = 255; + } else { + pal[0].rgbRed = pal[0].rgbGreen = pal[0].rgbBlue = 255; + } + } else { // need to build the scale for greyscale images + if (photometric == PHOTOMETRIC_MINISBLACK) { + for (int32_t i=0; i<(1< 0) { + if (red[n] >= 256 || green[n] >= 256 || blue[n] >= 256) { + Palette16Bits=TRUE; + break; + } + } + } + + // load the palette in the DIB + for (int32_t i = (1 << ( BIG_palette ? bitspersample : bpp )) - 1; i >= 0; i--) { + if (Palette16Bits) { + pal[i].rgbRed =(uint8_t) CVT(red[i]); + pal[i].rgbGreen = (uint8_t) CVT(green[i]); + pal[i].rgbBlue = (uint8_t) CVT(blue[i]); + } else { + pal[i].rgbRed = (uint8_t) red[i]; + pal[i].rgbGreen = (uint8_t) green[i]; + pal[i].rgbBlue = (uint8_t) blue[i]; + } + } + break; + } + if (!BIG_palette) { // + VK (BIG palette is stored until image is ready) + SetPalette(pal,/*head.biClrUsed*/ 1<(int32_t)(head.biSizeImage*samplesperpixel)) + bitsize = head.biSizeImage*samplesperpixel; + if (bitsize<(int32_t)(info.dwEffWidth*rowsperstrip)) + bitsize = info.dwEffWidth*rowsperstrip; + + if ((bitspersample > 8) && (bitspersample != 16)) // + VK (for bitspersample == 9..15,17..32..64 + bitsize *= (bitspersample + 7)/8; + + int32_t tiled_image = TIFFIsTiled(m_tif); + uint32 tw=0, tl=0; + uint8_t* tilebuf=NULL; + if (tiled_image){ + TIFFGetField(m_tif, TIFFTAG_TILEWIDTH, &tw); + TIFFGetField(m_tif, TIFFTAG_TILELENGTH, &tl); + rowsperstrip = tl; + bitsize = TIFFTileSize(m_tif) * (int32_t)(1+width/tw); + tilebuf = (uint8_t*)malloc(TIFFTileSize(m_tif)); + } + + bits = (uint8_t*)malloc(bitspersample==16? bitsize*2 : bitsize); // * VK + uint8_t * bits16 = NULL; // + VK + int32_t line16 = 0; // + VK + + if (!tiled_image && bitspersample==16) { // + VK + + line16 = line; + line = CalculateLine(width, 8 * samplesperpixel); + bits16 = bits; + bits = (uint8_t*)malloc(bitsize); + } + + if (bits==NULL){ + if (bits16) free(bits16); // + VK + if (pal) free(pal); // + VK + if (tilebuf)free(tilebuf); // + VK + cx_throw("CxImageTIF can't allocate memory"); + } + +#ifdef FIX_16BPP_DARKIMG // + VK: for each line, store shift count bits used to fix it + uint8_t* row_shifts = NULL; + if (bits16) row_shifts = (uint8_t*)malloc(height); +#endif + + for (ys = 0; ys < height; ys += rowsperstrip) { + + if (info.nEscape){ // - cancel decoding + free(bits); + cx_throw("Cancelled"); + } + + nrow = (ys + rowsperstrip > height ? height - ys : rowsperstrip); + + if (tiled_image){ + uint32 imagew = TIFFScanlineSize(m_tif); + uint32 tilew = TIFFTileRowSize(m_tif); + int32_t iskew = imagew - tilew; + uint8* bufp = (uint8*) bits; + + uint32 colb = 0; + for (uint32 col = 0; col < width; col += tw) { + if (TIFFReadTile(m_tif, tilebuf, col, ys, 0, 0) < 0){ + free(tilebuf); + free(bits); + cx_throw("Corrupted tiled TIFF file!"); + } + + if (colb + tw > imagew) { + uint32 owidth = imagew - colb; + uint32 oskew = tilew - owidth; + TileToStrip(bufp + colb, tilebuf, nrow, owidth, oskew + iskew, oskew ); + } else { + TileToStrip(bufp + colb, tilebuf, nrow, tilew, iskew, 0); + } + colb += tilew; + } + + } else { + if (TIFFReadEncodedStrip(m_tif, TIFFComputeStrip(m_tif, ys, 0), + (bits16? bits16 : bits), nrow * (bits16 ? line16 : line)) == -1) { // * VK + +#ifdef NOT_IGNORE_CORRUPTED + free(bits); + if (bits16) free(bits16); // + VK + cx_throw("Corrupted TIFF file!"); +#else + break; +#endif + } + } + + for (y = 0; y < nrow; y++) { + int32_t offset=(nrow-y-1)*line; + if ((bitspersample==16) && !BIG_palette) { // * VK + int32_t offset16 = (nrow-y-1)*line16; // + VK + if (bits16) { // + VK + +#ifdef FIX_16BPP_DARKIMG + int32_t the_shift; + uint8_t hi_byte, hi_max=0; + uint32_t xi; + for (xi=0;xi<(uint32)line;xi++) { + hi_byte = bits16[xi*2+offset16+1]; + if(hi_byte>hi_max) + hi_max = hi_byte; + } + the_shift = (hi_max == 0) ? 8 : 0; + if (!the_shift) + while( ! (hi_max & 0x80) ) { + the_shift++; + hi_max <<= 1; + } + row_shifts[height-ys-nrow+y] = the_shift; + the_shift = 8 - the_shift; + for (xi=0;xi<(uint32)line;xi++) + bits[xi+offset]= ((bits16[xi*2+offset16+1]<<8) | bits16[xi*2+offset16]) >> the_shift; +#else + for (uint32_t xi=0;xi<(uint32)line;xi++) + bits[xi+offset]=bits16[xi*2+offset16+1]; +#endif + } else { + for (uint32_t xi=0;xi=(int32_t)width){ + yi--; + xi=0; + } + } + } else { //photometric==PHOTOMETRIC_CIELAB + if (head.biBitCount!=24){ //fix image + Create(width,height,24,CXIMAGE_FORMAT_TIF); +#if CXIMAGE_SUPPORT_ALPHA + if (samplesperpixel==4) AlphaCreate(); +#endif //CXIMAGE_SUPPORT_ALPHA + } + + int32_t xi=0; + uint32 ii=0; + int32_t yi=height-ys-nrow+y; + RGBQUAD c; + int32_t l,a,b,bitsoffset; + double p,cx,cy,cz,cr,cg,cb; + while (ii127) a-=256; + if (b>127) b-=256; + // lab to xyz + p = (l/2.55 + 16) / 116.0; + cx = pow( p + a * 0.002, 3); + cy = pow( p, 3); + cz = pow( p - b * 0.005, 3); + // white point + cx*=0.95047; + //cy*=1.000; + cz*=1.0883; + // xyz to rgb + cr = 3.240479 * cx - 1.537150 * cy - 0.498535 * cz; + cg = -0.969256 * cx + 1.875992 * cy + 0.041556 * cz; + cb = 0.055648 * cx - 0.204043 * cy + 1.057311 * cz; + + if ( cr > 0.00304 ) cr = 1.055 * pow(cr,0.41667) - 0.055; + else cr = 12.92 * cr; + if ( cg > 0.00304 ) cg = 1.055 * pow(cg,0.41667) - 0.055; + else cg = 12.92 * cg; + if ( cb > 0.00304 ) cb = 1.055 * pow(cb,0.41667) - 0.055; + else cb = 12.92 * cb; + + c.rgbRed =(uint8_t)max(0,min(255,(int32_t)(cr*255))); + c.rgbGreen=(uint8_t)max(0,min(255,(int32_t)(cg*255))); + c.rgbBlue =(uint8_t)max(0,min(255,(int32_t)(cb*255))); + + SetPixelColor(xi,yi,c); +#if CXIMAGE_SUPPORT_ALPHA + if (samplesperpixel==4) AlphaSet(xi,yi,bits[bitsoffset+3]); +#endif //CXIMAGE_SUPPORT_ALPHA + ii++; + xi++; + if (xi>=(int32_t)width){ + yi--; + xi=0; + } + } + } + } + } + free(bits); + if (bits16) free(bits16); + +#ifdef FIX_16BPP_DARKIMG + if (row_shifts && (samplesperpixel == 1) && (bitspersample==16) && !BIG_palette) { + // 1. calculate maximum necessary shift + int32_t min_row_shift = 8; + for( y=0; y row_shifts[y]) min_row_shift = row_shifts[y]; + } + // 2. for rows having less shift value, correct such rows: + for( y=0; y>= need_shift; + } + } + } + if (row_shifts) free( row_shifts ); +#endif + + if (tiled_image) free(tilebuf); + if (pal) free(pal); + + switch(orientation){ + case ORIENTATION_TOPRIGHT: /* row 0 top, col 0 rhs */ + Mirror(); + break; + case ORIENTATION_BOTRIGHT: /* row 0 bottom, col 0 rhs */ + Flip(); + Mirror(); + break; + case ORIENTATION_BOTLEFT: /* row 0 bottom, col 0 lhs */ + Flip(); + break; + case ORIENTATION_LEFTTOP: /* row 0 lhs, col 0 top */ + RotateRight(); + Mirror(); + break; + case ORIENTATION_RIGHTTOP: /* row 0 rhs, col 0 top */ + RotateLeft(); + break; + case ORIENTATION_RIGHTBOT: /* row 0 rhs, col 0 bottom */ + RotateLeft(); + Mirror(); + break; + case ORIENTATION_LEFTBOT: /* row 0 lhs, col 0 bottom */ + RotateRight(); + break; + } + + } + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + if (m_tif) TIFFClose(m_tif); + if (info.nEscape == -1 && info.dwType == CXIMAGE_FORMAT_TIF) return true; + return false; + } + TIFFClose(m_tif); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageTIF::Encode(CxFile * hFile, bool bAppend) +{ + cx_try + { + if (hFile==NULL) cx_throw(CXIMAGE_ERR_NOFILE); + if (pDib==NULL) cx_throw(CXIMAGE_ERR_NOIMAGE); + + // replaced "w+b" with "a", to append an image directly on an existing file + if (m_tif2==NULL) m_tif2=_TIFFOpenEx(hFile, "a"); + if (m_tif2==NULL) cx_throw("initialization fail"); + + if (bAppend || m_pages) m_multipage=true; + m_pages++; + + if (!EncodeBody(m_tif2,m_multipage,m_pages,m_pages)) cx_throw("Error saving TIFF file"); + if (bAppend) { + if (!TIFFWriteDirectory(m_tif2)) cx_throw("Error saving TIFF directory"); + } + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + if (m_tif2){ + TIFFClose(m_tif2); + m_tif2=NULL; + m_multipage=false; + m_pages=0; + } + return false; + } + if (!bAppend){ + TIFFClose(m_tif2); + m_tif2=NULL; + m_multipage=false; + m_pages=0; + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +// Thanks to Abe +bool CxImageTIF::Encode(CxFile * hFile, CxImage ** pImages, int32_t pagecount) +{ + cx_try + { + if (hFile==NULL) cx_throw("invalid file pointer"); + if (pImages==NULL || pagecount<=0) cx_throw("multipage TIFF, no images!"); + + int32_t i; + for (i=0; iIsValid())) + cx_throw("Empty image"); + } + + CxImageTIF ghost; + for (i=0; i some viewers do not handle PHOTOMETRIC_MINISBLACK: + * let's transform the image in PHOTOMETRIC_MINISWHITE + */ + //invert the colors + RGBQUAD tempRGB=GetPaletteColor(0); + SetPaletteColor(0,GetPaletteColor(1)); + SetPaletteColor(1,tempRGB); + //invert the pixels + uint8_t *iSrc=info.pImage; + for (uint32_t i=0;irgbRed != x)||(rgb->rgbRed != rgb->rgbGreen)||(rgb->rgbRed != rgb->rgbBlue)){ + photometric = PHOTOMETRIC_PALETTE; + break; + } + rgb++; + } + break; + case 24: + case 32: + photometric = PHOTOMETRIC_RGB; + break; + } + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid() && bitcount==8) samplesperpixel=2; //8bpp + alpha layer +#endif //CXIMAGE_SUPPORT_ALPHA + +// line = CalculateLine(width, bitspersample * samplesperpixel); +// pitch = (uint16)CalculatePitch(line); + + //prepare the palette struct + RGBQUAD pal[256]; + if (GetPalette()){ + uint8_t b; + memcpy(pal,GetPalette(),GetPaletteSize()); + for(uint16_t a=0;a gives better compression + TIFFSetField(m_tif, TIFFTAG_ROWSPERSTRIP, rowsperstrip); + + // handle metrics + TIFFSetField(m_tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH); + TIFFSetField(m_tif, TIFFTAG_XRESOLUTION, (float)info.xDPI); + TIFFSetField(m_tif, TIFFTAG_YRESOLUTION, (float)info.yDPI); +// TIFFSetField(m_tif, TIFFTAG_XPOSITION, (float)info.xOffset); +// TIFFSetField(m_tif, TIFFTAG_YPOSITION, (float)info.yOffset); + + // multi-paging - Thanks to Abe + if (multipage) + { + char page_number[20]; + sprintf(page_number, "Page %d", page); + + TIFFSetField(m_tif, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE); + TIFFSetField(m_tif, TIFFTAG_PAGENUMBER, page,pagecount); + TIFFSetField(m_tif, TIFFTAG_PAGENAME, page_number); + } else { + TIFFSetField(m_tif, TIFFTAG_SUBFILETYPE, 0); + } + + // palettes (image colormaps are automatically scaled to 16-bits) + if (photometric == PHOTOMETRIC_PALETTE) { + uint16 *r, *g, *b; + r = (uint16 *) _TIFFmalloc(sizeof(uint16) * 3 * 256); + g = r + 256; + b = g + 256; + + for (int32_t i = 255; i >= 0; i--) { + b[i] = (uint16)SCALE((uint16)pal[i].rgbRed); + g[i] = (uint16)SCALE((uint16)pal[i].rgbGreen); + r[i] = (uint16)SCALE((uint16)pal[i].rgbBlue); + } + + TIFFSetField(m_tif, TIFFTAG_COLORMAP, r, g, b); + _TIFFfree(r); + } + + // compression + if (GetCodecOption(CXIMAGE_FORMAT_TIF)) { + compression = (uint16_t)GetCodecOption(CXIMAGE_FORMAT_TIF); + } else { + switch (bitcount) { + case 1 : + compression = COMPRESSION_CCITTFAX4; + break; + case 4 : + case 8 : + compression = COMPRESSION_LZW; + break; + case 24 : + case 32 : + compression = COMPRESSION_JPEG; + break; + default : + compression = COMPRESSION_NONE; + break; + } + } + TIFFSetField(m_tif, TIFFTAG_COMPRESSION, compression); + + switch (compression) { + case COMPRESSION_JPEG: + TIFFSetField(m_tif, TIFFTAG_JPEGQUALITY, GetJpegQuality()); + TIFFSetField(m_tif, TIFFTAG_ROWSPERSTRIP, ((7+rowsperstrip)>>3)<<3); + break; + case COMPRESSION_LZW: + if (bitcount>=8) TIFFSetField(m_tif, TIFFTAG_PREDICTOR, 2); + break; + } + + // read the DIB lines from bottom to top and save them in the TIF + + uint8_t *bits; + switch(bitcount) { + case 1 : + case 4 : + case 8 : + { + if (samplesperpixel==1){ + bits = (uint8_t*)malloc(info.dwEffWidth); + if (!bits) return false; + for (y = 0; y < height; y++) { + memcpy(bits,info.pImage + (height - y - 1)*info.dwEffWidth,info.dwEffWidth); + if (TIFFWriteScanline(m_tif,bits, y, 0)==-1){ + free(bits); + return false; + } + } + free(bits); + } +#if CXIMAGE_SUPPORT_ALPHA + else { //8bpp + alpha layer + bits = (uint8_t*)malloc(2*width); + if (!bits) return false; + for (y = 0; y < height; y++) { + for (x=0;x 0) { + uint32 j = cols; + while (j-- > 0) + *out++ = *in++; + out += outskew; + in += inskew; + } +} +//////////////////////////////////////////////////////////////////////////////// +TIFF* CxImageTIF::TIFFOpenEx(CxFile * hFile) +{ + if (hFile) return _TIFFOpenEx(hFile, "rb"); + return NULL; +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageTIF::TIFFCloseEx(TIFF* tif) +{ + if (tif) TIFFClose(tif); +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageTIF::MoveBits( uint8_t* dest, uint8_t* from, int32_t count, int32_t bpp ) +{ int32_t offbits = 0; + uint16 w; + uint32 d; + if (bpp <= 8) { + while (count-- > 0) { + if (offbits + bpp <= 8) + w = *from >> (8 - offbits - bpp); + else { + w = *from++ << (offbits + bpp - 8); + w |= *from >> (16 - offbits - bpp); + } + offbits += bpp; + if (offbits >= 8) { + offbits -= 8; + if (offbits == 0) from++; + } + *dest++ = (uint8_t)w & ((1 << bpp)-1); + } + } else if (bpp < 16) { + while (count-- > 0) { + d = (*from << 24) | (from[1]<<16) | (from[2]<<8) | from[3]; + d >>= (24 - offbits); + *dest++ = (uint8_t) ( d ); + offbits += bpp; + while (offbits >= 8) { + from++; + offbits -= 8; + } + } + } else if (bpp < 32) { + while (count-- > 0) { + d = (*from << 24) | (from[1]<<16) | (from[2]<<8) | from[3]; + //d = *(uint32*)from; + *dest++ = (uint8_t) ( d >> (offbits + bpp - 8) ); + offbits += bpp; + while (offbits >= 8) { + from++; + offbits -= 8; + } + } + } else { + while (count-- > 0) { + d = *(uint32*)from; + *dest++ = (uint8_t) (d >> 24); + from += 4; + } + } +} +//////////////////////////////////////////////////////////////////////////////// +void CxImageTIF::MoveBitsPal( uint8_t* dest, uint8_t*from, int32_t count, int32_t bpp, RGBQUAD* pal ) +{ int32_t offbits = 0; + uint32 d; + uint16 palidx; + while (count-- > 0) { + d = (*from << 24) | ( *( from + 1 ) << 16 ) + | ( *( from + 2 ) << 8 ) + | ( *( from + 3 ) ); + palidx = (uint16) (d >> (32 - offbits - bpp)); + if (bpp < 16) { + palidx <<= 16-bpp; + palidx = (palidx >> 8) | (palidx <<8); + palidx >>= 16-bpp; + } else palidx = (palidx >> 8) | (palidx << 8); + *dest++ = pal[palidx].rgbBlue; + *dest++ = pal[palidx].rgbGreen; + *dest++ = pal[palidx].rgbRed; + offbits += bpp; + while (offbits >= 8) { + from++; + offbits -= 8; + } + } +} +//////////////////////////////////////////////////////////////////////////////// + +#endif // CXIMAGE_SUPPORT_TIF diff --git a/DuiLib/3rd/CxImage/ximatif.h b/DuiLib/3rd/CxImage/ximatif.h new file mode 100644 index 0000000..605af09 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximatif.h @@ -0,0 +1,62 @@ +/* + * File: ximatif.h + * Purpose: TIFF Image Class Loader and Writer + */ +/* ========================================================== + * CxImageTIF (c) 07/Aug/2001 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * + * Special thanks to Troels Knakkergaard for new features, enhancements and bugfixes + * + * Special thanks to Abe for MultiPageTIFF code. + * + * LibTIFF is: + * Copyright (c) 1988-1997 Sam Leffler + * Copyright (c) 1991-1997 Silicon Graphics, Inc. + * ========================================================== + */ + +#if !defined(__ximatif_h) +#define __ximatif_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_TIF + +#include "../tiff/tiffio.h" + +class DLL_EXP CxImageTIF: public CxImage +{ +public: + CxImageTIF(): CxImage(CXIMAGE_FORMAT_TIF) {m_tif2=NULL; m_multipage=false; m_pages=0;} + ~CxImageTIF(); + + TIFF* TIFFOpenEx(CxFile * hFile); + void TIFFCloseEx(TIFF* tif); + +// bool Load(const TCHAR * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_TIF);} +// bool Save(const TCHAR * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_TIF);} + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile, bool bAppend=false); + bool Encode(CxFile * hFile, CxImage ** pImages, int32_t pagecount); + bool Encode(FILE *hFile, bool bAppend=false) { CxIOFile file(hFile); return Encode(&file,bAppend); } + bool Encode(FILE *hFile, CxImage ** pImages, int32_t pagecount) + { CxIOFile file(hFile); return Encode(&file, pImages, pagecount); } +#endif // CXIMAGE_SUPPORT_ENCODE + +protected: + void TileToStrip(uint8* out, uint8* in, uint32 rows, uint32 cols, int32_t outskew, int32_t inskew); + bool EncodeBody(TIFF *m_tif, bool multipage=false, int32_t page=0, int32_t pagecount=0); + TIFF *m_tif2; + bool m_multipage; + int32_t m_pages; + void MoveBits( uint8_t* dest, uint8_t* from, int32_t count, int32_t bpp ); + void MoveBitsPal( uint8_t* dest, uint8_t*from, int32_t count, int32_t bpp, RGBQUAD* pal ); +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximatran.cpp b/DuiLib/3rd/CxImage/ximatran.cpp new file mode 100644 index 0000000..42238a1 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximatran.cpp @@ -0,0 +1,2728 @@ +// xImaTran.cpp : Transformation functions +/* 07/08/2001 v1.00 - Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximage.h" +#include "ximath.h" + +#if CXIMAGE_SUPPORT_BASICTRANSFORMATIONS +//////////////////////////////////////////////////////////////////////////////// +/** + * Increases the number of bits per pixel of the image. + * \param nbit: 4, 8, 24 + */ +bool CxImage::IncreaseBpp(uint32_t nbit) +{ + if (!pDib) return false; + switch (nbit){ + case 4: + { + if (head.biBitCount==4) return true; + if (head.biBitCount>4) return false; + + CxImage tmp; + tmp.CopyInfo(*this); + tmp.Create(head.biWidth,head.biHeight,4,info.dwType); + tmp.SetPalette(GetPalette(),GetNumColors()); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + +#if CXIMAGE_SUPPORT_SELECTION + tmp.SelectionCopy(*this); +#endif //CXIMAGE_SUPPORT_SELECTION + +#if CXIMAGE_SUPPORT_ALPHA + tmp.AlphaCopy(*this); +#endif //CXIMAGE_SUPPORT_ALPHA + + for (int32_t y=0;y8) return false; + + CxImage tmp; + tmp.CopyInfo(*this); + tmp.Create(head.biWidth,head.biHeight,8,info.dwType); + tmp.SetPalette(GetPalette(),GetNumColors()); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + +#if CXIMAGE_SUPPORT_SELECTION + tmp.SelectionCopy(*this); +#endif //CXIMAGE_SUPPORT_SELECTION + +#if CXIMAGE_SUPPORT_ALPHA + tmp.AlphaCopy(*this); +#endif //CXIMAGE_SUPPORT_ALPHA + + for (int32_t y=0;y24) return false; + + CxImage tmp; + tmp.CopyInfo(*this); + tmp.Create(head.biWidth,head.biHeight,24,info.dwType); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + if (info.nBkgndIndex>=0) //translate transparency + tmp.info.nBkgndColor=GetPaletteColor((uint8_t)info.nBkgndIndex); + +#if CXIMAGE_SUPPORT_SELECTION + tmp.SelectionCopy(*this); +#endif //CXIMAGE_SUPPORT_SELECTION + +#if CXIMAGE_SUPPORT_ALPHA + tmp.AlphaCopy(*this); + if (AlphaPaletteIsValid() && !AlphaIsValid()) tmp.AlphaCreate(); +#endif //CXIMAGE_SUPPORT_ALPHA + + for (int32_t y=0;y= 0) info.nBkgndIndex = ppal[info.nBkgndIndex].rgbBlue; + //create a "real" 8 bit gray scale image + if (head.biBitCount==8){ + uint8_t *img=info.pImage; + for(uint32_t i=0;i> 1]&((uint8_t)0x0F<> pos)].rgbBlue; + } else { + uint8_t pos = (uint8_t)(7-x%8); + iDst[x]= ppal[(uint8_t)((iSrc[x >> 3]&((uint8_t)0x01<> pos)].rgbBlue; + } + } + } + Transfer(ima); + } + } else { //from RGB to 8 bit gray scale + uint8_t *iSrc=info.pImage; + CxImage ima; + ima.CopyInfo(*this); + if (!ima.Create(head.biWidth,head.biHeight,8,info.dwType)) return false; + ima.SetGrayPalette(); + if (GetTransIndex()>=0){ + RGBQUAD c = GetTransColor(); + ima.SetTransIndex((uint8_t)RGB2GRAY(c.rgbRed,c.rgbGreen,c.rgbBlue)); + } +#if CXIMAGE_SUPPORT_SELECTION + ima.SelectionCopy(*this); +#endif //CXIMAGE_SUPPORT_SELECTION +#if CXIMAGE_SUPPORT_ALPHA + ima.AlphaCopy(*this); +#endif //CXIMAGE_SUPPORT_ALPHA + uint8_t *img=ima.GetBits(); + int32_t l8=ima.GetEffWidth(); + int32_t l=head.biWidth * 3; + for(int32_t y=0; y < head.biHeight; y++) { + for(int32_t x=0,x8=0; x < l; x+=3,x8++) { + img[x8+y*l8]=(uint8_t)RGB2GRAY(*(iSrc+x+2),*(iSrc+x+1),*(iSrc+x+0)); + } + iSrc+=info.dwEffWidth; + } + Transfer(ima); + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa Mirror + * \author [qhbo] + */ +bool CxImage::Flip(bool bFlipSelection, bool bFlipAlpha) +{ + if (!pDib) return false; + + uint8_t *buff = (uint8_t*)malloc(info.dwEffWidth); + if (!buff) return false; + + uint8_t *iSrc,*iDst; + iSrc = GetBits(head.biHeight-1); + iDst = GetBits(0); + for (int32_t i=0; i<(head.biHeight/2); ++i) + { + memcpy(buff, iSrc, info.dwEffWidth); + memcpy(iSrc, iDst, info.dwEffWidth); + memcpy(iDst, buff, info.dwEffWidth); + iSrc-=info.dwEffWidth; + iDst+=info.dwEffWidth; + } + + free(buff); + + if (bFlipSelection){ +#if CXIMAGE_SUPPORT_SELECTION + SelectionFlip(); +#endif //CXIMAGE_SUPPORT_SELECTION + } + + if (bFlipAlpha){ +#if CXIMAGE_SUPPORT_ALPHA + AlphaFlip(); +#endif //CXIMAGE_SUPPORT_ALPHA + } + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \sa Flip + */ +bool CxImage::Mirror(bool bMirrorSelection, bool bMirrorAlpha) +{ + if (!pDib) return false; + + CxImage* imatmp = new CxImage(*this,false,true,true); + if (!imatmp) return false; + if (!imatmp->IsValid()){ + delete imatmp; + return false; + } + + uint8_t *iSrc,*iDst; + int32_t wdt=(head.biWidth-1) * (head.biBitCount==24 ? 3:1); + iSrc=info.pImage + wdt; + iDst=imatmp->info.pImage; + int32_t x,y; + switch (head.biBitCount){ + case 24: + for(y=0; y < head.biHeight; y++){ + for(x=0; x <= wdt; x+=3){ + *(iDst+x)=*(iSrc-x); + *(iDst+x+1)=*(iSrc-x+1); + *(iDst+x+2)=*(iSrc-x+2); + } + iSrc+=info.dwEffWidth; + iDst+=info.dwEffWidth; + } + break; + case 8: + for(y=0; y < head.biHeight; y++){ + for(x=0; x <= wdt; x++) + *(iDst+x)=*(iSrc-x); + iSrc+=info.dwEffWidth; + iDst+=info.dwEffWidth; + } + break; + default: + for(y=0; y < head.biHeight; y++){ + for(x=0; x <= wdt; x++) + imatmp->SetPixelIndex(x,y,GetPixelIndex(wdt-x,y)); + } + } + + if (bMirrorSelection){ +#if CXIMAGE_SUPPORT_SELECTION + imatmp->SelectionMirror(); +#endif //CXIMAGE_SUPPORT_SELECTION + } + + if (bMirrorAlpha){ +#if CXIMAGE_SUPPORT_ALPHA + imatmp->AlphaMirror(); +#endif //CXIMAGE_SUPPORT_ALPHA + } + + Transfer(*imatmp); + delete imatmp; + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +#define RBLOCK 64 + +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::RotateLeft(CxImage* iDst) +{ + if (!pDib) return false; + + int32_t newWidth = GetHeight(); + int32_t newHeight = GetWidth(); + + CxImage imgDest; + imgDest.CopyInfo(*this); + imgDest.Create(newWidth,newHeight,GetBpp(),GetType()); + imgDest.SetPalette(GetPalette()); + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) imgDest.AlphaCreate(); +#endif + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()) imgDest.SelectionCreate(); +#endif + + int32_t x,x2,y,dlineup; + + // Speedy rotate for BW images + if (head.biBitCount == 1) { + + uint8_t *sbits, *dbits, *dbitsmax, bitpos, *nrow,*srcdisp; + ldiv_t div_r; + + uint8_t *bsrc = GetBits(), *bdest = imgDest.GetBits(); + dbitsmax = bdest + imgDest.head.biSizeImage - 1; + dlineup = 8 * imgDest.info.dwEffWidth - imgDest.head.biWidth; + + imgDest.Clear(0); + for (y = 0; y < head.biHeight; y++) { + // Figure out the Column we are going to be copying to + div_r = ldiv(y + dlineup, (int32_t)8); + // set bit pos of src column byte + bitpos = (uint8_t)(1 << div_r.rem); + srcdisp = bsrc + y * info.dwEffWidth; + for (x = 0; x < (int32_t)info.dwEffWidth; x++) { + // Get Source Bits + sbits = srcdisp + x; + // Get destination column + nrow = bdest + (x * 8) * imgDest.info.dwEffWidth + imgDest.info.dwEffWidth - 1 - div_r.quot; + for (int32_t z = 0; z < 8; z++) { + // Get Destination Byte + dbits = nrow + z * imgDest.info.dwEffWidth; + if ((dbits < bdest) || (dbits > dbitsmax)) break; + if (*sbits & (128 >> z)) *dbits |= bitpos; + } + } + }//for y + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) { + for (x = 0; x < newWidth; x++){ + x2=newWidth-x-1; + for (y = 0; y < newHeight; y++){ + imgDest.AlphaSet(x,y,BlindAlphaGet(y, x2)); + }//for y + }//for x + } +#endif //CXIMAGE_SUPPORT_ALPHA + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()) { + imgDest.info.rSelectionBox.left = newWidth-info.rSelectionBox.top; + imgDest.info.rSelectionBox.right = newWidth-info.rSelectionBox.bottom; + imgDest.info.rSelectionBox.bottom = info.rSelectionBox.left; + imgDest.info.rSelectionBox.top = info.rSelectionBox.right; + for (x = 0; x < newWidth; x++){ + x2=newWidth-x-1; + for (y = 0; y < newHeight; y++){ + imgDest.SelectionSet(x,y,BlindSelectionGet(y, x2)); + }//for y + }//for x + } +#endif //CXIMAGE_SUPPORT_SELECTION + + } else { + //anything other than BW: + //bd, 10. 2004: This optimized version of rotation rotates image by smaller blocks. It is quite + //a bit faster than obvious algorithm, because it produces much less CPU cache misses. + //This optimization can be tuned by changing block size (RBLOCK). 96 is good value for current + //CPUs (tested on Athlon XP and Celeron D). Larger value (if CPU has enough cache) will increase + //speed somehow, but once you drop out of CPU's cache, things will slow down drastically. + //For older CPUs with less cache, lower value would yield better results. + + uint8_t *srcPtr, *dstPtr; //source and destionation for 24-bit version + int32_t xs, ys; //x-segment and y-segment + for (xs = 0; xs < newWidth; xs+=RBLOCK) { //for all image blocks of RBLOCK*RBLOCK pixels + for (ys = 0; ys < newHeight; ys+=RBLOCK) { + if (head.biBitCount==24) { + //RGB24 optimized pixel access: + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ //do rotation + info.nProgress = (int32_t)(100*x/newWidth); + x2=newWidth-x-1; + dstPtr = (uint8_t*) imgDest.BlindGetPixelPointer(x,ys); + srcPtr = (uint8_t*) BlindGetPixelPointer(ys, x2); + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + //imgDest.SetPixelColor(x, y, GetPixelColor(y, x2)); + *(dstPtr) = *(srcPtr); + *(dstPtr+1) = *(srcPtr+1); + *(dstPtr+2) = *(srcPtr+2); + srcPtr += 3; + dstPtr += imgDest.info.dwEffWidth; + }//for y + }//for x + } else { + //anything else than 24bpp (and 1bpp): palette + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + info.nProgress = (int32_t)(100*x/newWidth); // + x2=newWidth-x-1; + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + imgDest.SetPixelIndex(x, y, BlindGetPixelIndex(y, x2)); + }//for y + }//for x + }//if (version selection) +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) { + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + x2=newWidth-x-1; + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + imgDest.AlphaSet(x,y,BlindAlphaGet(y, x2)); + }//for y + }//for x + }//if (alpha channel) +#endif //CXIMAGE_SUPPORT_ALPHA + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()) { + imgDest.info.rSelectionBox.left = newWidth-info.rSelectionBox.top; + imgDest.info.rSelectionBox.right = newWidth-info.rSelectionBox.bottom; + imgDest.info.rSelectionBox.bottom = info.rSelectionBox.left; + imgDest.info.rSelectionBox.top = info.rSelectionBox.right; + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + x2=newWidth-x-1; + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + imgDest.SelectionSet(x,y,BlindSelectionGet(y, x2)); + }//for y + }//for x + }//if (selection) +#endif //CXIMAGE_SUPPORT_SELECTION + }//for ys + }//for xs + }//if + + //select the destination + if (iDst) iDst->Transfer(imgDest); + else Transfer(imgDest); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::RotateRight(CxImage* iDst) +{ + if (!pDib) return false; + + int32_t newWidth = GetHeight(); + int32_t newHeight = GetWidth(); + + CxImage imgDest; + imgDest.CopyInfo(*this); + imgDest.Create(newWidth,newHeight,GetBpp(),GetType()); + imgDest.SetPalette(GetPalette()); + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) imgDest.AlphaCreate(); +#endif + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()) imgDest.SelectionCreate(); +#endif + + int32_t x,y,y2; + // Speedy rotate for BW images + if (head.biBitCount == 1) { + + uint8_t *sbits, *dbits, *dbitsmax, bitpos, *nrow,*srcdisp; + ldiv_t div_r; + + uint8_t *bsrc = GetBits(), *bdest = imgDest.GetBits(); + dbitsmax = bdest + imgDest.head.biSizeImage - 1; + + imgDest.Clear(0); + for (y = 0; y < head.biHeight; y++) { + // Figure out the Column we are going to be copying to + div_r = ldiv(y, (int32_t)8); + // set bit pos of src column byte + bitpos = (uint8_t)(128 >> div_r.rem); + srcdisp = bsrc + y * info.dwEffWidth; + for (x = 0; x < (int32_t)info.dwEffWidth; x++) { + // Get Source Bits + sbits = srcdisp + x; + // Get destination column + nrow = bdest + (imgDest.head.biHeight-1-(x*8)) * imgDest.info.dwEffWidth + div_r.quot; + for (int32_t z = 0; z < 8; z++) { + // Get Destination Byte + dbits = nrow - z * imgDest.info.dwEffWidth; + if ((dbits < bdest) || (dbits > dbitsmax)) break; + if (*sbits & (128 >> z)) *dbits |= bitpos; + } + } + } + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()){ + for (y = 0; y < newHeight; y++){ + y2=newHeight-y-1; + for (x = 0; x < newWidth; x++){ + imgDest.AlphaSet(x,y,BlindAlphaGet(y2, x)); + } + } + } +#endif //CXIMAGE_SUPPORT_ALPHA + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()){ + imgDest.info.rSelectionBox.left = info.rSelectionBox.bottom; + imgDest.info.rSelectionBox.right = info.rSelectionBox.top; + imgDest.info.rSelectionBox.bottom = newHeight-info.rSelectionBox.right; + imgDest.info.rSelectionBox.top = newHeight-info.rSelectionBox.left; + for (y = 0; y < newHeight; y++){ + y2=newHeight-y-1; + for (x = 0; x < newWidth; x++){ + imgDest.SelectionSet(x,y,BlindSelectionGet(y2, x)); + } + } + } +#endif //CXIMAGE_SUPPORT_SELECTION + + } else { + //anything else but BW + uint8_t *srcPtr, *dstPtr; //source and destionation for 24-bit version + int32_t xs, ys; //x-segment and y-segment + for (xs = 0; xs < newWidth; xs+=RBLOCK) { + for (ys = 0; ys < newHeight; ys+=RBLOCK) { + if (head.biBitCount==24) { + //RGB24 optimized pixel access: + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + info.nProgress = (int32_t)(100*y/newHeight); // + y2=newHeight-y-1; + dstPtr = (uint8_t*) imgDest.BlindGetPixelPointer(xs,y); + srcPtr = (uint8_t*) BlindGetPixelPointer(y2, xs); + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + //imgDest.SetPixelColor(x, y, GetPixelColor(y2, x)); + *(dstPtr) = *(srcPtr); + *(dstPtr+1) = *(srcPtr+1); + *(dstPtr+2) = *(srcPtr+2); + dstPtr += 3; + srcPtr += info.dwEffWidth; + }//for x + }//for y + } else { + //anything else than BW & RGB24: palette + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + info.nProgress = (int32_t)(100*y/newHeight); // + y2=newHeight-y-1; + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + imgDest.SetPixelIndex(x, y, BlindGetPixelIndex(y2, x)); + }//for x + }//for y + }//if +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()){ + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + y2=newHeight-y-1; + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + imgDest.AlphaSet(x,y,BlindAlphaGet(y2, x)); + }//for x + }//for y + }//if (has alpha) +#endif //CXIMAGE_SUPPORT_ALPHA + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()){ + imgDest.info.rSelectionBox.left = info.rSelectionBox.bottom; + imgDest.info.rSelectionBox.right = info.rSelectionBox.top; + imgDest.info.rSelectionBox.bottom = newHeight-info.rSelectionBox.right; + imgDest.info.rSelectionBox.top = newHeight-info.rSelectionBox.left; + for (y = ys; y < min(newHeight, ys+RBLOCK); y++){ + y2=newHeight-y-1; + for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ + imgDest.SelectionSet(x,y,BlindSelectionGet(y2, x)); + }//for x + }//for y + }//if (has alpha) +#endif //CXIMAGE_SUPPORT_SELECTION + }//for ys + }//for xs + }//if + + //select the destination + if (iDst) iDst->Transfer(imgDest); + else Transfer(imgDest); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Negative() +{ + if (!pDib) return false; + + if (head.biBitCount<=8){ + if (IsGrayScale()){ //GRAYSCALE, selection + if (pSelection){ + for(int32_t y=info.rSelectionBox.bottom; y invert transparent color too + info.nBkgndColor.rgbBlue = (uint8_t)(255-info.nBkgndColor.rgbBlue); + info.nBkgndColor.rgbGreen = (uint8_t)(255-info.nBkgndColor.rgbGreen); + info.nBkgndColor.rgbRed = (uint8_t)(255-info.nBkgndColor.rgbRed); + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_BASICTRANSFORMATIONS +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_TRANSFORMATION +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_EXIF +bool CxImage::RotateExif(int32_t orientation /* = 0 */) +{ + bool ret = true; + if (orientation <= 0) + orientation = info.ExifInfo.Orientation; + if (orientation == 3) + ret = Rotate180(); + else if (orientation == 6) + ret = RotateRight(); + else if (orientation == 8) + ret = RotateLeft(); + else if (orientation == 5) + ret = RotateLeft(); + + info.ExifInfo.Orientation = 1; + return ret; +} +#endif //CXIMAGE_SUPPORT_EXIF + +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Rotate(float angle, CxImage* iDst) +{ + if (!pDib) return false; + + if (fmod(angle,180.0f)==0.0f && fmod(angle,360.0f)!=0.0f) + return Rotate180(iDst); + + // Copyright (c) 1996-1998 Ulrich von Zadow + + // Negative the angle, because the y-axis is negative. + double ang = -angle*acos((float)0)/90; + int32_t newWidth, newHeight; + int32_t nWidth = GetWidth(); + int32_t nHeight= GetHeight(); + double cos_angle = cos(ang); + double sin_angle = sin(ang); + + // Calculate the size of the new bitmap + POINT p1={0,0}; + POINT p2={nWidth,0}; + POINT p3={0,nHeight}; + POINT p4={nWidth,nHeight}; + CxPoint2 newP1,newP2,newP3,newP4, leftTop, rightTop, leftBottom, rightBottom; + + newP1.x = (float)p1.x; + newP1.y = (float)p1.y; + newP2.x = (float)(p2.x*cos_angle - p2.y*sin_angle); + newP2.y = (float)(p2.x*sin_angle + p2.y*cos_angle); + newP3.x = (float)(p3.x*cos_angle - p3.y*sin_angle); + newP3.y = (float)(p3.x*sin_angle + p3.y*cos_angle); + newP4.x = (float)(p4.x*cos_angle - p4.y*sin_angle); + newP4.y = (float)(p4.x*sin_angle + p4.y*cos_angle); + + leftTop.x = min(min(newP1.x,newP2.x),min(newP3.x,newP4.x)); + leftTop.y = min(min(newP1.y,newP2.y),min(newP3.y,newP4.y)); + rightBottom.x = max(max(newP1.x,newP2.x),max(newP3.x,newP4.x)); + rightBottom.y = max(max(newP1.y,newP2.y),max(newP3.y,newP4.y)); + leftBottom.x = leftTop.x; + leftBottom.y = rightBottom.y; + rightTop.x = rightBottom.x; + rightTop.y = leftTop.y; + + newWidth = (int32_t) floor(0.5f + rightTop.x - leftTop.x); + newHeight= (int32_t) floor(0.5f + leftBottom.y - leftTop.y); + CxImage imgDest; + imgDest.CopyInfo(*this); + imgDest.Create(newWidth,newHeight,GetBpp(),GetType()); + imgDest.SetPalette(GetPalette()); + +#if CXIMAGE_SUPPORT_ALPHA + if(AlphaIsValid()) //MTA: Fix for rotation problem when the image has an alpha channel + { + imgDest.AlphaCreate(); + imgDest.AlphaClear(); + } +#endif //CXIMAGE_SUPPORT_ALPHA + + int32_t x,y,newX,newY,oldX,oldY; + + if (head.biClrUsed==0){ //RGB + for (y = (int32_t)leftTop.y, newY = 0; y<=(int32_t)leftBottom.y; y++,newY++){ + info.nProgress = (int32_t)(100*newY/newHeight); + if (info.nEscape) break; + for (x = (int32_t)leftTop.x, newX = 0; x<=(int32_t)rightTop.x; x++,newX++){ + oldX = (int32_t)(x*cos_angle + y*sin_angle + 0.5); + oldY = (int32_t)(y*cos_angle - x*sin_angle + 0.5); + imgDest.SetPixelColor(newX,newY,GetPixelColor(oldX,oldY)); +#if CXIMAGE_SUPPORT_ALPHA + imgDest.AlphaSet(newX,newY,AlphaGet(oldX,oldY)); //MTA: copy the alpha value +#endif //CXIMAGE_SUPPORT_ALPHA + } + } + } else { //PALETTE + for (y = (int32_t)leftTop.y, newY = 0; y<=(int32_t)leftBottom.y; y++,newY++){ + info.nProgress = (int32_t)(100*newY/newHeight); + if (info.nEscape) break; + for (x = (int32_t)leftTop.x, newX = 0; x<=(int32_t)rightTop.x; x++,newX++){ + oldX = (int32_t)(x*cos_angle + y*sin_angle + 0.5); + oldY = (int32_t)(y*cos_angle - x*sin_angle + 0.5); + imgDest.SetPixelIndex(newX,newY,GetPixelIndex(oldX,oldY)); +#if CXIMAGE_SUPPORT_ALPHA + imgDest.AlphaSet(newX,newY,AlphaGet(oldX,oldY)); //MTA: copy the alpha value +#endif //CXIMAGE_SUPPORT_ALPHA + } + } + } + //select the destination + if (iDst) iDst->Transfer(imgDest); + else Transfer(imgDest); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Rotates image around it's center. + * Method can use interpolation with paletted images, but does not change pallete, so results vary. + * (If you have only four colours in a palette, there's not much room for interpolation.) + * + * \param angle - angle in degrees (positive values rotate clockwise) + * \param *iDst - destination image (if null, this image is changed) + * \param inMethod - interpolation method used + * (IM_NEAREST_NEIGHBOUR produces aliasing (fast), IM_BILINEAR softens picture a bit (slower) + * IM_SHARPBICUBIC is slower and produces some halos...) + * \param ofMethod - overflow method (how to choose colour of pixels that have no source) + * \param replColor - replacement colour to use (OM_COLOR, OM_BACKGROUND with no background colour...) + * \param optimizeRightAngles - call faster methods for 90, 180, and 270 degree rotations. Faster methods + * are called for angles, where error (in location of corner pixels) is less + * than 0.25 pixels. + * \param bKeepOriginalSize - rotates the image without resizing. + * + * \author ***bd*** 2.2004 + */ +bool CxImage::Rotate2(float angle, + CxImage *iDst, + InterpolationMethod inMethod, + OverflowMethod ofMethod, + RGBQUAD *replColor, + bool const optimizeRightAngles, + bool const bKeepOriginalSize) +{ + if (!pDib) return false; //no dib no go + + if (fmod(angle,180.0f)==0.0f && fmod(angle,360.0f)!=0.0f) + return Rotate180(iDst); + + double ang = -angle*acos(0.0f)/90.0f; //convert angle to radians and invert (positive angle performs clockwise rotation) + float cos_angle = (float) cos(ang); //these two are needed later (to rotate) + float sin_angle = (float) sin(ang); + + //Calculate the size of the new bitmap (rotate corners of image) + CxPoint2 p[4]; //original corners of the image + p[0]=CxPoint2(-0.5f,-0.5f); + p[1]=CxPoint2(GetWidth()-0.5f,-0.5f); + p[2]=CxPoint2(-0.5f,GetHeight()-0.5f); + p[3]=CxPoint2(GetWidth()-0.5f,GetHeight()-0.5f); + CxPoint2 newp[4]; //rotated positions of corners + //(rotate corners) + if (bKeepOriginalSize){ + for (int32_t i=0; i<4; i++) { + newp[i].x = p[i].x; + newp[i].y = p[i].y; + }//for + } else { + for (int32_t i=0; i<4; i++) { + newp[i].x = (p[i].x*cos_angle - p[i].y*sin_angle); + newp[i].y = (p[i].x*sin_angle + p[i].y*cos_angle); + }//for i + + if (optimizeRightAngles) { + //For rotations of 90, -90 or 180 or 0 degrees, call faster routines + if (newp[3].Distance(CxPoint2(GetHeight()-0.5f, 0.5f-GetWidth())) < 0.25) + //rotation right for circa 90 degrees (diagonal pixels less than 0.25 pixel away from 90 degree rotation destination) + return RotateRight(iDst); + if (newp[3].Distance(CxPoint2(0.5f-GetHeight(), -0.5f+GetWidth())) < 0.25) + //rotation left for ~90 degrees + return RotateLeft(iDst); + if (newp[3].Distance(CxPoint2(0.5f-GetWidth(), 0.5f-GetHeight())) < 0.25) + //rotation left for ~180 degrees + return Rotate180(iDst); + if (newp[3].Distance(p[3]) < 0.25) { + //rotation not significant + if (iDst) iDst->Copy(*this); //copy image to iDst, if required + return true; //and we're done + }//if + }//if + }//if + + //(read new dimensions from location of corners) + float minx = (float) min(min(newp[0].x,newp[1].x),min(newp[2].x,newp[3].x)); + float miny = (float) min(min(newp[0].y,newp[1].y),min(newp[2].y,newp[3].y)); + float maxx = (float) max(max(newp[0].x,newp[1].x),max(newp[2].x,newp[3].x)); + float maxy = (float) max(max(newp[0].y,newp[1].y),max(newp[2].y,newp[3].y)); + int32_t newWidth = (int32_t) floor(maxx-minx+0.5f); + int32_t newHeight= (int32_t) floor(maxy-miny+0.5f); + float ssx=((maxx+minx)- ((float) newWidth-1))/2.0f; //start for x + float ssy=((maxy+miny)- ((float) newHeight-1))/2.0f; //start for y + + float newxcenteroffset = 0.5f * newWidth; + float newycenteroffset = 0.5f * newHeight; + if (bKeepOriginalSize){ + ssx -= 0.5f * GetWidth(); + ssy -= 0.5f * GetHeight(); + } + + //create destination image + CxImage imgDest; + imgDest.CopyInfo(*this); + imgDest.Create(newWidth,newHeight,GetBpp(),GetType()); + imgDest.SetPalette(GetPalette()); +#if CXIMAGE_SUPPORT_ALPHA + if(AlphaIsValid()) imgDest.AlphaCreate(); //MTA: Fix for rotation problem when the image has an alpha channel +#endif //CXIMAGE_SUPPORT_ALPHA + + RGBQUAD rgb; //pixel colour + RGBQUAD rc; + if (replColor!=0) + rc=*replColor; + else { + rc.rgbRed=255; rc.rgbGreen=255; rc.rgbBlue=255; rc.rgbReserved=0; + }//if + float x,y; //destination location (float, with proper offset) + float origx, origy; //origin location + int32_t destx, desty; //destination location + + y=ssy; //initialize y + if (!IsIndexed()){ //RGB24 + //optimized RGB24 implementation (direct write to destination): + uint8_t *pxptr; +#if CXIMAGE_SUPPORT_ALPHA + uint8_t *pxptra=0; +#endif //CXIMAGE_SUPPORT_ALPHA + for (desty=0; destyTransfer(imgDest); + else Transfer(imgDest); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Rotate180(CxImage* iDst) +{ + if (!pDib) return false; + + int32_t wid = GetWidth(); + int32_t ht = GetHeight(); + + CxImage imgDest; + imgDest.CopyInfo(*this); + imgDest.Create(wid,ht,GetBpp(),GetType()); + imgDest.SetPalette(GetPalette()); + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) imgDest.AlphaCreate(); +#endif //CXIMAGE_SUPPORT_ALPHA + + int32_t x,y,y2; + for (y = 0; y < ht; y++){ + info.nProgress = (int32_t)(100*y/ht); // + y2=ht-y-1; + for (x = 0; x < wid; x++){ + if(head.biClrUsed==0)//RGB + imgDest.SetPixelColor(wid-x-1, y2, BlindGetPixelColor(x, y)); + else //PALETTE + imgDest.SetPixelIndex(wid-x-1, y2, BlindGetPixelIndex(x, y)); + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) imgDest.AlphaSet(wid-x-1, y2,BlindAlphaGet(x, y)); +#endif //CXIMAGE_SUPPORT_ALPHA + + } + } + + //select the destination + if (iDst) iDst->Transfer(imgDest); + else Transfer(imgDest); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * Resizes the image. mode can be 0 for slow (bilinear) method , + * 1 for fast (nearest pixel) method, or 2 for accurate (bicubic spline interpolation) method. + * The function is faster with 24 and 1 bpp images, slow for 4 bpp images and slowest for 8 bpp images. + */ +bool CxImage::Resample(int32_t newx, int32_t newy, int32_t mode, CxImage* iDst) +{ + if (newx==0 || newy==0) return false; + + if (head.biWidth==newx && head.biHeight==newy){ + if (iDst) iDst->Copy(*this); + return true; + } + + float xScale, yScale, fX, fY; + xScale = (float)head.biWidth / (float)newx; + yScale = (float)head.biHeight / (float)newy; + + CxImage newImage; + newImage.CopyInfo(*this); + newImage.Create(newx,newy,head.biBitCount,GetType()); + newImage.SetPalette(GetPalette()); + if (!newImage.IsValid()){ + strcpy(info.szLastError,newImage.GetLastError()); + return false; + } + + switch (mode) { + case 1: // nearest pixel + { + for(int32_t y=0; y=head.biHeight) yy = head.biHeight-1; + for(int32_t n=-1; n<3; n++) { + r2 = r1 * KernelBSpline(b - (float)n); + xx = i_x+n; + if (xx<0) xx=0; + if (xx>=head.biWidth) xx=head.biWidth-1; + + if (head.biClrUsed){ + rgb = GetPixelColor(xx,yy); + } else { + iDst = info.pImage + yy*info.dwEffWidth + xx*3; + rgb.rgbBlue = *iDst++; + rgb.rgbGreen= *iDst++; + rgb.rgbRed = *iDst; + } + + rr += rgb.rgbRed * r2; + gg += rgb.rgbGreen * r2; + bb += rgb.rgbBlue * r2; + } + } + + if (head.biClrUsed) + newImage.SetPixelColor(x,y,RGB(rr,gg,bb)); + else { + iDst = newImage.info.pImage + y*newImage.info.dwEffWidth + x*3; + *iDst++ = (uint8_t)bb; + *iDst++ = (uint8_t)gg; + *iDst = (uint8_t)rr; + } + + } + } + break; + } + default: // bilinear interpolation + if (!(head.biWidth>newx && head.biHeight>newy && head.biBitCount==24)) { + // (c) 1999 Steve McMahon (steve@dogma.demon.co.uk) + int32_t ifX, ifY, ifX1, ifY1, xmax, ymax; + float ir1, ir2, ig1, ig2, ib1, ib2, dx, dy; + uint8_t r,g,b; + RGBQUAD rgb1, rgb2, rgb3, rgb4; + xmax = head.biWidth-1; + ymax = head.biHeight-1; + for(int32_t y=0; y + const int32_t ACCURACY = 1000; + int32_t i,j; // index for faValue + int32_t x,y; // coordinates in source image + uint8_t* pSource; + uint8_t* pDest = newImage.info.pImage; + int32_t* naAccu = new int32_t[3 * newx + 3]; + int32_t* naCarry = new int32_t[3 * newx + 3]; + int32_t* naTemp; + int32_t nWeightX,nWeightY; + float fEndX; + int32_t nScale = (int32_t)(ACCURACY * xScale * yScale); + + memset(naAccu, 0, sizeof(int32_t) * 3 * newx); + memset(naCarry, 0, sizeof(int32_t) * 3 * newx); + + int32_t u, v = 0; // coordinates in dest image + float fEndY = yScale - 1.0f; + for (y = 0; y < head.biHeight; y++){ + info.nProgress = (int32_t)(100*y/head.biHeight); // + if (info.nEscape) break; + pSource = info.pImage + y * info.dwEffWidth; + u = i = 0; + fEndX = xScale - 1.0f; + if ((float)y < fEndY) { // complete source row goes into dest row + for (x = 0; x < head.biWidth; x++){ + if ((float)x < fEndX){ // complete source pixel goes into dest pixel + for (j = 0; j < 3; j++) naAccu[i + j] += (*pSource++) * ACCURACY; + } else { // source pixel is splitted for 2 dest pixels + nWeightX = (int32_t)(((float)x - fEndX) * ACCURACY); + for (j = 0; j < 3; j++){ + naAccu[i] += (ACCURACY - nWeightX) * (*pSource); + naAccu[3 + i++] += nWeightX * (*pSource++); + } + fEndX += xScale; + u++; + } + } + } else { // source row is splitted for 2 dest rows + nWeightY = (int32_t)(((float)y - fEndY) * ACCURACY); + for (x = 0; x < head.biWidth; x++){ + if ((float)x < fEndX){ // complete source pixel goes into 2 pixel + for (j = 0; j < 3; j++){ + naAccu[i + j] += ((ACCURACY - nWeightY) * (*pSource)); + naCarry[i + j] += nWeightY * (*pSource++); + } + } else { // source pixel is splitted for 4 dest pixels + nWeightX = (int32_t)(((float)x - fEndX) * ACCURACY); + for (j = 0; j < 3; j++) { + naAccu[i] += ((ACCURACY - nWeightY) * (ACCURACY - nWeightX)) * (*pSource) / ACCURACY; + *pDest++ = (uint8_t)(naAccu[i] / nScale); + naCarry[i] += (nWeightY * (ACCURACY - nWeightX) * (*pSource)) / ACCURACY; + naAccu[i + 3] += ((ACCURACY - nWeightY) * nWeightX * (*pSource)) / ACCURACY; + naCarry[i + 3] = (nWeightY * nWeightX * (*pSource)) / ACCURACY; + i++; + pSource++; + } + fEndX += xScale; + u++; + } + } + if (u < newx){ // possibly not completed due to rounding errors + for (j = 0; j < 3; j++) *pDest++ = (uint8_t)(naAccu[i++] / nScale); + } + naTemp = naCarry; + naCarry = naAccu; + naAccu = naTemp; + memset(naCarry, 0, sizeof(int32_t) * 3); // need only to set first pixel zero + pDest = newImage.info.pImage + (++v * newImage.info.dwEffWidth); + fEndY += yScale; + } + } + if (v < newy){ // possibly not completed due to rounding errors + for (i = 0; i < 3 * newx; i++) *pDest++ = (uint8_t)(naAccu[i] / nScale); + } + delete [] naAccu; + delete [] naCarry; + } + } + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()){ + if (1 == mode){ + newImage.AlphaCreate(); + for(int32_t y=0; yTransfer(newImage); + else Transfer(newImage); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * New simpler resample. Adds new interpolation methods and simplifies code (using GetPixelColorInterpolated + * and GetAreaColorInterpolated). It also (unlike old method) interpolates alpha layer. + * + * \param newx, newy - size of resampled image + * \param inMethod - interpolation method to use (see comments at GetPixelColorInterpolated) + * If image size is being reduced, averaging is used instead (or simultaneously with) inMethod. + * \param ofMethod - what to replace outside pixels by (only significant for bordering pixels of enlarged image) + * \param iDst - pointer to destination CxImage or NULL. + * \param disableAveraging - force no averaging when shrinking images (Produces aliasing. + * You probably just want to leave this off...) + * + * \author ***bd*** 2.2004 + */ +bool CxImage::Resample2( + int32_t newx, int32_t newy, + InterpolationMethod const inMethod, + OverflowMethod const ofMethod, + CxImage* const iDst, + bool const disableAveraging) +{ + if (newx<=0 || newy<=0 || !pDib) return false; + + if (head.biWidth==newx && head.biHeight==newy) { + //image already correct size (just copy and return) + if (iDst) iDst->Copy(*this); + return true; + }//if + + //calculate scale of new image (less than 1 for enlarge) + float xScale, yScale; + xScale = (float)head.biWidth / (float)newx; + yScale = (float)head.biHeight / (float)newy; + + //create temporary destination image + CxImage newImage; + newImage.CopyInfo(*this); + newImage.Create(newx,newy,head.biBitCount,GetType()); + newImage.SetPalette(GetPalette()); + if (!newImage.IsValid()){ + strcpy(info.szLastError,newImage.GetLastError()); + return false; + } + + //and alpha channel if required +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) newImage.AlphaCreate(); + uint8_t *pxptra = 0; // destination alpha data +#endif + + float sX, sY; //source location + int32_t dX,dY; //destination pixel (int32_t value) + if ((xScale<=1 && yScale<=1) || disableAveraging) { + //image is being enlarged (or interpolation on demand) + if (!IsIndexed()) { + //RGB24 image (optimized version with direct writes) + RGBQUAD q; //pixel colour + uint8_t *pxptr; //pointer to destination pixel + for(dY=0; dYTransfer(newImage); + else + Transfer(newImage); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Reduces the number of bits per pixel to nbit (1, 4 or 8). + * ppal points to a valid palette for the final image; if not supplied the function will use a standard palette. + * ppal is not necessary for reduction to 1 bpp. + */ +bool CxImage::DecreaseBpp(uint32_t nbit, bool errordiffusion, RGBQUAD* ppal, uint32_t clrimportant) +{ + if (!pDib) return false; + if (head.biBitCount < nbit){ + strcpy(info.szLastError,"DecreaseBpp: target BPP greater than source BPP"); + return false; + } + if (head.biBitCount == nbit){ + if (clrimportant==0) return true; + if (head.biClrImportant && (head.biClrImportant 128) { + tmp.SetPixelIndex(x, y, 1); + error = level - 255; + } else { + tmp.SetPixelIndex(x, y, 0); + error = level; + } + + nlevel = GetPixelIndex(x + 1, y) + (error * 8) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + 1, y, level); + nlevel = GetPixelIndex(x + 2, y) + (error * 4) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + 2, y, level); + int32_t i; + for (i = -2; i < 3; i++) { + switch (i) { + case -2: + coeff = 2; + break; + case -1: + coeff = 4; + break; + case 0: + coeff = 8; + break; + case 1: + coeff = 4; + break; + case 2: + coeff = 2; + break; + } + nlevel = GetPixelIndex(x + i, y + 1) + (error * coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + i, y + 1, level); + } + } + } + break; + } + case 3: + { + //Stucki error diffusion (Thanks to Franco Gerevini) + int32_t TotalCoeffSum = 42; + int32_t error, nlevel, coeff=1; + uint8_t level; + + for (int32_t y = 0; y < head.biHeight; y++) { + info.nProgress = (int32_t)(100 * y / head.biHeight); + if (info.nEscape) + break; + for (int32_t x = 0; x < head.biWidth; x++) { + level = BlindGetPixelIndex(x, y); + if (level > 128) { + tmp.SetPixelIndex(x, y, 1); + error = level - 255; + } else { + tmp.SetPixelIndex(x, y, 0); + error = level; + } + + nlevel = GetPixelIndex(x + 1, y) + (error * 8) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + 1, y, level); + nlevel = GetPixelIndex(x + 2, y) + (error * 4) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + 2, y, level); + int32_t i; + for (i = -2; i < 3; i++) { + switch (i) { + case -2: + coeff = 2; + break; + case -1: + coeff = 4; + break; + case 0: + coeff = 8; + break; + case 1: + coeff = 4; + break; + case 2: + coeff = 2; + break; + } + nlevel = GetPixelIndex(x + i, y + 1) + (error * coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + i, y + 1, level); + } + for (i = -2; i < 3; i++) { + switch (i) { + case -2: + coeff = 1; + break; + case -1: + coeff = 2; + break; + case 0: + coeff = 4; + break; + case 1: + coeff = 2; + break; + case 2: + coeff = 1; + break; + } + nlevel = GetPixelIndex(x + i, y + 2) + (error * coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + i, y + 2, level); + } + } + } + break; + } + case 4: + { + //Jarvis, Judice and Ninke error diffusion (Thanks to Franco Gerevini) + int32_t TotalCoeffSum = 48; + int32_t error, nlevel, coeff=1; + uint8_t level; + + for (int32_t y = 0; y < head.biHeight; y++) { + info.nProgress = (int32_t)(100 * y / head.biHeight); + if (info.nEscape) + break; + for (int32_t x = 0; x < head.biWidth; x++) { + level = BlindGetPixelIndex(x, y); + if (level > 128) { + tmp.SetPixelIndex(x, y, 1); + error = level - 255; + } else { + tmp.SetPixelIndex(x, y, 0); + error = level; + } + + nlevel = GetPixelIndex(x + 1, y) + (error * 7) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + 1, y, level); + nlevel = GetPixelIndex(x + 2, y) + (error * 5) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + 2, y, level); + int32_t i; + for (i = -2; i < 3; i++) { + switch (i) { + case -2: + coeff = 3; + break; + case -1: + coeff = 5; + break; + case 0: + coeff = 7; + break; + case 1: + coeff = 5; + break; + case 2: + coeff = 3; + break; + } + nlevel = GetPixelIndex(x + i, y + 1) + (error * coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + i, y + 1, level); + } + for (i = -2; i < 3; i++) { + switch (i) { + case -2: + coeff = 1; + break; + case -1: + coeff = 3; + break; + case 0: + coeff = 5; + break; + case 1: + coeff = 3; + break; + case 2: + coeff = 1; + break; + } + nlevel = GetPixelIndex(x + i, y + 2) + (error * coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + i, y + 2, level); + } + } + } + break; + } + case 5: + { + //Sierra error diffusion (Thanks to Franco Gerevini) + int32_t TotalCoeffSum = 32; + int32_t error, nlevel, coeff=1; + uint8_t level; + + for (int32_t y = 0; y < head.biHeight; y++) { + info.nProgress = (int32_t)(100 * y / head.biHeight); + if (info.nEscape) + break; + for (int32_t x = 0; x < head.biWidth; x++) { + level = BlindGetPixelIndex(x, y); + if (level > 128) { + tmp.SetPixelIndex(x, y, 1); + error = level - 255; + } else { + tmp.SetPixelIndex(x, y, 0); + error = level; + } + + nlevel = GetPixelIndex(x + 1, y) + (error * 5) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + 1, y, level); + nlevel = GetPixelIndex(x + 2, y) + (error * 3) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + 2, y, level); + int32_t i; + for (i = -2; i < 3; i++) { + switch (i) { + case -2: + coeff = 2; + break; + case -1: + coeff = 4; + break; + case 0: + coeff = 5; + break; + case 1: + coeff = 4; + break; + case 2: + coeff = 2; + break; + } + nlevel = GetPixelIndex(x + i, y + 1) + (error * coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + i, y + 1, level); + } + for (i = -1; i < 2; i++) { + switch (i) { + case -1: + coeff = 2; + break; + case 0: + coeff = 3; + break; + case 1: + coeff = 2; + break; + } + nlevel = GetPixelIndex(x + i, y + 2) + (error * coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(x + i, y + 2, level); + } + } + } + break; + } + case 6: + { + //Stevenson and Arce error diffusion (Thanks to Franco Gerevini) + int32_t TotalCoeffSum = 200; + int32_t error, nlevel; + uint8_t level; + + for (int32_t y = 0; y < head.biHeight; y++) { + info.nProgress = (int32_t)(100 * y / head.biHeight); + if (info.nEscape) + break; + for (int32_t x = 0; x < head.biWidth; x++) { + level = BlindGetPixelIndex(x, y); + if (level > 128) { + tmp.SetPixelIndex(x, y, 1); + error = level - 255; + } else { + tmp.SetPixelIndex(x, y, 0); + error = level; + } + + int32_t tmp_index_x = x + 2; + int32_t tmp_index_y = y; + int32_t tmp_coeff = 32; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x - 3; + tmp_index_y = y + 1; + tmp_coeff = 12; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x - 1; + tmp_coeff = 26; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x + 1; + tmp_coeff = 30; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x + 3; + tmp_coeff = 16; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x - 2; + tmp_index_y = y + 2; + tmp_coeff = 12; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x; + tmp_coeff = 26; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x + 2; + tmp_coeff = 12; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x - 3; + tmp_index_y = y + 3; + tmp_coeff = 5; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x - 1; + tmp_coeff = 12; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x + 1; + tmp_coeff = 12; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + + tmp_index_x = x + 3; + tmp_coeff = 5; + nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum; + level = (uint8_t)min(255, max(0, (int32_t)nlevel)); + SetPixelIndex(tmp_index_x, tmp_index_y, level); + } + } + break; + } + case 7: + { + // Bayer ordered dither + int32_t order = 4; + //create Bayer matrix + if (order>4) order = 4; + int32_t size = (1 << (2*order)); + uint8_t* Bmatrix = (uint8_t*) malloc(size * sizeof(uint8_t)); + for(int32_t i = 0; i < size; i++) { + int32_t n = order; + int32_t x = i / n; + int32_t y = i % n; + int32_t dither = 0; + while (n-- > 0){ + dither = (((dither<<1)|((x&1) ^ (y&1)))<<1) | (y&1); + x >>= 1; + y >>= 1; + } + Bmatrix[i] = (uint8_t)(dither); + } + + int32_t scale = max(0,(8-2*order)); + int32_t level; + for (int32_t y=0;y> scale; + if(level > Bmatrix[ (x % order) + order * (y % order) ]){ + tmp.SetPixelIndex(x,y,1); + } else { + tmp.SetPixelIndex(x,y,0); + } + } + } + + free(Bmatrix); + + break; + } + case 8: + { + // 8x8 Bayer ordered dither + int32_t const pattern8x8[8][8] = { + { 0, 32, 8, 40, 2, 34, 10, 42}, /* 8x8 Bayer ordered dithering */ + {48, 16, 56, 24, 50, 18, 58, 26}, /* pattern. Each input pixel */ + {12, 44, 4, 36, 14, 46, 6, 38}, /* is scaled to the 0..63 range */ + {60, 28, 52, 20, 62, 30, 54, 22}, /* before looking in this table */ + { 3, 35, 11, 43, 1, 33, 9, 41}, /* to determine the action. */ + {51, 19, 59, 27, 49, 17, 57, 25}, + {15, 47, 7, 39, 13, 45, 5, 37}, + {63, 31, 55, 23, 61, 29, 53, 21} }; + + for (int32_t y=0;y> 2; + if(level && level >= pattern8x8[x & 7][y & 7]){ + tmp.SetPixelIndex(x,y,1); + } else { + tmp.SetPixelIndex(x,y,0); + } + } + } + break; + } + case 9: + { + // 16x16 Bayer ordered dither + int32_t const pattern16x16[16][16] = { + { 1,235, 59,219, 15,231, 55,215, 2,232, 56,216, 12,228, 52,212}, + { 129, 65,187,123,143, 79,183,119,130, 66,184,120,140, 76,180,116}, + { 33,193, 17,251, 47,207, 31,247, 34,194, 18,248, 44,204, 28,244}, + { 161, 97,145, 81,175,111,159, 95,162, 98,146, 82,172,108,156, 92}, + { 9,225, 49,209, 5,239, 63,223, 10,226, 50,210, 6,236, 60,220}, + { 137, 73,177,113,133, 69,191,127,138, 74,178,114,134, 70,188,124}, + { 41,201, 25,241, 37,197, 21,255, 42,202, 26,242, 38,198, 22,252}, + { 169,105,153, 89,165,101,149, 85,170,106,154, 90,166,102,150, 86}, + { 3,233, 57,217, 13,229, 53,213, 0,234, 58,218, 14,230, 54,214}, + { 131, 67,185,121,141, 77,181,117,128, 64,186,122,142, 78,182,118}, + { 35,195, 19,249, 45,205, 29,245, 32,192, 16,250, 46,206, 30,246}, + { 163, 99,147, 83,173,109,157, 93,160, 96,144, 80,174,110,158, 94}, + { 11,227, 51,211, 7,237, 61,221, 8,224, 48,208, 4,238, 62,222}, + { 139, 75,179,115,135, 71,189,125,136, 72,176,112,132, 68,190,126}, + { 43,203, 27,243, 39,199, 23,253, 40,200, 24,240, 36,196, 20,254}, + { 171,107,155, 91,167,103,151, 87,168,104,152, 88,164,100,148, 84} + }; + + for (int32_t y=0;y pattern16x16[x & 15][y & 15]){ + tmp.SetPixelIndex(x,y,1); + } else { + tmp.SetPixelIndex(x,y,0); + } + } + } + break; + } + default: + { + // Floyd-Steinberg error diffusion (Thanks to Steve McMahon) + int32_t error,nlevel,coeff=1; + uint8_t level; + + for (int32_t y=0;y 128){ + tmp.SetPixelIndex(x,y,1); + error = level-255; + } else { + tmp.SetPixelIndex(x,y,0); + error = level; + } + + nlevel = GetPixelIndex(x+1,y) + (error * 7)/16; + level = (uint8_t)min(255,max(0,(int32_t)nlevel)); + SetPixelIndex(x+1,y,level); + for(int32_t i=-1; i<2; i++){ + switch(i){ + case -1: + coeff=3; break; + case 0: + coeff=5; break; + case 1: + coeff=1; break; + } + nlevel = GetPixelIndex(x+i,y+1) + (error * coeff)/16; + level = (uint8_t)min(255,max(0,(int32_t)nlevel)); + SetPixelIndex(x+i,y+1,level); + } + } + } + } + } + + tmp.SetPaletteColor(0,0,0,0); + tmp.SetPaletteColor(1,255,255,255); + Transfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * CropRotatedRectangle + * \param topx,topy : topmost and leftmost point of the rectangle + (topmost, and if there are 2 topmost points, the left one) + * \param width : size of the right hand side of rect, from (topx,topy) roundwalking clockwise + * \param height : size of the left hand side of rect, from (topx,topy) roundwalking clockwise + * \param angle : angle of the right hand side of rect, from (topx,topy) + * \param iDst : pointer to destination image (if 0, this image is modified) + * \author [VATI] + */ +bool CxImage::CropRotatedRectangle( int32_t topx, int32_t topy, int32_t width, int32_t height, float angle, CxImage* iDst) +{ + if (!pDib) return false; + + + int32_t startx,starty,endx,endy; + double cos_angle = cos(angle/*/57.295779513082320877*/); + double sin_angle = sin(angle/*/57.295779513082320877*/); + + // if there is nothing special, call the original Crop(): + if ( fabs(angle)<0.0002 ) + return Crop( topx, topy, topx+width, topy+height, iDst); + + startx = min(topx, topx - (int32_t)(sin_angle*(double)height)); + endx = topx + (int32_t)(cos_angle*(double)width); + endy = topy + (int32_t)(cos_angle*(double)height + sin_angle*(double)width); + // check: corners of the rectangle must be inside + if ( IsInside( startx, topy )==false || + IsInside( endx, endy ) == false ) + return false; + + // first crop to bounding rectangle + CxImage tmp(*this, true, false, true); + // tmp.Copy(*this, true, false, true); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + if (!tmp.Crop( startx, topy, endx, endy)){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + // the midpoint of the image now became the same as the midpoint of the rectangle + // rotate new image with minus angle amount + if ( false == tmp.Rotate( (float)(-angle*57.295779513082320877) ) ) // Rotate expects angle in degrees + return false; + + // crop rotated image to the original selection rectangle + endx = (tmp.head.biWidth+width)/2; + startx = (tmp.head.biWidth-width)/2; + starty = (tmp.head.biHeight+height)/2; + endy = (tmp.head.biHeight-height)/2; + if ( false == tmp.Crop( startx, starty, endx, endy ) ) + return false; + + if (iDst) iDst->Transfer(tmp); + else Transfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Crop(const RECT& rect, CxImage* iDst) +{ + return Crop(rect.left, rect.top, rect.right, rect.bottom, iDst); +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Crop(int32_t left, int32_t top, int32_t right, int32_t bottom, CxImage* iDst) +{ + if (!pDib) return false; + + int32_t startx = max(0L,min(left,head.biWidth)); + int32_t endx = max(0L,min(right,head.biWidth)); + int32_t starty = head.biHeight - max(0L,min(top,head.biHeight)); + int32_t endy = head.biHeight - max(0L,min(bottom,head.biHeight)); + + if (startx==endx || starty==endy) return false; + + if (startx>endx) {int32_t tmp=startx; startx=endx; endx=tmp;} + if (starty>endy) {int32_t tmp=starty; starty=endy; endy=tmp;} + + CxImage tmp; + tmp.CopyInfo(*this); + tmp.Create(endx-startx,endy-starty,head.biBitCount,info.dwType); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + tmp.SetPalette(GetPalette(),head.biClrUsed); + tmp.info.nBkgndIndex = info.nBkgndIndex; + tmp.info.nBkgndColor = info.nBkgndColor; + + switch (head.biBitCount) { + case 1: + case 4: + { + for(int32_t y=starty, yd=0; y + for(int32_t x=startx, xd=0; x> 3; + uint8_t* pDest = tmp.info.pImage; + uint8_t* pSrc = info.pImage + starty * info.dwEffWidth + (startx*head.biBitCount >> 3); + for(int32_t y=starty; y + memcpy(pDest,pSrc,linelen); + pDest+=tmp.info.dwEffWidth; + pSrc+=info.dwEffWidth; + } + } + } + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()){ // + tmp.AlphaCreate(); + if (!tmp.AlphaIsValid()) return false; + uint8_t* pDest = tmp.pAlpha; + uint8_t* pSrc = pAlpha + startx + starty*head.biWidth; + for (int32_t y=starty; yTransfer(tmp); + else Transfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * \param xgain, ygain : can be from 0 to 1. + * \param xpivot, ypivot : is the center of the transformation. + * \param bEnableInterpolation : if true, enables bilinear interpolation. + * \return true if everything is ok + */ +bool CxImage::Skew(float xgain, float ygain, int32_t xpivot, int32_t ypivot, bool bEnableInterpolation) +{ + if (!pDib) return false; + float nx,ny; + + CxImage tmp(*this); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + int32_t xmin,xmax,ymin,ymax; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + for(int32_t y=ymin; y top) || (x < left) || (x > right)) { + tmp.SetPixelIndex(x,y, pixel); + } else { + tmp.SetPixelIndex(x,y,GetPixelIndex(x-left,y-bottom)); + } + } + } + break; + } + case 8: + case 24: + { + if (head.biBitCount == 8) { + uint8_t pixel = tmp.GetNearestIndex( canvascolor); + memset(tmp.info.pImage, pixel, + (tmp.info.dwEffWidth * newHeight)); + } else { + for (int32_t y = 0; y < newHeight; ++y) { + uint8_t *pDest = tmp.info.pImage + (y * tmp.info.dwEffWidth); + for (int32_t x = 0; x < newWidth; ++x) { + *pDest++ = canvascolor.rgbBlue; + *pDest++ = canvascolor.rgbGreen; + *pDest++ = canvascolor.rgbRed; + } + } + } + + uint8_t* pDest = tmp.info.pImage + (tmp.info.dwEffWidth * bottom) + (left*(head.biBitCount >> 3)); + uint8_t* pSrc = info.pImage; + for(int32_t y=bottom; y <= top; y++){ + info.nProgress = (int32_t)(100*y/(1 + top - bottom)); + memcpy(pDest,pSrc,(head.biBitCount >> 3) * (right - left + 1)); + pDest+=tmp.info.dwEffWidth; + pSrc+=info.dwEffWidth; + } + } + } + +#if CXIMAGE_SUPPORT_SELECTION + if (SelectionIsValid()){ + if (!tmp.SelectionCreate()) + return false; + uint8_t* pSrc = SelectionGetPointer(); + uint8_t* pDst = tmp.SelectionGetPointer(left,bottom); + for(int32_t y=bottom; y <= top; y++){ + memcpy(pDst,pSrc, (right - left + 1)); + pSrc+=head.biWidth; + pDst+=tmp.head.biWidth; + } + tmp.info.rSelectionBox.left = info.rSelectionBox.left + left; + tmp.info.rSelectionBox.right = info.rSelectionBox.right + left; + tmp.info.rSelectionBox.top = info.rSelectionBox.top + bottom; + tmp.info.rSelectionBox.bottom = info.rSelectionBox.bottom + bottom; + } +#endif //CXIMAGE_SUPPORT_SELECTION + +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()){ + if (!tmp.AlphaCreate()) + return false; + tmp.AlphaSet(canvascolor.rgbReserved); + uint8_t* pSrc = AlphaGetPointer(); + uint8_t* pDst = tmp.AlphaGetPointer(left,bottom); + for(int32_t y=bottom; y <= top; y++){ + memcpy(pDst,pSrc, (right - left + 1)); + pSrc+=head.biWidth; + pDst+=tmp.head.biWidth; + } + } +#endif //CXIMAGE_SUPPORT_ALPHA + + //select the destination + if (iDst) iDst->Transfer(tmp); + else Transfer(tmp); + + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImage::Expand(int32_t newx, int32_t newy, RGBQUAD canvascolor, CxImage* iDst) +{ + //thanks to + + if (!pDib) return false; + + if ((newx < head.biWidth) || (newy < head.biHeight)) return false; + + int32_t nAddLeft = (newx - head.biWidth) / 2; + int32_t nAddTop = (newy - head.biHeight) / 2; + + return Expand(nAddLeft, nAddTop, newx - (head.biWidth + nAddLeft), newy - (head.biHeight + nAddTop), canvascolor, iDst); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Resamples the image with the correct aspect ratio, and fills the borders. + * \param newx, newy = thumbnail size. + * \param canvascolor = border color. + * \param iDst = pointer to destination image (if it's 0, this image is modified). + * \return true if everything is ok. + * \author [Colin Urquhart] + */ +bool CxImage::Thumbnail(int32_t newx, int32_t newy, RGBQUAD canvascolor, CxImage* iDst) +{ + if (!pDib) return false; + + if ((newx <= 0) || (newy <= 0)) return false; + + CxImage tmp(*this); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + // determine whether we need to shrink the image + if ((head.biWidth > newx) || (head.biHeight > newy)) { + float fScale; + float fAspect = (float) newx / (float) newy; + if (fAspect * head.biHeight > head.biWidth) { + fScale = (float) newy / head.biHeight; + } else { + fScale = (float) newx / head.biWidth; + } + tmp.Resample((int32_t) (fScale * head.biWidth), (int32_t) (fScale * head.biHeight), 0); + } + + // expand the frame + tmp.Expand(newx, newy, canvascolor); + + //select the destination + if (iDst) iDst->Transfer(tmp); + else Transfer(tmp); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Perform circle_based transformations. + * \param type - for different transformations + * - 0 for normal (proturberant) FishEye + * - 1 for reverse (concave) FishEye + * - 2 for Swirle + * - 3 for Cilinder mirror + * - 4 for bathroom + * + * \param rmax - effect radius. If 0, the whole image is processed + * \param Koeff - only for swirle + * \author Arkadiy Olovyannikov ark(at)msun(dot)ru + */ +bool CxImage::CircleTransform(int32_t type,int32_t rmax,float Koeff) +{ + if (!pDib) return false; + + int32_t nx,ny; + double angle,radius,rnew; + + CxImage tmp(*this); + if (!tmp.IsValid()){ + strcpy(info.szLastError,tmp.GetLastError()); + return false; + } + + int32_t xmin,xmax,ymin,ymax,xmid,ymid; + if (pSelection){ + xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right; + ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top; + } else { + xmin = ymin = 0; + xmax = head.biWidth; ymax=head.biHeight; + } + + xmid = (int32_t) (tmp.GetWidth()/2); + ymid = (int32_t) (tmp.GetHeight()/2); + + if (!rmax) rmax=(int32_t)sqrt((float)((xmid-xmin)*(xmid-xmin)+(ymid-ymin)*(ymid-ymin))); + if (Koeff==0.0f) Koeff=1.0f; + + for(int32_t y=ymin; yhead.biWidth || newy>head.biHeight) { + //let me repeat... this method can't enlarge image + strcpy(info.szLastError,"QIShrink can't enlarge image"); + return false; + } + + if (newx==head.biWidth && newy==head.biHeight) { + //image already correct size (just copy and return) + if (iDst) iDst->Copy(*this); + return true; + }//if + + //create temporary destination image + CxImage newImage; + newImage.CopyInfo(*this); + newImage.Create(newx,newy,(bChangeBpp)?24:head.biBitCount,GetType()); + newImage.SetPalette(GetPalette()); + if (!newImage.IsValid()){ + strcpy(info.szLastError,newImage.GetLastError()); + return false; + } + + //and alpha channel if required +#if CXIMAGE_SUPPORT_ALPHA + if (AlphaIsValid()) newImage.AlphaCreate(); +#endif + + const int32_t oldx = head.biWidth; + const int32_t oldy = head.biHeight; + + int32_t accuCellSize = 4; +#if CXIMAGE_SUPPORT_ALPHA + uint8_t *alphaPtr; + if (AlphaIsValid()) accuCellSize=5; +#endif + + uint32_t *accu = new uint32_t[newx*accuCellSize]; //array for suming pixels... one pixel for every destination column + uint32_t *accuPtr; //pointer for walking through accu + //each cell consists of blue, red, green component and count of pixels summed in this cell + memset(accu, 0, newx * accuCellSize * sizeof(uint32_t)); //clear accu + + if (!IsIndexed()) { + //RGB24 version with pointers + uint8_t *destPtr, *srcPtr, *destPtrS, *srcPtrS; //destination and source pixel, and beginnings of current row + srcPtrS=(uint8_t*)BlindGetPixelPointer(0,0); + destPtrS=(uint8_t*)newImage.BlindGetPixelPointer(0,0); + int32_t ex=0, ey=0; //ex and ey replace division... + int32_t dy=0; + //(we just add pixels, until by adding newx or newy we get a number greater than old size... then + // it's time to move to next pixel) + + for(int32_t y=0; yoldx) { //when we reach oldx, it's time to move to new slot + accuPtr += accuCellSize; + ex -= oldx; //(substract oldx from ex and resume from there on) + }//if (ex overflow) + }//for x + + if (ey>=oldy) { //now when this happens + ey -= oldy; //it's time to move to new destination row + destPtr = destPtrS; //reset pointers to proper initial values + accuPtr = accu; +#if CXIMAGE_SUPPORT_ALPHA + alphaPtr = newImage.AlphaGetPointer(0, dy++); +#endif + for (int32_t k=0; koldx) { //when we reach oldx, it's time to move to new slot + accuPtr += accuCellSize; + ex -= oldx; //(substract oldx from ex and resume from there on) + }//if (ex overflow) + }//for x + + if (ey>=oldy) { //now when this happens + ey -= oldy; //it's time to move to new destination row + accuPtr = accu; + for (int32_t dx=0; dxTransfer(newImage); + else + Transfer(newImage); + return true; + +} + +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_TRANSFORMATION diff --git a/DuiLib/3rd/CxImage/ximawbmp.cpp b/DuiLib/3rd/CxImage/ximawbmp.cpp new file mode 100644 index 0000000..86afbea --- /dev/null +++ b/DuiLib/3rd/CxImage/ximawbmp.cpp @@ -0,0 +1,134 @@ +/* + * File: ximawbmp.cpp + * Purpose: Platform Independent WBMP Image Class Loader and Writer + * 12/Jul/2002 Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximawbmp.h" + +#if CXIMAGE_SUPPORT_WBMP + +#include "ximaiter.h" + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageWBMP::Decode(CxFile *hFile) +{ + if (hFile == NULL) return false; + + WBMPHEADER wbmpHead; + + cx_try + { + ReadOctet(hFile, &wbmpHead.Type); + + uint32_t dat; + ReadOctet(hFile, &dat); + wbmpHead.FixHeader = (uint8_t)dat; + + ReadOctet(hFile, &wbmpHead.ImageWidth); + ReadOctet(hFile, &wbmpHead.ImageHeight); + + if (hFile->Eof()) + cx_throw("Not a WBMP"); + + if (wbmpHead.Type != 0) + cx_throw("Unsupported WBMP type"); + + head.biWidth = wbmpHead.ImageWidth; + head.biHeight= wbmpHead.ImageHeight; + + if (head.biWidth<=0 || head.biHeight<=0) + cx_throw("Corrupted WBMP"); + + if (info.nEscape == -1){ + info.dwType = CXIMAGE_FORMAT_WBMP; + return true; + } + + Create(head.biWidth, head.biHeight, 1, CXIMAGE_FORMAT_WBMP); + if (!IsValid()) cx_throw("WBMP Create failed"); + SetGrayPalette(); + + int32_t linewidth=(head.biWidth+7)/8; + CImageIterator iter(this); + iter.Upset(); + for (int32_t y=0; y < head.biHeight; y++){ + hFile->Read(iter.GetRow(),linewidth,1); + iter.PrevRow(); + } + + } cx_catch { + if (strcmp(message,"")) strncpy(info.szLastError,message,255); + return FALSE; + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImageWBMP::ReadOctet(CxFile * hFile, uint32_t *data) +{ + uint8_t c; + *data = 0; + do { + if (hFile->Eof()) return false; + c = (uint8_t)hFile->GetC(); + *data <<= 7; + *data |= (c & 0x7F); + } while ((c&0x80)!=0); + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageWBMP::Encode(CxFile * hFile) +{ + if (EncodeSafeCheck(hFile)) return false; + + //check format limits + if (head.biBitCount!=1){ + strcpy(info.szLastError,"Can't save this image as WBMP"); + return false; + } + + WBMPHEADER wbmpHead; + wbmpHead.Type=0; + wbmpHead.FixHeader=0; + wbmpHead.ImageWidth=head.biWidth; + wbmpHead.ImageHeight=head.biHeight; + + // Write the file header + hFile->PutC('\0'); + hFile->PutC('\0'); + WriteOctet(hFile,wbmpHead.ImageWidth); + WriteOctet(hFile,wbmpHead.ImageHeight); + // Write the pixels + int32_t linewidth=(wbmpHead.ImageWidth+7)/8; + CImageIterator iter(this); + iter.Upset(); + for (uint32_t y=0; y < wbmpHead.ImageHeight; y++){ + hFile->Write(iter.GetRow(),linewidth,1); + iter.PrevRow(); + } + return true; +} +//////////////////////////////////////////////////////////////////////////////// +bool CxImageWBMP::WriteOctet(CxFile * hFile, const uint32_t data) +{ + int32_t ns = 0; + while (data>>(ns+7)) ns+=7; + while (ns>0){ + if (!hFile->PutC(0x80 | (uint8_t)(data>>ns))) return false; + ns-=7; + } + if (!(hFile->PutC((uint8_t)(0x7F & data)))) return false; + return true; +} +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_ENCODE +//////////////////////////////////////////////////////////////////////////////// +#endif // CXIMAGE_SUPPORT_WBMP + diff --git a/DuiLib/3rd/CxImage/ximawbmp.h b/DuiLib/3rd/CxImage/ximawbmp.h new file mode 100644 index 0000000..1d1e7ed --- /dev/null +++ b/DuiLib/3rd/CxImage/ximawbmp.h @@ -0,0 +1,49 @@ +/* + * File: ximawbmp.h + * Purpose: WBMP Image Class Loader and Writer + */ +/* ========================================================== + * CxImageWBMP (c) 12/Jul/2002 Davide Pizzolato - www.xdp.it + * For conditions of distribution and use, see copyright notice in ximage.h + * ========================================================== + */ +#if !defined(__ximaWBMP_h) +#define __ximaWBMP_h + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_WBMP + +class CxImageWBMP: public CxImage +{ +#pragma pack(1) +typedef struct tagWbmpHeader +{ + uint32_t Type; // 0 + uint8_t FixHeader; // 0 + uint32_t ImageWidth; // Image Width + uint32_t ImageHeight; // Image Height +} WBMPHEADER; +#pragma pack() +public: + CxImageWBMP(): CxImage(CXIMAGE_FORMAT_WBMP) {} + +// bool Load(const TCHAR * imageFileName){ return CxImage::Load(imageFileName,CXIMAGE_FORMAT_WBMP);} +// bool Save(const TCHAR * imageFileName){ return CxImage::Save(imageFileName,CXIMAGE_FORMAT_WBMP);} + bool Decode(CxFile * hFile); + bool Decode(FILE *hFile) { CxIOFile file(hFile); return Decode(&file); } +protected: + bool ReadOctet(CxFile * hFile, uint32_t *data); + +public: +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +protected: + bool WriteOctet(CxFile * hFile, const uint32_t data); +#endif // CXIMAGE_SUPPORT_ENCODE +}; + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximawmf.cpp b/DuiLib/3rd/CxImage/ximawmf.cpp new file mode 100644 index 0000000..1bb3058 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximawmf.cpp @@ -0,0 +1,483 @@ +/* +********************************************************************* + * File: ximawmf.cpp + * Purpose: Windows Metafile Class Loader and Writer + * Author: Volker Horch - vhorch@gmx.de + * created: 13-Jun-2002 + * + * Note: If the code below works, i wrote it. + * If it doesn't work, i don't know who wrote it. +********************************************************************* + */ + +/* +********************************************************************* + Note by Author: +********************************************************************* + + Metafile Formats: + ================= + + There are 2 kinds of Windows Metafiles: + - Standard Windows Metafile + - Placeable Windows Metafile + + A StandardWindows Metafile looks like: + - Metafile Header (MEATAHEADER) + - Metafile Records + + A Placeable Metafile looks like: + - Aldus Header (METAFILEHEADER) + - Metafile Header (METAHEADER) + - Metafile Records + + The "Metafile Header" and the "Metafile Records" are the same + for both formats. However, the Standard Metafile does not contain any + information about the original dimensions or x/y ratio of the Metafile. + + I decided, to allow only placeable Metafiles here. If you also want to + enable Standard Metafiles, you will have to guess the dimensions of + the image. + +********************************************************************* + Limitations: see ximawmf.h + you may configure some stuff there +********************************************************************* +*/ + +#include "ximawmf.h" + +#if CXIMAGE_SUPPORT_WMF && CXIMAGE_SUPPORT_WINDOWS + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +bool CxImageWMF::Decode(CxFile *hFile, int32_t nForceWidth, int32_t nForceHeight) +{ + if (hFile == NULL) return false; + + HENHMETAFILE hMeta; + HDC hDC; + int32_t cx,cy; + + //save the current position of the file + int32_t pos = hFile->Tell(); + + // Read the Metafile and convert to an Enhanced Metafile + METAFILEHEADER mfh; + hMeta = ConvertWmfFiletoEmf(hFile, &mfh); + if (hMeta) { // ok, it's a WMF + +///////////////////////////////////////////////////////////////////// +// We use the original WMF size information, because conversion to +// EMF adjusts the Metafile to Full Screen or does not set rclBounds at all +// ENHMETAHEADER emh; +// uint32_t uRet; +// uRet = GetEnhMetaFileHeader(hMeta, // handle of enhanced metafile +// sizeof(ENHMETAHEADER), // size of buffer, in bytes +// &emh); // address of buffer to receive data +// if (!uRet){ +// DeleteEnhMetaFile(hMeta); +// return false; +// } +// // calculate size +// cx = emh.rclBounds.right - emh.rclBounds.left; +// cy = emh.rclBounds.bottom - emh.rclBounds.top; +///////////////////////////////////////////////////////////////////// + + // calculate size + // scale the metafile (pixels/inch of metafile => pixels/inch of display) + // mfh.inch already checked to be <> 0 + + hDC = ::GetDC(0); + int32_t cx1 = ::GetDeviceCaps(hDC, LOGPIXELSX); + int32_t cy1 = ::GetDeviceCaps(hDC, LOGPIXELSY); + ::ReleaseDC(0, hDC); + + cx = (mfh.inch/2 + (mfh.bbox.right - mfh.bbox.left) * cx1) / mfh.inch; + cy = (mfh.inch/2 + (mfh.bbox.bottom - mfh.bbox.top) * cy1) / mfh.inch; + + } else { // maybe it's an EMF... + + hFile->Seek(pos,SEEK_SET); + + ENHMETAHEADER emh; + hMeta = ConvertEmfFiletoEmf(hFile, &emh); + + if (!hMeta){ + strcpy(info.szLastError,"corrupted WMF"); + return false; // definitively give up + } + + // ok, it's an EMF; calculate canvas size + cx = emh.rclBounds.right - emh.rclBounds.left; + cy = emh.rclBounds.bottom - emh.rclBounds.top; + + // alternative methods, sometime not so reliable... [DP] + //cx = emh.szlDevice.cx; + //cy = emh.szlDevice.cy; + // + //hDC = ::GetDC(0); + //float hscale = (float)GetDeviceCaps(hDC, HORZRES)/(100.0f * GetDeviceCaps(hDC, HORZSIZE)); + //float vscale = (float)GetDeviceCaps(hDC, VERTRES)/(100.0f * GetDeviceCaps(hDC, VERTSIZE)); + //::ReleaseDC(0, hDC); + //cx = (int32_t)((emh.rclFrame.right - emh.rclFrame.left) * hscale); + //cy = (int32_t)((emh.rclFrame.bottom - emh.rclFrame.top) * vscale); + } + + if (info.nEscape == -1) { // Check if cancelled + head.biWidth = cx; + head.biHeight= cy; + info.dwType = CXIMAGE_FORMAT_WMF; + DeleteEnhMetaFile(hMeta); + strcpy(info.szLastError,"output dimensions returned"); + return true; + } + + if (!cx || !cy) { + DeleteEnhMetaFile(hMeta); + strcpy(info.szLastError,"empty WMF"); + return false; + } + + if (nForceWidth) cx=nForceWidth; + if (nForceHeight) cy=nForceHeight; + ShrinkMetafile(cx, cy); // !! Otherwise Bitmap may have bombastic size + + HDC hDC0 = ::GetDC(0); // DC of screen + HBITMAP hBitmap = CreateCompatibleBitmap(hDC0, cx, cy); // has # colors of display + hDC = CreateCompatibleDC(hDC0); // memory dc compatible with screen + ::ReleaseDC(0, hDC0); // don't need anymore. get rid of it. + + if (hDC){ + if (hBitmap){ + RECT rc = {0,0,cx,cy}; + int32_t bpp = ::GetDeviceCaps(hDC, BITSPIXEL); + + HBITMAP hBitmapOld = (HBITMAP)SelectObject(hDC, hBitmap); + + // clear out the entire bitmap with windows background + // because the MetaFile may not contain background information + uint32_t dwBack = XMF_COLOR_BACK; +#if XMF_SUPPORT_TRANSPARENCY + if (bpp == 24) dwBack = XMF_COLOR_TRANSPARENT; +#endif + uint32_t OldColor = SetBkColor(hDC, dwBack); + ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL); + SetBkColor(hDC, OldColor); + + //retrieves optional palette entries from the specified enhanced metafile + PLOGPALETTE plogPal; + PBYTE pjTmp; + HPALETTE hPal; + int32_t iEntries = GetEnhMetaFilePaletteEntries(hMeta, 0, NULL); + if (iEntries) { + if ((plogPal = (PLOGPALETTE)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, + sizeof(uint32_t) + sizeof(PALETTEENTRY)*iEntries )) == NULL) { + DeleteObject(hBitmap); + DeleteDC(hDC); + DeleteEnhMetaFile(hMeta); + strcpy(info.szLastError,"Cancelled"); + return false; + } + + plogPal->palVersion = 0x300; + plogPal->palNumEntries = (uint16_t) iEntries; + pjTmp = (PBYTE) plogPal; + pjTmp += 4; + + GetEnhMetaFilePaletteEntries(hMeta, iEntries, (PPALETTEENTRY)pjTmp); + hPal = CreatePalette(plogPal); + GlobalFree(plogPal); + + SelectPalette(hDC, hPal, FALSE); + RealizePalette(hDC); + } + + // Play the Metafile into Memory DC + BOOL bRet = PlayEnhMetaFile(hDC, // handle to a device context + hMeta, // handle to an enhanced metafile + &rc); // pointer to bounding rectangle + + SelectObject(hDC, hBitmapOld); + DeleteEnhMetaFile(hMeta); // we are done with this one + + if (info.nEscape) { // Check if cancelled + DeleteObject(hBitmap); + DeleteDC(hDC); + strcpy(info.szLastError,"Cancelled"); + return false; + } + + // the Bitmap now has the image. + // Create our DIB and convert the DDB into DIB + if (!Create(cx, cy, bpp, CXIMAGE_FORMAT_WMF)) { + DeleteObject(hBitmap); + DeleteDC(hDC); + return false; + } + +#if XMF_SUPPORT_TRANSPARENCY + if (bpp == 24) { + RGBQUAD rgbTrans = { XMF_RGBQUAD_TRANSPARENT }; + SetTransColor(rgbTrans); + } +#endif + // We're finally ready to get the DIB. Call the driver and let + // it party on our bitmap. It will fill in the color table, + // and bitmap bits of our global memory block. + bRet = GetDIBits(hDC, hBitmap, 0, + (uint32_t)cy, GetBits(), (LPBITMAPINFO)pDib, DIB_RGB_COLORS); + + DeleteObject(hBitmap); + DeleteDC(hDC); + + return (bRet!=0); + } else { + DeleteDC(hDC); + } + } else { + if (hBitmap) DeleteObject(hBitmap); + } + + DeleteEnhMetaFile(hMeta); + + return false; +} + +/********************************************************************** + Function: CheckMetafileHeader + Purpose: Check if the Metafileheader of a file is valid +**********************************************************************/ +BOOL CxImageWMF::CheckMetafileHeader(METAFILEHEADER *metafileheader) +{ + uint16_t *pw; + uint16_t cs; + int32_t i; + + // check magic # + if (metafileheader->key != 0x9ac6cdd7L) return false; + + // test checksum of header + pw = (uint16_t *)metafileheader; + cs = *pw; + pw++; + for (i = 0; i < 9; i++) { + cs ^= *pw; + pw++; + } + + if (cs != metafileheader->checksum) return false; + + // check resolution + if ((metafileheader->inch <= 0) || (metafileheader->inch > 2540)) return false; + + return true; +} + +/********************************************************************** + Function: ConvertWmfFiletoEmf + Purpose: Converts a Windows Metafile into an Enhanced Metafile +**********************************************************************/ +HENHMETAFILE CxImageWMF::ConvertWmfFiletoEmf(CxFile *fp, METAFILEHEADER *metafileheader) +{ + HENHMETAFILE hMeta; + uint32_t lenFile; + uint32_t len; + uint8_t *p; + METAHEADER mfHeader; + uint32_t seekpos; + + hMeta = 0; + + // get length of the file + lenFile = fp->Size(); + + // a placeable metafile starts with a METAFILEHEADER + // read it and check metafileheader + len = fp->Read(metafileheader, 1, sizeof(METAFILEHEADER)); + if (len < sizeof(METAFILEHEADER)) return (hMeta); + + if (CheckMetafileHeader(metafileheader)) { + // This is a placeable metafile + // Convert the placeable format into something that can + // be used with GDI metafile functions + seekpos = sizeof(METAFILEHEADER); + } else { + // Not a placeable wmf. A windows metafile? + // at least not scaleable. + // we could try to convert, but would loose ratio. don't allow this + return (hMeta); + + //metafileheader->bbox.right = ?; + //metafileheader->bbox.left = ?; + //metafileheader->bbox.bottom = ?; + //metafileheader->bbox.top = ?; + //metafileheader->inch = ?; + // + //seekpos = 0; + // fp->Seek(0, SEEK_SET); // rewind + } + + // At this point we have a metaheader regardless of whether + // the metafile was a windows metafile or a placeable metafile + // so check to see if it is valid. There is really no good + // way to do this so just make sure that the mtType is either + // 1 or 2 (memory or disk file) + // in addition we compare the length of the METAHEADER against + // the length of the file. if filelength < len => no Metafile + + len = fp->Read(&mfHeader, 1, sizeof(METAHEADER)); + if (len < sizeof(METAHEADER)) return (hMeta); + + if ((mfHeader.mtType != 1) && (mfHeader.mtType != 2)) return (hMeta); + + // Length in Bytes from METAHEADER + len = mfHeader.mtSize * 2; + if (len > lenFile) return (hMeta); + + // Allocate memory for the metafile bits + p = (uint8_t *)malloc(len); + if (!p) return (hMeta); + + // seek back to METAHEADER and read all the stuff at once + fp->Seek(seekpos, SEEK_SET); + lenFile = fp->Read(p, 1, len); + if (lenFile != len) { + free(p); + return (hMeta); + } + + // the following (commented code) works, but adjusts rclBound of the + // Enhanced Metafile to full screen. + // the METAFILEHEADER from above is needed to scale the image + +// hMeta = SetWinMetaFileBits(len, p, NULL, NULL); + + // scale the metafile (pixels/inch of metafile => pixels/inch of display) + + METAFILEPICT mfp; + int32_t cx1, cy1; + HDC hDC; + + hDC = ::GetDC(0); + cx1 = ::GetDeviceCaps(hDC, LOGPIXELSX); + cy1 = ::GetDeviceCaps(hDC, LOGPIXELSY); + + memset(&mfp, 0, sizeof(mfp)); + + mfp.mm = MM_ANISOTROPIC; + mfp.xExt = 10000; //(metafileheader->bbox.right - metafileheader->bbox.left) * cx1 / metafileheader->inch; + mfp.yExt = 10000; //(metafileheader->bbox.bottom - metafileheader->bbox.top) * cy1 / metafileheader->inch; + mfp.hMF = 0; + + // in MM_ANISOTROPIC mode xExt and yExt are in MM_HIENGLISH + // MM_HIENGLISH means: Each logical unit is converted to 0.001 inch + //mfp.xExt *= 1000; + //mfp.yExt *= 1000; + // ???? + //int32_t k = 332800 / ::GetSystemMetrics(SM_CXSCREEN); + //mfp.xExt *= k; mfp.yExt *= k; + + // fix for Win9x + while ((mfp.xExt < 6554) && (mfp.yExt < 6554)) + { + mfp.xExt *= 10; + mfp.yExt *= 10; + } + + hMeta = SetWinMetaFileBits(len, p, hDC, &mfp); + + if (!hMeta){ //try 2nd conversion using a different mapping + mfp.mm = MM_TEXT; + hMeta = SetWinMetaFileBits(len, p, hDC, &mfp); + } + + ::ReleaseDC(0, hDC); + + // Free Memory + free(p); + + return (hMeta); +} +///////////////////////////////////////////////////////////////////// +HENHMETAFILE CxImageWMF::ConvertEmfFiletoEmf(CxFile *pFile, ENHMETAHEADER *pemfh) +{ + HENHMETAFILE hMeta; + int32_t iLen = pFile->Size(); + + // Check the header first: + int32_t pos = pFile->Tell(); + int32_t iLenRead = pFile->Read(pemfh, 1, sizeof(ENHMETAHEADER)); + if (iLenRead < sizeof(ENHMETAHEADER)) return NULL; + if (pemfh->iType != EMR_HEADER) return NULL; + if (pemfh->dSignature != ENHMETA_SIGNATURE) return NULL; + //if (pemfh->nBytes != (uint32_t)iLen) return NULL; + pFile->Seek(pos,SEEK_SET); + + uint8_t* pBuff = (uint8_t *)malloc(iLen); + if (!pBuff) return (FALSE); + + // Read the Enhanced Metafile + iLenRead = pFile->Read(pBuff, 1, iLen); + if (iLenRead != iLen) { + free(pBuff); + return NULL; + } + + // Make it a Memory Metafile + hMeta = SetEnhMetaFileBits(iLen, pBuff); + + free(pBuff); // finished with this one + + if (!hMeta) return NULL; // oops. + + // Get the Enhanced Metafile Header + uint32_t uRet = GetEnhMetaFileHeader(hMeta, // handle of enhanced metafile + sizeof(ENHMETAHEADER), // size of buffer, in bytes + pemfh); // address of buffer to receive data + + if (!uRet) { + DeleteEnhMetaFile(hMeta); + return NULL; + } + + return (hMeta); +} +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_DECODE +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_ENCODE +///////////////////////////////////////////////////////////////////// +bool CxImageWMF::Encode(CxFile * hFile) +{ + if (hFile == NULL) return false; + strcpy(info.szLastError, "Save WMF not supported"); + return false; +} +#endif // CXIMAGE_SUPPORT_ENCODE +///////////////////////////////////////////////////////////////////// + +/********************************************************************** +Function: ShrinkMetafile +Purpose: Shrink the size of a metafile to be not larger than + the definition +**********************************************************************/ +void CxImageWMF::ShrinkMetafile(int32_t &cx, int32_t &cy) +{ + int32_t xScreen = XMF_MAXSIZE_CX; + int32_t yScreen = XMF_MAXSIZE_CY; + + if (cx > xScreen){ + cy = cy * xScreen / cx; + cx = xScreen; + } + + if (cy > yScreen){ + cx = cx * yScreen / cy; + cy = yScreen; + } +} + +#endif // CIMAGE_SUPPORT_WMF + diff --git a/DuiLib/3rd/CxImage/ximawmf.h b/DuiLib/3rd/CxImage/ximawmf.h new file mode 100644 index 0000000..a649a67 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximawmf.h @@ -0,0 +1,154 @@ +/* +********************************************************************* + * File: ximawmf.h + * Purpose: Windows Metafile Class Loader and Writer + * Author: Volker Horch - vhorch@gmx.de + * created: 13-Jun-2002 +********************************************************************* + */ + +/* +********************************************************************* + Notes by Author: +********************************************************************* + + Limitations: + ============ + + a) Transparency: + + A Metafile is vector graphics, which has transparency by design. + This class always converts into a Bitmap format. Transparency is + supported, but there is no good way to find out, which parts + of the Metafile are transparent. There are two ways how we can + handle this: + + - Clear the Background of the Bitmap with the background color + you like (i have used COLOR_WINDOW) and don't support transparency. + + below #define XMF_SUPPORT_TRANSPARENCY 0 + #define XMF_COLOR_BACK RGB(Background color you like) + + - Clear the Background of the Bitmap with a very unusual color + (which one ?) and use this color as the transparent color + + below #define XMF_SUPPORT_TRANSPARENCY 1 + #define XMF_COLOR_TRANSPARENT_R ... + #define XMF_COLOR_TRANSPARENT_G ... + #define XMF_COLOR_TRANSPARENT_B ... + + b) Resolution + + Once we have converted the Metafile into a Bitmap and we zoom in + or out, the image may not look very good. If we still had the + original Metafile, zooming would produce good results always. + + c) Size + + Although the filesize of a Metafile may be very small, it might + produce a Bitmap with a bombastic size. Assume you have a Metafile + with an image size of 6000*4000, which contains just one Metafile + record ((e.g. a line from (0,0) to (6000, 4000)). The filesize + of this Metafile would be let's say 100kB. If we convert it to + a 6000*4000 Bitmap with 24 Bits/Pixes, the Bitmap would consume + about 68MB of memory. + + I have choosen, to limit the size of the Bitmap to max. + screensize, to avoid memory problems. + + If you want something else, + modify #define XMF_MAXSIZE_CX / XMF_MAXSIZE_CY below + +********************************************************************* +*/ + +#ifndef _XIMAWMF_H +#define _XIMAWMF_H + +#include "ximage.h" + +#if CXIMAGE_SUPPORT_WMF && CXIMAGE_SUPPORT_WINDOWS + +class CxImageWMF: public CxImage +{ + +#pragma pack(1) + +typedef struct tagRECT16 +{ + int16_t left; + int16_t top; + int16_t right; + int16_t bottom; +} RECT16; + +// taken from Windos 3.11 SDK Documentation (Programmer's Reference Volume 4: Resources) +typedef struct tagMETAFILEHEADER +{ + uint32_t key; // always 0x9ac6cdd7 + uint16_t reserved1; // reserved = 0 + RECT16 bbox; // bounding rectangle in metafile units as defined in "inch" + uint16_t inch; // number of metafile units per inch (should be < 1440) + uint32_t reserved2; // reserved = 0 + uint16_t checksum; // sum of the first 10 WORDS (using XOR operator) +} METAFILEHEADER; + +#pragma pack() + +public: + CxImageWMF(): CxImage(CXIMAGE_FORMAT_WMF) { } + + bool Decode(CxFile * hFile, int32_t nForceWidth=0, int32_t nForceHeight=0); + bool Decode(FILE *hFile, int32_t nForceWidth=0, int32_t nForceHeight=0) + { CxIOFile file(hFile); return Decode(&file,nForceWidth,nForceHeight); } + +#if CXIMAGE_SUPPORT_ENCODE + bool Encode(CxFile * hFile); + bool Encode(FILE *hFile) { CxIOFile file(hFile); return Encode(&file); } +#endif // CXIMAGE_SUPPORT_ENCODE + +protected: + void ShrinkMetafile(int32_t &cx, int32_t &cy); + BOOL CheckMetafileHeader(METAFILEHEADER *pmetafileheader); + HENHMETAFILE ConvertWmfFiletoEmf(CxFile *pFile, METAFILEHEADER *pmetafileheader); + HENHMETAFILE ConvertEmfFiletoEmf(CxFile *pFile, ENHMETAHEADER *pemfh); + +}; + +#define METAFILEKEY 0x9ac6cdd7L + +// Background color definition (if no transparency). see Notes above +#define XMF_COLOR_BACK GetSysColor(COLOR_WINDOW) +// alternatives +//#define XMF_COLOR_BACK RGB(192, 192, 192) // lite gray +//#define XMF_COLOR_BACK RGB( 0, 0, 0) // black +//#define XMF_COLOR_BACK RGB(255, 255, 255) // white + + +// transparency support. see Notes above +#define XMF_SUPPORT_TRANSPARENCY 0 +#define XMF_COLOR_TRANSPARENT_R 211 +#define XMF_COLOR_TRANSPARENT_G 121 +#define XMF_COLOR_TRANSPARENT_B 112 +// don't change +#define XMF_COLOR_TRANSPARENT RGB (XMF_COLOR_TRANSPARENT_R, \ + XMF_COLOR_TRANSPARENT_G, \ + XMF_COLOR_TRANSPARENT_B) +// don't change +#define XMF_RGBQUAD_TRANSPARENT XMF_COLOR_TRANSPARENT_B, \ + XMF_COLOR_TRANSPARENT_G, \ + XMF_COLOR_TRANSPARENT_R, \ + 0 +// max. size. see Notes above +// alternatives +//#define XMF_MAXSIZE_CX (GetSystemMetrics(SM_CXSCREEN)-10) +//#define XMF_MAXSIZE_CY (GetSystemMetrics(SM_CYSCREEN)-50) +//#define XMF_MAXSIZE_CX (2*GetSystemMetrics(SM_CXSCREEN)/3) +//#define XMF_MAXSIZE_CY (2*GetSystemMetrics(SM_CYSCREEN)/3) +#define XMF_MAXSIZE_CX 4000 +#define XMF_MAXSIZE_CY 4000 + + +#endif + +#endif diff --git a/DuiLib/3rd/CxImage/ximawnd.cpp b/DuiLib/3rd/CxImage/ximawnd.cpp new file mode 100644 index 0000000..2661b32 --- /dev/null +++ b/DuiLib/3rd/CxImage/ximawnd.cpp @@ -0,0 +1,1900 @@ +// xImaWnd.cpp : Windows functions +/* 07/08/2001 v1.00 - Davide Pizzolato - www.xdp.it + * CxImage version 7.0.1 07/Jan/2011 + */ + +#include "ximage.h" + +#include "ximaiter.h" +#include "ximabmp.h" + +//////////////////////////////////////////////////////////////////////////////// +#if defined (_WIN32_WCE) + +#ifndef DEFAULT_GUI_FONT +#define DEFAULT_GUI_FONT 17 +#endif + +#ifndef PROOF_QUALITY +#define PROOF_QUALITY 2 +#endif + +struct DIBINFO : public BITMAPINFO +{ + RGBQUAD arColors[255]; // Color table info - adds an extra 255 entries to palette + operator LPBITMAPINFO() { return (LPBITMAPINFO) this; } + operator LPBITMAPINFOHEADER() { return &bmiHeader; } + RGBQUAD* ColorTable() { return bmiColors; } +}; + +int32_t BytesPerLine(int32_t nWidth, int32_t nBitsPerPixel) +{ + return ( (nWidth * nBitsPerPixel + 31) & (~31) ) / 8; +} + +int32_t NumColorEntries(int32_t nBitsPerPixel, int32_t nCompression, uint32_t biClrUsed) +{ + int32_t nColors = 0; + switch (nBitsPerPixel) + { + case 1: + nColors = 2; break; + case 2: + nColors = 4; break; // winCE only + case 4: + nColors = 16; break; + case 8: + nColors =256; break; + case 24: + nColors = 0; break; + case 16: + case 32: + nColors = 3; break; // I've found that PocketPCs need this regardless of BI_RGB or BI_BITFIELDS + default: + ASSERT(FALSE); + } + // If biClrUsed is provided, and it is a legal value, use it + if (biClrUsed > 0 && biClrUsed <= (uint32_t)nColors) + return biClrUsed; + + return nColors; +} + +int32_t GetDIBits( + HDC hdc, // handle to DC + HBITMAP hbmp, // handle to bitmap + uint32_t uStartScan, // first scan line to set + uint32_t cScanLines, // number of scan lines to copy + LPVOID lpvBits, // array for bitmap bits + LPBITMAPINFO lpbi, // bitmap data buffer + uint32_t uUsage // RGB or palette index +) +{ + uint32_t iColorTableSize = 0; + + if (!hbmp) + return 0; + + // Get dimensions of bitmap + BITMAP bm; + if (!::GetObject(hbmp, sizeof(bm),(LPVOID)&bm)) + return 0; + + //3. Creating new bitmap and receive pointer to it's bits. + HBITMAP hTargetBitmap; + void *pBuffer; + + //3.1 Initilize DIBINFO structure + DIBINFO dibInfo; + dibInfo.bmiHeader.biBitCount = 24; + dibInfo.bmiHeader.biClrImportant = 0; + dibInfo.bmiHeader.biClrUsed = 0; + dibInfo.bmiHeader.biCompression = 0; + dibInfo.bmiHeader.biHeight = bm.bmHeight; + dibInfo.bmiHeader.biPlanes = 1; + dibInfo.bmiHeader.biSize = 40; + dibInfo.bmiHeader.biSizeImage = bm.bmHeight*BytesPerLine(bm.bmWidth,24); + dibInfo.bmiHeader.biWidth = bm.bmWidth; + dibInfo.bmiHeader.biXPelsPerMeter = 3780; + dibInfo.bmiHeader.biYPelsPerMeter = 3780; + dibInfo.bmiColors[0].rgbBlue = 0; + dibInfo.bmiColors[0].rgbGreen = 0; + dibInfo.bmiColors[0].rgbRed = 0; + dibInfo.bmiColors[0].rgbReserved = 0; + + //3.2 Create bitmap and receive pointer to points into pBuffer + HDC hDC = ::GetDC(NULL); + ASSERT(hDC); + hTargetBitmap = CreateDIBSection( + hDC, + (const BITMAPINFO*)dibInfo, + DIB_RGB_COLORS, + (void**)&pBuffer, + NULL, + 0); + + ::ReleaseDC(NULL, hDC); + + //4. Copy source bitmap into the target bitmap. + + //4.1 Create 2 device contexts + HDC memDc = CreateCompatibleDC(NULL); + if (!memDc) { + ASSERT(FALSE); + } + + HDC targetDc = CreateCompatibleDC(NULL); + if (!targetDc) { + ASSERT(FALSE); + } + + //4.2 Select source bitmap into one DC, target into another + HBITMAP hOldBitmap1 = (HBITMAP)::SelectObject(memDc, hbmp); + HBITMAP hOldBitmap2 = (HBITMAP)::SelectObject(targetDc, hTargetBitmap); + + //4.3 Copy source bitmap into the target one + BitBlt(targetDc, 0, 0, bm.bmWidth, bm.bmHeight, memDc, 0, 0, SRCCOPY); + + //4.4 Restore device contexts + ::SelectObject(memDc, hOldBitmap1); + ::SelectObject(targetDc, hOldBitmap2); + DeleteDC(memDc); + DeleteDC(targetDc); + + //Here we can bitmap bits: pBuffer. Note: + // 1. pBuffer contains 3 bytes per point + // 2. Lines ane from the bottom to the top! + // 3. Points in the line are from the left to the right + // 4. Bytes in one point are BGR (blue, green, red) not RGB + // 5. Don't delete pBuffer, it will be automatically deleted + // when delete hTargetBitmap + lpvBits = pBuffer; + + DeleteObject(hbmp); + //DeleteObject(hTargetBitmap); + + return 1; +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +#if CXIMAGE_SUPPORT_WINDOWS +//////////////////////////////////////////////////////////////////////////////// +int32_t CxImage::Blt(HDC pDC, int32_t x, int32_t y) +{ + if((pDib==0)||(pDC==0)||(!info.bEnabled)) return 0; + + HBRUSH brImage = CreateDIBPatternBrushPt(pDib, DIB_RGB_COLORS); + POINT pt; + SetBrushOrgEx(pDC,x,y,&pt); // + HBRUSH brOld = (HBRUSH) SelectObject(pDC, brImage); + PatBlt(pDC, x, y, head.biWidth, head.biHeight, PATCOPY); + SelectObject(pDC, brOld); + SetBrushOrgEx(pDC,pt.x,pt.y,NULL); + DeleteObject(brImage); + return 1; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Transfer the image in a global bitmap handle (clipboard copy) + */ +HANDLE CxImage::CopyToHandle() +{ + HANDLE hMem=NULL; + if (pDib){ + hMem= GlobalAlloc(GHND, GetSize()); + if (hMem){ + uint8_t* pDst=(uint8_t*)GlobalLock(hMem); + if (pDst){ + memcpy(pDst,pDib,GetSize()); + } + GlobalUnlock(hMem); + } + } + return hMem; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Global object (clipboard paste) constructor + * \param hMem: source bitmap object, the clipboard format must be CF_DIB + * \return true if everything is ok + */ +bool CxImage::CreateFromHANDLE(HANDLE hMem) +{ + if (!Destroy()) + return false; + + uint32_t dwSize = GlobalSize(hMem); + if (!dwSize) return false; + + uint8_t *lpVoid; //pointer to the bitmap + lpVoid = (uint8_t *)GlobalLock(hMem); + BITMAPINFOHEADER *pHead; //pointer to the bitmap header + pHead = (BITMAPINFOHEADER *)lpVoid; + if (lpVoid){ + + //CxMemFile hFile(lpVoid,dwSize); + + //copy the bitmap header + memcpy(&head,pHead,sizeof(BITMAPINFOHEADER)); + //check if it's a top-down bitmap + bool bTopDownDib = head.biHeight<0; + if (bTopDownDib) head.biHeight=-head.biHeight; + //create the image + if(!Create(head.biWidth,head.biHeight,head.biBitCount)){ + GlobalUnlock(hMem); + return false; + } + //preserve DPI + SetXDPI((int32_t)floor(head.biXPelsPerMeter * 254.0 / 10000.0 + 0.5)); + SetYDPI((int32_t)floor(head.biYPelsPerMeter * 254.0 / 10000.0 + 0.5)); + + /*//copy the pixels (old way) + if((pHead->biCompression != BI_RGB) || (pHead->biBitCount == 32)){ // + // BITFIELD case + // set the internal header in the dib + memcpy(pDib,&head,sizeof(head)); + // get the bitfield masks + uint32_t bf[3]; + memcpy(bf,lpVoid+pHead->biSize,12); + // transform into RGB + Bitfield2RGB(lpVoid+pHead->biSize+12,bf[0],bf[1],bf[2],(uint8_t)pHead->biBitCount); + } else { //normal bitmap + memcpy(pDib,lpVoid,GetSize()); + }*/ + + // + // fill in color map + bool bIsOldBmp = (head.biSize == sizeof(BITMAPCOREHEADER)); + RGBQUAD *pRgb = GetPalette(); + if (pRgb) { + // number of colors to fill in + int32_t nColors = DibNumColors(pHead); + if (bIsOldBmp) { + /* get pointer to BITMAPCOREINFO (old style 1.x) */ + LPBITMAPCOREINFO lpbmc = (LPBITMAPCOREINFO)lpVoid; + for (int32_t i = nColors - 1; i >= 0; i--) { + pRgb[i].rgbRed = lpbmc->bmciColors[i].rgbtRed; + pRgb[i].rgbGreen = lpbmc->bmciColors[i].rgbtGreen; + pRgb[i].rgbBlue = lpbmc->bmciColors[i].rgbtBlue; + pRgb[i].rgbReserved = (uint8_t)0; + } + } else { + /* get pointer to BITMAPINFO (new style 3.x) */ + LPBITMAPINFO lpbmi = (LPBITMAPINFO)lpVoid; + for (int32_t i = nColors - 1; i >= 0; i--) { + pRgb[i].rgbRed = lpbmi->bmiColors[i].rgbRed; + pRgb[i].rgbGreen = lpbmi->bmiColors[i].rgbGreen; + pRgb[i].rgbBlue = lpbmi->bmiColors[i].rgbBlue; + pRgb[i].rgbReserved = (uint8_t)0; + } + } + } + + // + uint32_t dwCompression = pHead->biCompression; + // compressed bitmap ? + if(dwCompression!=BI_RGB || pHead->biBitCount==32 || pHead->biBitCount ==16) { + // get the bitmap bits + LPSTR lpDIBBits = (LPSTR)((uint8_t*)pHead + *(uint32_t*)pHead + (uint16_t)(GetNumColors() * sizeof(RGBQUAD))); + // decode and copy them to our image + switch (pHead->biBitCount) { + case 32 : + { + // BITFIELD case + if (dwCompression == BI_BITFIELDS || dwCompression == BI_RGB) { + // get the bitfield masks + uint32_t bf[3]; + memcpy(bf,lpVoid+pHead->biSize,12); + // transform into RGB + Bitfield2RGB(lpVoid+pHead->biSize+12,bf[0],bf[1],bf[2],(uint8_t)pHead->biBitCount); + } else { + // "unknown compression"; + GlobalUnlock(hMem); + return false; + } + } + break; + case 16 : + { + // get the bitfield masks + int32_t offset=0; + uint32_t bf[3]; + if (dwCompression == BI_BITFIELDS) { + memcpy(bf,lpVoid+pHead->biSize,12); + offset= 12; + } else { + bf[0] = 0x7C00; + bf[1] = 0x3E0; + bf[2] = 0x1F; // RGB555 + } + // copy the pixels + memcpy(info.pImage, lpDIBBits + offset, head.biHeight*((head.biWidth+1)/2)*4); + // transform into RGB + Bitfield2RGB(info.pImage, bf[0], bf[1], bf[2], 16); + } + break; + case 8 : + case 4 : + case 1 : + { + switch (dwCompression) { + case BI_RLE4: + { + uint8_t status_byte = 0; + uint8_t second_byte = 0; + int32_t scanline = 0; + int32_t bits = 0; + BOOL low_nibble = FALSE; + CImageIterator iter(this); + + for (BOOL bContinue = TRUE; bContinue; ) { + status_byte = *(lpDIBBits++); + switch (status_byte) { + case RLE_COMMAND : + status_byte = *(lpDIBBits++); + switch (status_byte) { + case RLE_ENDOFLINE : + bits = 0; + scanline++; + low_nibble = FALSE; + break; + case RLE_ENDOFBITMAP : + bContinue = FALSE; + break; + case RLE_DELTA : + { + // read the delta values + uint8_t delta_x; + uint8_t delta_y; + delta_x = *(lpDIBBits++); + delta_y = *(lpDIBBits++); + // apply them + bits += delta_x / 2; + scanline += delta_y; + break; + } + default : + second_byte = *(lpDIBBits++); + uint8_t* sline = iter.GetRow(scanline); + for (int32_t i = 0; i < status_byte; i++) { + if ((uint8_t*)(sline+bits) < (uint8_t*)(info.pImage+head.biSizeImage)){ + if (low_nibble) { + if (i&1) + *(sline + bits) |= (second_byte & 0x0f); + else + *(sline + bits) |= (second_byte & 0xf0)>>4; + bits++; + } else { + if (i&1) + *(sline + bits) = (uint8_t)(second_byte & 0x0f)<<4; + else + *(sline + bits) = (uint8_t)(second_byte & 0xf0); + } + } + + if ((i & 1) && (i != (status_byte - 1))) + second_byte = *(lpDIBBits++); + + low_nibble = !low_nibble; + } + if ((((status_byte+1) >> 1) & 1 ) == 1) + second_byte = *(lpDIBBits++); + break; + }; + break; + default : + { + uint8_t* sline = iter.GetRow(scanline); + second_byte = *(lpDIBBits++); + for (unsigned i = 0; i < status_byte; i++) { + if ((uint8_t*)(sline+bits) < (uint8_t*)(info.pImage+head.biSizeImage)){ + if (low_nibble) { + if (i&1) + *(sline + bits) |= (second_byte & 0x0f); + else + *(sline + bits) |= (second_byte & 0xf0)>>4; + bits++; + } else { + if (i&1) + *(sline + bits) = (uint8_t)(second_byte & 0x0f)<<4; + else + *(sline + bits) = (uint8_t)(second_byte & 0xf0); + } + } + low_nibble = !low_nibble; + } + } + break; + }; + } + } + break; + case BI_RLE8 : + { + uint8_t status_byte = 0; + uint8_t second_byte = 0; + int32_t scanline = 0; + int32_t bits = 0; + CImageIterator iter(this); + + for (BOOL bContinue = TRUE; bContinue; ) { + status_byte = *(lpDIBBits++); + if (status_byte==RLE_COMMAND) { + status_byte = *(lpDIBBits++); + switch (status_byte) { + case RLE_ENDOFLINE : + bits = 0; + scanline++; + break; + case RLE_ENDOFBITMAP : + bContinue = FALSE; + break; + case RLE_DELTA : + { + // read the delta values + uint8_t delta_x; + uint8_t delta_y; + delta_x = *(lpDIBBits++); + delta_y = *(lpDIBBits++); + // apply them + bits += delta_x; + scanline += delta_y; + } + break; + default : + int32_t nNumBytes = sizeof(uint8_t) * status_byte; + memcpy((void *)(iter.GetRow(scanline) + bits), lpDIBBits, nNumBytes); + lpDIBBits += nNumBytes; + // align run length to even number of bytes + if ((status_byte & 1) == 1) + second_byte = *(lpDIBBits++); + bits += status_byte; + break; + }; + } else { + uint8_t *sline = iter.GetRow(scanline); + second_byte = *(lpDIBBits++); + for (unsigned i = 0; i < status_byte; i++) { + if ((uint8_t*)(sline+bits) < (uint8_t*)(info.pImage+head.biSizeImage)){ + *(sline + bits) = second_byte; + bits++; + } else { + bContinue = FALSE; //don't delete: we are in memory, it is not as with files + break; + } + } + } + } + } + break; + default : + { + // "compression type not supported"; + GlobalUnlock(hMem); + return false; + } + } + } + } + } else { + //normal bitmap (not compressed) + memcpy(pDib,lpVoid,GetSize()); + } + + GlobalUnlock(hMem); + + if (bTopDownDib) Flip(); + + return true; + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Transfer the image in a icon handle, with transparency. + * \param hdc: target device context (the screen, usually) + * \param bTransparency : (optional) exports trancparency + * \return icon handle, or NULL if an error occurs. + * \sa MakeBitmap + * \author [brunom] + */ +HICON CxImage::MakeIcon(HDC hdc, bool bTransparency) +{ + HICON hDestIcon = 0; + + ICONINFO csDest; + + csDest.fIcon = TRUE; + csDest.xHotspot = 0; + csDest.yHotspot = 0; + + // Assign HBITMAP with Transparency to ICON Info structure + csDest.hbmColor = MakeBitmap( hdc, bTransparency ); + + // Create Mask just in case we need a Mask for the Icons + CxImage a_Mask; + GetTransparentMask(&a_Mask); + + // Assign Mask + csDest.hbmMask = a_Mask.MakeBitmap(); + + // Create Icon + hDestIcon = ::CreateIconIndirect(&csDest); + + return hDestIcon; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Transfer the image in a bitmap handle + * \param hdc: target device context (the screen, usually) + * \param bTransparency : (optional) exports trancparency + * \return bitmap handle, or NULL if an error occurs. + * \sa Draw2HBITMAP, MakeIcon + * \author []; changes [brunom] + */ +HBITMAP CxImage::MakeBitmap(HDC hdc, bool bTransparency) +{ + if (!pDib) + return NULL; + + // Create HBITMAP with Trancparency + if( (pAlpha!=0) && bTransparency ) + { + HDC hMemDC; + if (hdc) + hMemDC = hdc; + else + hMemDC = CreateCompatibleDC(NULL); + + BITMAPINFO bi; + + // Fill in the BITMAPINFOHEADER + bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bi.bmiHeader.biWidth = GetWidth(); + bi.bmiHeader.biHeight = GetHeight(); + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + bi.bmiHeader.biCompression = BI_RGB; + bi.bmiHeader.biSizeImage = 4 * GetWidth() * GetHeight(); + bi.bmiHeader.biXPelsPerMeter = 0; + bi.bmiHeader.biYPelsPerMeter = 0; + bi.bmiHeader.biClrUsed = 0; + bi.bmiHeader.biClrImportant = 0; + + COLORREF* pCrBits = NULL; + HBITMAP hbmp = CreateDIBSection ( + hMemDC, &bi, DIB_RGB_COLORS, (void **)&pCrBits, + NULL, NULL); + + if (!hdc) + DeleteDC(hMemDC); + + DIBSECTION ds; + if (::GetObject (hbmp, sizeof (DIBSECTION), &ds) == 0) + { + return 0; + } + + // transfer Pixels from CxImage to Bitmap + RGBQUAD* pBit = (RGBQUAD*) ds.dsBm.bmBits; + int32_t lPx,lPy; + for( lPy=0 ; lPy < bi.bmiHeader.biHeight ; ++lPy ) + { + for( lPx=0 ; lPx < bi.bmiHeader.biWidth ; ++lPx ) + { + RGBQUAD lPixel = GetPixelColor(lPx,lPy,true); + *pBit = lPixel; + pBit++; + } + } + + return hbmp; + } + + // Create HBITMAP without Trancparency + if (!hdc){ + // this call to CreateBitmap doesn't create a DIB + // // Create a device-independent bitmap + // return CreateBitmap(head.biWidth,head.biHeight, 1, head.biBitCount, GetBits()); + // use instead this code + HDC hMemDC = CreateCompatibleDC(NULL); + LPVOID pBit32; + HBITMAP bmp = CreateDIBSection(hMemDC,(LPBITMAPINFO)pDib,DIB_RGB_COLORS, &pBit32, NULL, 0); + if (pBit32) memcpy(pBit32, GetBits(), head.biSizeImage); + DeleteDC(hMemDC); + return bmp; + } + + // this single line seems to work very well + //HBITMAP bmp = CreateDIBitmap(hdc, (LPBITMAPINFOHEADER)pDib, CBM_INIT, + // GetBits(), (LPBITMAPINFO)pDib, DIB_RGB_COLORS); + // this alternative works also with _WIN32_WCE + LPVOID pBit32; + HBITMAP bmp = CreateDIBSection(hdc, (LPBITMAPINFO)pDib, DIB_RGB_COLORS, &pBit32, NULL, 0); + if (pBit32) memcpy(pBit32, GetBits(), head.biSizeImage); + + return bmp; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * check if the bitmap contains transparency data + * \param hbmp : bitmap resource handle + * \return true the bitmap has transparency + * \author [brunom] + */ +bool CxImage::IsHBITMAPAlphaValid( HBITMAP hbmp ) +{ + bool lbAlphaValid = false; + if (hbmp) + { + BITMAP bm; + // get informations about the bitmap + GetObject(hbmp, sizeof(BITMAP), (LPSTR) &bm); + + // for alpha there must bee 32 Bit's per Pixel ?? + if( bm.bmBitsPixel == 32 ) + { + BITMAPINFO l_BitmapInfo; + l_BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + l_BitmapInfo.bmiHeader.biWidth = bm.bmWidth; + l_BitmapInfo.bmiHeader.biHeight = bm.bmHeight; + l_BitmapInfo.bmiHeader.biPlanes = bm.bmPlanes; + l_BitmapInfo.bmiHeader.biBitCount = bm.bmBitsPixel; + l_BitmapInfo.bmiHeader.biCompression = BI_RGB; + + // create Buffer for Image + RGBQUAD * l_pRawBytes = new RGBQUAD[bm.bmWidth * bm.bmHeight]; + + HDC dc = ::GetDC(NULL); + + if(dc) + { + // Get Pixel Data from Image + if(GetDIBits(dc, hbmp, 0, bm.bmHeight, l_pRawBytes, &l_BitmapInfo, DIB_RGB_COLORS)) + { + RGBQUAD * lpArray = l_pRawBytes; + RGBQUAD * lpArrayEnd = l_pRawBytes + (bm.bmWidth * bm.bmHeight); + + // check if Alpha Channel is realy valid (anny value not zero) + for( ;lpArray != lpArrayEnd ; ++lpArray ) + { + // any alpha value not zero + if( lpArray->rgbReserved != 0 ) + { + // must be vaid alph channel + lbAlphaValid = true; + break; + } + } + } + ::ReleaseDC(NULL, dc); + } + // free temporary Memory + delete [] l_pRawBytes; + } + } + + return lbAlphaValid; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Bitmap resource constructor + * \param hbmp : bitmap resource handle + * \param hpal : (optional) palette, useful for 8bpp DC + * \param bTransparency : (optional) for 32bpp images only, imports trancparency + * \return true if everything is ok + * \author []; changes [brunom] + */ +bool CxImage::CreateFromHBITMAP(HBITMAP hbmp, HPALETTE hpal, bool bTransparency) +{ + if (!Destroy()) + return false; + + if (hbmp) { + BITMAP bm; + // get informations about the bitmap + GetObject(hbmp, sizeof(BITMAP), (LPSTR) &bm); + + // Transparency in HBITMAP + if(bTransparency && IsHBITMAPAlphaValid(hbmp)) + { + bool l_bResult = true; + + BITMAPINFO l_BitmapInfo; + l_BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + l_BitmapInfo.bmiHeader.biWidth = bm.bmWidth; + l_BitmapInfo.bmiHeader.biHeight = bm.bmHeight; + l_BitmapInfo.bmiHeader.biPlanes = bm.bmPlanes; + l_BitmapInfo.bmiHeader.biBitCount = bm.bmBitsPixel; + l_BitmapInfo.bmiHeader.biCompression = BI_RGB; + + RGBQUAD *l_pRawBytes = new RGBQUAD[bm.bmWidth * bm.bmHeight]; + + HDC dc = ::GetDC(NULL); + + if(dc) + { + if(GetDIBits(dc, hbmp, 0, bm.bmHeight, l_pRawBytes, &l_BitmapInfo, DIB_RGB_COLORS)) + l_bResult = CreateFromArray((uint8_t*)l_pRawBytes, bm.bmWidth, bm.bmHeight, bm.bmBitsPixel, bm.bmWidthBytes, false); + else + l_bResult = false; + + ::ReleaseDC(NULL, dc); + } + else + l_bResult = false; + + delete [] l_pRawBytes; + + return l_bResult; + } + else + { + // create the image + if (!Create(bm.bmWidth, bm.bmHeight, bm.bmBitsPixel, 0)) + return false; + // create a device context for the bitmap + HDC dc = ::GetDC(NULL); + if (!dc) + return false; + + if (hpal){ + SelectObject(dc,hpal); //the palette you should get from the user or have a stock one + RealizePalette(dc); + } + + // copy the pixels + if (GetDIBits(dc, hbmp, 0, head.biHeight, info.pImage, + (LPBITMAPINFO)pDib, DIB_RGB_COLORS) == 0){ //replace &head with pDib + strcpy(info.szLastError,"GetDIBits failed"); + ::ReleaseDC(NULL, dc); + return false; + } + ::ReleaseDC(NULL, dc); + return true; + } + } + return false; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * icon resource constructor + * \param hico : icon resource handle + * \param bTransparency : (optional) for 32bpp images only, imports trancparency + * \return true if everything is ok + * \author []; changes [Arlen Albert Keshabian], [brunom] + */ +#if !defined (_WIN32_WCE) +bool CxImage::CreateFromHICON(HICON hico, bool bTransparency) +{ + if (!Destroy() || !hico) + return false; + + bool l_bResult = true; + + ICONINFO iinfo; + GetIconInfo(hico,&iinfo); + + //BITMAP l_Bitmap; + //GetObject(iinfo.hbmColor, sizeof(BITMAP), &l_Bitmap); + + l_bResult = CreateFromHBITMAP( iinfo.hbmColor, NULL, bTransparency ); + +#if CXIMAGE_SUPPORT_ALPHA + if(l_bResult && ((!IsHBITMAPAlphaValid(iinfo.hbmColor)) || (!bTransparency)) ) + { + CxImage mask; + mask.CreateFromHBITMAP(iinfo.hbmMask); + mask.GrayScale(); + mask.Negative(); + AlphaSet(mask); + } +#endif + + DeleteObject(iinfo.hbmColor); // + DeleteObject(iinfo.hbmMask); // + + return l_bResult; +} +#endif //_WIN32_WCE +//////////////////////////////////////////////////////////////////////////////// +int32_t CxImage::Draw(HDC hdc, const RECT& rect, RECT* pClipRect, bool bSmooth, bool bFlipY) +{ + return Draw(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, pClipRect,bSmooth, bFlipY); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Draws the image in the specified device context, with support for alpha channel, alpha palette, transparency, opacity. + * \param hdc : destination device context + * \param x,y : (optional) offset + * \param cx,cy : (optional) size. + * - If cx or cy are not specified (or less than 0), the normal width or height will be used + * - If cx or cy are different than width or height, the image will be stretched + * + * \param pClipRect : limit the drawing operations inside a given rectangle in the output device context. + * \param bSmooth : activates a bilinear filter that will enhance the appearence for zommed pictures. + * Quite slow. Needs CXIMAGE_SUPPORT_INTERPOLATION. + * \param bFlipY : draws a mirror image along the y-axis + * \return true if everything is ok + */ +int32_t CxImage::Draw(HDC hdc, int32_t x, int32_t y, int32_t cx, int32_t cy, RECT* pClipRect, bool bSmooth, bool bFlipY) +{ + if((pDib==0)||(hdc==0)||(cx==0)||(cy==0)||(!info.bEnabled)) return 0; + + if (cx < 0) cx = head.biWidth; + if (cy < 0) cy = head.biHeight; + bool bTransparent = info.nBkgndIndex >= 0; + bool bAlpha = pAlpha != 0; + + //required for MM_ANISOTROPIC, MM_HIENGLISH, and similar modes [Greg Peatfield] + int32_t hdc_Restore = ::SaveDC(hdc); + if (!hdc_Restore) + return 0; + +#if !defined (_WIN32_WCE) + RECT mainbox; // (experimental) + if (pClipRect){ + GetClipBox(hdc,&mainbox); + HRGN rgn = CreateRectRgnIndirect(pClipRect); + ExtSelectClipRgn(hdc,rgn,RGN_AND); + DeleteObject(rgn); + } +#endif + + //find the smallest area to paint + RECT clipbox,paintbox; + GetClipBox(hdc,&clipbox); + + paintbox.top = min(clipbox.bottom,max(clipbox.top,y)); + paintbox.left = min(clipbox.right,max(clipbox.left,x)); + paintbox.right = max(clipbox.left,min(clipbox.right,x+cx)); + paintbox.bottom = max(clipbox.top,min(clipbox.bottom,y+cy)); + + int32_t destw = paintbox.right - paintbox.left; + int32_t desth = paintbox.bottom - paintbox.top; + + if (!(bTransparent || bAlpha || info.bAlphaPaletteEnabled)){ + if (cx==head.biWidth && cy==head.biHeight){ //NORMAL +#if !defined (_WIN32_WCE) + SetStretchBltMode(hdc,COLORONCOLOR); +#endif + if (bFlipY){ + StretchDIBits(hdc, x, y+cy-1, + cx, -cy, 0, 0, cx, cy, + info.pImage,(BITMAPINFO*)pDib,DIB_RGB_COLORS,SRCCOPY); + } else { + SetDIBitsToDevice(hdc, x, y, cx, cy, 0, 0, 0, cy, + info.pImage,(BITMAPINFO*)pDib,DIB_RGB_COLORS); + } + } else { //STRETCH + //pixel informations + RGBQUAD c={0,0,0,0}; + //Preparing Bitmap Info + BITMAPINFO bmInfo; + memset(&bmInfo.bmiHeader,0,sizeof(BITMAPINFOHEADER)); + bmInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); + bmInfo.bmiHeader.biWidth=destw; + bmInfo.bmiHeader.biHeight=desth; + bmInfo.bmiHeader.biPlanes=1; + bmInfo.bmiHeader.biBitCount=24; + uint8_t *pbase; //points to the final dib + uint8_t *pdst; //current pixel from pbase + uint8_t *ppix; //current pixel from image + //get the background + HDC TmpDC=CreateCompatibleDC(hdc); + HBITMAP TmpBmp=CreateDIBSection(hdc,&bmInfo,DIB_RGB_COLORS,(void**)&pbase,0,0); + HGDIOBJ TmpObj=SelectObject(TmpDC,TmpBmp); + + if (pbase){ + int32_t xx,yy; + int32_t sx,sy; + float dx,dy; + uint8_t *psrc; + + int32_t ew = ((((24 * destw) + 31) / 32) * 4); + int32_t ymax = paintbox.bottom; + int32_t xmin = paintbox.left; + float fx=(float)head.biWidth/(float)cx; + float fy=(float)head.biHeight/(float)cy; + + for(yy=0;yy 1 && fy > 1) { + c = GetAreaColorInterpolated(dx - 0.5f, dy - 0.5f, fx, fy, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } else { + c = GetPixelColorInterpolated(dx - 0.5f, dy - 0.5f, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } + } else +#endif //CXIMAGE_SUPPORT_INTERPOLATION + { + if (head.biClrUsed){ + c=GetPaletteColor(GetPixelIndex(sx,sy)); + } else { + ppix = psrc + sx*3; + c.rgbBlue = *ppix++; + c.rgbGreen= *ppix++; + c.rgbRed = *ppix; + } + } + *pdst++=c.rgbBlue; + *pdst++=c.rgbGreen; + *pdst++=c.rgbRed; + } + } + } + //paint the image & cleanup + SetDIBitsToDevice(hdc,paintbox.left,paintbox.top,destw,desth,0,0,0,desth,pbase,&bmInfo,0); + DeleteObject(SelectObject(TmpDC,TmpObj)); + DeleteDC(TmpDC); + } + } else { // draw image with transparent/alpha blending + ////////////////////////////////////////////////////////////////// + //Alpha blend - Thanks to Florian Egel + + //pixel informations + RGBQUAD c={0,0,0,0}; + RGBQUAD ct = GetTransColor(); + int32_t* pc = (int32_t*)&c; + int32_t* pct= (int32_t*)&ct; + int32_t cit = GetTransIndex(); + int32_t ci = 0; + + //Preparing Bitmap Info + BITMAPINFO bmInfo; + memset(&bmInfo.bmiHeader,0,sizeof(BITMAPINFOHEADER)); + bmInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); + bmInfo.bmiHeader.biWidth=destw; + bmInfo.bmiHeader.biHeight=desth; + bmInfo.bmiHeader.biPlanes=1; + bmInfo.bmiHeader.biBitCount=24; + + uint8_t *pbase; //points to the final dib + uint8_t *pdst; //current pixel from pbase + uint8_t *ppix; //current pixel from image + + //get the background + HDC TmpDC=CreateCompatibleDC(hdc); + HBITMAP TmpBmp=CreateDIBSection(hdc,&bmInfo,DIB_RGB_COLORS,(void**)&pbase,0,0); + HGDIOBJ TmpObj=SelectObject(TmpDC,TmpBmp); + BitBlt(TmpDC,0,0,destw,desth,hdc,paintbox.left,paintbox.top,SRCCOPY); + + if (pbase){ + int32_t xx,yy,alphaoffset,ix,iy; + uint8_t a,a1,*psrc; + int32_t ew = ((((24 * destw) + 31) / 32) * 4); + int32_t ymax = paintbox.bottom; + int32_t xmin = paintbox.left; + + if (cx!=head.biWidth || cy!=head.biHeight){ + //STRETCH + float fx=(float)head.biWidth/(float)cx; + float fy=(float)head.biHeight/(float)cy; + float dx,dy; + int32_t sx,sy; + + for(yy=0;yy>8); + + if (head.biClrUsed){ + ci = GetPixelIndex(sx,sy); +#if CXIMAGE_SUPPORT_INTERPOLATION + if (bSmooth){ + if (fx > 1 && fy > 1) { + c = GetAreaColorInterpolated(dx - 0.5f, dy - 0.5f, fx, fy, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } else { + c = GetPixelColorInterpolated(dx - 0.5f, dy - 0.5f, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } + } else +#endif //CXIMAGE_SUPPORT_INTERPOLATION + { + c = GetPaletteColor(GetPixelIndex(sx,sy)); + } + if (info.bAlphaPaletteEnabled){ + a = (uint8_t)((a*(1+c.rgbReserved))>>8); + } + } else { +#if CXIMAGE_SUPPORT_INTERPOLATION + if (bSmooth){ + if (fx > 1 && fy > 1) { + c = GetAreaColorInterpolated(dx - 0.5f, dy - 0.5f, fx, fy, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } else { + c = GetPixelColorInterpolated(dx - 0.5f, dy - 0.5f, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } + } else +#endif //CXIMAGE_SUPPORT_INTERPOLATION + { + ppix = psrc + sx*3; + c.rgbBlue = *ppix++; + c.rgbGreen= *ppix++; + c.rgbRed = *ppix; + } + } + //if (*pc!=*pct || !bTransparent){ + //if ((head.biClrUsed && ci!=cit) || ((!head.biClrUsed||bSmooth) && *pc!=*pct) || !bTransparent){ + if ((head.biClrUsed && ci!=cit) || (!head.biClrUsed && *pc!=*pct) || !bTransparent){ + // DJT, assume many pixels are fully transparent or opaque and thus avoid multiplication + if (a == 0) { // Transparent, retain dest + pdst+=3; + } else if (a == 255) { // opaque, ignore dest + *pdst++= c.rgbBlue; + *pdst++= c.rgbGreen; + *pdst++= c.rgbRed; + } else { // semi transparent + a1=(uint8_t)~a; + *pdst++=(uint8_t)((*pdst * a1 + a * c.rgbBlue)>>8); + *pdst++=(uint8_t)((*pdst * a1 + a * c.rgbGreen)>>8); + *pdst++=(uint8_t)((*pdst * a1 + a * c.rgbRed)>>8); + } + } else { + pdst+=3; + } + } + } + } else { + //NORMAL + iy=head.biHeight-ymax+y; + for(yy=0;yy>8); + + if (head.biClrUsed){ + ci = GetPixelIndex(ix,iy); + c = GetPaletteColor((uint8_t)ci); + if (info.bAlphaPaletteEnabled){ + a = (uint8_t)((a*(1+c.rgbReserved))>>8); + } + } else { + c.rgbBlue = *ppix++; + c.rgbGreen= *ppix++; + c.rgbRed = *ppix++; + } + + //if (*pc!=*pct || !bTransparent){ + if ((head.biClrUsed && ci!=cit) || (!head.biClrUsed && *pc!=*pct) || !bTransparent){ + // DJT, assume many pixels are fully transparent or opaque and thus avoid multiplication + if (a == 0) { // Transparent, retain dest + pdst+=3; + } else if (a == 255) { // opaque, ignore dest + *pdst++= c.rgbBlue; + *pdst++= c.rgbGreen; + *pdst++= c.rgbRed; + } else { // semi transparent + a1=(uint8_t)~a; + *pdst++=(uint8_t)((*pdst * a1 + a * c.rgbBlue)>>8); + *pdst++=(uint8_t)((*pdst * a1 + a * c.rgbGreen)>>8); + *pdst++=(uint8_t)((*pdst * a1 + a * c.rgbRed)>>8); + } + } else { + pdst+=3; + } + } + } + } + } + //paint the image & cleanup + SetDIBitsToDevice(hdc,paintbox.left,paintbox.top,destw,desth,0,0,0,desth,pbase,&bmInfo,0); + DeleteObject(SelectObject(TmpDC,TmpObj)); + DeleteDC(TmpDC); + } + +#if !defined (_WIN32_WCE) + if (pClipRect){ // (experimental) + HRGN rgn = CreateRectRgnIndirect(&mainbox); + ExtSelectClipRgn(hdc,rgn,RGN_OR); + DeleteObject(rgn); + } +#endif + + ::RestoreDC(hdc,hdc_Restore); + return 1; +} + +//////////////////////////////////////////////////////////////////////////////// +/** + * renders the image into a HBITMAP handle + * \param hdc : destination device context + * \param x,y : (optional) offset + * \param cx,cy : (optional) size. + * - If cx or cy are not specified (or less than 0), the normal width or height will be used + * - If cx or cy are different than width or height, the image will be stretched + * \param pClipRect : limit the drawing operations inside a given rectangle in the output device context. + * \param bSmooth : activates a bilinear filter that will enhance the appearence for zommed pictures. + * Quite slow. Needs CXIMAGE_SUPPORT_INTERPOLATION. + * \return HBITMAP handle, NULL in case of error + * \sa MakeBitmap + */ +HBITMAP CxImage::Draw2HBITMAP(HDC hdc, int32_t x, int32_t y, int32_t cx, int32_t cy, RECT* pClipRect, bool bSmooth) +{ + if((pDib==0)||(hdc==0)||(cx==0)||(cy==0)||(!info.bEnabled)) return 0; + + if (cx < 0) cx = head.biWidth; + if (cy < 0) cy = head.biHeight; + bool bTransparent = info.nBkgndIndex >= 0; + bool bAlpha = pAlpha != 0; + + //required for MM_ANISOTROPIC, MM_HIENGLISH, and similar modes [Greg Peatfield] + int32_t hdc_Restore = ::SaveDC(hdc); + if (!hdc_Restore) + return 0; + +#if !defined (_WIN32_WCE) + RECT mainbox; // (experimental) + if (pClipRect){ + GetClipBox(hdc,&mainbox); + HRGN rgn = CreateRectRgnIndirect(pClipRect); + ExtSelectClipRgn(hdc,rgn,RGN_AND); + DeleteObject(rgn); + } +#endif + + HBITMAP TmpBmp; + + //find the smallest area to paint + RECT clipbox,paintbox; + GetClipBox(hdc,&clipbox); + + paintbox.top = min(clipbox.bottom,max(clipbox.top,y)); + paintbox.left = min(clipbox.right,max(clipbox.left,x)); + paintbox.right = max(clipbox.left,min(clipbox.right,x+cx)); + paintbox.bottom = max(clipbox.top,min(clipbox.bottom,y+cy)); + + int32_t destw = paintbox.right - paintbox.left; + int32_t desth = paintbox.bottom - paintbox.top; + + if (!(bTransparent || bAlpha || info.bAlphaPaletteEnabled)){ + if (cx==head.biWidth && cy==head.biHeight){ //NORMAL +#if !defined (_WIN32_WCE) + SetStretchBltMode(hdc,COLORONCOLOR); +#endif + SetDIBitsToDevice(hdc, x, y, cx, cy, 0, 0, 0, cy, + info.pImage,(BITMAPINFO*)pDib,DIB_RGB_COLORS); + } else { //STRETCH + //pixel informations + RGBQUAD c={0,0,0,0}; + //Preparing Bitmap Info + BITMAPINFO bmInfo; + memset(&bmInfo.bmiHeader,0,sizeof(BITMAPINFOHEADER)); + bmInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); + bmInfo.bmiHeader.biWidth=destw; + bmInfo.bmiHeader.biHeight=desth; + bmInfo.bmiHeader.biPlanes=1; + bmInfo.bmiHeader.biBitCount=24; + uint8_t *pbase; //points to the final dib + uint8_t *pdst; //current pixel from pbase + uint8_t *ppix; //current pixel from image + //get the background + HDC TmpDC=CreateCompatibleDC(hdc); + TmpBmp=CreateDIBSection(hdc,&bmInfo,DIB_RGB_COLORS,(void**)&pbase,0,0); + HGDIOBJ TmpObj=SelectObject(TmpDC,TmpBmp); + + if (pbase){ + int32_t xx,yy; + int32_t sx,sy; + float dx,dy; + uint8_t *psrc; + + int32_t ew = ((((24 * destw) + 31) / 32) * 4); + int32_t ymax = paintbox.bottom; + int32_t xmin = paintbox.left; + float fx=(float)head.biWidth/(float)cx; + float fy=(float)head.biHeight/(float)cy; + + for(yy=0;yy 1 && fy > 1) { + c = GetAreaColorInterpolated(dx - 0.5f, dy - 0.5f, fx, fy, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } else { + c = GetPixelColorInterpolated(dx - 0.5f, dy - 0.5f, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } + } else +#endif //CXIMAGE_SUPPORT_INTERPOLATION + { + if (head.biClrUsed){ + c=GetPaletteColor(GetPixelIndex(sx,sy)); + } else { + ppix = psrc + sx*3; + c.rgbBlue = *ppix++; + c.rgbGreen= *ppix++; + c.rgbRed = *ppix; + } + } + *pdst++=c.rgbBlue; + *pdst++=c.rgbGreen; + *pdst++=c.rgbRed; + } + } + } + //cleanup + SelectObject(TmpDC,TmpObj); + DeleteDC(TmpDC); + } + } else { // draw image with transparent/alpha blending + ////////////////////////////////////////////////////////////////// + //Alpha blend - Thanks to Florian Egel + + //pixel informations + RGBQUAD c={0,0,0,0}; + RGBQUAD ct = GetTransColor(); + int32_t* pc = (int32_t*)&c; + int32_t* pct= (int32_t*)&ct; + int32_t cit = GetTransIndex(); + int32_t ci = 0; + + //Preparing Bitmap Info + BITMAPINFO bmInfo; + memset(&bmInfo.bmiHeader,0,sizeof(BITMAPINFOHEADER)); + bmInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); + bmInfo.bmiHeader.biWidth=destw; + bmInfo.bmiHeader.biHeight=desth; + bmInfo.bmiHeader.biPlanes=1; + bmInfo.bmiHeader.biBitCount=24; + + uint8_t *pbase; //points to the final dib + uint8_t *pdst; //current pixel from pbase + uint8_t *ppix; //current pixel from image + + //get the background + HDC TmpDC=CreateCompatibleDC(hdc); + TmpBmp=CreateDIBSection(hdc,&bmInfo,DIB_RGB_COLORS,(void**)&pbase,0,0); + HGDIOBJ TmpObj=SelectObject(TmpDC,TmpBmp); + BitBlt(TmpDC,0,0,destw,desth,hdc,paintbox.left,paintbox.top,SRCCOPY); + + if (pbase){ + int32_t xx,yy,alphaoffset,ix,iy; + uint8_t a,a1,*psrc; + int32_t ew = ((((24 * destw) + 31) / 32) * 4); + int32_t ymax = paintbox.bottom; + int32_t xmin = paintbox.left; + + if (cx!=head.biWidth || cy!=head.biHeight){ + //STRETCH + float fx=(float)head.biWidth/(float)cx; + float fy=(float)head.biHeight/(float)cy; + float dx,dy; + int32_t sx,sy; + + for(yy=0;yy>8); + + if (head.biClrUsed){ + ci = GetPixelIndex(sx,sy); +#if CXIMAGE_SUPPORT_INTERPOLATION + if (bSmooth){ + if (fx > 1 && fy > 1) { + c = GetAreaColorInterpolated(dx - 0.5f, dy - 0.5f, fx, fy, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } else { + c = GetPixelColorInterpolated(dx - 0.5f, dy - 0.5f, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } + } else +#endif //CXIMAGE_SUPPORT_INTERPOLATION + { + c = GetPaletteColor(GetPixelIndex(sx,sy)); + } + if (info.bAlphaPaletteEnabled){ + a = (uint8_t)((a*(1+c.rgbReserved))>>8); + } + } else { +#if CXIMAGE_SUPPORT_INTERPOLATION + if (bSmooth){ + if (fx > 1 && fy > 1) { + c = GetAreaColorInterpolated(dx - 0.5f, dy - 0.5f, fx, fy, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } else { + c = GetPixelColorInterpolated(dx - 0.5f, dy - 0.5f, CxImage::IM_BILINEAR, CxImage::OM_REPEAT); + } + } else +#endif //CXIMAGE_SUPPORT_INTERPOLATION + { + ppix = psrc + sx*3; + c.rgbBlue = *ppix++; + c.rgbGreen= *ppix++; + c.rgbRed = *ppix; + } + } + //if (*pc!=*pct || !bTransparent){ + //if ((head.biClrUsed && ci!=cit) || ((!head.biClrUsed||bSmooth) && *pc!=*pct) || !bTransparent){ + if ((head.biClrUsed && ci!=cit) || (!head.biClrUsed && *pc!=*pct) || !bTransparent){ + // DJT, assume many pixels are fully transparent or opaque and thus avoid multiplication + if (a == 0) { // Transparent, retain dest + pdst+=3; + } else if (a == 255) { // opaque, ignore dest + *pdst++= c.rgbBlue; + *pdst++= c.rgbGreen; + *pdst++= c.rgbRed; + } else { // semi transparent + a1=(uint8_t)~a; + *pdst++=(uint8_t)((*pdst * a1 + a * c.rgbBlue)>>8); + *pdst++=(uint8_t)((*pdst * a1 + a * c.rgbGreen)>>8); + *pdst++=(uint8_t)((*pdst * a1 + a * c.rgbRed)>>8); + } + } else { + pdst+=3; + } + } + } + } else { + //NORMAL + iy=head.biHeight-ymax+y; + for(yy=0;yy>8); + + if (head.biClrUsed){ + ci = GetPixelIndex(ix,iy); + c = GetPaletteColor((uint8_t)ci); + if (info.bAlphaPaletteEnabled){ + a = (uint8_t)((a*(1+c.rgbReserved))>>8); + } + } else { + c.rgbBlue = *ppix++; + c.rgbGreen= *ppix++; + c.rgbRed = *ppix++; + } + + //if (*pc!=*pct || !bTransparent){ + if ((head.biClrUsed && ci!=cit) || (!head.biClrUsed && *pc!=*pct) || !bTransparent){ + // DJT, assume many pixels are fully transparent or opaque and thus avoid multiplication + if (a == 0) { // Transparent, retain dest + pdst+=3; + } else if (a == 255) { // opaque, ignore dest + *pdst++= c.rgbBlue; + *pdst++= c.rgbGreen; + *pdst++= c.rgbRed; + } else { // semi transparent + a1=(uint8_t)~a; + *pdst++=(uint8_t)((*pdst * a1 + a * c.rgbBlue)>>8); + *pdst++=(uint8_t)((*pdst * a1 + a * c.rgbGreen)>>8); + *pdst++=(uint8_t)((*pdst * a1 + a * c.rgbRed)>>8); + } + } else { + pdst+=3; + } + } + } + } + } + //cleanup + SelectObject(TmpDC,TmpObj); + DeleteDC(TmpDC); + } + +#if !defined (_WIN32_WCE) + if (pClipRect){ // (experimental) + HRGN rgn = CreateRectRgnIndirect(&mainbox); + ExtSelectClipRgn(hdc,rgn,RGN_OR); + DeleteObject(rgn); + } +#endif + + ::RestoreDC(hdc,hdc_Restore); + return TmpBmp; +} + +//////////////////////////////////////////////////////////////////////////////// +int32_t CxImage::Draw2(HDC hdc, const RECT& rect) +{ + return Draw2(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Draws (stretch) the image with single transparency support + * \param hdc : destination device context + * \param x,y : (optional) offset + * \param cx,cy : (optional) size. + * - If cx or cy are not specified (or less than 0), the normal width or height will be used + * - If cx or cy are different than width or height, the image will be stretched + * + * \return true if everything is ok + */ +int32_t CxImage::Draw2(HDC hdc, int32_t x, int32_t y, int32_t cx, int32_t cy) +{ + if((pDib==0)||(hdc==0)||(cx==0)||(cy==0)||(!info.bEnabled)) return 0; + if (cx < 0) cx = head.biWidth; + if (cy < 0) cy = head.biHeight; + bool bTransparent = (info.nBkgndIndex >= 0); + + //required for MM_ANISOTROPIC, MM_HIENGLISH, and similar modes [Greg Peatfield] + int32_t hdc_Restore = ::SaveDC(hdc); + if (!hdc_Restore) + return 0; + + if (!bTransparent){ +#if !defined (_WIN32_WCE) + SetStretchBltMode(hdc,COLORONCOLOR); +#endif + StretchDIBits(hdc, x, y, cx, cy, 0, 0, head.biWidth, head.biHeight, + info.pImage,(BITMAPINFO*)pDib, DIB_RGB_COLORS,SRCCOPY); + } else { + // draw image with transparent background + const int32_t safe = 0; // or else GDI fails in the following - sometimes + RECT rcDst = {x+safe, y+safe, x+cx, y+cy}; + if (RectVisible(hdc, &rcDst)){ + ///////////////////////////////////////////////////////////////// + // True Mask Method - Thanks to Paul Reynolds and Ron Gery + int32_t nWidth = head.biWidth; + int32_t nHeight = head.biHeight; + // Create two memory dcs for the image and the mask + HDC dcImage=CreateCompatibleDC(hdc); + HDC dcTrans=CreateCompatibleDC(hdc); + // Select the image into the appropriate dc + HBITMAP bm = CreateCompatibleBitmap(hdc, nWidth, nHeight); + HBITMAP pOldBitmapImage = (HBITMAP)SelectObject(dcImage,bm); +#if !defined (_WIN32_WCE) + SetStretchBltMode(dcImage,COLORONCOLOR); +#endif + StretchDIBits(dcImage, 0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight, + info.pImage,(BITMAPINFO*)pDib,DIB_RGB_COLORS,SRCCOPY); + + // Create the mask bitmap + HBITMAP bitmapTrans = CreateBitmap(nWidth, nHeight, 1, 1, NULL); + // Select the mask bitmap into the appropriate dc + HBITMAP pOldBitmapTrans = (HBITMAP)SelectObject(dcTrans, bitmapTrans); + // Build mask based on transparent colour + RGBQUAD rgbBG; + if (head.biBitCount<24) rgbBG = GetPaletteColor((uint8_t)info.nBkgndIndex); + else rgbBG = info.nBkgndColor; + COLORREF crColour = RGB(rgbBG.rgbRed, rgbBG.rgbGreen, rgbBG.rgbBlue); + COLORREF crOldBack = SetBkColor(dcImage,crColour); + BitBlt(dcTrans,0, 0, nWidth, nHeight, dcImage, 0, 0, SRCCOPY); + + // Do the work - True Mask method - cool if not actual display + StretchBlt(hdc,x, y,cx,cy, dcImage, 0, 0, nWidth, nHeight, SRCINVERT); + StretchBlt(hdc,x, y,cx,cy, dcTrans, 0, 0, nWidth, nHeight, SRCAND); + StretchBlt(hdc,x, y,cx,cy, dcImage, 0, 0, nWidth, nHeight, SRCINVERT); + + // Restore settings + SelectObject(dcImage,pOldBitmapImage); + SelectObject(dcTrans,pOldBitmapTrans); + SetBkColor(hdc,crOldBack); + DeleteObject( bitmapTrans ); // RG 29/01/2002 + DeleteDC(dcImage); + DeleteDC(dcTrans); + DeleteObject(bm); + } + } + ::RestoreDC(hdc,hdc_Restore); + return 1; +} +//////////////////////////////////////////////////////////////////////////////// +int32_t CxImage::Stretch(HDC hdc, const RECT& rect, uint32_t dwRop) +{ + return Stretch(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, dwRop); +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Stretch the image. Obsolete: use Draw() or Draw2() + * \param hdc : destination device context + * \param xoffset,yoffset : (optional) offset + * \param xsize,ysize : size. + * \param dwRop : raster operation code (see BitBlt documentation) + * \return true if everything is ok + */ +int32_t CxImage::Stretch(HDC hdc, int32_t xoffset, int32_t yoffset, int32_t xsize, int32_t ysize, uint32_t dwRop) +{ + if((pDib)&&(hdc)) { + //palette must be correctly filled +#if !defined (_WIN32_WCE) + SetStretchBltMode(hdc,COLORONCOLOR); +#endif + StretchDIBits(hdc, xoffset, yoffset, + xsize, ysize, 0, 0, head.biWidth, head.biHeight, + info.pImage,(BITMAPINFO*)pDib,DIB_RGB_COLORS,dwRop); + return 1; + } + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +/** + * Tiles the device context in the specified rectangle with the image. + * \param hdc : destination device context + * \param rc : tiled rectangle in the output device context + * \return true if everything is ok + */ +int32_t CxImage::Tile(HDC hdc, RECT *rc) +{ + if((pDib)&&(hdc)&&(rc)) { + int32_t w = rc->right - rc->left; + int32_t h = rc->bottom - rc->top; + int32_t x,y,z; + int32_t bx=head.biWidth; + int32_t by=head.biHeight; + for (y = 0 ; y < h ; y += by){ + if ((y+by)>h) by=h-y; + z=bx; + for (x = 0 ; x < w ; x += z){ + if ((x+z)>w) z=w-x; + RECT r = {rc->left + x,rc->top + y,rc->left + x + z,rc->top + y + by}; + Draw(hdc,rc->left + x, rc->top + y,-1,-1,&r); + } + } + return 1; + } + return 0; +} +//////////////////////////////////////////////////////////////////////////////// +// For UNICODE support: char -> TCHAR +int32_t CxImage::DrawString(HDC hdc, int32_t x, int32_t y, const TCHAR* text, RGBQUAD color, const TCHAR* font, int32_t lSize, int32_t lWeight, uint8_t bItalic, uint8_t bUnderline, bool bSetAlpha) +//int32_t CxImage::DrawString(HDC hdc, int32_t x, int32_t y, const char* text, RGBQUAD color, const char* font, int32_t lSize, int32_t lWeight, uint8_t bItalic, uint8_t bUnderline, bool bSetAlpha) +{ + if (IsValid()){ + //get the background + HDC pDC; + if (hdc) pDC=hdc; else pDC = ::GetDC(0); + if (pDC==NULL) return 0; + HDC TmpDC=CreateCompatibleDC(pDC); + if (hdc==NULL) ::ReleaseDC(0, pDC); + if (TmpDC==NULL) return 0; + //choose the font + HFONT m_Font; + LOGFONT* m_pLF; + m_pLF=(LOGFONT*)calloc(1,sizeof(LOGFONT)); + _tcsncpy(m_pLF->lfFaceName,font,31); // For UNICODE support + //strncpy(m_pLF->lfFaceName,font,31); + m_pLF->lfHeight=lSize; + m_pLF->lfWeight=lWeight; + m_pLF->lfItalic=bItalic; + m_pLF->lfUnderline=bUnderline; + m_Font=CreateFontIndirect(m_pLF); + //select the font in the dc + HFONT pOldFont=NULL; + if (m_Font) + pOldFont = (HFONT)SelectObject(TmpDC,m_Font); + else + pOldFont = (HFONT)SelectObject(TmpDC,GetStockObject(DEFAULT_GUI_FONT)); + + //Set text color + SetTextColor(TmpDC,RGB(255,255,255)); + SetBkColor(TmpDC,RGB(0,0,0)); + //draw the text + SetBkMode(TmpDC,OPAQUE); + //Set text position; + RECT pos = {0,0,0,0}; + //int32_t len = (int32_t)strlen(text); + int32_t len = (int32_t)_tcslen(text); // For UNICODE support + ::DrawText(TmpDC,text,len,&pos,DT_CALCRECT); + pos.right+=pos.bottom; //for italics + + //Preparing Bitmap Info + int32_t width=pos.right; + int32_t height=pos.bottom; + BITMAPINFO bmInfo; + memset(&bmInfo.bmiHeader,0,sizeof(BITMAPINFOHEADER)); + bmInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); + bmInfo.bmiHeader.biWidth=width; + bmInfo.bmiHeader.biHeight=height; + bmInfo.bmiHeader.biPlanes=1; + bmInfo.bmiHeader.biBitCount=24; + uint8_t *pbase; //points to the final dib + + HBITMAP TmpBmp=CreateDIBSection(TmpDC,&bmInfo,DIB_RGB_COLORS,(void**)&pbase,0,0); + HGDIOBJ TmpObj=SelectObject(TmpDC,TmpBmp); + memset(pbase,0,height*((((24 * width) + 31) / 32) * 4)); + + ::DrawText(TmpDC,text,len,&pos,0); + + CxImage itext; + itext.CreateFromHBITMAP(TmpBmp); + + y=head.biHeight-y-1; + for (int32_t ix=0;ix +int32_t CxImage::DrawStringEx(HDC hdc, int32_t x, int32_t y, CXTEXTINFO *pTextType, bool bSetAlpha ) +{ + if (!IsValid()) + return -1; + + //get the background + HDC pDC; + if (hdc) pDC=hdc; else pDC = ::GetDC(0); + if (pDC==NULL) return 0; + HDC TmpDC=CreateCompatibleDC(pDC); + if (hdc==NULL) ::ReleaseDC(0, pDC); + if (TmpDC==NULL) return 0; + + //choose the font + HFONT m_Font; + m_Font=CreateFontIndirect( &pTextType->lfont ); + + // get colors in RGBQUAD + RGBQUAD p_forecolor = RGBtoRGBQUAD(pTextType->fcolor); + RGBQUAD p_backcolor = RGBtoRGBQUAD(pTextType->bcolor); + + // check alignment and re-set default if necessary + if ( pTextType->align != DT_CENTER && + pTextType->align != DT_LEFT && + pTextType->align != DT_RIGHT ) + pTextType->align = DT_CENTER; + + // check rounding radius and re-set default if necessary + if ( pTextType->b_round > 50 ) + pTextType->b_round = 10; + + // check opacity and re-set default if necessary + if ( pTextType->b_opacity > 1. || pTextType->b_opacity < .0 ) + pTextType->b_opacity = 0.; + + //select the font in the dc + HFONT pOldFont=NULL; + if (m_Font) + pOldFont = (HFONT)SelectObject(TmpDC,m_Font); + else + pOldFont = (HFONT)SelectObject(TmpDC,GetStockObject(DEFAULT_GUI_FONT)); + + //Set text color + SetTextColor(TmpDC,RGB(255,255,255)); + SetBkColor(TmpDC,RGB(0,0,0)); + SetBkMode(TmpDC,OPAQUE); + //Set text position; + RECT pos = {0,0,0,0}; + + // get text length and number of lines + int32_t i=0, numlines=1, len=(int32_t)_tcsclen(pTextType->text); + while (itext[i++]==13 ) + numlines++; + } + + ::DrawText(TmpDC, pTextType->text, len, &pos, /*DT_EDITCONTROL|DT_EXTERNALLEADING|*/DT_NOPREFIX | DT_CALCRECT ); + + // increase only if it's really italics, and only one line height + if ( pTextType->lfont.lfItalic ) + pos.right += pos.bottom/2/numlines; + + // background frame and rounding radius + int32_t frame = 0, roundR = 0; + if ( pTextType->opaque ) + { + roundR= (int32_t)(pos.bottom/numlines * pTextType->b_round / 100 ) ; + frame = (int32_t)(/*3.5 + */0.29289*roundR ) ; + pos.right += pos.bottom/numlines/3 ; // JUST FOR BEAUTY + } + + //Preparing Bitmap Info + int32_t width=pos.right +frame*2; + int32_t height=pos.bottom +frame*2; + BITMAPINFO bmInfo; + memset(&bmInfo.bmiHeader,0,sizeof(BITMAPINFOHEADER)); + bmInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); + bmInfo.bmiHeader.biWidth=width; + bmInfo.bmiHeader.biHeight=height; + bmInfo.bmiHeader.biPlanes=1; + bmInfo.bmiHeader.biBitCount=24; + uint8_t *pbase; //points to the final dib + + HBITMAP TmpBmp=CreateDIBSection(TmpDC,&bmInfo,DIB_RGB_COLORS,(void**)&pbase,0,0); + HGDIOBJ TmpObj=SelectObject(TmpDC,TmpBmp); + memset(pbase,0,height*((((24 * width) + 31) / 32) * 4)); + + ::DrawText(TmpDC,pTextType->text,len, &pos, /*DT_EDITCONTROL|DT_EXTERNALLEADING|*/DT_NOPREFIX| pTextType->align ); + + CxImage itext; + itext.CreateFromHBITMAP(TmpBmp); + y=head.biHeight-y-1; + + itext.Negative(); + +#if CXIMAGE_SUPPORT_DSP + if (pTextType->smooth==FALSE){ + itext.Threshold(128); + } else { + //itext.TextBlur(); + } +#endif + + //move the insertion point according to alignment type + // DT_CENTER: cursor points to the center of text rectangle + // DT_RIGHT: cursor points to right side end of text rectangle + // DT_LEFT: cursor points to left end of text rectangle + if ( pTextType->align == DT_CENTER ) + x -= width/2; + else if ( pTextType->align == DT_RIGHT ) + x -= width; + if (x<0) x=0; + + //draw the background first, if it exists + int32_t ix,iy; + if ( pTextType->opaque ) + { + int32_t ixf=0; + for (ix=0;ix=width-roundR-1 ) + ixf = (int32_t)(.5+roundR-sqrt((float)(roundR*roundR-(width-1-ix-roundR)*(width-1-ix-roundR)))); + else + ixf=0; + + for (iy=0;iy height-ixf-1 || iy < ixf )) || + (ix>=width-roundR-1 && ( iy > height-ixf-1 || iy < ixf )) ) + continue; + else + if ( pTextType->b_opacity > 0.0 && pTextType->b_opacity < 1.0 ) + { + RGBQUAD bcolor, pcolor; + // calculate a transition color from original image to background color: + pcolor = GetPixelColor(x+ix,y+iy); + bcolor.rgbBlue = (uint8_t)(pTextType->b_opacity * pcolor.rgbBlue + (1.0-pTextType->b_opacity) * p_backcolor.rgbBlue ); + bcolor.rgbRed = (uint8_t)(pTextType->b_opacity * pcolor.rgbRed + (1.0-pTextType->b_opacity) * p_backcolor.rgbRed ) ; + bcolor.rgbGreen = (uint8_t)(pTextType->b_opacity * pcolor.rgbGreen + (1.0-pTextType->b_opacity) * p_backcolor.rgbGreen ) ; + bcolor.rgbReserved = 0; + SetPixelColor(x+ix,y+iy,bcolor,bSetAlpha); + } + else + SetPixelColor(x+ix,y+iy,p_backcolor,bSetAlpha); + } + } + } + + // draw the text itself + for (ix=0;ixlfont.lfHeight = -36; + txt->lfont.lfCharSet = EASTEUROPE_CHARSET; // just for Central-European users + txt->lfont.lfWeight = FW_NORMAL; + txt->lfont.lfWidth = 0; + txt->lfont.lfEscapement = 0; + txt->lfont.lfOrientation = 0; + txt->lfont.lfItalic = FALSE; + txt->lfont.lfUnderline = FALSE; + txt->lfont.lfStrikeOut = FALSE; + txt->lfont.lfOutPrecision = OUT_DEFAULT_PRECIS; + txt->lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + txt->lfont.lfQuality = PROOF_QUALITY; + txt->lfont.lfPitchAndFamily= DEFAULT_PITCH | FF_DONTCARE ; + _stprintf( txt->lfont.lfFaceName, _T("Arial")); //use TCHAR mappings + + // initial colors + txt->fcolor = RGB( 255,255,160 ); // default foreground: light goldyellow + txt->bcolor = RGB( 0, 80,160 ); // default background: light blue + + // background + txt->opaque = TRUE; // text has a non-transparent background; + txt->smooth = TRUE; + txt->b_opacity = 0.0; // default: opaque background + txt->b_outline = 0; // default: no outline (OUTLINE NOT IMPLEMENTED AT THIS TIME) + txt->b_round = 20; // default: rounding radius is 20% of the rectangle height + // the text + _stprintf( txt->text, _T("Sample Text 01234")); // text use TCHAR mappings + txt->align = DT_CENTER; + return; +} + +#if CXIMAGE_SUPPORT_LAYERS +//////////////////////////////////////////////////////////////////////////////// +int32_t CxImage::LayerDrawAll(HDC hdc, const RECT& rect, RECT* pClipRect, bool bSmooth) +{ + return LayerDrawAll(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, pClipRect,bSmooth); +} +//////////////////////////////////////////////////////////////////////////////// +int32_t CxImage::LayerDrawAll(HDC hdc, int32_t x, int32_t y, int32_t cx, int32_t cy, RECT* pClipRect, bool bSmooth) +{ + int32_t n=0; + CxImage* pLayer; + while(pLayer=GetLayer(n++)){ + if (pLayer->Draw(hdc,x+pLayer->info.xOffset,y+pLayer->info.yOffset,cx,cy,pClipRect,bSmooth)==0) + return 0; + if (pLayer->LayerDrawAll(hdc,x+pLayer->info.xOffset,y+pLayer->info.yOffset,cx,cy,pClipRect,bSmooth)==0) + return 0; + } + return 1; +} +#endif //CXIMAGE_SUPPORT_LAYERS + +//////////////////////////////////////////////////////////////////////////////// +#endif //CXIMAGE_SUPPORT_WINDOWS +//////////////////////////////////////////////////////////////////////////////// diff --git a/DuiLib/3rd/CxImage/xiofile.h b/DuiLib/3rd/CxImage/xiofile.h new file mode 100644 index 0000000..faceeb2 --- /dev/null +++ b/DuiLib/3rd/CxImage/xiofile.h @@ -0,0 +1,125 @@ +#if !defined(__xiofile_h) +#define __xiofile_h + +#include "xfile.h" +//#include + +class DLL_EXP CxIOFile : public CxFile + { +public: + CxIOFile(FILE* fp = NULL) + { + m_fp = fp; + m_bCloseFile = (bool)(fp==0); + } + + ~CxIOFile() + { + Close(); + } +////////////////////////////////////////////////////////// + bool Open(const TCHAR * filename, const TCHAR * mode) + { + if (m_fp) return false; // Can't re-open without closing first + + m_fp = _tfopen(filename, mode); + if (!m_fp) return false; + + m_bCloseFile = true; + + return true; + } +////////////////////////////////////////////////////////// + virtual bool Close() + { + int32_t iErr = 0; + if ( (m_fp) && (m_bCloseFile) ){ + iErr = fclose(m_fp); + m_fp = NULL; + } + return (bool)(iErr==0); + } +////////////////////////////////////////////////////////// + virtual size_t Read(void *buffer, size_t size, size_t count) + { + if (!m_fp) return 0; + return fread(buffer, size, count, m_fp); + } +////////////////////////////////////////////////////////// + virtual size_t Write(const void *buffer, size_t size, size_t count) + { + if (!m_fp) return 0; + return fwrite(buffer, size, count, m_fp); + } +////////////////////////////////////////////////////////// + virtual bool Seek(int32_t offset, int32_t origin) + { + if (!m_fp) return false; + return (bool)(fseek(m_fp, offset, origin) == 0); + } +////////////////////////////////////////////////////////// + virtual int32_t Tell() + { + if (!m_fp) return 0; + return ftell(m_fp); + } +////////////////////////////////////////////////////////// + virtual int32_t Size() + { + if (!m_fp) return -1; + int32_t pos,size; + pos = ftell(m_fp); + fseek(m_fp, 0, SEEK_END); + size = ftell(m_fp); + fseek(m_fp, pos,SEEK_SET); + return size; + } +////////////////////////////////////////////////////////// + virtual bool Flush() + { + if (!m_fp) return false; + return (bool)(fflush(m_fp) == 0); + } +////////////////////////////////////////////////////////// + virtual bool Eof() + { + if (!m_fp) return true; + return (bool)(feof(m_fp) != 0); + } +////////////////////////////////////////////////////////// + virtual int32_t Error() + { + if (!m_fp) return -1; + return ferror(m_fp); + } +////////////////////////////////////////////////////////// + virtual bool PutC(uint8_t c) + { + if (!m_fp) return false; + return (bool)(fputc(c, m_fp) == c); + } +////////////////////////////////////////////////////////// + virtual int32_t GetC() + { + if (!m_fp) return EOF; + return getc(m_fp); + } +////////////////////////////////////////////////////////// + virtual char * GetS(char *string, int32_t n) + { + if (!m_fp) return NULL; + return fgets(string,n,m_fp); + } +////////////////////////////////////////////////////////// + virtual int32_t Scanf(const char *format, void* output) + { + if (!m_fp) return EOF; + return fscanf(m_fp, format, output); + } +////////////////////////////////////////////////////////// +protected: + FILE *m_fp; + bool m_bCloseFile; + }; + +#endif diff --git a/DuiLib/3rd/CxImage/xmemfile.cpp b/DuiLib/3rd/CxImage/xmemfile.cpp new file mode 100644 index 0000000..f64028c --- /dev/null +++ b/DuiLib/3rd/CxImage/xmemfile.cpp @@ -0,0 +1,213 @@ +#include "xmemfile.h" + +////////////////////////////////////////////////////////// +CxMemFile::CxMemFile(uint8_t* pBuffer, uint32_t size) +{ + m_pBuffer = pBuffer; + m_Position = 0; + m_Size = m_Edge = size; + m_bFreeOnClose = (bool)(pBuffer==0); + m_bEOF = false; +} +////////////////////////////////////////////////////////// +CxMemFile::~CxMemFile() +{ + Close(); +} +////////////////////////////////////////////////////////// +bool CxMemFile::Close() +{ + if ( (m_pBuffer) && (m_bFreeOnClose) ){ + free(m_pBuffer); + m_pBuffer = NULL; + m_Size = 0; + } + return true; +} +////////////////////////////////////////////////////////// +bool CxMemFile::Open() +{ + if (m_pBuffer) return false; // Can't re-open without closing first + + m_Position = m_Size = m_Edge = 0; + m_pBuffer=(uint8_t*)malloc(1); + m_bFreeOnClose = true; + + return (m_pBuffer!=0); +} +////////////////////////////////////////////////////////// +uint8_t* CxMemFile::GetBuffer(bool bDetachBuffer) +{ + //can only detach, avoid inadvertantly attaching to + // memory that may not be ours [Jason De Arte] + if( bDetachBuffer ) + m_bFreeOnClose = false; + return m_pBuffer; +} +////////////////////////////////////////////////////////// +size_t CxMemFile::Read(void *buffer, size_t size, size_t count) +{ + if (buffer==NULL) return 0; + + if (m_pBuffer==NULL) return 0; + if (m_Position >= (int32_t)m_Size){ + m_bEOF = true; + return 0; + } + + int32_t nCount = (int32_t)(count*size); + if (nCount == 0) return 0; + + int32_t nRead; + if (m_Position + nCount > (int32_t)m_Size){ + m_bEOF = true; + nRead = (m_Size - m_Position); + } else + nRead = nCount; + + memcpy(buffer, m_pBuffer + m_Position, nRead); + m_Position += nRead; + + return (size_t)(nRead/size); +} +////////////////////////////////////////////////////////// +size_t CxMemFile::Write(const void *buffer, size_t size, size_t count) +{ + m_bEOF = false; + if (m_pBuffer==NULL) return 0; + if (buffer==NULL) return 0; + + int32_t nCount = (int32_t)(count*size); + if (nCount == 0) return 0; + + if (m_Position + nCount > m_Edge){ + if (!Alloc(m_Position + nCount)){ + return false; + } + } + + memcpy(m_pBuffer + m_Position, buffer, nCount); + + m_Position += nCount; + + if (m_Position > (int32_t)m_Size) m_Size = m_Position; + + return count; +} +////////////////////////////////////////////////////////// +bool CxMemFile::Seek(int32_t offset, int32_t origin) +{ + m_bEOF = false; + if (m_pBuffer==NULL) return false; + int32_t lNewPos = m_Position; + + if (origin == SEEK_SET) lNewPos = offset; + else if (origin == SEEK_CUR) lNewPos += offset; + else if (origin == SEEK_END) lNewPos = m_Size + offset; + else return false; + + if (lNewPos < 0) lNewPos = 0; + + m_Position = lNewPos; + return true; +} +////////////////////////////////////////////////////////// +int32_t CxMemFile::Tell() +{ + if (m_pBuffer==NULL) return -1; + return m_Position; +} +////////////////////////////////////////////////////////// +int32_t CxMemFile::Size() +{ + if (m_pBuffer==NULL) return -1; + return m_Size; +} +////////////////////////////////////////////////////////// +bool CxMemFile::Flush() +{ + if (m_pBuffer==NULL) return false; + return true; +} +////////////////////////////////////////////////////////// +bool CxMemFile::Eof() +{ + if (m_pBuffer==NULL) return true; + return m_bEOF; +} +////////////////////////////////////////////////////////// +int32_t CxMemFile::Error() +{ + if (m_pBuffer==NULL) return -1; + return (m_Position > (int32_t)m_Size); +} +////////////////////////////////////////////////////////// +bool CxMemFile::PutC(uint8_t c) +{ + m_bEOF = false; + if (m_pBuffer==NULL) return false; + + if (m_Position >= m_Edge){ + if (!Alloc(m_Position + 1)){ + return false; + } + } + + m_pBuffer[m_Position++] = c; + + if (m_Position > (int32_t)m_Size) m_Size = m_Position; + + return true; +} +////////////////////////////////////////////////////////// +int32_t CxMemFile::GetC() +{ + if (m_pBuffer==NULL || m_Position >= (int32_t)m_Size){ + m_bEOF = true; + return EOF; + } + return *(uint8_t*)((uint8_t*)m_pBuffer + m_Position++); +} +////////////////////////////////////////////////////////// +char * CxMemFile::GetS(char *string, int32_t n) +{ + n--; + int32_t c,i=0; + while (i (uint32_t)m_Edge) + { + // find new buffer size + uint32_t dwNewBufferSize = (uint32_t)(((dwNewLen>>16)+1)<<16); + + // allocate new buffer + if (m_pBuffer == NULL) m_pBuffer = (uint8_t*)malloc(dwNewBufferSize); + else m_pBuffer = (uint8_t*)realloc(m_pBuffer, dwNewBufferSize); + // I own this buffer now (caller knows nothing about it) + m_bFreeOnClose = true; + + m_Edge = dwNewBufferSize; + } + return (m_pBuffer!=0); +} +////////////////////////////////////////////////////////// +void CxMemFile::Free() +{ + Close(); +} +////////////////////////////////////////////////////////// diff --git a/DuiLib/3rd/CxImage/xmemfile.h b/DuiLib/3rd/CxImage/xmemfile.h new file mode 100644 index 0000000..4c4e843 --- /dev/null +++ b/DuiLib/3rd/CxImage/xmemfile.h @@ -0,0 +1,42 @@ +#if !defined(__xmemfile_h) +#define __xmemfile_h + +#include "xfile.h" + +////////////////////////////////////////////////////////// +class DLL_EXP CxMemFile : public CxFile +{ +public: + CxMemFile(uint8_t* pBuffer = NULL, uint32_t size = 0); + ~CxMemFile(); + + bool Open(); + uint8_t* GetBuffer(bool bDetachBuffer = true); + + virtual bool Close(); + virtual size_t Read(void *buffer, size_t size, size_t count); + virtual size_t Write(const void *buffer, size_t size, size_t count); + virtual bool Seek(int32_t offset, int32_t origin); + virtual int32_t Tell(); + virtual int32_t Size(); + virtual bool Flush(); + virtual bool Eof(); + virtual int32_t Error(); + virtual bool PutC(uint8_t c); + virtual int32_t GetC(); + virtual char * GetS(char *string, int32_t n); + virtual int32_t Scanf(const char *format, void* output); + +protected: + bool Alloc(uint32_t nBytes); + void Free(); + + uint8_t* m_pBuffer; + uint32_t m_Size; + bool m_bFreeOnClose; + int32_t m_Position; //current position + int32_t m_Edge; //buffer size + bool m_bEOF; +}; + +#endif diff --git a/DuiLib/Bind/BindBase.h b/DuiLib/Bind/BindBase.h new file mode 100644 index 0000000..d419fe8 --- /dev/null +++ b/DuiLib/Bind/BindBase.h @@ -0,0 +1,45 @@ +#ifndef __BIND_BASE_H__ +#define __BIND_BASE_H__ + +#pragma once + + + +namespace DuiLib { + class BindCtrlBase { + //friend class WindowImplBase; + public: + BindCtrlBase (string_view_t ctrl_name): m_ctrl_name (ctrl_name) { /*s_bind_ctrls[m_ctrl_name.data ()] = this;*/ } + virtual ~BindCtrlBase () {} + + protected: + virtual string_view_t GetClassType () const = 0; + virtual void binded () {} + CControlUI *m_ctrl = nullptr; + string_view_t m_ctrl_name; + + //static CPaintManagerUI *s_pm; + private: + //static std::map s_bind_ctrls; + //static void init_binding (CPaintManagerUI *pm); + }; + + + + //template + //class BindVarBase: public BindCtrlBase { + //public: + // BindVarBase (string_view_t ctrl_name): BindCtrlBase (ctrl_name) {} + // virtual ~BindVarBase () = default; + // string_view_t GetVarType () const; + + // // ʺδɣ + // //void operator= (T &o); + // //T &operator() (); + + //protected: + // void binded () override; + //}; +} + +#endif //__BIND_BASE_H__ diff --git a/DuiLib/Bind/BindCtrls.hpp b/DuiLib/Bind/BindCtrls.hpp new file mode 100644 index 0000000..4cf805e --- /dev/null +++ b/DuiLib/Bind/BindCtrls.hpp @@ -0,0 +1,95 @@ +#ifndef __BIND_CTRLS_HPP__ +#define __BIND_CTRLS_HPP__ + +#pragma once + +namespace DuiLib { +#define DEF_BINDCTRL(CTRL_TYPE) \ + class Bind##CTRL_TYPE##UI: public BindCtrlBase { \ + public: \ + Bind##CTRL_TYPE##UI (string_view_t ctrl_name, CPaintManagerUI *pm = nullptr): BindCtrlBase (ctrl_name), m_pm (pm) {} \ + C##CTRL_TYPE##UI *operator* () noexcept { \ + if (!m_ctrl) { \ + if (!m_pm) { \ + if (!(m_pm = CPaintManagerUI::GetPaintManager (_T ("")))) \ + ASSERT (false); \ + } \ + m_ctrl = m_pm->FindControl (m_ctrl_name); \ + } \ + return static_cast (m_ctrl); \ + } \ + C##CTRL_TYPE##UI *operator-> () noexcept { return operator* (); } \ + protected: \ + string_view_t GetClassType () const override { return _T (#CTRL_TYPE##"UI"); } \ + CPaintManagerUI *m_pm = nullptr; \ + } + + + + // Core + DEF_BINDCTRL (Control); + //class BindControlUI: public BindCtrlBase { + //public: + // BindControlUI (string_view_t ctrl_name, CPaintManagerUI *pm = nullptr): BindCtrlBase (ctrl_name), m_pm (pm) {} + // CControlUI *operator* () noexcept { + // if (!m_ctrl) { + // if (!m_pm) { + // if (!(m_pm = CPaintManagerUI::GetPaintManager (_T ("")))) + // ASSERT (false); + // } + // m_ctrl = m_pm->FindControl (m_ctrl_name); + // } + // return static_cast (m_ctrl); + // } + // CControlUI *operator-> () noexcept { return operator* (); } + //protected: + // string_view_t GetClassType () const override { return _T ("ControlUI"); } + // CPaintManagerUI *m_pm = nullptr; + //}; + DEF_BINDCTRL (Container); + + // Control + DEF_BINDCTRL (ActiveX); + //DEF_BINDCTRL (Animation); + DEF_BINDCTRL (Button); + DEF_BINDCTRL (ColorPalette); + DEF_BINDCTRL (Combo); + DEF_BINDCTRL (ComboBox); + DEF_BINDCTRL (DateTime); + DEF_BINDCTRL (Edit); + DEF_BINDCTRL (FadeButton); + DEF_BINDCTRL (Flash); + DEF_BINDCTRL (GifAnim); +#ifdef USE_XIMAGE_EFFECT + DEF_BINDCTRL (GifAnimEx); +#endif + DEF_BINDCTRL (GroupBox); + DEF_BINDCTRL (HotKey); + DEF_BINDCTRL (IPAddress); + DEF_BINDCTRL (IPAddressEx); + DEF_BINDCTRL (Label); + DEF_BINDCTRL (List); + DEF_BINDCTRL (ListEx); + DEF_BINDCTRL (Menu); + DEF_BINDCTRL (Option); + DEF_BINDCTRL (CheckBox); + DEF_BINDCTRL (Progress); + DEF_BINDCTRL (RichEdit); + DEF_BINDCTRL (Ring); + DEF_BINDCTRL (RollText); + DEF_BINDCTRL (ScrollBar); + DEF_BINDCTRL (Slider); + DEF_BINDCTRL (Text); + DEF_BINDCTRL (TreeView); + DEF_BINDCTRL (WebBrowser); + + // Layout + DEF_BINDCTRL (AnimationTabLayout); + DEF_BINDCTRL (ChildLayout); + DEF_BINDCTRL (HorizontalLayout); + DEF_BINDCTRL (TabLayout); + DEF_BINDCTRL (TileLayout); + DEF_BINDCTRL (VerticalLayout); +} + +#endif //__BIND_CTRLS_HPP__ diff --git a/DuiLib/Bind/StdAfx.h b/DuiLib/Bind/StdAfx.h new file mode 100644 index 0000000..c07aaba --- /dev/null +++ b/DuiLib/Bind/StdAfx.h @@ -0,0 +1 @@ +#include "../StdAfx.h" \ No newline at end of file diff --git a/DuiLib/Control/StdAfx.h b/DuiLib/Control/StdAfx.h new file mode 100644 index 0000000..c07aaba --- /dev/null +++ b/DuiLib/Control/StdAfx.h @@ -0,0 +1 @@ +#include "../StdAfx.h" \ No newline at end of file diff --git a/DuiLib/Control/UIActiveX.cpp b/DuiLib/Control/UIActiveX.cpp new file mode 100644 index 0000000..8778c11 --- /dev/null +++ b/DuiLib/Control/UIActiveX.cpp @@ -0,0 +1,1115 @@ +#include "StdAfx.h" + +namespace DuiLib { + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + class CActiveXCtrl; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + class CActiveXWnd: public CWindowWnd { + public: + CActiveXWnd (): m_iLayeredTick (0), m_bDrawCaret (false) {} + HWND Init (CActiveXCtrl* pOwner, HWND hWndParent); + + string_view_t GetWindowClassName () const; + void OnFinalMessage (HWND hWnd); + + LRESULT HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam); + + protected: + void DoVerb (LONG iVerb); + + LRESULT OnCreate (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnTimer (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnMouseActivate (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnSetFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnKillFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnEraseBkgnd (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnPaint (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnPrint (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + + protected: + enum { + DEFAULT_TIMERID = 20, + }; + + CActiveXCtrl* m_pOwner; + int m_iLayeredTick; + bool m_bDrawCaret; + }; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + class CActiveXEnum: public IEnumUnknown { + public: + CActiveXEnum (IUnknown* pUnk): m_pUnk (pUnk), m_dwRef (1), m_iPos (0) { + m_pUnk->AddRef (); + } + virtual ~CActiveXEnum () { + m_pUnk->Release (); + } + + LONG m_iPos; + ULONG m_dwRef; + IUnknown* m_pUnk; + + STDMETHOD_ (ULONG, AddRef)() { + return ++m_dwRef; + } + STDMETHOD_ (ULONG, Release)() { + LONG lRef = --m_dwRef; + if (lRef == 0) delete this; + return lRef; + } + STDMETHOD (QueryInterface)(REFIID riid, LPVOID *ppvObject) { + *ppvObject = nullptr; + if (riid == IID_IUnknown) *ppvObject = static_cast(this); + else if (riid == IID_IEnumUnknown) *ppvObject = static_cast(this); + if (*ppvObject) AddRef (); + return (!*ppvObject) ? E_NOINTERFACE : S_OK; + } + STDMETHOD (Next)(ULONG celt, IUnknown **rgelt, ULONG *pceltFetched) { + if (pceltFetched) *pceltFetched = 0; + if (++m_iPos > 1) return S_FALSE; + *rgelt = m_pUnk; + (*rgelt)->AddRef (); + if (pceltFetched) *pceltFetched = 1; + return S_OK; + } + STDMETHOD (Skip)(ULONG celt) { + m_iPos += celt; + return S_OK; + } + STDMETHOD (Reset)(void) { + m_iPos = 0; + return S_OK; + } + STDMETHOD (Clone)(IEnumUnknown **ppenum) { + return E_NOTIMPL; + } + }; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + class CActiveXFrameWnd: public IOleInPlaceFrame { + public: + CActiveXFrameWnd (CActiveXUI* pOwner): m_dwRef (1), m_pOwner (pOwner), m_pActiveObject (nullptr) {} + virtual ~CActiveXFrameWnd () { + if (m_pActiveObject) m_pActiveObject->Release (); + } + + ULONG m_dwRef; + CActiveXUI* m_pOwner; + IOleInPlaceActiveObject* m_pActiveObject; + + // IUnknown + STDMETHOD_ (ULONG, AddRef)() { + return ++m_dwRef; + } + STDMETHOD_ (ULONG, Release)() { + ULONG lRef = --m_dwRef; + if (lRef == 0) delete this; + return lRef; + } + STDMETHOD (QueryInterface)(REFIID riid, LPVOID *ppvObject) { + *ppvObject = nullptr; + if (riid == IID_IUnknown) *ppvObject = static_cast(this); + else if (riid == IID_IOleWindow) *ppvObject = static_cast(this); + else if (riid == IID_IOleInPlaceFrame) *ppvObject = static_cast(this); + else if (riid == IID_IOleInPlaceUIWindow) *ppvObject = static_cast(this); + if (*ppvObject) AddRef (); + return (!*ppvObject) ? E_NOINTERFACE : S_OK; + } + // IOleInPlaceFrameWindow + STDMETHOD (InsertMenus)(HMENU /*hmenuShared*/, LPOLEMENUGROUPWIDTHS /*lpMenuWidths*/) { + return S_OK; + } + STDMETHOD (SetMenu)(HMENU /*hmenuShared*/, HOLEMENU /*holemenu*/, HWND /*hwndActiveObject*/) { + return S_OK; + } + STDMETHOD (RemoveMenus)(HMENU /*hmenuShared*/) { + return S_OK; + } + STDMETHOD (SetStatusText)(LPCOLESTR /*pszStatusText*/) { + return S_OK; + } + STDMETHOD (EnableModeless)(BOOL /*fEnable*/) { + return S_OK; + } + STDMETHOD (TranslateAccelerator)(LPMSG /*lpMsg*/, WORD /*wID*/) { + return S_FALSE; + } + // IOleWindow + STDMETHOD (GetWindow)(HWND* phwnd) { + if (!m_pOwner) return E_UNEXPECTED; + *phwnd = m_pOwner->GetManager ()->GetPaintWindow (); + return S_OK; + } + STDMETHOD (ContextSensitiveHelp)(BOOL /*fEnterMode*/) { + return S_OK; + } + // IOleInPlaceUIWindow + STDMETHOD (GetBorder)(LPRECT /*lprectBorder*/) { + return S_OK; + } + STDMETHOD (RequestBorderSpace)(LPCBORDERWIDTHS /*pborderwidths*/) { + return INPLACE_E_NOTOOLSPACE; + } + STDMETHOD (SetBorderSpace)(LPCBORDERWIDTHS /*pborderwidths*/) { + return S_OK; + } + STDMETHOD (SetActiveObject)(IOleInPlaceActiveObject* pActiveObject, LPCOLESTR /*pszObjName*/) { + if (pActiveObject) pActiveObject->AddRef (); + if (m_pActiveObject) m_pActiveObject->Release (); + m_pActiveObject = pActiveObject; + return S_OK; + } + }; + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class CActiveXCtrl: + public IOleClientSite, + public IOleInPlaceSiteWindowless, + public IOleControlSite, + public IObjectWithSite, + public IOleContainer { + friend class CActiveXUI; + friend class CActiveXWnd; + public: + CActiveXCtrl (); + virtual ~CActiveXCtrl (); + + // IUnknown + STDMETHOD_ (ULONG, AddRef)(); + STDMETHOD_ (ULONG, Release)(); + STDMETHOD (QueryInterface)(REFIID riid, LPVOID *ppvObject); + + // IObjectWithSite + STDMETHOD (SetSite)(IUnknown *pUnkSite); + STDMETHOD (GetSite)(REFIID riid, LPVOID* ppvSite); + + // IOleClientSite + STDMETHOD (SaveObject)(void); + STDMETHOD (GetMoniker)(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker** ppmk); + STDMETHOD (GetContainer)(IOleContainer** ppContainer); + STDMETHOD (ShowObject)(void); + STDMETHOD (OnShowWindow)(BOOL fShow); + STDMETHOD (RequestNewObjectLayout)(void); + + // IOleInPlaceSiteWindowless + STDMETHOD (CanWindowlessActivate)(void); + STDMETHOD (GetCapture)(void); + STDMETHOD (SetCapture)(BOOL fCapture); + STDMETHOD (GetFocus)(void); + STDMETHOD (SetFocus)(BOOL fFocus); + STDMETHOD (GetDC)(LPCRECT pRect, DWORD grfFlags, HDC* phDC); + STDMETHOD (ReleaseDC)(HDC hDC); + STDMETHOD (InvalidateRect)(LPCRECT pRect, BOOL fErase); + STDMETHOD (InvalidateRgn)(HRGN hRGN, BOOL fErase); + STDMETHOD (ScrollRect)(INT dx, INT dy, LPCRECT pRectScroll, LPCRECT pRectClip); + STDMETHOD (AdjustRect)(LPRECT prc); + STDMETHOD (OnDefWindowMessage)(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT* plResult); + + // IOleInPlaceSiteEx + STDMETHOD (OnInPlaceActivateEx)(BOOL *pfNoRedraw, DWORD dwFlags); + STDMETHOD (OnInPlaceDeactivateEx)(BOOL fNoRedraw); + STDMETHOD (RequestUIActivate)(void); + + // IOleInPlaceSite + STDMETHOD (CanInPlaceActivate)(void); + STDMETHOD (OnInPlaceActivate)(void); + STDMETHOD (OnUIActivate)(void); + STDMETHOD (GetWindowContext)(IOleInPlaceFrame** ppFrame, IOleInPlaceUIWindow** ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo); + STDMETHOD (Scroll)(SIZE scrollExtant); + STDMETHOD (OnUIDeactivate)(BOOL fUndoable); + STDMETHOD (OnInPlaceDeactivate)(void); + STDMETHOD (DiscardUndoState)(void); + STDMETHOD (DeactivateAndUndo)(void); + STDMETHOD (OnPosRectChange)(LPCRECT lprcPosRect); + + // IOleWindow + STDMETHOD (GetWindow)(HWND* phwnd); + STDMETHOD (ContextSensitiveHelp)(BOOL fEnterMode); + + // IOleControlSite + STDMETHOD (OnControlInfoChanged)(void); + STDMETHOD (LockInPlaceActive)(BOOL fLock); + STDMETHOD (GetExtendedControl)(IDispatch** ppDisp); + STDMETHOD (TransformCoords)(POINTL* pPtlHimetric, POINTF* pPtfContainer, DWORD dwFlags); + STDMETHOD (TranslateAccelerator)(MSG* pMsg, DWORD grfModifiers); + STDMETHOD (OnFocus)(BOOL fGotFocus); + STDMETHOD (ShowPropertyFrame)(void); + + // IOleContainer + STDMETHOD (EnumObjects)(DWORD grfFlags, IEnumUnknown** ppenum); + STDMETHOD (LockContainer)(BOOL fLock); + + // IParseDisplayName + STDMETHOD (ParseDisplayName)(IBindCtx* pbc, LPOLESTR pszDisplayName, ULONG* pchEaten, IMoniker** ppmkOut); + + protected: + HRESULT CreateActiveXWnd (); + + protected: + LONG m_dwRef; + CActiveXUI* m_pOwner; + CActiveXWnd* m_pWindow; + IUnknown* m_pUnkSite; + IViewObject* m_pViewObject; + IOleInPlaceObjectWindowless* m_pInPlaceObject; + bool m_bLocked; + bool m_bFocused; + bool m_bCaptured; + bool m_bUIActivated; + bool m_bInPlaceActive; + bool m_bWindowless; + }; + + CActiveXCtrl::CActiveXCtrl (): m_pOwner (nullptr), m_pWindow (nullptr), m_pUnkSite (nullptr), m_pViewObject (nullptr), m_pInPlaceObject (nullptr) { + m_dwRef = 1; + m_bLocked = m_bFocused = m_bCaptured = m_bWindowless = m_bUIActivated = m_bInPlaceActive = false; + } + + CActiveXCtrl::~CActiveXCtrl () { + if (m_pWindow) { + ::DestroyWindow (m_pWindow->GetHWND ()); + delete m_pWindow; + } + if (m_pUnkSite) m_pUnkSite->Release (); + if (m_pViewObject) m_pViewObject->Release (); + if (m_pInPlaceObject) m_pInPlaceObject->Release (); + } + + STDMETHODIMP CActiveXCtrl::QueryInterface (REFIID riid, LPVOID *ppvObject) { + *ppvObject = nullptr; + if (riid == IID_IUnknown) *ppvObject = static_cast(this); + else if (riid == IID_IOleClientSite) *ppvObject = static_cast(this); + else if (riid == IID_IOleInPlaceSiteWindowless) *ppvObject = static_cast(this); + else if (riid == IID_IOleInPlaceSiteEx) *ppvObject = static_cast(this); + else if (riid == IID_IOleInPlaceSite) *ppvObject = static_cast(this); + else if (riid == IID_IOleWindow) *ppvObject = static_cast(this); + else if (riid == IID_IOleControlSite) *ppvObject = static_cast(this); + else if (riid == IID_IOleContainer) *ppvObject = static_cast(this); + else if (riid == IID_IObjectWithSite) *ppvObject = static_cast(this); + if (*ppvObject) AddRef (); + return (!*ppvObject) ? E_NOINTERFACE : S_OK; + } + + STDMETHODIMP_ (ULONG) CActiveXCtrl::AddRef () { + return ++m_dwRef; + } + + STDMETHODIMP_ (ULONG) CActiveXCtrl::Release () { + LONG lRef = --m_dwRef; + if (lRef == 0) delete this; + return lRef; + } + + STDMETHODIMP CActiveXCtrl::SetSite (IUnknown *pUnkSite) { + DUITRACE (_T ("AX: CActiveXCtrl::SetSite")); + if (m_pUnkSite) { + m_pUnkSite->Release (); + m_pUnkSite = nullptr; + } + if (pUnkSite) { + m_pUnkSite = pUnkSite; + m_pUnkSite->AddRef (); + } + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::GetSite (REFIID riid, LPVOID* ppvSite) { + DUITRACE (_T ("AX: CActiveXCtrl::GetSite")); + if (!ppvSite) return E_POINTER; + *ppvSite = nullptr; + if (!m_pUnkSite) return E_FAIL; + return m_pUnkSite->QueryInterface (riid, ppvSite); + } + + STDMETHODIMP CActiveXCtrl::SaveObject (void) { + DUITRACE (_T ("AX: CActiveXCtrl::SaveObject")); + return E_NOTIMPL; + } + + STDMETHODIMP CActiveXCtrl::GetMoniker (DWORD dwAssign, DWORD dwWhichMoniker, IMoniker** ppmk) { + DUITRACE (_T ("AX: CActiveXCtrl::GetMoniker")); + if (ppmk) *ppmk = nullptr; + return E_NOTIMPL; + } + + STDMETHODIMP CActiveXCtrl::GetContainer (IOleContainer** ppContainer) { + DUITRACE (_T ("AX: CActiveXCtrl::GetContainer")); + if (!ppContainer) return E_POINTER; + *ppContainer = nullptr; + HRESULT Hr = E_NOTIMPL; + if (m_pUnkSite) Hr = m_pUnkSite->QueryInterface (IID_IOleContainer, (LPVOID*) ppContainer); + if (FAILED (Hr)) Hr = QueryInterface (IID_IOleContainer, (LPVOID*) ppContainer); + return Hr; + } + + STDMETHODIMP CActiveXCtrl::ShowObject (void) { + DUITRACE (_T ("AX: CActiveXCtrl::ShowObject")); + if (!m_pOwner) return E_UNEXPECTED; + HDC hDC = ::GetDC (m_pOwner->m_hwndHost); + if (hDC == NULL) return E_FAIL; + if (m_pViewObject) m_pViewObject->Draw (DVASPECT_CONTENT, -1, nullptr, nullptr, NULL, hDC, (RECTL*) &m_pOwner->m_rcItem, (RECTL*) &m_pOwner->m_rcItem, nullptr, NULL); + ::ReleaseDC (m_pOwner->m_hwndHost, hDC); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::OnShowWindow (BOOL fShow) { + DUITRACE (_T ("AX: CActiveXCtrl::OnShowWindow")); + return E_NOTIMPL; + } + + STDMETHODIMP CActiveXCtrl::RequestNewObjectLayout (void) { + DUITRACE (_T ("AX: CActiveXCtrl::RequestNewObjectLayout")); + return E_NOTIMPL; + } + + STDMETHODIMP CActiveXCtrl::CanWindowlessActivate (void) { + DUITRACE (_T ("AX: CActiveXCtrl::CanWindowlessActivate")); + return S_OK; // Yes, we can!! + } + + STDMETHODIMP CActiveXCtrl::GetCapture (void) { + DUITRACE (_T ("AX: CActiveXCtrl::GetCapture")); + if (!m_pOwner) return E_UNEXPECTED; + return m_bCaptured ? S_OK : S_FALSE; + } + + STDMETHODIMP CActiveXCtrl::SetCapture (BOOL fCapture) { + DUITRACE (_T ("AX: CActiveXCtrl::SetCapture")); + if (!m_pOwner) return E_UNEXPECTED; + m_bCaptured = (fCapture == TRUE); + if (fCapture) ::SetCapture (m_pOwner->m_hwndHost); else ::ReleaseCapture (); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::GetFocus (void) { + DUITRACE (_T ("AX: CActiveXCtrl::GetFocus")); + if (!m_pOwner) return E_UNEXPECTED; + return m_bFocused ? S_OK : S_FALSE; + } + + STDMETHODIMP CActiveXCtrl::SetFocus (BOOL fFocus) { + DUITRACE (_T ("AX: CActiveXCtrl::SetFocus")); + if (!m_pOwner) return E_UNEXPECTED; + if (fFocus) m_pOwner->SetFocus (); + m_bFocused = (fFocus == TRUE); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::GetDC (LPCRECT pRect, DWORD grfFlags, HDC* phDC) { + DUITRACE (_T ("AX: CActiveXCtrl::GetDC")); + if (!phDC) return E_POINTER; + if (!m_pOwner) return E_UNEXPECTED; + if (m_bWindowless) return S_FALSE; + *phDC = ::GetDC (m_pOwner->m_hwndHost); + if ((grfFlags & OLEDC_PAINTBKGND) != 0) { + RECT rcItem = m_pOwner->GetPos (); + if (!m_bWindowless) ::OffsetRect (&rcItem, -rcItem.left, -rcItem.top); + ::FillRect (*phDC, &rcItem, (HBRUSH) (COLOR_WINDOW + 1)); + } + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::ReleaseDC (HDC hDC) { + DUITRACE (_T ("AX: CActiveXCtrl::ReleaseDC")); + if (!m_pOwner) return E_UNEXPECTED; + ::ReleaseDC (m_pOwner->m_hwndHost, hDC); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::InvalidateRect (LPCRECT pRect, BOOL fErase) { + DUITRACE (_T ("AX: CActiveXCtrl::InvalidateRect")); + if (!m_pOwner) return E_UNEXPECTED; + if (!m_pOwner->m_hwndHost) return E_FAIL; + return ::InvalidateRect (m_pOwner->m_hwndHost, pRect, fErase) ? S_OK : E_FAIL; + } + + STDMETHODIMP CActiveXCtrl::InvalidateRgn (HRGN hRGN, BOOL fErase) { + DUITRACE (_T ("AX: CActiveXCtrl::InvalidateRgn")); + if (!m_pOwner) return E_UNEXPECTED; + return ::InvalidateRgn (m_pOwner->m_hwndHost, hRGN, fErase) ? S_OK : E_FAIL; + } + + STDMETHODIMP CActiveXCtrl::ScrollRect (INT dx, INT dy, LPCRECT pRectScroll, LPCRECT pRectClip) { + DUITRACE (_T ("AX: CActiveXCtrl::ScrollRect")); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::AdjustRect (LPRECT prc) { + DUITRACE (_T ("AX: CActiveXCtrl::AdjustRect")); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::OnDefWindowMessage (UINT msg, WPARAM wParam, LPARAM lParam, LRESULT* plResult) { + DUITRACE (_T ("AX: CActiveXCtrl::OnDefWindowMessage")); + if (!m_pOwner) return E_UNEXPECTED; + *plResult = ::DefWindowProc (m_pOwner->m_hwndHost, msg, wParam, lParam); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::OnInPlaceActivateEx (BOOL* pfNoRedraw, DWORD dwFlags) { + DUITRACE (_T ("AX: CActiveXCtrl::OnInPlaceActivateEx")); + ASSERT (!m_pInPlaceObject); + if (!m_pOwner) return E_UNEXPECTED; + if (!m_pOwner->m_pUnk) return E_UNEXPECTED; + ::OleLockRunning (m_pOwner->m_pUnk, TRUE, FALSE); + HWND hWndFrame = m_pOwner->GetManager ()->GetPaintWindow (); + HRESULT Hr = E_FAIL; + if ((dwFlags & ACTIVATE_WINDOWLESS) != 0) { + m_bWindowless = true; + Hr = m_pOwner->m_pUnk->QueryInterface (IID_IOleInPlaceObjectWindowless, (LPVOID*) &m_pInPlaceObject); + m_pOwner->m_hwndHost = hWndFrame; + m_pOwner->GetManager ()->AddMessageFilter (m_pOwner); + } + if (FAILED (Hr)) { + m_bWindowless = false; + Hr = CreateActiveXWnd (); + if (FAILED (Hr)) return Hr; + Hr = m_pOwner->m_pUnk->QueryInterface (IID_IOleInPlaceObject, (LPVOID*) &m_pInPlaceObject); + } + if (m_pInPlaceObject && !m_pOwner->IsMFC ()) { + RECT rcItem = m_pOwner->m_rcItem; + if (!m_bWindowless) ::OffsetRect (&rcItem, -rcItem.left, -rcItem.top); + m_pInPlaceObject->SetObjectRects (&rcItem, &rcItem); + } + m_bInPlaceActive = SUCCEEDED (Hr); + return Hr; + } + + STDMETHODIMP CActiveXCtrl::OnInPlaceDeactivateEx (BOOL fNoRedraw) { + DUITRACE (_T ("AX: CActiveXCtrl::OnInPlaceDeactivateEx")); + m_bInPlaceActive = false; + if (m_pInPlaceObject) { + m_pInPlaceObject->Release (); + m_pInPlaceObject = nullptr; + } + if (m_pWindow) { + ::DestroyWindow (m_pWindow->GetHWND ()); + delete m_pWindow; + m_pWindow = nullptr; + } + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::RequestUIActivate (void) { + DUITRACE (_T ("AX: CActiveXCtrl::RequestUIActivate")); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::CanInPlaceActivate (void) { + DUITRACE (_T ("AX: CActiveXCtrl::CanInPlaceActivate")); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::OnInPlaceActivate (void) { + DUITRACE (_T ("AX: CActiveXCtrl::OnInPlaceActivate")); + BOOL bDummy = FALSE; + return OnInPlaceActivateEx (&bDummy, 0); + } + + STDMETHODIMP CActiveXCtrl::OnUIActivate (void) { + DUITRACE (_T ("AX: CActiveXCtrl::OnUIActivate")); + m_bUIActivated = true; + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::GetWindowContext (IOleInPlaceFrame** ppFrame, IOleInPlaceUIWindow** ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo) { + DUITRACE (_T ("AX: CActiveXCtrl::GetWindowContext")); + if (!ppDoc) return E_POINTER; + if (!ppFrame) return E_POINTER; + if (!lprcPosRect) return E_POINTER; + if (!lprcClipRect) return E_POINTER; + if (m_pWindow) { + ::GetClientRect (m_pWindow->GetHWND (), lprcPosRect); + ::GetClientRect (m_pWindow->GetHWND (), lprcClipRect); + } + *ppFrame = new CActiveXFrameWnd (m_pOwner); + *ppDoc = nullptr; + ACCEL ac = { 0 }; + HACCEL hac = ::CreateAcceleratorTable (&ac, 1); + lpFrameInfo->cb = sizeof (OLEINPLACEFRAMEINFO); + lpFrameInfo->fMDIApp = FALSE; + lpFrameInfo->hwndFrame = m_pOwner->GetManager ()->GetPaintWindow (); + lpFrameInfo->haccel = hac; + lpFrameInfo->cAccelEntries = 1; + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::Scroll (SIZE scrollExtant) { + DUITRACE (_T ("AX: CActiveXCtrl::Scroll")); + return E_NOTIMPL; + } + + STDMETHODIMP CActiveXCtrl::OnUIDeactivate (BOOL fUndoable) { + DUITRACE (_T ("AX: CActiveXCtrl::OnUIDeactivate")); + m_bUIActivated = false; + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::OnInPlaceDeactivate (void) { + DUITRACE (_T ("AX: CActiveXCtrl::OnInPlaceDeactivate")); + return OnInPlaceDeactivateEx (TRUE); + } + + STDMETHODIMP CActiveXCtrl::DiscardUndoState (void) { + DUITRACE (_T ("AX: CActiveXCtrl::DiscardUndoState")); + return E_NOTIMPL; + } + + STDMETHODIMP CActiveXCtrl::DeactivateAndUndo (void) { + DUITRACE (_T ("AX: CActiveXCtrl::DeactivateAndUndo")); + return E_NOTIMPL; + } + + STDMETHODIMP CActiveXCtrl::OnPosRectChange (LPCRECT lprcPosRect) { + DUITRACE (_T ("AX: CActiveXCtrl::OnPosRectChange")); + return E_NOTIMPL; + } + + STDMETHODIMP CActiveXCtrl::GetWindow (HWND* phwnd) { + DUITRACE (_T ("AX: CActiveXCtrl::GetWindow")); + if (!m_pOwner) return E_UNEXPECTED; + if (!m_pOwner->m_hwndHost) { + CreateActiveXWnd (); + return E_FAIL; + } + *phwnd = m_pOwner->m_hwndHost; + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::ContextSensitiveHelp (BOOL fEnterMode) { + DUITRACE (_T ("AX: CActiveXCtrl::ContextSensitiveHelp")); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::OnControlInfoChanged (void) { + DUITRACE (_T ("AX: CActiveXCtrl::OnControlInfoChanged")); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::LockInPlaceActive (BOOL fLock) { + DUITRACE (_T ("AX: CActiveXCtrl::LockInPlaceActive")); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::GetExtendedControl (IDispatch** ppDisp) { + DUITRACE (_T ("AX: CActiveXCtrl::GetExtendedControl")); + if (!ppDisp) return E_POINTER; + if (!m_pOwner) return E_UNEXPECTED; + if (!m_pOwner->m_pUnk) return E_UNEXPECTED; + return m_pOwner->m_pUnk->QueryInterface (IID_IDispatch, (LPVOID*) ppDisp); + } + + STDMETHODIMP CActiveXCtrl::TransformCoords (POINTL* pPtlHimetric, POINTF* pPtfContainer, DWORD dwFlags) { + DUITRACE (_T ("AX: CActiveXCtrl::TransformCoords")); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::TranslateAccelerator (MSG *pMsg, DWORD grfModifiers) { + DUITRACE (_T ("AX: CActiveXCtrl::TranslateAccelerator")); + return S_FALSE; + } + + STDMETHODIMP CActiveXCtrl::OnFocus (BOOL fGotFocus) { + DUITRACE (_T ("AX: CActiveXCtrl::OnFocus")); + m_bFocused = (fGotFocus == TRUE); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::ShowPropertyFrame (void) { + DUITRACE (_T ("AX: CActiveXCtrl::ShowPropertyFrame")); + return E_NOTIMPL; + } + + STDMETHODIMP CActiveXCtrl::EnumObjects (DWORD grfFlags, IEnumUnknown** ppenum) { + DUITRACE (_T ("AX: CActiveXCtrl::EnumObjects")); + if (!ppenum) return E_POINTER; + if (!m_pOwner) return E_UNEXPECTED; + *ppenum = new CActiveXEnum (m_pOwner->m_pUnk); + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::LockContainer (BOOL fLock) { + DUITRACE (_T ("AX: CActiveXCtrl::LockContainer")); + m_bLocked = fLock != FALSE; + return S_OK; + } + + STDMETHODIMP CActiveXCtrl::ParseDisplayName (IBindCtx *pbc, LPOLESTR pszDisplayName, ULONG* pchEaten, IMoniker** ppmkOut) { + DUITRACE (_T ("AX: CActiveXCtrl::ParseDisplayName")); + return E_NOTIMPL; + } + + HRESULT CActiveXCtrl::CreateActiveXWnd () { + if (m_pWindow) return S_OK; + m_pWindow = new CActiveXWnd; + if (!m_pWindow) return E_OUTOFMEMORY; + m_pOwner->m_hwndHost = m_pWindow->Init (this, m_pOwner->GetManager ()->GetPaintWindow ()); + return S_OK; + } + + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + HWND CActiveXWnd::Init (CActiveXCtrl* pOwner, HWND hWndParent) { + m_pOwner = pOwner; + UINT uStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; + Create (hWndParent, _T ("UIActiveX"), uStyle, 0L, 0, 0, 0, 0, NULL); + return m_hWnd; + } + + string_view_t CActiveXWnd::GetWindowClassName () const { + return _T ("ActiveXWnd"); + } + + void CActiveXWnd::OnFinalMessage (HWND hWnd) { + if (m_pOwner->m_pOwner->GetManager ()->IsLayered ()) { + m_pOwner->m_pOwner->GetManager ()->RemoveNativeWindow (hWnd); + } + } + + void CActiveXWnd::DoVerb (LONG iVerb) { + if (!m_pOwner) return; + if (!m_pOwner->m_pOwner) return; + IOleObject* pUnk = nullptr; + m_pOwner->m_pOwner->GetControl (IID_IOleObject, (LPVOID*) &pUnk); + if (!pUnk) return; + CSafeRelease RefOleObject = pUnk; + IOleClientSite* pOleClientSite = nullptr; + m_pOwner->QueryInterface (IID_IOleClientSite, (LPVOID*) &pOleClientSite); + CSafeRelease RefOleClientSite = pOleClientSite; + pUnk->DoVerb (iVerb, nullptr, pOleClientSite, 0, m_hWnd, &m_pOwner->m_pOwner->GetPos ()); + } + + LRESULT CActiveXWnd::HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam) { + LRESULT lRes = 0; + BOOL bHandled = TRUE; + switch (uMsg) { + case WM_CREATE: lRes = OnCreate (uMsg, wParam, lParam, bHandled); break; + case WM_TIMER: lRes = OnTimer (uMsg, wParam, lParam, bHandled); break; + case WM_PAINT: lRes = OnPaint (uMsg, wParam, lParam, bHandled); break; + case WM_PRINT: lRes = OnPrint (uMsg, wParam, lParam, bHandled); break; + case WM_SETFOCUS: lRes = OnSetFocus (uMsg, wParam, lParam, bHandled); break; + case WM_KILLFOCUS: lRes = OnKillFocus (uMsg, wParam, lParam, bHandled); break; + case WM_ERASEBKGND: lRes = OnEraseBkgnd (uMsg, wParam, lParam, bHandled); break; + case WM_MOUSEACTIVATE: lRes = OnMouseActivate (uMsg, wParam, lParam, bHandled); break; + case WM_MOUSEWHEEL: break; + default: + bHandled = FALSE; + } + if (!bHandled) return CWindowWnd::HandleMessage (uMsg, wParam, lParam); + return lRes; + } + + LRESULT CActiveXWnd::OnCreate (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + if (m_pOwner->m_pOwner->GetManager ()->IsLayered ()) { + ::SetTimer (m_hWnd, CARET_TIMERID, ::GetCaretBlinkTime (), nullptr); + } + return 0; + } + + LRESULT CActiveXWnd::OnTimer (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + if (wParam == DEFAULT_TIMERID) { + if (m_pOwner->m_pOwner->GetManager ()->IsLayered ()) { + //m_pOwner->m_pOwner->GetManager()->AddNativeWindow(m_pOwner->m_pOwner, m_hWnd); + m_iLayeredTick += 1; + if (m_iLayeredTick >= 10) { + m_iLayeredTick = 0; + m_bDrawCaret = !m_bDrawCaret; + } + } + return 0; + } + bHandled = FALSE; + return 0; + } + + LRESULT CActiveXWnd::OnEraseBkgnd (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + if (!m_pOwner->m_pViewObject) bHandled = FALSE; + return 1; + } + + LRESULT CActiveXWnd::OnMouseActivate (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + IOleObject* pUnk = nullptr; + m_pOwner->m_pOwner->GetControl (IID_IOleObject, (LPVOID*) &pUnk); + if (!pUnk) return 0; + CSafeRelease RefOleObject = pUnk; + DWORD dwMiscStatus = 0; + pUnk->GetMiscStatus (DVASPECT_CONTENT, &dwMiscStatus); + if ((dwMiscStatus & OLEMISC_NOUIACTIVATE) != 0) return 0; + if (!m_pOwner->m_bInPlaceActive) DoVerb (OLEIVERB_INPLACEACTIVATE); + bHandled = FALSE; + return 0; + } + + LRESULT CActiveXWnd::OnSetFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + bHandled = FALSE; + m_pOwner->m_bFocused = true; + if (!m_pOwner->m_bUIActivated) DoVerb (OLEIVERB_UIACTIVATE); + return 0; + } + + LRESULT CActiveXWnd::OnKillFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + bHandled = FALSE; + m_pOwner->m_bFocused = false; + return 0; + } + + LRESULT CActiveXWnd::OnPaint (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + PAINTSTRUCT ps = { 0 }; + ::BeginPaint (m_hWnd, &ps); + ::EndPaint (m_hWnd, &ps); + return 1; + } + + LRESULT CActiveXWnd::OnPrint (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + RECT rcClient = { 0 }; + ::GetClientRect (m_hWnd, &rcClient); + m_pOwner->m_pViewObject->Draw (DVASPECT_CONTENT, -1, nullptr, nullptr, NULL, (HDC) wParam, (RECTL*) &rcClient, nullptr, nullptr, NULL); + + if (m_bDrawCaret) { + RECT rcPos = m_pOwner->m_pOwner->GetPos (); + GUITHREADINFO guiThreadInfo; + guiThreadInfo.cbSize = sizeof (GUITHREADINFO); + ::GetGUIThreadInfo (0, &guiThreadInfo); + if (guiThreadInfo.hwndCaret) { + POINT ptCaret = { 0 }; + ptCaret.x = guiThreadInfo.rcCaret.left; + ptCaret.y = guiThreadInfo.rcCaret.top; + ::ClientToScreen (guiThreadInfo.hwndCaret, &ptCaret); + ::ScreenToClient (m_pOwner->m_pOwner->GetManager ()->GetPaintWindow (), &ptCaret); + if (::PtInRect (&rcPos, ptCaret)) { + RECT rcCaret = { 0 }; + rcCaret = guiThreadInfo.rcCaret; + rcCaret.right = rcCaret.left; + CRenderEngine::DrawLine ((HDC) wParam, rcCaret, 1, 0xFF000000); + } + } + } + + return 1; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CActiveXUI) + + CActiveXUI::CActiveXUI () {} + + CActiveXUI::~CActiveXUI () { + ReleaseControl (); + } + + string_view_t CActiveXUI::GetClass () const { + return _T ("ActiveXUI"); + } + + LPVOID CActiveXUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_ACTIVEX) return static_cast(this); + return CControlUI::GetInterface (pstrName); + } + + HWND CActiveXUI::GetHostWindow () const { + return m_hwndHost; + } + + static void PixelToHiMetric (const SIZEL* lpSizeInPix, LPSIZEL lpSizeInHiMetric) { +#define HIMETRIC_PER_INCH 2540 +#define MAP_PIX_TO_LOGHIM(x,ppli) MulDiv(HIMETRIC_PER_INCH, (x), (ppli)) +#define MAP_LOGHIM_TO_PIX(x,ppli) MulDiv((ppli), (x), HIMETRIC_PER_INCH) + int nPixelsPerInchX; // Pixels per logical inch along width + int nPixelsPerInchY; // Pixels per logical inch along height + HDC hDCScreen = ::GetDC (NULL); + nPixelsPerInchX = ::GetDeviceCaps (hDCScreen, LOGPIXELSX); + nPixelsPerInchY = ::GetDeviceCaps (hDCScreen, LOGPIXELSY); + ::ReleaseDC (NULL, hDCScreen); + lpSizeInHiMetric->cx = MAP_PIX_TO_LOGHIM (lpSizeInPix->cx, nPixelsPerInchX); + lpSizeInHiMetric->cy = MAP_PIX_TO_LOGHIM (lpSizeInPix->cy, nPixelsPerInchY); + } + + void CActiveXUI::SetVisible (bool bVisible) { + CControlUI::SetVisible (bVisible); + if (m_hwndHost != NULL && !m_pControl->m_bWindowless) + ::ShowWindow (m_hwndHost, IsVisible () ? SW_SHOW : SW_HIDE); + } + + void CActiveXUI::SetInternVisible (bool bVisible) { + CControlUI::SetInternVisible (bVisible); + if (m_hwndHost != NULL && !m_pControl->m_bWindowless) + ::ShowWindow (m_hwndHost, IsVisible () ? SW_SHOW : SW_HIDE); + } + + void CActiveXUI::SetPos (RECT rc, bool bNeedInvalidate) { + CControlUI::SetPos (rc, bNeedInvalidate); + + if (!m_bCreated) DoCreateControl (); + + if (!m_pUnk) return; + if (!m_pControl) return; + + SIZEL hmSize = { 0 }; + SIZEL pxSize = { 0 }; + pxSize.cx = m_rcItem.right - m_rcItem.left; + pxSize.cy = m_rcItem.bottom - m_rcItem.top; + PixelToHiMetric (&pxSize, &hmSize); + + if (m_pUnk) { + m_pUnk->SetExtent (DVASPECT_CONTENT, &hmSize); + } + if (m_pControl->m_pInPlaceObject) { + RECT rcItem = m_rcItem; + if (!m_pControl->m_bWindowless) ::OffsetRect (&rcItem, -rcItem.left, -rcItem.top); + m_pControl->m_pInPlaceObject->SetObjectRects (&rcItem, &rcItem); + } + if (!m_pControl->m_bWindowless) { + ASSERT (m_pControl->m_pWindow); + ::MoveWindow (m_pControl->m_pWindow->GetHWND (), m_rcItem.left, m_rcItem.top, m_rcItem.right - m_rcItem.left, m_rcItem.bottom - m_rcItem.top, TRUE); + } + } + + void CActiveXUI::Move (SIZE szOffset, bool bNeedInvalidate) { + CControlUI::Move (szOffset, bNeedInvalidate); + if (!m_pControl->m_bWindowless) { + ASSERT (m_pControl->m_pWindow); + ::MoveWindow (m_pControl->m_pWindow->GetHWND (), m_rcItem.left, m_rcItem.top, m_rcItem.right - m_rcItem.left, m_rcItem.bottom - m_rcItem.top, TRUE); + } + } + + bool CActiveXUI::DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + if (m_pControl && m_pControl->m_bWindowless && m_pControl->m_pViewObject) { + m_pControl->m_pViewObject->Draw (DVASPECT_CONTENT, -1, nullptr, nullptr, NULL, hDC, (RECTL*) &m_rcItem, (RECTL*) &m_rcItem, nullptr, NULL); + } + return true; + } + + void CActiveXUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("clsid")) CreateControl (pstrValue); + else if (pstrName == _T ("modulename")) SetModuleName (pstrValue); + else if (pstrName == _T ("delaycreate")) SetDelayCreate (FawTools::parse_bool (pstrValue)); + else CControlUI::SetAttribute (pstrName, pstrValue); + } + + LRESULT CActiveXUI::MessageHandler (UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled) { + if (!m_pControl) return 0; + ASSERT (m_pControl->m_bWindowless); + if (!m_pControl->m_bInPlaceActive) return 0; + if (!m_pControl->m_pInPlaceObject) return 0; + if (!IsMouseEnabled () && uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST) return 0; + bool bWasHandled = true; + if ((uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST) || uMsg == WM_SETCURSOR) { + // Mouse message only go when captured or inside rect + DWORD dwHitResult = m_pControl->m_bCaptured ? HITRESULT_HIT : HITRESULT_OUTSIDE; + if (dwHitResult == HITRESULT_OUTSIDE && m_pControl->m_pViewObject) { + IViewObjectEx* pViewEx = nullptr; + m_pControl->m_pViewObject->QueryInterface (IID_IViewObjectEx, (LPVOID*) &pViewEx); + if (pViewEx) { + POINT ptMouse = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + pViewEx->QueryHitPoint (DVASPECT_CONTENT, &m_rcItem, ptMouse, 0, &dwHitResult); + pViewEx->Release (); + } + } + if (dwHitResult != HITRESULT_HIT) return 0; + if (uMsg == WM_SETCURSOR) bWasHandled = false; + } else if (uMsg >= WM_KEYFIRST && uMsg <= WM_KEYLAST) { + // Keyboard messages just go when we have focus + if (!IsFocused ()) return 0; + } else { + switch (uMsg) { + case WM_HELP: + case WM_CONTEXTMENU: + bWasHandled = false; + break; + default: + return 0; + } + } + LRESULT lResult = 0; + HRESULT Hr = m_pControl->m_pInPlaceObject->OnWindowMessage (uMsg, wParam, lParam, &lResult); + if (Hr == S_OK) bHandled = bWasHandled; + return lResult; + } + + bool CActiveXUI::IsDelayCreate () const { + return m_bDelayCreate; + } + + void CActiveXUI::SetDelayCreate (bool bDelayCreate) { + if (m_bDelayCreate == bDelayCreate) return; + if (bDelayCreate == false) { + if (m_bCreated == false && m_clsid != IID_NULL) DoCreateControl (); + } + m_bDelayCreate = bDelayCreate; + } + + bool CActiveXUI::IsMFC () const { + return m_bMFC; + } + + void CActiveXUI::SetMFC (bool bMFC/* = false*/) { + if (m_bMFC == bMFC) return; + m_bMFC = bMFC; + } + + bool CActiveXUI::CreateControl (string_view_t pstrCLSID) { + CLSID clsid = { 0 }; + OLECHAR szCLSID[100] = { 0 }; +#ifndef _UNICODE + ::MultiByteToWideChar (::GetACP (), 0, pstrCLSID.data (), -1, szCLSID, lengthof (szCLSID) - 1); +#else + _tcsncpy (szCLSID, pstrCLSID.data (), lengthof (szCLSID) - 1); +#endif + if (pstrCLSID[0] == '{') ::CLSIDFromString (szCLSID, &clsid); + else ::CLSIDFromProgID (szCLSID, &clsid); + return CreateControl (clsid); + } + + bool CActiveXUI::CreateControl (const CLSID clsid) { + ASSERT (clsid != IID_NULL); + if (clsid == IID_NULL) return false; + m_bCreated = false; + m_clsid = clsid; + if (!m_bDelayCreate) DoCreateControl (); + return true; + } + + void CActiveXUI::ReleaseControl () { + // ƳϢ + if (m_pManager) m_pManager->RemoveMessageFilter (this); + + if (m_pUnk) { + IObjectWithSite* pSite = nullptr; + m_pUnk->QueryInterface (IID_IObjectWithSite, (LPVOID*) &pSite); + if (pSite) { + pSite->SetSite (nullptr); + pSite->Release (); + } + if (!IsMFC ()) { + m_pUnk->Close (OLECLOSE_NOSAVE); + } + m_pUnk->SetClientSite (nullptr); + m_pUnk->Release (); + m_pUnk = nullptr; + } + // CActiveXCtrl + if (m_pControl) { + m_pControl->m_pOwner = nullptr; + m_pControl->Release (); + m_pControl = nullptr; + } + + m_hwndHost = NULL; + } + + typedef HRESULT (__stdcall *DllGetClassObjectFunc)(REFCLSID rclsid, REFIID riid, LPVOID* ppv); + + bool CActiveXUI::DoCreateControl () { + ReleaseControl (); + // At this point we'll create the ActiveX control + m_bCreated = true; + IOleControl* pOleControl = nullptr; + + HRESULT Hr = -1; + if (!m_sModuleName.empty ()) { + HMODULE hModule = ::LoadLibrary (m_sModuleName.c_str ()); + if (hModule) { + IClassFactory* aClassFactory = nullptr; + DllGetClassObjectFunc aDllGetClassObjectFunc = (DllGetClassObjectFunc)::GetProcAddress (hModule, "DllGetClassObject"); + Hr = aDllGetClassObjectFunc (m_clsid, IID_IClassFactory, (LPVOID*) &aClassFactory); + if (SUCCEEDED (Hr)) { + Hr = aClassFactory->CreateInstance (nullptr, IID_IOleObject, (LPVOID*) &pOleControl); + } + aClassFactory->Release (); + } + } + if (FAILED (Hr)) { + Hr = ::CoCreateInstance (m_clsid, nullptr, CLSCTX_ALL, IID_IOleControl, (LPVOID*) &pOleControl); + } + ASSERT (SUCCEEDED (Hr)); + if (FAILED (Hr)) return false; + pOleControl->QueryInterface (IID_IOleObject, (LPVOID*) &m_pUnk); + pOleControl->Release (); + if (!m_pUnk) return false; + // Create the host too + m_pControl = new CActiveXCtrl (); + m_pControl->m_pOwner = this; + // More control creation stuff + DWORD dwMiscStatus = 0; + m_pUnk->GetMiscStatus (DVASPECT_CONTENT, &dwMiscStatus); + IOleClientSite* pOleClientSite = nullptr; + m_pControl->QueryInterface (IID_IOleClientSite, (LPVOID*) &pOleClientSite); + CSafeRelease RefOleClientSite = pOleClientSite; + // Initialize control + if ((dwMiscStatus & OLEMISC_SETCLIENTSITEFIRST) != 0) m_pUnk->SetClientSite (pOleClientSite); + IPersistStreamInit* pPersistStreamInit = nullptr; + m_pUnk->QueryInterface (IID_IPersistStreamInit, (LPVOID*) &pPersistStreamInit); + if (pPersistStreamInit) { + Hr = pPersistStreamInit->InitNew (); + pPersistStreamInit->Release (); + } + if (FAILED (Hr)) return false; + if ((dwMiscStatus & OLEMISC_SETCLIENTSITEFIRST) == 0) m_pUnk->SetClientSite (pOleClientSite); + // Grab the view... + Hr = m_pUnk->QueryInterface (IID_IViewObjectEx, (LPVOID*) &m_pControl->m_pViewObject); + if (FAILED (Hr)) Hr = m_pUnk->QueryInterface (IID_IViewObject2, (LPVOID*) &m_pControl->m_pViewObject); + if (FAILED (Hr)) Hr = m_pUnk->QueryInterface (IID_IViewObject, (LPVOID*) &m_pControl->m_pViewObject); + // Activate and done... + m_pUnk->SetHostNames (OLESTR ("UIActiveX"), nullptr); + if (m_pManager) m_pManager->SendNotify ((CControlUI*) this, DUI_MSGTYPE_SHOWACTIVEX, 0, 0, false); + if ((dwMiscStatus & OLEMISC_INVISIBLEATRUNTIME) == 0) { + try { + if (m_pManager) Hr = m_pUnk->DoVerb (OLEIVERB_INPLACEACTIVATE, nullptr, pOleClientSite, 0, m_pManager->GetPaintWindow (), &m_rcItem); + } catch (...) { + } + } + IObjectWithSite* pSite = nullptr; + m_pUnk->QueryInterface (IID_IObjectWithSite, (LPVOID*) &pSite); + if (pSite) { + pSite->SetSite (static_cast(m_pControl)); + pSite->Release (); + } + return SUCCEEDED (Hr); + } + + HRESULT CActiveXUI::GetControl (const IID iid, LPVOID* ppRet) { + ASSERT (ppRet); + ASSERT (!*ppRet); + if (!ppRet) return E_POINTER; + if (!m_pUnk) return E_PENDING; + return m_pUnk->QueryInterface (iid, (LPVOID*) ppRet); + } + + CLSID CActiveXUI::GetClisd () const { + return m_clsid; + } + + string_view_t CActiveXUI::GetModuleName () const { + return m_sModuleName; + } + + void CActiveXUI::SetModuleName (string_view_t pstrText) { + m_sModuleName = pstrText; + } + +} // namespace DuiLib \ No newline at end of file diff --git a/DuiLib/Control/UIActiveX.h b/DuiLib/Control/UIActiveX.h new file mode 100644 index 0000000..f731b89 --- /dev/null +++ b/DuiLib/Control/UIActiveX.h @@ -0,0 +1,79 @@ +#ifndef __UIACTIVEX_H__ +#define __UIACTIVEX_H__ + +#pragma once + +struct IOleObject; + + +namespace DuiLib { + ///////////////////////////////////////////////////////////////////////////////////// + // + + class CActiveXCtrl; + + template< class T > + class CSafeRelease { + public: + CSafeRelease (T* p): m_p (p) {}; + virtual ~CSafeRelease () { if (m_p) m_p->Release (); } + T* Detach () { T* t = m_p; m_p = nullptr; return t; } + T* m_p; + }; + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CActiveXUI: public CControlUI, public IMessageFilterUI { + DECLARE_DUICONTROL (CActiveXUI) + + friend class CActiveXCtrl; + public: + CActiveXUI (); + virtual ~CActiveXUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + HWND GetHostWindow () const; + + virtual bool IsDelayCreate () const; + virtual void SetDelayCreate (bool bDelayCreate = true); + virtual bool IsMFC () const; + virtual void SetMFC (bool bMFC = false); + + bool CreateControl (const CLSID clsid); + bool CreateControl (string_view_t pstrCLSID); + HRESULT GetControl (const IID iid, LPVOID* ppRet); + CLSID GetClisd () const; + string_view_t GetModuleName () const; + void SetModuleName (string_view_t pstrText); + + void SetVisible (bool bVisible = true); + void SetInternVisible (bool bVisible = true); + void SetPos (RECT rc, bool bNeedInvalidate = true); + void Move (SIZE szOffset, bool bNeedInvalidate = true); + bool DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl); + + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + LRESULT MessageHandler (UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled); + + protected: + virtual void ReleaseControl (); + virtual bool DoCreateControl (); + + protected: + CLSID m_clsid = IID_NULL; + CDuiString m_sModuleName; + bool m_bCreated = false; + bool m_bDelayCreate = true; + bool m_bMFC = false; + IOleObject *m_pUnk = nullptr; + CActiveXCtrl *m_pControl = nullptr; + HWND m_hwndHost = NULL; + }; + +} // namespace DuiLib + +#endif // __UIACTIVEX_H__ diff --git a/DuiLib/Control/UIAnimation.cpp b/DuiLib/Control/UIAnimation.cpp new file mode 100644 index 0000000..fc2117b --- /dev/null +++ b/DuiLib/Control/UIAnimation.cpp @@ -0,0 +1,142 @@ +#include "StdAfx.h" +#include "UIAnimation.h" +#include +#include + +namespace DuiLib { + struct CUIAnimation::Imp { + std::vector m_arAnimations; + }; + + CUIAnimation::CUIAnimation (CControlUI* pOwner): m_pImp (new CUIAnimation::Imp ()) { + ASSERT (pOwner); + m_pControl = pOwner; + } + CUIAnimation:: ~CUIAnimation () { + if (m_pImp) { + delete m_pImp; + m_pImp = nullptr; + } + } + BOOL CUIAnimation::StartAnimation (int nElapse, int nTotalFrame, int nAnimationID /*= 0*/, BOOL bLoop/* = FALSE*/) { + CAnimationData* pData = GetAnimationDataByID (nAnimationID); + if (pData || nElapse <= 0 || nTotalFrame <= 0 || !m_pControl) { + ASSERT (FALSE); + return FALSE; + } + + CAnimationData* pAnimation = new CAnimationData (nElapse, nTotalFrame, nAnimationID, bLoop); + if (!pAnimation) return FALSE; + + if (m_pControl->GetManager ()->SetTimer (m_pControl, nAnimationID, nElapse)) { + m_pImp->m_arAnimations.push_back (pAnimation); + return TRUE; + } + return FALSE; + } + + void CUIAnimation::StopAnimation (int nAnimationID /*= 0*/) { + if (!m_pControl) return; + + if (nAnimationID != 0) { + CAnimationData* pData = GetAnimationDataByID (nAnimationID); + if (nullptr != pData) { + m_pControl->GetManager ()->KillTimer (m_pControl, nAnimationID); + m_pImp->m_arAnimations.erase (std::remove (m_pImp->m_arAnimations.begin (), m_pImp->m_arAnimations.end (), pData), m_pImp->m_arAnimations.end ()); + if (pData) { + delete pData; + pData = nullptr; + } + return; + } + } else { + size_t nCount = m_pImp->m_arAnimations.size (); + for (size_t i = 0; i < nCount; ++i) { + CAnimationData* pData = m_pImp->m_arAnimations[i]; + if (pData) { + m_pControl->GetManager ()->KillTimer (m_pControl, pData->m_nAnimationID); + if (pData) { + delete pData; + pData = nullptr; + } + } + } + m_pImp->m_arAnimations.clear (); + } + } + + BOOL CUIAnimation::IsAnimationRunning (int nAnimationID) { + CAnimationData* pData = GetAnimationDataByID (nAnimationID); + return !!pData; + } + + int CUIAnimation::GetCurrentFrame (int nAnimationID/* = 0*/) { + CAnimationData* pData = GetAnimationDataByID (nAnimationID); + if (!pData) { + ASSERT (FALSE); + return -1; + } + return pData->m_nCurFrame; + } + + BOOL CUIAnimation::SetCurrentFrame (int nFrame, int nAnimationID/* = 0*/) { + CAnimationData* pData = GetAnimationDataByID (nAnimationID); + if (!pData) { + ASSERT (FALSE); + return FALSE; + } + + if (nFrame >= 0 && nFrame <= pData->m_nTotalFrame) { + pData->m_nCurFrame = nFrame; + return TRUE; + } else { + ASSERT (FALSE); + } + return FALSE; + } + + void CUIAnimation::OnAnimationElapse (int nAnimationID) { + if (!m_pControl) return; + + CAnimationData* pData = GetAnimationDataByID (nAnimationID); + if (!pData) return; + + int nCurFrame = pData->m_nCurFrame; + if (nCurFrame == 0) { + OnAnimationStart (nAnimationID, pData->m_bFirstLoop); + pData->m_bFirstLoop = FALSE; + } + + OnAnimationStep (pData->m_nTotalFrame, nCurFrame, nAnimationID); + + if (nCurFrame >= pData->m_nTotalFrame) { + OnAnimationStop (nAnimationID); + if (pData->m_bLoop) { + pData->m_nCurFrame = 0; + } else { + m_pControl->GetManager ()->KillTimer (m_pControl, nAnimationID); + m_pImp->m_arAnimations.erase (std::remove (m_pImp->m_arAnimations.begin (), m_pImp->m_arAnimations.end (), pData), m_pImp->m_arAnimations.end ()); + delete pData; + pData = nullptr; + } + } + + if (nullptr != pData) { + ++(pData->m_nCurFrame); + } + } + + CAnimationData* CUIAnimation::GetAnimationDataByID (int nAnimationID) { + CAnimationData* pRet = nullptr; + size_t nCount = m_pImp->m_arAnimations.size (); + for (size_t i = 0; i < nCount; ++i) { + if (m_pImp->m_arAnimations[i]->m_nAnimationID == nAnimationID) { + pRet = m_pImp->m_arAnimations[i]; + break; + } + } + + return pRet; + } + +} // namespace DuiLib \ No newline at end of file diff --git a/DuiLib/Control/UIAnimation.h b/DuiLib/Control/UIAnimation.h new file mode 100644 index 0000000..88c4e93 --- /dev/null +++ b/DuiLib/Control/UIAnimation.h @@ -0,0 +1,77 @@ +#ifndef __UIANIMATION_H__ +#define __UIANIMATION_H__ + +#include "UIButton.h" +#pragma once + +namespace DuiLib { + + class UILIB_API IUIAnimation { + public: + virtual ~IUIAnimation () {} + + virtual BOOL StartAnimation (int nElapse, int nTotalFrame, int nAnimationID = 0, BOOL bLoop = FALSE) = 0; + virtual void StopAnimation (int nAnimationID = 0) = 0; + virtual BOOL IsAnimationRunning (int nAnimationID) = 0; + virtual int GetCurrentFrame (int nAnimationID = 0) = 0; + virtual BOOL SetCurrentFrame (int nFrame, int nAnimationID = 0) = 0; + + virtual void OnAnimationStep (int nTotalFrame, int nCurFrame, int nAnimationID) = 0; + virtual void OnAnimationStart (int nAnimationID, BOOL bFirstLoop) = 0; + virtual void OnAnimationStop (int nAnimationID) = 0; + + virtual void OnAnimationElapse (int nAnimationID) = 0; + }; + + class UILIB_API CAnimationData { + public: + CAnimationData (int nElipse, int nFrame, int nID, BOOL bLoop) { + m_nElapse = nElipse; + m_nTotalFrame = nFrame; + m_bLoop = bLoop; + m_nAnimationID = nID; + } + + //protected: + public: + friend class CDUIAnimation; + + int m_nAnimationID; + int m_nElapse; + + int m_nTotalFrame; + int m_nCurFrame = 0; + + BOOL m_bLoop; + BOOL m_bFirstLoop = TRUE; + }; + + class UILIB_API CUIAnimation: public IUIAnimation { + struct Imp; + public: + CUIAnimation (CControlUI* pOwner); + virtual ~CUIAnimation (); + + virtual BOOL StartAnimation (int nElapse, int nTotalFrame, int nAnimationID = 0, BOOL bLoop = FALSE); + virtual void StopAnimation (int nAnimationID = 0); + virtual BOOL IsAnimationRunning (int nAnimationID); + virtual int GetCurrentFrame (int nAnimationID = 0); + virtual BOOL SetCurrentFrame (int nFrame, int nAnimationID = 0); + + virtual void OnAnimationStart (int nAnimationID, BOOL bFirstLoop) {} + virtual void OnAnimationStep (int nTotalFrame, int nCurFrame, int nAnimationID) {} + virtual void OnAnimationStop (int nAnimationID) {} + + virtual void OnAnimationElapse (int nAnimationID); + + protected: + CAnimationData* GetAnimationDataByID (int nAnimationID); + + protected: + CControlUI *m_pControl; + Imp *m_pImp; + }; + +} // namespace DuiLib + +#endif // __UIANIMATION_H__ \ No newline at end of file diff --git a/DuiLib/Control/UIButton.cpp b/DuiLib/Control/UIButton.cpp new file mode 100644 index 0000000..a898c3a --- /dev/null +++ b/DuiLib/Control/UIButton.cpp @@ -0,0 +1,472 @@ +#include "StdAfx.h" +#include "UIButton.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CButtonUI) + + CButtonUI::CButtonUI () { + m_uTextStyle = DT_SINGLELINE | DT_VCENTER | DT_CENTER; + } + + string_view_t CButtonUI::GetClass () const { + return _T ("ButtonUI"); + } + + LPVOID CButtonUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_BUTTON) return static_cast(this); + return CLabelUI::GetInterface (pstrName.data ()); + } + + UINT CButtonUI::GetControlFlags () const { + return (IsKeyboardEnabled () ? UIFLAG_TABSTOP : 0) | (IsEnabled () ? UIFLAG_SETCURSOR : 0); + } + + void CButtonUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CLabelUI::DoEvent (event); + return; + } + + if (event.Type == UIEVENT_SETFOCUS) { + Invalidate (); + } else if (event.Type == UIEVENT_KILLFOCUS) { + Invalidate (); + } else if (event.Type == UIEVENT_KEYDOWN) { + if (IsKeyboardEnabled ()) { + if (event.chKey == VK_SPACE || event.chKey == VK_RETURN) { + Activate (); + return; + } + } + } else if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK) { + if (::PtInRect (&m_rcItem, event.ptMouse) && IsEnabled ()) { + m_uButtonState |= UISTATE_PUSHED | UISTATE_CAPTURED; + Invalidate (); + } + return; + } else if (event.Type == UIEVENT_MOUSEMOVE) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + if (::PtInRect (&m_rcItem, event.ptMouse)) m_uButtonState |= UISTATE_PUSHED; + else m_uButtonState &= ~UISTATE_PUSHED; + Invalidate (); + } + return; + } else if (event.Type == UIEVENT_BUTTONUP) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + m_uButtonState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED); + Invalidate (); + if (::PtInRect (&m_rcItem, event.ptMouse)) Activate (); + } + return; + } else if (event.Type == UIEVENT_CONTEXTMENU) { + if (IsContextMenuUsed ()) { + m_pManager->SendNotify (this, DUI_MSGTYPE_MENU, event.wParam, event.lParam); + } + return; + } else if (event.Type == UIEVENT_MOUSEENTER) { + if (IsEnabled ()) { + m_uButtonState |= UISTATE_HOT; + Invalidate (); + } + } else if (event.Type == UIEVENT_MOUSELEAVE) { + if (IsEnabled ()) { + m_uButtonState &= ~UISTATE_HOT; + Invalidate (); + } + } + CLabelUI::DoEvent (event); + } + + bool CButtonUI::Activate () { + if (!CControlUI::Activate ()) return false; + if (m_pManager) { + m_pManager->SendNotify (this, DUI_MSGTYPE_CLICK); + BindTriggerTabSel (); + } + return true; + } + + void CButtonUI::SetEnabled (bool bEnable) { + CControlUI::SetEnabled (bEnable); + if (!IsEnabled ()) { + m_uButtonState = 0; + } + } + + + void CButtonUI::SetHotFont (int index) { + m_iHotFont = index; + Invalidate (); + } + + int CButtonUI::GetHotFont () const { + return m_iHotFont; + } + + void CButtonUI::SetPushedFont (int index) { + m_iPushedFont = index; + Invalidate (); + } + + int CButtonUI::GetPushedFont () const { + return m_iPushedFont; + } + + void CButtonUI::SetFocusedFont (int index) { + m_iFocusedFont = index; + Invalidate (); + } + + int CButtonUI::GetFocusedFont () const { + return m_iFocusedFont; + } + + void CButtonUI::SetHotBkColor (DWORD dwColor) { + m_dwHotBkColor = dwColor; + Invalidate (); + } + + DWORD CButtonUI::GetHotBkColor () const { + return m_dwHotBkColor; + } + + void CButtonUI::SetPushedBkColor (DWORD dwColor) { + m_dwPushedBkColor = dwColor; + Invalidate (); + } + + DWORD CButtonUI::GetPushedBkColor () const { + return m_dwPushedBkColor; + } + + void CButtonUI::SetDisabledBkColor (DWORD dwColor) { + m_dwDisabledBkColor = dwColor; + Invalidate (); + } + + DWORD CButtonUI::GetDisabledBkColor () const { + return m_dwDisabledBkColor; + } + + void CButtonUI::SetHotTextColor (DWORD dwColor) { + m_dwHotTextColor = dwColor; + } + + DWORD CButtonUI::GetHotTextColor () const { + return m_dwHotTextColor; + } + + void CButtonUI::SetPushedTextColor (DWORD dwColor) { + m_dwPushedTextColor = dwColor; + } + + DWORD CButtonUI::GetPushedTextColor () const { + return m_dwPushedTextColor; + } + + void CButtonUI::SetFocusedTextColor (DWORD dwColor) { + m_dwFocusedTextColor = dwColor; + } + + DWORD CButtonUI::GetFocusedTextColor () const { + return m_dwFocusedTextColor; + } + + string_view_t CButtonUI::GetNormalImage () { + return m_sNormalImage; + } + + void CButtonUI::SetNormalImage (string_view_t pStrImage) { + m_sNormalImage = pStrImage; + Invalidate (); + } + + string_view_t CButtonUI::GetHotImage () { + return m_sHotImage; + } + + void CButtonUI::SetHotImage (string_view_t pStrImage) { + m_sHotImage = pStrImage; + Invalidate (); + } + + string_view_t CButtonUI::GetPushedImage () { + return m_sPushedImage; + } + + void CButtonUI::SetPushedImage (string_view_t pStrImage) { + m_sPushedImage = pStrImage; + Invalidate (); + } + + string_view_t CButtonUI::GetFocusedImage () { + return m_sFocusedImage; + } + + void CButtonUI::SetFocusedImage (string_view_t pStrImage) { + m_sFocusedImage = pStrImage; + Invalidate (); + } + + string_view_t CButtonUI::GetDisabledImage () { + return m_sDisabledImage; + } + + void CButtonUI::SetDisabledImage (string_view_t pStrImage) { + m_sDisabledImage = pStrImage; + Invalidate (); + } + + string_view_t CButtonUI::GetHotForeImage () { + return m_sHotForeImage; + } + + void CButtonUI::SetHotForeImage (string_view_t pStrImage) { + m_sHotForeImage = pStrImage; + Invalidate (); + } + + void CButtonUI::SetStateCount (int nCount) { + m_nStateCount = nCount; + Invalidate (); + } + + int CButtonUI::GetStateCount () const { + return m_nStateCount; + } + + string_view_t CButtonUI::GetStateImage () { + return m_sStateImage; + } + + void CButtonUI::SetStateImage (string_view_t pStrImage) { + m_sNormalImage.clear (); + m_sStateImage = pStrImage; + Invalidate (); + } + + void CButtonUI::BindTabIndex (int _BindTabIndex) { + if (_BindTabIndex >= 0) + m_iBindTabIndex = _BindTabIndex; + } + + void CButtonUI::BindTabLayoutName (string_view_t _TabLayoutName) { + if (!_TabLayoutName.empty ()) + m_sBindTabLayoutName = _TabLayoutName; + } + + void CButtonUI::BindTriggerTabSel (int _SetSelectIndex /*= -1*/) { + string_view_t pstrName = GetBindTabLayoutName (); + if (pstrName.empty () || (GetBindTabLayoutIndex () < 0 && _SetSelectIndex < 0)) + return; + + CTabLayoutUI* pTabLayout = static_cast(GetManager ()->FindControl (pstrName)); + if (!pTabLayout) return; + pTabLayout->SelectItem (_SetSelectIndex >= 0 ? _SetSelectIndex : GetBindTabLayoutIndex ()); + } + + void CButtonUI::RemoveBindTabIndex () { + m_iBindTabIndex = -1; + m_sBindTabLayoutName.clear (); + } + + int CButtonUI::GetBindTabLayoutIndex () { + return m_iBindTabIndex; + } + + string_view_t CButtonUI::GetBindTabLayoutName () { + return m_sBindTabLayoutName; + } + + void CButtonUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("normalimage")) SetNormalImage (pstrValue); + else if (pstrName == _T ("hotimage")) SetHotImage (pstrValue); + else if (pstrName == _T ("pushedimage")) SetPushedImage (pstrValue); + else if (pstrName == _T ("focusedimage")) SetFocusedImage (pstrValue); + else if (pstrName == _T ("disabledimage")) SetDisabledImage (pstrValue); + else if (pstrName == _T ("hotforeimage")) SetHotForeImage (pstrValue); + else if (pstrName == _T ("stateimage")) SetStateImage (pstrValue); + else if (pstrName == _T ("statecount")) SetStateCount (_ttoi (pstrValue.data ())); + else if (pstrName == _T ("bindtabindex")) BindTabIndex (_ttoi (pstrValue.data ())); + else if (pstrName == _T ("bindtablayoutname")) BindTabLayoutName (pstrValue); + else if (pstrName == _T ("hotbkcolor")) { + SetHotBkColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("pushedbkcolor")) { + SetPushedBkColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("disabledbkcolor")) { + SetDisabledBkColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("hottextcolor")) { + SetHotTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("pushedtextcolor")) { + SetPushedTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("focusedtextcolor")) { + SetFocusedTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("hotfont")) SetHotFont (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("pushedfont")) SetPushedFont (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("focuedfont")) SetFocusedFont (FawTools::parse_dec (pstrValue)); + + else CLabelUI::SetAttribute (pstrName, pstrValue); + } + + void CButtonUI::PaintText (HDC hDC) { + if (IsFocused ()) m_uButtonState |= UISTATE_FOCUSED; + else m_uButtonState &= ~UISTATE_FOCUSED; + if (!IsEnabled ()) m_uButtonState |= UISTATE_DISABLED; + else m_uButtonState &= ~UISTATE_DISABLED; + + if (m_dwTextColor == 0) m_dwTextColor = m_pManager->GetDefaultFontColor (); + if (m_dwDisabledTextColor == 0) m_dwDisabledTextColor = m_pManager->GetDefaultDisabledColor (); + + CDuiString sText = GetText (); + if (sText.empty ()) return; + + RECT _rcTextPadding = CButtonUI::m_rcTextPadding; + GetManager ()->GetDPIObj ()->Scale (&_rcTextPadding); + int nLinks = 0; + RECT rc = m_rcItem; + rc.left += _rcTextPadding.left; + rc.right -= _rcTextPadding.right; + rc.top += _rcTextPadding.top; + rc.bottom -= _rcTextPadding.bottom; + + DWORD clrColor = IsEnabled () ? m_dwTextColor : m_dwDisabledTextColor; + + if (((m_uButtonState & UISTATE_PUSHED) != 0) && (GetPushedTextColor () != 0)) + clrColor = GetPushedTextColor (); + else if (((m_uButtonState & UISTATE_HOT) != 0) && (GetHotTextColor () != 0)) + clrColor = GetHotTextColor (); + else if (((m_uButtonState & UISTATE_FOCUSED) != 0) && (GetFocusedTextColor () != 0)) + clrColor = GetFocusedTextColor (); + + int iFont = GetFont (); + if (((m_uButtonState & UISTATE_PUSHED) != 0) && (GetPushedFont () != -1)) + iFont = GetPushedFont (); + else if (((m_uButtonState & UISTATE_HOT) != 0) && (GetHotFont () != -1)) + iFont = GetHotFont (); + else if (((m_uButtonState & UISTATE_FOCUSED) != 0) && (GetFocusedFont () != -1)) + iFont = GetFocusedFont (); + + if (m_bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rc, sText, clrColor, nullptr, nullptr, nLinks, iFont, m_uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rc, sText, clrColor, iFont, m_uTextStyle); + } + + void CButtonUI::PaintBkColor (HDC hDC) { + if ((m_uButtonState & UISTATE_DISABLED) != 0) { + if (m_dwDisabledBkColor != 0) { + CRenderEngine::DrawColor (hDC, m_rcPaint, GetAdjustColor (m_dwDisabledBkColor)); + return; + } + } else if ((m_uButtonState & UISTATE_PUSHED) != 0) { + if (m_dwPushedBkColor != 0) { + CRenderEngine::DrawColor (hDC, m_rcPaint, GetAdjustColor (m_dwPushedBkColor)); + return; + } + } else if ((m_uButtonState & UISTATE_HOT) != 0) { + if (m_dwHotBkColor != 0) { + CRenderEngine::DrawColor (hDC, m_rcPaint, GetAdjustColor (m_dwHotBkColor)); + return; + } + } + + return CControlUI::PaintBkColor (hDC); + } + + void CButtonUI::PaintStatusImage (HDC hDC) { + if (!m_sStateImage.empty () && m_nStateCount > 0) { + TDrawInfo info; + info.Parse (m_sStateImage, _T (""), m_pManager); + const TImageInfo* pImage = m_pManager->GetImageEx (info.sImageName, info.sResType, info.dwMask, info.bHSL); + if (m_sNormalImage.empty () && pImage) { + SIZE szImage = { pImage->nX, pImage->nY }; + SIZE szStatus = { pImage->nX / m_nStateCount, pImage->nY }; + if (szImage.cx > 0 && szImage.cy > 0) { + RECT rcSrc = { 0, 0, szImage.cx, szImage.cy }; + if (m_nStateCount > 0) { + int iLeft = rcSrc.left + 0 * szStatus.cx; + int iRight = iLeft + szStatus.cx; + int iTop = rcSrc.top; + int iBottom = iTop + szStatus.cy; + m_sNormalImage.Format (_T ("res='%s' restype='%s' dest='%d,%d,%d,%d' source='%d,%d,%d,%d'"), info.sImageName.c_str (), info.sResType.c_str (), info.rcDest.left, info.rcDest.top, info.rcDest.right, info.rcDest.bottom, iLeft, iTop, iRight, iBottom); + } + if (m_nStateCount > 1) { + int iLeft = rcSrc.left + 1 * szStatus.cx; + int iRight = iLeft + szStatus.cx; + int iTop = rcSrc.top; + int iBottom = iTop + szStatus.cy; + m_sHotImage.Format (_T ("res='%s' restype='%s' dest='%d,%d,%d,%d' source='%d,%d,%d,%d'"), info.sImageName.c_str (), info.sResType.c_str (), info.rcDest.left, info.rcDest.top, info.rcDest.right, info.rcDest.bottom, iLeft, iTop, iRight, iBottom); + m_sPushedImage.Format (_T ("res='%s' restype='%s' dest='%d,%d,%d,%d' source='%d,%d,%d,%d'"), info.sImageName.c_str (), info.sResType.c_str (), info.rcDest.left, info.rcDest.top, info.rcDest.right, info.rcDest.bottom, iLeft, iTop, iRight, iBottom); + } + if (m_nStateCount > 2) { + int iLeft = rcSrc.left + 2 * szStatus.cx; + int iRight = iLeft + szStatus.cx; + int iTop = rcSrc.top; + int iBottom = iTop + szStatus.cy; + m_sPushedImage.Format (_T ("res='%s' restype='%s' dest='%d,%d,%d,%d' source='%d,%d,%d,%d'"), info.sImageName.c_str (), info.sResType.c_str (), info.rcDest.left, info.rcDest.top, info.rcDest.right, info.rcDest.bottom, iLeft, iTop, iRight, iBottom); + } + if (m_nStateCount > 3) { + int iLeft = rcSrc.left + 3 * szStatus.cx; + int iRight = iLeft + szStatus.cx; + int iTop = rcSrc.top; + int iBottom = iTop + szStatus.cy; + m_sDisabledImage.Format (_T ("res='%s' restype='%s' dest='%d,%d,%d,%d' source='%d,%d,%d,%d'"), info.sImageName.c_str (), info.sResType.c_str (), info.rcDest.left, info.rcDest.top, info.rcDest.right, info.rcDest.bottom, iLeft, iTop, iRight, iBottom); + } + } + } + } + + if (IsFocused ()) m_uButtonState |= UISTATE_FOCUSED; + else m_uButtonState &= ~UISTATE_FOCUSED; + if (!IsEnabled ()) m_uButtonState |= UISTATE_DISABLED; + else m_uButtonState &= ~UISTATE_DISABLED; + if (!::IsWindowEnabled (m_pManager->GetPaintWindow ())) { + m_uButtonState &= UISTATE_DISABLED; + } + if ((m_uButtonState & UISTATE_DISABLED) != 0) { + if (!m_sDisabledImage.empty ()) { + if (!DrawImage (hDC, m_sDisabledImage)) { + } else return; + } + } else if ((m_uButtonState & UISTATE_PUSHED) != 0) { + if (!m_sPushedImage.empty ()) { + if (!DrawImage (hDC, m_sPushedImage)) { + } else return; + } + } else if ((m_uButtonState & UISTATE_HOT) != 0) { + if (!m_sHotImage.empty ()) { + if (!DrawImage (hDC, m_sHotImage)) { + } else return; + } + } else if ((m_uButtonState & UISTATE_FOCUSED) != 0) { + if (!m_sFocusedImage.empty ()) { + if (!DrawImage (hDC, m_sFocusedImage)) { + } else return; + } + } + + if (!m_sNormalImage.empty ()) { + if (!DrawImage (hDC, m_sNormalImage)) { + } + } + } + + void CButtonUI::PaintForeImage (HDC hDC) { + if ((m_uButtonState & UISTATE_PUSHED) != 0) { + if (!m_sPushedForeImage.empty ()) { + if (!DrawImage (hDC, m_sPushedForeImage)) { + } else return; + } + } else if ((m_uButtonState & UISTATE_HOT) != 0) { + if (!m_sHotForeImage.empty ()) { + if (!DrawImage (hDC, m_sHotForeImage)) { + } else return; + } + } + if (!m_sForeImage.empty ()) { + if (!DrawImage (hDC, m_sForeImage)) { + } + } + } +} \ No newline at end of file diff --git a/DuiLib/Control/UIButton.h b/DuiLib/Control/UIButton.h new file mode 100644 index 0000000..65d591d --- /dev/null +++ b/DuiLib/Control/UIButton.h @@ -0,0 +1,102 @@ +#ifndef __UIBUTTON_H__ +#define __UIBUTTON_H__ + +#pragma once + +namespace DuiLib { + class UILIB_API CButtonUI: public CLabelUI { + DECLARE_DUICONTROL (CButtonUI) + + public: + CButtonUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetControlFlags () const; + + bool Activate (); + void SetEnabled (bool bEnable = true); + void DoEvent (TEventUI& event); + + virtual string_view_t GetNormalImage (); + virtual void SetNormalImage (string_view_t pStrImage); + virtual string_view_t GetHotImage (); + virtual void SetHotImage (string_view_t pStrImage); + virtual string_view_t GetPushedImage (); + virtual void SetPushedImage (string_view_t pStrImage); + virtual string_view_t GetFocusedImage (); + virtual void SetFocusedImage (string_view_t pStrImage); + virtual string_view_t GetDisabledImage (); + virtual void SetDisabledImage (string_view_t pStrImage); + virtual string_view_t GetHotForeImage (); + virtual void SetHotForeImage (string_view_t pStrImage); + void SetStateCount (int nCount); + int GetStateCount () const; + virtual string_view_t GetStateImage (); + virtual void SetStateImage (string_view_t pStrImage); + + void BindTabIndex (int _BindTabIndex); + void BindTabLayoutName (string_view_t _TabLayoutName); + void BindTriggerTabSel (int _SetSelectIndex = -1); + void RemoveBindTabIndex (); + int GetBindTabLayoutIndex (); + string_view_t GetBindTabLayoutName (); + + void SetHotFont (int index); + int GetHotFont () const; + void SetPushedFont (int index); + int GetPushedFont () const; + void SetFocusedFont (int index); + int GetFocusedFont () const; + + void SetHotBkColor (DWORD dwColor); + DWORD GetHotBkColor () const; + void SetPushedBkColor (DWORD dwColor); + DWORD GetPushedBkColor () const; + void SetDisabledBkColor (DWORD dwColor); + DWORD GetDisabledBkColor () const; + void SetHotTextColor (DWORD dwColor); + DWORD GetHotTextColor () const; + void SetPushedTextColor (DWORD dwColor); + DWORD GetPushedTextColor () const; + void SetFocusedTextColor (DWORD dwColor); + DWORD GetFocusedTextColor () const; + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + void PaintText (HDC hDC); + + void PaintBkColor (HDC hDC); + void PaintStatusImage (HDC hDC); + void PaintForeImage (HDC hDC); + + protected: + UINT m_uButtonState = 0; + + int m_iHotFont = -1; + int m_iPushedFont = -1; + int m_iFocusedFont = -1; + + DWORD m_dwHotBkColor = 0; + DWORD m_dwPushedBkColor = 0; + DWORD m_dwDisabledBkColor = 0; + DWORD m_dwHotTextColor = 0; + DWORD m_dwPushedTextColor = 0; + DWORD m_dwFocusedTextColor = 0; + + CDuiString m_sNormalImage; + CDuiString m_sHotImage; + CDuiString m_sHotForeImage; + CDuiString m_sPushedImage; + CDuiString m_sPushedForeImage; + CDuiString m_sFocusedImage; + CDuiString m_sDisabledImage; + int m_nStateCount = 0; + CDuiString m_sStateImage; + + int m_iBindTabIndex = -1; + CDuiString m_sBindTabLayoutName; + }; + +} // namespace DuiLib + +#endif // __UIBUTTON_H__ \ No newline at end of file diff --git a/DuiLib/Control/UIColorPalette.cpp b/DuiLib/Control/UIColorPalette.cpp new file mode 100644 index 0000000..ca4e633 --- /dev/null +++ b/DuiLib/Control/UIColorPalette.cpp @@ -0,0 +1,337 @@ +#include "StdAfx.h" +#include + +namespace DuiLib { +#define HSLMAX 255 /* H,L, and S vary over 0-HSLMAX */ +#define RGBMAX 255 /* R,G, and B vary over 0-RGBMAX */ +#define HSLUNDEFINED (HSLMAX*2/3) + + /* + * Convert hue value to RGB + */ + static float HueToRGB (float v1, float v2, float vH) { + if (vH < 0.0f) vH += 1.0f; + if (vH > 1.0f) vH -= 1.0f; + if ((6.0f * vH) < 1.0f) return (v1 + (v2 - v1) * 6.0f * vH); + if ((2.0f * vH) < 1.0f) return (v2); + if ((3.0f * vH) < 2.0f) return (v1 + (v2 - v1) * ((2.0f / 3.0f) - vH) * 6.0f); + return (v1); + } + + /* + * Convert color RGB to HSL + * pHue HSL hue value [0 - 1] + * pSat HSL saturation value [0 - 1] + * pLue HSL luminance value [0 - 1] + */ + + static void RGBToHSL (DWORD clr, float *pHue, float *pSat, float *pLue) { + float R = (float) (GetRValue (clr) / 255.0f); //RGB from 0 to 255 + float G = (float) (GetGValue (clr) / 255.0f); + float B = (float) (GetBValue (clr) / 255.0f); + + float fMin = min (R, min (G, B)); //Min. value of RGB + float fMax = max (R, max (G, B)); //Max. value of RGB + float fDelta = fMax - fMin; //Delta RGB value + + float H = 0.0f, S = 0.0f, L = (fMax + fMin) / 2.0f; + + if (fDelta != 0) { + //Chromatic data... + float del_R, del_G, del_B; + + if (L < 0.5) S = fDelta / (fMax + fMin); + else S = fDelta / (2.0f - fMax - fMin); + + del_R = (((fMax - R) / 6.0f) + (fDelta / 2.0f)) / fDelta; + del_G = (((fMax - G) / 6.0f) + (fDelta / 2.0f)) / fDelta; + del_B = (((fMax - B) / 6.0f) + (fDelta / 2.0f)) / fDelta; + + if (R == fMax) H = del_B - del_G; + else if (G == fMax) H = (1.0f / 3.0f) + del_R - del_B; + else if (B == fMax) H = (2.0f / 3.0f) + del_G - del_R; + + if (H < 0.0f) H += 1.0f; + if (H > 1.0f) H -= 1.0f; + } + + *pHue = H; + *pSat = S; + *pLue = L; + } + + /* + * Convert color HSL to RGB + * H HSL hue value [0 - 1] + * S HSL saturation value [0 - 1] + * L HSL luminance value [0 - 1] + */ + static DWORD HSLToRGB (float H, float S, float L) { + BYTE R, G, B; + + if (S == 0) { + //HSL from 0 to 1 + R = G = B = (BYTE) (L * 255.0f); //RGB results from 0 to 255 + } else { + float var_2 = (L < 0.5 ? (L * (1.0f + S)) : ((L + S) - (S * L))); + float var_1 = (2.0f * L - var_2); + + R = (BYTE) (255.0f * HueToRGB (var_1, var_2, H + (1.0f / 3.0f))); + G = (BYTE) (255.0f * HueToRGB (var_1, var_2, H)); + B = (BYTE) (255.0f * HueToRGB (var_1, var_2, H - (1.0f / 3.0f))); + } + + return RGB (R, G, B); + } + + /* + * _HSLToRGB color HSL value to RGB + * clr RGB color value + * nHue HSL hue value [0 - 360] + * nSat HSL saturation value [0 - 200] + * nLue HSL luminance value [0 - 200] + */ +#define _HSLToRGB(h,s,l) (0xFF << 24 | HSLToRGB((float)h / 360.0f,(float)s / 200.0f,l / 200.0f)) + + /////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CColorPaletteUI) + + CColorPaletteUI::CColorPaletteUI () {} + + CColorPaletteUI::~CColorPaletteUI () { + if (m_pBits) free (m_pBits); + + if (m_hMemBitmap) { + ::DeleteObject (m_hMemBitmap); + } + + } + + DWORD CColorPaletteUI::GetSelectColor () { + DWORD dwColor = _HSLToRGB (m_nCurH, m_nCurS, m_nCurB); + return 0xFF << 24 | GetRValue (dwColor) << 16 | GetGValue (dwColor) << 8 | GetBValue (dwColor); + } + + void CColorPaletteUI::SetSelectColor (DWORD dwColor) { + float H = 0, S = 0, B = 0; + COLORREF dwBkClr = RGB (GetBValue (dwColor), GetGValue (dwColor), GetRValue (dwColor)); + RGBToHSL (dwBkClr, &H, &S, &B); + m_nCurH = (int) (H * 360); + m_nCurS = (int) (S * 200); + m_nCurB = (int) (B * 200); + UpdatePalletData (); + NeedUpdate (); + } + + string_view_t CColorPaletteUI::GetClass () const { + return _T ("ColorPaletteUI"); + } + + LPVOID CColorPaletteUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_COLORPALETTE) return static_cast(this); + return CControlUI::GetInterface (pstrName); + } + + void CColorPaletteUI::SetPalletHeight (int nHeight) { + m_nPalletHeight = nHeight; + } + int CColorPaletteUI::GetPalletHeight () const { + return m_nPalletHeight; + } + void CColorPaletteUI::SetBarHeight (int nHeight) { + if (nHeight > 150) { + nHeight = 150; //߶ȣڵǰƣnheight190Խʱ + } + m_nBarHeight = nHeight; + } + int CColorPaletteUI::GetBarHeight () const { + return m_nBarHeight; + } + + void CColorPaletteUI::SetThumbImage (string_view_t pszImage) { + if (m_strThumbImage != pszImage) { + m_strThumbImage = pszImage; + NeedUpdate (); + } + } + + string_view_t CColorPaletteUI::GetThumbImage () const { + return m_strThumbImage; + } + + void CColorPaletteUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("palletheight")) SetPalletHeight (_ttoi (pstrValue.data ())); + else if (pstrName == _T ("barheight")) SetBarHeight (_ttoi (pstrValue.data ())); + else if (pstrName == _T ("thumbimage")) SetThumbImage (pstrValue); + else CControlUI::SetAttribute (pstrName, pstrValue); + } + + void CColorPaletteUI::DoInit () { + m_MemDc = CreateCompatibleDC (GetManager ()->GetPaintDC ()); + m_hMemBitmap = CreateCompatibleBitmap (GetManager ()->GetPaintDC (), 400, 360); + SelectObject (m_MemDc, m_hMemBitmap); + + ::GetObject (m_hMemBitmap, sizeof (m_bmInfo), &m_bmInfo); + DWORD dwSize = m_bmInfo.bmHeight * m_bmInfo.bmWidthBytes; + m_pBits = (BYTE *) malloc (dwSize); + ::GetBitmapBits (m_hMemBitmap, dwSize, m_pBits); + } + + void CColorPaletteUI::SetPos (RECT rc, bool bNeedInvalidate) { + CControlUI::SetPos (rc, bNeedInvalidate); + + m_ptLastPalletMouse.x = m_nCurH * (m_rcItem.right - m_rcItem.left) / 360 + m_rcItem.left; + m_ptLastPalletMouse.y = (200 - m_nCurB) * m_nPalletHeight / 200 + m_rcItem.top; + + UpdatePalletData (); + UpdateBarData (); + } + + void CColorPaletteUI::DoEvent (TEventUI& event) { + CControlUI::DoEvent (event); + + if (event.Type == UIEVENT_BUTTONDOWN) { + if (event.ptMouse.x >= m_rcItem.left && event.ptMouse.y >= m_rcItem.top && + event.ptMouse.x < m_rcItem.right && event.ptMouse.y < m_rcItem.top + m_nPalletHeight) { + int x = (event.ptMouse.x - m_rcItem.left) * 360 / (m_rcItem.right - m_rcItem.left); + int y = (event.ptMouse.y - m_rcItem.top) * 200 / m_nPalletHeight; + x = min (max (x, 0), 360); + y = min (max (y, 0), 200); + + m_ptLastPalletMouse = event.ptMouse; + if (m_ptLastPalletMouse.x < m_rcItem.left) m_ptLastPalletMouse.x = m_rcItem.left; + if (m_ptLastPalletMouse.x > m_rcItem.right) m_ptLastPalletMouse.x = m_rcItem.right; + if (m_ptLastPalletMouse.y < m_rcItem.top) m_ptLastPalletMouse.y = m_rcItem.top; + if (m_ptLastPalletMouse.y > m_rcItem.top + m_nPalletHeight) m_ptLastPalletMouse.y = m_rcItem.top + m_nPalletHeight; + + m_nCurH = x; + m_nCurB = 200 - y; + + m_uButtonState |= UISTATE_PUSHED; + m_bIsInPallet = true; + m_bIsInBar = false; + + UpdateBarData (); + } + + if (event.ptMouse.x >= m_rcItem.left && event.ptMouse.y >= m_rcItem.bottom - m_nBarHeight && + event.ptMouse.x < m_rcItem.right && event.ptMouse.y < m_rcItem.bottom) { + m_nCurS = (event.ptMouse.x - m_rcItem.left) * 200 / (m_rcItem.right - m_rcItem.left); + m_uButtonState |= UISTATE_PUSHED; + m_bIsInBar = true; + m_bIsInPallet = false; + UpdatePalletData (); + } + + Invalidate (); + return; + } + if (event.Type == UIEVENT_BUTTONUP) { + if ((m_uButtonState | UISTATE_PUSHED) && (IsEnabled ())) { + m_pManager->SendNotify (this, DUI_MSGTYPE_COLORCHANGED, GetSelectColor (), 0); + } + + m_uButtonState &= ~UISTATE_PUSHED; + m_bIsInPallet = false; + m_bIsInBar = false; + + Invalidate (); + return; + } + if (event.Type == UIEVENT_MOUSEMOVE) { + if (!(m_uButtonState &UISTATE_PUSHED)) { + m_bIsInBar = false; + m_bIsInPallet = false; + } + if (m_bIsInPallet == true) { + POINT pt = event.ptMouse; + pt.x -= m_rcItem.left; + pt.y -= m_rcItem.top; + + if (pt.x >= 0 && pt.y >= 0 && pt.x <= m_rcItem.right && pt.y <= m_rcItem.top + m_nPalletHeight) { + int x = pt.x * 360 / (m_rcItem.right - m_rcItem.left); + int y = pt.y * 200 / m_nPalletHeight; + x = min (max (x, 0), 360); + y = min (max (y, 0), 200); + + m_ptLastPalletMouse = event.ptMouse; + if (m_ptLastPalletMouse.x < m_rcItem.left) m_ptLastPalletMouse.x = m_rcItem.left; + if (m_ptLastPalletMouse.x > m_rcItem.right) m_ptLastPalletMouse.x = m_rcItem.right; + if (m_ptLastPalletMouse.y < m_rcItem.top) m_ptLastPalletMouse.y = m_rcItem.top; + if (m_ptLastPalletMouse.y >= m_rcItem.top + m_nPalletHeight) m_ptLastPalletMouse.y = m_rcItem.top + m_nPalletHeight; + + m_nCurH = x; + m_nCurB = 200 - y; + + UpdateBarData (); + } + } else if (m_bIsInBar == true) { + m_nCurS = (event.ptMouse.x - m_rcItem.left) * 200 / (m_rcItem.right - m_rcItem.left); + m_nCurS = min (max (m_nCurS, 0), 200); + UpdatePalletData (); + } + + Invalidate (); + return; + } + + } + + void CColorPaletteUI::PaintBkColor (HDC hDC) { + PaintPallet (hDC); + } + + void CColorPaletteUI::PaintPallet (HDC hDC) { + int nSaveDC = ::SaveDC (hDC); + + ::SetStretchBltMode (hDC, HALFTONE); + //ģʽڴͼؼ + StretchBlt (hDC, m_rcItem.left, m_rcItem.top, m_rcItem.right - m_rcItem.left, m_nPalletHeight, m_MemDc, 0, 1, 360, 200, SRCCOPY); + StretchBlt (hDC, m_rcItem.left, m_rcItem.bottom - m_nBarHeight, m_rcItem.right - m_rcItem.left, m_nBarHeight, m_MemDc, 0, 210, 200, m_nBarHeight, SRCCOPY); + + RECT rcCurSorPaint = { m_ptLastPalletMouse.x - 4, m_ptLastPalletMouse.y - 4, m_ptLastPalletMouse.x + 4, m_ptLastPalletMouse.y + 4 }; + CRenderEngine::DrawImageString (hDC, m_pManager, rcCurSorPaint, m_rcPaint, m_strThumbImage); + + rcCurSorPaint.left = m_rcItem.left + m_nCurS * (m_rcItem.right - m_rcItem.left) / 200 - 4; + rcCurSorPaint.right = m_rcItem.left + m_nCurS * (m_rcItem.right - m_rcItem.left) / 200 + 4; + rcCurSorPaint.top = m_rcItem.bottom - m_nBarHeight / 2 - 4; + rcCurSorPaint.bottom = m_rcItem.bottom - m_nBarHeight / 2 + 4; + CRenderEngine::DrawImageString (hDC, m_pManager, rcCurSorPaint, m_rcPaint, m_strThumbImage); + ::RestoreDC (hDC, nSaveDC); + } + + void CColorPaletteUI::UpdatePalletData () { + for (int y = 0; y < 200; ++y) { + for (int x = 0; x < 360; ++x) { + BYTE *pPiexl = LPBYTE (m_pBits) + ((200 - y)*m_bmInfo.bmWidthBytes) + ((x*m_bmInfo.bmBitsPixel) / 8); + DWORD dwColor = _HSLToRGB (x, m_nCurS, y); + if (dwColor == 0xFF000000) dwColor = 0xFF000001; + pPiexl[0] = GetBValue (dwColor); + pPiexl[1] = GetGValue (dwColor); + pPiexl[2] = GetRValue (dwColor); + } + } + + SetBitmapBits (m_hMemBitmap, m_bmInfo.bmWidthBytes * m_bmInfo.bmHeight, m_pBits); + } + + + void CColorPaletteUI::UpdateBarData () { + //ﻭBar + for (int y = 0; y < m_nBarHeight; ++y) { + for (int x = 0; x < 200; ++x) { + BYTE *pPiexl = LPBYTE (m_pBits) + ((210 + y)*m_bmInfo.bmWidthBytes) + ((x*m_bmInfo.bmBitsPixel) / 8); + DWORD dwColor = _HSLToRGB (m_nCurH, x, m_nCurB); + if (dwColor == 0xFF000000) dwColor = 0xFF000001; + pPiexl[0] = GetBValue (dwColor); + pPiexl[1] = GetGValue (dwColor); + pPiexl[2] = GetRValue (dwColor); + } + } + + SetBitmapBits (m_hMemBitmap, m_bmInfo.bmWidthBytes * m_bmInfo.bmHeight, m_pBits); + } + +} \ No newline at end of file diff --git a/DuiLib/Control/UIColorPalette.h b/DuiLib/Control/UIColorPalette.h new file mode 100644 index 0000000..edf9b9a --- /dev/null +++ b/DuiLib/Control/UIColorPalette.h @@ -0,0 +1,64 @@ +#ifndef UI_PALLET_H +#define UI_PALLET_H +#pragma once + +namespace DuiLib { + ///////////////////////////////////////////////////////////////////////////////////// + // + class UILIB_API CColorPaletteUI: public CControlUI { + DECLARE_DUICONTROL (CColorPaletteUI) + public: + CColorPaletteUI (); + virtual ~CColorPaletteUI (); + + //ȡձѡɫֱduilibɫ + DWORD GetSelectColor (); + void SetSelectColor (DWORD dwColor); + + virtual string_view_t GetClass () const; + virtual LPVOID GetInterface (string_view_t pstrName); + virtual void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + ///ȡ Palletɫ棩ĸ߶ + void SetPalletHeight (int nHeight); + int GetPalletHeight () const; + + ///ȡ ·Barѡĸ߶ + void SetBarHeight (int nHeight); + int GetBarHeight () const; + ///ȡ ѡͼ· + void SetThumbImage (string_view_t pszImage); + string_view_t GetThumbImage () const; + + virtual void SetPos (RECT rc, bool bNeedInvalidate = true); + virtual void DoInit (); + virtual void DoEvent (TEventUI& event); + virtual void PaintBkColor (HDC hDC); + virtual void PaintPallet (HDC hDC); + + protected: + // + void UpdatePalletData (); + void UpdateBarData (); + + private: + HDC m_MemDc; + HBITMAP m_hMemBitmap = NULL; + BITMAP m_bmInfo = { 0 }; + BYTE *m_pBits = nullptr; + UINT m_uButtonState = 0; + bool m_bIsInBar = false; + bool m_bIsInPallet = false; + int m_nCurH = 180; + int m_nCurS = 200; + int m_nCurB = 100; + + int m_nPalletHeight = 200; + int m_nBarHeight = 10; + POINT m_ptLastPalletMouse; + POINT m_ptLastBarMouse; + CDuiString m_strThumbImage; + }; +} + +#endif // UI_PALLET_H \ No newline at end of file diff --git a/DuiLib/Control/UICombo.cpp b/DuiLib/Control/UICombo.cpp new file mode 100644 index 0000000..6540f8f --- /dev/null +++ b/DuiLib/Control/UICombo.cpp @@ -0,0 +1,1048 @@ +#include "StdAfx.h" + +namespace DuiLib { + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + class CComboWnd: public CWindowWnd, public INotifyUI { + public: + void Init (CComboUI* pOwner); + string_view_t GetWindowClassName () const; + void OnFinalMessage (HWND hWnd); + + LRESULT HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam); + void Notify (TNotifyUI& msg) override; + + void EnsureVisible (int iIndex); + void Scroll (int dx, int dy); + +#if(_WIN32_WINNT >= 0x0501) + virtual UINT GetClassStyle () const; +#endif + bool IsHitItem (POINT ptMouse); + public: + CPaintManagerUI m_pm; + CComboUI* m_pOwner; + CVerticalLayoutUI* m_pLayout; + int m_iOldSel; + bool m_bHitItem; + }; + + void CComboWnd::Notify (TNotifyUI& msg) { + if (msg.sType == _T ("windowinit")) { + EnsureVisible (m_iOldSel); + } else if (msg.sType == _T ("click")) { + // Դ + CDuiString sName = msg.pSender->GetName (); + CControlUI* pCtrl = msg.pSender; + while (pCtrl) { + IListItemUI* pListItem = (IListItemUI*) pCtrl->GetInterface (DUI_CTRL_LISTITEM); + if (pListItem) { + break; + } + pCtrl = pCtrl->GetParent (); + } + if (m_pOwner->GetManager () != NULL) m_pOwner->GetManager ()->SendNotify (msg.pSender, DUI_MSGTYPE_CLICK, 0, 0); + } + } + + void CComboWnd::Init (CComboUI* pOwner) { + m_bHitItem = false; + m_pOwner = pOwner; + m_pLayout = nullptr; + m_iOldSel = m_pOwner->GetCurSel (); + + // Position the popup window in absolute space + SIZE szDrop = m_pOwner->GetDropBoxSize (); + RECT rcOwner = pOwner->GetPos (); + RECT rc = rcOwner; + rc.top = rc.bottom; // leftbottomλΪ + rc.bottom = rc.top + szDrop.cy; // 㵯ڸ߶ + if (szDrop.cx > 0) rc.right = rc.left + szDrop.cx; // 㵯ڿ + + SIZE szAvailable = { rc.right - rc.left, rc.bottom - rc.top }; + int cyFixed = 0; + for (int it = 0; it < pOwner->GetCount (); it++) { + CControlUI* pControl = static_cast(pOwner->GetItemAt (it)); + if (!pControl->IsVisible ()) continue; + SIZE sz = pControl->EstimateSize (szAvailable); + cyFixed += sz.cy; + } + cyFixed += 4; + rc.bottom = rc.top + MIN (cyFixed, szDrop.cy); + + ::MapWindowRect (pOwner->GetManager ()->GetPaintWindow (), HWND_DESKTOP, &rc); + + MONITORINFO oMonitor = {}; + oMonitor.cbSize = sizeof (oMonitor); + ::GetMonitorInfo (::MonitorFromWindow (GetHWND (), MONITOR_DEFAULTTOPRIMARY), &oMonitor); + RECT rcWork = oMonitor.rcWork; + if (rc.bottom > rcWork.bottom) { + rc.left = rcOwner.left; + rc.right = rcOwner.right; + if (szDrop.cx > 0) rc.right = rc.left + szDrop.cx; + rc.top = rcOwner.top - MIN (cyFixed, szDrop.cy); + rc.bottom = rcOwner.top; + ::MapWindowRect (pOwner->GetManager ()->GetPaintWindow (), HWND_DESKTOP, &rc); + } + + Create (pOwner->GetManager ()->GetPaintWindow (), _T (""), WS_POPUP, WS_EX_TOOLWINDOW, rc); + // HACK: Don't deselect the parent's caption + HWND hWndParent = m_hWnd; + while (::GetParent (hWndParent) != NULL) hWndParent = ::GetParent (hWndParent); + ::ShowWindow (m_hWnd, SW_SHOW); + ::SendMessage (hWndParent, WM_NCACTIVATE, TRUE, 0L); + } + + string_view_t CComboWnd::GetWindowClassName () const { + return _T ("ComboWnd"); + } + + void CComboWnd::OnFinalMessage (HWND hWnd) { + m_pOwner->m_pWindow = nullptr; + m_pOwner->m_uButtonState &= ~UISTATE_PUSHED; + m_pOwner->Invalidate (); + delete this; + } + + bool CComboWnd::IsHitItem (POINT ptMouse) { + CControlUI* pControl = m_pm.FindControl (ptMouse); + if (pControl) { + LPVOID pInterface = pControl->GetInterface (DUI_CTRL_SCROLLBAR); + if (pInterface) return false; + + while (pControl) { + IListItemUI* pListItem = (IListItemUI*) pControl->GetInterface (DUI_CTRL_LISTITEM); + if (pListItem) { + return true; + } + pControl = pControl->GetParent (); + } + } + + return false; + } + + LRESULT CComboWnd::HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (uMsg == WM_CREATE) { + m_pm.SetForceUseSharedRes (true); + m_pm.Init (m_hWnd); + // The trick is to add the items to the new container. Their owner gets + // reassigned by this operation - which is why it is important to reassign + // the items back to the righfull owner/manager when the window closes. + m_pLayout = new CVerticalLayoutUI; + m_pLayout->SetManager (&m_pm, nullptr, true); + string_view_t pDefaultAttributes = m_pOwner->GetManager ()->GetDefaultAttributeList (_T ("VerticalLayout")); + if (!pDefaultAttributes.empty ()) { + m_pLayout->ApplyAttributeList (pDefaultAttributes); + } + m_pLayout->SetInset ({ 1, 1, 1, 1 }); + m_pLayout->SetBkColor (0xFFFFFFFF); + m_pLayout->SetBorderColor (0xFFC6C7D2); + m_pLayout->SetBorderSize (1); + m_pLayout->SetAutoDestroy (false); + m_pLayout->EnableScrollBar (); + m_pLayout->ApplyAttributeList (m_pOwner->GetDropBoxAttributeList ()); + for (int i = 0; i < m_pOwner->GetCount (); i++) { + m_pLayout->Add (static_cast(m_pOwner->GetItemAt (i))); + } + CShadowUI *pShadow = m_pOwner->GetManager ()->GetShadow (); + if (pShadow && m_pOwner) { + pShadow->CopyShadow (m_pm.GetShadow ()); + m_pm.GetShadow ()->ShowShadow (m_pOwner->IsShowShadow ()); + } + m_pm.AttachDialog (m_pLayout); + m_pm.AddNotifier (this); + return 0; + } else if (uMsg == WM_CLOSE) { + m_pOwner->SetManager (m_pOwner->GetManager (), m_pOwner->GetParent (), false); + RECT rcnullptr = { 0 }; + for (int i = 0; i < m_pOwner->GetCount (); i++) static_cast(m_pOwner->GetItemAt (i))->SetPos (rcnullptr); + m_pOwner->SetFocus (); + } else if (uMsg == WM_LBUTTONDOWN) { + POINT pt = { 0 }; + ::GetCursorPos (&pt); + ::ScreenToClient (m_pm.GetPaintWindow (), &pt); + m_bHitItem = IsHitItem (pt); + } else if (uMsg == WM_LBUTTONUP) { + POINT pt = { 0 }; + ::GetCursorPos (&pt); + ::ScreenToClient (m_pm.GetPaintWindow (), &pt); + if (m_bHitItem && IsHitItem (pt)) { + PostMessage (WM_KILLFOCUS); + } + m_bHitItem = false; + } else if (uMsg == WM_KEYDOWN) { + switch (wParam) { + case VK_ESCAPE: + m_pOwner->SelectItem (m_iOldSel, true); + EnsureVisible (m_iOldSel); + case VK_RETURN: + PostMessage (WM_KILLFOCUS); + break; + default: + TEventUI event; + event.Type = UIEVENT_KEYDOWN; + event.chKey = (TCHAR) wParam; + m_pOwner->DoEvent (event); + EnsureVisible (m_pOwner->GetCurSel ()); + return 0; + } + } else if (uMsg == WM_MOUSEWHEEL) { + int zDelta = (int) (short) HIWORD (wParam); + TEventUI event = { 0 }; + event.Type = UIEVENT_SCROLLWHEEL; + event.wParam = MAKELPARAM (zDelta < 0 ? SB_LINEDOWN : SB_LINEUP, 0); + event.lParam = lParam; + event.dwTimestamp = ::GetTickCount (); + if (m_pOwner->GetScrollSelect ()) { + m_pOwner->DoEvent (event); + EnsureVisible (m_pOwner->GetCurSel ()); + return 0; + } else { + m_pLayout->DoEvent (event); + return 0; + } + } else if (uMsg == WM_KILLFOCUS) { + if (m_hWnd != (HWND) wParam) PostMessage (WM_CLOSE); + } + + LRESULT lRes = 0; + if (m_pm.MessageHandler (uMsg, wParam, lParam, lRes)) return lRes; + return CWindowWnd::HandleMessage (uMsg, wParam, lParam); + } + + void CComboWnd::EnsureVisible (int iIndex) { + int nCurSel = m_pOwner->GetCurSel (); + if (nCurSel < 0) return; + m_pLayout->FindSelectable (nCurSel, false); + RECT rcItem = m_pLayout->GetItemAt (iIndex)->GetPos (); + RECT rcList = m_pLayout->GetPos (); + CScrollBarUI* pHorizontalScrollBar = m_pLayout->GetHorizontalScrollBar (); + if (pHorizontalScrollBar && pHorizontalScrollBar->IsVisible ()) rcList.bottom -= pHorizontalScrollBar->GetFixedHeight (); + int iPos = m_pLayout->GetScrollPos ().cy; + if (rcItem.top >= rcList.top && rcItem.bottom < rcList.bottom) return; + int dx = 0; + if (rcItem.top < rcList.top) dx = rcItem.top - rcList.top; + if (rcItem.bottom > rcList.bottom) dx = rcItem.bottom - rcList.bottom; + Scroll (0, dx); + } + + void CComboWnd::Scroll (int dx, int dy) { + if (dx == 0 && dy == 0) return; + SIZE sz = m_pLayout->GetScrollPos (); + m_pLayout->SetScrollPos ({ sz.cx + dx, sz.cy + dy }); + } + +#if(_WIN32_WINNT >= 0x0501) + UINT CComboWnd::GetClassStyle () const { + if (m_pOwner->IsShowShadow ()) { + return __super::GetClassStyle (); + + } else { + return __super::GetClassStyle () | CS_DROPSHADOW; + } + } +#endif + //////////////////////////////////////////////////////// + IMPLEMENT_DUICONTROL (CComboUI) + + CComboUI::CComboUI (): m_uTextStyle (DT_VCENTER | DT_SINGLELINE) { + m_ListInfo.nColumns = 0; + m_ListInfo.nFont = -1; + m_ListInfo.uTextStyle = DT_VCENTER; + m_ListInfo.dwTextColor = 0xFF000000; + m_ListInfo.dwBkColor = 0; + m_ListInfo.bAlternateBk = false; + m_ListInfo.dwSelectedTextColor = 0xFF000000; + m_ListInfo.dwSelectedBkColor = 0xFFC1E3FF; + m_ListInfo.dwHotTextColor = 0xFF000000; + m_ListInfo.dwHotBkColor = 0xFFE9F5FF; + m_ListInfo.dwDisabledTextColor = 0xFFCCCCCC; + m_ListInfo.dwDisabledBkColor = 0xFFFFFFFF; + m_ListInfo.dwLineColor = 0; + m_ListInfo.bShowHtml = false; + m_ListInfo.bMultiExpandable = false; + ::ZeroMemory (&m_ListInfo.rcTextPadding, sizeof (m_ListInfo.rcTextPadding)); + ::ZeroMemory (&m_ListInfo.rcColumn, sizeof (m_ListInfo.rcColumn)); + } + + string_view_t CComboUI::GetClass () const { + return _T ("ComboUI"); + } + + LPVOID CComboUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_COMBO) return static_cast(this); + if (pstrName == _T ("IListOwner")) return static_cast(this); + return CContainerUI::GetInterface (pstrName); + } + + UINT CComboUI::GetControlFlags () const { + return UIFLAG_TABSTOP | UIFLAG_SETCURSOR; + } + + void CComboUI::DoInit () {} + + UINT CComboUI::GetListType () { + return LT_COMBO; + } + + TListInfoUI* CComboUI::GetListInfo () { + return &m_ListInfo; + } + + int CComboUI::GetCurSel () const { + return m_iCurSel; + } + + bool CComboUI::SelectItem (int iIndex, bool bTakeFocus) { + if (iIndex == m_iCurSel) return true; + int iOldSel = m_iCurSel; + if (m_iCurSel >= 0) { + CControlUI* pControl = static_cast(m_items[m_iCurSel]); + if (!pControl) return false; + IListItemUI* pListItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (pListItem) pListItem->Select (false); + m_iCurSel = -1; + } + if (iIndex < 0) return false; + if (m_items.GetSize () == 0) return false; + if (iIndex >= m_items.GetSize ()) iIndex = m_items.GetSize () - 1; + CControlUI* pControl = static_cast(m_items[iIndex]); + if (!pControl || !pControl->IsEnabled ()) return false; + IListItemUI* pListItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (!pListItem) return false; + m_iCurSel = iIndex; + if (m_pWindow || bTakeFocus) pControl->SetFocus (); + pListItem->Select (true); + if (m_pManager) m_pManager->SendNotify (this, DUI_MSGTYPE_ITEMSELECT, m_iCurSel, iOldSel); + Invalidate (); + + return true; + } + + bool CComboUI::SelectMultiItem (int iIndex, bool bTakeFocus) { + return SelectItem (iIndex, bTakeFocus); + } + + bool CComboUI::UnSelectItem (int iIndex, bool bOthers) { + return false; + } + + bool CComboUI::SetItemIndex (CControlUI* pControl, int iIndex) { + int iOrginIndex = GetItemIndex (pControl); + if (iOrginIndex == -1) return false; + if (iOrginIndex == iIndex) return true; + + IListItemUI* pSelectedListItem = nullptr; + if (m_iCurSel >= 0) pSelectedListItem = + static_cast(GetItemAt (m_iCurSel)->GetInterface (_T ("ListItem"))); + if (!CContainerUI::SetItemIndex (pControl, iIndex)) return false; + int iMinIndex = min (iOrginIndex, iIndex); + int iMaxIndex = max (iOrginIndex, iIndex); + for (int i = iMinIndex; i < iMaxIndex + 1; ++i) { + CControlUI* p = GetItemAt (i); + IListItemUI* pListItem = static_cast(p->GetInterface (_T ("ListItem"))); + if (pListItem) { + pListItem->SetIndex (i); + } + } + if (m_iCurSel >= 0 && pSelectedListItem) m_iCurSel = pSelectedListItem->GetIndex (); + return true; + } + + bool CComboUI::Add (CControlUI* pControl) { + IListItemUI* pListItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (pListItem) { + pListItem->SetOwner (this); + pListItem->SetIndex (m_items.GetSize ()); + } + return CContainerUI::Add (pControl); + } + + bool CComboUI::AddAt (CControlUI* pControl, int iIndex) { + if (!CContainerUI::AddAt (pControl, iIndex)) return false; + + // The list items should know about us + IListItemUI* pListItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (pListItem) { + pListItem->SetOwner (this); + pListItem->SetIndex (iIndex); + } + + for (int i = iIndex + 1; i < GetCount (); ++i) { + CControlUI* p = GetItemAt (i); + pListItem = static_cast(p->GetInterface (_T ("ListItem"))); + if (pListItem) { + pListItem->SetIndex (i); + } + } + if (m_iCurSel >= iIndex) m_iCurSel += 1; + return true; + } + + bool CComboUI::Remove (CControlUI* pControl) { + int iIndex = GetItemIndex (pControl); + if (iIndex == -1) return false; + + if (!CContainerUI::RemoveAt (iIndex)) return false; + + for (int i = iIndex; i < GetCount (); ++i) { + CControlUI* p = GetItemAt (i); + IListItemUI* pListItem = static_cast(p->GetInterface (_T ("ListItem"))); + if (pListItem) { + pListItem->SetIndex (i); + } + } + + if (iIndex == m_iCurSel && m_iCurSel >= 0) { + int iSel = m_iCurSel; + m_iCurSel = -1; + SelectItem (FindSelectable (iSel, false)); + } else if (iIndex < m_iCurSel) m_iCurSel -= 1; + return true; + } + + bool CComboUI::RemoveAt (int iIndex) { + if (!CContainerUI::RemoveAt (iIndex)) return false; + + for (int i = iIndex; i < GetCount (); ++i) { + CControlUI* p = GetItemAt (i); + IListItemUI* pListItem = static_cast(p->GetInterface (_T ("ListItem"))); + if (pListItem) pListItem->SetIndex (i); + } + + if (iIndex == m_iCurSel && m_iCurSel >= 0) { + int iSel = m_iCurSel; + m_iCurSel = -1; + SelectItem (FindSelectable (iSel, false)); + } else if (iIndex < m_iCurSel) m_iCurSel -= 1; + return true; + } + + void CComboUI::RemoveAll () { + m_iCurSel = -1; + CContainerUI::RemoveAll (); + } + + void CComboUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CContainerUI::DoEvent (event); + return; + } + + if (event.Type == UIEVENT_SETFOCUS) { + Invalidate (); + } + if (event.Type == UIEVENT_KILLFOCUS) { + Invalidate (); + } + if (event.Type == UIEVENT_BUTTONDOWN) { + if (IsEnabled ()) { + Activate (); + m_uButtonState |= UISTATE_PUSHED | UISTATE_CAPTURED; + } + return; + } + if (event.Type == UIEVENT_BUTTONUP) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + m_uButtonState &= ~UISTATE_CAPTURED; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_MOUSEMOVE) { + return; + } + if (event.Type == UIEVENT_KEYDOWN) { + switch (event.chKey) { + case VK_F4: + Activate (); + return; + case VK_UP: + SelectItem (FindSelectable (m_iCurSel - 1, false)); + return; + case VK_DOWN: + SelectItem (FindSelectable (m_iCurSel + 1, true)); + return; + case VK_PRIOR: + SelectItem (FindSelectable (m_iCurSel - 1, false)); + return; + case VK_NEXT: + SelectItem (FindSelectable (m_iCurSel + 1, true)); + return; + case VK_HOME: + SelectItem (FindSelectable (0, false)); + return; + case VK_END: + SelectItem (FindSelectable (GetCount () - 1, true)); + return; + } + } + if (event.Type == UIEVENT_SCROLLWHEEL) { + if (GetScrollSelect ()) { + bool bDownward = LOWORD (event.wParam) == SB_LINEDOWN; + SelectItem (FindSelectable (m_iCurSel + (bDownward ? 1 : -1), bDownward)); + } + return; + } + if (event.Type == UIEVENT_CONTEXTMENU) { + return; + } + if (event.Type == UIEVENT_MOUSEENTER) { + if (::PtInRect (&m_rcItem, event.ptMouse)) { + if ((m_uButtonState & UISTATE_HOT) == 0) + m_uButtonState |= UISTATE_HOT; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_MOUSELEAVE) { + if ((m_uButtonState & UISTATE_HOT) != 0) { + m_uButtonState &= ~UISTATE_HOT; + Invalidate (); + } + return; + } + CControlUI::DoEvent (event); + } + + SIZE CComboUI::EstimateSize (SIZE szAvailable) { + if (m_cxyFixed.cy == 0) return { m_cxyFixed.cx, m_pManager->GetDefaultFontInfo ()->tm.tmHeight + 12 }; + return CControlUI::EstimateSize (szAvailable); + } + + bool CComboUI::Activate () { + if (!CControlUI::Activate ()) return false; + if (m_pManager) m_pManager->SendNotify (this, DUI_MSGTYPE_PREDROPDOWN); + if (m_pWindow) return true; + m_pWindow = new CComboWnd (); + ASSERT (m_pWindow); + m_pWindow->Init (this); + if (m_pManager) m_pManager->SendNotify (this, DUI_MSGTYPE_DROPDOWN); + Invalidate (); + return true; + } + + CDuiString CComboUI::GetText () const { + if (m_iCurSel < 0 || m_iCurSel >= m_items.GetSize ()) { + return __super::GetText (); + } else { + CControlUI* pControl = static_cast(m_items[m_iCurSel]); + return pControl->GetText (); + } + } + + void CComboUI::SetText (string_view_t pstrText) { + int iOldSel = m_iCurSel; + m_iCurSel = -2; + for (int i = 0; i < m_items.GetSize (); ++i) { + CControlUI *ctrl = static_cast(m_items[i]); + if (!ctrl) + continue; + if (ctrl->GetText () == pstrText && ctrl->IsEnabled () && m_iCurSel == -2) + m_iCurSel = i; + IListItemUI *item = static_cast(ctrl->GetInterface (_T ("ListItem"))); + if (item) + item->Select (m_iCurSel == i); + } + CContainerUI::SetText (pstrText); + if (m_pManager && m_iCurSel >= 0) + m_pManager->SendNotify (this, DUI_MSGTYPE_ITEMSELECT, m_iCurSel, iOldSel); + } + + void CComboUI::SetEnabled (bool bEnable) { + CContainerUI::SetEnabled (bEnable); + if (!IsEnabled ()) m_uButtonState = 0; + } + + string_view_t CComboUI::GetDropBoxAttributeList () { + return m_sDropBoxAttributes; + } + + void CComboUI::SetDropBoxAttributeList (string_view_t pstrList) { + m_sDropBoxAttributes = pstrList; + } + + SIZE CComboUI::GetDropBoxSize () const { + return m_szDropBox; + } + + void CComboUI::SetDropBoxSize (SIZE szDropBox) { + m_szDropBox = szDropBox; + } + + void CComboUI::SetTextStyle (UINT uStyle) { + m_uTextStyle = uStyle; + Invalidate (); + } + + UINT CComboUI::GetTextStyle () const { + return m_uTextStyle; + } + + void CComboUI::SetTextColor (DWORD dwTextColor) { + m_dwTextColor = dwTextColor; + Invalidate (); + } + + DWORD CComboUI::GetTextColor () const { + return m_dwTextColor; + } + + void CComboUI::SetDisabledTextColor (DWORD dwTextColor) { + m_dwDisabledTextColor = dwTextColor; + Invalidate (); + } + + DWORD CComboUI::GetDisabledTextColor () const { + return m_dwDisabledTextColor; + } + + void CComboUI::SetFont (int index) { + m_iFont = index; + Invalidate (); + } + + int CComboUI::GetFont () const { + return m_iFont; + } + + RECT CComboUI::GetTextPadding () const { + return m_rcTextPadding; + } + + void CComboUI::SetTextPadding (RECT rc) { + m_rcTextPadding = rc; + Invalidate (); + } + + bool CComboUI::IsShowHtml () { + return m_bShowHtml; + } + + void CComboUI::SetShowHtml (bool bShowHtml) { + if (m_bShowHtml == bShowHtml) return; + + m_bShowHtml = bShowHtml; + Invalidate (); + } + + bool CComboUI::IsShowShadow () { + return m_bShowShadow; + } + + void CComboUI::SetShowShadow (bool bShow) { + if (m_bShowShadow == bShow) return; + + m_bShowShadow = bShow; + Invalidate (); + } + + string_view_t CComboUI::GetNormalImage () const { + return m_sNormalImage; + } + + void CComboUI::SetNormalImage (string_view_t pStrImage) { + m_sNormalImage = pStrImage; + Invalidate (); + } + + string_view_t CComboUI::GetHotImage () const { + return m_sHotImage; + } + + void CComboUI::SetHotImage (string_view_t pStrImage) { + m_sHotImage = pStrImage; + Invalidate (); + } + + string_view_t CComboUI::GetPushedImage () const { + return m_sPushedImage; + } + + void CComboUI::SetPushedImage (string_view_t pStrImage) { + m_sPushedImage = pStrImage; + Invalidate (); + } + + string_view_t CComboUI::GetFocusedImage () const { + return m_sFocusedImage; + } + + void CComboUI::SetFocusedImage (string_view_t pStrImage) { + m_sFocusedImage = pStrImage; + Invalidate (); + } + + string_view_t CComboUI::GetDisabledImage () const { + return m_sDisabledImage; + } + + void CComboUI::SetDisabledImage (string_view_t pStrImage) { + m_sDisabledImage = pStrImage; + Invalidate (); + } + + bool CComboUI::GetScrollSelect () { + return m_bScrollSelect; + } + + void CComboUI::SetScrollSelect (bool bScrollSelect) { + m_bScrollSelect = bScrollSelect; + } + + void CComboUI::SetItemFont (int index) { + m_ListInfo.nFont = index; + Invalidate (); + } + + void CComboUI::SetItemTextStyle (UINT uStyle) { + m_ListInfo.uTextStyle = uStyle; + Invalidate (); + } + + RECT CComboUI::GetItemTextPadding () const { + return m_ListInfo.rcTextPadding; + } + + void CComboUI::SetItemTextPadding (RECT rc) { + m_ListInfo.rcTextPadding = rc; + Invalidate (); + } + + void CComboUI::SetItemTextColor (DWORD dwTextColor) { + m_ListInfo.dwTextColor = dwTextColor; + Invalidate (); + } + + void CComboUI::SetItemBkColor (DWORD dwBkColor) { + m_ListInfo.dwBkColor = dwBkColor; + } + + void CComboUI::SetItemBkImage (string_view_t pStrImage) { + m_ListInfo.sBkImage = pStrImage; + } + + DWORD CComboUI::GetItemTextColor () const { + return m_ListInfo.dwTextColor; + } + + DWORD CComboUI::GetItemBkColor () const { + return m_ListInfo.dwBkColor; + } + + string_view_t CComboUI::GetItemBkImage () const { + return m_ListInfo.sBkImage; + } + + bool CComboUI::IsAlternateBk () const { + return m_ListInfo.bAlternateBk; + } + + void CComboUI::SetAlternateBk (bool bAlternateBk) { + m_ListInfo.bAlternateBk = bAlternateBk; + } + + void CComboUI::SetSelectedItemTextColor (DWORD dwTextColor) { + m_ListInfo.dwSelectedTextColor = dwTextColor; + } + + void CComboUI::SetSelectedItemBkColor (DWORD dwBkColor) { + m_ListInfo.dwSelectedBkColor = dwBkColor; + } + + void CComboUI::SetSelectedItemImage (string_view_t pStrImage) { + m_ListInfo.sSelectedImage = pStrImage; + } + + DWORD CComboUI::GetSelectedItemTextColor () const { + return m_ListInfo.dwSelectedTextColor; + } + + DWORD CComboUI::GetSelectedItemBkColor () const { + return m_ListInfo.dwSelectedBkColor; + } + + string_view_t CComboUI::GetSelectedItemImage () const { + return m_ListInfo.sSelectedImage; + } + + void CComboUI::SetHotItemTextColor (DWORD dwTextColor) { + m_ListInfo.dwHotTextColor = dwTextColor; + } + + void CComboUI::SetHotItemBkColor (DWORD dwBkColor) { + m_ListInfo.dwHotBkColor = dwBkColor; + } + + void CComboUI::SetHotItemImage (string_view_t pStrImage) { + m_ListInfo.sHotImage = pStrImage; + } + + DWORD CComboUI::GetHotItemTextColor () const { + return m_ListInfo.dwHotTextColor; + } + DWORD CComboUI::GetHotItemBkColor () const { + return m_ListInfo.dwHotBkColor; + } + + string_view_t CComboUI::GetHotItemImage () const { + return m_ListInfo.sHotImage; + } + + void CComboUI::SetDisabledItemTextColor (DWORD dwTextColor) { + m_ListInfo.dwDisabledTextColor = dwTextColor; + } + + void CComboUI::SetDisabledItemBkColor (DWORD dwBkColor) { + m_ListInfo.dwDisabledBkColor = dwBkColor; + } + + void CComboUI::SetDisabledItemImage (string_view_t pStrImage) { + m_ListInfo.sDisabledImage = pStrImage; + } + + DWORD CComboUI::GetDisabledItemTextColor () const { + return m_ListInfo.dwDisabledTextColor; + } + + DWORD CComboUI::GetDisabledItemBkColor () const { + return m_ListInfo.dwDisabledBkColor; + } + + string_view_t CComboUI::GetDisabledItemImage () const { + return m_ListInfo.sDisabledImage; + } + + DWORD CComboUI::GetItemLineColor () const { + return m_ListInfo.dwLineColor; + } + + void CComboUI::SetItemLineColor (DWORD dwLineColor) { + m_ListInfo.dwLineColor = dwLineColor; + } + + bool CComboUI::IsItemShowHtml () { + return m_ListInfo.bShowHtml; + } + + void CComboUI::SetItemShowHtml (bool bShowHtml) { + if (m_ListInfo.bShowHtml == bShowHtml) return; + + m_ListInfo.bShowHtml = bShowHtml; + Invalidate (); + } + + void CComboUI::SetPos (RECT rc, bool bNeedInvalidate) { + if (!::EqualRect (&rc, &m_rcItem)) { + // + if (m_pWindow && ::IsWindow (m_pWindow->GetHWND ())) m_pWindow->Close (); + // ԪشСΪ0 + RECT rcnullptr = { 0 }; + for (int i = 0; i < m_items.GetSize (); i++) static_cast(m_items[i])->SetPos (rcnullptr); + // λ + CControlUI::SetPos (rc, bNeedInvalidate); + } + } + + void CComboUI::Move (SIZE szOffset, bool bNeedInvalidate) { + CControlUI::Move (szOffset, bNeedInvalidate); + } + void CComboUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("align")) { + if (pstrValue.find (_T ("left")) != string_t::npos) { + m_uTextStyle &= ~(DT_CENTER | DT_RIGHT | DT_SINGLELINE); + m_uTextStyle |= DT_LEFT; + } + if (pstrValue.find (_T ("center")) != string_t::npos) { + m_uTextStyle &= ~(DT_LEFT | DT_RIGHT); + m_uTextStyle |= DT_CENTER; + } + if (pstrValue.find (_T ("right")) != string_t::npos) { + m_uTextStyle &= ~(DT_LEFT | DT_CENTER | DT_SINGLELINE); + m_uTextStyle |= DT_RIGHT; + } + } else if (pstrName == _T ("valign")) { + if (pstrValue.find (_T ("top")) != string_t::npos) { + m_uTextStyle &= ~(DT_BOTTOM | DT_VCENTER); + m_uTextStyle |= (DT_TOP | DT_SINGLELINE); + } + if (pstrValue.find (_T ("vcenter")) != string_t::npos) { + m_uTextStyle &= ~(DT_TOP | DT_BOTTOM); + m_uTextStyle |= (DT_VCENTER | DT_SINGLELINE); + } + if (pstrValue.find (_T ("bottom")) != string_t::npos) { + m_uTextStyle &= ~(DT_TOP | DT_VCENTER); + m_uTextStyle |= (DT_BOTTOM | DT_SINGLELINE); + } + } else if (pstrName == _T ("endellipsis")) { + if (FawTools::parse_bool (pstrValue)) m_uTextStyle |= DT_END_ELLIPSIS; + else m_uTextStyle &= ~DT_END_ELLIPSIS; + } else if (pstrName == _T ("wordbreak")) { + if (FawTools::parse_bool (pstrValue)) { + m_uTextStyle &= ~DT_SINGLELINE; + m_uTextStyle |= DT_WORDBREAK | DT_EDITCONTROL; + } else { + m_uTextStyle &= ~DT_WORDBREAK & ~DT_EDITCONTROL; + m_uTextStyle |= DT_SINGLELINE; + } + } else if (pstrName == _T ("font")) SetFont (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("textcolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetTextColor (clrColor); + } else if (pstrName == _T ("disabledtextcolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetDisabledTextColor (clrColor); + } else if (pstrName == _T ("textpadding")) { + RECT rcTextPadding = FawTools::parse_rect (pstrValue); + SetTextPadding (rcTextPadding); + } else if (pstrName == _T ("showhtml")) SetShowHtml (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("showshadow")) SetShowShadow (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("normalimage")) SetNormalImage (pstrValue); + else if (pstrName == _T ("hotimage")) SetHotImage (pstrValue); + else if (pstrName == _T ("pushedimage")) SetPushedImage (pstrValue); + else if (pstrName == _T ("focusedimage")) SetFocusedImage (pstrValue); + else if (pstrName == _T ("disabledimage")) SetDisabledImage (pstrValue); + else if (pstrName == _T ("scrollselect")) SetScrollSelect (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("dropbox")) SetDropBoxAttributeList (pstrValue); + else if (pstrName == _T ("dropboxsize")) { + SIZE szDropBoxSize = FawTools::parse_size (pstrValue); + SetDropBoxSize (szDropBoxSize); + } else if (pstrName == _T ("itemfont")) SetItemFont (_ttoi (pstrValue.data ())); + else if (pstrName == _T ("itemalign")) { + if (pstrValue.find (_T ("left")) != string_t::npos) { + m_ListInfo.uTextStyle &= ~(DT_CENTER | DT_RIGHT); + m_ListInfo.uTextStyle |= DT_LEFT; + } + if (pstrValue.find (_T ("center")) != string_t::npos) { + m_ListInfo.uTextStyle &= ~(DT_LEFT | DT_RIGHT); + m_ListInfo.uTextStyle |= DT_CENTER; + } + if (pstrValue.find (_T ("right")) != string_t::npos) { + m_ListInfo.uTextStyle &= ~(DT_LEFT | DT_CENTER); + m_ListInfo.uTextStyle |= DT_RIGHT; + } + } else if (pstrName == _T ("itemvalign")) { + if (pstrValue.find (_T ("top")) != string_t::npos) { + m_ListInfo.uTextStyle &= ~(DT_VCENTER | DT_BOTTOM); + m_ListInfo.uTextStyle |= DT_TOP; + } + if (pstrValue.find (_T ("vcenter")) != string_t::npos) { + m_ListInfo.uTextStyle &= ~(DT_TOP | DT_BOTTOM | DT_WORDBREAK); + m_ListInfo.uTextStyle |= DT_VCENTER | DT_SINGLELINE; + } + if (pstrValue.find (_T ("bottom")) != string_t::npos) { + m_ListInfo.uTextStyle &= ~(DT_TOP | DT_VCENTER); + m_ListInfo.uTextStyle |= DT_BOTTOM; + } + } else if (pstrName == _T ("itemendellipsis")) { + if (FawTools::parse_bool (pstrValue)) m_ListInfo.uTextStyle |= DT_END_ELLIPSIS; + else m_ListInfo.uTextStyle &= ~DT_END_ELLIPSIS; + } else if (pstrName == _T ("itemtextpadding")) { + RECT rcTextPadding = FawTools::parse_rect (pstrValue); + SetItemTextPadding (rcTextPadding); + } else if (pstrName == _T ("itemtextcolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetItemTextColor (clrColor); + } else if (pstrName == _T ("itembkcolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetItemBkColor (clrColor); + } else if (pstrName == _T ("itembkimage")) SetItemBkImage (pstrValue); + else if (pstrName == _T ("itemaltbk")) SetAlternateBk (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("itemselectedtextcolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetSelectedItemTextColor (clrColor); + } else if (pstrName == _T ("itemselectedbkcolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetSelectedItemBkColor (clrColor); + } else if (pstrName == _T ("itemselectedimage")) SetSelectedItemImage (pstrValue); + else if (pstrName == _T ("itemhottextcolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetHotItemTextColor (clrColor); + } else if (pstrName == _T ("itemhotbkcolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetHotItemBkColor (clrColor); + } else if (pstrName == _T ("itemhotimage")) SetHotItemImage (pstrValue); + else if (pstrName == _T ("itemdisabledtextcolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetDisabledItemTextColor (clrColor); + } else if (pstrName == _T ("itemdisabledbkcolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetDisabledItemBkColor (clrColor); + } else if (pstrName == _T ("itemdisabledimage")) SetDisabledItemImage (pstrValue); + else if (pstrName == _T ("itemlinecolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetItemLineColor (clrColor); + } else if (pstrName == _T ("itemshowhtml")) SetItemShowHtml (FawTools::parse_bool (pstrValue)); + else CContainerUI::SetAttribute (pstrName, pstrValue); + } + + bool CComboUI::DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + return CControlUI::DoPaint (hDC, rcPaint, pStopControl); + } + + void CComboUI::PaintStatusImage (HDC hDC) { + if (IsFocused ()) m_uButtonState |= UISTATE_FOCUSED; + else m_uButtonState &= ~UISTATE_FOCUSED; + if (!IsEnabled ()) m_uButtonState |= UISTATE_DISABLED; + else m_uButtonState &= ~UISTATE_DISABLED; + + if ((m_uButtonState & UISTATE_DISABLED) != 0) { + if (!m_sDisabledImage.empty ()) { + if (DrawImage (hDC, m_sDisabledImage)) + return; + } + } else if ((m_uButtonState & UISTATE_PUSHED) != 0) { + if (!m_sPushedImage.empty ()) { + if (DrawImage (hDC, m_sPushedImage)) + return; + } + } else if ((m_uButtonState & UISTATE_HOT) != 0) { + if (!m_sHotImage.empty ()) { + if (DrawImage (hDC, m_sHotImage)) + return; + } + } else if ((m_uButtonState & UISTATE_FOCUSED) != 0) { + if (!m_sFocusedImage.empty ()) { + if (DrawImage (hDC, m_sFocusedImage)) + return; + } + } + + if (!m_sNormalImage.empty ()) { + if (!DrawImage (hDC, m_sNormalImage)) { + } else return; + } + } + + void CComboUI::PaintText (HDC hDC) { + if (m_dwTextColor == 0) m_dwTextColor = m_pManager->GetDefaultFontColor (); + if (m_dwDisabledTextColor == 0) m_dwDisabledTextColor = m_pManager->GetDefaultDisabledColor (); + + RECT rc = m_rcItem; + rc.left += m_rcTextPadding.left; + rc.right -= m_rcTextPadding.right; + rc.top += m_rcTextPadding.top; + rc.bottom -= m_rcTextPadding.bottom; + + CDuiString sText = GetText (); + if (sText.empty ()) return; + int nLinks = 0; + if (IsEnabled ()) { + if (m_bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rc, sText, m_dwTextColor, nullptr, nullptr, nLinks, m_iFont, m_uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rc, sText, m_dwTextColor, m_iFont, m_uTextStyle); + } else { + if (m_bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rc, sText, m_dwDisabledTextColor, nullptr, nullptr, nLinks, m_iFont, m_uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rc, sText, m_dwDisabledTextColor, m_iFont, m_uTextStyle); + } + } + +} // namespace DuiLib diff --git a/DuiLib/Control/UICombo.h b/DuiLib/Control/UICombo.h new file mode 100644 index 0000000..775db61 --- /dev/null +++ b/DuiLib/Control/UICombo.h @@ -0,0 +1,150 @@ +#ifndef __UICOMBO_H__ +#define __UICOMBO_H__ + +#pragma once + +namespace DuiLib { + ///////////////////////////////////////////////////////////////////////////////////// + // + + class CComboWnd; + + class UILIB_API CComboUI: public CContainerUI, public IListOwnerUI { + DECLARE_DUICONTROL (CComboUI) + friend class CComboWnd; + public: + CComboUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + void DoInit (); + UINT GetControlFlags () const; + + CDuiString GetText () const; + void SetText (string_view_t pstrText) override; + void SetEnabled (bool bEnable = true); + + void SetTextStyle (UINT uStyle); + UINT GetTextStyle () const; + void SetTextColor (DWORD dwTextColor); + DWORD GetTextColor () const; + void SetDisabledTextColor (DWORD dwTextColor); + DWORD GetDisabledTextColor () const; + void SetFont (int index); + int GetFont () const; + RECT GetTextPadding () const; + void SetTextPadding (RECT rc); + bool IsShowHtml (); + void SetShowHtml (bool bShowHtml = true); + bool IsShowShadow (); + void SetShowShadow (bool bShow = true); + + string_view_t GetDropBoxAttributeList (); + void SetDropBoxAttributeList (string_view_t pstrList); + SIZE GetDropBoxSize () const; + void SetDropBoxSize (SIZE szDropBox); + + UINT GetListType (); + TListInfoUI* GetListInfo (); + int GetCurSel () const; + bool SelectItem (int iIndex, bool bTakeFocus = false); + bool SelectMultiItem (int iIndex, bool bTakeFocus = false); + bool UnSelectItem (int iIndex, bool bOthers = false); + bool SetItemIndex (CControlUI* pControl, int iIndex); + + bool Add (CControlUI* pControl); + bool AddAt (CControlUI* pControl, int iIndex); + bool Remove (CControlUI* pControl); + bool RemoveAt (int iIndex); + void RemoveAll (); + + bool Activate (); + + string_view_t GetNormalImage () const; + void SetNormalImage (string_view_t pStrImage); + string_view_t GetHotImage () const; + void SetHotImage (string_view_t pStrImage); + string_view_t GetPushedImage () const; + void SetPushedImage (string_view_t pStrImage); + string_view_t GetFocusedImage () const; + void SetFocusedImage (string_view_t pStrImage); + string_view_t GetDisabledImage () const; + void SetDisabledImage (string_view_t pStrImage); + + bool GetScrollSelect (); + void SetScrollSelect (bool bScrollSelect); + + void SetItemFont (int index); + void SetItemTextStyle (UINT uStyle); + RECT GetItemTextPadding () const; + void SetItemTextPadding (RECT rc); + DWORD GetItemTextColor () const; + void SetItemTextColor (DWORD dwTextColor); + DWORD GetItemBkColor () const; + void SetItemBkColor (DWORD dwBkColor); + string_view_t GetItemBkImage () const; + void SetItemBkImage (string_view_t pStrImage); + bool IsAlternateBk () const; + void SetAlternateBk (bool bAlternateBk); + DWORD GetSelectedItemTextColor () const; + void SetSelectedItemTextColor (DWORD dwTextColor); + DWORD GetSelectedItemBkColor () const; + void SetSelectedItemBkColor (DWORD dwBkColor); + string_view_t GetSelectedItemImage () const; + void SetSelectedItemImage (string_view_t pStrImage); + DWORD GetHotItemTextColor () const; + void SetHotItemTextColor (DWORD dwTextColor); + DWORD GetHotItemBkColor () const; + void SetHotItemBkColor (DWORD dwBkColor); + string_view_t GetHotItemImage () const; + void SetHotItemImage (string_view_t pStrImage); + DWORD GetDisabledItemTextColor () const; + void SetDisabledItemTextColor (DWORD dwTextColor); + DWORD GetDisabledItemBkColor () const; + void SetDisabledItemBkColor (DWORD dwBkColor); + string_view_t GetDisabledItemImage () const; + void SetDisabledItemImage (string_view_t pStrImage); + DWORD GetItemLineColor () const; + void SetItemLineColor (DWORD dwLineColor); + bool IsItemShowHtml (); + void SetItemShowHtml (bool bShowHtml = true); + + SIZE EstimateSize (SIZE szAvailable); + void SetPos (RECT rc, bool bNeedInvalidate = true); + void Move (SIZE szOffset, bool bNeedInvalidate = true); + void DoEvent (TEventUI& event); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + bool DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl); + void PaintText (HDC hDC); + void PaintStatusImage (HDC hDC); + + protected: + CComboWnd *m_pWindow = nullptr; + + int m_iCurSel = -1; + DWORD m_dwTextColor = 0; + DWORD m_dwDisabledTextColor = 0; + int m_iFont = -1; + UINT m_uTextStyle; + RECT m_rcTextPadding = { 0 }; + bool m_bShowHtml = false; + bool m_bShowShadow; + CDuiString m_sDropBoxAttributes; + SIZE m_szDropBox = { 0, 150 }; + UINT m_uButtonState = 0; + + CDuiString m_sNormalImage; + CDuiString m_sHotImage; + CDuiString m_sPushedImage; + CDuiString m_sFocusedImage; + CDuiString m_sDisabledImage; + + bool m_bScrollSelect = true; + TListInfoUI m_ListInfo; + }; + +} // namespace DuiLib + +#endif // __UICOMBO_H__ diff --git a/DuiLib/Control/UIComboBox.cpp b/DuiLib/Control/UIComboBox.cpp new file mode 100644 index 0000000..d6a430d --- /dev/null +++ b/DuiLib/Control/UIComboBox.cpp @@ -0,0 +1,95 @@ +#include "StdAfx.h" +#include "UIComboBox.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CComboBoxUI) + + CComboBoxUI::CComboBoxUI () {} + + string_view_t CComboBoxUI::GetClass () const { + return _T ("ComboBoxUI"); + } + + void CComboBoxUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("arrowimage")) + m_sArrowImage = pstrValue; + else + CComboUI::SetAttribute (pstrName, pstrValue); + } + + void CComboBoxUI::PaintStatusImage (HDC hDC) { + if (m_sArrowImage.empty ()) + CComboUI::PaintStatusImage (hDC); + else { + // get index + if (IsFocused ()) m_uButtonState |= UISTATE_FOCUSED; + else m_uButtonState &= ~UISTATE_FOCUSED; + if (!IsEnabled ()) m_uButtonState |= UISTATE_DISABLED; + else m_uButtonState &= ~UISTATE_DISABLED; + + int nIndex = 0; + if ((m_uButtonState & UISTATE_DISABLED) != 0) + nIndex = 4; + else if ((m_uButtonState & UISTATE_PUSHED) != 0) + nIndex = 2; + else if ((m_uButtonState & UISTATE_HOT) != 0) + nIndex = 1; + else if ((m_uButtonState & UISTATE_FOCUSED) != 0) + nIndex = 3; + + // make modify string + CDuiString sModify = m_sArrowImage; + + size_t nPos1 = sModify.find (_T ("source")); + size_t nPos2 = sModify.find (_T ("'"), nPos1 + 7); + if (nPos2 == string_t::npos) return; //first + size_t nPos3 = sModify.find (_T ("'"), nPos2 + 1); + if (nPos3 == string_t::npos) return; //second + + RECT rcBmpPart = FawTools::parse_rect (string_view_t (&sModify[nPos2 + 1])); + + m_nArrowWidth = (rcBmpPart.right - rcBmpPart.left) / 5; + rcBmpPart.left += nIndex * m_nArrowWidth; + rcBmpPart.right = rcBmpPart.left + m_nArrowWidth; + + RECT rcDest { 0, 0, m_rcItem.right - m_rcItem.left, m_rcItem.bottom - m_rcItem.top }; + ::InflateRect (&rcDest, -GetBorderSize (), -GetBorderSize ()); + rcDest.left = rcDest.right - m_nArrowWidth; + + CDuiString sSource = sModify.Mid (nPos1, nPos3 + 1 - nPos1); + CDuiString sReplace; + sReplace.Format (_T ("source='%d,%d,%d,%d' dest='%d,%d,%d,%d'"), + rcBmpPart.left, rcBmpPart.top, rcBmpPart.right, rcBmpPart.bottom, + rcDest.left, rcDest.top, rcDest.right, rcDest.bottom); + + sModify.Replace (sSource, sReplace); + + // draw image + if (!DrawImage (hDC, m_sArrowImage, sModify)) { + } + } + } + + void CComboBoxUI::PaintText (HDC hDC) { + RECT rcText = m_rcItem; + rcText.left += m_rcTextPadding.left; + rcText.right -= m_rcTextPadding.right; + rcText.top += m_rcTextPadding.top; + rcText.bottom -= m_rcTextPadding.bottom; + + rcText.right -= m_nArrowWidth; // add this line than CComboUI::PaintText(HDC hDC) + + if (m_iCurSel >= 0) { + CControlUI* pControl = static_cast(m_items[m_iCurSel]); + IListItemUI* pElement = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (pElement) { + pElement->DrawItemText (hDC, rcText); + } else { + RECT rcOldPos = pControl->GetPos (); + pControl->SetPos (rcText); + pControl->DoPaint (hDC, rcText, nullptr); + pControl->SetPos (rcOldPos); + } + } + } +} diff --git a/DuiLib/Control/UIComboBox.h b/DuiLib/Control/UIComboBox.h new file mode 100644 index 0000000..24f8edf --- /dev/null +++ b/DuiLib/Control/UIComboBox.h @@ -0,0 +1,27 @@ +#ifndef __UICOMBOBOX_H__ +#define __UICOMBOBOX_H__ + +#pragma once + +namespace DuiLib { + /// չб + /// arrowimage,һͼƬƽֳ5,Normal/Hot/Pushed/Focused/Disabled(source) + /// + class UILIB_API CComboBoxUI: public CComboUI { + DECLARE_DUICONTROL (CComboBoxUI) + public: + CComboBoxUI (); + string_view_t GetClass () const; + + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + void PaintText (HDC hDC); + void PaintStatusImage (HDC hDC); + + protected: + CDuiString m_sArrowImage; + int m_nArrowWidth = 0; + }; +} + +#endif // __UICOMBOBOX_H__ diff --git a/DuiLib/Control/UIDateTime.cpp b/DuiLib/Control/UIDateTime.cpp new file mode 100644 index 0000000..e3c6b94 --- /dev/null +++ b/DuiLib/Control/UIDateTime.cpp @@ -0,0 +1,236 @@ +#include "StdAfx.h" +#include "UIDateTime.h" + +namespace DuiLib { + //CDateTimeUI::m_nDTUpdateFlag +#define DT_NONE 0 +#define DT_UPDATE 1 +#define DT_DELETE 2 +#define DT_KEEP 3 + + class CDateTimeWnd: public CWindowWnd { + public: + CDateTimeWnd (); + + void Init (CDateTimeUI* pOwner); + RECT CalPos (); + + string_view_t GetWindowClassName () const; + string_view_t GetSuperClassName () const; + void OnFinalMessage (HWND hWnd); + + LRESULT HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam); + protected: + CDateTimeUI* m_pOwner; + HBRUSH m_hBkBrush; + bool m_bInit; + bool m_bDropOpen; + SYSTEMTIME m_oldSysTime; + }; + + CDateTimeWnd::CDateTimeWnd (): m_pOwner (nullptr), m_hBkBrush (nullptr), m_bInit (false), m_bDropOpen (false) {} + + void CDateTimeWnd::Init (CDateTimeUI* pOwner) { + m_pOwner = pOwner; + m_pOwner->m_nDTUpdateFlag = DT_NONE; + + if (m_hWnd == NULL) { + RECT rcPos = CalPos (); + UINT uStyle = WS_CHILD; + Create (m_pOwner->GetManager ()->GetPaintWindow (), _T (""), uStyle, 0, rcPos); + SetWindowFont (m_hWnd, m_pOwner->GetManager ()->GetFontInfo (m_pOwner->GetFont ())->hFont, TRUE); + } + + if (m_pOwner->GetText ().empty ()) { + ::GetLocalTime (&m_pOwner->m_sysTime); + } + memcpy (&m_oldSysTime, &m_pOwner->m_sysTime, sizeof (SYSTEMTIME)); + ::SendMessage (m_hWnd, DTM_SETSYSTEMTIME, 0, (LPARAM) &m_pOwner->m_sysTime); + ::ShowWindow (m_hWnd, SW_SHOWNOACTIVATE); + ::SetFocus (m_hWnd); + + m_bInit = true; + } + + RECT CDateTimeWnd::CalPos () { + RECT rcPos = m_pOwner->GetPos (); + + CControlUI* pParent = m_pOwner; + RECT rcParent = { 0 }; + while (!!(pParent = pParent->GetParent ())) { + if (!pParent->IsVisible ()) { + rcPos.left = rcPos.top = rcPos.right = rcPos.bottom = 0; + break; + } + rcParent = pParent->GetClientPos (); + if (!::IntersectRect (&rcPos, &rcPos, &rcParent)) { + rcPos.left = rcPos.top = rcPos.right = rcPos.bottom = 0; + break; + } + } + + return rcPos; + } + + string_view_t CDateTimeWnd::GetWindowClassName () const { + return _T ("DateTimeWnd"); + } + + string_view_t CDateTimeWnd::GetSuperClassName () const { + return DATETIMEPICK_CLASS; + } + + void CDateTimeWnd::OnFinalMessage (HWND hWnd) { + if (m_hBkBrush != NULL) ::DeleteObject (m_hBkBrush); + //m_pOwner->GetManager()->RemoveNativeWindow(hWnd); + m_pOwner->m_pWindow = nullptr; + delete this; + } + + LRESULT CDateTimeWnd::HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam) { + LRESULT lRes = 0; + BOOL bHandled = TRUE; + if (uMsg == WM_CREATE) { + //m_pOwner->GetManager()->AddNativeWindow(m_pOwner, m_hWnd); + bHandled = FALSE; + } else if (uMsg == WM_KEYDOWN && wParam == VK_ESCAPE) { + memcpy (&m_pOwner->m_sysTime, &m_oldSysTime, sizeof (SYSTEMTIME)); + m_pOwner->m_nDTUpdateFlag = DT_UPDATE; + m_pOwner->UpdateText (); + PostMessage (WM_CLOSE); + return lRes; + } else if (uMsg == OCM_NOTIFY) { + NMHDR* pHeader = (NMHDR*) lParam; + if (pHeader && pHeader->hwndFrom == m_hWnd) { + if (pHeader->code == DTN_DATETIMECHANGE) { + LPNMDATETIMECHANGE lpChage = (LPNMDATETIMECHANGE) lParam; + ::SendMessage (m_hWnd, DTM_GETSYSTEMTIME, 0, (LPARAM) &m_pOwner->m_sysTime); + m_pOwner->m_nDTUpdateFlag = DT_UPDATE; + m_pOwner->UpdateText (); + } else if (pHeader->code == DTN_DROPDOWN) { + m_bDropOpen = true; + + } else if (pHeader->code == DTN_CLOSEUP) { + ::SendMessage (m_hWnd, DTM_GETSYSTEMTIME, 0, (LPARAM) &m_pOwner->m_sysTime); + m_pOwner->m_nDTUpdateFlag = DT_UPDATE; + m_pOwner->UpdateText (); + PostMessage (WM_CLOSE); + m_bDropOpen = false; + } + } + bHandled = FALSE; + } else if (uMsg == WM_KILLFOCUS) { + if (!m_bDropOpen) { + PostMessage (WM_CLOSE); + } + bHandled = FALSE; + } else if (uMsg == WM_PAINT) { + if (m_pOwner->GetManager ()->IsLayered ()) { + //m_pOwner->GetManager()->AddNativeWindow(m_pOwner, m_hWnd); + } + bHandled = FALSE; + } else bHandled = FALSE; + if (!bHandled) return CWindowWnd::HandleMessage (uMsg, wParam, lParam); + return lRes; + } + ////////////////////////////////////////////////////////////////////////// + // + IMPLEMENT_DUICONTROL (CDateTimeUI) + + CDateTimeUI::CDateTimeUI () { + ::GetLocalTime (&m_sysTime); + m_bReadOnly = false; + m_pWindow = nullptr; + m_nDTUpdateFlag = DT_UPDATE; + UpdateText (); + m_nDTUpdateFlag = DT_NONE; + } + + string_view_t CDateTimeUI::GetClass () const { + return _T ("DateTimeUI"); + } + + LPVOID CDateTimeUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_DATETIME) return static_cast(this); + return CLabelUI::GetInterface (pstrName); + } + + SYSTEMTIME& CDateTimeUI::GetTime () { + return m_sysTime; + } + + void CDateTimeUI::SetTime (SYSTEMTIME* pst) { + m_sysTime = *pst; + Invalidate (); + m_nDTUpdateFlag = DT_UPDATE; + UpdateText (); + m_nDTUpdateFlag = DT_NONE; + } + + void CDateTimeUI::SetReadOnly (bool bReadOnly) { + m_bReadOnly = bReadOnly; + Invalidate (); + } + + bool CDateTimeUI::IsReadOnly () const { + return m_bReadOnly; + } + + void CDateTimeUI::UpdateText () { + if (m_nDTUpdateFlag == DT_DELETE) { + SetText (_T ("")); + } else if (m_nDTUpdateFlag == DT_UPDATE) { + CDuiString sText; + sText.Format (_T ("%4d-%02d-%02d"), m_sysTime.wYear, m_sysTime.wMonth, m_sysTime.wDay, m_sysTime.wHour, m_sysTime.wMinute); + SetText (sText); + } + } + + void CDateTimeUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CLabelUI::DoEvent (event); + return; + } else if (event.Type == UIEVENT_SETCURSOR && IsEnabled ()) { + ::SetCursor (::LoadCursor (nullptr, IDC_IBEAM)); + return; + } else if (event.Type == UIEVENT_WINDOWSIZE) { + if (m_pWindow) m_pManager->SetFocusNeeded (this); + } else if (event.Type == UIEVENT_SCROLLWHEEL) { + if (m_pWindow) return; + } else if (event.Type == UIEVENT_SETFOCUS && IsEnabled ()) { + if (m_pWindow) return; + m_pWindow = new CDateTimeWnd (); + ASSERT (m_pWindow); + m_pWindow->Init (this); + m_pWindow->ShowWindow (); + } else if (event.Type == UIEVENT_KILLFOCUS && IsEnabled ()) { + Invalidate (); + } else if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK || event.Type == UIEVENT_RBUTTONDOWN) { + if (IsEnabled ()) { + GetManager ()->ReleaseCapture (); + if (IsFocused () && !m_pWindow) { + m_pWindow = new CDateTimeWnd (); + ASSERT (m_pWindow); + } + if (m_pWindow) { + m_pWindow->Init (this); + m_pWindow->ShowWindow (); + } + } + return; + } else if (event.Type == UIEVENT_MOUSEMOVE) { + return; + } else if (event.Type == UIEVENT_BUTTONUP) { + return; + } else if (event.Type == UIEVENT_CONTEXTMENU) { + return; + } else if (event.Type == UIEVENT_MOUSEENTER) { + return; + } else if (event.Type == UIEVENT_MOUSELEAVE) { + return; + } + + CLabelUI::DoEvent (event); + } +} diff --git a/DuiLib/Control/UIDateTime.h b/DuiLib/Control/UIDateTime.h new file mode 100644 index 0000000..57b13bf --- /dev/null +++ b/DuiLib/Control/UIDateTime.h @@ -0,0 +1,36 @@ +#ifndef __UIDATETIME_H__ +#define __UIDATETIME_H__ + +#pragma once + +namespace DuiLib { + class CDateTimeWnd; + + /// ʱѡؼ + class UILIB_API CDateTimeUI: public CLabelUI { + DECLARE_DUICONTROL (CDateTimeUI) + friend class CDateTimeWnd; + public: + CDateTimeUI (); + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + SYSTEMTIME& GetTime (); + void SetTime (SYSTEMTIME* pst); + + void SetReadOnly (bool bReadOnly); + bool IsReadOnly () const; + + void UpdateText (); + + void DoEvent (TEventUI& event); + + protected: + SYSTEMTIME m_sysTime; + int m_nDTUpdateFlag; + bool m_bReadOnly; + + CDateTimeWnd* m_pWindow; + }; +} +#endif // __UIDATETIME_H__ \ No newline at end of file diff --git a/DuiLib/Control/UIEdit.cpp b/DuiLib/Control/UIEdit.cpp new file mode 100644 index 0000000..cd62035 --- /dev/null +++ b/DuiLib/Control/UIEdit.cpp @@ -0,0 +1,639 @@ +#include "StdAfx.h" +#include "UIEdit.h" + +namespace DuiLib { + class CEditWnd: public CWindowWnd { + public: + CEditWnd (); + + void Init (CEditUI* pOwner); + RECT CalPos (); + + string_view_t GetWindowClassName () const; + string_view_t GetSuperClassName () const; + void OnFinalMessage (HWND hWnd); + + LRESULT HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam); + LRESULT OnKillFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnEditChanged (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + + protected: + enum { + DEFAULT_TIMERID = 20, + }; + + CEditUI* m_pOwner; + HBRUSH m_hBkBrush; + bool m_bInit; + bool m_bDrawCaret; + }; + + + CEditWnd::CEditWnd (): m_pOwner (nullptr), m_hBkBrush (NULL), m_bInit (false), m_bDrawCaret (false) {} + + void CEditWnd::Init (CEditUI* pOwner) { + m_pOwner = pOwner; + RECT rcPos = CalPos (); + UINT uStyle = 0; + if (m_pOwner->GetManager ()->IsLayered ()) { + uStyle = WS_POPUP | ES_AUTOHSCROLL | WS_VISIBLE; + RECT rcWnd = { 0 }; + ::GetWindowRect (m_pOwner->GetManager ()->GetPaintWindow (), &rcWnd); + rcPos.left += rcWnd.left; + rcPos.right += rcWnd.left; + rcPos.top += rcWnd.top - 1; + rcPos.bottom += rcWnd.top - 1; + } else { + uStyle = WS_CHILD | ES_AUTOHSCROLL; + } + UINT uTextStyle = m_pOwner->GetTextStyle (); + if (uTextStyle & DT_LEFT) uStyle |= ES_LEFT; + else if (uTextStyle & DT_CENTER) uStyle |= ES_CENTER; + else if (uTextStyle & DT_RIGHT) uStyle |= ES_RIGHT; + if (m_pOwner->IsPasswordMode ()) uStyle |= ES_PASSWORD; + Create (m_pOwner->GetManager ()->GetPaintWindow (), _T (""), uStyle, 0, rcPos); + HFONT hFont = NULL; + int iFontIndex = m_pOwner->GetFont (); + if (iFontIndex != -1) + hFont = m_pOwner->GetManager ()->GetFont (iFontIndex); + if (hFont == NULL) + hFont = m_pOwner->GetManager ()->GetDefaultFontInfo ()->hFont; + + SetWindowFont (m_hWnd, hFont, TRUE); + Edit_LimitText (m_hWnd, m_pOwner->GetMaxChar ()); + if (m_pOwner->IsPasswordMode ()) Edit_SetPasswordChar (m_hWnd, m_pOwner->GetPasswordChar ()); + Edit_SetText (m_hWnd, m_pOwner->GetText ().data ()); + Edit_SetModify (m_hWnd, FALSE); + SendMessage (EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELPARAM (0, 0)); + Edit_Enable (m_hWnd, m_pOwner->IsEnabled () == true); + Edit_SetReadOnly (m_hWnd, m_pOwner->IsReadOnly () == true); + + //Styls + LONG styleValue = ::GetWindowLong (m_hWnd, GWL_STYLE); + styleValue |= pOwner->GetWindowStyls (); + ::SetWindowLong (GetHWND (), GWL_STYLE, styleValue); + //Styls + ::ShowWindow (m_hWnd, SW_SHOWNOACTIVATE); + ::SetFocus (m_hWnd); + if (m_pOwner->IsAutoSelAll ()) { + int nSize = GetWindowTextLength (m_hWnd); + if (nSize == 0) nSize = 1; + Edit_SetSel (m_hWnd, 0, nSize); + } else { + int nSize = GetWindowTextLength (m_hWnd); + Edit_SetSel (m_hWnd, nSize, nSize); + } + + m_bInit = true; + } + + RECT CEditWnd::CalPos () { + RECT rcPos = m_pOwner->GetPos (); + RECT rcInset = m_pOwner->GetTextPadding (); + rcPos.left += rcInset.left; + rcPos.top += rcInset.top; + rcPos.right -= rcInset.right; + rcPos.bottom -= rcInset.bottom; + LONG lEditHeight = m_pOwner->GetManager ()->GetFontInfo (m_pOwner->GetFont ())->tm.tmHeight; + if (lEditHeight < rcPos.bottom - rcPos.top) { + rcPos.top += (rcPos.bottom - rcPos.top - lEditHeight) / 2; + rcPos.bottom = rcPos.top + lEditHeight; + } + + CControlUI* pParent = m_pOwner; + RECT rcParent = { 0 }; + while (!!(pParent = pParent->GetParent ())) { + if (!pParent->IsVisible ()) { + rcPos.left = rcPos.top = rcPos.right = rcPos.bottom = 0; + break; + } + rcParent = pParent->GetClientPos (); + if (!::IntersectRect (&rcPos, &rcPos, &rcParent)) { + rcPos.left = rcPos.top = rcPos.right = rcPos.bottom = 0; + break; + } + } + + return rcPos; + } + + string_view_t CEditWnd::GetWindowClassName () const { + return _T ("EditWnd"); + } + + string_view_t CEditWnd::GetSuperClassName () const { + return WC_EDIT; + } + + void CEditWnd::OnFinalMessage (HWND hWnd) { + m_pOwner->Invalidate (); + // Clear reference and die + if (m_hBkBrush) ::DeleteObject (m_hBkBrush); + if (m_pOwner->GetManager ()->IsLayered ()) { + m_pOwner->GetManager ()->RemoveNativeWindow (hWnd); + } + m_pOwner->m_pWindow = nullptr; + delete this; + } + + LRESULT CEditWnd::HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam) { + LRESULT lRes = 0; + BOOL bHandled = TRUE; + if (uMsg == WM_CREATE) { + //m_pOwner->GetManager()->AddNativeWindow(m_pOwner, m_hWnd); + //if( m_pOwner->GetManager()->IsLayered() ) { + // ::SetTimer(m_hWnd, DEFAULT_TIMERID, ::GetCaretBlinkTime(), nullptr); + //} + bHandled = FALSE; + } else if (uMsg == WM_KILLFOCUS) lRes = OnKillFocus (uMsg, wParam, lParam, bHandled); + else if (uMsg == OCM_COMMAND) { + if (GET_WM_COMMAND_CMD (wParam, lParam) == EN_CHANGE) lRes = OnEditChanged (uMsg, wParam, lParam, bHandled); + else if (GET_WM_COMMAND_CMD (wParam, lParam) == EN_UPDATE) { + RECT rcClient = { 0 }; + ::GetClientRect (m_hWnd, &rcClient); + ::InvalidateRect (m_hWnd, &rcClient, FALSE); + } + } else if (uMsg == WM_KEYDOWN && TCHAR (wParam) == VK_RETURN) { + m_pOwner->GetManager ()->SendNotify (m_pOwner, DUI_MSGTYPE_RETURN); + } else if (uMsg == WM_KEYDOWN && TCHAR (wParam) == VK_TAB) { + if (m_pOwner->GetManager ()->IsLayered ()) { + m_pOwner->GetManager ()->SetNextTabControl (); + } + } else if (uMsg == OCM__BASE + WM_CTLCOLOREDIT || uMsg == OCM__BASE + WM_CTLCOLORSTATIC) { + //if (m_pOwner->GetManager()->IsLayered() && !m_pOwner->GetManager()->IsPainting()) { + // m_pOwner->GetManager()->AddNativeWindow(m_pOwner, m_hWnd); + //} + + ::SetBkMode ((HDC) wParam, TRANSPARENT); + DWORD dwTextColor = m_pOwner->GetNativeEditTextColor (); + ::SetTextColor ((HDC) wParam, RGB (GetBValue (dwTextColor), GetGValue (dwTextColor), GetRValue (dwTextColor))); + DWORD clrColor = m_pOwner->GetNativeEditBkColor (); + if (clrColor < 0xFF000000) { + if (m_hBkBrush) ::DeleteObject (m_hBkBrush); + RECT rcWnd = m_pOwner->GetManager ()->GetNativeWindowRect (m_hWnd); + HBITMAP hBmpEditBk = CRenderEngine::GenerateBitmap (m_pOwner->GetManager (), rcWnd, m_pOwner, clrColor); + m_hBkBrush = ::CreatePatternBrush (hBmpEditBk); + ::DeleteObject (hBmpEditBk); + } else { + if (!m_hBkBrush) { + m_hBkBrush = ::CreateSolidBrush (RGB (GetBValue (clrColor), GetGValue (clrColor), GetRValue (clrColor))); + } + } + return (LRESULT) m_hBkBrush; + } else if (uMsg == WM_PAINT) { + //if (m_pOwner->GetManager()->IsLayered()) { + // m_pOwner->GetManager()->AddNativeWindow(m_pOwner, m_hWnd); + //} + bHandled = FALSE; + } else if (uMsg == WM_PRINT) { + //if (m_pOwner->GetManager()->IsLayered()) { + // lRes = CWindowWnd::HandleMessage(uMsg, wParam, lParam); + // if( m_pOwner->IsEnabled() && m_bDrawCaret ) { + // RECT rcClient = { 0 }; + // ::GetClientRect(m_hWnd, &rcClient); + // POINT ptCaret = { 0 }; + // ::GetCaretPos(&ptCaret); + // RECT rcCaret = { ptCaret.x, ptCaret.y, ptCaret.x, ptCaret.y+rcClient.bottom-rcClient.top }; + // CRenderEngine::DrawLine((HDC)wParam, rcCaret, 1, 0xFF000000); + // } + // return lRes; + //} + bHandled = FALSE; + } else if (uMsg == WM_TIMER) { + if (wParam == CARET_TIMERID) { + m_bDrawCaret = !m_bDrawCaret; + RECT rcClient = { 0 }; + ::GetClientRect (m_hWnd, &rcClient); + ::InvalidateRect (m_hWnd, &rcClient, FALSE); + return 0; + } + bHandled = FALSE; + } else bHandled = FALSE; + + if (!bHandled) return CWindowWnd::HandleMessage (uMsg, wParam, lParam); + return lRes; + } + + LRESULT CEditWnd::OnKillFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + LRESULT lRes = ::DefWindowProc (m_hWnd, uMsg, wParam, lParam); + //if ((HWND)wParam != m_pOwner->GetManager()->GetPaintWindow()) { + // ::SendMessage(m_pOwner->GetManager()->GetPaintWindow(), WM_KILLFOCUS, wParam, lParam); + //} + PostMessage (WM_CLOSE); + return lRes; + } + + LRESULT CEditWnd::OnEditChanged (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { + if (!m_bInit) return 0; + if (!m_pOwner) return 0; + // Copy text back + int cchLen = ::GetWindowTextLength (m_hWnd) + 1; + string_t str (cchLen, _T ('\0')); + ::GetWindowText (m_hWnd, &str[0], cchLen); + m_pOwner->m_sText = str.c_str (); + m_pOwner->GetManager ()->SendNotify (m_pOwner, DUI_MSGTYPE_TEXTCHANGED); + if (m_pOwner->GetManager ()->IsLayered ()) m_pOwner->Invalidate (); + return 0; + } + + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CEditUI) + + CEditUI::CEditUI () { + SetTextPadding ({ 4, 3, 4, 3 }); + SetBkColor (0xFFFFFFFF); + } + + string_view_t CEditUI::GetClass () const { + return _T ("EditUI"); + } + + LPVOID CEditUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_EDIT) return static_cast(this); + return CLabelUI::GetInterface (pstrName); + } + + UINT CEditUI::GetControlFlags () const { + if (!IsEnabled ()) return CControlUI::GetControlFlags (); + + return UIFLAG_SETCURSOR | UIFLAG_TABSTOP; + } + + void CEditUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CLabelUI::DoEvent (event); + return; + } + + if (event.Type == UIEVENT_SETCURSOR && IsEnabled ()) { + ::SetCursor (::LoadCursor (nullptr, IDC_IBEAM)); + return; + } + if (event.Type == UIEVENT_WINDOWSIZE) { + if (m_pWindow) m_pManager->SetFocusNeeded (this); + } + if (event.Type == UIEVENT_SCROLLWHEEL) { + if (m_pWindow) return; + } + if (event.Type == UIEVENT_SETFOCUS && IsEnabled ()) { + if (m_pWindow) return; + m_pWindow = new CEditWnd (); + ASSERT (m_pWindow); + m_pWindow->Init (this); + Invalidate (); + } + if (event.Type == UIEVENT_KILLFOCUS && IsEnabled ()) { + Invalidate (); + } + if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK || event.Type == UIEVENT_RBUTTONDOWN) { + if (IsEnabled ()) { + GetManager ()->ReleaseCapture (); + if (IsFocused () && !m_pWindow) { + m_pWindow = new CEditWnd (); + ASSERT (m_pWindow); + m_pWindow->Init (this); + + if (PtInRect (&m_rcItem, event.ptMouse)) { + int nSize = GetWindowTextLength (m_pWindow->GetHWND ()); + if (nSize == 0) nSize = 1; + Edit_SetSel (m_pWindow->GetHWND (), 0, nSize); + } + } else if (m_pWindow) { + if (!m_bAutoSelAll) { + POINT pt = event.ptMouse; + pt.x -= m_rcItem.left + m_rcTextPadding.left; + pt.y -= m_rcItem.top + m_rcTextPadding.top; + Edit_SetSel (m_pWindow->GetHWND (), 0, 0); + ::SendMessage (m_pWindow->GetHWND (), WM_LBUTTONDOWN, event.wParam, MAKELPARAM (pt.x, pt.y)); + } + } + } + return; + } + if (event.Type == UIEVENT_MOUSEMOVE) { + return; + } + if (event.Type == UIEVENT_BUTTONUP) { + return; + } + if (event.Type == UIEVENT_CONTEXTMENU) { + return; + } + if (event.Type == UIEVENT_MOUSEENTER) { + if (::PtInRect (&m_rcItem, event.ptMouse)) { + if (IsEnabled ()) { + if ((m_uButtonState & UISTATE_HOT) == 0) { + m_uButtonState |= UISTATE_HOT; + Invalidate (); + } + } + } + } + if (event.Type == UIEVENT_MOUSELEAVE) { + if (IsEnabled ()) { + m_uButtonState &= ~UISTATE_HOT; + Invalidate (); + } + return; + + //if( !::PtInRect(&m_rcItem, event.ptMouse ) ) { + // if( IsEnabled() ) { + // if( (m_uButtonState & UISTATE_HOT) != 0 ) { + // m_uButtonState &= ~UISTATE_HOT; + // Invalidate(); + // } + // } + // if (m_pManager) m_pManager->RemoveMouseLeaveNeeded(this); + // } + // else { + // if (m_pManager) m_pManager->AddMouseLeaveNeeded(this); + // return; + // } + } + CLabelUI::DoEvent (event); + } + + void CEditUI::SetEnabled (bool bEnable) { + CControlUI::SetEnabled (bEnable); + if (!IsEnabled ()) { + m_uButtonState = 0; + } + } + + void CEditUI::SetText (string_view_t pstrText) { + m_sText = pstrText; + if (m_pWindow) Edit_SetText (m_pWindow->GetHWND (), m_sText.c_str ()); + Invalidate (); + } + + void CEditUI::SetMaxChar (UINT uMax) { + m_uMaxChar = uMax; + if (m_pWindow) Edit_LimitText (m_pWindow->GetHWND (), m_uMaxChar); + } + + UINT CEditUI::GetMaxChar () { + return m_uMaxChar; + } + + void CEditUI::SetReadOnly (bool bReadOnly) { + if (m_bReadOnly == bReadOnly) return; + + m_bReadOnly = bReadOnly; + if (m_pWindow) Edit_SetReadOnly (m_pWindow->GetHWND (), m_bReadOnly); + Invalidate (); + } + + bool CEditUI::IsReadOnly () const { + return m_bReadOnly; + } + + void CEditUI::SetNumberOnly (bool bNumberOnly) { + if (bNumberOnly) { + m_iWindowStyls |= ES_NUMBER; + } else { + m_iWindowStyls &= ~ES_NUMBER; + } + } + + bool CEditUI::IsNumberOnly () const { + return (m_iWindowStyls & ES_NUMBER) ? true : false; + } + + int CEditUI::GetWindowStyls () const { + return m_iWindowStyls; + } + + void CEditUI::SetPasswordMode (bool bPasswordMode) { + if (m_bPasswordMode == bPasswordMode) return; + m_bPasswordMode = bPasswordMode; + Invalidate (); + if (m_pWindow) { + LONG styleValue = ::GetWindowLong (m_pWindow->GetHWND (), GWL_STYLE); + bPasswordMode ? styleValue |= ES_PASSWORD : styleValue &= ~ES_PASSWORD; + ::SetWindowLong (m_pWindow->GetHWND (), GWL_STYLE, styleValue); + } + } + + bool CEditUI::IsPasswordMode () const { + return m_bPasswordMode; + } + + void CEditUI::SetPasswordChar (TCHAR cPasswordChar) { + if (m_cPasswordChar == cPasswordChar) return; + m_cPasswordChar = cPasswordChar; + if (m_pWindow) Edit_SetPasswordChar (m_pWindow->GetHWND (), m_cPasswordChar); + Invalidate (); + } + + TCHAR CEditUI::GetPasswordChar () const { + return m_cPasswordChar; + } + + string_view_t CEditUI::GetNormalImage () { + return m_sNormalImage; + } + + void CEditUI::SetNormalImage (string_view_t pStrImage) { + m_sNormalImage = pStrImage; + Invalidate (); + } + + string_view_t CEditUI::GetHotImage () { + return m_sHotImage; + } + + void CEditUI::SetHotImage (string_view_t pStrImage) { + m_sHotImage = pStrImage; + Invalidate (); + } + + string_view_t CEditUI::GetFocusedImage () { + return m_sFocusedImage; + } + + void CEditUI::SetFocusedImage (string_view_t pStrImage) { + m_sFocusedImage = pStrImage; + Invalidate (); + } + + string_view_t CEditUI::GetDisabledImage () { + return m_sDisabledImage; + } + + void CEditUI::SetDisabledImage (string_view_t pStrImage) { + m_sDisabledImage = pStrImage; + Invalidate (); + } + + void CEditUI::SetNativeEditBkColor (DWORD dwBkColor) { + m_dwEditbkColor = dwBkColor; + } + + DWORD CEditUI::GetNativeEditBkColor () const { + return m_dwEditbkColor; + } + + void CEditUI::SetNativeEditTextColor (string_view_t pStrColor) { + m_dwEditTextColor = (DWORD) FawTools::parse_hex (pStrColor); + } + + DWORD CEditUI::GetNativeEditTextColor () const { + return m_dwEditTextColor; + } + + bool CEditUI::IsAutoSelAll () { + return m_bAutoSelAll; + } + + void CEditUI::SetAutoSelAll (bool bAutoSelAll) { + m_bAutoSelAll = bAutoSelAll; + } + + void CEditUI::SetSel (long nStartChar, long nEndChar) { + if (m_pWindow) Edit_SetSel (m_pWindow->GetHWND (), nStartChar, nEndChar); + } + + void CEditUI::SetSelAll () { + SetSel (0, -1); + } + + void CEditUI::SetReplaceSel (string_view_t lpszReplace) { + if (m_pWindow) Edit_ReplaceSel (m_pWindow->GetHWND (), lpszReplace.data ()); + } + + void CEditUI::SetTipValue (string_view_t pStrTipValue) { + m_sTipValue = pStrTipValue; + } + + CDuiString CEditUI::GetTipValue () { + if (!IsResourceText ()) return m_sTipValue; + return CResourceManager::GetInstance ()->GetText (m_sTipValue); + } + + void CEditUI::SetTipValueColor (string_view_t pStrColor) { + m_dwTipValueColor = (DWORD) FawTools::parse_hex (pStrColor); + } + + DWORD CEditUI::GetTipValueColor () { + return m_dwTipValueColor; + } + + + void CEditUI::SetPos (RECT rc, bool bNeedInvalidate) { + CControlUI::SetPos (rc, bNeedInvalidate); + if (m_pWindow) { + RECT rcPos = m_pWindow->CalPos (); + ::SetWindowPos (m_pWindow->GetHWND (), NULL, rcPos.left, rcPos.top, rcPos.right - rcPos.left, + rcPos.bottom - rcPos.top, SWP_NOZORDER | SWP_NOACTIVATE); + } + } + + void CEditUI::Move (SIZE szOffset, bool bNeedInvalidate) { + CControlUI::Move (szOffset, bNeedInvalidate); + if (m_pWindow) { + RECT rcPos = m_pWindow->CalPos (); + ::SetWindowPos (m_pWindow->GetHWND (), NULL, rcPos.left, rcPos.top, rcPos.right - rcPos.left, + rcPos.bottom - rcPos.top, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + } + } + + void CEditUI::SetVisible (bool bVisible) { + CControlUI::SetVisible (bVisible); + if (!IsVisible () && m_pWindow) m_pManager->SetFocus (nullptr); + } + + void CEditUI::SetInternVisible (bool bVisible) { + if (!IsVisible () && m_pWindow) m_pManager->SetFocus (nullptr); + } + + SIZE CEditUI::EstimateSize (SIZE szAvailable) { + if (m_cxyFixed.cy == 0) return { m_cxyFixed.cx, m_pManager->GetFontInfo (GetFont ())->tm.tmHeight + 6 }; + return CControlUI::EstimateSize (szAvailable); + } + + void CEditUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("readonly")) SetReadOnly (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("numberonly")) SetNumberOnly (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("autoselall")) SetAutoSelAll (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("password")) SetPasswordMode (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("passwordchar")) SetPasswordChar (pstrValue[0]); + else if (pstrName == _T ("maxchar")) SetMaxChar (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("normalimage")) SetNormalImage (pstrValue); + else if (pstrName == _T ("hotimage")) SetHotImage (pstrValue); + else if (pstrName == _T ("focusedimage")) SetFocusedImage (pstrValue); + else if (pstrName == _T ("disabledimage")) SetDisabledImage (pstrValue); + else if (pstrName == _T ("tipvalue")) SetTipValue (pstrValue); + else if (pstrName == _T ("tipvaluecolor")) SetTipValueColor (pstrValue); + else if (pstrName == _T ("nativetextcolor")) SetNativeEditTextColor (pstrValue); + else if (pstrName == _T ("nativebkcolor")) SetNativeEditBkColor ((DWORD) FawTools::parse_hex (pstrValue)); + else CLabelUI::SetAttribute (pstrName, pstrValue); + } + + void CEditUI::PaintStatusImage (HDC hDC) { + if (IsFocused ()) m_uButtonState |= UISTATE_FOCUSED; + else m_uButtonState &= ~UISTATE_FOCUSED; + if (!IsEnabled ()) m_uButtonState |= UISTATE_DISABLED; + else m_uButtonState &= ~UISTATE_DISABLED; + + if ((m_uButtonState & UISTATE_DISABLED) != 0) { + if (!m_sDisabledImage.empty ()) { + if (!DrawImage (hDC, m_sDisabledImage)) { + } else return; + } + } else if ((m_uButtonState & UISTATE_FOCUSED) != 0) { + if (!m_sFocusedImage.empty ()) { + if (!DrawImage (hDC, m_sFocusedImage)) { + } else return; + } + } else if ((m_uButtonState & UISTATE_HOT) != 0) { + if (!m_sHotImage.empty ()) { + if (!DrawImage (hDC, m_sHotImage)) { + } else return; + } + } + + if (!m_sNormalImage.empty ()) { + if (!DrawImage (hDC, m_sNormalImage)) { + } else return; + } + } + + void CEditUI::PaintText (HDC hDC) { + DWORD mCurTextColor = m_dwTextColor; + + if (m_dwTextColor == 0) mCurTextColor = m_dwTextColor = m_pManager->GetDefaultFontColor (); + if (m_dwDisabledTextColor == 0) m_dwDisabledTextColor = m_pManager->GetDefaultDisabledColor (); + + CDuiString sDrawText = GetText (); + CDuiString sTipValue = GetTipValue (); + if (sDrawText == sTipValue || sDrawText == _T ("")) { + mCurTextColor = m_dwTipValueColor; + sDrawText = sTipValue; + } else { + CDuiString sTemp = sDrawText; + if (m_bPasswordMode) { + sDrawText.clear (); + string_view_t pStr = sTemp; + while (!pStr.empty ()) { + sDrawText += m_cPasswordChar; + pStr = pStr.substr (1); + } + } + } + + RECT rc = m_rcItem; + rc.left += m_rcTextPadding.left; + rc.right -= m_rcTextPadding.right; + rc.top += m_rcTextPadding.top; + rc.bottom -= m_rcTextPadding.bottom; + if (IsEnabled ()) { + CRenderEngine::DrawText (hDC, m_pManager, rc, sDrawText, mCurTextColor, m_iFont, DT_SINGLELINE | m_uTextStyle); + } else { + CRenderEngine::DrawText (hDC, m_pManager, rc, sDrawText, m_dwDisabledTextColor, m_iFont, DT_SINGLELINE | m_uTextStyle); + } + } +} diff --git a/DuiLib/Control/UIEdit.h b/DuiLib/Control/UIEdit.h new file mode 100644 index 0000000..3268916 --- /dev/null +++ b/DuiLib/Control/UIEdit.h @@ -0,0 +1,88 @@ +#ifndef __UIEDIT_H__ +#define __UIEDIT_H__ + +#pragma once + +namespace DuiLib { + class CEditWnd; + + class UILIB_API CEditUI: public CLabelUI { + DECLARE_DUICONTROL (CEditUI) + friend class CEditWnd; + public: + CEditUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetControlFlags () const; + + void SetEnabled (bool bEnable = true); + void SetText (string_view_t pstrText); + void SetMaxChar (UINT uMax); + UINT GetMaxChar (); + void SetReadOnly (bool bReadOnly); + bool IsReadOnly () const; + void SetPasswordMode (bool bPasswordMode); + bool IsPasswordMode () const; + void SetPasswordChar (TCHAR cPasswordChar); + TCHAR GetPasswordChar () const; + void SetNumberOnly (bool bNumberOnly); + bool IsNumberOnly () const; + int GetWindowStyls () const; + + string_view_t GetNormalImage (); + void SetNormalImage (string_view_t pStrImage); + string_view_t GetHotImage (); + void SetHotImage (string_view_t pStrImage); + string_view_t GetFocusedImage (); + void SetFocusedImage (string_view_t pStrImage); + string_view_t GetDisabledImage (); + void SetDisabledImage (string_view_t pStrImage); + void SetNativeEditBkColor (DWORD dwBkColor); + DWORD GetNativeEditBkColor () const; + void SetNativeEditTextColor (string_view_t pStrColor); + DWORD GetNativeEditTextColor () const; + + bool IsAutoSelAll (); + void SetAutoSelAll (bool bAutoSelAll); + void SetSel (long nStartChar, long nEndChar); + void SetSelAll (); + void SetReplaceSel (string_view_t lpszReplace); + + void SetTipValue (string_view_t pStrTipValue); + CDuiString GetTipValue (); + void SetTipValueColor (string_view_t pStrColor); + DWORD GetTipValueColor (); + + void SetPos (RECT rc, bool bNeedInvalidate = true); + void Move (SIZE szOffset, bool bNeedInvalidate = true); + void SetVisible (bool bVisible = true); + void SetInternVisible (bool bVisible = true); + SIZE EstimateSize (SIZE szAvailable); + void DoEvent (TEventUI& event); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + void PaintStatusImage (HDC hDC); + void PaintText (HDC hDC); + + protected: + CEditWnd *m_pWindow = nullptr; + + UINT m_uMaxChar = 255; + bool m_bReadOnly = false; + bool m_bPasswordMode = false; + bool m_bAutoSelAll = false; + TCHAR m_cPasswordChar = _T ('*'); + UINT m_uButtonState = 0; + CDuiString m_sNormalImage; + CDuiString m_sHotImage; + CDuiString m_sFocusedImage; + CDuiString m_sDisabledImage; + CDuiString m_sTipValue; + DWORD m_dwTipValueColor = 0xFFBAC0C5; + DWORD m_dwEditbkColor = 0xFFFFFFFF; + DWORD m_dwEditTextColor = 0x00000000; + int m_iWindowStyls = 0; + }; +} +#endif // __UIEDIT_H__ \ No newline at end of file diff --git a/DuiLib/Control/UIFadeButton.cpp b/DuiLib/Control/UIFadeButton.cpp new file mode 100644 index 0000000..5073f2c --- /dev/null +++ b/DuiLib/Control/UIFadeButton.cpp @@ -0,0 +1,126 @@ +#include "StdAfx.h" +#include "UIFadeButton.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CFadeButtonUI) + + CFadeButtonUI::CFadeButtonUI (): CUIAnimation (&(*this)) {} + + CFadeButtonUI::~CFadeButtonUI () { + StopAnimation (); + } + + string_view_t CFadeButtonUI::GetClass () const { + return _T ("FadeButtonUI"); + } + + LPVOID CFadeButtonUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("FadeButton")) + return static_cast(this); + return CButtonUI::GetInterface (pstrName); + } + + void CFadeButtonUI::SetNormalImage (string_view_t pStrImage) { + m_sNormalImage = pStrImage; + m_sLastImage = m_sNormalImage; + } + + void CFadeButtonUI::DoEvent (TEventUI& event) { + if (event.Type == UIEVENT_MOUSEENTER && !IsAnimationRunning (FADE_IN_ID)) { + m_bFadeAlpha = 0; + m_bMouseHove = TRUE; + StopAnimation (FADE_OUT_ID); + StartAnimation (FADE_ELLAPSE, FADE_FRAME_COUNT, FADE_IN_ID); + Invalidate (); + return; + } else if (event.Type == UIEVENT_MOUSELEAVE && !IsAnimationRunning (FADE_OUT_ID)) { + m_bFadeAlpha = 0; + m_bMouseLeave = TRUE; + StopAnimation (FADE_IN_ID); + StartAnimation (FADE_ELLAPSE, FADE_FRAME_COUNT, FADE_OUT_ID); + Invalidate (); + return; + } else if (event.Type == UIEVENT_TIMER) { + OnTimer ((int) event.wParam); + } + CButtonUI::DoEvent (event); + } + + void CFadeButtonUI::OnTimer (int nTimerID) { + OnAnimationElapse (nTimerID); + } + + void CFadeButtonUI::PaintStatusImage (HDC hDC) { + if (IsFocused ()) m_uButtonState |= UISTATE_FOCUSED; + else m_uButtonState &= ~UISTATE_FOCUSED; + if (!IsEnabled ()) m_uButtonState |= UISTATE_DISABLED; + else m_uButtonState &= ~UISTATE_DISABLED; + + if ((m_uButtonState & UISTATE_DISABLED) != 0) { + if (!m_sDisabledImage.empty ()) { + if (DrawImage (hDC, m_sDisabledImage)) + return; + } + } else if ((m_uButtonState & UISTATE_PUSHED) != 0) { + if (!m_sPushedImage.empty ()) { + if (DrawImage (hDC, m_sPushedImage)) + return; + } + } else if ((m_uButtonState & UISTATE_FOCUSED) != 0) { + if (!m_sFocusedImage.empty ()) { + if (DrawImage (hDC, m_sFocusedImage)) + return; + } + } + + if (!m_sNormalImage.empty ()) { + if (IsAnimationRunning (FADE_IN_ID) || IsAnimationRunning (FADE_OUT_ID)) { + if (m_bMouseHove) { + m_bMouseHove = FALSE; + m_sLastImage = m_sHotImage; + DrawImage (hDC, m_sNormalImage); + return; + } else if (m_bMouseLeave) { + m_bMouseLeave = FALSE; + m_sLastImage = m_sNormalImage; + DrawImage (hDC, m_sHotImage); + return; + } + + m_sOldImage = m_sNormalImage; + m_sNewImage = m_sHotImage; + if (IsAnimationRunning (FADE_OUT_ID)) { + m_sOldImage = m_sHotImage; + m_sNewImage = m_sNormalImage; + } + CDuiString sFadeOut, sFadeIn; + sFadeOut.Format (_T ("fade='%d'"), 255 - m_bFadeAlpha); + sFadeIn.Format (_T ("fade='%d'"), m_bFadeAlpha); + DrawImage (hDC, m_sOldImage, sFadeOut); + DrawImage (hDC, m_sNewImage, sFadeIn); + return; + } else { + if (m_bMouseHove) { + m_bMouseHove = FALSE; + m_sLastImage = m_sHotImage; + DrawImage (hDC, m_sNormalImage); + } else if (m_bMouseLeave) { + m_bMouseLeave = FALSE; + m_sLastImage = m_sNormalImage; + DrawImage (hDC, m_sHotImage); + } else { + if (m_sLastImage.empty ()) + m_sLastImage = m_sNormalImage; + DrawImage (hDC, m_sLastImage); + } + } + } + } + + void CFadeButtonUI::OnAnimationStep (INT nTotalFrame, INT nCurFrame, INT nAnimationID) { + m_bFadeAlpha = (BYTE) ((nCurFrame / (double) nTotalFrame) * 255); + m_bFadeAlpha = m_bFadeAlpha == 0 ? 10 : m_bFadeAlpha; + Invalidate (); + } + +} // namespace DuiLib \ No newline at end of file diff --git a/DuiLib/Control/UIFadeButton.h b/DuiLib/Control/UIFadeButton.h new file mode 100644 index 0000000..a6dc873 --- /dev/null +++ b/DuiLib/Control/UIFadeButton.h @@ -0,0 +1,44 @@ +#ifndef __UIFADEBUTTON_H__ +#define __UIFADEBUTTON_H__ + +#include "UIAnimation.h" +#pragma once + +namespace DuiLib { + + class UILIB_API CFadeButtonUI: public CButtonUI, public CUIAnimation { + DECLARE_DUICONTROL (CFadeButtonUI) + public: + CFadeButtonUI (); + virtual ~CFadeButtonUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + void SetNormalImage (string_view_t pStrImage); + + void DoEvent (TEventUI& event); + void OnTimer (int nTimerID); + void PaintStatusImage (HDC hDC); + + virtual void OnAnimationStart (INT nAnimationID, BOOL bFirstLoop) {} + virtual void OnAnimationStep (INT nTotalFrame, INT nCurFrame, INT nAnimationID); + virtual void OnAnimationStop (INT nAnimationID) {} + + protected: + CDuiString m_sOldImage; + CDuiString m_sNewImage; + CDuiString m_sLastImage; + BYTE m_bFadeAlpha; + BOOL m_bMouseHove = FALSE; + BOOL m_bMouseLeave = FALSE; + enum { + FADE_IN_ID = 8, + FADE_OUT_ID = 9, + FADE_ELLAPSE = 10, + FADE_FRAME_COUNT = 30, + }; + }; + +} // namespace DuiLib + +#endif // __UIFADEBUTTON_H__ \ No newline at end of file diff --git a/DuiLib/Control/UIFlash.cpp b/DuiLib/Control/UIFlash.cpp new file mode 100644 index 0000000..4650274 --- /dev/null +++ b/DuiLib/Control/UIFlash.cpp @@ -0,0 +1,217 @@ +#include "StdAfx.h" +#include "UIFlash.h" +#include + +#define DISPID_FLASHEVENT_FLASHCALL ( 0x00C5 ) +#define DISPID_FLASHEVENT_FSCOMMAND ( 0x0096 ) +#define DISPID_FLASHEVENT_ONPROGRESS ( 0x07A6 ) + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CFlashUI) + + CFlashUI::CFlashUI (void) { + CDuiString strFlashCLSID = _T ("{D27CDB6E-AE6D-11CF-96B8-444553540000}"); + OLECHAR szCLSID[100] = { 0 }; +#ifndef _UNICODE + ::MultiByteToWideChar (::GetACP (), 0, strFlashCLSID, -1, szCLSID, lengthof (szCLSID) - 1); +#else + _tcsncpy (szCLSID, strFlashCLSID.c_str (), lengthof (szCLSID) - 1); +#endif + ::CLSIDFromString (szCLSID, &m_clsid); + } + + CFlashUI::~CFlashUI (void) { + if (m_pFlashEventHandler) { + m_pFlashEventHandler->Release (); + m_pFlashEventHandler = nullptr; + } + ReleaseControl (); + } + + string_view_t CFlashUI::GetClass () const { + return DUI_CTRL_FLASH; + } + + LPVOID CFlashUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_FLASH) return static_cast(this); + return CActiveXUI::GetInterface (pstrName); + } + + HRESULT STDMETHODCALLTYPE CFlashUI::GetTypeInfoCount (__RPC__out UINT *pctinfo) { + return E_NOTIMPL; + } + + HRESULT STDMETHODCALLTYPE CFlashUI::GetTypeInfo (UINT iTInfo, LCID lcid, __RPC__deref_out_opt ITypeInfo **ppTInfo) { + return E_NOTIMPL; + } + + HRESULT STDMETHODCALLTYPE CFlashUI::GetIDsOfNames (__RPC__in REFIID riid, __RPC__in_ecount_full (cNames) LPOLESTR *rgszNames, UINT cNames, LCID lcid, __RPC__out_ecount_full (cNames) DISPID *rgDispId) { + return E_NOTIMPL; + } + + HRESULT STDMETHODCALLTYPE CFlashUI::Invoke (DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) { + + return S_OK; + switch (dispIdMember) { + case DISPID_FLASHEVENT_FLASHCALL: + if (pDispParams->cArgs != 1 || pDispParams->rgvarg[0].vt != VT_BSTR) + return E_INVALIDARG; + return this->FlashCall (pDispParams->rgvarg[0].bstrVal); + case DISPID_FLASHEVENT_FSCOMMAND: + if (pDispParams && pDispParams->cArgs == 2) { + if (pDispParams->rgvarg[0].vt == VT_BSTR && + pDispParams->rgvarg[1].vt == VT_BSTR) { + return FSCommand (pDispParams->rgvarg[1].bstrVal, pDispParams->rgvarg[0].bstrVal); + } else { + return DISP_E_TYPEMISMATCH; + } + } else { + return DISP_E_BADPARAMCOUNT; + } + case DISPID_FLASHEVENT_ONPROGRESS: + return OnProgress (*pDispParams->rgvarg[0].plVal); + case DISPID_READYSTATECHANGE: + return this->OnReadyStateChange (pDispParams->rgvarg[0].lVal); + } + + return S_OK; + } + + HRESULT STDMETHODCALLTYPE CFlashUI::QueryInterface (REFIID riid, void **ppvObject) { + *ppvObject = nullptr; + + if (riid == IID_IUnknown) + *ppvObject = static_cast(this); + else if (riid == IID_IDispatch) + *ppvObject = static_cast(this); + else if (riid == __uuidof(_IShockwaveFlashEvents)) + *ppvObject = static_cast<_IShockwaveFlashEvents*>(this); + + if (*ppvObject) + AddRef (); + return (!*ppvObject) ? E_NOINTERFACE : S_OK; + } + + ULONG STDMETHODCALLTYPE CFlashUI::AddRef (void) { + ::InterlockedIncrement (&m_dwRef); + return m_dwRef; + } + + ULONG STDMETHODCALLTYPE CFlashUI::Release (void) { + ::InterlockedDecrement (&m_dwRef); + return m_dwRef; + } + + HRESULT CFlashUI::OnReadyStateChange (long newState) { + if (m_pFlashEventHandler) { + return m_pFlashEventHandler->OnReadyStateChange (newState); + } + return S_OK; + } + + HRESULT CFlashUI::OnProgress (long percentDone) { + if (m_pFlashEventHandler) { + return m_pFlashEventHandler->OnProgress (percentDone); + } + return S_OK; + } + + HRESULT CFlashUI::FSCommand (_bstr_t command, _bstr_t args) { + if (m_pFlashEventHandler) { + return m_pFlashEventHandler->FSCommand (command, args); + } + return S_OK; + } + + HRESULT CFlashUI::FlashCall (_bstr_t request) { + if (m_pFlashEventHandler) { + return m_pFlashEventHandler->FlashCall (request); + } + return S_OK; + } + + void CFlashUI::ReleaseControl () { + //GetManager()->RemoveTranslateAccelerator(this); + RegisterEventHandler (FALSE); + if (m_pFlash) { + m_pFlash->Release (); + m_pFlash = nullptr; + } + } + + bool CFlashUI::DoCreateControl () { + if (!CActiveXUI::DoCreateControl ()) + return false; + //GetManager()->AddTranslateAccelerator(this); + GetControl (__uuidof(IShockwaveFlash), (LPVOID*) &m_pFlash); + RegisterEventHandler (TRUE); + return true; + } + + void CFlashUI::SetFlashEventHandler (CFlashEventHandler* pHandler) { + if (m_pFlashEventHandler) { + m_pFlashEventHandler->Release (); + } + if (!pHandler) { + m_pFlashEventHandler = pHandler; + return; + } + m_pFlashEventHandler = pHandler; + m_pFlashEventHandler->AddRef (); + } + + LRESULT CFlashUI::TranslateAccelerator (MSG *pMsg) { + if (pMsg->message < WM_KEYFIRST || pMsg->message > WM_KEYLAST) + return S_FALSE; + + if (!m_pFlash) + return E_NOTIMPL; + + // ǰWebڲǽ,ټ + BOOL bIsChild = FALSE; + HWND hTempWnd = NULL; + HWND hWndFocus = ::GetFocus (); + + hTempWnd = hWndFocus; + while (hTempWnd) { + if (hTempWnd == m_hwndHost) { + bIsChild = TRUE; + break; + } + hTempWnd = ::GetParent (hTempWnd); + } + if (!bIsChild) + return S_FALSE; + + CComPtr pObj; + if (FAILED (m_pFlash->QueryInterface (IID_IOleInPlaceActiveObject, (LPVOID *) &pObj))) + return S_FALSE; + + HRESULT hResult = pObj->TranslateAccelerator (pMsg); + return hResult; + } + + HRESULT CFlashUI::RegisterEventHandler (BOOL inAdvise) { + if (!m_pFlash) + return S_FALSE; + + HRESULT hr = S_FALSE; + CComPtr pCPC; + CComPtr pCP; + + hr = m_pFlash->QueryInterface (IID_IConnectionPointContainer, (void **) &pCPC); + if (FAILED (hr)) + return hr; + hr = pCPC->FindConnectionPoint (__uuidof(_IShockwaveFlashEvents), &pCP); + if (FAILED (hr)) + return hr; + + if (inAdvise) { + hr = pCP->Advise ((IDispatch*) this, &m_dwCookie); + } else { + hr = pCP->Unadvise (m_dwCookie); + } + return hr; + } + +}; \ No newline at end of file diff --git a/DuiLib/Control/UIFlash.h b/DuiLib/Control/UIFlash.h new file mode 100644 index 0000000..5034747 --- /dev/null +++ b/DuiLib/Control/UIFlash.h @@ -0,0 +1,63 @@ +#ifndef __UIFLASH_H__ +#define __UIFLASH_H__ +#pragma once + +// \Utils\Flash11.tlb ΪFlash11ӿļַڵͰ汾ڣʹע +// #import "PROGID:ShockwaveFlash.ShockwaveFlash" \ +// raw_interfaces_only, /* Don't add raw_ to method names */ \ +// named_guids, /* Named guids and declspecs */ \ +// rename("IDispatchEx","IMyDispatchEx") /* fix conflicting with IDispatchEx ant dispex.h */ +// using namespace ShockwaveFlashObjects; +#include "../Utils/FlashEventHandler.h" +#include "../Utils/flash11.tlh" + +class CActiveXCtrl; + +namespace DuiLib { + class UILIB_API CFlashUI + : public CActiveXUI + // , public IOleInPlaceSiteWindowless // ͸ģʽͼҪʵӿ + , public _IShockwaveFlashEvents + , public ITranslateAccelerator { + DECLARE_DUICONTROL (CFlashUI) + public: + CFlashUI (void); + virtual ~CFlashUI (void); + + void SetFlashEventHandler (CFlashEventHandler* pHandler); + virtual bool DoCreateControl (); + IShockwaveFlash *m_pFlash = nullptr; + + private: + virtual string_view_t GetClass () const; + virtual LPVOID GetInterface (string_view_t pstrName); + + virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount (__RPC__out UINT *pctinfo); + virtual HRESULT STDMETHODCALLTYPE GetTypeInfo (UINT iTInfo, LCID lcid, __RPC__deref_out_opt ITypeInfo **ppTInfo); + virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames (__RPC__in REFIID riid, __RPC__in_ecount_full (cNames) LPOLESTR *rgszNames, UINT cNames, LCID lcid, __RPC__out_ecount_full (cNames) DISPID *rgDispId); + virtual HRESULT STDMETHODCALLTYPE Invoke (DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr); + + virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID riid, void **ppvObject); + virtual ULONG STDMETHODCALLTYPE AddRef (void); + virtual ULONG STDMETHODCALLTYPE Release (void); + + HRESULT OnReadyStateChange (long newState); + HRESULT OnProgress (long percentDone); + HRESULT FSCommand (_bstr_t command, _bstr_t args); + HRESULT FlashCall (_bstr_t request); + + virtual void ReleaseControl (); + HRESULT RegisterEventHandler (BOOL inAdvise); + + // ITranslateAccelerator + // DuilibϢַWebBrowser + virtual LRESULT TranslateAccelerator (MSG *pMsg); + + private: + LONG m_dwRef = 0; + DWORD m_dwCookie = 0; + CFlashEventHandler *m_pFlashEventHandler = nullptr; + }; +} + +#endif // __UIFLASH_H__ diff --git a/DuiLib/Control/UIGifAnim.cpp b/DuiLib/Control/UIGifAnim.cpp new file mode 100644 index 0000000..af961e8 --- /dev/null +++ b/DuiLib/Control/UIGifAnim.cpp @@ -0,0 +1,318 @@ +#include "StdAfx.h" +#include "UIGifAnim.h" + +/////////////////////////////////////////////////////////////////////////////////////// +//DECLARE_HANDLE (HZIP); // An HZIP identifies a zip file that has been opened +//typedef DWORD ZRESULT; +//typedef struct { +// int index; // index of this file within the zip +// char name[MAX_PATH]; // filename within the zip +// DWORD attr; // attributes, as in GetFileAttributes. +// FILETIME atime, ctime, mtime;// access, create, modify filetimes +// long comp_size; // sizes of item, compressed and uncompressed. These +// long unc_size; // may be -1 if not yet known (e.g. being streamed in) +//} ZIPENTRY; +//typedef struct { +// int index; // index of this file within the zip +// TCHAR name[MAX_PATH]; // filename within the zip +// DWORD attr; // attributes, as in GetFileAttributes. +// FILETIME atime, ctime, mtime;// access, create, modify filetimes +// long comp_size; // sizes of item, compressed and uncompressed. These +// long unc_size; // may be -1 if not yet known (e.g. being streamed in) +//} ZIPENTRYW; +//#define OpenZip OpenZipU +//#define CloseZip(hz) CloseZipU(hz) +//extern HZIP OpenZipU (void *z, unsigned int len, DWORD flags); +//extern ZRESULT CloseZipU (HZIP hz); +//#ifdef _UNICODE +//#define ZIPENTRY ZIPENTRYW +//#define GetZipItem GetZipItemW +//#define FindZipItem FindZipItemW +//#else +//#define GetZipItem GetZipItemA +//#define FindZipItem FindZipItemA +//#endif +//extern ZRESULT GetZipItemA (HZIP hz, int index, ZIPENTRY *ze); +//extern ZRESULT GetZipItemW (HZIP hz, int index, ZIPENTRYW *ze); +//extern ZRESULT FindZipItemA (HZIP hz, const TCHAR *name, bool ic, int *index, ZIPENTRY *ze); +//extern ZRESULT FindZipItemW (HZIP hz, const TCHAR *name, bool ic, int *index, ZIPENTRYW *ze); +//extern ZRESULT UnzipItem (HZIP hz, int index, void *dst, unsigned int len, DWORD flags); +namespace DuiLib { + IMPLEMENT_DUICONTROL (CGifAnimUI) + CGifAnimUI::CGifAnimUI (void) {} + + + CGifAnimUI::~CGifAnimUI (void) { + DeleteGif (); + m_pManager->KillTimer (this, EVENT_TIEM_ID); + + } + + string_view_t CGifAnimUI::GetClass () const { + return _T ("GifAnimUI"); + } + + LPVOID CGifAnimUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_GIFANIM) return static_cast(this); + return CControlUI::GetInterface (pstrName); + } + + void CGifAnimUI::DoInit () { + InitGifImage (); + } + + bool CGifAnimUI::DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + if (!::IntersectRect (&m_rcPaint, &rcPaint, &m_rcItem)) return true; + if (!m_pGifImage) { + InitGifImage (); + } + DrawFrame (hDC); + return true; + } + + void CGifAnimUI::DoEvent (TEventUI& event) { + if (event.Type == UIEVENT_TIMER) + OnTimer ((UINT_PTR) event.wParam); + } + + void CGifAnimUI::SetVisible (bool bVisible /* = true */) { + CControlUI::SetVisible (bVisible); + if (bVisible) + PlayGif (); + else + StopGif (); + } + + void CGifAnimUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("bkimage")) SetBkImage (pstrValue); + else if (pstrName == _T ("autoplay")) { + SetAutoPlay (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("autosize")) { + SetAutoSize (FawTools::parse_bool (pstrValue)); + } else + CControlUI::SetAttribute (pstrName, pstrValue); + } + + void CGifAnimUI::SetBkImage (string_view_t pStrImage) { + if (m_sBkImage == pStrImage || nullptr == pStrImage) return; + + m_sBkImage = pStrImage; + + StopGif (); + DeleteGif (); + + Invalidate (); + + } + + string_view_t CGifAnimUI::GetBkImage () { + return m_sBkImage; + } + + void CGifAnimUI::SetAutoPlay (bool bIsAuto) { + m_bIsAutoPlay = bIsAuto; + } + + bool CGifAnimUI::IsAutoPlay () const { + return m_bIsAutoPlay; + } + + void CGifAnimUI::SetAutoSize (bool bIsAuto) { + m_bIsAutoSize = bIsAuto; + } + + bool CGifAnimUI::IsAutoSize () const { + return m_bIsAutoSize; + } + + void CGifAnimUI::PlayGif () { + if (m_bIsPlaying || !m_pGifImage || m_nFrameCount <= 1) { + return; + } + + long lPause = ((long*) m_pPropertyItem->value)[m_nFramePosition] * 10; + if (lPause == 0) lPause = 100; + m_pManager->SetTimer (this, EVENT_TIEM_ID, lPause); + + m_bIsPlaying = true; + } + + void CGifAnimUI::PauseGif () { + if (!m_bIsPlaying || !m_pGifImage) { + return; + } + + m_pManager->KillTimer (this, EVENT_TIEM_ID); + this->Invalidate (); + m_bIsPlaying = false; + } + + void CGifAnimUI::StopGif () { + if (!m_bIsPlaying) { + return; + } + + m_pManager->KillTimer (this, EVENT_TIEM_ID); + m_nFramePosition = 0; + this->Invalidate (); + m_bIsPlaying = false; + } + + void CGifAnimUI::InitGifImage () { + m_pGifImage = CRenderEngine::GdiplusLoadImage (GetBkImage ()); + if (!m_pGifImage) return; + UINT nCount = 0; + nCount = m_pGifImage->GetFrameDimensionsCount (); + GUID* pDimensionIDs = new GUID[nCount]; + m_pGifImage->GetFrameDimensionsList (pDimensionIDs, nCount); + m_nFrameCount = m_pGifImage->GetFrameCount (&pDimensionIDs[0]); + if (m_nFrameCount > 1) { + int nSize = m_pGifImage->GetPropertyItemSize (PropertyTagFrameDelay); + m_pPropertyItem = (Gdiplus::PropertyItem*) malloc (nSize); + m_pGifImage->GetPropertyItem (PropertyTagFrameDelay, nSize, m_pPropertyItem); + } + delete[] pDimensionIDs; + pDimensionIDs = nullptr; + + if (m_bIsAutoSize) { + SetFixedWidth (m_pGifImage->GetWidth ()); + SetFixedHeight (m_pGifImage->GetHeight ()); + } + if (m_bIsAutoPlay) { + PlayGif (); + } + } + + void CGifAnimUI::DeleteGif () { + if (m_pStream) { + m_pStream->Release (); + m_pStream = nullptr; + } + if (m_pGifImage) { + delete m_pGifImage; + m_pGifImage = nullptr; + } + + if (m_pPropertyItem) { + free (m_pPropertyItem); + m_pPropertyItem = nullptr; + } + m_nFrameCount = 0; + m_nFramePosition = 0; + } + + void CGifAnimUI::OnTimer (UINT_PTR idEvent) { + if (idEvent != EVENT_TIEM_ID) + return; + m_pManager->KillTimer (this, EVENT_TIEM_ID); + this->Invalidate (); + + m_nFramePosition = (++m_nFramePosition) % m_nFrameCount; + + long lPause = ((long*) m_pPropertyItem->value)[m_nFramePosition] * 10; + if (lPause == 0) lPause = 100; + m_pManager->SetTimer (this, EVENT_TIEM_ID, lPause); + } + + void CGifAnimUI::DrawFrame (HDC hDC) { + if (!hDC || !m_pGifImage) return; + GUID pageGuid = Gdiplus::FrameDimensionTime; + Gdiplus::Graphics graphics (hDC); + graphics.DrawImage (m_pGifImage, m_rcItem.left, m_rcItem.top, m_rcItem.right - m_rcItem.left, m_rcItem.bottom - m_rcItem.top); + m_pGifImage->SelectActiveFrame (&pageGuid, m_nFramePosition); + } + + Gdiplus::Image* CGifAnimUI::LoadGifFromFile (string_view_t pstrGifPath) { + LPBYTE pData = NULL; + DWORD dwSize = 0; + + do { + CDuiString sFile = CPaintManagerUI::GetResourcePath (); + if (CPaintManagerUI::GetResourceZip ().empty ()) { + sFile += pstrGifPath; + HANDLE hFile = ::CreateFile (sFile.c_str (), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) break; + dwSize = ::GetFileSize (hFile, NULL); + if (dwSize == 0) break; + + DWORD dwRead = 0; + pData = new BYTE[dwSize]; + ::ReadFile (hFile, pData, dwSize, &dwRead, NULL); + ::CloseHandle (hFile); + + if (dwRead != dwSize) { + delete[] pData; + pData = NULL; + break; + } + } else { + sFile += CPaintManagerUI::GetResourceZip (); + HZIP hz = NULL; + if (CPaintManagerUI::IsCachedResourceZip ()) hz = (HZIP) CPaintManagerUI::GetResourceZipHandle (); + //else hz = OpenZip ((void*) sFile, 0, 2); + else hz = OpenZip (sFile.c_str ()); + if (hz == NULL) break; + ZIPENTRY ze; + int i; + if (FindZipItem (hz, pstrGifPath.data (), true, &i, &ze) != 0) break; + dwSize = ze.unc_size; + if (dwSize == 0) break; + pData = new BYTE[dwSize]; + //int res = UnzipItem (hz, i, pData, dwSize, 3); + int res = UnzipItem (hz, i, pData, dwSize); + if (res != 0x00000000 && res != 0x00000600) { + delete[] pData; + pData = NULL; + if (!CPaintManagerUI::IsCachedResourceZip ()) CloseZip (hz); + break; + } + if (!CPaintManagerUI::IsCachedResourceZip ()) CloseZip (hz); + } + + } while (0); + + while (!pData) { + //ͼƬ, ֱȥȡbitmap.m_lpstrָ· + HANDLE hFile = ::CreateFile (pstrGifPath.data (), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, \ + FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) break; + dwSize = ::GetFileSize (hFile, NULL); + if (dwSize == 0) break; + + DWORD dwRead = 0; + pData = new BYTE[dwSize]; + ::ReadFile (hFile, pData, dwSize, &dwRead, NULL); + ::CloseHandle (hFile); + + if (dwRead != dwSize) { + delete[] pData; + pData = NULL; + } + break; + } + if (!pData) { + return NULL; + } + + Gdiplus::Image* pImage = LoadGifFromMemory (pData, dwSize); + delete[] pData; + return pImage; + } + + Gdiplus::Image* CGifAnimUI::LoadGifFromMemory (LPVOID pBuf, size_t dwSize) { + HGLOBAL hMem = ::GlobalAlloc (GMEM_MOVEABLE, dwSize); + BYTE* pMem = (BYTE*)::GlobalLock (hMem); + + memcpy (pMem, pBuf, dwSize); + ::GlobalUnlock (hMem); + + ::CreateStreamOnHGlobal (hMem, TRUE, &m_pStream); + Gdiplus::Image *pImg = Gdiplus::Image::FromStream (m_pStream); + if (!pImg || pImg->GetLastStatus () != Gdiplus::Ok) { + m_pStream->Release (); + m_pStream = NULL; + return 0; + } + return pImg; + } +} \ No newline at end of file diff --git a/DuiLib/Control/UIGifAnim.h b/DuiLib/Control/UIGifAnim.h new file mode 100644 index 0000000..312add4 --- /dev/null +++ b/DuiLib/Control/UIGifAnim.h @@ -0,0 +1,55 @@ +#ifndef GifAnimUI_h__ +#define GifAnimUI_h__ + +#pragma once + +namespace DuiLib { + class UILIB_API CGifAnimUI: public CControlUI { + enum { + EVENT_TIEM_ID = 100, + }; + DECLARE_DUICONTROL (CGifAnimUI) + public: + CGifAnimUI (void); + virtual ~CGifAnimUI (void); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + void DoInit () override; + bool DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl); + void DoEvent (TEventUI& event); + void SetVisible (bool bVisible = true); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + void SetBkImage (string_view_t pStrImage); + string_view_t GetBkImage (); + + void SetAutoPlay (bool bIsAuto = true); + bool IsAutoPlay () const; + void SetAutoSize (bool bIsAuto = true); + bool IsAutoSize () const; + void PlayGif (); + void PauseGif (); + void StopGif (); + + private: + void InitGifImage (); + void DeleteGif (); + void OnTimer (UINT_PTR idEvent); + void DrawFrame (HDC hDC); // GIFÿ֡ + Gdiplus::Image* LoadGifFromFile (string_view_t pstrGifPath); + Gdiplus::Image* LoadGifFromMemory (LPVOID pBuf, size_t dwSize); + private: + Gdiplus::Image *m_pGifImage = nullptr; + UINT m_nFrameCount = 0; // gifͼƬ֡ + UINT m_nFramePosition = 0; // ǰŵڼ֡ + Gdiplus::PropertyItem *m_pPropertyItem = nullptr; // ֮֡֡ʱ + + CDuiString m_sBkImage; + bool m_bIsAutoPlay = true; // ǷԶgif + bool m_bIsAutoSize = false; // ǷԶͼƬôС + bool m_bIsPlaying = false; + IStream *m_pStream = nullptr; + }; +} + +#endif // GifAnimUI_h__ \ No newline at end of file diff --git a/DuiLib/Control/UIGifAnimEx.cpp b/DuiLib/Control/UIGifAnimEx.cpp new file mode 100644 index 0000000..a999b2c --- /dev/null +++ b/DuiLib/Control/UIGifAnimEx.cpp @@ -0,0 +1,164 @@ +#include "StdAfx.h" +#ifdef USE_XIMAGE_EFFECT +#include "UIGifAnimEx.h" +#include "../3rd/CxImage/ximage.h" +// +namespace DuiLib { +#define GIFANIMUIEX_EVENT_TIEM_ID 100 + IMPLEMENT_DUICONTROL (CGifAnimExUI) + struct CGifAnimExUI::Imp { + bool m_bRealStop;//ⲿֹͣ + bool m_bLoadImg;//ǷعͼƬ + bool m_bTimer;//Ƿʱ + bool m_bAutoStart;//ǷԶʼ + int m_nDelay;//ѭ + UINT m_nFrameCount;//gifͼƬ֡ + UINT m_nFramePosition;//ǰŵڼ֡ + CxImage *m_pGifImage;//gif + CPaintManagerUI*& m_pManager; + CGifAnimExUI *m_pOwer;//ӵ + Imp (CPaintManagerUI* & pManager):m_pManager (pManager), + m_bLoadImg (false), m_bTimer (false), + m_nDelay (100), m_pGifImage (nullptr), m_nFrameCount (0U), + m_nFramePosition (0U), + m_pOwer (nullptr), m_bRealStop (false), m_bAutoStart (true) {} + void SetOwer (CGifAnimExUI *pOwer) { + m_pOwer = pOwer; + } + virtual ~Imp () { + if (m_pGifImage) { + delete m_pGifImage; + m_pGifImage = nullptr; + } + } + inline void CheckLoadImage () { + if (!m_bLoadImg) + LoadGifImage (); + } + inline bool IsLoadImage () { + return m_bLoadImg; + } + virtual void LoadGifImage () { + CDuiString sImag = m_pOwer->GetBkImage (); + m_bLoadImg = true; + m_pGifImage = CRenderEngine::LoadGifImageX (std::variant (sImag), 0, 0); + if (!m_pGifImage) return; + m_nFrameCount = m_pGifImage->GetNumFrames (); + m_nFramePosition = 0; + m_nDelay = m_pGifImage->GetFrameDelay (); + if (m_nDelay <= 0) + m_nDelay = 100; + if (!m_bAutoStart) + m_bRealStop = true; + if (m_bAutoStart && m_pOwer->IsVisible ()) + StartAnim (); + } + void SetAutoStart (bool bAuto) { + m_bAutoStart = bAuto; + } + void StartAnim () { + if (!m_bTimer) { + if (!IsLoadImage ()) { + LoadGifImage (); + m_pOwer->Invalidate (); + } + if (m_pGifImage) + m_pManager->SetTimer (m_pOwer, GIFANIMUIEX_EVENT_TIEM_ID, m_nDelay); + m_bTimer = true; + } + } + void StopAnim (bool bGoFirstFrame)//bGoFirstFrame Ƿܵһ֡ + { + if (m_bTimer) { + if (bGoFirstFrame) { + m_nFramePosition = 0U; + m_pOwer->Invalidate (); + } + m_pManager->KillTimer (m_pOwer, GIFANIMUIEX_EVENT_TIEM_ID); + m_bTimer = false; + } + } + void EventOnTimer (const WPARAM idEvent) { + if (idEvent != GIFANIMUIEX_EVENT_TIEM_ID) + return; + ++m_nFramePosition; + if (m_nFramePosition >= m_nFrameCount) + m_nFramePosition = 0; + if (!m_pOwer->IsVisible ())return; + m_pOwer->Invalidate (); + } + void DrawFrame (HDC hDC, const RECT& rcPaint, const RECT &rcItem) { + if (!hDC || !m_pGifImage) return; + if (m_pGifImage) { + if (CxImage* pImage = m_pGifImage->GetFrame (m_nFramePosition)) + pImage->Draw2 (hDC, rcItem); + } + } + void EventSetVisible (bool bVisible) { + if (bVisible) { + if (!m_bRealStop) + StartAnim (); + } else + StopAnim (true); + } + }; + CGifAnimExUI::CGifAnimExUI (void):m_pImp (new CGifAnimExUI::Imp (m_pManager)) { + this; + m_pImp->SetOwer (this); + } + CGifAnimExUI::~CGifAnimExUI (void) { + if (m_pImp) { + m_pImp->StopAnim (false); + delete m_pImp; + m_pImp = nullptr; + } + } + string_view_t CGifAnimExUI::GetClass () const { + return _T ("GifAnimUI"); + } + LPVOID CGifAnimExUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("GifAnim")) + return static_cast(this); + return CLabelUI::GetInterface (pstrName); + } + void CGifAnimExUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("auto")) + m_pImp->SetAutoStart (FawTools::parse_bool (pstrValue)); + else + __super::SetAttribute (pstrName, pstrValue); + } + void CGifAnimExUI::Init () { + __super::Init (); + m_pImp->CheckLoadImage (); + } + void CGifAnimExUI::SetVisible (bool bVisible /*= true*/) { + __super::SetVisible (bVisible); + m_pImp->EventSetVisible (bVisible); + } + void CGifAnimExUI::SetInternVisible (bool bVisible/* = true*/) { + __super::SetInternVisible (bVisible); + m_pImp->EventSetVisible (bVisible); + } + + bool CGifAnimExUI::DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + if (!::IntersectRect (&m_rcPaint, &rcPaint, &m_rcItem)) return true; + m_pImp->DrawFrame (hDC, rcPaint, m_rcItem); + + return true; + } + void CGifAnimExUI::DoEvent (TEventUI& event) { + this; + WPARAM nID = event.wParam; + if (event.Type == UIEVENT_TIMER) + m_pImp->EventOnTimer (nID); + } + void CGifAnimExUI::StartAnim () { + m_pImp->m_bRealStop = false; + m_pImp->StartAnim (); + } + void CGifAnimExUI::StopAnim () { + m_pImp->m_bRealStop = true; + m_pImp->StopAnim (true); + } +} +#endif//USE_XIMAGE_EFFECT \ No newline at end of file diff --git a/DuiLib/Control/UIGifAnimEx.h b/DuiLib/Control/UIGifAnimEx.h new file mode 100644 index 0000000..6f8163d --- /dev/null +++ b/DuiLib/Control/UIGifAnimEx.h @@ -0,0 +1,37 @@ +#ifndef GifAnimUIEX_h__ +#define GifAnimUIEX_h__ +#pragma once +/* write by wangji 2016.03.16 +** gifؼgdi+ռCPUߵ⣬ximage +** ע⣺ʹõʱԤͷļаUIlib.hǰȶUSE_XIMAGE_EFFECT +** #define USE_XIMAGE_EFFECT +** #include "UIlib.h" +*/ +#ifdef USE_XIMAGE_EFFECT +namespace DuiLib { + class CLabelUI; + + class UILIB_API CGifAnimExUI: public CLabelUI { + DECLARE_DUICONTROL (CGifAnimExUI) + public: + CGifAnimExUI (void); + virtual ~CGifAnimExUI (void); + public: + virtual string_view_t GetClass () const; + virtual LPVOID GetInterface (string_view_t pstrName); + virtual void Init (); + virtual void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + virtual void SetVisible (bool bVisible = true); + virtual void SetInternVisible (bool bVisible = true); + virtual bool DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl); + virtual void DoEvent (TEventUI& event); + public: + void StartAnim (); + void StopAnim (); + protected: + struct Imp; + Imp* m_pImp; + }; +} +#endif //USE_XIMAGE_EFFECT +#endif // GifAnimUIEx_h__ diff --git a/DuiLib/Control/UIGroupBox.cpp b/DuiLib/Control/UIGroupBox.cpp new file mode 100644 index 0000000..08952ef --- /dev/null +++ b/DuiLib/Control/UIGroupBox.cpp @@ -0,0 +1,156 @@ +#include "StdAfx.h" +#include "UIGroupBox.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CGroupBoxUI) + + ////////////////////////////////////////////////////////////////////////// + // + CGroupBoxUI::CGroupBoxUI (): m_uTextStyle (DT_SINGLELINE | DT_VCENTER | DT_CENTER) { + SetInset ({ 20, 25, 20, 20 }); + } + + CGroupBoxUI::~CGroupBoxUI () {} + + string_view_t CGroupBoxUI::GetClass () const { + return _T ("GroupBoxUI"); + } + + LPVOID CGroupBoxUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("GroupBox")) return static_cast(this); + return CVerticalLayoutUI::GetInterface (pstrName); + } + void CGroupBoxUI::SetTextColor (DWORD dwTextColor) { + m_dwTextColor = dwTextColor; + Invalidate (); + } + + DWORD CGroupBoxUI::GetTextColor () const { + return m_dwTextColor; + } + void CGroupBoxUI::SetDisabledTextColor (DWORD dwTextColor) { + m_dwDisabledTextColor = dwTextColor; + Invalidate (); + } + + DWORD CGroupBoxUI::GetDisabledTextColor () const { + return m_dwDisabledTextColor; + } + void CGroupBoxUI::SetFont (int index) { + m_iFont = index; + Invalidate (); + } + + int CGroupBoxUI::GetFont () const { + return m_iFont; + } + void CGroupBoxUI::PaintText (HDC hDC) { + CDuiString sText = GetText (); + if (sText.empty ()) { + return; + } + + if (m_dwTextColor == 0) m_dwTextColor = m_pManager->GetDefaultFontColor (); + if (m_dwDisabledTextColor == 0) m_dwDisabledTextColor = m_pManager->GetDefaultDisabledColor (); + if (sText.empty ()) return; + + RECT rcText = m_rcItem; + ::InflateRect (&rcText, -5, -5); + SIZE szAvailable = { rcText.right - rcText.left, rcText.bottom - rcText.top }; + SIZE sz = CalcrectSize (szAvailable); + + // + rcText.left = rcText.left + 15; + rcText.top = rcText.top - 5; + rcText.right = rcText.left + sz.cx; + rcText.bottom = rcText.top + sz.cy; + + DWORD dwTextColor = m_dwTextColor; + if (!IsEnabled ()) dwTextColor = m_dwDisabledTextColor; + CRenderEngine::DrawText (hDC, m_pManager, rcText, sText, dwTextColor, m_iFont, m_uTextStyle, GetAdjustColor (m_dwBackColor)); + } + void CGroupBoxUI::PaintBorder (HDC hDC) { + int nBorderSize; + SIZE cxyBorderRound = { 0 }; + RECT rcBorderSize = { 0 }; + if (m_pManager) { + nBorderSize = GetManager ()->GetDPIObj ()->Scale (m_nBorderSize); + cxyBorderRound = GetManager ()->GetDPIObj ()->Scale (m_cxyBorderRound); + rcBorderSize = GetManager ()->GetDPIObj ()->Scale (m_rcBorderSize); + } else { + nBorderSize = m_nBorderSize; + cxyBorderRound = m_cxyBorderRound; + rcBorderSize = m_rcBorderSize; + } + + if (nBorderSize > 0) { + //RECT rcItem = m_rcItem; + //rcItem.Deflate (5, 5); + + //if (cxyBorderRound.cx > 0 || cxyBorderRound.cy > 0) {//ԲDZ߿ + // if (IsFocused () && m_dwFocusBorderColor != 0) + // CRenderEngine::DrawRoundRect (hDC, rcItem, nBorderSize, cxyBorderRound.cx, cxyBorderRound.cy, GetAdjustColor (m_dwFocusBorderColor)); + // else + // CRenderEngine::DrawRoundRect (hDC, rcItem, nBorderSize, cxyBorderRound.cx, cxyBorderRound.cy, GetAdjustColor (m_dwBorderColor)); + //} else { + // if (IsFocused () && m_dwFocusBorderColor != 0) + // CRenderEngine::DrawRect (hDC, rcItem, nBorderSize, GetAdjustColor (m_dwFocusBorderColor)); + // else + // CRenderEngine::DrawRect (hDC, rcItem, nBorderSize, GetAdjustColor (m_dwBorderColor)); + //} + Gdiplus::Bitmap gb (m_rcItem.right - m_rcItem.left, m_rcItem.bottom - m_rcItem.top, PixelFormat32bppARGB); + Gdiplus::Graphics gg (&gb); + RECT rcItem = m_rcItem; + rcItem.right -= rcItem.left; + rcItem.bottom -= rcItem.top; + rcItem.left = rcItem.top = 0; + ::InflateRect (&rcItem, -5, -5); + DWORD dwColor = GetAdjustColor ((IsFocused () && m_dwFocusBorderColor != 0) ? m_dwFocusBorderColor : m_dwBorderColor); + HDC gghdc = gg.GetHDC (); + if (cxyBorderRound.cx > 0 || cxyBorderRound.cy > 0) { + CRenderEngine::DrawRoundRect (gghdc, rcItem, nBorderSize, cxyBorderRound.cx, cxyBorderRound.cy, dwColor); + } else { + CRenderEngine::DrawRect (gghdc, rcItem, nBorderSize, dwColor); + } + gg.ReleaseHDC (gghdc); + + // λõı߿ + SIZE szAvailable = { rcItem.right - rcItem.left, rcItem.bottom - rcItem.top }; + SIZE sz = CalcrectSize (szAvailable); + rcItem.left = rcItem.left + 15; + rcItem.top = rcItem.top - 5; + rcItem.right = rcItem.left + sz.cx; + rcItem.bottom = rcItem.top + sz.cy; + Gdiplus::Color trans_c ((Gdiplus::ARGB) 0); + for (int i = rcItem.left; i <= rcItem.right; ++i) + for (int j = rcItem.top; j <= rcItem.bottom; ++j) + gb.SetPixel (i, j, trans_c); + Gdiplus::Graphics gx (hDC); + gx.DrawImage (&gb, Gdiplus::Rect (m_rcItem.left, m_rcItem.top, m_rcItem.right - m_rcItem.left, m_rcItem.bottom - m_rcItem.top), 0, 0, gb.GetWidth (), gb.GetHeight (), Gdiplus::UnitPixel); + } + + PaintText (hDC); + } + + SIZE CGroupBoxUI::CalcrectSize (SIZE szAvailable) { + SIZE cxyFixed = GetFixedXY (); + RECT rcText = { 0, 0, MAX (szAvailable.cx, cxyFixed.cx), 20 }; + + CDuiString sText = GetText (); + + CRenderEngine::DrawText (m_pManager->GetPaintDC (), m_pManager, rcText, sText, m_dwTextColor, m_iFont, DT_CALCRECT | m_uTextStyle); + SIZE cXY = { rcText.right - rcText.left, rcText.bottom - rcText.top }; + return cXY; + } + void CGroupBoxUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("textcolor")) { + SetTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("disabledtextcolor")) { + SetDisabledTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("font")) { + SetFont (FawTools::parse_dec (pstrValue)); + } + + CVerticalLayoutUI::SetAttribute (pstrName, pstrValue); + } +} diff --git a/DuiLib/Control/UIGroupBox.h b/DuiLib/Control/UIGroupBox.h new file mode 100644 index 0000000..8bfef1a --- /dev/null +++ b/DuiLib/Control/UIGroupBox.h @@ -0,0 +1,38 @@ +#ifndef __UIGROUPBOX_H__ +#define __UIGROUPBOX_H__ + +#pragma once + +namespace DuiLib { + + class UILIB_API CGroupBoxUI: public CVerticalLayoutUI { + DECLARE_DUICONTROL (CGroupBoxUI) + public: + CGroupBoxUI (); + virtual ~CGroupBoxUI (); + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + void SetTextColor (DWORD dwTextColor); + DWORD GetTextColor () const; + void SetDisabledTextColor (DWORD dwTextColor); + DWORD GetDisabledTextColor () const; + void SetFont (int index); + int GetFont () const; + + protected: + //Paint + virtual void PaintText (HDC hDC); + virtual void PaintBorder (HDC hDC); + virtual void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + private: + SIZE CalcrectSize (SIZE szAvailable); + + protected: + DWORD m_dwTextColor = 0; + DWORD m_dwDisabledTextColor = 0; + int m_iFont = -1; + UINT m_uTextStyle; + }; +} +#endif // __UIGROUPBOX_H__ \ No newline at end of file diff --git a/DuiLib/Control/UIHotKey.cpp b/DuiLib/Control/UIHotKey.cpp new file mode 100644 index 0000000..522ece9 --- /dev/null +++ b/DuiLib/Control/UIHotKey.cpp @@ -0,0 +1,448 @@ +#include "stdafx.h" +#include "UIHotKey.h" +namespace DuiLib { + CHotKeyWnd::CHotKeyWnd (void): m_pOwner (nullptr), m_hBkBrush (NULL), m_bInit (false) {} + void CHotKeyWnd::Init (CHotKeyUI * pOwner) { + m_pOwner = pOwner; + do { + if (nullptr == m_pOwner) { + break; + } + RECT rcPos = CalPos (); + UINT uStyle = WS_CHILD | ES_AUTOHSCROLL; + HWND hWnd = Create (m_pOwner->GetManager ()->GetPaintWindow (), _T (""), uStyle, 0, rcPos); + if (!IsWindow (hWnd)) { + break; + } + SetWindowFont (m_hWnd, m_pOwner->GetManager ()->GetFontInfo (m_pOwner->GetFont ())->hFont, TRUE); + SetHotKey (m_pOwner->m_wVirtualKeyCode, m_pOwner->m_wModifiers); + m_pOwner->m_sText = GetHotKeyName (); + ::EnableWindow (m_hWnd, m_pOwner->IsEnabled () == true); + ::ShowWindow (m_hWnd, SW_SHOWNOACTIVATE); + ::SetFocus (m_hWnd); + m_bInit = true; + } while (0); + } + + + RECT CHotKeyWnd::CalPos () { + RECT rcPos = m_pOwner->GetPos (); + RECT rcInset = m_pOwner->GetTextPadding (); + rcPos.left += rcInset.left; + rcPos.top += rcInset.top; + rcPos.right -= rcInset.right; + rcPos.bottom -= rcInset.bottom; + LONG lHeight = m_pOwner->GetManager ()->GetFontInfo (m_pOwner->GetFont ())->tm.tmHeight; + if (lHeight < rcPos.bottom - rcPos.top) { + rcPos.top += (rcPos.bottom - rcPos.top - lHeight) / 2; + rcPos.bottom = rcPos.top + lHeight; + } + return rcPos; + } + + + string_view_t CHotKeyWnd::GetWindowClassName () const { + return _T ("HotKeyClass"); + } + + + void CHotKeyWnd::OnFinalMessage (HWND /*hWnd*/) { + // Clear reference and die + if (m_hBkBrush) ::DeleteObject (m_hBkBrush); + m_pOwner->m_pWindow = nullptr; + delete this; + } + + LRESULT CHotKeyWnd::HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam) { + LRESULT lRes = 0; + BOOL bHandled = TRUE; + if (uMsg == WM_KILLFOCUS) lRes = OnKillFocus (uMsg, wParam, lParam, bHandled); + else if (uMsg == OCM_COMMAND) { + if (GET_WM_COMMAND_CMD (wParam, lParam) == EN_CHANGE) lRes = OnEditChanged (uMsg, wParam, lParam, bHandled); + else if (GET_WM_COMMAND_CMD (wParam, lParam) == EN_UPDATE) { + RECT rcClient = { 0 }; + ::GetClientRect (m_hWnd, &rcClient); + ::InvalidateRect (m_hWnd, &rcClient, FALSE); + } + } else if (uMsg == WM_KEYDOWN && TCHAR (wParam) == VK_RETURN) { + m_pOwner->GetManager ()->SendNotify (m_pOwner, _T ("return")); + } else if ((uMsg == WM_NCACTIVATE) || (uMsg == WM_NCACTIVATE) || (uMsg == WM_NCCALCSIZE)) { + return 0; + } else if (uMsg == WM_PAINT) { + PAINTSTRUCT ps = { 0 }; + HDC hDC = ::BeginPaint (m_hWnd, &ps); + DWORD dwTextColor = m_pOwner->GetTextColor (); + DWORD dwBkColor = m_pOwner->GetNativeBkColor (); + CDuiString strText = GetHotKeyName (); + RECT rect = { 0 }; + ::GetClientRect (m_hWnd, &rect); + ::SetBkMode (hDC, TRANSPARENT); + ::SetTextColor (hDC, RGB (GetBValue (dwTextColor), GetGValue (dwTextColor), GetRValue (dwTextColor))); + HBRUSH hBrush = CreateSolidBrush (RGB (GetBValue (dwBkColor), GetGValue (dwBkColor), GetRValue (dwBkColor))); + ::FillRect (hDC, &rect, hBrush); + ::DeleteObject (hBrush); + HFONT hOldFont = (HFONT) SelectObject (hDC, GetWindowFont (m_hWnd)); + ::SIZE size = { 0 }; + ::GetTextExtentPoint32 (hDC, strText.c_str (), (int) strText.length (), &size); + ::DrawText (hDC, strText.c_str (), -1, &rect, DT_LEFT | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX); + ::SelectObject (hDC, hOldFont); + ::SetCaretPos (size.cx, 0); + ::EndPaint (m_hWnd, &ps); + bHandled = TRUE; + } else bHandled = FALSE; + if (!bHandled) return CWindowWnd::HandleMessage (uMsg, wParam, lParam); + return lRes; + } + + + string_view_t CHotKeyWnd::GetSuperClassName () const { + return HOTKEY_CLASS; + } + + + LRESULT CHotKeyWnd::OnKillFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + LRESULT lRes = ::DefWindowProc (m_hWnd, uMsg, wParam, lParam); + ::SendMessage (m_hWnd, WM_CLOSE, 0, 0); + return lRes; + } + + + LRESULT CHotKeyWnd::OnEditChanged (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + if (!m_bInit) return 0; + if (!m_pOwner) return 0; + GetHotKey (m_pOwner->m_wVirtualKeyCode, m_pOwner->m_wModifiers); + if (m_pOwner->m_wVirtualKeyCode == 0) { + m_pOwner->m_sText = _T (""); + m_pOwner->m_wModifiers = 0; + } else { + m_pOwner->m_sText = GetHotKeyName (); + } + m_pOwner->GetManager ()->SendNotify (m_pOwner, _T ("textchanged")); + return 0; + } + + + void CHotKeyWnd::SetHotKey (WORD wVirtualKeyCode, WORD wModifiers) { + ASSERT (::IsWindow (m_hWnd)); + ::SendMessage (m_hWnd, HKM_SETHOTKEY, MAKEWORD (wVirtualKeyCode, wModifiers), 0L); + } + + DWORD CHotKeyWnd::GetHotKey () const { + ASSERT (::IsWindow (m_hWnd)); + return (DWORD) (::SendMessage (m_hWnd, HKM_GETHOTKEY, 0, 0L)); + } + + void CHotKeyWnd::GetHotKey (WORD &wVirtualKeyCode, WORD &wModifiers) const { + DWORD dw = GetHotKey (); + wVirtualKeyCode = LOBYTE (LOWORD (dw)); + wModifiers = HIBYTE (LOWORD (dw)); + } + + void CHotKeyWnd::SetRules (WORD wInvalidComb, WORD wModifiers) { + ASSERT (::IsWindow (m_hWnd)); + ::SendMessage (m_hWnd, HKM_SETRULES, wInvalidComb, MAKELPARAM (wModifiers, 0)); + } + + + CDuiString CHotKeyWnd::GetKeyName (UINT vk, BOOL fExtended) { + UINT nScanCode = ::MapVirtualKeyEx (vk, 0, ::GetKeyboardLayout (0)); + switch (vk) { + // Keys which are "extended" (except for Return which is Numeric Enter as extended) + case VK_INSERT: + case VK_DELETE: + case VK_HOME: + case VK_END: + case VK_NEXT: // Page down + case VK_PRIOR: // Page up + case VK_LEFT: + case VK_RIGHT: + case VK_UP: + case VK_DOWN: + nScanCode |= 0x100; // Add extended bit + } + if (fExtended) + nScanCode |= 0x01000000L; + + TCHAR szStr[MAX_PATH] = { 0 }; + ::GetKeyNameText (nScanCode << 16, szStr, MAX_PATH); + + return CDuiString (szStr); + } + + + CDuiString CHotKeyWnd::GetHotKeyName () { + ASSERT (::IsWindow (m_hWnd)); + + CDuiString strKeyName; + WORD wCode = 0; + WORD wModifiers = 0; + const TCHAR szPlus[] = _T (" + "); + + GetHotKey (wCode, wModifiers); + if (wCode != 0 || wModifiers != 0) { + if (wModifiers & HOTKEYF_CONTROL) { + strKeyName += GetKeyName (VK_CONTROL, FALSE); + strKeyName += szPlus; + } + + + if (wModifiers & HOTKEYF_SHIFT) { + strKeyName += GetKeyName (VK_SHIFT, FALSE); + strKeyName += szPlus; + } + + + if (wModifiers & HOTKEYF_ALT) { + strKeyName += GetKeyName (VK_MENU, FALSE); + strKeyName += szPlus; + } + + + strKeyName += GetKeyName (wCode, wModifiers & HOTKEYF_EXT); + } + + return strKeyName; + } + + + ////////////////////////////////////////////////////////////////////////// + IMPLEMENT_DUICONTROL (CHotKeyUI) + + CHotKeyUI::CHotKeyUI (): m_pWindow (nullptr), m_wVirtualKeyCode (0), m_wModifiers (0), m_uButtonState (0), m_dwHotKeybkColor (0xFFFFFFFF) { + SetTextPadding ({ 4, 3, 4, 3 }); + SetBkColor (0xFFFFFFFF); + } + + string_view_t CHotKeyUI::GetClass () const { + return _T ("HotKeyUI"); + } + + LPVOID CHotKeyUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("HotKey")) return static_cast(this); + return CLabelUI::GetInterface (pstrName); + } + + UINT CHotKeyUI::GetControlFlags () const { + if (!IsEnabled ()) return CControlUI::GetControlFlags (); + + return UIFLAG_SETCURSOR | UIFLAG_TABSTOP; + } + + void CHotKeyUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CLabelUI::DoEvent (event); + return; + } + + if (event.Type == UIEVENT_SETCURSOR && IsEnabled ()) { + ::SetCursor (::LoadCursor (NULL, IDC_IBEAM)); + return; + } + if (event.Type == UIEVENT_WINDOWSIZE) { + if (m_pWindow) m_pManager->SetFocusNeeded (this); + } + if (event.Type == UIEVENT_SCROLLWHEEL) { + if (m_pWindow) return; + } + if (event.Type == UIEVENT_SETFOCUS && IsEnabled ()) { + if (m_pWindow) return; + m_pWindow = new CHotKeyWnd (); + ASSERT (m_pWindow); + m_pWindow->Init (this); + Invalidate (); + } + if (event.Type == UIEVENT_KILLFOCUS && IsEnabled ()) { + Invalidate (); + } + if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK || event.Type == UIEVENT_RBUTTONDOWN) { + if (IsEnabled ()) { + GetManager ()->ReleaseCapture (); + if (IsFocused () && !m_pWindow) { + m_pWindow = new CHotKeyWnd (); + ASSERT (m_pWindow); + m_pWindow->Init (this); + } + } + return; + } + if (event.Type == UIEVENT_MOUSEMOVE) { + return; + } + if (event.Type == UIEVENT_BUTTONUP) { + return; + } + if (event.Type == UIEVENT_CONTEXTMENU) { + return; + } + if (event.Type == UIEVENT_MOUSEENTER) { + if (IsEnabled ()) { + m_uButtonState |= UISTATE_HOT; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_MOUSELEAVE) { + if (IsEnabled ()) { + m_uButtonState &= ~UISTATE_HOT; + Invalidate (); + } + return; + } + CLabelUI::DoEvent (event); + } + + void CHotKeyUI::SetEnabled (bool bEnable) { + CControlUI::SetEnabled (bEnable); + if (!IsEnabled ()) { + m_uButtonState = 0; + } + } + + void CHotKeyUI::SetText (string_view_t pstrText) { + m_sText = pstrText; + if (m_pWindow) Edit_SetText (m_pWindow->GetHWND (), m_sText.data ()); + Invalidate (); + } + + string_view_t CHotKeyUI::GetNormalImage () { + return m_sNormalImage; + } + + void CHotKeyUI::SetNormalImage (string_view_t pStrImage) { + m_sNormalImage = pStrImage; + Invalidate (); + } + + string_view_t CHotKeyUI::GetHotImage () { + return m_sHotImage; + } + + void CHotKeyUI::SetHotImage (string_view_t pStrImage) { + m_sHotImage = pStrImage; + Invalidate (); + } + + string_view_t CHotKeyUI::GetFocusedImage () { + return m_sFocusedImage; + } + + void CHotKeyUI::SetFocusedImage (string_view_t pStrImage) { + m_sFocusedImage = pStrImage; + Invalidate (); + } + + string_view_t CHotKeyUI::GetDisabledImage () { + return m_sDisabledImage; + } + + void CHotKeyUI::SetDisabledImage (string_view_t pStrImage) { + m_sDisabledImage = pStrImage; + Invalidate (); + } + + void CHotKeyUI::SetNativeBkColor (DWORD dwBkColor) { + m_dwHotKeybkColor = dwBkColor; + } + + DWORD CHotKeyUI::GetNativeBkColor () const { + return m_dwHotKeybkColor; + } + + void CHotKeyUI::SetPos (RECT rc) { + CControlUI::SetPos (rc); + if (m_pWindow) { + RECT rcPos = m_pWindow->CalPos (); + ::SetWindowPos (m_pWindow->GetHWND (), NULL, rcPos.left, rcPos.top, rcPos.right - rcPos.left, + rcPos.bottom - rcPos.top, SWP_NOZORDER | SWP_NOACTIVATE); + } + } + + void CHotKeyUI::SetVisible (bool bVisible) { + CControlUI::SetVisible (bVisible); + if (!IsVisible () && m_pWindow) m_pManager->SetFocus (nullptr); + } + + void CHotKeyUI::SetInternVisible (bool bVisible) { + if (!IsVisible () && m_pWindow) m_pManager->SetFocus (nullptr); + } + + SIZE CHotKeyUI::EstimateSize (SIZE szAvailable) { + if (m_cxyFixed.cy == 0) return { m_cxyFixed.cx, m_pManager->GetFontInfo (GetFont ())->tm.tmHeight + 6 }; + return CControlUI::EstimateSize (szAvailable); + } + + void CHotKeyUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("normalimage")) SetNormalImage (pstrValue); + else if (pstrName == _T ("hotimage")) SetHotImage (pstrValue); + else if (pstrName == _T ("focusedimage")) SetFocusedImage (pstrValue); + else if (pstrName == _T ("disabledimage")) SetDisabledImage (pstrValue); + else if (pstrName == _T ("nativebkcolor")) SetNativeBkColor ((DWORD) FawTools::parse_hex (pstrValue)); + else CLabelUI::SetAttribute (pstrName, pstrValue); + } + + void CHotKeyUI::PaintStatusImage (HDC hDC) { + if (IsFocused ()) m_uButtonState |= UISTATE_FOCUSED; + else m_uButtonState &= ~UISTATE_FOCUSED; + if (!IsEnabled ()) m_uButtonState |= UISTATE_DISABLED; + else m_uButtonState &= ~UISTATE_DISABLED; + + if ((m_uButtonState & UISTATE_DISABLED) != 0) { + if (!m_sDisabledImage.empty ()) { + if (!DrawImage (hDC, m_sDisabledImage)) { + } else return; + } + } else if ((m_uButtonState & UISTATE_FOCUSED) != 0) { + if (!m_sFocusedImage.empty ()) { + if (!DrawImage (hDC, m_sFocusedImage)) { + } else return; + } + } else if ((m_uButtonState & UISTATE_HOT) != 0) { + if (!m_sHotImage.empty ()) { + if (!DrawImage (hDC, m_sHotImage)) { + } else return; + } + } + + if (!m_sNormalImage.empty ()) { + if (!DrawImage (hDC, m_sNormalImage)) { + } else return; + } + } + + void CHotKeyUI::PaintText (HDC hDC) { + if (m_dwTextColor == 0) m_dwTextColor = m_pManager->GetDefaultFontColor (); + if (m_dwDisabledTextColor == 0) m_dwDisabledTextColor = m_pManager->GetDefaultDisabledColor (); + if (m_sText.empty ()) return; + CDuiString sText = m_sText; + RECT rc = m_rcItem; + rc.left += m_rcTextPadding.left; + rc.right -= m_rcTextPadding.right; + rc.top += m_rcTextPadding.top; + rc.bottom -= m_rcTextPadding.bottom; + DWORD dwTextColor = m_dwTextColor; + if (!IsEnabled ())dwTextColor = m_dwDisabledTextColor; + CRenderEngine::DrawText (hDC, m_pManager, rc, sText, dwTextColor, m_iFont, DT_SINGLELINE | m_uTextStyle); + } + + DWORD CHotKeyUI::GetHotKey () const { + return (MAKEWORD (m_wVirtualKeyCode, m_wModifiers)); + } + + void CHotKeyUI::GetHotKey (WORD &wVirtualKeyCode, WORD &wModifiers) const { + wVirtualKeyCode = m_wVirtualKeyCode; + wModifiers = m_wModifiers; + } + + void CHotKeyUI::SetHotKey (WORD wVirtualKeyCode, WORD wModifiers) { + m_wVirtualKeyCode = wVirtualKeyCode; + m_wModifiers = wModifiers; + + if (m_pWindow) return; + m_pWindow = new CHotKeyWnd (); + ASSERT (m_pWindow); + m_pWindow->Init (this); + Invalidate (); + } + +}// Duilib \ No newline at end of file diff --git a/DuiLib/Control/UIHotKey.h b/DuiLib/Control/UIHotKey.h new file mode 100644 index 0000000..6ccc8d7 --- /dev/null +++ b/DuiLib/Control/UIHotKey.h @@ -0,0 +1,85 @@ +#ifndef __UIHOTKEY_H__ +#define __UIHOTKEY_H__ +#pragma once + +namespace DuiLib { + class CHotKeyUI; + + class UILIB_API CHotKeyWnd: public CWindowWnd { + public: + CHotKeyWnd (void); + + public: + void Init (CHotKeyUI * pOwner); + RECT CalPos (); + string_view_t GetWindowClassName () const; + void OnFinalMessage (HWND hWnd); + string_view_t GetSuperClassName () const; + LRESULT HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam); + LRESULT OnKillFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnEditChanged (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + public: + void SetHotKey (WORD wVirtualKeyCode, WORD wModifiers); + void GetHotKey (WORD &wVirtualKeyCode, WORD &wModifiers) const; + DWORD GetHotKey (void) const; + CDuiString GetHotKeyName (); + void SetRules (WORD wInvalidComb, WORD wModifiers); + CDuiString GetKeyName (UINT vk, BOOL fExtended); + protected: + CHotKeyUI * m_pOwner; + HBRUSH m_hBkBrush; + bool m_bInit; + }; + + class UILIB_API CHotKeyUI: public CLabelUI { + DECLARE_DUICONTROL (CHotKeyUI) + friend CHotKeyWnd; + public: + CHotKeyUI (); + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetControlFlags () const; + void SetEnabled (bool bEnable = true); + void SetText (string_view_t pstrText); + string_view_t GetNormalImage (); + void SetNormalImage (string_view_t pStrImage); + string_view_t GetHotImage (); + void SetHotImage (string_view_t pStrImage); + string_view_t GetFocusedImage (); + void SetFocusedImage (string_view_t pStrImage); + string_view_t GetDisabledImage (); + void SetDisabledImage (string_view_t pStrImage); + void SetNativeBkColor (DWORD dwBkColor); + DWORD GetNativeBkColor () const; + + void SetPos (RECT rc); + void SetVisible (bool bVisible = true); + void SetInternVisible (bool bVisible = true); + SIZE EstimateSize (SIZE szAvailable); + void DoEvent (TEventUI& event); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + void PaintStatusImage (HDC hDC); + void PaintText (HDC hDC); + public: + void GetHotKey (WORD &wVirtualKeyCode, WORD &wModifiers) const; + DWORD GetHotKey (void) const; + void SetHotKey (WORD wVirtualKeyCode, WORD wModifiers); + + protected: + CHotKeyWnd * m_pWindow; + UINT m_uButtonState; + CDuiString m_sNormalImage; + CDuiString m_sHotImage; + CDuiString m_sFocusedImage; + CDuiString m_sDisabledImage; + DWORD m_dwHotKeybkColor; + + protected: + WORD m_wVirtualKeyCode; + WORD m_wModifiers; + }; +} + + +#endif \ No newline at end of file diff --git a/DuiLib/Control/UIIPAddress.cpp b/DuiLib/Control/UIIPAddress.cpp new file mode 100644 index 0000000..94fd34f --- /dev/null +++ b/DuiLib/Control/UIIPAddress.cpp @@ -0,0 +1,234 @@ +#include "StdAfx.h" +#pragma comment( lib, "ws2_32.lib" ) + +DWORD GetLocalIpAddress () { + WORD wVersionRequested = MAKEWORD (2, 2); + WSADATA wsaData; + if (WSAStartup (wVersionRequested, &wsaData) != 0) + return 0; + char local[255] = { 0 }; + gethostname (local, sizeof (local)); + hostent* ph = gethostbyname (local); + if (!ph) + return 0; + in_addr addr; + memcpy (&addr, ph->h_addr_list[0], sizeof (in_addr)); + DWORD dwIP = MAKEIPADDRESS (addr.S_un.S_un_b.s_b1, addr.S_un.S_un_b.s_b2, addr.S_un.S_un_b.s_b3, addr.S_un.S_un_b.s_b4); + return dwIP; +} + +namespace DuiLib { + //CDateTimeUI::m_nDTUpdateFlag +#define IP_NONE 0 +#define IP_UPDATE 1 +#define IP_DELETE 2 +#define IP_KEEP 3 + + class CIPAddressWnd: public CWindowWnd { + public: + CIPAddressWnd (); + + void Init (CIPAddressUI* pOwner); + RECT CalPos (); + + string_view_t GetWindowClassName () const; + string_view_t GetSuperClassName () const; + void OnFinalMessage (HWND hWnd); + + LRESULT HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam); + LRESULT OnKillFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + + protected: + CIPAddressUI *m_pOwner = nullptr; + HBRUSH m_hBkBrush = NULL; + bool m_bInit = false; + }; + + CIPAddressWnd::CIPAddressWnd () {} + + void CIPAddressWnd::Init (CIPAddressUI* pOwner) { + m_pOwner = pOwner; + m_pOwner->m_nIPUpdateFlag = IP_NONE; + + if (!m_hWnd) { + INITCOMMONCONTROLSEX CommCtrl; + CommCtrl.dwSize = sizeof (CommCtrl); + CommCtrl.dwICC = ICC_INTERNET_CLASSES;//ָClass + if (InitCommonControlsEx (&CommCtrl)) { + RECT rcPos = CalPos (); + UINT uStyle = WS_CHILD | WS_TABSTOP | WS_GROUP; + Create (m_pOwner->GetManager ()->GetPaintWindow (), _T (""), uStyle, 0, rcPos); + } + SetWindowFont (m_hWnd, m_pOwner->GetManager ()->GetFontInfo (m_pOwner->GetFont ())->hFont, TRUE); + } + + if (m_pOwner->GetText ().empty ()) + m_pOwner->m_dwIP = GetLocalIpAddress (); + ::SendMessage (m_hWnd, IPM_SETADDRESS, 0, m_pOwner->m_dwIP); + ::ShowWindow (m_hWnd, SW_SHOW); + ::SetFocus (m_hWnd); + + m_bInit = true; + } + + RECT CIPAddressWnd::CalPos () { + return m_pOwner->GetPos (); + } + + string_view_t CIPAddressWnd::GetWindowClassName () const { + return _T ("IPAddressWnd"); + } + + string_view_t CIPAddressWnd::GetSuperClassName () const { + return WC_IPADDRESS; + } + + void CIPAddressWnd::OnFinalMessage (HWND /*hWnd*/) { + // Clear reference and die + if (m_hBkBrush) ::DeleteObject (m_hBkBrush); + m_pOwner->m_pWindow = nullptr; + delete this; + } + + LRESULT CIPAddressWnd::HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam) { + LRESULT lRes = 0; + BOOL bHandled = TRUE; + if (uMsg == WM_KILLFOCUS) { + bHandled = TRUE; + return 0; + lRes = OnKillFocus (uMsg, wParam, lParam, bHandled); + } else if (uMsg == WM_KEYUP && (wParam == VK_DELETE || wParam == VK_BACK)) { + lRes = ::DefWindowProc (m_hWnd, uMsg, wParam, lParam); + m_pOwner->m_nIPUpdateFlag = IP_DELETE; + m_pOwner->UpdateText (); + PostMessage (WM_CLOSE); + return lRes; + } else if (uMsg == WM_KEYUP && wParam == VK_ESCAPE) { + lRes = ::DefWindowProc (m_hWnd, uMsg, wParam, lParam); + m_pOwner->m_nIPUpdateFlag = IP_KEEP; + PostMessage (WM_CLOSE); + return lRes; + } else if (uMsg == OCM_COMMAND) { + if (GET_WM_COMMAND_CMD (wParam, lParam) == EN_KILLFOCUS) { + lRes = OnKillFocus (uMsg, wParam, lParam, bHandled); + } + } else bHandled = FALSE; + if (!bHandled) return CWindowWnd::HandleMessage (uMsg, wParam, lParam); + return lRes; + } + + LRESULT CIPAddressWnd::OnKillFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + HWND hWndFocus = GetFocus (); + while (hWndFocus) { + if (GetFocus () == m_hWnd) { + bHandled = TRUE; + return 0; + } + hWndFocus = GetParent (hWndFocus); + } + + LRESULT lRes = ::DefWindowProc (m_hWnd, uMsg, wParam, lParam); + if (m_pOwner->m_nIPUpdateFlag == IP_NONE) { + ::SendMessage (m_hWnd, IPM_GETADDRESS, 0, (LPARAM) &m_pOwner->m_dwIP); + m_pOwner->m_nIPUpdateFlag = IP_UPDATE; + m_pOwner->UpdateText (); + } + ::ShowWindow (m_hWnd, SW_HIDE); + return lRes; + } + + ////////////////////////////////////////////////////////////////////////// + // + IMPLEMENT_DUICONTROL (CIPAddressUI) + + CIPAddressUI::CIPAddressUI () { + m_dwIP = GetLocalIpAddress (); + m_nIPUpdateFlag = IP_UPDATE; + UpdateText (); + m_nIPUpdateFlag = IP_NONE; + } + + string_view_t CIPAddressUI::GetClass () const { + return _T ("DateTimeUI"); + } + + LPVOID CIPAddressUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_IPADDRESS) return static_cast(this); + return CLabelUI::GetInterface (pstrName); + } + + DWORD CIPAddressUI::GetIP () { + return m_dwIP; + } + + void CIPAddressUI::SetIP (DWORD dwIP) { + m_dwIP = dwIP; + UpdateText (); + } + + void CIPAddressUI::SetReadOnly (bool bReadOnly) { + m_bReadOnly = bReadOnly; + Invalidate (); + } + + bool CIPAddressUI::IsReadOnly () const { + return m_bReadOnly; + } + + void CIPAddressUI::UpdateText () { + if (m_nIPUpdateFlag == IP_DELETE) + SetText (_T ("")); + else if (m_nIPUpdateFlag == IP_UPDATE) { + in_addr addr; + addr.S_un.S_addr = m_dwIP; + string_t szIP = FawTools::format_str (_T ("%d.%d.%d.%d"), addr.S_un.S_un_b.s_b4, addr.S_un.S_un_b.s_b3, addr.S_un.S_un_b.s_b2, addr.S_un.S_un_b.s_b1); + SetText (szIP); + } + } + + void CIPAddressUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CLabelUI::DoEvent (event); + return; + } else if (event.Type == UIEVENT_SETCURSOR && IsEnabled ()) { + ::SetCursor (::LoadCursor (nullptr, IDC_IBEAM)); + return; + } else if (event.Type == UIEVENT_WINDOWSIZE) { + if (m_pWindow) m_pManager->SetFocusNeeded (this); + } else if (event.Type == UIEVENT_SCROLLWHEEL) { + if (m_pWindow) return; + } else if (event.Type == UIEVENT_SETFOCUS && IsEnabled ()) { + if (m_pWindow) { + return; + } + m_pWindow = new CIPAddressWnd (); + ASSERT (m_pWindow); + m_pWindow->Init (this); + m_pWindow->ShowWindow (); + } else if (event.Type == UIEVENT_KILLFOCUS && IsEnabled ()) { + Invalidate (); + } else if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK || event.Type == UIEVENT_RBUTTONDOWN) { + if (IsEnabled ()) { + GetManager ()->ReleaseCapture (); + if (IsFocused () && !m_pWindow) { + m_pWindow = new CIPAddressWnd (); + ASSERT (m_pWindow); + } + if (m_pWindow) { + m_pWindow->Init (this); + m_pWindow->ShowWindow (); + } + } + return; + } else if (event.Type == UIEVENT_MOUSEMOVE || event.Type == UIEVENT_BUTTONUP || event.Type == UIEVENT_CONTEXTMENU || event.Type == UIEVENT_MOUSEENTER || event.Type == UIEVENT_MOUSELEAVE) { + return; + } + + CLabelUI::DoEvent (event); + } + + void CIPAddressUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + CLabelUI::SetAttribute (pstrName, pstrValue); + } +} diff --git a/DuiLib/Control/UIIPAddress.h b/DuiLib/Control/UIIPAddress.h new file mode 100644 index 0000000..386ba04 --- /dev/null +++ b/DuiLib/Control/UIIPAddress.h @@ -0,0 +1,41 @@ +#ifndef __UIIPADDRESS_H__ +#define __UIIPADDRESS_H__ + +#pragma once + +//ÿؼһdtstyle + +namespace DuiLib { + class CIPAddressWnd; + + /// ʱѡؼ + class UILIB_API CIPAddressUI: public CLabelUI { + DECLARE_DUICONTROL (CIPAddressUI) + + friend class CIPAddressWnd; + public: + CIPAddressUI (); + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + DWORD GetIP (); + void SetIP (DWORD dwIP); + + void SetReadOnly (bool bReadOnly); + bool IsReadOnly () const; + + void UpdateText (); + + void DoEvent (TEventUI& event); + + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + protected: + DWORD m_dwIP; + bool m_bReadOnly = false; + int m_nIPUpdateFlag; + + CIPAddressWnd *m_pWindow = nullptr; + }; +} +#endif // __UIIPADDRESS_H__ \ No newline at end of file diff --git a/DuiLib/Control/UIIPAddressEx.cpp b/DuiLib/Control/UIIPAddressEx.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8e9748ef63b2756e51a1910ba3e5a45faa56597d GIT binary patch literal 21936 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 U7S?7~Q;*hLOdZ}~Eapx4KU&(u+W-In literal 0 HcmV?d00001 diff --git a/DuiLib/Control/UIIPAddressEx.h b/DuiLib/Control/UIIPAddressEx.h new file mode 100644 index 0000000..b7b6568 --- /dev/null +++ b/DuiLib/Control/UIIPAddressEx.h @@ -0,0 +1,42 @@ +#ifndef __UIIPADDRESSEX_H__ +#define __UIIPADDRESSEX_H__ + +#pragma once + +//ÿؼһdtstyle + +namespace DuiLib { + + /// IPؼ + class UILIB_API CIPAddressExUI: public CEditUI { + DECLARE_DUICONTROL (CIPAddressExUI) + public: + CIPAddressExUI (); + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetControlFlags () const; + void DoEvent (TEventUI& event); + void PaintText (HDC hDC); + + void SetIP (LPCWSTR lpIP); + CDuiString GetIP (); + + private: + void CharToInt (); + void GetNumInput (TCHAR chKey); + void UpdateText (); + void IncNum (); + void DecNum (); + + protected: + int m_nFirst = 0; + int m_nSecond = 0; + int m_nThird = 0; + int m_nFourth = 0; + int m_nActiveSection = 0; + + TCHAR m_chNum; + CDuiString m_strNum; + }; +} +#endif // __UIIPADDRESSEX_H__ \ No newline at end of file diff --git a/DuiLib/Control/UILabel.cpp b/DuiLib/Control/UILabel.cpp new file mode 100644 index 0000000..9ff4c24 --- /dev/null +++ b/DuiLib/Control/UILabel.cpp @@ -0,0 +1,271 @@ +#include "StdAfx.h" +#include "UILabel.h" + +#include +namespace DuiLib { + IMPLEMENT_DUICONTROL (CLabelUI) + + CLabelUI::CLabelUI (): m_uTextStyle (DT_VCENTER | DT_SINGLELINE) {} + + CLabelUI::~CLabelUI () {} + + string_view_t CLabelUI::GetClass () const { + return _T ("LabelUI"); + } + + LPVOID CLabelUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("Label")) return static_cast(this); + return CControlUI::GetInterface (pstrName); + } + + UINT CLabelUI::GetControlFlags () const { + return IsEnabled () ? UIFLAG_SETCURSOR : 0; + } + void CLabelUI::SetTextStyle (UINT uStyle) { + m_uTextStyle = uStyle; + Invalidate (); + } + + UINT CLabelUI::GetTextStyle () const { + return m_uTextStyle; + } + + void CLabelUI::SetTextColor (DWORD dwTextColor) { + m_dwTextColor = dwTextColor; + Invalidate (); + } + + DWORD CLabelUI::GetTextColor () const { + return m_dwTextColor; + } + + void CLabelUI::SetDisabledTextColor (DWORD dwTextColor) { + m_dwDisabledTextColor = dwTextColor; + Invalidate (); + } + + DWORD CLabelUI::GetDisabledTextColor () const { + return m_dwDisabledTextColor; + } + + void CLabelUI::SetFont (int index) { + m_iFont = index; + m_bNeedEstimateSize = true; + Invalidate (); + } + + int CLabelUI::GetFont () const { + return m_iFont; + } + + RECT CLabelUI::GetTextPadding () const { + return m_rcTextPadding; + } + + void CLabelUI::SetTextPadding (RECT rc) { + m_rcTextPadding = rc; + m_bNeedEstimateSize = true; + Invalidate (); + } + + bool CLabelUI::IsShowHtml () { + return m_bShowHtml; + } + + void CLabelUI::SetShowHtml (bool bShowHtml) { + if (m_bShowHtml == bShowHtml) return; + + m_bShowHtml = bShowHtml; + m_bNeedEstimateSize = true; + Invalidate (); + } + + SIZE CLabelUI::EstimateSize (SIZE szAvailable) { + if (m_cxyFixed.cx > 0 && m_cxyFixed.cy > 0) { + if (m_pManager != nullptr) { + return m_pManager->GetDPIObj ()->Scale (m_cxyFixed); + } + return m_cxyFixed; + } + + if ((szAvailable.cx != m_szAvailableLast.cx || szAvailable.cy != m_szAvailableLast.cy)) { + m_bNeedEstimateSize = true; + } + + if (m_bNeedEstimateSize) { + CDuiString sText = GetText (); + m_bNeedEstimateSize = false; + m_szAvailableLast = szAvailable; + m_cxyFixedLast = m_cxyFixed; + if ((m_uTextStyle & DT_SINGLELINE)) { + if (m_cxyFixedLast.cy == 0) { + m_cxyFixedLast.cy = m_pManager->GetFontInfo (m_iFont)->tm.tmHeight + 8; + m_cxyFixedLast.cy += GetManager ()->GetDPIObj ()->Scale (m_rcTextPadding.top + m_rcTextPadding.bottom); + } + if (m_cxyFixedLast.cx == 0) { + if (m_bAutoCalcWidth) { + RECT rcText = { 0, 0, 9999, m_cxyFixedLast.cy }; + if (m_bShowHtml) { + int nLinks = 0; + CRenderEngine::DrawHtmlText (m_pManager->GetPaintDC (), m_pManager, rcText, sText, 0, NULL, NULL, nLinks, m_iFont, DT_CALCRECT | m_uTextStyle & ~DT_RIGHT & ~DT_CENTER); + } else { + CRenderEngine::DrawText (m_pManager->GetPaintDC (), m_pManager, rcText, sText, 0, m_iFont, DT_CALCRECT | m_uTextStyle & ~DT_RIGHT & ~DT_CENTER); + } + m_cxyFixedLast.cx = rcText.right - rcText.left + GetManager ()->GetDPIObj ()->Scale (m_rcTextPadding.left + m_rcTextPadding.right); + } else { + //m_cxyFixedLast.cx = szAvailable.cx; + } + } + } else { + if (m_cxyFixedLast.cx == 0) { + //m_cxyFixedLast.cx = szAvailable.cx; + } + if (m_cxyFixedLast.cy == 0) { + if (m_bAutoCalcHeight) { + RECT rcText = { 0, 0, m_cxyFixedLast.cx, 9999 }; + rcText.left += m_rcTextPadding.left; + rcText.right -= m_rcTextPadding.right; + if (m_bShowHtml) { + int nLinks = 0; + CRenderEngine::DrawHtmlText (m_pManager->GetPaintDC (), m_pManager, rcText, sText, 0, NULL, NULL, nLinks, m_iFont, DT_CALCRECT | m_uTextStyle & ~DT_RIGHT & ~DT_CENTER); + } else { + CRenderEngine::DrawText (m_pManager->GetPaintDC (), m_pManager, rcText, sText, 0, m_iFont, DT_CALCRECT | m_uTextStyle & ~DT_RIGHT & ~DT_CENTER); + } + m_cxyFixedLast.cy = rcText.bottom - rcText.top + GetManager ()->GetDPIObj ()->Scale (m_rcTextPadding.top + m_rcTextPadding.bottom); + } else { + //m_cxyFixedLast.cy = szAvailable.cy; + } + } + } + } + return m_cxyFixedLast; + } + + void CLabelUI::DoEvent (TEventUI& event) { + if (event.Type == UIEVENT_SETFOCUS) { + m_bFocused = true; + return; + } + if (event.Type == UIEVENT_KILLFOCUS) { + m_bFocused = false; + return; + } + CControlUI::DoEvent (event); + } + + void CLabelUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("align")) { + if (pstrValue.find (_T ("left")) != string_t::npos) { + m_uTextStyle &= ~(DT_CENTER | DT_RIGHT); + m_uTextStyle |= DT_LEFT; + } + if (pstrValue.find (_T ("center")) != string_t::npos) { + m_uTextStyle &= ~(DT_LEFT | DT_RIGHT); + m_uTextStyle |= DT_CENTER; + } + if (pstrValue.find (_T ("right")) != string_t::npos) { + m_uTextStyle &= ~(DT_LEFT | DT_CENTER); + m_uTextStyle |= DT_RIGHT; + } + } else if (pstrName == _T ("valign")) { + if (pstrValue.find (_T ("top")) != string_t::npos) { + m_uTextStyle &= ~(DT_BOTTOM | DT_VCENTER | DT_WORDBREAK); + m_uTextStyle |= (DT_TOP | DT_SINGLELINE); + } + if (pstrValue.find (_T ("vcenter")) != string_t::npos) { + m_uTextStyle &= ~(DT_TOP | DT_BOTTOM | DT_WORDBREAK); + m_uTextStyle |= (DT_VCENTER | DT_SINGLELINE); + } + if (pstrValue.find (_T ("bottom")) != string_t::npos) { + m_uTextStyle &= ~(DT_TOP | DT_VCENTER | DT_WORDBREAK); + m_uTextStyle |= (DT_BOTTOM | DT_SINGLELINE); + } + } else if (pstrName == _T ("endellipsis")) { + if (FawTools::parse_bool (pstrValue)) m_uTextStyle |= DT_END_ELLIPSIS; + else m_uTextStyle &= ~DT_END_ELLIPSIS; + } else if (pstrName == _T ("wordbreak")) { + if (FawTools::parse_bool (pstrValue)) { + m_uTextStyle &= ~DT_SINGLELINE; + m_uTextStyle |= DT_WORDBREAK | DT_EDITCONTROL; + } else { + m_uTextStyle &= ~DT_WORDBREAK & ~DT_EDITCONTROL; + m_uTextStyle |= DT_SINGLELINE; + } + } else if (pstrName == _T ("noprefix")) { + if (FawTools::parse_bool (pstrValue)) { + m_uTextStyle |= DT_NOPREFIX; + } else { + m_uTextStyle = m_uTextStyle & ~DT_NOPREFIX; + } + } else if (pstrName == _T ("font")) SetFont (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("textcolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetTextColor (clrColor); + } else if (pstrName == _T ("disabledtextcolor")) { + DWORD clrColor = (DWORD) FawTools::parse_hex (pstrValue); + SetDisabledTextColor (clrColor); + } else if (pstrName == _T ("textpadding")) { + RECT rcTextPadding = FawTools::parse_rect (pstrValue); + SetTextPadding (rcTextPadding); + } else if (pstrName == _T ("showhtml")) { + SetShowHtml (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("autocalcwidth")) { + SetAutoCalcWidth (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("autocalcheight")) { + SetAutoCalcHeight (FawTools::parse_bool (pstrValue)); + } else { + CControlUI::SetAttribute (pstrName, pstrValue); + } + } + + void CLabelUI::PaintText (HDC hDC) { + if (m_dwTextColor == 0) m_dwTextColor = m_pManager->GetDefaultFontColor (); + if (m_dwDisabledTextColor == 0) m_dwDisabledTextColor = m_pManager->GetDefaultDisabledColor (); + + RECT rc = m_rcItem; + RECT _rcTextPadding = CLabelUI::m_rcTextPadding; + GetManager ()->GetDPIObj ()->Scale (&_rcTextPadding); + rc.left += _rcTextPadding.left; + rc.right -= _rcTextPadding.right; + rc.top += _rcTextPadding.top; + rc.bottom -= _rcTextPadding.bottom; + + CDuiString sText = GetText (); + if (sText.empty ()) return; + int nLinks = 0; + if (IsEnabled ()) { + if (m_bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rc, sText, m_dwTextColor, nullptr, nullptr, nLinks, m_iFont, m_uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rc, sText, m_dwTextColor, m_iFont, m_uTextStyle); + } else { + if (m_bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rc, sText, m_dwDisabledTextColor, nullptr, nullptr, nLinks, m_iFont, m_uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rc, sText, m_dwDisabledTextColor, m_iFont, m_uTextStyle); + } + } + + bool CLabelUI::GetAutoCalcWidth () const { + return m_bAutoCalcWidth; + } + + void CLabelUI::SetAutoCalcWidth (bool bAutoCalcWidth) { + m_bAutoCalcWidth = bAutoCalcWidth; + } + + bool CLabelUI::GetAutoCalcHeight () const { + return m_bAutoCalcHeight; + } + + void CLabelUI::SetAutoCalcHeight (bool bAutoCalcHeight) { + m_bAutoCalcHeight = bAutoCalcHeight; + } + + void CLabelUI::SetText (string_view_t pstrText) { + CControlUI::SetText (pstrText); + if (GetAutoCalcWidth () || GetAutoCalcHeight ()) { + NeedParentUpdate (); + } + } +} \ No newline at end of file diff --git a/DuiLib/Control/UILabel.h b/DuiLib/Control/UILabel.h new file mode 100644 index 0000000..10f5c4c --- /dev/null +++ b/DuiLib/Control/UILabel.h @@ -0,0 +1,58 @@ +#ifndef __UILABEL_H__ +#define __UILABEL_H__ + +#pragma once + +namespace DuiLib { + class UILIB_API CLabelUI: public CControlUI { + DECLARE_DUICONTROL (CLabelUI) + public: + CLabelUI (); + virtual ~CLabelUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetControlFlags () const; + + void SetTextStyle (UINT uStyle); + UINT GetTextStyle () const; + void SetTextColor (DWORD dwTextColor); + DWORD GetTextColor () const; + void SetDisabledTextColor (DWORD dwTextColor); + DWORD GetDisabledTextColor () const; + void SetFont (int index); + int GetFont () const; + RECT GetTextPadding () const; + void SetTextPadding (RECT rc); + bool IsShowHtml (); + void SetShowHtml (bool bShowHtml = true); + + SIZE EstimateSize (SIZE szAvailable); + void DoEvent (TEventUI& event); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + void PaintText (HDC hDC); + + virtual bool GetAutoCalcWidth () const; + virtual void SetAutoCalcWidth (bool bAutoCalcWidth); + virtual bool GetAutoCalcHeight () const; + virtual void SetAutoCalcHeight (bool bAutoCalcHeight); + virtual void SetText (string_view_t pstrText); + + protected: + DWORD m_dwTextColor = 0; + DWORD m_dwDisabledTextColor = 0; + int m_iFont = -1; + UINT m_uTextStyle; + RECT m_rcTextPadding = { 0 }; + bool m_bShowHtml = false; + bool m_bAutoCalcWidth = false; + bool m_bAutoCalcHeight = false; + + SIZE m_szAvailableLast = { 0, 0 }; + SIZE m_cxyFixedLast = { 0, 0 }; + bool m_bNeedEstimateSize = false; + }; +} + +#endif // __UILABEL_H__ diff --git a/DuiLib/Control/UIList.cpp b/DuiLib/Control/UIList.cpp new file mode 100644 index 0000000..585b002 --- /dev/null +++ b/DuiLib/Control/UIList.cpp @@ -0,0 +1,2581 @@ +#include "StdAfx.h" + +namespace DuiLib { + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CListUI) + + CListUI::CListUI () { + m_pList = new CListBodyUI (this); + m_pHeader = new CListHeaderUI; + + Add (m_pHeader); + CVerticalLayoutUI::Add (m_pList); + + m_ListInfo.nColumns = 0; + m_ListInfo.nFont = -1; + m_ListInfo.uTextStyle = DT_VCENTER | DT_SINGLELINE; + m_ListInfo.dwTextColor = 0xFF000000; + m_ListInfo.dwBkColor = 0; + m_ListInfo.bAlternateBk = false; + m_ListInfo.dwSelectedTextColor = 0xFF000000; + m_ListInfo.dwSelectedBkColor = 0xFFC1E3FF; + m_ListInfo.dwHotTextColor = 0xFF000000; + m_ListInfo.dwHotBkColor = 0xFFE9F5FF; + m_ListInfo.dwDisabledTextColor = 0xFFCCCCCC; + m_ListInfo.dwDisabledBkColor = 0xFFFFFFFF; + m_ListInfo.dwLineColor = 0; + m_ListInfo.bShowRowLine = false; + m_ListInfo.bShowColumnLine = false; + m_ListInfo.bShowHtml = false; + m_ListInfo.bMultiExpandable = false; + m_ListInfo.bRSelected = false; + ::ZeroMemory (&m_ListInfo.rcTextPadding, sizeof (m_ListInfo.rcTextPadding)); + ::ZeroMemory (&m_ListInfo.rcColumn, sizeof (m_ListInfo.rcColumn)); + } + + string_view_t CListUI::GetClass () const { + return _T ("ListUI"); + } + + UINT CListUI::GetControlFlags () const { + return UIFLAG_TABSTOP; + } + + LPVOID CListUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_LIST) return static_cast(this); + if (pstrName == _T ("IList")) return static_cast(this); + if (pstrName == _T ("IListOwner")) return static_cast(this); + return CVerticalLayoutUI::GetInterface (pstrName); + } + + CControlUI* CListUI::GetItemAt (int iIndex) const { + return m_pList->GetItemAt (iIndex); + } + + int CListUI::GetItemIndex (CControlUI* pControl) const { + if (pControl->GetInterface (_T ("ListHeader"))) return CVerticalLayoutUI::GetItemIndex (pControl); + // We also need to recognize header sub-items + if (pControl->GetClass ().find (_T ("ListHeaderItemUI")) != string_t::npos) return m_pHeader->GetItemIndex (pControl); + + return m_pList->GetItemIndex (pControl); + } + + bool CListUI::SetItemIndex (CControlUI* pControl, int iIndex) { + if (pControl->GetInterface (_T ("ListHeader"))) return CVerticalLayoutUI::SetItemIndex (pControl, iIndex); + // We also need to recognize header sub-items + if (pControl->GetClass ().find (_T ("ListHeaderItemUI")) != string_t::npos) return m_pHeader->SetItemIndex (pControl, iIndex); + + int iOrginIndex = m_pList->GetItemIndex (pControl); + if (iOrginIndex == -1) return false; + if (iOrginIndex == iIndex) return true; + + IListItemUI* pSelectedListItem = nullptr; + if (m_iCurSel >= 0) pSelectedListItem = + static_cast(GetItemAt (m_iCurSel)->GetInterface (_T ("ListItem"))); + if (!m_pList->SetItemIndex (pControl, iIndex)) return false; + int iMinIndex = min (iOrginIndex, iIndex); + int iMaxIndex = max (iOrginIndex, iIndex); + for (int i = iMinIndex; i < iMaxIndex + 1; ++i) { + CControlUI* p = m_pList->GetItemAt (i); + IListItemUI* pListItem = static_cast(p->GetInterface (_T ("ListItem"))); + if (pListItem) { + pListItem->SetIndex (i); + } + } + if (m_iCurSel >= 0 && pSelectedListItem) m_iCurSel = pSelectedListItem->GetIndex (); + return true; + } + + int CListUI::GetCount () const { + return m_pList->GetCount (); + } + + bool CListUI::Add (CControlUI* pControl) { + // Override the Add() method so we can add items specifically to + // the intended widgets. Headers are assumed to be + // answer the correct interface so we can add multiple list headers. + if (pControl->GetInterface (_T ("ListHeader"))) { + if (m_pHeader != pControl && m_pHeader->GetCount () == 0) { + CVerticalLayoutUI::Remove (m_pHeader); + m_pHeader = static_cast(pControl); + } + m_ListInfo.nColumns = MIN (m_pHeader->GetCount (), UILIST_MAX_COLUMNS); + return CVerticalLayoutUI::AddAt (pControl, 0); + } + // We also need to recognize header sub-items + if (pControl->GetClass ().find (_T ("ListHeaderItemUI")) != string_t::npos) { + bool ret = m_pHeader->Add (pControl); + m_ListInfo.nColumns = MIN (m_pHeader->GetCount (), UILIST_MAX_COLUMNS); + return ret; + } + // The list items should know about us + IListItemUI* pListItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (pListItem) { + pListItem->SetOwner (this); + pListItem->SetIndex (GetCount ()); + return m_pList->Add (pControl); + } + return CVerticalLayoutUI::Add (pControl); + } + + bool CListUI::AddAt (CControlUI* pControl, int iIndex) { + // Override the AddAt() method so we can add items specifically to + // the intended widgets. Headers and are assumed to be + // answer the correct interface so we can add multiple list headers. + if (pControl->GetInterface (_T ("ListHeader"))) { + if (m_pHeader != pControl && m_pHeader->GetCount () == 0) { + CVerticalLayoutUI::Remove (m_pHeader); + m_pHeader = static_cast(pControl); + } + m_ListInfo.nColumns = MIN (m_pHeader->GetCount (), UILIST_MAX_COLUMNS); + return CVerticalLayoutUI::AddAt (pControl, 0); + } + // We also need to recognize header sub-items + if (pControl->GetClass ().find (_T ("ListHeaderItemUI")) != string_t::npos) { + bool ret = m_pHeader->AddAt (pControl, iIndex); + m_ListInfo.nColumns = MIN (m_pHeader->GetCount (), UILIST_MAX_COLUMNS); + return ret; + } + if (!m_pList->AddAt (pControl, iIndex)) return false; + + // The list items should know about us + IListItemUI* pListItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (pListItem) { + pListItem->SetOwner (this); + pListItem->SetIndex (iIndex); + } + + for (int i = iIndex + 1; i < m_pList->GetCount (); ++i) { + CControlUI* p = m_pList->GetItemAt (i); + pListItem = static_cast(p->GetInterface (_T ("ListItem"))); + if (pListItem) { + pListItem->SetIndex (i); + } + } + if (m_iCurSel >= iIndex) m_iCurSel += 1; + return true; + } + + bool CListUI::Remove (CControlUI* pControl) { + if (pControl->GetInterface (_T ("ListHeader"))) return CVerticalLayoutUI::Remove (pControl); + // We also need to recognize header sub-items + if (pControl->GetClass ().find (_T ("ListHeaderItemUI")) != string_t::npos) return m_pHeader->Remove (pControl); + + int iIndex = m_pList->GetItemIndex (pControl); + if (iIndex == -1) return false; + + if (!m_pList->RemoveAt (iIndex)) return false; + + for (int i = iIndex; i < m_pList->GetCount (); ++i) { + CControlUI* p = m_pList->GetItemAt (i); + IListItemUI* pListItem = static_cast(p->GetInterface (_T ("ListItem"))); + if (pListItem) { + pListItem->SetIndex (i); + } + } + + if (iIndex == m_iCurSel && m_iCurSel >= 0) { + int iSel = m_iCurSel; + m_iCurSel = -1; + SelectItem (FindSelectable (iSel, false)); + } else if (iIndex < m_iCurSel) m_iCurSel -= 1; + return true; + } + + bool CListUI::RemoveAt (int iIndex) { + if (!m_pList->RemoveAt (iIndex)) return false; + + for (int i = iIndex; i < m_pList->GetCount (); ++i) { + CControlUI* p = m_pList->GetItemAt (i); + IListItemUI* pListItem = static_cast(p->GetInterface (_T ("ListItem"))); + if (pListItem) pListItem->SetIndex (i); + } + + if (iIndex == m_iCurSel && m_iCurSel >= 0) { + int iSel = m_iCurSel; + m_iCurSel = -1; + SelectItem (FindSelectable (iSel, false)); + } else if (iIndex < m_iCurSel) m_iCurSel -= 1; + return true; + } + + void CListUI::RemoveAll () { + m_iCurSel = -1; + m_iExpandedItem = -1; + m_aSelItems.Empty (); + m_pList->RemoveAll (); + } + + void CListUI::SetPos (RECT rc, bool bNeedInvalidate) { + CVerticalLayoutUI::SetPos (rc, bNeedInvalidate); + + if (!m_pHeader) return; + // Determine general list information and the size of header columns + m_ListInfo.nColumns = MIN (m_pHeader->GetCount (), UILIST_MAX_COLUMNS); + // The header/columns may or may not be visible at runtime. In either case + // we should determine the correct dimensions... + + if (!m_pHeader->IsVisible ()) { + for (int it = 0; it < m_pHeader->GetCount (); it++) { + static_cast(m_pHeader->GetItemAt (it))->SetInternVisible (true); + } + m_pHeader->SetPos ({ rc.left, 0, rc.right, 0 }, bNeedInvalidate); + } + + for (int i = 0; i < m_ListInfo.nColumns; i++) { + CControlUI* pControl = static_cast(m_pHeader->GetItemAt (i)); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) continue; + RECT rcPos = pControl->GetPos (); + m_ListInfo.rcColumn[i] = pControl->GetPos (); + } + if (!m_pHeader->IsVisible ()) { + for (int it = 0; it < m_pHeader->GetCount (); it++) { + static_cast(m_pHeader->GetItemAt (it))->SetInternVisible (false); + } + } + m_pList->SetPos (m_pList->GetPos (), bNeedInvalidate); + } + + void CListUI::Move (SIZE szOffset, bool bNeedInvalidate) { + CVerticalLayoutUI::Move (szOffset, bNeedInvalidate); + if (!m_pHeader->IsVisible ()) m_pHeader->Move (szOffset, false); + } + + int CListUI::GetMinSelItemIndex () { + if (m_aSelItems.GetSize () <= 0) + return -1; + int min = (int) m_aSelItems.GetAt (0); + int index; + for (int i = 0; i < m_aSelItems.GetSize (); ++i) { + index = (int) m_aSelItems.GetAt (i); + if (min > index) + min = index; + } + return min; + } + + int CListUI::GetMaxSelItemIndex () { + if (m_aSelItems.GetSize () <= 0) + return -1; + int max = (int) m_aSelItems.GetAt (0); + int index; + for (int i = 0; i < m_aSelItems.GetSize (); ++i) { + index = (int) m_aSelItems.GetAt (i); + if (max < index) + max = index; + } + return max; + } + + void CListUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CVerticalLayoutUI::DoEvent (event); + return; + } + + switch (event.Type) { + case UIEVENT_SETFOCUS: + m_bFocused = true; + return; + case UIEVENT_KILLFOCUS: + m_bFocused = false; + return; + case UIEVENT_KEYDOWN: + switch (event.chKey) { + case VK_UP: + { + if (m_aSelItems.GetSize () > 0) { + int index = GetMinSelItemIndex () - 1; + UnSelectAllItems (); + index > 0 ? SelectItem (index, true) : SelectItem (0, true); + } + } + return; + case VK_DOWN: + { + if (m_aSelItems.GetSize () > 0) { + int index = GetMaxSelItemIndex () + 1; + UnSelectAllItems (); + index + 1 > m_pList->GetCount () ? SelectItem (GetCount () - 1, true) : SelectItem (index, true); + } + } + return; + case VK_PRIOR: + PageUp (); + return; + case VK_NEXT: + PageDown (); + return; + case VK_HOME: + SelectItem (FindSelectable (0, false), true); + return; + case VK_END: + SelectItem (FindSelectable (GetCount () - 1, true), true); + return; + case VK_RETURN: + if (m_iCurSel != -1) GetItemAt (m_iCurSel)->Activate (); + return; + case 0x41:// Ctrl+A + { + if (IsMultiSelect () && (GetKeyState (VK_CONTROL) & 0x8000)) { + SelectAllItems (); + } + return; + } + } + break; + case UIEVENT_SCROLLWHEEL: + switch (LOWORD (event.wParam)) { + case SB_LINEUP: + if (m_bScrollSelect && !IsMultiSelect ()) SelectItem (FindSelectable (m_iCurSel - 1, false), true); + else LineUp (); + return; + case SB_LINEDOWN: + if (m_bScrollSelect && !IsMultiSelect ()) SelectItem (FindSelectable (m_iCurSel + 1, true), true); + else LineDown (); + return; + } + break; + } + CVerticalLayoutUI::DoEvent (event); + } + + bool CListUI::IsFixedScrollbar () { + return m_bFixedScrollbar; + } + + void CListUI::SetFixedScrollbar (bool bFixed) { + m_bFixedScrollbar = bFixed; + Invalidate (); + } + + CListHeaderUI* CListUI::GetHeader () const { + return m_pHeader; + } + + CContainerUI* CListUI::GetList () const { + return m_pList; + } + + bool CListUI::GetScrollSelect () { + return m_bScrollSelect; + } + + void CListUI::SetScrollSelect (bool bScrollSelect) { + m_bScrollSelect = bScrollSelect; + } + + int CListUI::GetCurSelActivate () const { + return m_iCurSelActivate; + } + + bool CListUI::SelectItemActivate (int iIndex) { + if (!SelectItem (iIndex, true)) { + return false; + } + + m_iCurSelActivate = iIndex; + return true; + } + + int CListUI::GetCurSel () const { + if (m_aSelItems.GetSize () <= 0) { + return -1; + } else { + return (int) m_aSelItems.GetAt (0); + } + + return -1; + } + + bool CListUI::SelectItem (int iIndex, bool bTakeFocus) { + // ȡѡ + UnSelectAllItems (); + // жǷϷб + if (iIndex < 0) return false; + CControlUI* pControl = GetItemAt (iIndex); + if (!pControl) return false; + IListItemUI* pListItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (!pListItem) return false; + if (!pListItem->Select (true)) { + return false; + } + int iLastSel = m_iCurSel; + m_iCurSel = iIndex; + m_aSelItems.Add ((LPVOID) iIndex); + EnsureVisible (iIndex); + if (bTakeFocus) pControl->SetFocus (); + if (m_pManager && iLastSel != m_iCurSel) { + m_pManager->SendNotify (this, DUI_MSGTYPE_ITEMSELECT, iIndex); + } + + return true; + } + + bool CListUI::SelectMultiItem (int iIndex, bool bTakeFocus) { + if (!IsMultiSelect ()) return SelectItem (iIndex, bTakeFocus); + + if (iIndex < 0) return false; + CControlUI* pControl = GetItemAt (iIndex); + if (!pControl) return false; + IListItemUI* pListItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (!pListItem) return false; + if (m_aSelItems.Find ((LPVOID) iIndex) >= 0) return false; + if (!pListItem->SelectMulti (true)) return false; + + m_iCurSel = iIndex; + m_aSelItems.Add ((LPVOID) iIndex); + EnsureVisible (iIndex); + if (bTakeFocus) pControl->SetFocus (); + if (m_pManager) { + m_pManager->SendNotify (this, DUI_MSGTYPE_ITEMSELECT, iIndex); + } + return true; + } + + void CListUI::SetMultiSelect (bool bMultiSel) { + m_bMultiSel = bMultiSel; + if (!bMultiSel) UnSelectAllItems (); + } + + bool CListUI::IsMultiSelect () const { + return m_bMultiSel; + } + + bool CListUI::UnSelectItem (int iIndex, bool bOthers) { + if (!IsMultiSelect ()) return false; + if (bOthers) { + for (int i = m_aSelItems.GetSize () - 1; i >= 0; --i) { + int iSelIndex = (int) m_aSelItems.GetAt (i); + if (iSelIndex == iIndex) continue; + CControlUI* pControl = GetItemAt (iSelIndex); + if (!pControl) continue; + if (!pControl->IsEnabled ()) continue; + IListItemUI* pSelListItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (!pSelListItem) continue; + if (!pSelListItem->SelectMulti (false)) continue; + m_aSelItems.Remove (i); + } + } else { + if (iIndex < 0) return false; + CControlUI* pControl = GetItemAt (iIndex); + if (!pControl) return false; + if (!pControl->IsEnabled ()) return false; + IListItemUI* pListItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (!pListItem) return false; + int aIndex = m_aSelItems.Find ((LPVOID) iIndex); + if (aIndex < 0) return false; + if (!pListItem->SelectMulti (false)) return false; + if (m_iCurSel == iIndex) m_iCurSel = -1; + m_aSelItems.Remove (aIndex); + } + return true; + } + + void CListUI::SelectAllItems () { + for (int i = 0; i < GetCount (); ++i) { + CControlUI* pControl = GetItemAt (i); + if (!pControl) continue; + if (!pControl->IsVisible ()) continue; + if (!pControl->IsEnabled ()) continue; + IListItemUI* pListItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (!pListItem) continue; + if (!pListItem->SelectMulti (true)) continue; + + m_aSelItems.Add ((LPVOID) i); + m_iCurSel = i; + } + } + + void CListUI::UnSelectAllItems () { + for (int i = 0; i < m_aSelItems.GetSize (); ++i) { + int iSelIndex = (int) m_aSelItems.GetAt (i); + CControlUI* pControl = GetItemAt (iSelIndex); + if (!pControl) continue; + if (!pControl->IsEnabled ()) continue; + IListItemUI* pListItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (!pListItem) continue; + if (!pListItem->SelectMulti (false)) continue; + } + m_aSelItems.Empty (); + m_iCurSel = -1; + } + + int CListUI::GetSelectItemCount () const { + return m_aSelItems.GetSize (); + } + + int CListUI::GetNextSelItem (int nItem) const { + if (m_aSelItems.GetSize () <= 0) + return -1; + + if (nItem < 0) { + return (int) m_aSelItems.GetAt (0); + } + int aIndex = m_aSelItems.Find ((LPVOID) nItem); + if (aIndex < 0) return -1; + if (aIndex + 1 > m_aSelItems.GetSize () - 1) + return -1; + return (int) m_aSelItems.GetAt (aIndex + 1); + } + + UINT CListUI::GetListType () { + return LT_LIST; + } + + TListInfoUI* CListUI::GetListInfo () { + return &m_ListInfo; + } + + bool CListUI::IsDelayedDestroy () const { + return m_pList->IsDelayedDestroy (); + } + + void CListUI::SetDelayedDestroy (bool bDelayed) { + m_pList->SetDelayedDestroy (bDelayed); + } + + int CListUI::GetChildPadding () const { + return m_pList->GetChildPadding (); + } + + void CListUI::SetChildPadding (int iPadding) { + m_pList->SetChildPadding (iPadding); + } + + void CListUI::SetItemFont (int index) { + m_ListInfo.nFont = index; + NeedUpdate (); + } + + void CListUI::SetItemTextStyle (UINT uStyle) { + m_ListInfo.uTextStyle = uStyle; + NeedUpdate (); + } + + void CListUI::SetItemTextPadding (RECT rc) { + m_ListInfo.rcTextPadding = rc; + NeedUpdate (); + } + + RECT CListUI::GetItemTextPadding () const { + RECT rect = m_ListInfo.rcTextPadding; + GetManager ()->GetDPIObj ()->Scale (&rect); + return rect; + } + + void CListUI::SetItemTextColor (DWORD dwTextColor) { + m_ListInfo.dwTextColor = dwTextColor; + Invalidate (); + } + + void CListUI::SetItemBkColor (DWORD dwBkColor) { + m_ListInfo.dwBkColor = dwBkColor; + Invalidate (); + } + + void CListUI::SetItemBkImage (string_view_t pStrImage) { + m_ListInfo.sBkImage = pStrImage; + Invalidate (); + } + + void CListUI::SetAlternateBk (bool bAlternateBk) { + m_ListInfo.bAlternateBk = bAlternateBk; + Invalidate (); + } + + DWORD CListUI::GetItemTextColor () const { + return m_ListInfo.dwTextColor; + } + + DWORD CListUI::GetItemBkColor () const { + return m_ListInfo.dwBkColor; + } + + string_view_t CListUI::GetItemBkImage () const { + return m_ListInfo.sBkImage; + } + + bool CListUI::IsAlternateBk () const { + return m_ListInfo.bAlternateBk; + } + + void CListUI::SetSelectedItemTextColor (DWORD dwTextColor) { + m_ListInfo.dwSelectedTextColor = dwTextColor; + Invalidate (); + } + + void CListUI::SetSelectedItemBkColor (DWORD dwBkColor) { + m_ListInfo.dwSelectedBkColor = dwBkColor; + Invalidate (); + } + + void CListUI::SetSelectedItemImage (string_view_t pStrImage) { + m_ListInfo.sSelectedImage = pStrImage; + Invalidate (); + } + + DWORD CListUI::GetSelectedItemTextColor () const { + return m_ListInfo.dwSelectedTextColor; + } + + DWORD CListUI::GetSelectedItemBkColor () const { + return m_ListInfo.dwSelectedBkColor; + } + + string_view_t CListUI::GetSelectedItemImage () const { + return m_ListInfo.sSelectedImage; + } + + void CListUI::SetHotItemTextColor (DWORD dwTextColor) { + m_ListInfo.dwHotTextColor = dwTextColor; + Invalidate (); + } + + void CListUI::SetHotItemBkColor (DWORD dwBkColor) { + m_ListInfo.dwHotBkColor = dwBkColor; + Invalidate (); + } + + void CListUI::SetHotItemImage (string_view_t pStrImage) { + m_ListInfo.sHotImage = pStrImage; + Invalidate (); + } + + DWORD CListUI::GetHotItemTextColor () const { + return m_ListInfo.dwHotTextColor; + } + DWORD CListUI::GetHotItemBkColor () const { + return m_ListInfo.dwHotBkColor; + } + + string_view_t CListUI::GetHotItemImage () const { + return m_ListInfo.sHotImage; + } + + void CListUI::SetDisabledItemTextColor (DWORD dwTextColor) { + m_ListInfo.dwDisabledTextColor = dwTextColor; + Invalidate (); + } + + void CListUI::SetDisabledItemBkColor (DWORD dwBkColor) { + m_ListInfo.dwDisabledBkColor = dwBkColor; + Invalidate (); + } + + void CListUI::SetDisabledItemImage (string_view_t pStrImage) { + m_ListInfo.sDisabledImage = pStrImage; + Invalidate (); + } + + DWORD CListUI::GetDisabledItemTextColor () const { + return m_ListInfo.dwDisabledTextColor; + } + + DWORD CListUI::GetDisabledItemBkColor () const { + return m_ListInfo.dwDisabledBkColor; + } + + string_view_t CListUI::GetDisabledItemImage () const { + return m_ListInfo.sDisabledImage; + } + + DWORD CListUI::GetItemLineColor () const { + return m_ListInfo.dwLineColor; + } + + void CListUI::SetItemLineColor (DWORD dwLineColor) { + m_ListInfo.dwLineColor = dwLineColor; + Invalidate (); + } + void CListUI::SetItemShowRowLine (bool bShowLine) { + m_ListInfo.bShowRowLine = bShowLine; + Invalidate (); + } + void CListUI::SetItemShowColumnLine (bool bShowLine) { + m_ListInfo.bShowColumnLine = bShowLine; + Invalidate (); + } + bool CListUI::IsItemShowHtml () { + return m_ListInfo.bShowHtml; + } + + void CListUI::SetItemShowHtml (bool bShowHtml) { + if (m_ListInfo.bShowHtml == bShowHtml) return; + + m_ListInfo.bShowHtml = bShowHtml; + NeedUpdate (); + } + + bool CListUI::IsItemRSelected () { + return m_ListInfo.bRSelected; + } + + void CListUI::SetItemRSelected (bool bSelected) { + if (m_ListInfo.bRSelected == bSelected) return; + + m_ListInfo.bRSelected = bSelected; + NeedUpdate (); + } + + void CListUI::SetMultiExpanding (bool bMultiExpandable) { + m_ListInfo.bMultiExpandable = bMultiExpandable; + } + + bool CListUI::ExpandItem (int iIndex, bool bExpand /*= true*/) { + if (m_iExpandedItem >= 0 && !m_ListInfo.bMultiExpandable) { + CControlUI* pControl = GetItemAt (m_iExpandedItem); + if (pControl) { + IListItemUI* pItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (pItem) pItem->Expand (false); + } + m_iExpandedItem = -1; + } + if (bExpand) { + CControlUI* pControl = GetItemAt (iIndex); + if (!pControl) return false; + if (!pControl->IsVisible ()) return false; + IListItemUI* pItem = static_cast(pControl->GetInterface (_T ("ListItem"))); + if (!pItem) return false; + m_iExpandedItem = iIndex; + if (!pItem->Expand (true)) { + m_iExpandedItem = -1; + return false; + } + } + NeedUpdate (); + return true; + } + + int CListUI::GetExpandedItem () const { + return m_iExpandedItem; + } + + void CListUI::EnsureVisible (int iIndex) { + if (m_iCurSel < 0) return; + RECT rcItem = m_pList->GetItemAt (iIndex)->GetPos (); + RECT rcList = m_pList->GetPos (); + RECT rcListInset = m_pList->GetInset (); + + rcList.left += rcListInset.left; + rcList.top += rcListInset.top; + rcList.right -= rcListInset.right; + rcList.bottom -= rcListInset.bottom; + + CScrollBarUI* pHorizontalScrollBar = m_pList->GetHorizontalScrollBar (); + if (pHorizontalScrollBar && pHorizontalScrollBar->IsVisible ()) rcList.bottom -= pHorizontalScrollBar->GetFixedHeight (); + + int iPos = m_pList->GetScrollPos ().cy; + if (rcItem.top >= rcList.top && rcItem.bottom < rcList.bottom) return; + int dx = 0; + if (rcItem.top < rcList.top) dx = rcItem.top - rcList.top; + if (rcItem.bottom > rcList.bottom) dx = rcItem.bottom - rcList.bottom; + Scroll (0, dx); + } + + void CListUI::Scroll (int dx, int dy) { + if (dx == 0 && dy == 0) return; + SIZE sz = m_pList->GetScrollPos (); + m_pList->SetScrollPos ({ sz.cx + dx, sz.cy + dy }); + } + + void CListUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("header")) GetHeader ()->SetVisible (pstrValue != _T ("hidden")); + else if (pstrName == _T ("headerbkimage")) GetHeader ()->SetBkImage (pstrValue); + else if (pstrName == _T ("scrollselect")) SetScrollSelect (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("fixedscrollbar")) SetFixedScrollbar (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("multiexpanding")) SetMultiExpanding (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("itemfont")) m_ListInfo.nFont = FawTools::parse_dec (pstrValue); + else if (pstrName == _T ("itemalign")) { + if (pstrValue.find (_T ("left")) != string_t::npos) { + m_ListInfo.uTextStyle &= ~(DT_CENTER | DT_RIGHT); + m_ListInfo.uTextStyle |= DT_LEFT; + } + if (pstrValue.find (_T ("center")) != string_t::npos) { + m_ListInfo.uTextStyle &= ~(DT_LEFT | DT_RIGHT); + m_ListInfo.uTextStyle |= DT_CENTER; + } + if (pstrValue.find (_T ("right")) != string_t::npos) { + m_ListInfo.uTextStyle &= ~(DT_LEFT | DT_CENTER); + m_ListInfo.uTextStyle |= DT_RIGHT; + } + } else if (pstrName == _T ("itemvalign")) { + if (pstrValue.find (_T ("top")) != string_t::npos) { + m_ListInfo.uTextStyle &= ~(DT_VCENTER | DT_BOTTOM); + m_ListInfo.uTextStyle |= DT_TOP; + } + if (pstrValue.find (_T ("vcenter")) != string_t::npos) { + m_ListInfo.uTextStyle &= ~(DT_TOP | DT_BOTTOM | DT_WORDBREAK); + m_ListInfo.uTextStyle |= DT_VCENTER | DT_SINGLELINE; + } + if (pstrValue.find (_T ("bottom")) != string_t::npos) { + m_ListInfo.uTextStyle &= ~(DT_TOP | DT_VCENTER); + m_ListInfo.uTextStyle |= DT_BOTTOM; + } + } else if (pstrName == _T ("itemendellipsis")) { + if (FawTools::parse_bool (pstrValue)) m_ListInfo.uTextStyle |= DT_END_ELLIPSIS; + else m_ListInfo.uTextStyle &= ~DT_END_ELLIPSIS; + } else if (pstrName == _T ("itemtextpadding")) { + RECT rcTextPadding = FawTools::parse_rect (pstrValue); + SetItemTextPadding (rcTextPadding); + } else if (pstrName == _T ("itemtextcolor")) { SetItemTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("itembkcolor")) { SetItemBkColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("itembkimage")) SetItemBkImage (pstrValue); + else if (pstrName == _T ("itemaltbk")) SetAlternateBk (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("itemselectedtextcolor")) { SetSelectedItemTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("itemselectedbkcolor")) { SetSelectedItemBkColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("itemselectedimage")) SetSelectedItemImage (pstrValue); + else if (pstrName == _T ("itemhottextcolor")) { SetHotItemTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("itemhotbkcolor")) { SetHotItemBkColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("itemhotimage")) SetHotItemImage (pstrValue); + else if (pstrName == _T ("itemdisabledtextcolor")) { SetDisabledItemTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("itemdisabledbkcolor")) { SetDisabledItemBkColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("itemdisabledimage")) SetDisabledItemImage (pstrValue); + else if (pstrName == _T ("itemlinecolor")) { SetItemLineColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("itemshowrowline")) SetItemShowRowLine (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("itemshowcolumnline")) SetItemShowColumnLine (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("itemshowhtml")) SetItemShowHtml (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("multiselect")) SetMultiSelect (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("itemrselected")) SetItemRSelected (FawTools::parse_bool (pstrValue)); + else CVerticalLayoutUI::SetAttribute (pstrName, pstrValue); + } + + IListCallbackUI* CListUI::GetTextCallback () const { + return m_pCallback; + } + + void CListUI::SetTextCallback (IListCallbackUI* pCallback) { + m_pCallback = pCallback; + } + + SIZE CListUI::GetScrollPos () const { + return m_pList->GetScrollPos (); + } + + SIZE CListUI::GetScrollRange () const { + return m_pList->GetScrollRange (); + } + + void CListUI::SetScrollPos (SIZE szPos, bool bMsg) { + m_pList->SetScrollPos (szPos, bMsg); + } + + void CListUI::LineUp () { + m_pList->LineUp (); + } + + void CListUI::LineDown () { + m_pList->LineDown (); + } + + void CListUI::PageUp () { + m_pList->PageUp (); + } + + void CListUI::PageDown () { + m_pList->PageDown (); + } + + void CListUI::HomeUp () { + m_pList->HomeUp (); + } + + void CListUI::EndDown () { + m_pList->EndDown (); + } + + void CListUI::LineLeft () { + m_pList->LineLeft (); + } + + void CListUI::LineRight () { + m_pList->LineRight (); + } + + void CListUI::PageLeft () { + m_pList->PageLeft (); + } + + void CListUI::PageRight () { + m_pList->PageRight (); + } + + void CListUI::HomeLeft () { + m_pList->HomeLeft (); + } + + void CListUI::EndRight () { + m_pList->EndRight (); + } + + void CListUI::EnableScrollBar (bool bEnableVertical, bool bEnableHorizontal) { + m_pList->EnableScrollBar (bEnableVertical, bEnableHorizontal); + } + + CScrollBarUI* CListUI::GetVerticalScrollBar () const { + return m_pList->GetVerticalScrollBar (); + } + + CScrollBarUI* CListUI::GetHorizontalScrollBar () const { + return m_pList->GetHorizontalScrollBar (); + } + + BOOL CListUI::SortItems (PULVCompareFunc pfnCompare, UINT_PTR dwData) { + if (!m_pList) + return FALSE; + return m_pList->SortItems (pfnCompare, dwData); + } + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + CListBodyUI::CListBodyUI (CListUI* pOwner): m_pOwner (pOwner) { + ASSERT (m_pOwner); + } + + BOOL CListBodyUI::SortItems (PULVCompareFunc pfnCompare, UINT_PTR dwData) { + if (!pfnCompare) + return FALSE; + m_pCompareFunc = pfnCompare; + m_compareData = dwData; + CControlUI **pData = (CControlUI **) m_items.GetData (); + qsort_s (m_items.GetData (), m_items.GetSize (), sizeof (CControlUI*), CListBodyUI::ItemComareFunc, this); + IListItemUI *pItem = nullptr; + for (int i = 0; i < m_items.GetSize (); ++i) { + pItem = (IListItemUI*) (static_cast(m_items[i])->GetInterface (TEXT ("ListItem"))); + if (pItem) { + pItem->SetIndex (i); + pItem->Select (false); + } + } + m_pOwner->SelectItem (-1); + if (m_pManager) { + SetPos (GetPos ()); + Invalidate (); + } + + return TRUE; + } + + int __cdecl CListBodyUI::ItemComareFunc (void *pvlocale, const void *item1, const void *item2) { + CListBodyUI *pThis = (CListBodyUI*) pvlocale; + if (!pThis || !item1 || !item2) + return 0; + return pThis->ItemComareFunc (item1, item2); + } + + int __cdecl CListBodyUI::ItemComareFunc (const void *item1, const void *item2) { + CControlUI *pControl1 = *(CControlUI**) item1; + CControlUI *pControl2 = *(CControlUI**) item2; + return m_pCompareFunc ((UINT_PTR) pControl1, (UINT_PTR) pControl2, m_compareData); + } + + int CListBodyUI::GetScrollStepSize () const { + if (m_pOwner) return m_pOwner->GetScrollStepSize (); + + return CVerticalLayoutUI::GetScrollStepSize (); + } + + void CListBodyUI::SetScrollPos (SIZE szPos, bool bMsg) { + int cx = 0; + int cy = 0; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + int iLastScrollPos = m_pVerticalScrollBar->GetScrollPos (); + m_pVerticalScrollBar->SetScrollPos (szPos.cy); + cy = m_pVerticalScrollBar->GetScrollPos () - iLastScrollPos; + } + + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + int iLastScrollPos = m_pHorizontalScrollBar->GetScrollPos (); + m_pHorizontalScrollBar->SetScrollPos (szPos.cx); + cx = m_pHorizontalScrollBar->GetScrollPos () - iLastScrollPos; + } + + RECT rcPos = { 0 }; + for (int it2 = 0; it2 < m_items.GetSize (); it2++) { + CControlUI* pControl = static_cast(m_items[it2]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) continue; + + rcPos = pControl->GetPos (); + rcPos.left -= cx; + rcPos.right -= cx; + rcPos.top -= cy; + rcPos.bottom -= cy; + pControl->SetPos (rcPos, true); + } + + Invalidate (); + if (m_pOwner) { + CListHeaderUI* pHeader = m_pOwner->GetHeader (); + if (!pHeader) return; + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + pInfo->nColumns = MIN (pHeader->GetCount (), UILIST_MAX_COLUMNS); + + if (!pHeader->IsVisible ()) { + for (int it = 0; it < pHeader->GetCount (); it++) { + static_cast(pHeader->GetItemAt (it))->SetInternVisible (true); + } + } + for (int i = 0; i < pInfo->nColumns; i++) { + CControlUI* pControl = static_cast(pHeader->GetItemAt (i)); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) continue; + + rcPos = pControl->GetPos (); + rcPos.left -= cx; + rcPos.right -= cx; + pControl->SetPos (rcPos); + pInfo->rcColumn[i] = pControl->GetPos (); + } + if (!pHeader->IsVisible ()) { + for (int it = 0; it < pHeader->GetCount (); it++) { + static_cast(pHeader->GetItemAt (it))->SetInternVisible (false); + } + } + } + } + + void CListBodyUI::SetPos (RECT rc, bool bNeedInvalidate) { + CControlUI::SetPos (rc, bNeedInvalidate); + rc = m_rcItem; + + // Adjust for inset + rc.left += m_rcInset.left; + rc.top += m_rcInset.top; + rc.right -= m_rcInset.right; + rc.bottom -= m_rcInset.bottom; + if (m_pOwner->IsFixedScrollbar () && m_pVerticalScrollBar) rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + else if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + + // Determine the minimum size + SIZE szAvailable = { rc.right - rc.left, rc.bottom - rc.top }; + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) + szAvailable.cx += m_pHorizontalScrollBar->GetScrollRange (); + + int cxNeeded = 0; + int nAdjustables = 0; + int cyFixed = 0; + int nEstimateNum = 0; + for (int it1 = 0; it1 < m_items.GetSize (); it1++) { + CControlUI* pControl = static_cast(m_items[it1]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) continue; + SIZE sz = pControl->EstimateSize (szAvailable); + if (sz.cy == 0) { + nAdjustables++; + } else { + if (sz.cy < pControl->GetMinHeight ()) sz.cy = pControl->GetMinHeight (); + if (sz.cy > pControl->GetMaxHeight ()) sz.cy = pControl->GetMaxHeight (); + } + cyFixed += sz.cy + pControl->GetPadding ().top + pControl->GetPadding ().bottom; + + RECT rcPadding = pControl->GetPadding (); + sz.cx = MAX (sz.cx, 0); + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + if (sz.cx > pControl->GetMaxWidth ()) sz.cx = pControl->GetMaxWidth (); + cxNeeded = MAX (cxNeeded, sz.cx); + nEstimateNum++; + } + cyFixed += (nEstimateNum - 1) * m_iChildPadding; + + if (m_pOwner) { + CListHeaderUI* pHeader = m_pOwner->GetHeader (); + if (pHeader && pHeader->GetCount () > 0) { + cxNeeded = MAX (0, pHeader->EstimateSize ({ rc.right - rc.left, rc.bottom - rc.top }).cx); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + int nOffset = m_pHorizontalScrollBar->GetScrollPos (); + RECT rcHeader = pHeader->GetPos (); + rcHeader.left = rc.left - nOffset; + pHeader->SetPos (rcHeader); + } + } + } + + // Place elements + int cyNeeded = 0; + int cyExpand = 0; + if (nAdjustables > 0) cyExpand = MAX (0, (szAvailable.cy - cyFixed) / nAdjustables); + // Position the elements + SIZE szRemaining = szAvailable; + int iPosY = rc.top; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + iPosY -= m_pVerticalScrollBar->GetScrollPos (); + } + int iPosX = rc.left; + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + iPosX -= m_pHorizontalScrollBar->GetScrollPos (); + } + int iAdjustable = 0; + int cyFixedRemaining = cyFixed; + for (int it2 = 0; it2 < m_items.GetSize (); it2++) { + CControlUI* pControl = static_cast(m_items[it2]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) { + SetFloatPos (it2); + continue; + } + + RECT rcPadding = pControl->GetPadding (); + szRemaining.cy -= rcPadding.top; + SIZE sz = pControl->EstimateSize (szRemaining); + if (sz.cy == 0) { + iAdjustable++; + sz.cy = cyExpand; + // Distribute remaining to last element (usually round-off left-overs) + if (iAdjustable == nAdjustables) { + sz.cy = MAX (0, szRemaining.cy - rcPadding.bottom - cyFixedRemaining); + } + if (sz.cy < pControl->GetMinHeight ()) sz.cy = pControl->GetMinHeight (); + if (sz.cy > pControl->GetMaxHeight ()) sz.cy = pControl->GetMaxHeight (); + } else { + if (sz.cy < pControl->GetMinHeight ()) sz.cy = pControl->GetMinHeight (); + if (sz.cy > pControl->GetMaxHeight ()) sz.cy = pControl->GetMaxHeight (); + cyFixedRemaining -= sz.cy; + } + + sz.cx = MAX (cxNeeded, szAvailable.cx - rcPadding.left - rcPadding.right); + + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + if (sz.cx > pControl->GetMaxWidth ()) sz.cx = pControl->GetMaxWidth (); + + RECT rcCtrl = { iPosX + rcPadding.left, iPosY + rcPadding.top, iPosX + rcPadding.left + sz.cx, iPosY + sz.cy + rcPadding.top + rcPadding.bottom }; + pControl->SetPos (rcCtrl); + + iPosY += sz.cy + m_iChildPadding + rcPadding.top + rcPadding.bottom; + cyNeeded += sz.cy + rcPadding.top + rcPadding.bottom; + szRemaining.cy -= sz.cy + m_iChildPadding + rcPadding.bottom; + } + cyNeeded += (nEstimateNum - 1) * m_iChildPadding; + + if (m_pHorizontalScrollBar) { + if (cxNeeded > rc.right - rc.left) { + if (m_pHorizontalScrollBar->IsVisible ()) { + m_pHorizontalScrollBar->SetScrollRange (cxNeeded - (rc.right - rc.left)); + } else { + m_pHorizontalScrollBar->SetVisible (true); + m_pHorizontalScrollBar->SetScrollRange (cxNeeded - (rc.right - rc.left)); + m_pHorizontalScrollBar->SetScrollPos (0); + rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + } + } else { + if (m_pHorizontalScrollBar->IsVisible ()) { + m_pHorizontalScrollBar->SetVisible (false); + m_pHorizontalScrollBar->SetScrollRange (0); + m_pHorizontalScrollBar->SetScrollPos (0); + rc.bottom += m_pHorizontalScrollBar->GetFixedHeight (); + } + } + } + UINT uListType = m_pOwner->GetListType (); + if (uListType == LT_LIST) { + // ߴ + int nItemCount = m_items.GetSize (); + if (nItemCount > 0) { + CControlUI* pControl = static_cast(m_items[0]); + int nFixedWidth = pControl->GetFixedWidth (); + if (nFixedWidth > 0) { + int nRank = (rc.right - rc.left) / nFixedWidth; + if (nRank > 0) { + cyNeeded = ((nItemCount - 1) / nRank + 1) * pControl->GetFixedHeight (); + } + } + } + } + // Process the scrollbar + ProcessScrollBar (rc, cxNeeded, cyNeeded); + } + + void CListBodyUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pOwner) m_pOwner->DoEvent (event); + else CVerticalLayoutUI::DoEvent (event); + return; + } + + CVerticalLayoutUI::DoEvent (event); + } + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CListHeaderUI) + + CListHeaderUI::CListHeaderUI (): + m_bIsScaleHeader (false) {} + + string_view_t CListHeaderUI::GetClass () const { + return _T ("ListHeaderUI"); + } + + LPVOID CListHeaderUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_LISTHEADER) return this; + return CHorizontalLayoutUI::GetInterface (pstrName); + } + + SIZE CListHeaderUI::EstimateSize (SIZE szAvailable) { + SIZE cXY = { 0, m_cxyFixed.cy }; + if (cXY.cy == 0 && m_pManager) { + for (int it = 0; it < m_items.GetSize (); it++) { + cXY.cy = MAX (cXY.cy, static_cast(m_items[it])->EstimateSize (szAvailable).cy); + } + int nMin = m_pManager->GetDefaultFontInfo ()->tm.tmHeight + 6; + cXY.cy = MAX (cXY.cy, nMin); + } + + for (int it = 0; it < m_items.GetSize (); it++) { + cXY.cx += static_cast(m_items[it])->EstimateSize (szAvailable).cx; + } + + return cXY; + } + + void CListHeaderUI::SetPos (RECT rc, bool bNeedInvalidate) { + CControlUI::SetPos (rc, bNeedInvalidate); + rc = m_rcItem; + + // Adjust for inset + rc.left += m_rcInset.left; + rc.top += m_rcInset.top; + rc.right -= m_rcInset.right; + rc.bottom -= m_rcInset.bottom; + + if (m_items.GetSize () == 0) { + return; + } + + + // Determine the width of elements that are sizeable + SIZE szAvailable = { rc.right - rc.left, rc.bottom - rc.top }; + + int nAdjustables = 0; + int cxFixed = 0; + int nEstimateNum = 0; + for (int it1 = 0; it1 < m_items.GetSize (); it1++) { + CControlUI* pControl = static_cast(m_items[it1]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) continue; + SIZE sz = pControl->EstimateSize (szAvailable); + if (sz.cx == 0) { + nAdjustables++; + } else { + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + if (sz.cx > pControl->GetMaxWidth ()) sz.cx = pControl->GetMaxWidth (); + } + cxFixed += sz.cx + pControl->GetPadding ().left + pControl->GetPadding ().right; + nEstimateNum++; + } + cxFixed += (nEstimateNum - 1) * m_iChildPadding; + + int cxExpand = 0; + int cxNeeded = 0; + if (nAdjustables > 0) cxExpand = MAX (0, (szAvailable.cx - cxFixed) / nAdjustables); + // Position the elements + SIZE szRemaining = szAvailable; + int iPosX = rc.left; + + int iAdjustable = 0; + int cxFixedRemaining = cxFixed; + + int nHeaderWidth = GetWidth (); + CListUI *pList = static_cast(GetParent ()); + if (pList) { + CScrollBarUI* pVScroll = pList->GetVerticalScrollBar (); + if (pVScroll) + nHeaderWidth -= pVScroll->GetWidth (); + } + for (int it2 = 0; it2 < m_items.GetSize (); it2++) { + CControlUI* pControl = static_cast(m_items[it2]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) { + SetFloatPos (it2); + continue; + } + RECT rcPadding = pControl->GetPadding (); + szRemaining.cx -= rcPadding.left; + + SIZE sz = { 0, 0 }; + if (m_bIsScaleHeader) { + CListHeaderItemUI* pHeaderItem = static_cast(pControl); + sz.cx = int (nHeaderWidth * (float) pHeaderItem->GetScale () / 100); + } else { + sz = pControl->EstimateSize (szRemaining); + } + + if (sz.cx == 0) { + iAdjustable++; + sz.cx = cxExpand; + // Distribute remaining to last element (usually round-off left-overs) + if (iAdjustable == nAdjustables) { + sz.cx = MAX (0, szRemaining.cx - rcPadding.right - cxFixedRemaining); + } + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + if (sz.cx > pControl->GetMaxWidth ()) sz.cx = pControl->GetMaxWidth (); + } else { + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + if (sz.cx > pControl->GetMaxWidth ()) sz.cx = pControl->GetMaxWidth (); + + cxFixedRemaining -= sz.cx; + } + + sz.cy = pControl->GetFixedHeight (); + if (sz.cy == 0) sz.cy = rc.bottom - rc.top - rcPadding.top - rcPadding.bottom; + if (sz.cy < 0) sz.cy = 0; + if (sz.cy < pControl->GetMinHeight ()) sz.cy = pControl->GetMinHeight (); + if (sz.cy > pControl->GetMaxHeight ()) sz.cy = pControl->GetMaxHeight (); + + RECT rcCtrl = { iPosX + rcPadding.left, rc.top + rcPadding.top, iPosX + sz.cx + rcPadding.left + rcPadding.right, rc.top + rcPadding.top + sz.cy }; + pControl->SetPos (rcCtrl); + iPosX += sz.cx + m_iChildPadding + rcPadding.left + rcPadding.right; + cxNeeded += sz.cx + rcPadding.left + rcPadding.right; + szRemaining.cx -= sz.cx + m_iChildPadding + rcPadding.right; + } + cxNeeded += (nEstimateNum - 1) * m_iChildPadding; + } + + void CListHeaderUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("scaleheader")) SetScaleHeader (FawTools::parse_bool (pstrValue)); + else CHorizontalLayoutUI::SetAttribute (pstrName, pstrValue); + } + + void CListHeaderUI::SetScaleHeader (bool bIsScale) { + m_bIsScaleHeader = bIsScale; + } + + bool CListHeaderUI::IsScaleHeader () const { + return m_bIsScaleHeader; + } + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CListHeaderItemUI) + + CListHeaderItemUI::CListHeaderItemUI (): m_bDragable (true), m_uButtonState (0), m_iSepWidth (4), + m_uTextStyle (DT_VCENTER | DT_CENTER | DT_SINGLELINE), m_dwTextColor (0), m_iFont (-1), m_bShowHtml (false), m_nScale (0) { + SetTextPadding ({ 2, 0, 2, 0 }); + ptLastMouse.x = ptLastMouse.y = 0; + SetMinWidth (16); + } + + string_view_t CListHeaderItemUI::GetClass () const { + return _T ("ListHeaderItemUI"); + } + + LPVOID CListHeaderItemUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_LISTHEADERITEM) return this; + return CContainerUI::GetInterface (pstrName); + } + + UINT CListHeaderItemUI::GetControlFlags () const { + if (IsEnabled () && m_iSepWidth != 0) return UIFLAG_SETCURSOR; + else return 0; + } + + void CListHeaderItemUI::SetEnabled (bool bEnable) { + CContainerUI::SetEnabled (bEnable); + if (!IsEnabled ()) { + m_uButtonState = 0; + } + } + + bool CListHeaderItemUI::IsDragable () const { + return m_bDragable; + } + + void CListHeaderItemUI::SetDragable (bool bDragable) { + m_bDragable = bDragable; + if (!m_bDragable) m_uButtonState &= ~UISTATE_CAPTURED; + } + + DWORD CListHeaderItemUI::GetSepWidth () const { + return m_iSepWidth; + } + + void CListHeaderItemUI::SetSepWidth (int iWidth) { + m_iSepWidth = iWidth; + } + + DWORD CListHeaderItemUI::GetTextStyle () const { + return m_uTextStyle; + } + + void CListHeaderItemUI::SetTextStyle (UINT uStyle) { + m_uTextStyle = uStyle; + Invalidate (); + } + + DWORD CListHeaderItemUI::GetTextColor () const { + return m_dwTextColor; + } + + + void CListHeaderItemUI::SetTextColor (DWORD dwTextColor) { + m_dwTextColor = dwTextColor; + } + + RECT CListHeaderItemUI::GetTextPadding () const { + return m_rcTextPadding; + } + + void CListHeaderItemUI::SetTextPadding (RECT rc) { + m_rcTextPadding = rc; + Invalidate (); + } + + void CListHeaderItemUI::SetFont (int index) { + m_iFont = index; + } + + bool CListHeaderItemUI::IsShowHtml () { + return m_bShowHtml; + } + + void CListHeaderItemUI::SetShowHtml (bool bShowHtml) { + if (m_bShowHtml == bShowHtml) return; + + m_bShowHtml = bShowHtml; + Invalidate (); + } + + string_view_t CListHeaderItemUI::GetNormalImage () const { + return m_sNormalImage; + } + + void CListHeaderItemUI::SetNormalImage (string_view_t pStrImage) { + m_sNormalImage = pStrImage; + Invalidate (); + } + + string_view_t CListHeaderItemUI::GetHotImage () const { + return m_sHotImage; + } + + void CListHeaderItemUI::SetHotImage (string_view_t pStrImage) { + m_sHotImage = pStrImage; + Invalidate (); + } + + string_view_t CListHeaderItemUI::GetPushedImage () const { + return m_sPushedImage; + } + + void CListHeaderItemUI::SetPushedImage (string_view_t pStrImage) { + m_sPushedImage = pStrImage; + Invalidate (); + } + + string_view_t CListHeaderItemUI::GetFocusedImage () const { + return m_sFocusedImage; + } + + void CListHeaderItemUI::SetFocusedImage (string_view_t pStrImage) { + m_sFocusedImage = pStrImage; + Invalidate (); + } + + string_view_t CListHeaderItemUI::GetSepImage () const { + return m_sSepImage; + } + + void CListHeaderItemUI::SetSepImage (string_view_t pStrImage) { + m_sSepImage = pStrImage; + Invalidate (); + } + + void CListHeaderItemUI::SetScale (int nScale) { + m_nScale = nScale; + } + + int CListHeaderItemUI::GetScale () const { + return m_nScale; + } + + void CListHeaderItemUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("dragable")) SetDragable (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("sepwidth")) SetSepWidth (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("align")) { + if (pstrValue.find (_T ("left")) != string_t::npos) { + m_uTextStyle &= ~(DT_CENTER | DT_RIGHT); + m_uTextStyle |= DT_LEFT; + } + if (pstrValue.find (_T ("center")) != string_t::npos) { + m_uTextStyle &= ~(DT_LEFT | DT_RIGHT); + m_uTextStyle |= DT_CENTER; + } + if (pstrValue.find (_T ("right")) != string_t::npos) { + m_uTextStyle &= ~(DT_LEFT | DT_CENTER); + m_uTextStyle |= DT_RIGHT; + } + } else if (pstrName == _T ("endellipsis")) { + if (FawTools::parse_bool (pstrValue)) m_uTextStyle |= DT_END_ELLIPSIS; + else m_uTextStyle &= ~DT_END_ELLIPSIS; + } else if (pstrName == _T ("font")) SetFont ((int) FawTools::parse_hex (pstrValue)); + else if (pstrName == _T ("textcolor")) { + SetTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("textpadding")) { + RECT rcTextPadding = FawTools::parse_rect (pstrValue); + SetTextPadding (rcTextPadding); + } else if (pstrName == _T ("showhtml")) SetShowHtml (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("normalimage")) SetNormalImage (pstrValue); + else if (pstrName == _T ("hotimage")) SetHotImage (pstrValue); + else if (pstrName == _T ("pushedimage")) SetPushedImage (pstrValue); + else if (pstrName == _T ("focusedimage")) SetFocusedImage (pstrValue); + else if (pstrName == _T ("sepimage")) SetSepImage (pstrValue); + else if (pstrName == _T ("scale")) { + SetScale (FawTools::parse_dec (pstrValue)); + + } else CContainerUI::SetAttribute (pstrName, pstrValue); + } + + void CListHeaderItemUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CContainerUI::DoEvent (event); + return; + } + + if (event.Type == UIEVENT_SETFOCUS) { + Invalidate (); + } + if (event.Type == UIEVENT_KILLFOCUS) { + Invalidate (); + } + if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK) { + if (!IsEnabled ()) return; + RECT rcSeparator = GetThumbRect (); + if (m_iSepWidth >= 0) + rcSeparator.left -= 4; + else + rcSeparator.right += 4; + if (::PtInRect (&rcSeparator, event.ptMouse)) { + if (m_bDragable) { + m_uButtonState |= UISTATE_CAPTURED; + ptLastMouse = event.ptMouse; + } + } else { + m_uButtonState |= UISTATE_PUSHED; + m_pManager->SendNotify (this, DUI_MSGTYPE_HEADERCLICK); + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_BUTTONUP) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + m_uButtonState &= ~UISTATE_CAPTURED; + if (GetParent ()) + GetParent ()->NeedParentUpdate (); + } else if ((m_uButtonState & UISTATE_PUSHED) != 0) { + m_uButtonState &= ~UISTATE_PUSHED; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_MOUSEMOVE) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + RECT rc = m_rcItem; + if (m_iSepWidth >= 0) { + rc.right -= ptLastMouse.x - event.ptMouse.x; + } else { + rc.left -= ptLastMouse.x - event.ptMouse.x; + } + + if (rc.right - rc.left > GetMinWidth ()) { + m_cxyFixed.cx = rc.right - rc.left; + ptLastMouse = event.ptMouse; + if (GetParent ()) + GetParent ()->NeedParentUpdate (); + } + } + return; + } + if (event.Type == UIEVENT_SETCURSOR) { + RECT rcSeparator = GetThumbRect (); + if (m_iSepWidth >= 0) + rcSeparator.left -= 4; + else + rcSeparator.right += 4; + if (IsEnabled () && m_bDragable && ::PtInRect (&rcSeparator, event.ptMouse)) { + ::SetCursor (::LoadCursor (NULL, IDC_SIZEWE)); + return; + } + } + if (event.Type == UIEVENT_MOUSEENTER) { + if (IsEnabled ()) { + m_uButtonState |= UISTATE_HOT; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_MOUSELEAVE) { + if (IsEnabled ()) { + m_uButtonState &= ~UISTATE_HOT; + Invalidate (); + } + return; + } + CContainerUI::DoEvent (event); + } + + SIZE CListHeaderItemUI::EstimateSize (SIZE szAvailable) { + if (m_cxyFixed.cy == 0) return { m_cxyFixed.cx, m_pManager->GetDefaultFontInfo ()->tm.tmHeight + 14 }; + return CContainerUI::EstimateSize (szAvailable); + } + + RECT CListHeaderItemUI::GetThumbRect () const { + if (m_iSepWidth >= 0) return { m_rcItem.right - m_iSepWidth, m_rcItem.top, m_rcItem.right, m_rcItem.bottom }; + else return { m_rcItem.left, m_rcItem.top, m_rcItem.left - m_iSepWidth, m_rcItem.bottom }; + } + + void CListHeaderItemUI::PaintStatusImage (HDC hDC) { + if (IsFocused ()) m_uButtonState |= UISTATE_FOCUSED; + else m_uButtonState &= ~UISTATE_FOCUSED; + + if ((m_uButtonState & UISTATE_PUSHED) != 0) { + if (m_sPushedImage.empty () && !m_sNormalImage.empty ()) DrawImage (hDC, m_sNormalImage); + if (!DrawImage (hDC, m_sPushedImage)) { + } + } else if ((m_uButtonState & UISTATE_HOT) != 0) { + if (m_sHotImage.empty () && !m_sNormalImage.empty ()) DrawImage (hDC, m_sNormalImage); + if (!DrawImage (hDC, m_sHotImage)) { + } + } else if ((m_uButtonState & UISTATE_FOCUSED) != 0) { + if (m_sFocusedImage.empty () && !m_sNormalImage.empty ()) DrawImage (hDC, m_sNormalImage); + if (!DrawImage (hDC, m_sFocusedImage)) { + } + } else { + if (!m_sNormalImage.empty ()) { + if (!DrawImage (hDC, m_sNormalImage)) { + } + } + } + + if (!m_sSepImage.empty ()) { + RECT rcThumb = GetThumbRect (); + rcThumb.left -= m_rcItem.left; + rcThumb.top -= m_rcItem.top; + rcThumb.right -= m_rcItem.left; + rcThumb.bottom -= m_rcItem.top; + + m_sSepImageModify.clear (); + m_sSepImageModify.Format (_T ("dest='%d,%d,%d,%d'"), rcThumb.left, rcThumb.top, rcThumb.right, rcThumb.bottom); + if (!DrawImage (hDC, m_sSepImage, m_sSepImageModify)) { + } + } + } + + void CListHeaderItemUI::PaintText (HDC hDC) { + if (m_dwTextColor == 0) m_dwTextColor = m_pManager->GetDefaultFontColor (); + + RECT rcText = m_rcItem; + rcText.left += m_rcTextPadding.left; + rcText.top += m_rcTextPadding.top; + rcText.right -= m_rcTextPadding.right; + rcText.bottom -= m_rcTextPadding.bottom; + + CDuiString sText = GetText (); + if (sText.empty ()) return; + int nLinks = 0; + if (m_bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rcText, sText, m_dwTextColor, nullptr, nullptr, nLinks, m_iFont, m_uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rcText, sText, m_dwTextColor, m_iFont, m_uTextStyle); + } + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + CListElementUI::CListElementUI (): m_iIndex (-1), + m_pOwner (nullptr), + m_bSelected (false), + m_uButtonState (0) {} + + string_view_t CListElementUI::GetClass () const { + return _T ("ListElementUI"); + } + + UINT CListElementUI::GetControlFlags () const { + return UIFLAG_WANTRETURN; + } + + LPVOID CListElementUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_LISTITEM) return static_cast(this); + if (pstrName == DUI_CTRL_LISTELEMENT) return static_cast(this); + return CControlUI::GetInterface (pstrName); + } + + IListOwnerUI* CListElementUI::GetOwner () { + return m_pOwner; + } + + void CListElementUI::SetOwner (CControlUI* pOwner) { + m_pOwner = static_cast(pOwner->GetInterface (_T ("IListOwner"))); + } + + void CListElementUI::SetVisible (bool bVisible) { + CControlUI::SetVisible (bVisible); + if (!IsVisible () && m_bSelected) { + m_bSelected = false; + if (m_pOwner) m_pOwner->SelectItem (-1); + } + } + + void CListElementUI::SetEnabled (bool bEnable) { + CControlUI::SetEnabled (bEnable); + if (!IsEnabled ()) { + m_uButtonState = 0; + } + } + + int CListElementUI::GetIndex () const { + return m_iIndex; + } + + void CListElementUI::SetIndex (int iIndex) { + m_iIndex = iIndex; + } + + void CListElementUI::Invalidate () { + if (!IsVisible ()) return; + + if (GetParent ()) { + CContainerUI* pParentContainer = static_cast(GetParent ()->GetInterface (_T ("Container"))); + if (pParentContainer) { + RECT rc = pParentContainer->GetPos (); + RECT rcInset = pParentContainer->GetInset (); + rc.left += rcInset.left; + rc.top += rcInset.top; + rc.right -= rcInset.right; + rc.bottom -= rcInset.bottom; + CScrollBarUI* pVerticalScrollBar = pParentContainer->GetVerticalScrollBar (); + if (pVerticalScrollBar && pVerticalScrollBar->IsVisible ()) rc.right -= pVerticalScrollBar->GetFixedWidth (); + CScrollBarUI* pHorizontalScrollBar = pParentContainer->GetHorizontalScrollBar (); + if (pHorizontalScrollBar && pHorizontalScrollBar->IsVisible ()) rc.bottom -= pHorizontalScrollBar->GetFixedHeight (); + + RECT invalidateRc = m_rcItem; + if (!::IntersectRect (&invalidateRc, &m_rcItem, &rc)) { + return; + } + + CControlUI* pParent = GetParent (); + RECT rcTemp = { 0 }; + RECT rcParent = { 0 }; + while (!!(pParent = pParent->GetParent ())) { + rcTemp = invalidateRc; + rcParent = pParent->GetPos (); + if (!::IntersectRect (&invalidateRc, &rcTemp, &rcParent)) { + return; + } + } + + if (m_pManager) m_pManager->Invalidate (invalidateRc); + } else { + CControlUI::Invalidate (); + } + } else { + CControlUI::Invalidate (); + } + } + + bool CListElementUI::Activate () { + if (!CControlUI::Activate ()) return false; + if (m_pManager) m_pManager->SendNotify (this, DUI_MSGTYPE_ITEMACTIVATE); + return true; + } + + bool CListElementUI::IsSelected () const { + return m_bSelected; + } + + bool CListElementUI::Select (bool bSelect) { + if (!IsEnabled ()) return false; + if (m_pOwner && m_bSelected) m_pOwner->UnSelectItem (m_iIndex, true); + if (bSelect == m_bSelected) return true; + m_bSelected = bSelect; + if (bSelect && m_pOwner) m_pOwner->SelectItem (m_iIndex); + Invalidate (); + + return true; + } + + bool CListElementUI::SelectMulti (bool bSelect) { + if (!IsEnabled ()) return false; + if (bSelect == m_bSelected) return true; + + m_bSelected = bSelect; + if (bSelect && m_pOwner) m_pOwner->SelectMultiItem (m_iIndex); + Invalidate (); + return true; + } + + bool CListElementUI::IsExpanded () const { + return false; + } + + bool CListElementUI::Expand (bool /*bExpand = true*/) { + return false; + } + + void CListElementUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pOwner) m_pOwner->DoEvent (event); + else CControlUI::DoEvent (event); + return; + } + + if (event.Type == UIEVENT_DBLCLICK) { + if (IsEnabled ()) { + Activate (); + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_KEYDOWN && IsEnabled ()) { + if (event.chKey == VK_RETURN) { + Activate (); + Invalidate (); + return; + } + } + // An important twist: The list-item will send the event not to its immediate + // parent but to the "attached" list. A list may actually embed several components + // in its path to the item, but key-presses etc. needs to go to the actual list. + if (m_pOwner) m_pOwner->DoEvent (event); else CControlUI::DoEvent (event); + } + + void CListElementUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("selected")) Select (); + else CControlUI::SetAttribute (pstrName, pstrValue); + } + + void CListElementUI::DrawItemBk (HDC hDC, const RECT& rcItem) { + ASSERT (m_pOwner); + if (!m_pOwner) return; + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + DWORD iBackColor = 0; + if (!pInfo->bAlternateBk || m_iIndex % 2 == 0) iBackColor = pInfo->dwBkColor; + if ((m_uButtonState & UISTATE_HOT) != 0 && pInfo->dwHotBkColor > 0) { + iBackColor = pInfo->dwHotBkColor; + } + if (IsSelected () && pInfo->dwSelectedBkColor > 0) { + iBackColor = pInfo->dwSelectedBkColor; + } + if (!IsEnabled () && pInfo->dwDisabledBkColor > 0) { + iBackColor = pInfo->dwDisabledBkColor; + } + + if (iBackColor != 0) { + CRenderEngine::DrawColor (hDC, m_rcItem, GetAdjustColor (iBackColor)); + } + + if (!IsEnabled ()) { + if (!pInfo->sDisabledImage.empty ()) { + if (!DrawImage (hDC, pInfo->sDisabledImage)) { + } else return; + } + } + if (IsSelected ()) { + if (!pInfo->sSelectedImage.empty ()) { + if (!DrawImage (hDC, pInfo->sSelectedImage)) { + } else return; + } + } + if ((m_uButtonState & UISTATE_HOT) != 0) { + if (!pInfo->sHotImage.empty ()) { + if (!DrawImage (hDC, pInfo->sHotImage)) { + } else return; + } + } + + if (!m_sBkImage.empty ()) { + if (!pInfo->bAlternateBk || m_iIndex % 2 == 0) { + if (!DrawImage (hDC, m_sBkImage)) { + } + } + } + + if (m_sBkImage.empty ()) { + if (!pInfo->sBkImage.empty ()) { + if (!DrawImage (hDC, pInfo->sBkImage)) { + } else return; + } + } + + if (pInfo->dwLineColor != 0) { + if (pInfo->bShowRowLine) { + RECT rcLine = { m_rcItem.left, m_rcItem.bottom - 1, m_rcItem.right, m_rcItem.bottom - 1 }; + CRenderEngine::DrawLine (hDC, rcLine, 1, GetAdjustColor (pInfo->dwLineColor)); + } + if (pInfo->bShowColumnLine) { + for (int i = 0; i < pInfo->nColumns; i++) { + RECT rcLine = { pInfo->rcColumn[i].right - 1, m_rcItem.top, pInfo->rcColumn[i].right - 1, m_rcItem.bottom }; + CRenderEngine::DrawLine (hDC, rcLine, 1, GetAdjustColor (pInfo->dwLineColor)); + } + } + } + } + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CListLabelElementUI) + + CListLabelElementUI::CListLabelElementUI () { SetMaxHeight (20); } + CListLabelElementUI::CListLabelElementUI (string_view_t text, int height) { + SetText (text); + SetMaxHeight (height); + } + + string_view_t CListLabelElementUI::GetClass () const { + return _T ("ListLabelElementUI"); + } + + LPVOID CListLabelElementUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_LISTLABELELEMENT) return static_cast(this); + return CListElementUI::GetInterface (pstrName); + } + + void CListLabelElementUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pOwner) m_pOwner->DoEvent (event); + else CListElementUI::DoEvent (event); + return; + } + + // Ҽѡ + if (m_pOwner) { + if (m_pOwner->GetListInfo ()->bRSelected && event.Type == UIEVENT_RBUTTONDOWN) { + if (IsEnabled ()) { + if ((GetKeyState (VK_CONTROL) & 0x8000)) { + SelectMulti (!IsSelected ()); + } else Select (); + } + return; + } + } + + if (event.Type == UIEVENT_BUTTONDOWN) { + if (IsEnabled ()) { + if ((GetKeyState (VK_CONTROL) & 0x8000)) { + SelectMulti (!IsSelected ()); + } else { + Select (); + } + } + return; + } + if (event.Type == UIEVENT_BUTTONUP) { + if (IsEnabled ()) { + m_pManager->SendNotify (this, DUI_MSGTYPE_ITEMCLICK); + } + return; + } + + if (event.Type == UIEVENT_MOUSEMOVE) { + return; + } + + if (event.Type == UIEVENT_MOUSEENTER) { + if (IsEnabled ()) { + m_uButtonState |= UISTATE_HOT; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_MOUSELEAVE) { + if ((m_uButtonState & UISTATE_HOT) != 0) { + m_uButtonState &= ~UISTATE_HOT; + Invalidate (); + } + return; + } + CListElementUI::DoEvent (event); + } + + SIZE CListLabelElementUI::EstimateSize (SIZE szAvailable) { + if (!m_pOwner) return { 0, 0 }; + CDuiString sText = GetText (); + + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + SIZE cXY = m_cxyFixed; + if (cXY.cy == 0 && m_pManager) { + cXY.cy = m_pManager->GetFontInfo (pInfo->nFont)->tm.tmHeight + 8; + cXY.cy += pInfo->rcTextPadding.top + pInfo->rcTextPadding.bottom; + } + + if (cXY.cx == 0 && m_pManager) { + RECT rcText = { 0, 0, 9999, cXY.cy }; + if (pInfo->bShowHtml) { + int nLinks = 0; + CRenderEngine::DrawHtmlText (m_pManager->GetPaintDC (), m_pManager, rcText, sText, 0, nullptr, nullptr, nLinks, pInfo->nFont, DT_SINGLELINE | DT_CALCRECT | pInfo->uTextStyle & ~DT_RIGHT & ~DT_CENTER); + } else { + CRenderEngine::DrawText (m_pManager->GetPaintDC (), m_pManager, rcText, sText, 0, pInfo->nFont, DT_SINGLELINE | DT_CALCRECT | pInfo->uTextStyle & ~DT_RIGHT & ~DT_CENTER); + } + cXY.cx = rcText.right - rcText.left + pInfo->rcTextPadding.left + pInfo->rcTextPadding.right; + } + + return cXY; + } + + bool CListLabelElementUI::DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + DrawItemBk (hDC, m_rcItem); + DrawItemText (hDC, m_rcItem); + return true; + } + + void CListLabelElementUI::DrawItemText (HDC hDC, const RECT& rcItem) { + CDuiString sText = GetText (); + if (sText.empty ()) return; + + if (!m_pOwner) return; + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + DWORD iTextColor = pInfo->dwTextColor; + if ((m_uButtonState & UISTATE_HOT) != 0) { + iTextColor = pInfo->dwHotTextColor; + } + if (IsSelected ()) { + iTextColor = pInfo->dwSelectedTextColor; + } + if (!IsEnabled ()) { + iTextColor = pInfo->dwDisabledTextColor; + } + int nLinks = 0; + RECT rcText = rcItem; + rcText.left += pInfo->rcTextPadding.left; + rcText.right -= pInfo->rcTextPadding.right; + rcText.top += pInfo->rcTextPadding.top; + rcText.bottom -= pInfo->rcTextPadding.bottom; + + if (pInfo->bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rcText, sText, iTextColor, nullptr, nullptr, nLinks, pInfo->nFont, pInfo->uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rcText, sText, iTextColor, pInfo->nFont, pInfo->uTextStyle); + } + + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CListTextElementUI) + + CListTextElementUI::CListTextElementUI (): m_nLinks (0), m_nHoverLink (-1), m_pOwner (nullptr) { + ::ZeroMemory (&m_rcLinks, sizeof (m_rcLinks)); + } + + CListTextElementUI::~CListTextElementUI () { + CDuiString* pText; + for (int it = 0; it < m_aTexts.GetSize (); it++) { + pText = static_cast(m_aTexts[it]); + if (pText) delete pText; + } + m_aTexts.Empty (); + } + + string_view_t CListTextElementUI::GetClass () const { + return _T ("ListTextElementUI"); + } + + LPVOID CListTextElementUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_LISTTEXTELEMENT) return static_cast(this); + return CListLabelElementUI::GetInterface (pstrName); + } + + UINT CListTextElementUI::GetControlFlags () const { + return UIFLAG_WANTRETURN | ((IsEnabled () && m_nLinks > 0) ? UIFLAG_SETCURSOR : 0); + } + + CDuiString CListTextElementUI::GetText (int iIndex) const { + CDuiString* pText = static_cast(m_aTexts.GetAt (iIndex)); + if (pText) { + if (!IsResourceText ()) + return pText->c_str (); + return CResourceManager::GetInstance ()->GetText (*pText); + } + return _T (""); + } + + void CListTextElementUI::SetText (int iIndex, string_view_t pstrText) { + if (!m_pOwner) return; + + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + if (iIndex < 0 || iIndex >= pInfo->nColumns) return; + while (m_aTexts.GetSize () < pInfo->nColumns) { + m_aTexts.Add (nullptr); + } + + CDuiString* pText = static_cast(m_aTexts[iIndex]); + if ((!pText && pstrText.empty ()) || (pText && *pText == pstrText)) return; + + if (pText) { + delete pText; pText = nullptr; + } + m_aTexts.SetAt (iIndex, new CDuiString (pstrText)); + + Invalidate (); + } + + void CListTextElementUI::SetOwner (CControlUI* pOwner) { + CListElementUI::SetOwner (pOwner); + m_pOwner = static_cast(pOwner->GetInterface (_T ("IList"))); + } + + CDuiString* CListTextElementUI::GetLinkContent (int iIndex) { + if (iIndex >= 0 && iIndex < m_nLinks) return &m_sLinks[iIndex]; + return nullptr; + } + + void CListTextElementUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pOwner) m_pOwner->DoEvent (event); + else CListLabelElementUI::DoEvent (event); + return; + } + + // When you hover over a link + if (event.Type == UIEVENT_SETCURSOR) { + for (int i = 0; i < m_nLinks; i++) { + if (::PtInRect (&m_rcLinks[i], event.ptMouse)) { + ::SetCursor (::LoadCursor (NULL, IDC_HAND)); + return; + } + } + } + if (event.Type == UIEVENT_BUTTONUP && IsEnabled ()) { + for (int i = 0; i < m_nLinks; i++) { + if (::PtInRect (&m_rcLinks[i], event.ptMouse)) { + m_pManager->SendNotify (this, DUI_MSGTYPE_LINK, i); + return; + } + } + } + if (m_nLinks > 0 && event.Type == UIEVENT_MOUSEMOVE) { + int nHoverLink = -1; + for (int i = 0; i < m_nLinks; i++) { + if (::PtInRect (&m_rcLinks[i], event.ptMouse)) { + nHoverLink = i; + break; + } + } + + if (m_nHoverLink != nHoverLink) { + Invalidate (); + m_nHoverLink = nHoverLink; + } + } + if (m_nLinks > 0 && event.Type == UIEVENT_MOUSELEAVE) { + if (m_nHoverLink != -1) { + Invalidate (); + m_nHoverLink = -1; + } + } + CListLabelElementUI::DoEvent (event); + } + + SIZE CListTextElementUI::EstimateSize (SIZE szAvailable) { + TListInfoUI* pInfo = nullptr; + if (m_pOwner) pInfo = m_pOwner->GetListInfo (); + + SIZE cXY = m_cxyFixed; + if (cXY.cy == 0 && m_pManager) { + cXY.cy = m_pManager->GetFontInfo (pInfo->nFont)->tm.tmHeight + 8; + if (pInfo) cXY.cy += pInfo->rcTextPadding.top + pInfo->rcTextPadding.bottom; + } + + return cXY; + } + + void CListTextElementUI::DrawItemText (HDC hDC, const RECT& /*rcItem*/) { + if (!m_pOwner) return; + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + DWORD iTextColor = pInfo->dwTextColor; + + if ((m_uButtonState & UISTATE_HOT) != 0) { + iTextColor = pInfo->dwHotTextColor; + } + if (IsSelected ()) { + iTextColor = pInfo->dwSelectedTextColor; + } + if (!IsEnabled ()) { + iTextColor = pInfo->dwDisabledTextColor; + } + IListCallbackUI* pCallback = m_pOwner->GetTextCallback (); + + m_nLinks = 0; + int nLinks = lengthof (m_rcLinks); + for (int i = 0; i < pInfo->nColumns; i++) { + RECT rcItem = { pInfo->rcColumn[i].left, m_rcItem.top, pInfo->rcColumn[i].right, m_rcItem.bottom }; + rcItem.left += pInfo->rcTextPadding.left; + rcItem.right -= pInfo->rcTextPadding.right; + rcItem.top += pInfo->rcTextPadding.top; + rcItem.bottom -= pInfo->rcTextPadding.bottom; + + CDuiString strText; + if (pCallback) + strText = pCallback->GetItemText (this, m_iIndex, i); + else + strText = GetText (i); + + if (pInfo->bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rcItem, strText, iTextColor, &m_rcLinks[m_nLinks], &m_sLinks[m_nLinks], nLinks, pInfo->nFont, pInfo->uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rcItem, strText, iTextColor, pInfo->nFont, pInfo->uTextStyle); + + m_nLinks += nLinks; + nLinks = lengthof (m_rcLinks) - m_nLinks; + } + for (int i = m_nLinks; i < lengthof (m_rcLinks); i++) { + ::ZeroMemory (m_rcLinks + i, sizeof (RECT)); + ((CDuiString*) (m_sLinks + i))->clear (); + } + } + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CListContainerElementUI) + + CListContainerElementUI::CListContainerElementUI (): + m_iIndex (-1), + m_pOwner (nullptr), + m_bSelected (false), + m_uButtonState (0) {} + + string_view_t CListContainerElementUI::GetClass () const { + return _T ("ListContainerElementUI"); + } + + UINT CListContainerElementUI::GetControlFlags () const { + return UIFLAG_WANTRETURN; + } + + LPVOID CListContainerElementUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_LISTITEM) return static_cast(this); + if (pstrName == DUI_CTRL_LISTCONTAINERELEMENT) return static_cast(this); + return CContainerUI::GetInterface (pstrName); + } + + IListOwnerUI* CListContainerElementUI::GetOwner () { + return m_pOwner; + } + + void CListContainerElementUI::SetOwner (CControlUI* pOwner) { + m_pOwner = static_cast(pOwner->GetInterface (_T ("IListOwner"))); + } + + void CListContainerElementUI::SetVisible (bool bVisible) { + CContainerUI::SetVisible (bVisible); + if (!IsVisible () && m_bSelected) { + m_bSelected = false; + if (m_pOwner) m_pOwner->SelectItem (-1); + } + } + + void CListContainerElementUI::SetEnabled (bool bEnable) { + CControlUI::SetEnabled (bEnable); + if (!IsEnabled ()) { + m_uButtonState = 0; + } + } + + int CListContainerElementUI::GetIndex () const { + return m_iIndex; + } + + void CListContainerElementUI::SetIndex (int iIndex) { + m_iIndex = iIndex; + } + + void CListContainerElementUI::Invalidate () { + if (!IsVisible ()) return; + + if (GetParent ()) { + CContainerUI* pParentContainer = static_cast(GetParent ()->GetInterface (_T ("Container"))); + if (pParentContainer) { + RECT rc = pParentContainer->GetPos (); + RECT rcInset = pParentContainer->GetInset (); + rc.left += rcInset.left; + rc.top += rcInset.top; + rc.right -= rcInset.right; + rc.bottom -= rcInset.bottom; + CScrollBarUI* pVerticalScrollBar = pParentContainer->GetVerticalScrollBar (); + if (pVerticalScrollBar && pVerticalScrollBar->IsVisible ()) rc.right -= pVerticalScrollBar->GetFixedWidth (); + CScrollBarUI* pHorizontalScrollBar = pParentContainer->GetHorizontalScrollBar (); + if (pHorizontalScrollBar && pHorizontalScrollBar->IsVisible ()) rc.bottom -= pHorizontalScrollBar->GetFixedHeight (); + + RECT invalidateRc = m_rcItem; + if (!::IntersectRect (&invalidateRc, &m_rcItem, &rc)) { + return; + } + + CControlUI* pParent = GetParent (); + RECT rcTemp = { 0 }; + RECT rcParent = { 0 }; + while (!!(pParent = pParent->GetParent ())) { + rcTemp = invalidateRc; + rcParent = pParent->GetPos (); + if (!::IntersectRect (&invalidateRc, &rcTemp, &rcParent)) { + return; + } + } + + if (m_pManager) m_pManager->Invalidate (invalidateRc); + } else { + CContainerUI::Invalidate (); + } + } else { + CContainerUI::Invalidate (); + } + } + + bool CListContainerElementUI::Activate () { + if (!CContainerUI::Activate ()) return false; + if (m_pManager) m_pManager->SendNotify (this, DUI_MSGTYPE_ITEMACTIVATE); + return true; + } + + bool CListContainerElementUI::IsSelected () const { + return m_bSelected; + } + + bool CListContainerElementUI::Select (bool bSelect) { + if (!IsEnabled ()) return false; + if (m_pOwner && m_bSelected) m_pOwner->UnSelectItem (m_iIndex, true); + if (bSelect == m_bSelected) return true; + m_bSelected = bSelect; + if (bSelect && m_pOwner) m_pOwner->SelectItem (m_iIndex); + Invalidate (); + + return true; + } + + bool CListContainerElementUI::SelectMulti (bool bSelect) { + if (!IsEnabled ()) return false; + if (bSelect == m_bSelected) return true; + + m_bSelected = bSelect; + if (bSelect && m_pOwner) m_pOwner->SelectMultiItem (m_iIndex); + Invalidate (); + return true; + } + + bool CListContainerElementUI::IsExpanded () const { + return false; + } + + bool CListContainerElementUI::Expand (bool /*bExpand = true*/) { + return false; + } + + void CListContainerElementUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pOwner) m_pOwner->DoEvent (event); + else CContainerUI::DoEvent (event); + return; + } + + if (event.Type == UIEVENT_DBLCLICK) { + if (IsEnabled ()) { + Activate (); + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_KEYDOWN && IsEnabled ()) { + if (event.chKey == VK_RETURN) { + Activate (); + Invalidate (); + return; + } + } + if (event.Type == UIEVENT_BUTTONDOWN) { + if (IsEnabled ()) { + if ((GetKeyState (VK_CONTROL) & 0x8000)) { + SelectMulti (!IsSelected ()); + } else Select (); + } + return; + } + // Ҽѡ + if (m_pOwner) { + if (m_pOwner->GetListInfo ()->bRSelected && event.Type == UIEVENT_RBUTTONDOWN) { + if (IsEnabled ()) { + if ((GetKeyState (VK_CONTROL) & 0x8000)) { + SelectMulti (!IsSelected ()); + } else Select (); + } + return; + } + } + + if (event.Type == UIEVENT_BUTTONUP) { + if (IsEnabled ()) { + m_pManager->SendNotify (this, DUI_MSGTYPE_ITEMCLICK); + } + return; + } + if (event.Type == UIEVENT_MOUSEMOVE) { + return; + } + if (event.Type == UIEVENT_MOUSEENTER) { + if (IsEnabled ()) { + m_uButtonState |= UISTATE_HOT; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_MOUSELEAVE) { + if ((m_uButtonState & UISTATE_HOT) != 0) { + m_uButtonState &= ~UISTATE_HOT; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_TIMER) { + m_pManager->SendNotify (this, DUI_MSGTYPE_TIMER, event.wParam, event.lParam); + return; + } + + if (event.Type == UIEVENT_CONTEXTMENU) { + if (IsContextMenuUsed ()) { + m_pManager->SendNotify (this, DUI_MSGTYPE_MENU, event.wParam, event.lParam); + return; + } + } + // An important twist: The list-item will send the event not to its immediate + // parent but to the "attached" list. A list may actually embed several components + // in its path to the item, but key-presses etc. needs to go to the actual list. + if (m_pOwner) m_pOwner->DoEvent (event); else CControlUI::DoEvent (event); + } + + void CListContainerElementUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("selected")) Select (); + //else if (pstrName == _T("expandable")) SetExpandable (FawTools::parse_bool (pstrValue)); + else CContainerUI::SetAttribute (pstrName, pstrValue); + } + + bool CListContainerElementUI::DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + DrawItemBk (hDC, m_rcItem); + return CContainerUI::DoPaint (hDC, rcPaint, pStopControl); + } + + void CListContainerElementUI::DrawItemText (HDC hDC, const RECT& rcItem) { + return; + } + + void CListContainerElementUI::DrawItemBk (HDC hDC, const RECT& rcItem) { + ASSERT (m_pOwner); + if (!m_pOwner) return; + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + DWORD iBackColor = 0; + if (!pInfo->bAlternateBk || m_iIndex % 2 == 0) iBackColor = pInfo->dwBkColor; + + if ((m_uButtonState & UISTATE_HOT) != 0 && pInfo->dwHotBkColor > 0) { + iBackColor = pInfo->dwHotBkColor; + } + if (IsSelected () && pInfo->dwSelectedBkColor > 0) { + iBackColor = pInfo->dwSelectedBkColor; + } + if (!IsEnabled () && pInfo->dwDisabledBkColor > 0) { + iBackColor = pInfo->dwDisabledBkColor; + } + if (iBackColor != 0) { + CRenderEngine::DrawColor (hDC, m_rcItem, GetAdjustColor (iBackColor)); + } + + if (!IsEnabled ()) { + if (!pInfo->sDisabledImage.empty ()) { + if (!DrawImage (hDC, pInfo->sDisabledImage)) { + } else return; + } + } + if (IsSelected ()) { + if (!pInfo->sSelectedImage.empty ()) { + if (!DrawImage (hDC, pInfo->sSelectedImage)) { + } else return; + } + } + if ((m_uButtonState & UISTATE_HOT) != 0) { + if (!pInfo->sHotImage.empty ()) { + if (!DrawImage (hDC, pInfo->sHotImage)) { + } else return; + } + } + if (!m_sBkImage.empty ()) { + if (!pInfo->bAlternateBk || m_iIndex % 2 == 0) { + if (!DrawImage (hDC, m_sBkImage)) { + } + } + } + + if (m_sBkImage.empty ()) { + if (!pInfo->sBkImage.empty ()) { + if (!DrawImage (hDC, pInfo->sBkImage)) { + } else return; + } + } + + if (pInfo->dwLineColor != 0) { + if (pInfo->bShowRowLine) { + RECT rcLine = { m_rcItem.left, m_rcItem.bottom - 1, m_rcItem.right, m_rcItem.bottom - 1 }; + CRenderEngine::DrawLine (hDC, rcLine, 1, GetAdjustColor (pInfo->dwLineColor)); + } + if (pInfo->bShowColumnLine) { + for (int i = 0; i < pInfo->nColumns; i++) { + RECT rcLine = { pInfo->rcColumn[i].right - 1, m_rcItem.top, pInfo->rcColumn[i].right - 1, m_rcItem.bottom }; + CRenderEngine::DrawLine (hDC, rcLine, 1, GetAdjustColor (pInfo->dwLineColor)); + } + } + } + } + + void CListContainerElementUI::SetPos (RECT rc, bool bNeedInvalidate) { + if (!m_pOwner) return; + + UINT uListType = m_pOwner->GetListType (); + if (uListType == LT_LIST) { + int nFixedWidth = GetFixedWidth (); + if (nFixedWidth > 0) { + int nRank = (rc.right - rc.left) / nFixedWidth; + if (nRank > 0) { + int nIndex = GetIndex (); + int nfloor = nIndex / nRank; + int nHeight = rc.bottom - rc.top; + + rc.top = rc.top - nHeight * (nIndex - nfloor); + rc.left = rc.left + nFixedWidth * (nIndex % nRank); + rc.right = rc.left + nFixedWidth; + rc.bottom = nHeight + rc.top; + } + } + } + CHorizontalLayoutUI::SetPos (rc, bNeedInvalidate); + + if (uListType != LT_LIST && uListType != LT_TREE) return; + CListUI* pList = static_cast(m_pOwner); + if (uListType == LT_TREE) { + pList = (CListUI*) pList->CControlUI::GetInterface (_T ("List")); + if (!pList) return; + } + + CListHeaderUI *pHeader = pList->GetHeader (); + if (!pHeader || !pHeader->IsVisible ()) return; + int nCount = m_items.GetSize (); + for (int i = 0; i < nCount; i++) { + CControlUI *pListItem = static_cast(m_items[i]); + CControlUI *pHeaderItem = pHeader->GetItemAt (i); + if (!pHeaderItem) return; + RECT rcHeaderItem = pHeaderItem->GetPos (); + if (pListItem && !(rcHeaderItem.left == 0 && rcHeaderItem.right == 0)) { + RECT rt = pListItem->GetPos (); + rt.left = rcHeaderItem.left; + rt.right = rcHeaderItem.right; + pListItem->SetPos (rt); + } + } + } +} // namespace DuiLib diff --git a/DuiLib/Control/UIList.h b/DuiLib/Control/UIList.h new file mode 100644 index 0000000..41e88b3 --- /dev/null +++ b/DuiLib/Control/UIList.h @@ -0,0 +1,514 @@ +#ifndef __UILIST_H__ +#define __UILIST_H__ + +namespace DuiLib { + ///////////////////////////////////////////////////////////////////////////////////// + // + + typedef int (CALLBACK *PULVCompareFunc)(UINT_PTR, UINT_PTR, UINT_PTR); + + class CListHeaderUI; + +#define UILIST_MAX_COLUMNS 32 + + typedef struct tagTListInfoUI { + int nColumns; + RECT rcColumn[UILIST_MAX_COLUMNS]; + int nFont; + UINT uTextStyle; + RECT rcTextPadding = { 0 }; + DWORD dwTextColor; + DWORD dwBkColor; + CDuiString sBkImage; + bool bAlternateBk; + DWORD dwSelectedTextColor; + DWORD dwSelectedBkColor; + CDuiString sSelectedImage; + DWORD dwHotTextColor; + DWORD dwHotBkColor; + CDuiString sHotImage; + DWORD dwDisabledTextColor; + DWORD dwDisabledBkColor; + CDuiString sDisabledImage; + DWORD dwLineColor; + bool bShowRowLine; + bool bShowColumnLine; + bool bShowHtml; + bool bMultiExpandable; + bool bRSelected; + } TListInfoUI; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class IListCallbackUI { + public: + virtual string_view_t GetItemText (CControlUI* pList, int iItem, int iSubItem) = 0; + }; + + class IListOwnerUI { + public: + virtual UINT GetListType () = 0; + virtual TListInfoUI* GetListInfo () = 0; + virtual int GetCurSel () const = 0; + virtual bool SelectItem (int iIndex, bool bTakeFocus = false) = 0; + virtual bool SelectMultiItem (int iIndex, bool bTakeFocus = false) = 0; + virtual bool UnSelectItem (int iIndex, bool bOthers = false) = 0; + virtual void DoEvent (TEventUI& event) = 0; + }; + + class IListUI: public IListOwnerUI { + public: + virtual CListHeaderUI* GetHeader () const = 0; + virtual CContainerUI* GetList () const = 0; + virtual IListCallbackUI* GetTextCallback () const = 0; + virtual void SetTextCallback (IListCallbackUI* pCallback) = 0; + virtual bool ExpandItem (int iIndex, bool bExpand = true) = 0; + virtual int GetExpandedItem () const = 0; + + virtual void SetMultiSelect (bool bMultiSel) = 0; + virtual bool IsMultiSelect () const = 0; + virtual void SelectAllItems () = 0; + virtual void UnSelectAllItems () = 0; + virtual int GetSelectItemCount () const = 0; + virtual int GetNextSelItem (int nItem) const = 0; + }; + + class IListItemUI { + public: + virtual int GetIndex () const = 0; + virtual void SetIndex (int iIndex) = 0; + virtual IListOwnerUI* GetOwner () = 0; + virtual void SetOwner (CControlUI* pOwner) = 0; + virtual bool IsSelected () const = 0; + virtual bool Select (bool bSelect = true) = 0; + virtual bool SelectMulti (bool bSelect = true) = 0; + virtual bool IsExpanded () const = 0; + virtual bool Expand (bool bExpand = true) = 0; + virtual void DrawItemText (HDC hDC, const RECT& rcItem) = 0; + }; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class CListBodyUI; + class CListHeaderUI; + class CEditUI; + class CComboBoxUI; + class UILIB_API CListUI: public CVerticalLayoutUI, public IListUI { + DECLARE_DUICONTROL (CListUI) + + public: + CListUI (); + + string_view_t GetClass () const; + UINT GetControlFlags () const; + LPVOID GetInterface (string_view_t pstrName); + + bool GetScrollSelect (); + void SetScrollSelect (bool bScrollSelect); + int GetCurSel () const; + int GetCurSelActivate () const; + bool SelectItem (int iIndex, bool bTakeFocus = false); + bool SelectItemActivate (int iIndex); // ˫ѡ + + bool SelectMultiItem (int iIndex, bool bTakeFocus = false); + void SetMultiSelect (bool bMultiSel); + bool IsMultiSelect () const; + bool UnSelectItem (int iIndex, bool bOthers = false); + void SelectAllItems (); + void UnSelectAllItems (); + int GetSelectItemCount () const; + int GetNextSelItem (int nItem) const; + + bool IsFixedScrollbar (); + void SetFixedScrollbar (bool bFixed); + + CListHeaderUI* GetHeader () const; + CContainerUI* GetList () const; + UINT GetListType (); + TListInfoUI* GetListInfo (); + + CControlUI* GetItemAt (int iIndex) const; + int GetItemIndex (CControlUI* pControl) const; + bool SetItemIndex (CControlUI* pControl, int iIndex); + int GetCount () const; + bool Add (CControlUI* pControl); + bool AddAt (CControlUI* pControl, int iIndex); + bool Remove (CControlUI* pControl); + bool RemoveAt (int iIndex); + void RemoveAll (); + + void EnsureVisible (int iIndex); + void Scroll (int dx, int dy); + + bool IsDelayedDestroy () const; + void SetDelayedDestroy (bool bDelayed); + int GetChildPadding () const; + void SetChildPadding (int iPadding); + + void SetItemFont (int index); + void SetItemTextStyle (UINT uStyle); + void SetItemTextPadding (RECT rc); + void SetItemTextColor (DWORD dwTextColor); + void SetItemBkColor (DWORD dwBkColor); + void SetItemBkImage (string_view_t pStrImage); + void SetAlternateBk (bool bAlternateBk); + void SetSelectedItemTextColor (DWORD dwTextColor); + void SetSelectedItemBkColor (DWORD dwBkColor); + void SetSelectedItemImage (string_view_t pStrImage); + void SetHotItemTextColor (DWORD dwTextColor); + void SetHotItemBkColor (DWORD dwBkColor); + void SetHotItemImage (string_view_t pStrImage); + void SetDisabledItemTextColor (DWORD dwTextColor); + void SetDisabledItemBkColor (DWORD dwBkColor); + void SetDisabledItemImage (string_view_t pStrImage); + void SetItemLineColor (DWORD dwLineColor); + void SetItemShowRowLine (bool bShowLine = false); + void SetItemShowColumnLine (bool bShowLine = false); + bool IsItemShowHtml (); + void SetItemShowHtml (bool bShowHtml = true); + bool IsItemRSelected (); + void SetItemRSelected (bool bSelected = true); + RECT GetItemTextPadding () const; + DWORD GetItemTextColor () const; + DWORD GetItemBkColor () const; + string_view_t GetItemBkImage () const; + bool IsAlternateBk () const; + DWORD GetSelectedItemTextColor () const; + DWORD GetSelectedItemBkColor () const; + string_view_t GetSelectedItemImage () const; + DWORD GetHotItemTextColor () const; + DWORD GetHotItemBkColor () const; + string_view_t GetHotItemImage () const; + DWORD GetDisabledItemTextColor () const; + DWORD GetDisabledItemBkColor () const; + string_view_t GetDisabledItemImage () const; + DWORD GetItemLineColor () const; + + void SetMultiExpanding (bool bMultiExpandable); + int GetExpandedItem () const; + bool ExpandItem (int iIndex, bool bExpand = true); + + void SetPos (RECT rc, bool bNeedInvalidate = true); + void Move (SIZE szOffset, bool bNeedInvalidate = true); + void DoEvent (TEventUI& event); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + IListCallbackUI* GetTextCallback () const; + void SetTextCallback (IListCallbackUI* pCallback); + + SIZE GetScrollPos () const; + SIZE GetScrollRange () const; + void SetScrollPos (SIZE szPos, bool bMsg = true); + void LineUp (); + void LineDown (); + void PageUp (); + void PageDown (); + void HomeUp (); + void EndDown (); + void LineLeft (); + void LineRight (); + void PageLeft (); + void PageRight (); + void HomeLeft (); + void EndRight (); + void EnableScrollBar (bool bEnableVertical = true, bool bEnableHorizontal = false); + virtual CScrollBarUI* GetVerticalScrollBar () const; + virtual CScrollBarUI* GetHorizontalScrollBar () const; + BOOL SortItems (PULVCompareFunc pfnCompare, UINT_PTR dwData); + + virtual BOOL CheckColumEditable (int nColum) { + return FALSE; + }; + virtual CRichEditUI* GetEditUI () { + return nullptr; + }; + virtual BOOL CheckColumComboBoxable (int nColum) { + return FALSE; + }; + virtual CComboBoxUI* GetComboBoxUI () { + return nullptr; + }; + + protected: + int GetMinSelItemIndex (); + int GetMaxSelItemIndex (); + + protected: + bool m_bFixedScrollbar = false; + bool m_bScrollSelect = false; + int m_iCurSel = -1; + bool m_bMultiSel = false; + CStdPtrArray m_aSelItems; + int m_iCurSelActivate = 0; // ˫ + int m_iExpandedItem = -1; + IListCallbackUI *m_pCallback = nullptr; + CListBodyUI *m_pList; + CListHeaderUI *m_pHeader; + TListInfoUI m_ListInfo; + + }; + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CListBodyUI: public CVerticalLayoutUI { + public: + CListBodyUI (CListUI* pOwner); + + + int GetScrollStepSize () const; + void SetScrollPos (SIZE szPos, bool bMsg = true); + void SetPos (RECT rc, bool bNeedInvalidate = true); + void DoEvent (TEventUI& event); + BOOL SortItems (PULVCompareFunc pfnCompare, UINT_PTR dwData); + protected: + static int __cdecl ItemComareFunc (void *pvlocale, const void *item1, const void *item2); + int __cdecl ItemComareFunc (const void *item1, const void *item2); + protected: + CListUI* m_pOwner; + PULVCompareFunc m_pCompareFunc; + UINT_PTR m_compareData; + }; + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CListHeaderUI: public CHorizontalLayoutUI { + DECLARE_DUICONTROL (CListHeaderUI) + public: + CListHeaderUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + SIZE EstimateSize (SIZE szAvailable); + void SetPos (RECT rc, bool bNeedInvalidate = true); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + void SetScaleHeader (bool bIsScale); + bool IsScaleHeader () const; + + private: + bool m_bIsScaleHeader; + }; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CListHeaderItemUI: public CContainerUI { + DECLARE_DUICONTROL (CListHeaderItemUI) + + public: + CListHeaderItemUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetControlFlags () const; + + void SetEnabled (bool bEnable = true); + + bool IsDragable () const; + void SetDragable (bool bDragable); + DWORD GetSepWidth () const; + void SetSepWidth (int iWidth); + DWORD GetTextStyle () const; + void SetTextStyle (UINT uStyle); + DWORD GetTextColor () const; + void SetTextColor (DWORD dwTextColor); + void SetTextPadding (RECT rc); + RECT GetTextPadding () const; + void SetFont (int index); + bool IsShowHtml (); + void SetShowHtml (bool bShowHtml = true); + string_view_t GetNormalImage () const; + void SetNormalImage (string_view_t pStrImage); + string_view_t GetHotImage () const; + void SetHotImage (string_view_t pStrImage); + string_view_t GetPushedImage () const; + void SetPushedImage (string_view_t pStrImage); + string_view_t GetFocusedImage () const; + void SetFocusedImage (string_view_t pStrImage); + string_view_t GetSepImage () const; + void SetSepImage (string_view_t pStrImage); + void SetScale (int nScale); + int GetScale () const; + + void DoEvent (TEventUI& event); + SIZE EstimateSize (SIZE szAvailable); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + RECT GetThumbRect () const; + + void PaintText (HDC hDC); + void PaintStatusImage (HDC hDC); + + protected: + POINT ptLastMouse = { 0 }; + bool m_bDragable; + UINT m_uButtonState; + int m_iSepWidth; + DWORD m_dwTextColor; + int m_iFont; + UINT m_uTextStyle; + bool m_bShowHtml; + RECT m_rcTextPadding = { 0 }; + CDuiString m_sNormalImage; + CDuiString m_sHotImage; + CDuiString m_sPushedImage; + CDuiString m_sFocusedImage; + CDuiString m_sSepImage; + CDuiString m_sSepImageModify; + int m_nScale; + }; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CListElementUI: public CControlUI, public IListItemUI { + public: + CListElementUI (); + + string_view_t GetClass () const; + UINT GetControlFlags () const; + LPVOID GetInterface (string_view_t pstrName); + + void SetEnabled (bool bEnable = true); + + int GetIndex () const; + void SetIndex (int iIndex); + + IListOwnerUI* GetOwner (); + void SetOwner (CControlUI* pOwner); + void SetVisible (bool bVisible = true); + + bool IsSelected () const; + bool Select (bool bSelect = true); + bool SelectMulti (bool bSelect = true); + bool IsExpanded () const; + bool Expand (bool bExpand = true); + + void Invalidate (); // ֱCControl::Invalidateᵼ¹ˢ£дˢ + bool Activate (); + + void DoEvent (TEventUI& event); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + void DrawItemBk (HDC hDC, const RECT& rcItem); + + protected: + int m_iIndex; + bool m_bSelected; + UINT m_uButtonState; + IListOwnerUI* m_pOwner; + }; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CListLabelElementUI: public CListElementUI { + DECLARE_DUICONTROL (CListLabelElementUI) + public: + CListLabelElementUI (); + CListLabelElementUI (string_view_t text, int height = 20); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + void DoEvent (TEventUI& event); + SIZE EstimateSize (SIZE szAvailable); + bool DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl); + + void DrawItemText (HDC hDC, const RECT& rcItem); + }; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CListTextElementUI: public CListLabelElementUI { + DECLARE_DUICONTROL (CListTextElementUI) + public: + CListTextElementUI (); + virtual ~CListTextElementUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetControlFlags () const; + + CDuiString GetText (int iIndex) const; + void SetText (int iIndex, string_view_t pstrText); + + void SetOwner (CControlUI* pOwner); + CDuiString* GetLinkContent (int iIndex); + + void DoEvent (TEventUI& event); + SIZE EstimateSize (SIZE szAvailable); + + void DrawItemText (HDC hDC, const RECT& rcItem); + + protected: + enum { + MAX_LINK = 8 + }; + int m_nLinks; + RECT m_rcLinks[MAX_LINK]; + CDuiString m_sLinks[MAX_LINK]; + int m_nHoverLink; + IListUI* m_pOwner; + CStdPtrArray m_aTexts; + }; + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CListContainerElementUI: public CHorizontalLayoutUI, public IListItemUI { + DECLARE_DUICONTROL (CListContainerElementUI) + public: + CListContainerElementUI (); + + string_view_t GetClass () const; + UINT GetControlFlags () const; + LPVOID GetInterface (string_view_t pstrName); + + int GetIndex () const; + void SetIndex (int iIndex); + + IListOwnerUI* GetOwner (); + void SetOwner (CControlUI* pOwner); + void SetVisible (bool bVisible = true); + void SetEnabled (bool bEnable = true); + + bool IsSelected () const; + bool Select (bool bSelect = true); + bool SelectMulti (bool bSelect = true); + bool IsExpanded () const; + bool Expand (bool bExpand = true); + + void Invalidate (); // ֱCControl::Invalidateᵼ¹ˢ£дˢ + bool Activate (); + + void DoEvent (TEventUI& event); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + bool DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl); + + virtual void DrawItemText (HDC hDC, const RECT& rcItem); + virtual void DrawItemBk (HDC hDC, const RECT& rcItem); + + void SetPos (RECT rc, bool bNeedInvalidate = true); + + protected: + int m_iIndex; + bool m_bSelected; + UINT m_uButtonState; + IListOwnerUI* m_pOwner; + }; + +} // namespace DuiLib + +#endif // __UILIST_H__ diff --git a/DuiLib/Control/UIListEx.cpp b/DuiLib/Control/UIListEx.cpp new file mode 100644 index 0000000..023e8f2 --- /dev/null +++ b/DuiLib/Control/UIListEx.cpp @@ -0,0 +1,1318 @@ +#include "stdafx.h" +#include "UIListEx.h" + +namespace DuiLib { + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CListExUI) + + CListExUI::CListExUI () {} + + string_view_t CListExUI::GetClass () const { + return _T ("XListUI"); + } + + UINT CListExUI::GetControlFlags () const { + return UIFLAG_TABSTOP; + } + + LPVOID CListExUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("ListEx")) return static_cast(this); + return CListUI::GetInterface (pstrName); + } + BOOL CListExUI::CheckColumEditable (int nColum) { + CListContainerHeaderItemUI* pHItem = static_cast(m_pHeader->GetItemAt (nColum)); + return pHItem ? pHItem->GetColumeEditable () : FALSE; + } + void CListExUI::InitListCtrl () { + if (!m_bAddMessageFilter) { + GetManager ()->AddNotifier (this); + m_bAddMessageFilter = TRUE; + } + } + CRichEditUI* CListExUI::GetEditUI () { + if (!m_pEditUI) { + m_pEditUI = new CRichEditUI; + m_pEditUI->SetName (_T ("ListEx_Edit")); + string_view_t pDefaultAttributes = GetManager ()->GetDefaultAttributeList (_T ("RichEdit")); + if (!pDefaultAttributes.empty ()) { + m_pEditUI->ApplyAttributeList (pDefaultAttributes); + } + + m_pEditUI->SetBkColor (0xFFFFFFFF); + m_pEditUI->SetRich (false); + m_pEditUI->SetMultiLine (false); + m_pEditUI->SetWantReturn (true); + m_pEditUI->SetFloat (true); + m_pEditUI->SetAttribute (_T ("autohscroll"), _T ("true")); + Add (m_pEditUI); + } + if (m_pComboBoxUI) { + RECT rc = { 0, 0, 0, 0 }; + m_pComboBoxUI->SetPos (rc); + } + + return m_pEditUI; + } + + BOOL CListExUI::CheckColumComboBoxable (int nColum) { + CListContainerHeaderItemUI* pHItem = static_cast(m_pHeader->GetItemAt (nColum)); + return pHItem ? pHItem->GetColumeComboable () : FALSE; + } + + CComboBoxUI* CListExUI::GetComboBoxUI () { + if (!m_pComboBoxUI) { + m_pComboBoxUI = new CComboBoxUI; + m_pComboBoxUI->SetName (_T ("ListEx_Combo")); + string_view_t pDefaultAttributes = GetManager ()->GetDefaultAttributeList (_T ("Combo")); + if (!pDefaultAttributes.empty ()) { + m_pComboBoxUI->ApplyAttributeList (pDefaultAttributes); + } + + Add (m_pComboBoxUI); + } + if (m_pEditUI) { + RECT rc = { 0, 0, 0, 0 }; + m_pEditUI->SetPos (rc); + } + + return m_pComboBoxUI; + } + + BOOL CListExUI::CheckColumCheckBoxable (int nColum) { + CControlUI* p = m_pHeader->GetItemAt (nColum); + CListContainerHeaderItemUI* pHItem = static_cast(p->GetInterface (_T ("ListContainerHeaderItem"))); + return pHItem ? pHItem->GetColumeCheckable () : FALSE; + } + + void CListExUI::Notify (TNotifyUI& msg) { + CDuiString strName = msg.pSender->GetName (); + + //ѡ + if (msg.sType == _T ("listheaditemchecked")) { + BOOL bCheck = (BOOL) msg.lParam; + //жǷDZLIST͵notify + CListHeaderUI* pHeader = GetHeader (); + for (int i = 0; i < pHeader->GetCount (); i++) { + if (pHeader->GetItemAt (i) == msg.pSender) { + for (int j = 0; j < GetCount (); ++j) { + CControlUI* p = GetItemAt (j); + CListTextExtElementUI* pLItem = static_cast(p->GetInterface (_T ("ListTextExElement"))); + if (pLItem) { + pLItem->SetCheck (bCheck); + } + } + break; + } + } + } else if (msg.sType == DUI_MSGTYPE_LISTITEMCHECKED) { + for (int i = 0; i < GetCount (); ++i) { + CControlUI* p = GetItemAt (i); + CListTextExtElementUI* pLItem = static_cast(p->GetInterface (_T ("ListTextExElement"))); + if (pLItem && pLItem == msg.pSender) { + OnListItemChecked (LOWORD (msg.wParam), HIWORD (msg.wParam), (BOOL) msg.lParam); + break; + } + } + } + + //༭Ͽ + if (strName == _T ("ListEx_Edit") && m_pEditUI && m_nRow >= 0 && m_nColum >= 0) { + if (msg.sType == DUI_MSGTYPE_SETFOCUS) { + + } else if (msg.sType == DUI_MSGTYPE_KILLFOCUS) { + CDuiString sText = m_pEditUI->GetText (); + CListTextExtElementUI* pRowCtrl = (CListTextExtElementUI*) GetItemAt (m_nRow); + if (pRowCtrl) { + pRowCtrl->SetText (m_nColum, sText); + } + + //õǰ + SetEditRowAndColum (-1, -1); + + //ر༭ + RECT rc = { 0, 0, 0, 0 }; + m_pEditUI->SetPos (rc); + m_pEditUI->SetVisible (false); + } + } else if (strName == _T ("ListEx_Combo") && m_pComboBoxUI && m_nRow >= 0 && m_nColum >= 0) { + int iCurSel, iOldSel; + iCurSel = (int) msg.wParam; + iOldSel = (int) msg.lParam; + + if (msg.sType == DUI_MSGTYPE_SETFOCUS) { + + } else if (msg.sType == DUI_MSGTYPE_KILLFOCUS) { + } else if (msg.sType == DUI_MSGTYPE_LISTITEMSELECT && iOldSel >= 0) { + CListTextExtElementUI* pRowCtrl = (CListTextExtElementUI*) GetItemAt (m_nRow); + if (pRowCtrl) { + pRowCtrl->SetText (m_nColum, m_pComboBoxUI->GetText ()); + } + + //Ͽ + RECT rc = { 0, 0, 0, 0 }; + m_pComboBoxUI->SetPos (rc); + } + } else if (msg.sType == _T ("scroll") && (m_pComboBoxUI || m_pEditUI) && m_nRow >= 0 && m_nColum >= 0) { + HideEditAndComboCtrl (); + } + } + void CListExUI::HideEditAndComboCtrl () { + //ر༭ + RECT rc = { 0, 0, 0, 0 }; + if (m_pEditUI) { + m_pEditUI->SetVisible (false); + } + + if (m_pComboBoxUI) { + m_pComboBoxUI->SetPos (rc); + } + } + IListComboCallbackUI* CListExUI::GetTextArrayCallback () const { + return m_pXCallback; + } + + void CListExUI::SetTextArrayCallback (IListComboCallbackUI* pCallback) { + m_pXCallback = pCallback; + } + void CListExUI::OnListItemClicked (int nIndex, int nColum, RECT* lpRCColum, string_view_t lpstrText) { + RECT rc = { 0, 0, 0, 0 }; + if (nColum < 0) { + if (m_pEditUI) { + m_pEditUI->SetPos (rc); + m_pEditUI->SetVisible (false); + } + if (m_pComboBoxUI) { + m_pComboBoxUI->SetPos (rc); + } + } else { + if (CheckColumEditable (nColum) && GetEditUI ()) { + //浱ǰ + SetEditRowAndColum (nIndex, nColum); + + m_pEditUI->SetVisible (true); + //ƶλ + m_pEditUI->SetFixedWidth (lpRCColum->right - lpRCColum->left); + m_pEditUI->SetFixedHeight (lpRCColum->bottom - lpRCColum->top); + m_pEditUI->SetFixedXY ({ lpRCColum->left, lpRCColum->top }); + SIZE szTextSize = CRenderEngine::GetTextSize (m_pManager->GetPaintDC (), m_pManager, _T ("TTT"), m_ListInfo.nFont, DT_CALCRECT | DT_SINGLELINE); + m_pEditUI->SetTextPadding ({ 2, (lpRCColum->bottom - lpRCColum->top - szTextSize.cy) / 2, 2, 0 }); + + // + m_pEditUI->SetText (lpstrText); + + m_pEditUI->SetFocus (); + } else if (CheckColumComboBoxable (nColum) && GetComboBoxUI ()) { + //Ͽ + m_pComboBoxUI->RemoveAll (); + + //浱ǰ + SetEditRowAndColum (nIndex, nColum); + + // + m_pComboBoxUI->SetText (lpstrText); + + //ȡ + if (m_pXCallback) { + m_pXCallback->GetItemComboTextArray (m_pComboBoxUI, nIndex, nColum); + } + + //ƶλ + m_pComboBoxUI->SetPos (*lpRCColum); + m_pComboBoxUI->SetVisible (TRUE); + } else { + if (m_pEditUI) { + m_pEditUI->SetPos (rc); + m_pEditUI->SetVisible (false); + } + if (m_pComboBoxUI) { + m_pComboBoxUI->SetPos (rc); + } + } + } + } + void CListExUI::OnListItemChecked (int nIndex, int nColum, BOOL bChecked) { + CControlUI* p = m_pHeader->GetItemAt (nColum); + CListContainerHeaderItemUI* pHItem = static_cast(p->GetInterface (_T ("ListContainerHeaderItem"))); + if (!pHItem) + return; + + //ѡУôǷȫѡ״̬ + if (bChecked) { + BOOL bCheckAll = TRUE; + for (int i = 0; i < GetCount (); i++) { + p = GetItemAt (i); + CListTextExtElementUI* pLItem = static_cast(p->GetInterface (_T ("ListTextExElement"))); + if (pLItem && !pLItem->GetCheck ()) { + bCheckAll = FALSE; + break; + } + } + if (bCheckAll) { + pHItem->SetCheck (TRUE); + } else { + pHItem->SetCheck (FALSE); + } + } else { + pHItem->SetCheck (FALSE); + } + } + void CListExUI::DoEvent (TEventUI& event) { + if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_SCROLLWHEEL) { + HideEditAndComboCtrl (); + } + + CListUI::DoEvent (event); + } + void CListExUI::SetColumItemColor (int nIndex, int nColum, DWORD iBKColor) { + CControlUI* p = GetItemAt (nIndex); + CListTextExtElementUI* pLItem = static_cast(p->GetInterface (_T ("ListTextExElement"))); + if (pLItem) { + DWORD iTextBkColor = iBKColor; + pLItem->SetColumItemColor (nColum, iTextBkColor); + } + } + + BOOL CListExUI::GetColumItemColor (int nIndex, int nColum, DWORD& iBKColor) { + CControlUI* p = GetItemAt (nIndex); + CListTextExtElementUI* pLItem = static_cast(p->GetInterface (_T ("ListTextExElement"))); + if (!pLItem) + return FALSE; + pLItem->GetColumItemColor (nColum, iBKColor); + return TRUE; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CListContainerHeaderItemUI) + + CListContainerHeaderItemUI::CListContainerHeaderItemUI (): m_bDragable (TRUE), m_uButtonState (0), m_iSepWidth (4), + m_uTextStyle (DT_VCENTER | DT_CENTER | DT_SINGLELINE), m_dwTextColor (0), m_iFont (-1), m_bShowHtml (FALSE), + m_bEditable (FALSE), m_bComboable (FALSE), m_bCheckBoxable (FALSE), m_uCheckBoxState (0), m_bChecked (FALSE), m_pOwner (nullptr) { + SetTextPadding ({ 2, 0, 2, 0 }); + ptLastMouse.x = ptLastMouse.y = 0; + SetMinWidth (16); + } + + string_view_t CListContainerHeaderItemUI::GetClass () const { + return _T ("ListContainerHeaderItem"); + } + + LPVOID CListContainerHeaderItemUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("ListContainerHeaderItem")) return this; + return CContainerUI::GetInterface (pstrName); + } + + UINT CListContainerHeaderItemUI::GetControlFlags () const { + if (IsEnabled () && m_iSepWidth != 0) return UIFLAG_SETCURSOR; + else return 0; + } + + void CListContainerHeaderItemUI::SetEnabled (BOOL bEnable) { + CContainerUI::SetEnabled (bEnable); + if (!IsEnabled ()) { + m_uButtonState = 0; + } + } + + BOOL CListContainerHeaderItemUI::IsDragable () const { + return m_bDragable; + } + + void CListContainerHeaderItemUI::SetDragable (BOOL bDragable) { + m_bDragable = bDragable; + if (!m_bDragable) m_uButtonState &= ~UISTATE_CAPTURED; + } + + DWORD CListContainerHeaderItemUI::GetSepWidth () const { + return m_iSepWidth; + } + + void CListContainerHeaderItemUI::SetSepWidth (int iWidth) { + m_iSepWidth = iWidth; + } + + DWORD CListContainerHeaderItemUI::GetTextStyle () const { + return m_uTextStyle; + } + + void CListContainerHeaderItemUI::SetTextStyle (UINT uStyle) { + m_uTextStyle = uStyle; + Invalidate (); + } + + DWORD CListContainerHeaderItemUI::GetTextColor () const { + return m_dwTextColor; + } + + + void CListContainerHeaderItemUI::SetTextColor (DWORD dwTextColor) { + m_dwTextColor = dwTextColor; + } + + RECT CListContainerHeaderItemUI::GetTextPadding () const { + return m_rcTextPadding; + } + + void CListContainerHeaderItemUI::SetTextPadding (RECT rc) { + m_rcTextPadding = rc; + Invalidate (); + } + + void CListContainerHeaderItemUI::SetFont (int index) { + m_iFont = index; + } + + BOOL CListContainerHeaderItemUI::IsShowHtml () { + return m_bShowHtml; + } + + void CListContainerHeaderItemUI::SetShowHtml (BOOL bShowHtml) { + if (m_bShowHtml == bShowHtml) return; + + m_bShowHtml = bShowHtml; + Invalidate (); + } + + string_view_t CListContainerHeaderItemUI::GetNormalImage () const { + return m_sNormalImage; + } + + void CListContainerHeaderItemUI::SetNormalImage (string_view_t pStrImage) { + m_sNormalImage = pStrImage; + Invalidate (); + } + + string_view_t CListContainerHeaderItemUI::GetHotImage () const { + return m_sHotImage; + } + + void CListContainerHeaderItemUI::SetHotImage (string_view_t pStrImage) { + m_sHotImage = pStrImage; + Invalidate (); + } + + string_view_t CListContainerHeaderItemUI::GetPushedImage () const { + return m_sPushedImage; + } + + void CListContainerHeaderItemUI::SetPushedImage (string_view_t pStrImage) { + m_sPushedImage = pStrImage; + Invalidate (); + } + + string_view_t CListContainerHeaderItemUI::GetFocusedImage () const { + return m_sFocusedImage; + } + + void CListContainerHeaderItemUI::SetFocusedImage (string_view_t pStrImage) { + m_sFocusedImage = pStrImage; + Invalidate (); + } + + string_view_t CListContainerHeaderItemUI::GetSepImage () const { + return m_sSepImage; + } + + void CListContainerHeaderItemUI::SetSepImage (string_view_t pStrImage) { + m_sSepImage = pStrImage; + Invalidate (); + } + + void CListContainerHeaderItemUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("dragable")) SetDragable (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("sepwidth")) SetSepWidth (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("align")) { + if (pstrValue.find (_T ("left")) != string_t::npos) { + m_uTextStyle &= ~(DT_CENTER | DT_RIGHT); + m_uTextStyle |= DT_LEFT; + } + if (pstrValue.find (_T ("center")) != string_t::npos) { + m_uTextStyle &= ~(DT_LEFT | DT_RIGHT); + m_uTextStyle |= DT_CENTER; + } + if (pstrValue.find (_T ("right")) != string_t::npos) { + m_uTextStyle &= ~(DT_LEFT | DT_CENTER); + m_uTextStyle |= DT_RIGHT; + } + } else if (pstrName == _T ("endellipsis")) { + if (FawTools::parse_bool (pstrValue)) m_uTextStyle |= DT_END_ELLIPSIS; + else m_uTextStyle &= ~DT_END_ELLIPSIS; + } else if (pstrName == _T ("font")) SetFont (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("textcolor")) { + SetTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("textpadding")) { + RECT rcTextPadding = FawTools::parse_rect (pstrValue); + SetTextPadding (rcTextPadding); + } else if (pstrName == _T ("showhtml")) SetShowHtml (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("normalimage")) SetNormalImage (pstrValue); + else if (pstrName == _T ("hotimage")) SetHotImage (pstrValue); + else if (pstrName == _T ("pushedimage")) SetPushedImage (pstrValue); + else if (pstrName == _T ("focusedimage")) SetFocusedImage (pstrValue); + else if (pstrName == _T ("sepimage")) SetSepImage (pstrValue); + + else if (pstrName == _T ("editable")) SetColumeEditable (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("comboable")) SetColumeComboable (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("checkable")) SetColumeCheckable (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("checkboxwidth")) SetCheckBoxWidth (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("checkboxheight")) SetCheckBoxHeight (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("checkboxnormalimage")) SetCheckBoxNormalImage (pstrValue); + else if (pstrName == _T ("checkboxhotimage")) SetCheckBoxHotImage (pstrValue); + else if (pstrName == _T ("checkboxpushedimage")) SetCheckBoxPushedImage (pstrValue); + else if (pstrName == _T ("checkboxfocusedimage")) SetCheckBoxFocusedImage (pstrValue); + else if (pstrName == _T ("checkboxdisabledimage")) SetCheckBoxDisabledImage (pstrValue); + else if (pstrName == _T ("checkboxselectedimage")) SetCheckBoxSelectedImage (pstrValue); + else if (pstrName == _T ("checkboxforeimage")) SetCheckBoxForeImage (pstrValue); + + else CContainerUI::SetAttribute (pstrName, pstrValue); + } + + void CListContainerHeaderItemUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CContainerUI::DoEvent (event); + return; + } + + //CheckBoxAble + if (m_bCheckBoxable) { + RECT rcCheckBox = { 0 }; + GetCheckBoxRect (rcCheckBox); + + if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK) { + if (::PtInRect (&rcCheckBox, event.ptMouse)) { + m_uCheckBoxState |= UISTATE_PUSHED | UISTATE_CAPTURED; + Invalidate (); + } + } else if (event.Type == UIEVENT_MOUSEMOVE) { + if ((m_uCheckBoxState & UISTATE_CAPTURED) != 0) { + if (::PtInRect (&rcCheckBox, event.ptMouse)) + m_uCheckBoxState |= UISTATE_PUSHED; + else + m_uCheckBoxState &= ~UISTATE_PUSHED; + Invalidate (); + } else if (::PtInRect (&rcCheckBox, event.ptMouse)) { + m_uCheckBoxState |= UISTATE_HOT; + Invalidate (); + } else { + m_uCheckBoxState &= ~UISTATE_HOT; + Invalidate (); + } + } else if (event.Type == UIEVENT_BUTTONUP) { + if ((m_uCheckBoxState & UISTATE_CAPTURED) != 0) { + if (::PtInRect (&rcCheckBox, event.ptMouse)) { + SetCheck (!GetCheck ()); + CContainerUI* pOwner = (CContainerUI*) m_pParent; + if (pOwner) { + m_pManager->SendNotify (this, DUI_MSGTYPE_LISTHEADITEMCHECKED, pOwner->GetItemIndex (this), m_bChecked); + } + + } + m_uCheckBoxState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED); + Invalidate (); + } else if (::PtInRect (&rcCheckBox, event.ptMouse)) { + + } + } else if (event.Type == UIEVENT_MOUSEENTER) { + if (::PtInRect (&rcCheckBox, event.ptMouse)) { + m_uCheckBoxState |= UISTATE_HOT; + Invalidate (); + } + } else if (event.Type == UIEVENT_MOUSELEAVE) { + m_uCheckBoxState &= ~UISTATE_HOT; + Invalidate (); + } + } + + if (event.Type == UIEVENT_SETFOCUS) { + Invalidate (); + } + if (event.Type == UIEVENT_KILLFOCUS) { + Invalidate (); + } + if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK) { + if (!IsEnabled ()) return; + RECT rcSeparator = GetThumbRect (); + if (m_iSepWidth >= 0) + rcSeparator.left -= 4; + else + rcSeparator.right += 4; + if (::PtInRect (&rcSeparator, event.ptMouse)) { + if (m_bDragable) { + m_uButtonState |= UISTATE_CAPTURED; + ptLastMouse = event.ptMouse; + } + } else { + m_uButtonState |= UISTATE_PUSHED; + m_pManager->SendNotify (this, DUI_MSGTYPE_LISTHEADERCLICK); + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_BUTTONUP) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + m_uButtonState &= ~UISTATE_CAPTURED; + if (GetParent ()) + GetParent ()->NeedParentUpdate (); + } else if ((m_uButtonState & UISTATE_PUSHED) != 0) { + m_uButtonState &= ~UISTATE_PUSHED; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_MOUSEMOVE) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + RECT rc = m_rcItem; + if (m_iSepWidth >= 0) { + rc.right -= ptLastMouse.x - event.ptMouse.x; + } else { + rc.left -= ptLastMouse.x - event.ptMouse.x; + } + + if (rc.right - rc.left > GetMinWidth ()) { + m_cxyFixed.cx = rc.right - rc.left; + ptLastMouse = event.ptMouse; + if (GetParent ()) + GetParent ()->NeedParentUpdate (); + } + } + return; + } + if (event.Type == UIEVENT_SETCURSOR) { + RECT rcSeparator = GetThumbRect (); + if (m_iSepWidth >= 0) + rcSeparator.left -= 4; + else + rcSeparator.right += 4; + if (IsEnabled () && m_bDragable && ::PtInRect (&rcSeparator, event.ptMouse)) { + ::SetCursor (::LoadCursor (nullptr, IDC_SIZEWE)); + return; + } + } + if (event.Type == UIEVENT_MOUSEENTER) { + if (IsEnabled ()) { + m_uButtonState |= UISTATE_HOT; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_MOUSELEAVE) { + if (IsEnabled ()) { + m_uButtonState &= ~UISTATE_HOT; + Invalidate (); + } + return; + } + CContainerUI::DoEvent (event); + } + + SIZE CListContainerHeaderItemUI::EstimateSize (SIZE szAvailable) { + if (m_cxyFixed.cy == 0) return { m_cxyFixed.cx, m_pManager->GetDefaultFontInfo ()->tm.tmHeight + 14 }; + return CContainerUI::EstimateSize (szAvailable); + } + + RECT CListContainerHeaderItemUI::GetThumbRect () const { + if (m_iSepWidth >= 0) return { m_rcItem.right - m_iSepWidth, m_rcItem.top, m_rcItem.right, m_rcItem.bottom }; + else return { m_rcItem.left, m_rcItem.top, m_rcItem.left - m_iSepWidth, m_rcItem.bottom }; + } + + void CListContainerHeaderItemUI::PaintStatusImage (HDC hDC) { + //HeadItem Bkgnd + if (IsFocused ()) m_uButtonState |= UISTATE_FOCUSED; + else m_uButtonState &= ~UISTATE_FOCUSED; + + if ((m_uButtonState & UISTATE_PUSHED) != 0) { + if (m_sPushedImage.empty () && !m_sNormalImage.empty ()) DrawImage (hDC, m_sNormalImage); + if (!DrawImage (hDC, m_sPushedImage)) { + } + } else if ((m_uButtonState & UISTATE_HOT) != 0) { + if (m_sHotImage.empty () && !m_sNormalImage.empty ()) DrawImage (hDC, m_sNormalImage); + if (!DrawImage (hDC, m_sHotImage)) { + } + } else if ((m_uButtonState & UISTATE_FOCUSED) != 0) { + if (m_sFocusedImage.empty () && !m_sNormalImage.empty ()) DrawImage (hDC, m_sNormalImage); + if (!DrawImage (hDC, m_sFocusedImage)) { + } + } else { + if (!m_sNormalImage.empty ()) { + if (!DrawImage (hDC, m_sNormalImage)) { + } + } + } + + if (!m_sSepImage.empty ()) { + RECT rcThumb = GetThumbRect (); + rcThumb.left -= m_rcItem.left; + rcThumb.top -= m_rcItem.top; + rcThumb.right -= m_rcItem.left; + rcThumb.bottom -= m_rcItem.top; + + m_sSepImageModify.clear (); + m_sSepImageModify.Format (_T ("dest='%d,%d,%d,%d'"), rcThumb.left, rcThumb.top, rcThumb.right, rcThumb.bottom); + if (!DrawImage (hDC, m_sSepImage, m_sSepImageModify)) { + } + } + + if (m_bCheckBoxable) { + m_uCheckBoxState &= ~UISTATE_PUSHED; + + if ((m_uCheckBoxState & UISTATE_SELECTED) != 0) { + if (!m_sCheckBoxSelectedImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxSelectedImage)) { + } else goto Label_ForeImage; + } + } + + if (IsFocused ()) m_uCheckBoxState |= UISTATE_FOCUSED; + else m_uCheckBoxState &= ~UISTATE_FOCUSED; + if (!IsEnabled ()) m_uCheckBoxState |= UISTATE_DISABLED; + else m_uCheckBoxState &= ~UISTATE_DISABLED; + + if ((m_uCheckBoxState & UISTATE_DISABLED) != 0) { + if (!m_sCheckBoxDisabledImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxDisabledImage)) { + } else return; + } + } else if ((m_uCheckBoxState & UISTATE_PUSHED) != 0) { + if (!m_sCheckBoxPushedImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxPushedImage)) { + } else return; + } + } else if ((m_uCheckBoxState & UISTATE_HOT) != 0) { + if (!m_sCheckBoxHotImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxHotImage)) { + } else return; + } + } else if ((m_uCheckBoxState & UISTATE_FOCUSED) != 0) { + if (!m_sCheckBoxFocusedImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxFocusedImage)) { + } else return; + } + } + + if (!m_sCheckBoxNormalImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxNormalImage)) { + } else return; + } + + Label_ForeImage: + if (!m_sCheckBoxForeImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxForeImage)) { + } + } + } + } + + void CListContainerHeaderItemUI::PaintText (HDC hDC) { + if (m_dwTextColor == 0) m_dwTextColor = m_pManager->GetDefaultFontColor (); + + RECT rcText = m_rcItem; + rcText.left += m_rcTextPadding.left; + rcText.top += m_rcTextPadding.top; + rcText.right -= m_rcTextPadding.right; + rcText.bottom -= m_rcTextPadding.bottom; + if (m_bCheckBoxable) { + RECT rcCheck = { 0 }; + GetCheckBoxRect (rcCheck); + rcText.left += (rcCheck.right - rcCheck.left); + } + + CDuiString sText = GetText (); + if (sText.empty ()) return; + + int nLinks = 0; + if (m_bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rcText, sText, m_dwTextColor, \ + nullptr, nullptr, nLinks, m_iFont, DT_SINGLELINE | m_uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rcText, sText, m_dwTextColor, \ + m_iFont, DT_SINGLELINE | m_uTextStyle); + } + + BOOL CListContainerHeaderItemUI::GetColumeEditable () { + return m_bEditable; + } + + void CListContainerHeaderItemUI::SetColumeEditable (BOOL bEnable) { + m_bEditable = bEnable; + } + + BOOL CListContainerHeaderItemUI::GetColumeComboable () { + return m_bComboable; + } + + void CListContainerHeaderItemUI::SetColumeComboable (BOOL bEnable) { + m_bComboable = bEnable; + } + + BOOL CListContainerHeaderItemUI::GetColumeCheckable () { + return m_bCheckBoxable; + } + void CListContainerHeaderItemUI::SetColumeCheckable (BOOL bEnable) { + m_bCheckBoxable = bEnable; + } + void CListContainerHeaderItemUI::SetCheck (BOOL bCheck) { + if (m_bChecked == bCheck) return; + m_bChecked = bCheck; + if (m_bChecked) m_uCheckBoxState |= UISTATE_SELECTED; + else m_uCheckBoxState &= ~UISTATE_SELECTED; + Invalidate (); + } + + BOOL CListContainerHeaderItemUI::GetCheck () { + return m_bChecked; + } + BOOL CListContainerHeaderItemUI::DrawCheckBoxImage (HDC hDC, string_view_t pStrImage, string_view_t pStrModify) { + RECT rcCheckBox = { 0 }; + GetCheckBoxRect (rcCheckBox); + return CRenderEngine::DrawImageString (hDC, m_pManager, rcCheckBox, m_rcPaint, pStrImage, pStrModify); + } + string_view_t CListContainerHeaderItemUI::GetCheckBoxNormalImage () { + return m_sCheckBoxNormalImage; + } + + void CListContainerHeaderItemUI::SetCheckBoxNormalImage (string_view_t pStrImage) { + m_sCheckBoxNormalImage = pStrImage; + } + + string_view_t CListContainerHeaderItemUI::GetCheckBoxHotImage () { + return m_sCheckBoxHotImage; + } + + void CListContainerHeaderItemUI::SetCheckBoxHotImage (string_view_t pStrImage) { + m_sCheckBoxHotImage = pStrImage; + } + + string_view_t CListContainerHeaderItemUI::GetCheckBoxPushedImage () { + return m_sCheckBoxPushedImage; + } + + void CListContainerHeaderItemUI::SetCheckBoxPushedImage (string_view_t pStrImage) { + m_sCheckBoxPushedImage = pStrImage; + } + + string_view_t CListContainerHeaderItemUI::GetCheckBoxFocusedImage () { + return m_sCheckBoxFocusedImage; + } + + void CListContainerHeaderItemUI::SetCheckBoxFocusedImage (string_view_t pStrImage) { + m_sCheckBoxFocusedImage = pStrImage; + } + + string_view_t CListContainerHeaderItemUI::GetCheckBoxDisabledImage () { + return m_sCheckBoxDisabledImage; + } + + void CListContainerHeaderItemUI::SetCheckBoxDisabledImage (string_view_t pStrImage) { + m_sCheckBoxDisabledImage = pStrImage; + } + string_view_t CListContainerHeaderItemUI::GetCheckBoxSelectedImage () { + return m_sCheckBoxSelectedImage; + } + + void CListContainerHeaderItemUI::SetCheckBoxSelectedImage (string_view_t pStrImage) { + m_sCheckBoxSelectedImage = pStrImage; + } + string_view_t CListContainerHeaderItemUI::GetCheckBoxForeImage () { + return m_sCheckBoxForeImage; + } + + void CListContainerHeaderItemUI::SetCheckBoxForeImage (string_view_t pStrImage) { + m_sCheckBoxForeImage = pStrImage; + } + int CListContainerHeaderItemUI::GetCheckBoxWidth () const { + return m_cxyCheckBox.cx; + } + + void CListContainerHeaderItemUI::SetCheckBoxWidth (int cx) { + if (cx < 0) return; + m_cxyCheckBox.cx = cx; + } + + int CListContainerHeaderItemUI::GetCheckBoxHeight () const { + return m_cxyCheckBox.cy; + } + + void CListContainerHeaderItemUI::SetCheckBoxHeight (int cy) { + if (cy < 0) return; + m_cxyCheckBox.cy = cy; + } + void CListContainerHeaderItemUI::GetCheckBoxRect (RECT &rc) { + memset (&rc, 0x00, sizeof (rc)); + int nItemHeight = m_rcItem.bottom - m_rcItem.top; + rc.left = m_rcItem.left + 6; + rc.top = m_rcItem.top + (nItemHeight - GetCheckBoxHeight ()) / 2; + rc.right = rc.left + GetCheckBoxWidth (); + rc.bottom = rc.top + GetCheckBoxHeight (); + } + + void CListContainerHeaderItemUI::SetOwner (CContainerUI* pOwner) { + m_pOwner = pOwner; + } + CContainerUI* CListContainerHeaderItemUI::GetOwner () { + return m_pOwner; + } + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CListTextExtElementUI) + + CListTextExtElementUI::CListTextExtElementUI (): + m_nLinks (0), m_nHoverLink (-1), m_pOwner (nullptr), m_uCheckBoxState (0), m_bChecked (FALSE) { + ::ZeroMemory (&m_rcLinks, sizeof (m_rcLinks)); + m_cxyCheckBox.cx = m_cxyCheckBox.cy = 0; + + ::ZeroMemory (&ColumCorlorArray, sizeof (ColumCorlorArray)); + } + + CListTextExtElementUI::~CListTextExtElementUI () { + CDuiString* pText; + for (int it = 0; it < m_aTexts.GetSize (); it++) { + pText = static_cast(m_aTexts[it]); + if (pText) delete pText; + } + m_aTexts.Empty (); + } + + string_view_t CListTextExtElementUI::GetClass () const { + return _T ("ListTextExElementUI"); + } + + LPVOID CListTextExtElementUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("ListTextExElement")) return static_cast(this); + return CListLabelElementUI::GetInterface (pstrName); + } + + UINT CListTextExtElementUI::GetControlFlags () const { + return UIFLAG_WANTRETURN | ((IsEnabled () && m_nLinks > 0) ? UIFLAG_SETCURSOR : 0); + } + + CDuiString CListTextExtElementUI::GetText (int iIndex) const { + CDuiString* pText = static_cast(m_aTexts.GetAt (iIndex)); + if (pText) return *pText; + return nullptr; + } + + void CListTextExtElementUI::SetText (int iIndex, string_view_t pstrText) { + if (!m_pOwner) return; + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + if (iIndex < 0 || iIndex >= pInfo->nColumns) return; + while (m_aTexts.GetSize () < pInfo->nColumns) { + m_aTexts.Add (nullptr); + } + + CDuiString* pText = static_cast(m_aTexts[iIndex]); + if ((!pText && pstrText.empty ()) || (pText && *pText == pstrText)) return; + + if (pText) + *pText = pstrText; + else + m_aTexts.SetAt (iIndex, new CDuiString (pstrText)); + Invalidate (); + } + + void CListTextExtElementUI::SetOwner (CControlUI* pOwner) { + CListElementUI::SetOwner (pOwner); + m_pOwner = static_cast(pOwner->GetInterface (_T ("List"))); + } + + CDuiString* CListTextExtElementUI::GetLinkContent (int iIndex) { + if (iIndex >= 0 && iIndex < m_nLinks) return &m_sLinks[iIndex]; + return nullptr; + } + + void CListTextExtElementUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pOwner) m_pOwner->DoEvent (event); + else CListLabelElementUI::DoEvent (event); + return; + } + + // When you hover over a link + if (event.Type == UIEVENT_SETCURSOR) { + for (int i = 0; i < m_nLinks; i++) { + if (::PtInRect (&m_rcLinks[i], event.ptMouse)) { + ::SetCursor (::LoadCursor (nullptr, IDC_HAND)); + return; + } + } + } + if (event.Type == UIEVENT_BUTTONUP && IsEnabled ()) { + for (int i = 0; i < m_nLinks; i++) { + if (::PtInRect (&m_rcLinks[i], event.ptMouse)) { + m_pManager->SendNotify (this, DUI_MSGTYPE_LINK, i); + return; + } + } + } + if (m_nLinks > 0 && event.Type == UIEVENT_MOUSEMOVE) { + int nHoverLink = -1; + for (int i = 0; i < m_nLinks; i++) { + if (::PtInRect (&m_rcLinks[i], event.ptMouse)) { + nHoverLink = i; + break; + } + } + + if (m_nHoverLink != nHoverLink) { + Invalidate (); + m_nHoverLink = nHoverLink; + } + } + if (m_nLinks > 0 && event.Type == UIEVENT_MOUSELEAVE) { + if (m_nHoverLink != -1) { + Invalidate (); + m_nHoverLink = -1; + } + } + + //ǷҪʾ༭Ͽ + CListExUI * pListCtrl = (CListExUI *) m_pOwner; + int nColum = HitTestColum (event.ptMouse); + if (event.Type == UIEVENT_BUTTONUP && m_pOwner->IsFocused ()) { + RECT rc = { 0, 0, 0, 0 }; + if (nColum >= 0) { + GetColumRect (nColum, rc); + } + + pListCtrl->OnListItemClicked (GetIndex (), nColum, &rc, GetText (nColum)); + } + + //ǷҪʾCheckBox + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + for (int i = 0; i < pInfo->nColumns; i++) { + if (pListCtrl->CheckColumCheckBoxable (i)) { + RECT rcCheckBox = { 0 }; + GetCheckBoxRect (i, rcCheckBox); + + if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK) { + if (::PtInRect (&rcCheckBox, event.ptMouse)) { + m_uCheckBoxState |= UISTATE_PUSHED | UISTATE_CAPTURED; + Invalidate (); + } + } else if (event.Type == UIEVENT_MOUSEMOVE) { + if ((m_uCheckBoxState & UISTATE_CAPTURED) != 0) { + if (::PtInRect (&rcCheckBox, event.ptMouse)) + m_uCheckBoxState |= UISTATE_PUSHED; + else + m_uCheckBoxState &= ~UISTATE_PUSHED; + Invalidate (); + } + } else if (event.Type == UIEVENT_BUTTONUP) { + if ((m_uCheckBoxState & UISTATE_CAPTURED) != 0) { + if (::PtInRect (&rcCheckBox, event.ptMouse)) { + SetCheck (!GetCheck ()); + if (m_pManager) { + m_pManager->SendNotify (this, DUI_MSGTYPE_LISTITEMCHECKED, MAKEWPARAM (GetIndex (), 0), m_bChecked); + } + } + m_uCheckBoxState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED); + Invalidate (); + } + } else if (event.Type == UIEVENT_MOUSEENTER) { + if (::PtInRect (&rcCheckBox, event.ptMouse)) { + m_uCheckBoxState |= UISTATE_HOT; + Invalidate (); + } + } else if (event.Type == UIEVENT_MOUSELEAVE) { + m_uCheckBoxState &= ~UISTATE_HOT; + Invalidate (); + } + } + } + + CListLabelElementUI::DoEvent (event); + } + + SIZE CListTextExtElementUI::EstimateSize (SIZE szAvailable) { + TListInfoUI* pInfo = nullptr; + if (m_pOwner) pInfo = m_pOwner->GetListInfo (); + + SIZE cXY = m_cxyFixed; + if (cXY.cy == 0 && m_pManager && pInfo) { + cXY.cy = m_pManager->GetFontInfo (pInfo->nFont)->tm.tmHeight + 8; + cXY.cy += pInfo->rcTextPadding.top + pInfo->rcTextPadding.bottom; + } + + return cXY; + } + + void CListTextExtElementUI::DrawItemText (HDC hDC, const RECT& /*rcItem*/) { + if (!m_pOwner) return; + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + DWORD iTextColor = pInfo->dwTextColor; + + if ((m_uButtonState & UISTATE_HOT) != 0) { + iTextColor = pInfo->dwHotTextColor; + } + if (IsSelected ()) { + iTextColor = pInfo->dwSelectedTextColor; + } + if (!IsEnabled ()) { + iTextColor = pInfo->dwDisabledTextColor; + } + IListCallbackUI* pCallback = m_pOwner->GetTextCallback (); + //DUIASSERT(pCallback); + //if (!pCallback) return; + + CListExUI * pListCtrl = (CListExUI *) m_pOwner; + m_nLinks = 0; + int nLinks = lengthof (m_rcLinks); + for (int i = 0; i < pInfo->nColumns; i++) { + RECT rcItem = { pInfo->rcColumn[i].left, m_rcItem.top, pInfo->rcColumn[i].right, m_rcItem.bottom }; + + DWORD iTextBkColor = 0; + if (GetColumItemColor (i, iTextBkColor)) { + CRenderEngine::DrawColor (hDC, rcItem, iTextBkColor); + } + + rcItem.left += pInfo->rcTextPadding.left; + rcItem.right -= pInfo->rcTextPadding.right; + rcItem.top += pInfo->rcTextPadding.top; + rcItem.bottom -= pInfo->rcTextPadding.bottom; + + //ǷҪʾCheckBox + if (pListCtrl->CheckColumCheckBoxable (i)) { + RECT rcCheckBox = { 0 }; + GetCheckBoxRect (i, rcCheckBox); + rcItem.left += (rcCheckBox.right - rcCheckBox.left); + } + + CDuiString strText;//ʹLPstring_view_t̫ by cddjr 2011/10/20 + if (pCallback) + strText = pCallback->GetItemText (this, m_iIndex, i); + else + strText = GetText (i); + if (pInfo->bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rcItem, strText, iTextColor, \ + &m_rcLinks[m_nLinks], &m_sLinks[m_nLinks], nLinks, pInfo->nFont, DT_SINGLELINE | pInfo->uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rcItem, strText, iTextColor, \ + pInfo->nFont, DT_SINGLELINE | pInfo->uTextStyle); + + m_nLinks += nLinks; + nLinks = lengthof (m_rcLinks) - m_nLinks; + } + for (int i = m_nLinks; i < lengthof (m_rcLinks); i++) { + ::ZeroMemory (m_rcLinks + i, sizeof (RECT)); + ((CDuiString*) (m_sLinks + i))->clear (); + } + } + void CListTextExtElementUI::PaintStatusImage (HDC hDC) { + CListExUI * pListCtrl = (CListExUI *) m_pOwner; + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + for (int i = 0; i < pInfo->nColumns; i++) { + if (pListCtrl->CheckColumCheckBoxable (i)) { + RECT rcCheckBox = { 0 }; + GetCheckBoxRect (i, rcCheckBox); + + m_uCheckBoxState &= ~UISTATE_PUSHED; + + if ((m_uCheckBoxState & UISTATE_SELECTED) != 0) { + if (!m_sCheckBoxSelectedImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxSelectedImage, nullptr, rcCheckBox)) { + } else goto Label_ForeImage; + } + } + + if (IsFocused ()) m_uCheckBoxState |= UISTATE_FOCUSED; + else m_uCheckBoxState &= ~UISTATE_FOCUSED; + if (!IsEnabled ()) m_uCheckBoxState |= UISTATE_DISABLED; + else m_uCheckBoxState &= ~UISTATE_DISABLED; + + if ((m_uCheckBoxState & UISTATE_DISABLED) != 0) { + if (!m_sCheckBoxDisabledImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxDisabledImage, nullptr, rcCheckBox)) { + } else return; + } + } else if ((m_uCheckBoxState & UISTATE_PUSHED) != 0) { + if (!m_sCheckBoxPushedImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxPushedImage, nullptr, rcCheckBox)) { + } else return; + } + } else if ((m_uCheckBoxState & UISTATE_HOT) != 0) { + if (!m_sCheckBoxHotImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxHotImage, nullptr, rcCheckBox)) { + } else return; + } + } else if ((m_uCheckBoxState & UISTATE_FOCUSED) != 0) { + if (!m_sCheckBoxFocusedImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxFocusedImage, nullptr, rcCheckBox)) { + } else return; + } + } + + if (!m_sCheckBoxNormalImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxNormalImage, nullptr, rcCheckBox)) { + } else return; + } + + Label_ForeImage: + if (!m_sCheckBoxForeImage.empty ()) { + if (!DrawCheckBoxImage (hDC, m_sCheckBoxForeImage, nullptr, rcCheckBox)) { + } + } + } + } + } + BOOL CListTextExtElementUI::DrawCheckBoxImage (HDC hDC, string_view_t pStrImage, string_view_t pStrModify, RECT& rcCheckBox) { + return CRenderEngine::DrawImageString (hDC, m_pManager, rcCheckBox, m_rcPaint, pStrImage, pStrModify); + } + void CListTextExtElementUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("checkboxwidth")) SetCheckBoxWidth (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("checkboxheight")) SetCheckBoxHeight (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("checkboxnormalimage")) SetCheckBoxNormalImage (pstrValue); + else if (pstrName == _T ("checkboxhotimage")) SetCheckBoxHotImage (pstrValue); + else if (pstrName == _T ("checkboxpushedimage")) SetCheckBoxPushedImage (pstrValue); + else if (pstrName == _T ("checkboxfocusedimage")) SetCheckBoxFocusedImage (pstrValue); + else if (pstrName == _T ("checkboxdisabledimage")) SetCheckBoxDisabledImage (pstrValue); + else if (pstrName == _T ("checkboxselectedimage")) SetCheckBoxSelectedImage (pstrValue); + else if (pstrName == _T ("checkboxforeimage")) SetCheckBoxForeImage (pstrValue); + else CListLabelElementUI::SetAttribute (pstrName, pstrValue); + } + string_view_t CListTextExtElementUI::GetCheckBoxNormalImage () { + return m_sCheckBoxNormalImage; + } + + void CListTextExtElementUI::SetCheckBoxNormalImage (string_view_t pStrImage) { + m_sCheckBoxNormalImage = pStrImage; + } + + string_view_t CListTextExtElementUI::GetCheckBoxHotImage () { + return m_sCheckBoxHotImage; + } + + void CListTextExtElementUI::SetCheckBoxHotImage (string_view_t pStrImage) { + m_sCheckBoxHotImage = pStrImage; + } + + string_view_t CListTextExtElementUI::GetCheckBoxPushedImage () { + return m_sCheckBoxPushedImage; + } + + void CListTextExtElementUI::SetCheckBoxPushedImage (string_view_t pStrImage) { + m_sCheckBoxPushedImage = pStrImage; + } + + string_view_t CListTextExtElementUI::GetCheckBoxFocusedImage () { + return m_sCheckBoxFocusedImage; + } + + void CListTextExtElementUI::SetCheckBoxFocusedImage (string_view_t pStrImage) { + m_sCheckBoxFocusedImage = pStrImage; + } + + string_view_t CListTextExtElementUI::GetCheckBoxDisabledImage () { + return m_sCheckBoxDisabledImage; + } + + void CListTextExtElementUI::SetCheckBoxDisabledImage (string_view_t pStrImage) { + m_sCheckBoxDisabledImage = pStrImage; + } + string_view_t CListTextExtElementUI::GetCheckBoxSelectedImage () { + return m_sCheckBoxSelectedImage; + } + + void CListTextExtElementUI::SetCheckBoxSelectedImage (string_view_t pStrImage) { + m_sCheckBoxSelectedImage = pStrImage; + } + string_view_t CListTextExtElementUI::GetCheckBoxForeImage () { + return m_sCheckBoxForeImage; + } + + void CListTextExtElementUI::SetCheckBoxForeImage (string_view_t pStrImage) { + m_sCheckBoxForeImage = pStrImage; + } + + bool CListTextExtElementUI::DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + if (!::IntersectRect (&m_rcPaint, &rcPaint, &m_rcItem)) return true; + DrawItemBk (hDC, m_rcItem); + PaintStatusImage (hDC); + DrawItemText (hDC, m_rcItem); + return true; + } + void CListTextExtElementUI::GetCheckBoxRect (int nIndex, RECT &rc) { + memset (&rc, 0x00, sizeof (rc)); + int nItemHeight = m_rcItem.bottom - m_rcItem.top; + rc.left = m_rcItem.left + 6; + rc.top = m_rcItem.top + (nItemHeight - GetCheckBoxHeight ()) / 2; + rc.right = rc.left + GetCheckBoxWidth (); + rc.bottom = rc.top + GetCheckBoxHeight (); + } + int CListTextExtElementUI::GetCheckBoxWidth () const { + return m_cxyCheckBox.cx; + } + + void CListTextExtElementUI::SetCheckBoxWidth (int cx) { + if (cx < 0) return; + m_cxyCheckBox.cx = cx; + } + + int CListTextExtElementUI::GetCheckBoxHeight () const { + return m_cxyCheckBox.cy; + } + + void CListTextExtElementUI::SetCheckBoxHeight (int cy) { + if (cy < 0) return; + m_cxyCheckBox.cy = cy; + } + + void CListTextExtElementUI::SetCheck (BOOL bCheck) { + if (m_bChecked == bCheck) return; + m_bChecked = bCheck; + if (m_bChecked) m_uCheckBoxState |= UISTATE_SELECTED; + else m_uCheckBoxState &= ~UISTATE_SELECTED; + Invalidate (); + } + + BOOL CListTextExtElementUI::GetCheck () const { + return m_bChecked; + } + + int CListTextExtElementUI::HitTestColum (POINT ptMouse) { + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + for (int i = 0; i < pInfo->nColumns; i++) { + RECT rcItem = { pInfo->rcColumn[i].left, m_rcItem.top, pInfo->rcColumn[i].right, m_rcItem.bottom }; + rcItem.left += pInfo->rcTextPadding.left; + rcItem.right -= pInfo->rcTextPadding.right; + rcItem.top += pInfo->rcTextPadding.top; + rcItem.bottom -= pInfo->rcTextPadding.bottom; + + if (::PtInRect (&rcItem, ptMouse)) { + return i; + } + } + return -1; + } + + BOOL CListTextExtElementUI::CheckColumEditable (int nColum) { + return m_pOwner->CheckColumEditable (nColum); + } + void CListTextExtElementUI::GetColumRect (int nColum, RECT &rc) { + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + RECT rcOwnerPos = m_pOwner->GetPos (); + rc.left = pInfo->rcColumn[nColum].left + 1; + rc.top = 1; + rc.right = pInfo->rcColumn[nColum].right - 1; + rc.bottom = m_rcItem.bottom - m_rcItem.top - 1; + OffsetRect (&rc, -rcOwnerPos.left, m_rcItem.top - rcOwnerPos.top); + } + + void CListTextExtElementUI::SetColumItemColor (int nColum, DWORD iBKColor) { + ColumCorlorArray[nColum].bEnable = TRUE; + ColumCorlorArray[nColum].iBKColor = iBKColor; + Invalidate (); + } + BOOL CListTextExtElementUI::GetColumItemColor (int nColum, DWORD& iBKColor) { + if (!ColumCorlorArray[nColum].bEnable) { + return FALSE; + } + iBKColor = ColumCorlorArray[nColum].iBKColor; + return TRUE; + } + +} // namespace DuiLib + diff --git a/DuiLib/Control/UIListEx.h b/DuiLib/Control/UIListEx.h new file mode 100644 index 0000000..4945435 --- /dev/null +++ b/DuiLib/Control/UIListEx.h @@ -0,0 +1,313 @@ +#ifndef __UILISTEX_H__ +#define __UILISTEX_H__ + +#pragma once + +#include "../Layout/UIVerticalLayout.h" +#include "../Layout/UIHorizontalLayout.h" + +namespace DuiLib { + + class IListComboCallbackUI { + public: + virtual void GetItemComboTextArray (CControlUI* pCtrl, int iItem, int iSubItem) = 0; + }; + + class CEditUI; + class CComboBoxUI; + + class UILIB_API CListExUI: public CListUI, public INotifyUI { + DECLARE_DUICONTROL (CListExUI) + + public: + CListExUI (); + + string_view_t GetClass () const; + UINT GetControlFlags () const; + LPVOID GetInterface (string_view_t pstrName); + + public: + virtual void DoEvent (TEventUI& event); + + public: + void InitListCtrl (); + + protected: + CRichEditUI *m_pEditUI = nullptr; + CComboBoxUI *m_pComboBoxUI = nullptr; + + public: + virtual BOOL CheckColumEditable (int nColum); + virtual CRichEditUI* GetEditUI (); + + virtual BOOL CheckColumComboBoxable (int nColum); + virtual CComboBoxUI* GetComboBoxUI (); + + virtual BOOL CheckColumCheckBoxable (int nColum); + + public: + virtual void Notify (TNotifyUI& msg); + BOOL m_bAddMessageFilter = FALSE; + int m_nRow = -1; + int m_nColum = -1; + void SetEditRowAndColum (int nRow, int nColum) { + m_nRow = nRow; m_nColum = nColum; + }; + + public: + IListComboCallbackUI *m_pXCallback = nullptr; + virtual IListComboCallbackUI* GetTextArrayCallback () const; + virtual void SetTextArrayCallback (IListComboCallbackUI* pCallback); + + public: + void OnListItemClicked (int nIndex, int nColum, RECT* lpRCColum, string_view_t lpstrText); + void OnListItemChecked (int nIndex, int nColum, BOOL bChecked); + + public: + void SetColumItemColor (int nIndex, int nColum, DWORD iBKColor); + BOOL GetColumItemColor (int nIndex, int nColum, DWORD& iBKColor); + + private: + void HideEditAndComboCtrl (); + }; + + ///////////////////////////////////////////////////////////////////////////////////// + // + class UILIB_API CListContainerHeaderItemUI: public CHorizontalLayoutUI { + DECLARE_DUICONTROL (CListContainerHeaderItemUI) + + public: + CListContainerHeaderItemUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetControlFlags () const; + + void SetEnabled (BOOL bEnable = TRUE); + + BOOL IsDragable () const; + void SetDragable (BOOL bDragable); + DWORD GetSepWidth () const; + void SetSepWidth (int iWidth); + DWORD GetTextStyle () const; + void SetTextStyle (UINT uStyle); + DWORD GetTextColor () const; + void SetTextColor (DWORD dwTextColor); + void SetTextPadding (RECT rc); + RECT GetTextPadding () const; + void SetFont (int index); + BOOL IsShowHtml (); + void SetShowHtml (BOOL bShowHtml = TRUE); + string_view_t GetNormalImage () const; + void SetNormalImage (string_view_t pStrImage); + string_view_t GetHotImage () const; + void SetHotImage (string_view_t pStrImage); + string_view_t GetPushedImage () const; + void SetPushedImage (string_view_t pStrImage); + string_view_t GetFocusedImage () const; + void SetFocusedImage (string_view_t pStrImage); + string_view_t GetSepImage () const; + void SetSepImage (string_view_t pStrImage); + + void DoEvent (TEventUI& event); + SIZE EstimateSize (SIZE szAvailable); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + RECT GetThumbRect () const; + + void PaintText (HDC hDC); + void PaintStatusImage (HDC hDC); + + protected: + POINT ptLastMouse = { 0 }; + BOOL m_bDragable; + UINT m_uButtonState; + int m_iSepWidth; + DWORD m_dwTextColor; + int m_iFont; + UINT m_uTextStyle; + BOOL m_bShowHtml; + RECT m_rcTextPadding = { 0 }; + CDuiString m_sNormalImage; + CDuiString m_sHotImage; + CDuiString m_sPushedImage; + CDuiString m_sFocusedImage; + CDuiString m_sSepImage; + CDuiString m_sSepImageModify; + + //ֱ֧༭ + BOOL m_bEditable; + + //֧Ͽ + BOOL m_bComboable; + + //ָ֧ѡ + BOOL m_bCheckBoxable; + + public: + BOOL GetColumeEditable (); + void SetColumeEditable (BOOL bEnable); + + BOOL GetColumeComboable (); + void SetColumeComboable (BOOL bEnable); + + BOOL GetColumeCheckable (); + void SetColumeCheckable (BOOL bEnable); + + public: + void SetCheck (BOOL bCheck); + BOOL GetCheck (); + + private: + UINT m_uCheckBoxState; + BOOL m_bChecked; + + CDuiString m_sCheckBoxNormalImage; + CDuiString m_sCheckBoxHotImage; + CDuiString m_sCheckBoxPushedImage; + CDuiString m_sCheckBoxFocusedImage; + CDuiString m_sCheckBoxDisabledImage; + + CDuiString m_sCheckBoxSelectedImage; + CDuiString m_sCheckBoxForeImage; + + SIZE m_cxyCheckBox = { 0 }; + + public: + BOOL DrawCheckBoxImage (HDC hDC, string_view_t pStrImage, string_view_t pStrModify = _T ("")); + string_view_t GetCheckBoxNormalImage (); + void SetCheckBoxNormalImage (string_view_t pStrImage); + string_view_t GetCheckBoxHotImage (); + void SetCheckBoxHotImage (string_view_t pStrImage); + string_view_t GetCheckBoxPushedImage (); + void SetCheckBoxPushedImage (string_view_t pStrImage); + string_view_t GetCheckBoxFocusedImage (); + void SetCheckBoxFocusedImage (string_view_t pStrImage); + string_view_t GetCheckBoxDisabledImage (); + void SetCheckBoxDisabledImage (string_view_t pStrImage); + + string_view_t GetCheckBoxSelectedImage (); + void SetCheckBoxSelectedImage (string_view_t pStrImage); + string_view_t GetCheckBoxForeImage (); + void SetCheckBoxForeImage (string_view_t pStrImage); + + void GetCheckBoxRect (RECT &rc); + + int GetCheckBoxWidth () const; // ʵʴСλʹGetPosȡõԤIJοֵ + void SetCheckBoxWidth (int cx); // ԤIJοֵ + int GetCheckBoxHeight () const; // ʵʴСλʹGetPosȡõԤIJοֵ + void SetCheckBoxHeight (int cy); // ԤIJοֵ + + + public: + CContainerUI* m_pOwner; + void SetOwner (CContainerUI* pOwner); + CContainerUI* GetOwner (); + + + + }; + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CListTextExtElementUI: public CListLabelElementUI { + DECLARE_DUICONTROL (CListTextExtElementUI) + + public: + CListTextExtElementUI (); + virtual ~CListTextExtElementUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetControlFlags () const; + + CDuiString GetText (int iIndex) const; + void SetText (int iIndex, string_view_t pstrText); + + void SetOwner (CControlUI* pOwner); + CDuiString* GetLinkContent (int iIndex); + + void DoEvent (TEventUI& event); + SIZE EstimateSize (SIZE szAvailable); + + void DrawItemText (HDC hDC, const RECT& rcItem); + + protected: + enum { + MAX_LINK = 8 + }; + int m_nLinks; + RECT m_rcLinks[MAX_LINK]; + CDuiString m_sLinks[MAX_LINK]; + int m_nHoverLink; + CListUI* m_pOwner; + CStdPtrArray m_aTexts; + + private: + UINT m_uCheckBoxState; + BOOL m_bChecked; + + CDuiString m_sCheckBoxNormalImage; + CDuiString m_sCheckBoxHotImage; + CDuiString m_sCheckBoxPushedImage; + CDuiString m_sCheckBoxFocusedImage; + CDuiString m_sCheckBoxDisabledImage; + + CDuiString m_sCheckBoxSelectedImage; + CDuiString m_sCheckBoxForeImage; + + SIZE m_cxyCheckBox = { 0 }; + + public: + virtual bool DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl); + virtual void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + virtual void PaintStatusImage (HDC hDC); + BOOL DrawCheckBoxImage (HDC hDC, string_view_t pStrImage, string_view_t pStrModify, RECT& rcCheckBox); + string_view_t GetCheckBoxNormalImage (); + void SetCheckBoxNormalImage (string_view_t pStrImage); + string_view_t GetCheckBoxHotImage (); + void SetCheckBoxHotImage (string_view_t pStrImage); + string_view_t GetCheckBoxPushedImage (); + void SetCheckBoxPushedImage (string_view_t pStrImage); + string_view_t GetCheckBoxFocusedImage (); + void SetCheckBoxFocusedImage (string_view_t pStrImage); + string_view_t GetCheckBoxDisabledImage (); + void SetCheckBoxDisabledImage (string_view_t pStrImage); + + string_view_t GetCheckBoxSelectedImage (); + void SetCheckBoxSelectedImage (string_view_t pStrImage); + string_view_t GetCheckBoxForeImage (); + void SetCheckBoxForeImage (string_view_t pStrImage); + + void GetCheckBoxRect (int nIndex, RECT &rc); + void GetColumRect (int nColum, RECT &rc); + + int GetCheckBoxWidth () const; // ʵʴСλʹGetPosȡõԤIJοֵ + void SetCheckBoxWidth (int cx); // ԤIJοֵ + int GetCheckBoxHeight () const; // ʵʴСλʹGetPosȡõԤIJοֵ + void SetCheckBoxHeight (int cy); // ԤIJοֵ + + void SetCheck (BOOL bCheck); + BOOL GetCheck () const; + + public: + int HitTestColum (POINT ptMouse); + BOOL CheckColumEditable (int nColum); + + private: + typedef struct tagColumColorNode { + BOOL bEnable; + DWORD iTextColor; + DWORD iBKColor; + }COLUMCOLORNODE; + + COLUMCOLORNODE ColumCorlorArray[UILIST_MAX_COLUMNS]; + + public: + void SetColumItemColor (int nColum, DWORD iBKColor); + BOOL GetColumItemColor (int nColum, DWORD& iBKColor); + + }; +} // namespace DuiLib + +#endif // __UILISTEX_H__ diff --git a/DuiLib/Control/UIMenu.cpp b/DuiLib/Control/UIMenu.cpp new file mode 100644 index 0000000..2cfb45d --- /dev/null +++ b/DuiLib/Control/UIMenu.cpp @@ -0,0 +1,1095 @@ +#include "StdAfx.h" + +#include "UIMenu.h" + +namespace DuiLib { + + ///////////////////////////////////////////////////////////////////////////////////// + // + IMPLEMENT_DUICONTROL (CMenuUI) + + CMenuUI::CMenuUI () { + if (GetHeader ()) + GetHeader ()->SetVisible (false); + } + + CMenuUI::~CMenuUI () {} + + string_view_t CMenuUI::GetClass () const { + return _T ("MenuUI"); + } + + LPVOID CMenuUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("Menu")) return static_cast(this); + return CListUI::GetInterface (pstrName); + } + + UINT CMenuUI::GetListType () { + return LT_MENU; + } + + void CMenuUI::DoEvent (TEventUI& event) { + return __super::DoEvent (event); + } + + bool CMenuUI::Add (CControlUI* pControl) { + CMenuElementUI* pMenuItem = static_cast(pControl->GetInterface (_T ("MenuElement"))); + if (!pMenuItem) + return false; + + for (int i = 0; i < pMenuItem->GetCount (); ++i) { + if (pMenuItem->GetItemAt (i)->GetInterface (_T ("MenuElement"))) { + (static_cast(pMenuItem->GetItemAt (i)->GetInterface (_T ("MenuElement"))))->SetInternVisible (false); + } + } + return CListUI::Add (pControl); + } + + bool CMenuUI::AddAt (CControlUI* pControl, int iIndex) { + CMenuElementUI* pMenuItem = static_cast(pControl->GetInterface (_T ("MenuElement"))); + if (!pMenuItem) + return false; + + for (int i = 0; i < pMenuItem->GetCount (); ++i) { + if (pMenuItem->GetItemAt (i)->GetInterface (_T ("MenuElement"))) { + (static_cast(pMenuItem->GetItemAt (i)->GetInterface (_T ("MenuElement"))))->SetInternVisible (false); + } + } + return CListUI::AddAt (pControl, iIndex); + } + + int CMenuUI::GetItemIndex (CControlUI* pControl) const { + CMenuElementUI* pMenuItem = static_cast(pControl->GetInterface (_T ("MenuElement"))); + if (!pMenuItem) + return -1; + + return __super::GetItemIndex (pControl); + } + + bool CMenuUI::SetItemIndex (CControlUI* pControl, int iIndex) { + CMenuElementUI* pMenuItem = static_cast(pControl->GetInterface (_T ("MenuElement"))); + if (!pMenuItem) + return false; + + return __super::SetItemIndex (pControl, iIndex); + } + + bool CMenuUI::Remove (CControlUI* pControl) { + CMenuElementUI* pMenuItem = static_cast(pControl->GetInterface (_T ("MenuElement"))); + if (!pMenuItem) + return false; + + return __super::Remove (pControl); + } + + SIZE CMenuUI::EstimateSize (SIZE szAvailable) { + int cxFixed = 0; + int cyFixed = 0; + for (int it = 0; it < GetCount (); it++) { + CControlUI* pControl = static_cast(GetItemAt (it)); + if (!pControl->IsVisible ()) continue; + SIZE sz = pControl->EstimateSize (szAvailable); + cyFixed += sz.cy; + if (cxFixed < sz.cx) + cxFixed = sz.cx; + } + + for (int it = 0; it < GetCount (); it++) { + CControlUI* pControl = static_cast(GetItemAt (it)); + if (!pControl->IsVisible ()) continue; + + pControl->SetFixedWidth (MulDiv (cxFixed, 100, GetManager ()->GetDPIObj ()->GetScale ())); + } + + return { cxFixed, cyFixed }; + } + + void CMenuUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + CListUI::SetAttribute (pstrName, pstrValue); + } + + ///////////////////////////////////////////////////////////////////////////////////// + // + + CMenuWnd::CMenuWnd (): + m_pOwner (nullptr), + m_pLayout (), + m_xml (_T ("")), + isClosing (false) { + m_dwAlignment = eMenuAlignment_Left | eMenuAlignment_Top; + } + + CMenuWnd::~CMenuWnd () { + + } + + void CMenuWnd::Close (UINT nRet) { + //ASSERT (::IsWindow (m_hWnd)); + if (!::IsWindow (m_hWnd)) return; + PostMessage (WM_CLOSE, (WPARAM) nRet, 0L); + isClosing = true; + } + + + BOOL CMenuWnd::Receive (ContextMenuParam param) { + switch (param.wParam) { + case 1: + Close (); + break; + case 2: + { + HWND hParent = GetParent (m_hWnd); + while (hParent) { + if (hParent == param.hWnd) { + Close (); + break; + } + hParent = GetParent (hParent); + } + } + break; + default: + break; + } + + return TRUE; + } + + CMenuWnd* CMenuWnd::CreateMenu (CMenuElementUI* pOwner, std::variant xml, POINT point, CPaintManagerUI* pMainPaintManager, CStdStringPtrMap* pMenuCheckInfo /*= nullptr*/, DWORD dwAlignment /*= eMenuAlignment_Left | eMenuAlignment_Top*/) { + CMenuWnd* pMenu = new CMenuWnd; + pMenu->Init (pOwner, xml, point, pMainPaintManager, pMenuCheckInfo, dwAlignment); + return pMenu; + } + + void CMenuWnd::DestroyMenu () { + CStdStringPtrMap* mCheckInfos = CMenuWnd::GetGlobalContextMenuObserver ().GetMenuCheckInfo (); + if (mCheckInfos) { + for (int i = 0; i < mCheckInfos->GetSize (); i++) { + MenuItemInfo* pItemInfo = (MenuItemInfo*) mCheckInfos->GetAt (i)->Data; + if (pItemInfo) { + delete pItemInfo; + pItemInfo = nullptr; + } + } + mCheckInfos->Resize (0); + } + } + + MenuItemInfo* CMenuWnd::SetMenuItemInfo (string_view_t pstrName, bool bChecked) { + if (pstrName.empty ()) return nullptr; + + CStdStringPtrMap* mCheckInfos = CMenuWnd::GetGlobalContextMenuObserver ().GetMenuCheckInfo (); + if (mCheckInfos) { + MenuItemInfo* pItemInfo = (MenuItemInfo*) mCheckInfos->Find (pstrName); + if (!pItemInfo) { + pItemInfo = new MenuItemInfo; + pItemInfo->szName = pstrName; + pItemInfo->bChecked = bChecked; + mCheckInfos->Insert (pstrName, pItemInfo); + } else { + pItemInfo->bChecked = bChecked; + } + + return pItemInfo; + } + return nullptr; + } + + void CMenuWnd::Init (CMenuElementUI* pOwner, std::variant xml, POINT point, + CPaintManagerUI* pMainPaintManager, CStdStringPtrMap* pMenuCheckInfo/* = nullptr*/, + DWORD dwAlignment/* = eMenuAlignment_Left | eMenuAlignment_Top*/) { + + m_BasedPoint = point; + m_pOwner = pOwner; + m_pLayout = nullptr; + m_xml = xml; + m_dwAlignment = dwAlignment; + + // һ˵Ĵ + if (!pOwner) { + ASSERT (pMainPaintManager); + CMenuWnd::GetGlobalContextMenuObserver ().SetManger (pMainPaintManager); + if (pMenuCheckInfo) + CMenuWnd::GetGlobalContextMenuObserver ().SetMenuCheckInfo (pMenuCheckInfo); + } + + CMenuWnd::GetGlobalContextMenuObserver ().AddReceiver (this); + + Create (!m_pOwner ? pMainPaintManager->GetPaintWindow () : m_pOwner->GetManager ()->GetPaintWindow (), _T (""), WS_POPUP, WS_EX_TOOLWINDOW | WS_EX_TOPMOST, { 0, 0, 0, 0 }); + + // HACK: Don't deselect the parent's caption + HWND hWndParent = m_hWnd; + while (::GetParent (hWndParent)) hWndParent = ::GetParent (hWndParent); + + ::ShowWindow (m_hWnd, SW_SHOW); + ::SendMessage (hWndParent, WM_NCACTIVATE, TRUE, 0L); + } + + string_view_t CMenuWnd::GetWindowClassName () const { + return _T ("DuiMenuWnd"); + } + + + void CMenuWnd::Notify (TNotifyUI& msg) { + if (CMenuWnd::GetGlobalContextMenuObserver ().GetManager ()) { + if (msg.sType == _T ("click") || msg.sType == _T ("valuechanged")) { + CMenuWnd::GetGlobalContextMenuObserver ().GetManager ()->SendNotify (msg, false); + } + } + + } + + CControlUI* CMenuWnd::CreateControl (string_view_t pstrClassName) { + if (pstrClassName == _T ("Menu")) { + return new CMenuUI (); + } else if (pstrClassName == _T ("MenuElement")) { + return new CMenuElementUI (); + } + return nullptr; + } + + + void CMenuWnd::OnFinalMessage (HWND hWnd) { + RemoveObserver (); + if (m_pOwner) { + for (int i = 0; i < m_pOwner->GetCount (); i++) { + if (static_cast(m_pOwner->GetItemAt (i)->GetInterface (_T ("MenuElement")))) { + (static_cast(m_pOwner->GetItemAt (i)))->SetOwner (m_pOwner->GetParent ()); + (static_cast(m_pOwner->GetItemAt (i)))->SetVisible (false); + (static_cast(m_pOwner->GetItemAt (i)->GetInterface (_T ("MenuElement"))))->SetInternVisible (false); + } + } + m_pOwner->m_pWindow = nullptr; + m_pOwner->m_uButtonState &= ~UISTATE_PUSHED; + m_pOwner->Invalidate (); + + // ڲڲɾ + delete this; + } + } + + LRESULT CMenuWnd::OnCreate (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + bool bShowShadow = false; + if (m_pOwner) { + LONG styleValue = ::GetWindowLong (GetHWND (), GWL_STYLE); + styleValue &= ~WS_CAPTION; + ::SetWindowLong (GetHWND (), GWL_STYLE, styleValue | WS_CLIPSIBLINGS | WS_CLIPCHILDREN); + RECT rcClient = { 0 }; + ::GetClientRect (GetHWND (), &rcClient); + ::SetWindowPos (GetHWND (), nullptr, rcClient.left, rcClient.top, rcClient.right - rcClient.left, \ + rcClient.bottom - rcClient.top, SWP_FRAMECHANGED); + + m_pm.Init (m_hWnd); + m_pm.GetDPIObj ()->SetScale (m_pOwner->GetManager ()->GetDPIObj ()->GetDPI ()); + // The trick is to add the items to the new container. Their owner gets + // reassigned by this operation - which is why it is important to reassign + // the items back to the righfull owner/manager when the window closes. + m_pLayout = new CMenuUI (); + m_pm.SetForceUseSharedRes (true); + m_pLayout->SetManager (&m_pm, nullptr, true); + string_view_t pDefaultAttributes = m_pOwner->GetManager ()->GetDefaultAttributeList (_T ("Menu")); + if (!pDefaultAttributes.empty ()) { + m_pLayout->ApplyAttributeList (pDefaultAttributes); + } + m_pLayout->GetList ()->SetAutoDestroy (false); + + for (int i = 0; i < m_pOwner->GetCount (); i++) { + if (m_pOwner->GetItemAt (i)->GetInterface (_T ("MenuElement"))) { + (static_cast(m_pOwner->GetItemAt (i)))->SetOwner (m_pLayout); + m_pLayout->Add (static_cast(m_pOwner->GetItemAt (i))); + } + } + + CShadowUI *pShadow = m_pOwner->GetManager ()->GetShadow (); + pShadow->CopyShadow (m_pm.GetShadow ()); + bShowShadow = m_pm.GetShadow ()->IsShowShadow (); + m_pm.GetShadow ()->ShowShadow (false); + m_pm.SetLayered (m_pOwner->GetManager ()->IsLayered ()); + m_pm.AttachDialog (m_pLayout); + m_pm.AddNotifier (this); + + ResizeSubMenu (); + } else { + m_pm.Init (m_hWnd); + m_pm.GetDPIObj ()->SetScale (CMenuWnd::GetGlobalContextMenuObserver ().GetManager ()->GetDPIObj ()->GetDPI ()); + CDialogBuilder builder; + + CControlUI* pRoot = builder.Create (m_xml, _T (""), this, &m_pm); + bShowShadow = m_pm.GetShadow ()->IsShowShadow (); + m_pm.GetShadow ()->ShowShadow (false); + m_pm.AttachDialog (pRoot); + m_pm.AddNotifier (this); + + ResizeMenu (); + } + GetMenuUI ()->m_pWindow = this; + m_pm.GetShadow ()->ShowShadow (bShowShadow); + m_pm.GetShadow ()->Create (&m_pm); + return 0; + } + + CMenuUI* CMenuWnd::GetMenuUI () { + return static_cast(m_pm.GetRoot ()); + } + + void CMenuWnd::ResizeMenu () { + CControlUI* pRoot = m_pm.GetRoot (); + +#if defined(WIN32) && !defined(UNDER_CE) + MONITORINFO oMonitor = {}; + oMonitor.cbSize = sizeof (oMonitor); + ::GetMonitorInfo (::MonitorFromWindow (GetHWND (), MONITOR_DEFAULTTOPRIMARY), &oMonitor); + RECT rcWork = oMonitor.rcWork; +#else + RECT rcWork = { 0 }; + GetWindowRect (m_pOwner->GetManager ()->GetPaintWindow (), &rcWork); +#endif + SIZE szAvailable = { rcWork.right - rcWork.left, rcWork.bottom - rcWork.top }; + szAvailable = pRoot->EstimateSize (szAvailable); + m_pm.SetInitSize (szAvailable.cx, szAvailable.cy); + + //MenuǩΪxmlĸڵ + CMenuUI *pMenuRoot = static_cast(pRoot); + ASSERT (pMenuRoot); + + SIZE szInit = m_pm.GetInitSize (); + RECT rc = { 0 }; + POINT point = m_BasedPoint; + rc.left = point.x; + rc.top = point.y; + rc.right = rc.left + szInit.cx; + rc.bottom = rc.top + szInit.cy; + + int nWidth = rc.right - rc.left; + int nHeight = rc.bottom - rc.top; + + if (m_dwAlignment & eMenuAlignment_Right) { + rc.right = point.x; + rc.left = rc.right - nWidth; + } + + if (m_dwAlignment & eMenuAlignment_Bottom) { + rc.bottom = point.y; + rc.top = rc.bottom - nHeight; + } + + SetForegroundWindow (m_hWnd); + MoveWindow (m_hWnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, FALSE); + SetWindowPos (m_hWnd, HWND_TOPMOST, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top + pMenuRoot->GetInset ().bottom + pMenuRoot->GetInset ().top, SWP_SHOWWINDOW); + } + + void CMenuWnd::ResizeSubMenu () { + // Position the popup window in absolute space + RECT rcOwner = m_pOwner->GetPos (); + RECT rc = rcOwner; + + int cxFixed = 0; + int cyFixed = 0; + +#if defined(WIN32) && !defined(UNDER_CE) + MONITORINFO oMonitor = {}; + oMonitor.cbSize = sizeof (oMonitor); + ::GetMonitorInfo (::MonitorFromWindow (GetHWND (), MONITOR_DEFAULTTOPRIMARY), &oMonitor); + RECT rcWork = oMonitor.rcWork; +#else + RECT rcWork = { 0 }; + GetWindowRect (m_pOwner->GetManager ()->GetPaintWindow (), &rcWork); +#endif + SIZE szAvailable = { rcWork.right - rcWork.left, rcWork.bottom - rcWork.top }; + + for (int it = 0; it < m_pOwner->GetCount (); it++) { + if (m_pOwner->GetItemAt (it)->GetInterface (_T ("MenuElement"))) { + CControlUI* pControl = static_cast(m_pOwner->GetItemAt (it)); + SIZE sz = pControl->EstimateSize (szAvailable); + cyFixed += sz.cy; + if (cxFixed < sz.cx) cxFixed = sz.cx; + } + } + + RECT rcWindow = { 0 }; + GetWindowRect (m_pOwner->GetManager ()->GetPaintWindow (), &rcWindow); + + rc.top = rcOwner.top; + rc.bottom = rc.top + cyFixed; + ::MapWindowRect (m_pOwner->GetManager ()->GetPaintWindow (), HWND_DESKTOP, &rc); + rc.left = rcWindow.right; + rc.right = rc.left + cxFixed; + rc.right += 2; + + bool bReachBottom = false; + bool bReachRight = false; + LONG chRightAlgin = 0; + LONG chBottomAlgin = 0; + + RECT rcPreWindow = { 0 }; + MenuObserverImpl::Iterator iterator (CMenuWnd::GetGlobalContextMenuObserver ()); + MenuMenuReceiverImplBase* pReceiver = iterator.next (); + while (pReceiver) { + CMenuWnd* pContextMenu = dynamic_cast(pReceiver); + if (pContextMenu) { + GetWindowRect (pContextMenu->GetHWND (), &rcPreWindow); + + bReachRight = rcPreWindow.left >= rcWindow.right; + bReachBottom = rcPreWindow.top >= rcWindow.bottom; + if (pContextMenu->GetHWND () == m_pOwner->GetManager ()->GetPaintWindow () || bReachBottom || bReachRight) + break; + } + pReceiver = iterator.next (); + } + + if (bReachBottom) { + rc.bottom = rcWindow.top; + rc.top = rc.bottom - cyFixed; + } + + if (bReachRight) { + rc.right = rcWindow.left; + rc.left = rc.right - cxFixed; + } + + if (rc.bottom > rcWork.bottom) { + rc.bottom = rc.top; + rc.top = rc.bottom - cyFixed; + } + + if (rc.right > rcWork.right) { + rc.right = rcWindow.left; + rc.left = rc.right - cxFixed; + } + + if (rc.top < rcWork.top) { + rc.top = rcOwner.top; + rc.bottom = rc.top + cyFixed; + } + + if (rc.left < rcWork.left) { + rc.left = rcWindow.right; + rc.right = rc.left + cxFixed; + } + + MoveWindow (m_hWnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top + m_pLayout->GetInset ().top + m_pLayout->GetInset ().bottom, FALSE); + } + + void CMenuWnd::setDPI (int DPI) { + m_pm.SetDPI (DPI); + } + + + LRESULT CMenuWnd::OnKillFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + HWND hFocusWnd = (HWND) wParam; + + BOOL bInMenuWindowList = FALSE; + ContextMenuParam param; + param.hWnd = GetHWND (); + + MenuObserverImpl::Iterator iterator (CMenuWnd::GetGlobalContextMenuObserver ()); + MenuMenuReceiverImplBase* pReceiver = iterator.next (); + while (pReceiver) { + CMenuWnd* pContextMenu = dynamic_cast(pReceiver); + if (pContextMenu && pContextMenu->GetHWND () == hFocusWnd) { + bInMenuWindowList = TRUE; + break; + } + pReceiver = iterator.next (); + } + + if (!bInMenuWindowList) { + param.wParam = 1; + CMenuWnd::GetGlobalContextMenuObserver ().RBroadcast (param); + return 0; + } + return 0; + } + LRESULT CMenuWnd::OnSize (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + SIZE szRoundCorner = m_pm.GetRoundCorner (); + if (!::IsIconic (GetHWND ())) { + RECT rcWnd = { 0 }; + ::GetWindowRect (GetHWND (), &rcWnd); + ::OffsetRect (&rcWnd, -rcWnd.left, -rcWnd.top); + rcWnd.right++; rcWnd.bottom++; + HRGN hRgn = ::CreateRoundRectRgn (rcWnd.left, rcWnd.top, rcWnd.right, rcWnd.bottom, szRoundCorner.cx, szRoundCorner.cy); + ::SetWindowRgn (GetHWND (), hRgn, TRUE); + ::DeleteObject (hRgn); + } + bHandled = FALSE; + return 0; + } + + LRESULT CMenuWnd::HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam) { + LRESULT lRes = 0; + BOOL bHandled = TRUE; + switch (uMsg) { + case WM_CREATE: + lRes = OnCreate (uMsg, wParam, lParam, bHandled); + break; + case WM_KILLFOCUS: + lRes = OnKillFocus (uMsg, wParam, lParam, bHandled); + break; + case WM_KEYDOWN: + if (wParam == VK_ESCAPE || wParam == VK_LEFT) + Close (); + break; + case WM_SIZE: + lRes = OnSize (uMsg, wParam, lParam, bHandled); + break; + case WM_CLOSE: + if (m_pOwner) { + m_pOwner->SetManager (m_pOwner->GetManager (), m_pOwner->GetParent (), false); + m_pOwner->SetPos (m_pOwner->GetPos ()); + m_pOwner->SetFocus (); + } + break; + case WM_RBUTTONDOWN: + case WM_CONTEXTMENU: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + return 0L; + break; + default: + bHandled = FALSE; + break; + } + + if (m_pm.MessageHandler (uMsg, wParam, lParam, lRes)) return lRes; + return CWindowWnd::HandleMessage (uMsg, wParam, lParam); + } + + ///////////////////////////////////////////////////////////////////////////////////// + // + IMPLEMENT_DUICONTROL (CMenuElementUI) + + CMenuElementUI::CMenuElementUI (): m_dwLineColor ((DWORD) DEFAULT_LINE_COLOR) { + m_cxyFixed.cy = ITEM_DEFAULT_HEIGHT; + m_cxyFixed.cx = ITEM_DEFAULT_WIDTH; + m_szIconSize.cy = ITEM_DEFAULT_ICON_SIZE; + m_szIconSize.cx = ITEM_DEFAULT_ICON_SIZE; + + m_rcLinePadding.top = m_rcLinePadding.bottom = 0; + m_rcLinePadding.left = DEFAULT_LINE_LEFT_INSET; + m_rcLinePadding.right = DEFAULT_LINE_RIGHT_INSET; + } + + CMenuElementUI::~CMenuElementUI () {} + + string_view_t CMenuElementUI::GetClass () const { + return _T ("MenuElementUI"); + } + + LPVOID CMenuElementUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("MenuElement")) return static_cast(this); + return CListContainerElementUI::GetInterface (pstrName); + } + + bool CMenuElementUI::DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + SIZE _cxyFixed = CMenuElementUI::m_cxyFixed; + _cxyFixed.cx = GetManager ()->GetDPIObj ()->Scale (_cxyFixed.cx); + _cxyFixed.cy = GetManager ()->GetDPIObj ()->Scale (_cxyFixed.cy); + RECT _rcLinePadding = CMenuElementUI::m_rcLinePadding; + GetManager ()->GetDPIObj ()->Scale (&_rcLinePadding); + + RECT rcTemp = { 0 }; + if (!::IntersectRect (&rcTemp, &rcPaint, &m_rcItem)) return true; + + if (m_bDrawLine) { + RECT rcLine = { m_rcItem.left + _rcLinePadding.left, m_rcItem.top + _cxyFixed.cy / 2, m_rcItem.right - _rcLinePadding.right, m_rcItem.top + _cxyFixed.cy / 2 }; + CRenderEngine::DrawLine (hDC, rcLine, 1, m_dwLineColor); + } else { + //CMenuElementUI::DrawItemBk(hDC, m_rcItem); + //DrawItemText(hDC, m_rcItem); + //DrawItemIcon(hDC, m_rcItem); + //DrawItemExpland(hDC, m_rcItem); + //for (int i = 0; i < GetCount(); ++i) + //{ + // if (!GetItemAt(i)->GetInterface(_T("MenuElement"))) { + // GetItemAt(i)->DoPaint(hDC, rcPaint); + // } + //} + + CRenderClip clip; + CRenderClip::GenerateClip (hDC, rcTemp, clip); + CMenuElementUI::DrawItemBk (hDC, m_rcItem); + DrawItemText (hDC, m_rcItem); + DrawItemIcon (hDC, m_rcItem); + DrawItemExpland (hDC, m_rcItem); + + if (m_items.GetSize () > 0) { + RECT rc = m_rcItem; + rc.left += m_rcInset.left; + rc.top += m_rcInset.top; + rc.right -= m_rcInset.right; + rc.bottom -= m_rcInset.bottom; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + + if (!::IntersectRect (&rcTemp, &rcPaint, &rc)) { + for (int it = 0; it < m_items.GetSize (); it++) { + CControlUI* pControl = static_cast(m_items[it]); + if (pControl == pStopControl) return false; + if (!pControl->IsVisible ()) continue; + if (pControl->GetInterface (_T ("MenuElement"))) continue; + if (!::IntersectRect (&rcTemp, &rcPaint, &pControl->GetPos ())) continue; + if (pControl->IsFloat ()) { + if (!::IntersectRect (&rcTemp, &m_rcItem, &pControl->GetPos ())) continue; + if (!pControl->Paint (hDC, rcPaint, pStopControl)) return false; + } + } + } else { + CRenderClip childClip; + CRenderClip::GenerateClip (hDC, rcTemp, childClip); + for (int it = 0; it < m_items.GetSize (); it++) { + CControlUI* pControl = static_cast(m_items[it]); + if (pControl == pStopControl) return false; + if (!pControl->IsVisible ()) continue; + if (pControl->GetInterface (_T ("MenuElement"))) continue; + if (!::IntersectRect (&rcTemp, &rcPaint, &pControl->GetPos ())) continue; + if (pControl->IsFloat ()) { + if (!::IntersectRect (&rcTemp, &m_rcItem, &pControl->GetPos ())) continue; + CRenderClip::UseOldClipBegin (hDC, childClip); + if (!pControl->Paint (hDC, rcPaint, pStopControl)) return false; + CRenderClip::UseOldClipEnd (hDC, childClip); + } else { + if (!::IntersectRect (&rcTemp, &rc, &pControl->GetPos ())) continue; + if (!pControl->Paint (hDC, rcPaint, pStopControl)) return false; + } + } + } + } + } + + if (m_pVerticalScrollBar) { + if (m_pVerticalScrollBar == pStopControl) return false; + if (m_pVerticalScrollBar->IsVisible ()) { + if (::IntersectRect (&rcTemp, &rcPaint, &m_pVerticalScrollBar->GetPos ())) { + if (!m_pVerticalScrollBar->Paint (hDC, rcPaint, pStopControl)) return false; + } + } + } + + if (m_pHorizontalScrollBar) { + if (m_pHorizontalScrollBar == pStopControl) return false; + if (m_pHorizontalScrollBar->IsVisible ()) { + if (::IntersectRect (&rcTemp, &rcPaint, &m_pHorizontalScrollBar->GetPos ())) { + if (!m_pHorizontalScrollBar->Paint (hDC, rcPaint, pStopControl)) return false; + } + } + } + return true; + } + + void CMenuElementUI::DrawItemIcon (HDC hDC, const RECT& rcItem) { + if (!m_strIcon.empty () && !(m_bCheckItem && !GetChecked ())) { + SIZE _cxyFixed = CMenuElementUI::m_cxyFixed; + _cxyFixed.cx = GetManager ()->GetDPIObj ()->Scale (_cxyFixed.cx); + _cxyFixed.cy = GetManager ()->GetDPIObj ()->Scale (_cxyFixed.cy); + + SIZE _szIconSize = CMenuElementUI::m_szIconSize; + _szIconSize.cx = GetManager ()->GetDPIObj ()->Scale (_szIconSize.cx); + _szIconSize.cy = GetManager ()->GetDPIObj ()->Scale (_szIconSize.cy); + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + RECT rcTextPadding = pInfo->rcTextPadding; + GetManager ()->GetDPIObj ()->Scale (&rcTextPadding); + int padding = (rcTextPadding.left - _szIconSize.cx) / 2; + RECT rcDest = + { + padding, + (_cxyFixed.cy - _szIconSize.cy) / 2, + padding + _szIconSize.cx, + (_cxyFixed.cy - _szIconSize.cy) / 2 + _szIconSize.cy + }; + GetManager ()->GetDPIObj ()->ScaleBack (&rcDest); + CDuiString pStrImage; + pStrImage.Format (_T ("dest='%d,%d,%d,%d'"), rcDest.left, rcDest.top, rcDest.right, rcDest.bottom); + DrawImage (hDC, m_strIcon, pStrImage); + } + } + + void CMenuElementUI::DrawItemExpland (HDC hDC, const RECT& rcItem) { + if (m_bShowExplandIcon) { + CDuiString strExplandIcon; + strExplandIcon = GetManager ()->GetDefaultAttributeList (_T ("ExplandIcon")); + if (strExplandIcon.empty ()) { + return; + } + SIZE _cxyFixed = CMenuElementUI::m_cxyFixed; + _cxyFixed.cx = GetManager ()->GetDPIObj ()->Scale (_cxyFixed.cx); + _cxyFixed.cy = GetManager ()->GetDPIObj ()->Scale (_cxyFixed.cy); + int padding = GetManager ()->GetDPIObj ()->Scale (ITEM_DEFAULT_EXPLAND_ICON_WIDTH) / 3; + const TDrawInfo* pDrawInfo = GetManager ()->GetDrawInfo (strExplandIcon, nullptr); + const TImageInfo *pImageInfo = GetManager ()->GetImageEx (pDrawInfo->sImageName, nullptr, 0); + if (!pImageInfo) { + return; + } + RECT rcDest = + { + _cxyFixed.cx - pImageInfo->nX - padding, + (_cxyFixed.cy - pImageInfo->nY) / 2, + _cxyFixed.cx - pImageInfo->nX - padding + pImageInfo->nX, + (_cxyFixed.cy - pImageInfo->nY) / 2 + pImageInfo->nY + }; + GetManager ()->GetDPIObj ()->ScaleBack (&rcDest); + CDuiString pStrImage; + pStrImage.Format (_T ("dest='%d,%d,%d,%d'"), rcDest.left, rcDest.top, rcDest.right, rcDest.bottom); + DrawImage (hDC, strExplandIcon, pStrImage); + } + } + + + void CMenuElementUI::DrawItemText (HDC hDC, const RECT& rcItem) { + CDuiString sText = GetText (); + if (sText.empty ()) return; + + if (!m_pOwner) return; + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + DWORD iTextColor = pInfo->dwTextColor; + if ((m_uButtonState & UISTATE_HOT) != 0) { + iTextColor = pInfo->dwHotTextColor; + } + if (IsSelected ()) { + iTextColor = pInfo->dwSelectedTextColor; + } + if (!IsEnabled ()) { + iTextColor = pInfo->dwDisabledTextColor; + } + int nLinks = 0; + RECT rcText = rcItem; + RECT rcTextPadding = pInfo->rcTextPadding; + GetManager ()->GetDPIObj ()->Scale (&rcTextPadding); + rcText.left += rcTextPadding.left; + rcText.right -= rcTextPadding.right; + rcText.top += rcTextPadding.top; + rcText.bottom -= rcTextPadding.bottom; + + if (pInfo->bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rcText, sText, iTextColor, \ + nullptr, nullptr, nLinks, pInfo->nFont, DT_SINGLELINE | pInfo->uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rcText, sText, iTextColor, \ + pInfo->nFont, DT_SINGLELINE | pInfo->uTextStyle); + } + + + SIZE CMenuElementUI::EstimateSize (SIZE szAvailable) { + SIZE _cxyFixed = CMenuElementUI::m_cxyFixed; + _cxyFixed.cx = GetManager ()->GetDPIObj ()->Scale (_cxyFixed.cx); + _cxyFixed.cy = GetManager ()->GetDPIObj ()->Scale (_cxyFixed.cy); + SIZE cXY = { 0 }; + for (int it = 0; it < GetCount (); it++) { + CControlUI* pControl = static_cast(GetItemAt (it)); + if (!pControl->IsVisible ()) continue; + SIZE sz = pControl->EstimateSize (szAvailable); + cXY.cy += sz.cy; + if (cXY.cx < sz.cx) + cXY.cx = sz.cx; + } + if (cXY.cy == 0) { + TListInfoUI* pInfo = m_pOwner->GetListInfo (); + + DWORD iTextColor = pInfo->dwTextColor; + if ((m_uButtonState & UISTATE_HOT) != 0) { + iTextColor = pInfo->dwHotTextColor; + } + if (IsSelected ()) { + iTextColor = pInfo->dwSelectedTextColor; + } + if (!IsEnabled ()) { + iTextColor = pInfo->dwDisabledTextColor; + } + CDuiString sText = GetText (); + + RECT rcText = { 0, 0, MAX (szAvailable.cx, _cxyFixed.cx), 9999 }; + RECT rcTextPadding = pInfo->rcTextPadding; + GetManager ()->GetDPIObj ()->Scale (&rcTextPadding); + rcText.left += rcTextPadding.left; + rcText.right -= rcTextPadding.right; + if (pInfo->bShowHtml) { + int nLinks = 0; + CRenderEngine::DrawHtmlText (m_pManager->GetPaintDC (), m_pManager, rcText, sText, iTextColor, nullptr, nullptr, nLinks, pInfo->nFont, DT_CALCRECT | pInfo->uTextStyle); + } else { + CRenderEngine::DrawText (m_pManager->GetPaintDC (), m_pManager, rcText, sText, iTextColor, pInfo->nFont, DT_CALCRECT | pInfo->uTextStyle); + } + cXY.cx = rcText.right - rcText.left + rcTextPadding.left + rcTextPadding.right; + cXY.cy = rcText.bottom - rcText.top + rcTextPadding.top + rcTextPadding.bottom; + } + + if (_cxyFixed.cy != 0) cXY.cy = _cxyFixed.cy; + if (cXY.cx < _cxyFixed.cx) + cXY.cx = _cxyFixed.cx; + + CMenuElementUI::m_cxyFixed.cy = MulDiv (cXY.cy, 100, GetManager ()->GetDPIObj ()->GetScale ()); + CMenuElementUI::m_cxyFixed.cx = MulDiv (cXY.cx, 100, GetManager ()->GetDPIObj ()->GetScale ()); + return cXY; + } + + void CMenuElementUI::DoEvent (TEventUI& event) { + if (event.Type == UIEVENT_MOUSEENTER) { + CListContainerElementUI::DoEvent (event); + if (m_pWindow) return; + bool hasSubMenu = false; + for (int i = 0; i < GetCount (); ++i) { + if (GetItemAt (i)->GetInterface (_T ("MenuElement"))) { + (static_cast(GetItemAt (i)->GetInterface (_T ("MenuElement"))))->SetVisible (true); + (static_cast(GetItemAt (i)->GetInterface (_T ("MenuElement"))))->SetInternVisible (true); + + hasSubMenu = true; + } + } + if (hasSubMenu) { + m_pOwner->SelectItem (GetIndex (), true); + CreateMenuWnd (); + } else { + ContextMenuParam param; + param.hWnd = m_pManager->GetPaintWindow (); + param.wParam = 2; + CMenuWnd::GetGlobalContextMenuObserver ().RBroadcast (param); + m_pOwner->SelectItem (GetIndex (), true); + } + return; + } + + + if (event.Type == UIEVENT_MOUSELEAVE) { + + bool hasSubMenu = false; + for (int i = 0; i < GetCount (); ++i) { + if (GetItemAt (i)->GetInterface (_T ("MenuElement"))) { + + hasSubMenu = true; + } + } + + if (!hasSubMenu) { + m_pOwner->SelectItem (-1, true); + } + } + + if (event.Type == UIEVENT_BUTTONUP) { + if (IsEnabled ()) { + CListContainerElementUI::DoEvent (event); + + if (m_pWindow) return; + + bool hasSubMenu = false; + for (int i = 0; i < GetCount (); ++i) { + if (GetItemAt (i)->GetInterface (_T ("MenuElement"))) { + (static_cast(GetItemAt (i)->GetInterface (_T ("MenuElement"))))->SetVisible (true); + (static_cast(GetItemAt (i)->GetInterface (_T ("MenuElement"))))->SetInternVisible (true); + + hasSubMenu = true; + } + } + if (hasSubMenu) { + CreateMenuWnd (); + } else { + SetChecked (!GetChecked ()); + + + bool isClosing = false; + CMenuUI* menuUI = static_cast(GetManager ()->GetRoot ()); + isClosing = (menuUI->m_pWindow->isClosing); + if (IsWindow (GetManager ()->GetPaintWindow ()) && !isClosing) { + if (CMenuWnd::GetGlobalContextMenuObserver ().GetManager ()) { + + MenuCmd* pMenuCmd = new MenuCmd (); + pMenuCmd->szName = GetName (); + pMenuCmd->szUserData = GetUserData (); + pMenuCmd->szText = GetText (); + pMenuCmd->bChecked = GetChecked (); + if (!PostMessage (CMenuWnd::GetGlobalContextMenuObserver ().GetManager ()->GetPaintWindow (), WM_MENUCLICK, (WPARAM) pMenuCmd, (LPARAM) this)) { + delete pMenuCmd; + pMenuCmd = nullptr; + } + } + } + ContextMenuParam param; + param.hWnd = m_pManager->GetPaintWindow (); + param.wParam = 1; + CMenuWnd::GetGlobalContextMenuObserver ().RBroadcast (param); + } + } + + return; + } + + if (event.Type == UIEVENT_KEYDOWN && event.chKey == VK_RIGHT) { + if (m_pWindow) return; + bool hasSubMenu = false; + for (int i = 0; i < GetCount (); ++i) { + if (GetItemAt (i)->GetInterface (_T ("MenuElement"))) { + (static_cast(GetItemAt (i)->GetInterface (_T ("MenuElement"))))->SetVisible (true); + (static_cast(GetItemAt (i)->GetInterface (_T ("MenuElement"))))->SetInternVisible (true); + hasSubMenu = true; + } + } + if (hasSubMenu) { + m_pOwner->SelectItem (GetIndex (), true); + CreateMenuWnd (); + } else { + ContextMenuParam param; + param.hWnd = m_pManager->GetPaintWindow (); + param.wParam = 2; + CMenuWnd::GetGlobalContextMenuObserver ().RBroadcast (param); + m_pOwner->SelectItem (GetIndex (), true); + } + + return; + } + + CListContainerElementUI::DoEvent (event); + } + + CMenuWnd* CMenuElementUI::GetMenuWnd () { + return m_pWindow; + } + + void CMenuElementUI::CreateMenuWnd () { + if (m_pWindow) return; + + m_pWindow = new CMenuWnd (); + ASSERT (m_pWindow); + + ContextMenuParam param; + param.hWnd = m_pManager->GetPaintWindow (); + param.wParam = 2; + CMenuWnd::GetGlobalContextMenuObserver ().RBroadcast (param); + + m_pWindow->Init (static_cast(this), _T (""), POINT { 0, 0 }, nullptr); + } + + void CMenuElementUI::SetLineType () { + m_bDrawLine = true; + if (m_cxyFixed.cy == 0 || m_cxyFixed.cy == ITEM_DEFAULT_HEIGHT) + SetFixedHeight (DEFAULT_LINE_HEIGHT); + + SetMouseChildEnabled (false); + SetMouseEnabled (false); + SetEnabled (false); + } + + void CMenuElementUI::SetLineColor (DWORD color) { + m_dwLineColor = color; + } + + DWORD CMenuElementUI::GetLineColor () const { + return m_dwLineColor; + } + void CMenuElementUI::SetLinePadding (RECT rcInset) { + m_rcLinePadding = rcInset; + } + + RECT CMenuElementUI::GetLinePadding () const { + return m_rcLinePadding; + } + + void CMenuElementUI::SetIcon (string_view_t strIcon) { + if (!strIcon.empty ()) + m_strIcon = strIcon; + } + + void CMenuElementUI::SetIconSize (LONG cx, LONG cy) { + m_szIconSize.cx = cx; + m_szIconSize.cy = cy; + } + + void CMenuElementUI::SetChecked (bool bCheck/* = true*/) { + SetItemInfo (GetName (), bCheck); + } + + bool CMenuElementUI::GetChecked () const { + string_view_t pstrName = GetName (); + if (pstrName.empty ()) return false; + + CStdStringPtrMap* mCheckInfos = CMenuWnd::GetGlobalContextMenuObserver ().GetMenuCheckInfo (); + if (mCheckInfos) { + MenuItemInfo* pItemInfo = (MenuItemInfo*) mCheckInfos->Find (pstrName); + if (pItemInfo) { + return pItemInfo->bChecked; + } + } + return false; + + } + + void CMenuElementUI::SetCheckItem (bool bCheckItem/* = false*/) { + m_bCheckItem = bCheckItem; + } + + bool CMenuElementUI::GetCheckItem () const { + return m_bCheckItem; + } + + void CMenuElementUI::SetShowExplandIcon (bool bShow) { + m_bShowExplandIcon = bShow; + } + + void CMenuElementUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("icon")) { + SetIcon (pstrValue); + } else if (pstrName == _T ("iconsize")) { + SIZE sz = FawTools::parse_size (pstrValue); + SetIconSize (sz.cx, sz.cy); + } else if (pstrName == _T ("checkitem")) { + SetCheckItem (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("ischeck")) { + CStdStringPtrMap* mCheckInfos = CMenuWnd::GetGlobalContextMenuObserver ().GetMenuCheckInfo (); + if (mCheckInfos) { + bool bFind = false; + for (int i = 0; i < mCheckInfos->GetSize (); i++) { + MenuItemInfo* itemInfo = (MenuItemInfo*) mCheckInfos->GetAt (i)->Data; + if (GetName () == itemInfo->szName) { + bFind = true; + break; + } + } + if (!bFind) SetChecked (FawTools::parse_bool (pstrValue)); + } + } else if (pstrName == _T ("linetype")) { + if (FawTools::parse_bool (pstrValue)) + SetLineType (); + } else if (pstrName == _T ("expland")) { + SetShowExplandIcon (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("linecolor")) { + SetLineColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("linepadding")) { + RECT rcInset = FawTools::parse_rect (pstrValue); + SetLinePadding (rcInset); + } else if (pstrName == _T ("height")) { + SetFixedHeight (FawTools::parse_dec (pstrValue)); + } else + CListContainerElementUI::SetAttribute (pstrName, pstrValue); + } + + + MenuItemInfo* CMenuElementUI::GetItemInfo (string_view_t pstrName) { + if (pstrName.empty ()) return nullptr; + + CStdStringPtrMap* mCheckInfos = CMenuWnd::GetGlobalContextMenuObserver ().GetMenuCheckInfo (); + if (mCheckInfos) { + MenuItemInfo* pItemInfo = (MenuItemInfo*) mCheckInfos->Find (pstrName); + if (pItemInfo) { + return pItemInfo; + } + } + + return nullptr; + } + + MenuItemInfo* CMenuElementUI::SetItemInfo (string_view_t pstrName, bool bChecked) { + if (pstrName.empty ()) return nullptr; + + CStdStringPtrMap* mCheckInfos = CMenuWnd::GetGlobalContextMenuObserver ().GetMenuCheckInfo (); + if (mCheckInfos) { + MenuItemInfo* pItemInfo = (MenuItemInfo*) mCheckInfos->Find (pstrName); + if (!pItemInfo) { + pItemInfo = new MenuItemInfo; + pItemInfo->szName = pstrName; + pItemInfo->bChecked = bChecked; + mCheckInfos->Insert (pstrName, pItemInfo); + } else { + pItemInfo->bChecked = bChecked; + } + + return pItemInfo; + } + return nullptr; + } +} // namespace DuiLib diff --git a/DuiLib/Control/UIMenu.h b/DuiLib/Control/UIMenu.h new file mode 100644 index 0000000..9a41d41 --- /dev/null +++ b/DuiLib/Control/UIMenu.h @@ -0,0 +1,356 @@ +#ifndef __UIMENU_H__ +#define __UIMENU_H__ + +#pragma once + +#include "../Utils/observer_impl_base.h" + +namespace DuiLib { + + struct ContextMenuParam { + // 1: remove all + // 2: remove the sub menu + WPARAM wParam; + HWND hWnd; + }; + + struct MenuItemInfo { + string_t szName; + bool bChecked; + }; + struct MenuCmd { + string_t szName; + string_t szUserData; + string_t szText; + BOOL bChecked; + }; + + enum MenuAlignment { + eMenuAlignment_Left = 1 << 1, + eMenuAlignment_Top = 1 << 2, + eMenuAlignment_Right = 1 << 3, + eMenuAlignment_Bottom = 1 << 4, + }; + + + enum MenuItemDefaultInfo { + ITEM_DEFAULT_HEIGHT = 30, //ÿһitemĬϸ߶ȣֻ״ʱԶ壩 + ITEM_DEFAULT_WIDTH = 150, //ڵĬϿ + + ITEM_DEFAULT_ICON_WIDTH = 26, //Ĭͼռ + ITEM_DEFAULT_ICON_SIZE = 16, //ĬͼĴС + + ITEM_DEFAULT_EXPLAND_ICON_WIDTH = 20, //Ĭ¼˵չͼռ + ITEM_DEFAULT_EXPLAND_ICON_SIZE = 9, //Ĭ¼˵չͼĴС + + DEFAULT_LINE_LEFT_INSET = ITEM_DEFAULT_ICON_WIDTH + 3, //ĬϷָߵ߾ + DEFAULT_LINE_RIGHT_INSET = 7, //ĬϷָߵұ߾ + DEFAULT_LINE_HEIGHT = 6, //ĬϷָռ߶ + DEFAULT_LINE_COLOR = 0xFFBCBFC4 //ĬϷָɫ + + }; + +#define WM_MENUCLICK WM_USER + 121 //հťϢ + + + /////////////////////////////////////////////// + class MenuMenuReceiverImplBase; + class MenuMenuObserverImplBase { + public: + virtual void AddReceiver (MenuMenuReceiverImplBase* receiver) = 0; + virtual void RemoveReceiver (MenuMenuReceiverImplBase* receiver) = 0; + virtual BOOL RBroadcast (ContextMenuParam param) = 0; + }; + ///////////////////////////////////////////////// + class MenuMenuReceiverImplBase { + public: + virtual void AddObserver (MenuMenuObserverImplBase* observer) = 0; + virtual void RemoveObserver () = 0; + virtual BOOL Receive (ContextMenuParam param) = 0; + }; + ///////////////////////////////////////////////// + + class MenuReceiverImpl; + class UILIB_API MenuObserverImpl: public MenuMenuObserverImplBase { + friend class Iterator; + public: + MenuObserverImpl (): + m_pMainWndPaintManager (nullptr), + m_pMenuCheckInfo (nullptr) { + pReceivers_ = new ReceiversVector; + } + + virtual ~MenuObserverImpl () { + if (pReceivers_) { + delete pReceivers_; + pReceivers_ = nullptr; + } + + } + + virtual void AddReceiver (MenuMenuReceiverImplBase* receiver) { + if (!receiver) + return; + + pReceivers_->push_back (receiver); + receiver->AddObserver (this); + } + + virtual void RemoveReceiver (MenuMenuReceiverImplBase* receiver) { + if (!receiver) + return; + + ReceiversVector::iterator it = pReceivers_->begin (); + for (; it != pReceivers_->end (); ++it) { + if (*it == receiver) { + pReceivers_->erase (it); + break; + } + } + } + + virtual BOOL RBroadcast (ContextMenuParam param) { + ReceiversVector::reverse_iterator it = pReceivers_->rbegin (); + for (; it != pReceivers_->rend (); ++it) { + (*it)->Receive (param); + } + + return BOOL (); + } + + + class Iterator { + MenuObserverImpl & _tbl; + DWORD index; + MenuMenuReceiverImplBase* ptr; + public: + Iterator (MenuObserverImpl & table) + : _tbl (table), index (0), ptr (nullptr) {} + + Iterator (const Iterator & v) + : _tbl (v._tbl), index (v.index), ptr (v.ptr) {} + + MenuMenuReceiverImplBase* next () { + if (index >= _tbl.pReceivers_->size ()) + return nullptr; + + for (; index < _tbl.pReceivers_->size (); ) { + ptr = (*(_tbl.pReceivers_))[index++]; + if (ptr) + return ptr; + } + return nullptr; + } + }; + + virtual void SetManger (CPaintManagerUI* pManager) { + if (pManager) + m_pMainWndPaintManager = pManager; + } + + virtual CPaintManagerUI* GetManager () const { + return m_pMainWndPaintManager; + } + + virtual void SetMenuCheckInfo (CStdStringPtrMap* pInfo) { + if (pInfo) + m_pMenuCheckInfo = pInfo; + else + m_pMenuCheckInfo = nullptr; + } + + virtual CStdStringPtrMap* GetMenuCheckInfo () const { + return m_pMenuCheckInfo; + } + + protected: + typedef std::vector ReceiversVector; + ReceiversVector *pReceivers_; + CPaintManagerUI* m_pMainWndPaintManager; + CStdStringPtrMap* m_pMenuCheckInfo; + }; + + //////////////////////////////////////////////////// + class UILIB_API MenuReceiverImpl: public MenuMenuReceiverImplBase { + public: + MenuReceiverImpl () { + pObservers_ = new ObserversVector; + } + + virtual ~MenuReceiverImpl () { + if (pObservers_) { + delete pObservers_; + pObservers_ = nullptr; + } + } + + virtual void AddObserver (MenuMenuObserverImplBase* observer) { + pObservers_->push_back (observer); + } + + virtual void RemoveObserver () { + ObserversVector::iterator it = pObservers_->begin (); + for (; it != pObservers_->end (); ++it) { + (*it)->RemoveReceiver (this); + } + } + + virtual BOOL Receive (ContextMenuParam param) { + return BOOL (); + } + + protected: + typedef std::vector ObserversVector; + ObserversVector* pObservers_; + }; + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class CListUI; + class CMenuWnd; + class UILIB_API CMenuUI: public CListUI { + DECLARE_DUICONTROL (CMenuUI) + public: + CMenuUI (); + virtual ~CMenuUI (); + CMenuWnd *m_pWindow = nullptr; + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetListType (); + + virtual void DoEvent (TEventUI& event); + + virtual bool Add (CControlUI* pControl); + virtual bool AddAt (CControlUI* pControl, int iIndex); + + virtual int GetItemIndex (CControlUI* pControl) const; + virtual bool SetItemIndex (CControlUI* pControl, int iIndex); + virtual bool Remove (CControlUI* pControl); + + SIZE EstimateSize (SIZE szAvailable); + + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + }; + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class CMenuElementUI; + class UILIB_API CMenuWnd: public CWindowWnd, public MenuReceiverImpl, public INotifyUI, public IDialogBuilderCallback { + public: + static MenuObserverImpl& GetGlobalContextMenuObserver () { + static MenuObserverImpl s_context_menu_observer; + return s_context_menu_observer; + } + static CMenuWnd* CreateMenu (CMenuElementUI* pOwner, std::variant xml, POINT point, + CPaintManagerUI* pMainPaintManager, CStdStringPtrMap* pMenuCheckInfo = nullptr, + DWORD dwAlignment = eMenuAlignment_Left | eMenuAlignment_Top); + static void DestroyMenu (); + static MenuItemInfo* SetMenuItemInfo (string_view_t pstrName, bool bChecked); + + public: + CMenuWnd (); + virtual ~CMenuWnd (); + void Close (UINT nRet = IDOK); + bool isClosing; + /* + *@pOwner һ˵ҪָDz˵ڲʹõ + *@xml ˵IJļ + *@point ˵Ͻ + *@pMainPaintManager ˵ĸָ + *@pMenuCheckInfo ˵ĵѡ͸ѡϢṹָ + *@dwAlignment ˵ijλãĬϳ²ࡣ + */ + + void Init (CMenuElementUI* pOwner, std::variant xml, POINT point, + CPaintManagerUI* pMainPaintManager, CStdStringPtrMap* pMenuCheckInfo = nullptr, + DWORD dwAlignment = eMenuAlignment_Left | eMenuAlignment_Top); + string_view_t GetWindowClassName () const; + void OnFinalMessage (HWND hWnd); + void Notify (TNotifyUI& msg); + CControlUI* CreateControl (string_view_t pstrClassName); + + LRESULT OnCreate (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnKillFocus (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnSize (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam); + + BOOL Receive (ContextMenuParam param); + + // ȡ˵ؼڶ̬Ӳ˵ + CMenuUI* GetMenuUI (); + + // µ˵ĴС + void ResizeMenu (); + + // µӲ˵ĴС + void ResizeSubMenu (); + void setDPI (int DPI); + + public: + + POINT m_BasedPoint; + std::variant m_xml; + CPaintManagerUI m_pm; + CMenuElementUI* m_pOwner; + CMenuUI *m_pLayout; + DWORD m_dwAlignment; //˵뷽ʽ + }; + + class CListContainerElementUI; + class UILIB_API CMenuElementUI: public CListContainerElementUI { + DECLARE_DUICONTROL (CMenuElementUI) + friend CMenuWnd; + public: + CMenuElementUI (); + virtual ~CMenuElementUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + bool DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl); + void DrawItemText (HDC hDC, const RECT& rcItem); + SIZE EstimateSize (SIZE szAvailable); + + void DoEvent (TEventUI& event); + + CMenuWnd* GetMenuWnd (); + void CreateMenuWnd (); + + void SetLineType (); + void SetLineColor (DWORD color); + DWORD GetLineColor () const; + void SetLinePadding (RECT rcInset); + RECT GetLinePadding () const; + void SetIcon (string_view_t strIcon); + void SetIconSize (LONG cx, LONG cy); + void DrawItemIcon (HDC hDC, const RECT& rcItem); + void SetChecked (bool bCheck = true); + bool GetChecked () const; + void SetCheckItem (bool bCheckItem = false); + bool GetCheckItem () const; + + void SetShowExplandIcon (bool bShow); + void DrawItemExpland (HDC hDC, const RECT& rcItem); + + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + MenuItemInfo* GetItemInfo (string_view_t pstrName); + MenuItemInfo* SetItemInfo (string_view_t pstrName, bool bChecked); + protected: + CMenuWnd *m_pWindow = nullptr; + + bool m_bDrawLine = false; //ָ + DWORD m_dwLineColor; //ָɫ + RECT m_rcLinePadding; //ָߵұ߾ + + SIZE m_szIconSize; //ͼ + CDuiString m_strIcon; + bool m_bCheckItem = false; + + bool m_bShowExplandIcon = false; + }; + +} // namespace DuiLib + +#endif // __UIMENU_H__ diff --git a/DuiLib/Control/UIOption.cpp b/DuiLib/Control/UIOption.cpp new file mode 100644 index 0000000..e95fb2d --- /dev/null +++ b/DuiLib/Control/UIOption.cpp @@ -0,0 +1,383 @@ +#include "StdAfx.h" +#include "UIOption.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (COptionUI) + COptionUI::COptionUI () {} + + COptionUI::~COptionUI () { + if (!m_sGroupName.empty () && m_pManager) m_pManager->RemoveOptionGroup (m_sGroupName, this); + } + + string_view_t COptionUI::GetClass () const { + return _T ("OptionUI"); + } + + LPVOID COptionUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_OPTION) return static_cast(this); + return CButtonUI::GetInterface (pstrName); + } + + void COptionUI::SetManager (CPaintManagerUI* pManager, CControlUI* pParent, bool bInit) { + CControlUI::SetManager (pManager, pParent, bInit); + if (bInit && !m_sGroupName.empty ()) { + if (m_pManager) m_pManager->AddOptionGroup (m_sGroupName, this); + } + } + + string_view_t COptionUI::GetGroup () const { + return m_sGroupName; + } + + void COptionUI::SetGroup (string_view_t pStrGroupName) { + if (pStrGroupName.empty ()) { + if (m_sGroupName.empty ()) return; + m_sGroupName.clear (); + } else { + if (m_sGroupName == pStrGroupName) return; + if (!m_sGroupName.empty () && m_pManager) m_pManager->RemoveOptionGroup (m_sGroupName, this); + m_sGroupName = pStrGroupName; + } + + if (!m_sGroupName.empty ()) { + if (m_pManager) m_pManager->AddOptionGroup (m_sGroupName, this); + } else { + if (m_pManager) m_pManager->RemoveOptionGroup (m_sGroupName, this); + } + + Selected (m_bSelected); + } + + bool COptionUI::IsSelected () const { + return m_bSelected; + } + + void COptionUI::Selected (bool bSelected, bool bMsg/* = true*/) { + if (m_bSelected == bSelected) return; + + m_bSelected = bSelected; + if (m_bSelected) m_uButtonState |= UISTATE_SELECTED; + else m_uButtonState &= ~UISTATE_SELECTED; + + if (m_pManager) { + if (!m_sGroupName.empty ()) { + if (m_bSelected) { + CStdPtrArray* aOptionGroup = m_pManager->GetOptionGroup (m_sGroupName); + for (int i = 0; i < aOptionGroup->GetSize (); i++) { + COptionUI* pControl = static_cast(aOptionGroup->GetAt (i)); + if (pControl != this) { + pControl->Selected (false, bMsg); + } + } + if (bMsg) { + m_pManager->SendNotify (this, DUI_MSGTYPE_SELECTCHANGED); + } + } + } else { + if (bMsg) { + m_pManager->SendNotify (this, DUI_MSGTYPE_SELECTCHANGED); + } + } + } + + Invalidate (); + } + + bool COptionUI::Activate () { + if (!CButtonUI::Activate ()) return false; + if (!m_sGroupName.empty ()) Selected (true); + else Selected (!m_bSelected); + + return true; + } + + void COptionUI::SetEnabled (bool bEnable) { + CControlUI::SetEnabled (bEnable); + if (!IsEnabled ()) { + if (m_bSelected) m_uButtonState = UISTATE_SELECTED; + else m_uButtonState = 0; + } + } + + string_view_t COptionUI::GetSelectedImage () { + return m_sSelectedImage; + } + + void COptionUI::SetSelectedImage (string_view_t pStrImage) { + m_sSelectedImage = pStrImage; + Invalidate (); + } + + string_view_t COptionUI::GetSelectedHotImage () { + return m_sSelectedHotImage; + } + + void COptionUI::SetSelectedHotImage (string_view_t pStrImage) { + m_sSelectedHotImage = pStrImage; + Invalidate (); + } + + string_view_t COptionUI::GetSelectedPushedImage () { + return m_sSelectedPushedImage; + } + + void COptionUI::SetSelectedPushedImage (string_view_t pStrImage) { + m_sSelectedPushedImage = pStrImage; + Invalidate (); + } + + void COptionUI::SetSelectedTextColor (DWORD dwTextColor) { + m_dwSelectedTextColor = dwTextColor; + } + + DWORD COptionUI::GetSelectedTextColor () { + if (m_dwSelectedTextColor == 0) m_dwSelectedTextColor = m_pManager->GetDefaultFontColor (); + return m_dwSelectedTextColor; + } + + void COptionUI::SetSelectedBkColor (DWORD dwBkColor) { + m_dwSelectedBkColor = dwBkColor; + } + + DWORD COptionUI::GetSelectedBkColor () { + return m_dwSelectedBkColor; + } + + string_view_t COptionUI::GetSelectedForedImage () { + return m_sSelectedForeImage; + } + + void COptionUI::SetSelectedForedImage (string_view_t pStrImage) { + m_sSelectedForeImage = pStrImage; + Invalidate (); + } + + void COptionUI::SetSelectedStateCount (int nCount) { + m_nSelectedStateCount = nCount; + Invalidate (); + } + + int COptionUI::GetSelectedStateCount () const { + return m_nSelectedStateCount; + } + + string_view_t COptionUI::GetSelectedStateImage () { + return m_sSelectedStateImage; + } + + void COptionUI::SetSelectedStateImage (string_view_t pStrImage) { + m_sSelectedStateImage = pStrImage; + Invalidate (); + } + void COptionUI::SetSelectedFont (int index) { + m_iSelectedFont = index; + Invalidate (); + } + + int COptionUI::GetSelectedFont () const { + return m_iSelectedFont; + } + void COptionUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("group")) SetGroup (pstrValue); + else if (pstrName == _T ("selected")) Selected (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("selectedimage")) SetSelectedImage (pstrValue); + else if (pstrName == _T ("selectedhotimage")) SetSelectedHotImage (pstrValue); + else if (pstrName == _T ("selectedpushedimage")) SetSelectedPushedImage (pstrValue); + else if (pstrName == _T ("selectedforeimage")) SetSelectedForedImage (pstrValue); + else if (pstrName == _T ("selectedstateimage")) SetSelectedStateImage (pstrValue); + else if (pstrName == _T ("selectedstatecount")) SetSelectedStateCount (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("selectedbkcolor")) SetSelectedBkColor ((DWORD) FawTools::parse_hex (pstrValue)); + else if (pstrName == _T ("selectedtextcolor")) SetSelectedTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + else if (pstrName == _T ("selectedfont")) SetSelectedFont (FawTools::parse_dec (pstrValue)); + else CButtonUI::SetAttribute (pstrName, pstrValue); + } + + void COptionUI::PaintBkColor (HDC hDC) { + if (IsSelected ()) { + if (m_dwSelectedBkColor != 0) { + CRenderEngine::DrawColor (hDC, m_rcPaint, GetAdjustColor (m_dwSelectedBkColor)); + } + } else { + return CButtonUI::PaintBkColor (hDC); + } + } + + void COptionUI::PaintStatusImage (HDC hDC) { + if (IsSelected ()) { + if (!m_sSelectedStateImage.empty () && m_nSelectedStateCount > 0) { + TDrawInfo info; + info.Parse (m_sSelectedStateImage, _T (""), m_pManager); + const TImageInfo* pImage = m_pManager->GetImageEx (info.sImageName, info.sResType, info.dwMask, info.bHSL); + if (m_sSelectedImage.empty () && pImage) { + SIZE szImage = { pImage->nX, pImage->nY }; + SIZE szStatus = { pImage->nX / m_nSelectedStateCount, pImage->nY }; + if (szImage.cx > 0 && szImage.cy > 0) { + RECT rcSrc = { 0, 0, szImage.cx, szImage.cy }; + if (m_nSelectedStateCount > 0) { + int iLeft = rcSrc.left + 0 * szStatus.cx; + int iRight = iLeft + szStatus.cx; + int iTop = rcSrc.top; + int iBottom = iTop + szStatus.cy; + m_sSelectedImage.Format (_T ("res='%s' restype='%s' dest='%d,%d,%d,%d' source='%d,%d,%d,%d'"), info.sImageName.c_str (), info.sResType.c_str (), info.rcDest.left, info.rcDest.top, info.rcDest.right, info.rcDest.bottom, iLeft, iTop, iRight, iBottom); + } + if (m_nSelectedStateCount > 1) { + int iLeft = rcSrc.left + 1 * szStatus.cx; + int iRight = iLeft + szStatus.cx; + int iTop = rcSrc.top; + int iBottom = iTop + szStatus.cy; + m_sSelectedHotImage.Format (_T ("res='%s' restype='%s' dest='%d,%d,%d,%d' source='%d,%d,%d,%d'"), info.sImageName.c_str (), info.sResType.c_str (), info.rcDest.left, info.rcDest.top, info.rcDest.right, info.rcDest.bottom, iLeft, iTop, iRight, iBottom); + m_sSelectedPushedImage.Format (_T ("res='%s' restype='%s' dest='%d,%d,%d,%d' source='%d,%d,%d,%d'"), info.sImageName.c_str (), info.sResType.c_str (), info.rcDest.left, info.rcDest.top, info.rcDest.right, info.rcDest.bottom, iLeft, iTop, iRight, iBottom); + } + if (m_nSelectedStateCount > 2) { + int iLeft = rcSrc.left + 2 * szStatus.cx; + int iRight = iLeft + szStatus.cx; + int iTop = rcSrc.top; + int iBottom = iTop + szStatus.cy; + m_sSelectedPushedImage.Format (_T ("res='%s' restype='%s' dest='%d,%d,%d,%d' source='%d,%d,%d,%d'"), info.sImageName.c_str (), info.sResType.c_str (), info.rcDest.left, info.rcDest.top, info.rcDest.right, info.rcDest.bottom, iLeft, iTop, iRight, iBottom); + } + } + } + } + + + if ((m_uButtonState & UISTATE_PUSHED) != 0 && !m_sSelectedPushedImage.empty ()) { + if (!DrawImage (hDC, m_sSelectedPushedImage)) { + } else return; + } else if ((m_uButtonState & UISTATE_HOT) != 0 && !m_sSelectedHotImage.empty ()) { + if (!DrawImage (hDC, m_sSelectedHotImage)) { + } else return; + } + + if (!m_sSelectedImage.empty ()) { + if (!DrawImage (hDC, m_sSelectedImage)) { + } + } + } else { + CButtonUI::PaintStatusImage (hDC); + } + } + + void COptionUI::PaintForeImage (HDC hDC) { + if (IsSelected ()) { + if (!m_sSelectedForeImage.empty ()) { + if (!DrawImage (hDC, m_sSelectedForeImage)) { + } else return; + } + } + + return CButtonUI::PaintForeImage (hDC); + } + + void COptionUI::PaintText (HDC hDC) { + if ((m_uButtonState & UISTATE_SELECTED) != 0) { + DWORD oldTextColor = m_dwTextColor; + if (m_dwSelectedTextColor != 0) m_dwTextColor = m_dwSelectedTextColor; + + if (m_dwTextColor == 0) m_dwTextColor = m_pManager->GetDefaultFontColor (); + if (m_dwDisabledTextColor == 0) m_dwDisabledTextColor = m_pManager->GetDefaultDisabledColor (); + + int iFont = GetFont (); + if (GetSelectedFont () != -1) { + iFont = GetSelectedFont (); + } + CDuiString sText = GetText (); + if (sText.empty ()) return; + int nLinks = 0; + RECT rc = m_rcItem; + RECT _rcTextPadding = CButtonUI::m_rcTextPadding; + GetManager ()->GetDPIObj ()->Scale (&_rcTextPadding); + rc.left += _rcTextPadding.left; + rc.right -= _rcTextPadding.right; + rc.top += _rcTextPadding.top; + rc.bottom -= _rcTextPadding.bottom; + + if (m_bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rc, sText, IsEnabled () ? m_dwTextColor : m_dwDisabledTextColor, \ + nullptr, nullptr, nLinks, iFont, m_uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rc, sText, IsEnabled () ? m_dwTextColor : m_dwDisabledTextColor, \ + iFont, m_uTextStyle); + + m_dwTextColor = oldTextColor; + } else + CButtonUI::PaintText (hDC); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // + IMPLEMENT_DUICONTROL (CCheckBoxUI) + + CCheckBoxUI::CCheckBoxUI (): m_bAutoCheck (false) { + + } + + string_view_t CCheckBoxUI::GetClass () const { + return _T ("CheckBoxUI"); + } + LPVOID CCheckBoxUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_CHECKBOX) return static_cast(this); + return COptionUI::GetInterface (pstrName); + } + + void CCheckBoxUI::SetCheck (bool bCheck) { + Selected (bCheck); + } + + bool CCheckBoxUI::GetCheck () const { + return IsSelected (); + } + + void CCheckBoxUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("EnableAutoCheck")) SetAutoCheck (FawTools::parse_bool (pstrValue)); + + COptionUI::SetAttribute (pstrName, pstrValue); + } + void CCheckBoxUI::SetAutoCheck (bool bEnable) { + m_bAutoCheck = bEnable; + } + void CCheckBoxUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else COptionUI::DoEvent (event); + return; + } + if (m_bAutoCheck && (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK)) { + if (::PtInRect (&m_rcItem, event.ptMouse) && IsEnabled ()) { + SetCheck (!GetCheck ()); + m_pManager->SendNotify (this, DUI_MSGTYPE_CHECKCLICK, 0, 0); + Invalidate (); + } + return; + } + COptionUI::DoEvent (event); + } + void CCheckBoxUI::Selected (bool bSelected, bool bMsg/* = true*/) { + if (m_bSelected == bSelected) return; + m_bSelected = bSelected; + if (m_bSelected) m_uButtonState |= UISTATE_SELECTED; + else m_uButtonState &= ~UISTATE_SELECTED; + + if (m_pManager) { + if (!m_sGroupName.empty ()) { + if (m_bSelected) { + CStdPtrArray* aOptionGroup = m_pManager->GetOptionGroup (m_sGroupName); + for (int i = 0; i < aOptionGroup->GetSize (); i++) { + COptionUI* pControl = static_cast(aOptionGroup->GetAt (i)); + if (pControl != this) { + pControl->Selected (false, bMsg); + } + } + if (bMsg) { + m_pManager->SendNotify (this, DUI_MSGTYPE_SELECTCHANGED, m_bSelected, 0); + } + } + } else { + if (bMsg) { + m_pManager->SendNotify (this, DUI_MSGTYPE_SELECTCHANGED, m_bSelected, 0); + } + } + } + + Invalidate (); + } +} \ No newline at end of file diff --git a/DuiLib/Control/UIOption.h b/DuiLib/Control/UIOption.h new file mode 100644 index 0000000..bc220ce --- /dev/null +++ b/DuiLib/Control/UIOption.h @@ -0,0 +1,100 @@ +#ifndef __UIOPTION_H__ +#define __UIOPTION_H__ + +#pragma once + +namespace DuiLib { + class UILIB_API COptionUI: public CButtonUI { + DECLARE_DUICONTROL (COptionUI) + public: + COptionUI (); + virtual ~COptionUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + void SetManager (CPaintManagerUI* pManager, CControlUI* pParent, bool bInit = true); + + bool Activate (); + void SetEnabled (bool bEnable = true); + + string_view_t GetSelectedImage (); + void SetSelectedImage (string_view_t pStrImage); + + string_view_t GetSelectedHotImage (); + void SetSelectedHotImage (string_view_t pStrImage); + + string_view_t GetSelectedPushedImage (); + void SetSelectedPushedImage (string_view_t pStrImage); + + void SetSelectedTextColor (DWORD dwTextColor); + DWORD GetSelectedTextColor (); + + void SetSelectedBkColor (DWORD dwBkColor); + DWORD GetSelectedBkColor (); + + string_view_t GetSelectedForedImage (); + void SetSelectedForedImage (string_view_t pStrImage); + + void SetSelectedStateCount (int nCount); + int GetSelectedStateCount () const; + virtual string_view_t GetSelectedStateImage (); + virtual void SetSelectedStateImage (string_view_t pStrImage); + + void SetSelectedFont (int index); + int GetSelectedFont () const; + + string_view_t GetGroup () const; + void SetGroup (string_view_t pStrGroupName = _T ("")); + bool IsSelected () const; + virtual void Selected (bool bSelected, bool bMsg = true); + + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + void PaintBkColor (HDC hDC); + void PaintStatusImage (HDC hDC); + void PaintForeImage (HDC hDC); + void PaintText (HDC hDC); + + protected: + bool m_bSelected = false; + CDuiString m_sGroupName; + + int m_iSelectedFont = -1; + + DWORD m_dwSelectedBkColor = 0; + DWORD m_dwSelectedTextColor = 0; + + CDuiString m_sSelectedImage; + CDuiString m_sSelectedHotImage; + CDuiString m_sSelectedPushedImage; + CDuiString m_sSelectedForeImage; + + int m_nSelectedStateCount = 0; + CDuiString m_sSelectedStateImage; + }; + + class UILIB_API CCheckBoxUI: public COptionUI { + DECLARE_DUICONTROL (CCheckBoxUI) + public: + CCheckBoxUI (); + + public: + virtual string_view_t GetClass () const; + virtual LPVOID GetInterface (string_view_t pstrName); + + void SetCheck (bool bCheck); + bool GetCheck () const; + + public: + virtual void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + void SetAutoCheck (bool bEnable); + virtual void DoEvent (TEventUI& event); + virtual void Selected (bool bSelected, bool bMsg = true); + + protected: + bool m_bAutoCheck; + }; +} // namespace DuiLib + +#endif // __UIOPTION_H__ \ No newline at end of file diff --git a/DuiLib/Control/UIProgress.cpp b/DuiLib/Control/UIProgress.cpp new file mode 100644 index 0000000..5815fb8 --- /dev/null +++ b/DuiLib/Control/UIProgress.cpp @@ -0,0 +1,151 @@ +#include "StdAfx.h" +#include "UIProgress.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CProgressUI) + + CProgressUI::CProgressUI () { + m_uTextStyle = DT_SINGLELINE | DT_CENTER; + SetFixedHeight (12); + } + + string_view_t CProgressUI::GetClass () const { + return _T ("ProgressUI"); + } + + LPVOID CProgressUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_PROGRESS) return static_cast(this); + return CLabelUI::GetInterface (pstrName); + } + + bool CProgressUI::IsShowText () { + return m_bShowText; + } + + void CProgressUI::SetShowText (bool bShowText) { + if (m_bShowText == bShowText) return; + m_bShowText = bShowText; + if (!m_bShowText) SetText (_T ("")); + } + + bool CProgressUI::IsHorizontal () { + return m_bHorizontal; + } + + void CProgressUI::SetHorizontal (bool bHorizontal) { + if (m_bHorizontal == bHorizontal) return; + + m_bHorizontal = bHorizontal; + Invalidate (); + } + + int CProgressUI::GetMinValue () const { + return m_nMin; + } + + void CProgressUI::SetMinValue (int nMin) { + m_nMin = nMin; + Invalidate (); + } + + int CProgressUI::GetMaxValue () const { + return m_nMax; + } + + void CProgressUI::SetMaxValue (int nMax) { + m_nMax = nMax; + Invalidate (); + } + + int CProgressUI::GetValue () const { + return m_nValue; + } + + void CProgressUI::SetValue (int nValue) { + if (nValue == m_nValue || nValue m_nMax) { + return; + } + m_nValue = nValue; + Invalidate (); + UpdateText (); + } + + void CProgressUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("hor")) SetHorizontal (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("min")) SetMinValue (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("max")) SetMaxValue (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("value")) SetValue (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("isstretchfore")) SetStretchForeImage (FawTools::parse_bool (pstrValue)); + else CLabelUI::SetAttribute (pstrName, pstrValue); + } + + void CProgressUI::PaintForeColor (HDC hDC) { + if (m_dwForeColor == 0) return; + + if (m_nMax <= m_nMin) m_nMax = m_nMin + 1; + if (m_nValue > m_nMax) m_nValue = m_nMax; + if (m_nValue < m_nMin) m_nValue = m_nMin; + + RECT rc = m_rcItem; + if (m_bHorizontal) { + rc.right = m_rcItem.left + (m_nValue - m_nMin) * (m_rcItem.right - m_rcItem.left) / (m_nMax - m_nMin); + } else { + rc.bottom = m_rcItem.top + (m_rcItem.bottom - m_rcItem.top) * (m_nMax - m_nValue) / (m_nMax - m_nMin); + + } + + CRenderEngine::DrawColor (hDC, rc, GetAdjustColor (m_dwForeColor)); + } + + void CProgressUI::PaintForeImage (HDC hDC) { + if (m_nMax <= m_nMin) m_nMax = m_nMin + 1; + if (m_nValue > m_nMax) m_nValue = m_nMax; + if (m_nValue < m_nMin) m_nValue = m_nMin; + + RECT rc = { 0 }; + if (m_bHorizontal) { + rc.right = (m_nValue - m_nMin) * (m_rcItem.right - m_rcItem.left) / (m_nMax - m_nMin); + rc.bottom = m_rcItem.bottom - m_rcItem.top; + } else { + rc.top = (m_rcItem.bottom - m_rcItem.top) * (m_nMax - m_nValue) / (m_nMax - m_nMin); + rc.right = m_rcItem.right - m_rcItem.left; + rc.bottom = m_rcItem.bottom - m_rcItem.top; + } + + if (!m_sForeImage.empty ()) { + m_sForeImageModify.clear (); + int sw = MulDiv (rc.right - rc.left, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int sh = MulDiv (rc.bottom - rc.top, 100, GetManager ()->GetDPIObj ()->GetScale ()); + rc.left = MulDiv (rc.left, 100, GetManager ()->GetDPIObj ()->GetScale ()); + rc.top = MulDiv (rc.top, 100, GetManager ()->GetDPIObj ()->GetScale ()); + rc.right = rc.left + sw; + rc.bottom = rc.top + sh; + if (m_bStretchForeImage) { + m_sForeImageModify.Format (_T ("dest='%d,%d,%d,%d'"), rc.left, rc.top, rc.right, rc.bottom); + } else { + m_sForeImageModify.Format (_T ("dest='%d,%d,%d,%d' source='%d,%d,%d,%d'"), rc.left, rc.top, rc.right, rc.bottom, rc.left, rc.top, rc.right, rc.bottom); + } + + if (DrawImage (hDC, m_sForeImage, m_sForeImageModify)) + return; + } + } + + bool CProgressUI::IsStretchForeImage () { + return m_bStretchForeImage; + } + + void CProgressUI::SetStretchForeImage (bool bStretchForeImage /*= true*/) { + if (m_bStretchForeImage == bStretchForeImage) return; + m_bStretchForeImage = bStretchForeImage; + Invalidate (); + } + + void CProgressUI::UpdateText () { + if (m_bShowText) { + CDuiString sText; + sText.Format (_T ("%.0f%%"), (m_nValue - m_nMin) * 100.0f / (m_nMax - m_nMin)); + SetText (sText); + } + } +} diff --git a/DuiLib/Control/UIProgress.h b/DuiLib/Control/UIProgress.h new file mode 100644 index 0000000..3c7cadc --- /dev/null +++ b/DuiLib/Control/UIProgress.h @@ -0,0 +1,45 @@ +#ifndef __UIPROGRESS_H__ +#define __UIPROGRESS_H__ + +#pragma once + +namespace DuiLib { + class UILIB_API CProgressUI: public CLabelUI { + DECLARE_DUICONTROL (CProgressUI) + public: + CProgressUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + bool IsShowText (); + void SetShowText (bool bShowText = true); + bool IsHorizontal (); + void SetHorizontal (bool bHorizontal = true); + bool IsStretchForeImage (); + void SetStretchForeImage (bool bStretchForeImage = true); + int GetMinValue () const; + void SetMinValue (int nMin); + int GetMaxValue () const; + void SetMaxValue (int nMax); + int GetValue () const; + void SetValue (int nValue); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + void PaintForeColor (HDC hDC); + void PaintForeImage (HDC hDC); + virtual void UpdateText (); + + protected: + bool m_bShowText = false; + bool m_bHorizontal = true; + bool m_bStretchForeImage = true; + int m_nMax = 100; + int m_nMin = 0; + int m_nValue = 0; + + CDuiString m_sForeImageModify; + }; + +} // namespace DuiLib + +#endif // __UIPROGRESS_H__ diff --git a/DuiLib/Control/UIRichEdit.cpp b/DuiLib/Control/UIRichEdit.cpp new file mode 100644 index 0000000..7255dd8 --- /dev/null +++ b/DuiLib/Control/UIRichEdit.cpp @@ -0,0 +1,2424 @@ +#include "StdAfx.h" +#include "UIRichEdit.h" + +#ifdef _USEIMM +#include +#pragma comment(lib, "imm32.lib") +#endif +// These constants are for backward compatibility. They are the +// sizes used for initialization and reset in RichEdit 1.0 + +namespace DuiLib { + +#define ID_RICH_UNDO 101 +#define ID_RICH_CUT 102 +#define ID_RICH_COPY 103 +#define ID_RICH_PASTE 104 +#define ID_RICH_CLEAR 105 +#define ID_RICH_SELECTALL 106 +#define ID_RICH_REDO 107 + + const LONG cInitTextMax = (32 * 1024) - 1; + + EXTERN_C const IID IID_ITextServices = { // 8d33f740-cf58-11ce-a89d-00aa006cadc5 + 0x8d33f740, + 0xcf58, + 0x11ce, + { 0xa8, 0x9d, 0x00, 0xaa, 0x00, 0x6c, 0xad, 0xc5 } + }; + + EXTERN_C const IID IID_ITextHost = { /* c5bdd8d0-d26e-11ce-a89e-00aa006cadc5 */ + 0xc5bdd8d0, + 0xd26e, + 0x11ce, + { 0xa8, 0x9e, 0x00, 0xaa, 0x00, 0x6c, 0xad, 0xc5 } + }; + +#ifndef LY_PER_INCH +#define LY_PER_INCH 1440 +#endif + +#ifndef HIMETRIC_PER_INCH +#define HIMETRIC_PER_INCH 2540 +#endif + +#include + + class CTxtWinHost: public ITextHost { + public: + CTxtWinHost (); + BOOL Init (CRichEditUI *re, const CREATESTRUCT *pcs); + virtual ~CTxtWinHost (); + + ITextServices* GetTextServices (void) { return pserv; } + void SetClientRect (RECT *prc); + RECT* GetClientRect () { return &rcClient; } + BOOL IsWordWrap (void) { return fWordWrap; } + void SetWordWrap (BOOL fWordWrap); + BOOL IsReadOnly (); + void SetReadOnly (BOOL fReadOnly); + + void SetFont (HFONT hFont); + void SetColor (DWORD dwColor); + SIZEL* GetExtent (); + void SetExtent (SIZEL *psizelExtent); + void LimitText (LONG nChars); + BOOL IsCaptured (); + BOOL IsShowCaret (); + void NeedFreshCaret (); + INT GetCaretWidth (); + INT GetCaretHeight (); + + BOOL GetAllowBeep (); + void SetAllowBeep (BOOL fAllowBeep); + WORD GetDefaultAlign (); + void SetDefaultAlign (WORD wNewAlign); + BOOL GetRichTextFlag (); + void SetRichTextFlag (BOOL fNew); + LONG GetDefaultLeftIndent (); + void SetDefaultLeftIndent (LONG lNewIndent); + BOOL SetSaveSelection (BOOL fSaveSelection); + HRESULT OnTxInPlaceDeactivate (); + HRESULT OnTxInPlaceActivate (LPCRECT prcClient); + BOOL GetActiveState (void) { + return fInplaceActive; + } + BOOL DoSetCursor (RECT *prc, POINT *pt); + void SetTransparent (BOOL fTransparent); + void GetControlRect (LPRECT prc); + LONG SetAccelPos (LONG laccelpos); + WCHAR SetPasswordChar (WCHAR chPasswordChar); + void SetDisabled (BOOL fOn); + LONG SetSelBarWidth (LONG lSelBarWidth); + BOOL GetTimerState (); + + void SetCharFormat (CHARFORMAT2W &c); + void SetParaFormat (PARAFORMAT2 &p); + + // ----------------------------- + // IUnknown interface + // ----------------------------- + virtual HRESULT _stdcall QueryInterface (REFIID riid, void **ppvObject); + virtual ULONG _stdcall AddRef (void); + virtual ULONG _stdcall Release (void); + + // ----------------------------- + // ITextHost interface + // ----------------------------- + virtual HDC TxGetDC (); + virtual INT TxReleaseDC (HDC hdc); + virtual BOOL TxShowScrollBar (INT fnBar, BOOL fShow); + virtual BOOL TxEnableScrollBar (INT fuSBFlags, INT fuArrowflags); + virtual BOOL TxSetScrollRange (INT fnBar, LONG nMinPos, INT nMaxPos, BOOL fRedraw); + virtual BOOL TxSetScrollPos (INT fnBar, INT nPos, BOOL fRedraw); + virtual void TxInvalidateRect (LPCRECT prc, BOOL fMode); + virtual void TxViewChange (BOOL fUpdate); + virtual BOOL TxCreateCaret (HBITMAP hbmp, INT xWidth, INT yHeight); + virtual BOOL TxShowCaret (BOOL fShow); + virtual BOOL TxSetCaretPos (INT x, INT y); + virtual BOOL TxSetTimer (UINT idTimer, UINT uTimeout); + virtual void TxKillTimer (UINT idTimer); + virtual void TxScrollWindowEx (INT dx, INT dy, LPCRECT lprcScroll, LPCRECT lprcClip, HRGN hrgnUpdate, LPRECT lprcUpdate, UINT fuScroll); + virtual void TxSetCapture (BOOL fCapture); + virtual void TxSetFocus (); + virtual void TxSetCursor (HCURSOR hcur, BOOL fText); + virtual BOOL TxScreenToClient (LPPOINT lppt); + virtual BOOL TxClientToScreen (LPPOINT lppt); + virtual HRESULT TxActivate (LONG * plOldState); + virtual HRESULT TxDeactivate (LONG lNewState); + virtual HRESULT TxGetClientRect (LPRECT prc); + virtual HRESULT TxGetViewInset (LPRECT prc); + virtual HRESULT TxGetCharFormat (const CHARFORMATW **ppCF); + virtual HRESULT TxGetParaFormat (const PARAFORMAT **ppPF); + virtual COLORREF TxGetSysColor (int nIndex); + virtual HRESULT TxGetBackStyle (TXTBACKSTYLE *pstyle); + virtual HRESULT TxGetMaxLength (DWORD *plength); + virtual HRESULT TxGetScrollBars (DWORD *pdwScrollBar); + virtual HRESULT TxGetPasswordChar (TCHAR *pch); + virtual HRESULT TxGetAcceleratorPos (LONG *pcp); + virtual HRESULT TxGetExtent (LPSIZEL lpExtent); + virtual HRESULT OnTxCharFormatChange (const CHARFORMATW * pcf); + virtual HRESULT OnTxParaFormatChange (const PARAFORMAT * ppf); + virtual HRESULT TxGetPropertyBits (DWORD dwMask, DWORD *pdwBits); + virtual HRESULT TxNotify (DWORD iNotify, void *pv); + virtual HIMC TxImmGetContext (void); + virtual void TxImmReleaseContext (HIMC himc); + virtual HRESULT TxGetSelectionBarWidth (LONG *lSelBarWidth); + + private: + CRichEditUI *m_re; + ULONG cRefs; // Reference Count + ITextServices *pserv; // pointer to Text Services object + // Properties + + DWORD dwStyle; // style bits + + unsigned fEnableAutoWordSel : 1; // enable Word style auto word selection? + unsigned fWordWrap : 1; // Whether control should word wrap + unsigned fAllowBeep : 1; // Whether beep is allowed + unsigned fRich : 1; // Whether control is rich text + unsigned fSaveSelection : 1; // Whether to save the selection when inactive + unsigned fInplaceActive : 1; // Whether control is inplace active + unsigned fTransparent : 1; // Whether control is transparent + unsigned fTimer : 1; // A timer is set + unsigned fCaptured : 1; + unsigned fShowCaret : 1; + unsigned fNeedFreshCaret : 1; // ıСλԭ겻 + + INT iCaretWidth; + INT iCaretHeight; + INT iCaretLastWidth; + INT iCaretLastHeight; + LONG lSelBarWidth; // Width of the selection bar + LONG cchTextMost; // maximum text size + DWORD dwEventMask; // DoEvent mask to pass on to parent window + LONG icf; + LONG ipf; + RECT rcClient; // Client Rect for this control + SIZEL sizelExtent; // Extent array + CHARFORMAT2W cf; // Default character format + PARAFORMAT2 pf; // Default paragraph format + LONG laccelpos; // Accelerator position + WCHAR chPasswordChar; // Password character + }; + + // Convert Pixels on the X axis to Himetric + LONG DXtoHimetricX (LONG dx, LONG xPerInch) { + return (LONG) MulDiv (dx, HIMETRIC_PER_INCH, xPerInch); + } + + // Convert Pixels on the Y axis to Himetric + LONG DYtoHimetricY (LONG dy, LONG yPerInch) { + return (LONG) MulDiv (dy, HIMETRIC_PER_INCH, yPerInch); + } + + HRESULT InitDefaultCharFormat (CRichEditUI* re, CHARFORMAT2W* pcf, HFONT hfont) { + memset (pcf, 0, sizeof (CHARFORMAT2W)); + if (!hfont) { + hfont = re->GetManager ()->GetFont (re->GetFont ()); + } + LOGFONT lf; + ::GetObject (hfont, sizeof (LOGFONT), &lf); + + DWORD dwColor = re->GetTextColor (); + if (re->GetManager ()->IsLayered ()) { + CRenderEngine::CheckAlphaColor (dwColor); + } + pcf->cbSize = sizeof (CHARFORMAT2W); + pcf->crTextColor = RGB (GetBValue (dwColor), GetGValue (dwColor), GetRValue (dwColor)); + LONG yPixPerInch = GetDeviceCaps (re->GetManager ()->GetPaintDC (), LOGPIXELSY); + pcf->yHeight = -lf.lfHeight * LY_PER_INCH / yPixPerInch; + pcf->yOffset = 0; + pcf->dwEffects = 0; + pcf->dwMask = CFM_SIZE | CFM_OFFSET | CFM_FACE | CFM_CHARSET | CFM_COLOR | CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE; + if (lf.lfWeight >= FW_BOLD) + pcf->dwEffects |= CFE_BOLD; + if (lf.lfItalic) + pcf->dwEffects |= CFE_ITALIC; + if (lf.lfUnderline) + pcf->dwEffects |= CFE_UNDERLINE; + pcf->bCharSet = lf.lfCharSet; + pcf->bPitchAndFamily = lf.lfPitchAndFamily; +#ifdef _UNICODE + _tcscpy (pcf->szFaceName, lf.lfFaceName); +#else + //need to thunk pcf->szFaceName to a standard char string.in this case it's easy because our thunk is also our copy + MultiByteToWideChar (CP_ACP, 0, lf.lfFaceName, LF_FACESIZE, pcf->szFaceName, LF_FACESIZE); +#endif + + return S_OK; + } + + HRESULT InitDefaultParaFormat (CRichEditUI* re, PARAFORMAT2* ppf) { + memset (ppf, 0, sizeof (PARAFORMAT2)); + ppf->cbSize = sizeof (PARAFORMAT2); + ppf->dwMask = PFM_ALL; + ppf->wAlignment = PFA_LEFT; + ppf->cTabCount = 1; + ppf->rgxTabs[0] = lDefaultTab; + + return S_OK; + } + + HRESULT CreateHost (CRichEditUI *re, const CREATESTRUCT *pcs, CTxtWinHost **pptec) { + HRESULT hr = E_FAIL; + CTxtWinHost *phost = new CTxtWinHost (); + if (phost) { + if (phost->Init (re, pcs)) { + *pptec = phost; + hr = S_OK; + } + } + + if (FAILED (hr)) { + delete phost; + } + + return TRUE; + } + + CTxtWinHost::CTxtWinHost (): m_re (nullptr) { + ::ZeroMemory (&cRefs, sizeof (CTxtWinHost) - offsetof (CTxtWinHost, cRefs)); + cchTextMost = cInitTextMax; + laccelpos = -1; + } + + CTxtWinHost::~CTxtWinHost () { + pserv->OnTxInPlaceDeactivate (); + pserv->Release (); + } + + ////////////////////// Create/Init/Destruct Commands /////////////////////// + + BOOL CTxtWinHost::Init (CRichEditUI *re, const CREATESTRUCT *pcs) { + IUnknown *pUnk = nullptr; + HRESULT hr; + + m_re = re; + // Initialize Reference count + cRefs = 1; + + // Create and cache CHARFORMAT for this control + if (FAILED (InitDefaultCharFormat (re, &cf, nullptr))) + return FALSE; + + // Create and cache PARAFORMAT for this control + if (FAILED (InitDefaultParaFormat (re, &pf))) + return FALSE; + + // edit controls created without a window are multiline by default + // so that paragraph formats can be + dwStyle = ES_MULTILINE; + + // edit controls are rich by default + fRich = re->IsRich (); + + cchTextMost = re->GetLimitText (); + + if (pcs) { + dwStyle = pcs->style; + + if (!(dwStyle & (ES_AUTOHSCROLL | WS_HSCROLL))) { + fWordWrap = TRUE; + } + } + + if (!(dwStyle & ES_LEFT)) { + if (dwStyle & ES_CENTER) + pf.wAlignment = PFA_CENTER; + else if (dwStyle & ES_RIGHT) + pf.wAlignment = PFA_RIGHT; + } + + fInplaceActive = TRUE; + + PCreateTextServices TextServicesProc = nullptr; +#ifdef _UNICODE + HMODULE hmod = LoadLibrary (_T ("Msftedit.dll")); +#else + HMODULE hmod = LoadLibrary (_T ("Riched20.dll")); +#endif + if (hmod) { + TextServicesProc = (PCreateTextServices) GetProcAddress (hmod, "CreateTextServices"); + } + if (TextServicesProc) { + hr = TextServicesProc (nullptr, this, &pUnk); + } + + hr = pUnk->QueryInterface (IID_ITextServices, (void **) &pserv); + + // Whether the previous call succeeded or failed we are done + // with the private interface. + pUnk->Release (); + + if (FAILED (hr)) + return FALSE; + + // Set window text + if (pcs && pcs->lpszName) { + std::wstring _text = FawTools::get_utf16 (pcs->lpszName); + if (FAILED (pserv->TxSetText (_text.c_str ()))) + return FALSE; + } + + return TRUE; + } + + ///////////////////////////////// IUnknown //////////////////////////////// + + + HRESULT CTxtWinHost::QueryInterface (REFIID riid, void **ppvObject) { + HRESULT hr = E_NOINTERFACE; + *ppvObject = nullptr; + + if (IsEqualIID (riid, IID_IUnknown) + || IsEqualIID (riid, IID_ITextHost)) { + AddRef (); + *ppvObject = (ITextHost *) this; + hr = S_OK; + } + + return hr; + } + + ULONG CTxtWinHost::AddRef (void) { + return ++cRefs; + } + + ULONG CTxtWinHost::Release (void) { + ULONG c_Refs = --cRefs; + + if (c_Refs == 0) { + delete this; + } + + return c_Refs; + } + + ///////////////////////////////// Far East Support ////////////////////////////////////// + + HIMC CTxtWinHost::TxImmGetContext (void) { + return nullptr; + } + + void CTxtWinHost::TxImmReleaseContext (HIMC himc) { + //::ImmReleaseContext( hwnd, himc ); + } + + //////////////////////////// ITextHost Interface //////////////////////////// + + HDC CTxtWinHost::TxGetDC () { + return m_re->GetManager ()->GetPaintDC (); + } + + int CTxtWinHost::TxReleaseDC (HDC hdc) { + return 1; + } + + BOOL CTxtWinHost::TxShowScrollBar (INT fnBar, BOOL fShow) { + CScrollBarUI* pVerticalScrollBar = m_re->GetVerticalScrollBar (); + CScrollBarUI* pHorizontalScrollBar = m_re->GetHorizontalScrollBar (); + if (fnBar == SB_VERT && pVerticalScrollBar) { + pVerticalScrollBar->SetVisible (fShow == TRUE); + } else if (fnBar == SB_HORZ && pHorizontalScrollBar) { + pHorizontalScrollBar->SetVisible (fShow == TRUE); + } else if (fnBar == SB_BOTH) { + if (pVerticalScrollBar) pVerticalScrollBar->SetVisible (fShow == TRUE); + if (pHorizontalScrollBar) pHorizontalScrollBar->SetVisible (fShow == TRUE); + } + return TRUE; + } + + BOOL CTxtWinHost::TxEnableScrollBar (INT fuSBFlags, INT fuArrowflags) { + if (fuSBFlags == SB_VERT) { + m_re->EnableScrollBar (true, m_re->GetHorizontalScrollBar ()); + m_re->GetVerticalScrollBar ()->SetVisible (fuArrowflags != ESB_DISABLE_BOTH); + } else if (fuSBFlags == SB_HORZ) { + m_re->EnableScrollBar (m_re->GetVerticalScrollBar (), true); + m_re->GetHorizontalScrollBar ()->SetVisible (fuArrowflags != ESB_DISABLE_BOTH); + } else if (fuSBFlags == SB_BOTH) { + m_re->EnableScrollBar (true, true); + m_re->GetVerticalScrollBar ()->SetVisible (fuArrowflags != ESB_DISABLE_BOTH); + m_re->GetHorizontalScrollBar ()->SetVisible (fuArrowflags != ESB_DISABLE_BOTH); + } + return TRUE; + } + + BOOL CTxtWinHost::TxSetScrollRange (INT fnBar, LONG nMinPos, INT nMaxPos, BOOL fRedraw) { + CScrollBarUI* pVerticalScrollBar = m_re->GetVerticalScrollBar (); + CScrollBarUI* pHorizontalScrollBar = m_re->GetHorizontalScrollBar (); + if (fnBar == SB_VERT && pVerticalScrollBar) { + if (nMaxPos - nMinPos - rcClient.bottom + rcClient.top <= 0) { + pVerticalScrollBar->SetVisible (false); + } else { + pVerticalScrollBar->SetVisible (true); + pVerticalScrollBar->SetScrollRange (nMaxPos - nMinPos - rcClient.bottom + rcClient.top); + } + } else if (fnBar == SB_HORZ && pHorizontalScrollBar) { + if (nMaxPos - nMinPos - rcClient.right + rcClient.left <= 0) { + pHorizontalScrollBar->SetVisible (false); + } else { + pHorizontalScrollBar->SetVisible (true); + pHorizontalScrollBar->SetScrollRange (nMaxPos - nMinPos - rcClient.right + rcClient.left); + } + } + return TRUE; + } + + BOOL CTxtWinHost::TxSetScrollPos (INT fnBar, INT nPos, BOOL fRedraw) { + CScrollBarUI* pVerticalScrollBar = m_re->GetVerticalScrollBar (); + CScrollBarUI* pHorizontalScrollBar = m_re->GetHorizontalScrollBar (); + if (fnBar == SB_VERT && pVerticalScrollBar) { + pVerticalScrollBar->SetScrollPos (nPos); + } else if (fnBar == SB_HORZ && pHorizontalScrollBar) { + pHorizontalScrollBar->SetScrollPos (nPos); + } + return TRUE; + } + + void CTxtWinHost::TxInvalidateRect (LPCRECT prc, BOOL fMode) { + if (!prc) { + m_re->GetManager ()->Invalidate (rcClient); + return; + } + RECT rc = *prc; + m_re->GetManager ()->Invalidate (rc); + } + + void CTxtWinHost::TxViewChange (BOOL fUpdate) { + if (m_re->OnTxViewChanged ()) m_re->Invalidate (); + } + + BOOL CTxtWinHost::TxCreateCaret (HBITMAP hbmp, INT xWidth, INT yHeight) { + iCaretWidth = xWidth; + iCaretHeight = yHeight; + return ::CreateCaret (m_re->GetManager ()->GetPaintWindow (), hbmp, xWidth, yHeight); + } + + BOOL CTxtWinHost::TxShowCaret (BOOL fShow) { + fShowCaret = fShow; + if (fShow) + return ::ShowCaret (m_re->GetManager ()->GetPaintWindow ()); + else + return ::HideCaret (m_re->GetManager ()->GetPaintWindow ()); + } + + BOOL CTxtWinHost::TxSetCaretPos (INT x, INT y) { + POINT ptCaret = { 0 }; + ::GetCaretPos (&ptCaret); + RECT rcCaret = { ptCaret.x, ptCaret.y, ptCaret.x + iCaretLastWidth, ptCaret.y + iCaretLastHeight }; + if (m_re->GetManager ()->IsLayered ()) m_re->GetManager ()->Invalidate (rcCaret); + else if (fNeedFreshCaret == TRUE) { + m_re->GetManager ()->Invalidate (rcCaret); + fNeedFreshCaret = FALSE; + } + rcCaret.left = x; + rcCaret.top = y; + rcCaret.right = x + iCaretWidth; + rcCaret.bottom = y + iCaretHeight; + if (m_re->GetManager ()->IsLayered ()) m_re->GetManager ()->Invalidate (rcCaret); + iCaretLastWidth = iCaretWidth; + iCaretLastHeight = iCaretHeight; + return ::SetCaretPos (x, y); + } + + BOOL CTxtWinHost::TxSetTimer (UINT idTimer, UINT uTimeout) { + fTimer = TRUE; + return m_re->GetManager ()->SetTimer (m_re, idTimer, uTimeout) == TRUE; + } + + void CTxtWinHost::TxKillTimer (UINT idTimer) { + m_re->GetManager ()->KillTimer (m_re, idTimer); + fTimer = FALSE; + } + + void CTxtWinHost::TxScrollWindowEx (INT dx, INT dy, LPCRECT lprcScroll, LPCRECT lprcClip, HRGN hrgnUpdate, LPRECT lprcUpdate, UINT fuScroll) { + return; + } + + void CTxtWinHost::TxSetCapture (BOOL fCapture) { + if (fCapture) m_re->GetManager ()->SetCapture (); + else m_re->GetManager ()->ReleaseCapture (); + fCaptured = fCapture; + } + + void CTxtWinHost::TxSetFocus () { + m_re->SetFocus (); + } + + void CTxtWinHost::TxSetCursor (HCURSOR hcur, BOOL fText) { + ::SetCursor (hcur); + } + + BOOL CTxtWinHost::TxScreenToClient (LPPOINT lppt) { + return ::ScreenToClient (m_re->GetManager ()->GetPaintWindow (), lppt); + } + + BOOL CTxtWinHost::TxClientToScreen (LPPOINT lppt) { + return ::ClientToScreen (m_re->GetManager ()->GetPaintWindow (), lppt); + } + + HRESULT CTxtWinHost::TxActivate (LONG *plOldState) { + return S_OK; + } + + HRESULT CTxtWinHost::TxDeactivate (LONG lNewState) { + return S_OK; + } + + HRESULT CTxtWinHost::TxGetClientRect (LPRECT prc) { + *prc = rcClient; + GetControlRect (prc); + return NOERROR; + } + + HRESULT CTxtWinHost::TxGetViewInset (LPRECT prc) { + prc->left = prc->right = prc->top = prc->bottom = 0; + return NOERROR; + } + + HRESULT CTxtWinHost::TxGetCharFormat (const CHARFORMATW **ppCF) { + *ppCF = &cf; + return NOERROR; + } + + HRESULT CTxtWinHost::TxGetParaFormat (const PARAFORMAT **ppPF) { + *ppPF = &pf; + return NOERROR; + } + + COLORREF CTxtWinHost::TxGetSysColor (int nIndex) { + return ::GetSysColor (nIndex); + } + + HRESULT CTxtWinHost::TxGetBackStyle (TXTBACKSTYLE *pstyle) { + *pstyle = !fTransparent ? TXTBACK_OPAQUE : TXTBACK_TRANSPARENT; + return NOERROR; + } + + HRESULT CTxtWinHost::TxGetMaxLength (DWORD *pLength) { + *pLength = cchTextMost; + return NOERROR; + } + + HRESULT CTxtWinHost::TxGetScrollBars (DWORD *pdwScrollBar) { + *pdwScrollBar = dwStyle & (WS_VSCROLL | WS_HSCROLL | ES_AUTOVSCROLL | + ES_AUTOHSCROLL | ES_DISABLENOSCROLL); + + return NOERROR; + } + + HRESULT CTxtWinHost::TxGetPasswordChar (TCHAR *pch) { +#ifdef _UNICODE + *pch = chPasswordChar; +#else + ::WideCharToMultiByte (CP_ACP, 0, &chPasswordChar, 1, pch, 1, nullptr, nullptr); +#endif + return NOERROR; + } + + HRESULT CTxtWinHost::TxGetAcceleratorPos (LONG *pcp) { + *pcp = laccelpos; + return S_OK; + } + + HRESULT CTxtWinHost::OnTxCharFormatChange (const CHARFORMATW *pcf) { + return S_OK; + } + + HRESULT CTxtWinHost::OnTxParaFormatChange (const PARAFORMAT *ppf) { + return S_OK; + } + + HRESULT CTxtWinHost::TxGetPropertyBits (DWORD dwMask, DWORD *pdwBits) { + DWORD dwProperties = 0; + + if (fRich) { + dwProperties = TXTBIT_RICHTEXT; + } + + if (dwStyle & ES_MULTILINE) { + dwProperties |= TXTBIT_MULTILINE; + } + + if (dwStyle & ES_READONLY) { + dwProperties |= TXTBIT_READONLY; + } + + if (dwStyle & ES_PASSWORD) { + dwProperties |= TXTBIT_USEPASSWORD; + } + + if (!(dwStyle & ES_NOHIDESEL)) { + dwProperties |= TXTBIT_HIDESELECTION; + } + + if (fEnableAutoWordSel) { + dwProperties |= TXTBIT_AUTOWORDSEL; + } + + if (fWordWrap) { + dwProperties |= TXTBIT_WORDWRAP; + } + + if (fAllowBeep) { + dwProperties |= TXTBIT_ALLOWBEEP; + } + + if (fSaveSelection) { + dwProperties |= TXTBIT_SAVESELECTION; + } + + *pdwBits = dwProperties & dwMask; + return NOERROR; + } + + + HRESULT CTxtWinHost::TxNotify (DWORD iNotify, void *pv) { + if (iNotify == EN_REQUESTRESIZE) { + RECT rc = { 0 }; + REQRESIZE *preqsz = (REQRESIZE *) pv; + GetControlRect (&rc); + rc.bottom = rc.top + preqsz->rc.bottom; + rc.right = rc.left + preqsz->rc.right; + SetClientRect (&rc); + return S_OK; + } + m_re->OnTxNotify (iNotify, pv); + return S_OK; + } + + HRESULT CTxtWinHost::TxGetExtent (LPSIZEL lpExtent) { + *lpExtent = sizelExtent; + return S_OK; + } + + HRESULT CTxtWinHost::TxGetSelectionBarWidth (LONG *plSelBarWidth) { + *plSelBarWidth = lSelBarWidth; + return S_OK; + } + + void CTxtWinHost::SetWordWrap (BOOL _fWordWrap) { + fWordWrap = _fWordWrap; + pserv->OnTxPropertyBitsChange (TXTBIT_WORDWRAP, _fWordWrap ? TXTBIT_WORDWRAP : 0); + } + + BOOL CTxtWinHost::IsReadOnly () { + return (dwStyle & ES_READONLY) != 0; + } + + void CTxtWinHost::SetReadOnly (BOOL fReadOnly) { + if (fReadOnly) { + dwStyle |= ES_READONLY; + } else { + dwStyle &= ~ES_READONLY; + } + + pserv->OnTxPropertyBitsChange (TXTBIT_READONLY, fReadOnly ? TXTBIT_READONLY : 0); + } + + void CTxtWinHost::SetFont (HFONT hFont) { + if (!hFont) return; + LOGFONT lf; + ::GetObject (hFont, sizeof (LOGFONT), &lf); + LONG yPixPerInch = ::GetDeviceCaps (m_re->GetManager ()->GetPaintDC (), LOGPIXELSY); + cf.yHeight = -lf.lfHeight * LY_PER_INCH / yPixPerInch; + if (lf.lfWeight >= FW_BOLD) cf.dwEffects |= CFE_BOLD; + else cf.dwEffects &= ~CFE_BOLD; + if (lf.lfItalic) cf.dwEffects |= CFE_ITALIC; + else cf.dwEffects &= ~CFE_ITALIC; + if (lf.lfUnderline) cf.dwEffects |= CFE_UNDERLINE; + else cf.dwEffects &= ~CFE_UNDERLINE; + cf.bCharSet = lf.lfCharSet; + cf.bPitchAndFamily = lf.lfPitchAndFamily; +#ifdef _UNICODE + _tcscpy (cf.szFaceName, lf.lfFaceName); +#else + //need to thunk pcf->szFaceName to a standard char string.in this case it's easy because our thunk is also our copy + MultiByteToWideChar (CP_ACP, 0, lf.lfFaceName, LF_FACESIZE, cf.szFaceName, LF_FACESIZE); +#endif + + pserv->OnTxPropertyBitsChange (TXTBIT_CHARFORMATCHANGE, + TXTBIT_CHARFORMATCHANGE); + } + + void CTxtWinHost::SetColor (DWORD dwColor) { + cf.crTextColor = RGB (GetBValue (dwColor), GetGValue (dwColor), GetRValue (dwColor)); + pserv->OnTxPropertyBitsChange (TXTBIT_CHARFORMATCHANGE, TXTBIT_CHARFORMATCHANGE); + } + + SIZEL* CTxtWinHost::GetExtent () { + return &sizelExtent; + } + + void CTxtWinHost::SetExtent (SIZEL *psizelExtent) { + sizelExtent = *psizelExtent; + pserv->OnTxPropertyBitsChange (TXTBIT_EXTENTCHANGE, TXTBIT_EXTENTCHANGE); + } + + void CTxtWinHost::LimitText (LONG nChars) { + cchTextMost = nChars; + if (cchTextMost <= 0) cchTextMost = cInitTextMax; + pserv->OnTxPropertyBitsChange (TXTBIT_MAXLENGTHCHANGE, TXTBIT_MAXLENGTHCHANGE); + } + + BOOL CTxtWinHost::IsCaptured () { + return fCaptured; + } + + BOOL CTxtWinHost::IsShowCaret () { + return fShowCaret; + } + + void CTxtWinHost::NeedFreshCaret () { + fNeedFreshCaret = TRUE; + } + + INT CTxtWinHost::GetCaretWidth () { + return iCaretWidth; + } + + INT CTxtWinHost::GetCaretHeight () { + return iCaretHeight; + } + + BOOL CTxtWinHost::GetAllowBeep () { + return fAllowBeep; + } + + void CTxtWinHost::SetAllowBeep (BOOL _fAllowBeep) { + fAllowBeep = _fAllowBeep; + + pserv->OnTxPropertyBitsChange (TXTBIT_ALLOWBEEP, _fAllowBeep ? TXTBIT_ALLOWBEEP : 0); + } + + WORD CTxtWinHost::GetDefaultAlign () { + return pf.wAlignment; + } + + void CTxtWinHost::SetDefaultAlign (WORD wNewAlign) { + pf.wAlignment = wNewAlign; + + // Notify control of property change + pserv->OnTxPropertyBitsChange (TXTBIT_PARAFORMATCHANGE, 0); + } + + BOOL CTxtWinHost::GetRichTextFlag () { + return fRich; + } + + void CTxtWinHost::SetRichTextFlag (BOOL fNew) { + fRich = fNew; + + pserv->OnTxPropertyBitsChange (TXTBIT_RICHTEXT, fNew ? TXTBIT_RICHTEXT : 0); + } + + LONG CTxtWinHost::GetDefaultLeftIndent () { + return pf.dxOffset; + } + + void CTxtWinHost::SetDefaultLeftIndent (LONG lNewIndent) { + pf.dxOffset = lNewIndent; + + pserv->OnTxPropertyBitsChange (TXTBIT_PARAFORMATCHANGE, 0); + } + + void CTxtWinHost::SetClientRect (RECT *prc) { + rcClient = *prc; + + LONG xPerInch = ::GetDeviceCaps (m_re->GetManager ()->GetPaintDC (), LOGPIXELSX); + LONG yPerInch = ::GetDeviceCaps (m_re->GetManager ()->GetPaintDC (), LOGPIXELSY); + sizelExtent.cx = DXtoHimetricX (rcClient.right - rcClient.left, xPerInch); + sizelExtent.cy = DYtoHimetricY (rcClient.bottom - rcClient.top, yPerInch); + + pserv->OnTxPropertyBitsChange (TXTBIT_VIEWINSETCHANGE, TXTBIT_VIEWINSETCHANGE); + } + + BOOL CTxtWinHost::SetSaveSelection (BOOL f_SaveSelection) { + BOOL fResult = f_SaveSelection; + + fSaveSelection = f_SaveSelection; + + // notify text services of property change + pserv->OnTxPropertyBitsChange (TXTBIT_SAVESELECTION, fSaveSelection ? TXTBIT_SAVESELECTION : 0); + + return fResult; + } + + HRESULT CTxtWinHost::OnTxInPlaceDeactivate () { + HRESULT hr = pserv->OnTxInPlaceDeactivate (); + + if (SUCCEEDED (hr)) { + fInplaceActive = FALSE; + } + + return hr; + } + + HRESULT CTxtWinHost::OnTxInPlaceActivate (LPCRECT prcClient) { + fInplaceActive = TRUE; + + HRESULT hr = pserv->OnTxInPlaceActivate (prcClient); + + if (FAILED (hr)) { + fInplaceActive = FALSE; + } + + return hr; + } + + BOOL CTxtWinHost::DoSetCursor (RECT *prc, POINT *pt) { + RECT rc = prc ? *prc : rcClient; + + // Is this in our rectangle? + if (PtInRect (&rc, *pt)) { + RECT *prcClient = (!fInplaceActive || prc) ? &rc : nullptr; + pserv->OnTxSetCursor (DVASPECT_CONTENT, -1, nullptr, nullptr, m_re->GetManager ()->GetPaintDC (), NULL, prcClient, pt->x, pt->y); + + return TRUE; + } + + return FALSE; + } + + void CTxtWinHost::GetControlRect (LPRECT prc) { + prc->top = rcClient.top; + prc->bottom = rcClient.bottom; + prc->left = rcClient.left; + prc->right = rcClient.right; + } + + void CTxtWinHost::SetTransparent (BOOL f_Transparent) { + fTransparent = f_Transparent; + + // notify text services of property change + pserv->OnTxPropertyBitsChange (TXTBIT_BACKSTYLECHANGE, 0); + } + + LONG CTxtWinHost::SetAccelPos (LONG l_accelpos) { + LONG laccelposOld = l_accelpos; + + laccelpos = l_accelpos; + + // notify text services of property change + pserv->OnTxPropertyBitsChange (TXTBIT_SHOWACCELERATOR, 0); + + return laccelposOld; + } + + WCHAR CTxtWinHost::SetPasswordChar (WCHAR ch_PasswordChar) { + WCHAR chOldPasswordChar = chPasswordChar; + + chPasswordChar = ch_PasswordChar; + + // notify text services of property change + pserv->OnTxPropertyBitsChange (TXTBIT_USEPASSWORD, (chPasswordChar != 0) ? TXTBIT_USEPASSWORD : 0); + + return chOldPasswordChar; + } + + void CTxtWinHost::SetDisabled (BOOL fOn) { + cf.dwMask |= CFM_COLOR | CFM_DISABLED; + cf.dwEffects |= CFE_AUTOCOLOR | CFE_DISABLED; + + if (!fOn) { + cf.dwEffects &= ~CFE_DISABLED; + } + + pserv->OnTxPropertyBitsChange (TXTBIT_CHARFORMATCHANGE, TXTBIT_CHARFORMATCHANGE); + } + + LONG CTxtWinHost::SetSelBarWidth (LONG l_SelBarWidth) { + LONG lOldSelBarWidth = lSelBarWidth; + + lSelBarWidth = l_SelBarWidth; + + if (lSelBarWidth) { + dwStyle |= ES_SELECTIONBAR; + } else { + dwStyle &= (~ES_SELECTIONBAR); + } + + pserv->OnTxPropertyBitsChange (TXTBIT_SELBARCHANGE, TXTBIT_SELBARCHANGE); + + return lOldSelBarWidth; + } + + BOOL CTxtWinHost::GetTimerState () { + return fTimer; + } + + void CTxtWinHost::SetCharFormat (CHARFORMAT2W &c) { + cf = c; + } + + void CTxtWinHost::SetParaFormat (PARAFORMAT2 &p) { + pf = p; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CRichEditUI) + CRichEditUI::CRichEditUI (): m_lTwhStyle (ES_MULTILINE) { + m_iLimitText = cInitTextMax; +#ifndef _UNICODE + m_fAccumulateDBC = true; +#else + m_fAccumulateDBC = false; +#endif + ::ZeroMemory (&m_rcTextPadding, sizeof (m_rcTextPadding)); + } + + CRichEditUI::~CRichEditUI () { + if (m_pTwh) { + m_pTwh->Release (); + m_pManager->RemoveMessageFilter (this); + } + } + + string_view_t CRichEditUI::GetClass () const { + return DUI_CTRL_RICHEDIT; + } + + LPVOID CRichEditUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_RICHEDIT) return static_cast(this); + return CContainerUI::GetInterface (pstrName); + } + + UINT CRichEditUI::GetControlFlags () const { + if (!IsEnabled ()) return CControlUI::GetControlFlags (); + + return UIFLAG_SETCURSOR | UIFLAG_TABSTOP; + } + + bool CRichEditUI::IsMultiLine () { + return (m_lTwhStyle & ES_MULTILINE) == ES_MULTILINE; + } + + void CRichEditUI::SetMultiLine (bool bMultiLine) { + if (!bMultiLine) m_lTwhStyle &= ~ES_MULTILINE; + else m_lTwhStyle |= ES_MULTILINE; + } + + bool CRichEditUI::IsWantTab () { + return m_bWantTab; + } + + void CRichEditUI::SetWantTab (bool bWantTab) { + m_bWantTab = bWantTab; + } + + bool CRichEditUI::IsWantReturn () { + return m_bWantReturn; + } + + void CRichEditUI::SetWantReturn (bool bWantReturn) { + m_bWantReturn = bWantReturn; + } + + bool CRichEditUI::IsWantCtrlReturn () { + return m_bWantCtrlReturn; + } + + void CRichEditUI::SetWantCtrlReturn (bool bWantCtrlReturn) { + m_bWantCtrlReturn = bWantCtrlReturn; + } + + bool CRichEditUI::IsTransparent () { + return m_bTransparent; + } + + void CRichEditUI::SetTransparent (bool bTransparent) { + m_bTransparent = bTransparent; + if (m_pTwh) m_pTwh->SetTransparent (bTransparent); + } + + bool CRichEditUI::IsRich () { + return m_bRich; + } + + void CRichEditUI::SetRich (bool bRich) { + m_bRich = bRich; + if (m_pTwh) m_pTwh->SetRichTextFlag (bRich); + } + + bool CRichEditUI::IsReadOnly () { + return m_bReadOnly; + } + + void CRichEditUI::SetReadOnly (bool bReadOnly) { + m_bReadOnly = bReadOnly; + if (m_pTwh) m_pTwh->SetReadOnly (bReadOnly); + } + + bool CRichEditUI::IsWordWrap () { + return m_bWordWrap; + } + + void CRichEditUI::SetWordWrap (bool bWordWrap) { + m_bWordWrap = bWordWrap; + if (m_pTwh) m_pTwh->SetWordWrap (bWordWrap); + } + + int CRichEditUI::GetFont () { + return m_iFont; + } + + void CRichEditUI::SetFont (int index) { + m_iFont = index; + if (m_pTwh) { + m_pTwh->SetFont (GetManager ()->GetFont (m_iFont)); + } + } + + void CRichEditUI::SetFont (string_view_t pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic) { + if (m_pTwh) { + LOGFONT lf = { 0 }; + ::GetObject (::GetStockObject (DEFAULT_GUI_FONT), sizeof (LOGFONT), &lf); + _tcsncpy (lf.lfFaceName, pStrFontName.data (), LF_FACESIZE); + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfHeight = -nSize; + if (bBold) lf.lfWeight += FW_BOLD; + if (bUnderline) lf.lfUnderline = TRUE; + if (bItalic) lf.lfItalic = TRUE; + HFONT hFont = ::CreateFontIndirect (&lf); + if (!hFont) return; + m_pTwh->SetFont (hFont); + ::DeleteObject (hFont); + } + } + + LONG CRichEditUI::GetWinStyle () { + return m_lTwhStyle; + } + + void CRichEditUI::SetWinStyle (LONG lStyle) { + m_lTwhStyle = lStyle; + } + + DWORD CRichEditUI::GetTextColor () { + return m_dwTextColor; + } + + void CRichEditUI::SetTextColor (DWORD dwTextColor) { + m_dwTextColor = dwTextColor; + if (m_pTwh) { + m_pTwh->SetColor (dwTextColor); + } + } + + int CRichEditUI::GetLimitText () { + return m_iLimitText; + } + + void CRichEditUI::SetLimitText (int iChars) { + m_iLimitText = iChars; + if (m_pTwh) { + m_pTwh->LimitText (m_iLimitText); + } + } + + long CRichEditUI::GetTextLength (DWORD dwFlags) const { + GETTEXTLENGTHEX textLenEx; + textLenEx.flags = dwFlags; +#ifdef _UNICODE + textLenEx.codepage = 1200; +#else + textLenEx.codepage = CP_ACP; +#endif + LRESULT lResult; + TxSendMessage (EM_GETTEXTLENGTHEX, (WPARAM) &textLenEx, 0, &lResult); + return (long) lResult; + } + + CDuiString CRichEditUI::GetText () const { + long lLen = GetTextLength (GTL_DEFAULT); + LPTSTR lpText = nullptr; + GETTEXTEX gt; + gt.flags = GT_DEFAULT; +#ifdef _UNICODE + gt.cb = sizeof (TCHAR) * (lLen + 1); + gt.codepage = 1200; + lpText = new TCHAR[lLen + 1]; + ::ZeroMemory (lpText, (lLen + 1) * sizeof (TCHAR)); +#else + gt.cb = sizeof (TCHAR) * lLen * 2 + 1; + gt.codepage = CP_ACP; + lpText = new TCHAR[lLen * 2 + 1]; + ::ZeroMemory (lpText, (lLen * 2 + 1) * sizeof (TCHAR)); +#endif + gt.lpDefaultChar = nullptr; + gt.lpUsedDefChar = nullptr; + TxSendMessage (EM_GETTEXTEX, (WPARAM) >, (LPARAM) lpText, 0); + CDuiString sText (lpText); + delete[] lpText; + return sText; + } + + void CRichEditUI::SetText (string_view_t pstrText) { + m_sText = pstrText; + if (!m_pTwh) return; + SetSel (0, -1); + ReplaceSel (pstrText, FALSE); + } + + bool CRichEditUI::IsModify () const { + if (!m_pTwh) return false; + LRESULT lResult; + TxSendMessage (EM_GETMODIFY, 0, 0, &lResult); + return (BOOL) lResult == TRUE; + } + + void CRichEditUI::SetModify (bool bModified) const { + TxSendMessage (EM_SETMODIFY, bModified, 0, 0); + } + + void CRichEditUI::GetSel (CHARRANGE &cr) const { + TxSendMessage (EM_EXGETSEL, 0, (LPARAM) &cr, 0); + } + + void CRichEditUI::GetSel (long& nStartChar, long& nEndChar) const { + CHARRANGE cr; + TxSendMessage (EM_EXGETSEL, 0, (LPARAM) &cr, 0); + nStartChar = cr.cpMin; + nEndChar = cr.cpMax; + } + + int CRichEditUI::SetSel (CHARRANGE &cr) { + LRESULT lResult; + TxSendMessage (EM_EXSETSEL, 0, (LPARAM) &cr, &lResult); + return (int) lResult; + } + + int CRichEditUI::SetSel (long nStartChar, long nEndChar) { + CHARRANGE cr; + cr.cpMin = nStartChar; + cr.cpMax = nEndChar; + LRESULT lResult; + TxSendMessage (EM_EXSETSEL, 0, (LPARAM) &cr, &lResult); + return (int) lResult; + } + + void CRichEditUI::ReplaceSel (string_view_t lpszNewText, bool bCanUndo) { +#ifdef _UNICODE + TxSendMessage (EM_REPLACESEL, (WPARAM) bCanUndo, (LPARAM) lpszNewText.data (), 0); +#else + size_t iLen = _tcslen (lpszNewText); + LPWSTR lpText = new WCHAR[iLen + 1]; + ::ZeroMemory (lpText, (iLen + 1) * sizeof (WCHAR)); + ::MultiByteToWideChar (CP_ACP, 0, lpszNewText, -1, (LPWSTR) lpText, (int) iLen); + TxSendMessage (EM_REPLACESEL, (WPARAM) bCanUndo, (LPARAM) lpText, 0); + delete[] lpText; +#endif + } + + void CRichEditUI::ReplaceSelW (LPCWSTR lpszNewText, bool bCanUndo) { + TxSendMessage (EM_REPLACESEL, (WPARAM) bCanUndo, (LPARAM) lpszNewText, 0); + } + + CDuiString CRichEditUI::GetSelText () const { + if (!m_pTwh) return CDuiString (); + CHARRANGE cr; + cr.cpMin = cr.cpMax = 0; + TxSendMessage (EM_EXGETSEL, 0, (LPARAM) &cr, 0); + LPWSTR lpText = nullptr; + lpText = new WCHAR[cr.cpMax - cr.cpMin + 1]; + ::ZeroMemory (lpText, (cr.cpMax - cr.cpMin + 1) * sizeof (WCHAR)); + TxSendMessage (EM_GETSELTEXT, 0, (LPARAM) lpText, 0); + CDuiString sText; +#ifdef UNICODE + sText = (LPCWSTR) lpText; +#else + sText = _conv_to_multi (lpText); +#endif + delete[] lpText; + return sText; + } + + int CRichEditUI::SetSelAll () { + return SetSel (0, -1); + } + + int CRichEditUI::SetSelNone () { + return SetSel (-1, 0); + } + + bool CRichEditUI::GetZoom (int& nNum, int& nDen) const { + LRESULT lResult; + TxSendMessage (EM_GETZOOM, (WPARAM) &nNum, (LPARAM) &nDen, &lResult); + return (BOOL) lResult == TRUE; + } + + bool CRichEditUI::SetZoom (int nNum, int nDen) { + if (nNum < 0 || nNum > 64) return false; + if (nDen < 0 || nDen > 64) return false; + LRESULT lResult; + TxSendMessage (EM_SETZOOM, nNum, nDen, &lResult); + return (BOOL) lResult == TRUE; + } + + bool CRichEditUI::SetZoomOff () { + LRESULT lResult; + TxSendMessage (EM_SETZOOM, 0, 0, &lResult); + return (BOOL) lResult == TRUE; + } + + WORD CRichEditUI::GetSelectionType () const { + LRESULT lResult; + TxSendMessage (EM_SELECTIONTYPE, 0, 0, &lResult); + return (WORD) lResult; + } + + bool CRichEditUI::GetAutoURLDetect () const { + LRESULT lResult; + TxSendMessage (EM_GETAUTOURLDETECT, 0, 0, &lResult); + return (BOOL) lResult == TRUE; + } + + bool CRichEditUI::SetAutoURLDetect (bool bAutoDetect) { + LRESULT lResult; + TxSendMessage (EM_AUTOURLDETECT, bAutoDetect, 0, &lResult); + return (BOOL) lResult == FALSE; + } + + DWORD CRichEditUI::GetEventMask () const { + LRESULT lResult; + TxSendMessage (EM_GETEVENTMASK, 0, 0, &lResult); + return (DWORD) lResult; + } + + DWORD CRichEditUI::SetEventMask (DWORD dwEventMask) { + LRESULT lResult; + TxSendMessage (EM_SETEVENTMASK, 0, dwEventMask, &lResult); + return (DWORD) lResult; + } + + CDuiString CRichEditUI::GetTextRange (long nStartChar, long nEndChar) const { + TEXTRANGEW tr = { 0 }; + tr.chrg.cpMin = nStartChar; + tr.chrg.cpMax = nEndChar; + LPWSTR lpText = nullptr; + lpText = new WCHAR[nEndChar - nStartChar + 1]; + ::ZeroMemory (lpText, (nEndChar - nStartChar + 1) * sizeof (WCHAR)); + tr.lpstrText = lpText; + TxSendMessage (EM_GETTEXTRANGE, 0, (LPARAM) &tr, 0); + CDuiString sText; +#ifdef UNICODE + sText = (LPCWSTR) lpText; +#else + sText = _conv_to_multi (lpText); +#endif + delete[] lpText; + return sText; + } + + void CRichEditUI::HideSelection (bool bHide, bool bChangeStyle) { + TxSendMessage (EM_HIDESELECTION, bHide, bChangeStyle, 0); + } + + void CRichEditUI::ScrollCaret () { + TxSendMessage (EM_SCROLLCARET, 0, 0, 0); + } + + int CRichEditUI::InsertText (long nInsertAfterChar, string_view_t lpstrText, bool bCanUndo) { + int nRet = SetSel (nInsertAfterChar, nInsertAfterChar); + ReplaceSel (lpstrText, bCanUndo); + return nRet; + } + + int CRichEditUI::AppendText (string_view_t lpstrText, bool bCanUndo) { + int nRet = SetSel (-1, -1); + ReplaceSel (lpstrText, bCanUndo); + return nRet; + } + + DWORD CRichEditUI::GetDefaultCharFormat (CHARFORMAT2 &cf) const { + cf.cbSize = sizeof (CHARFORMAT2); + LRESULT lResult; + TxSendMessage (EM_GETCHARFORMAT, 0, (LPARAM) &cf, &lResult); + return (DWORD) lResult; + } + + bool CRichEditUI::SetDefaultCharFormat (CHARFORMAT2 &cf) { + if (!m_pTwh) return false; + cf.cbSize = sizeof (CHARFORMAT2); + LRESULT lResult; + TxSendMessage (EM_SETCHARFORMAT, 0, (LPARAM) &cf, &lResult); + if ((BOOL) lResult == TRUE) { + CHARFORMAT2W cfw; + cfw.cbSize = sizeof (CHARFORMAT2W); + TxSendMessage (EM_GETCHARFORMAT, 1, (LPARAM) &cfw, 0); + m_pTwh->SetCharFormat (cfw); + return true; + } + return false; + } + + DWORD CRichEditUI::GetSelectionCharFormat (CHARFORMAT2 &cf) const { + cf.cbSize = sizeof (CHARFORMAT2); + LRESULT lResult; + TxSendMessage (EM_GETCHARFORMAT, 1, (LPARAM) &cf, &lResult); + return (DWORD) lResult; + } + + bool CRichEditUI::SetSelectionCharFormat (CHARFORMAT2 &cf) { + if (!m_pTwh) return false; + cf.cbSize = sizeof (CHARFORMAT2); + LRESULT lResult; + TxSendMessage (EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf, &lResult); + return (BOOL) lResult == TRUE; + } + + bool CRichEditUI::SetWordCharFormat (CHARFORMAT2 &cf) { + if (!m_pTwh) return false; + cf.cbSize = sizeof (CHARFORMAT2); + LRESULT lResult; + TxSendMessage (EM_SETCHARFORMAT, SCF_SELECTION | SCF_WORD, (LPARAM) &cf, &lResult); + return (BOOL) lResult == TRUE; + } + + DWORD CRichEditUI::GetParaFormat (PARAFORMAT2 &pf) const { + pf.cbSize = sizeof (PARAFORMAT2); + LRESULT lResult; + TxSendMessage (EM_GETPARAFORMAT, 0, (LPARAM) &pf, &lResult); + return (DWORD) lResult; + } + + bool CRichEditUI::SetParaFormat (PARAFORMAT2 &pf) { + if (!m_pTwh) return false; + pf.cbSize = sizeof (PARAFORMAT2); + LRESULT lResult; + TxSendMessage (EM_SETPARAFORMAT, 0, (LPARAM) &pf, &lResult); + if ((BOOL) lResult == TRUE) { + m_pTwh->SetParaFormat (pf); + return true; + } + return false; + } + + bool CRichEditUI::CanUndo () { + if (!m_pTwh) return false; + LRESULT lResult; + TxSendMessage (EM_CANUNDO, 0, 0, &lResult); + return (BOOL) lResult == TRUE; + } + + bool CRichEditUI::CanRedo () { + if (!m_pTwh) return false; + LRESULT lResult; + TxSendMessage (EM_CANREDO, 0, 0, &lResult); + return (BOOL) lResult == TRUE; + } + + bool CRichEditUI::CanPaste () { + if (!m_pTwh) return false; + LRESULT lResult; + TxSendMessage (EM_CANPASTE, 0, 0, &lResult); + return (BOOL) lResult == TRUE; + } + bool CRichEditUI::Redo () { + if (!m_pTwh) return false; + LRESULT lResult; + TxSendMessage (EM_REDO, 0, 0, &lResult); + return (BOOL) lResult == TRUE; + } + + bool CRichEditUI::Undo () { + if (!m_pTwh) return false; + LRESULT lResult; + TxSendMessage (EM_UNDO, 0, 0, &lResult); + return (BOOL) lResult == TRUE; + } + + void CRichEditUI::Clear () { + TxSendMessage (WM_CLEAR, 0, 0, 0); + } + + void CRichEditUI::Copy () { + TxSendMessage (WM_COPY, 0, 0, 0); + } + + void CRichEditUI::Cut () { + TxSendMessage (WM_CUT, 0, 0, 0); + } + + void CRichEditUI::Paste () { + TxSendMessage (WM_PASTE, 0, 0, 0); + } + + int CRichEditUI::GetLineCount () const { + if (!m_pTwh) return 0; + LRESULT lResult; + TxSendMessage (EM_GETLINECOUNT, 0, 0, &lResult); + return (int) lResult; + } + + CDuiString CRichEditUI::GetLine (int nIndex, int nMaxLength) const { + LPWSTR lpText = nullptr; + lpText = new WCHAR[nMaxLength + 1]; + ::ZeroMemory (lpText, (nMaxLength + 1) * sizeof (WCHAR)); + *(LPWORD) lpText = (WORD) nMaxLength; + TxSendMessage (EM_GETLINE, nIndex, (LPARAM) lpText, 0); + CDuiString sText; +#ifdef UNICODE + sText = (LPCWSTR) lpText; +#else + sText = _conv_to_multi (lpText); +#endif + delete[] lpText; + return sText; + } + + int CRichEditUI::LineIndex (int nLine) const { + LRESULT lResult; + TxSendMessage (EM_LINEINDEX, nLine, 0, &lResult); + return (int) lResult; + } + + int CRichEditUI::LineLength (int nLine) const { + LRESULT lResult; + TxSendMessage (EM_LINELENGTH, nLine, 0, &lResult); + return (int) lResult; + } + + bool CRichEditUI::LineScroll (int nLines, int nChars) { + LRESULT lResult; + TxSendMessage (EM_LINESCROLL, nChars, nLines, &lResult); + return (BOOL) lResult == TRUE; + } + + POINT CRichEditUI::GetCharPos (long lChar) const { + POINT pt = { 0 }; + TxSendMessage (EM_POSFROMCHAR, (WPARAM) &pt, (LPARAM) lChar, 0); + return pt; + } + + long CRichEditUI::LineFromChar (long nIndex) const { + if (!m_pTwh) return 0L; + LRESULT lResult; + TxSendMessage (EM_EXLINEFROMCHAR, 0, nIndex, &lResult); + return (long) lResult; + } + + POINT CRichEditUI::PosFromChar (UINT nChar) const { + POINT pt = { 0 }; + TxSendMessage (EM_POSFROMCHAR, (WPARAM) &pt, nChar, 0); + return { pt.x, pt.y }; + } + + int CRichEditUI::CharFromPos (POINT pt) const { + if (!m_pTwh) return 0; + LRESULT lResult; + TxSendMessage (EM_CHARFROMPOS, 0, (LPARAM) &pt, &lResult); + return (int) lResult; + } + + void CRichEditUI::EmptyUndoBuffer () { + TxSendMessage (EM_EMPTYUNDOBUFFER, 0, 0, 0); + } + + UINT CRichEditUI::SetUndoLimit (UINT nLimit) { + if (!m_pTwh) return 0; + LRESULT lResult; + TxSendMessage (EM_SETUNDOLIMIT, (WPARAM) nLimit, 0, &lResult); + return (UINT) lResult; + } + + long CRichEditUI::StreamIn (int nFormat, EDITSTREAM &es) { + if (!m_pTwh) return 0L; + LRESULT lResult; + TxSendMessage (EM_STREAMIN, nFormat, (LPARAM) &es, &lResult); + return (long) lResult; + } + + long CRichEditUI::StreamOut (int nFormat, EDITSTREAM &es) { + if (!m_pTwh) return 0L; + LRESULT lResult; + TxSendMessage (EM_STREAMOUT, nFormat, (LPARAM) &es, &lResult); + return (long) lResult; + } + + void CRichEditUI::SetAccumulateDBCMode (bool bDBCMode) { + m_fAccumulateDBC = bDBCMode; + } + + bool CRichEditUI::IsAccumulateDBCMode () { + return m_fAccumulateDBC; + } + + void CRichEditUI::DoInit () { + if (m_bInited) + return; + + CREATESTRUCT cs; + cs.style = m_lTwhStyle; + cs.x = 0; + cs.y = 0; + cs.cy = 0; + cs.cx = 0; + cs.lpszName = m_sText.c_str (); + CreateHost (this, &cs, &m_pTwh); + if (m_pTwh) { + if (m_bTransparent) m_pTwh->SetTransparent (TRUE); + LRESULT lResult; + m_pTwh->GetTextServices ()->TxSendMessage (EM_SETLANGOPTIONS, 0, 0, &lResult); + m_pTwh->GetTextServices ()->TxSendMessage (EM_SETEVENTMASK, 0, ENM_DROPFILES | ENM_LINK | ENM_CHANGE, &lResult); + m_pTwh->OnTxInPlaceActivate (nullptr); + m_pManager->AddMessageFilter (this); + if (m_pManager->IsLayered ()) m_pManager->SetTimer (this, DEFAULT_TIMERID, ::GetCaretBlinkTime ()); + if (!m_bEnabled) { + m_pTwh->SetColor (m_pManager->GetDefaultDisabledColor ()); + } + } + + m_bInited = true; + } + + HRESULT CRichEditUI::TxSendMessage (UINT msg, WPARAM wparam, LPARAM lparam, LRESULT *plresult) const { + if (m_pTwh) { + if (msg == WM_KEYDOWN && TCHAR (wparam) == VK_RETURN) { + if (!m_bWantReturn || (::GetKeyState (VK_CONTROL) < 0 && !m_bWantCtrlReturn)) { + if (m_pManager) m_pManager->SendNotify ((CControlUI*) this, DUI_MSGTYPE_RETURN); + return S_OK; + } + } + return m_pTwh->GetTextServices ()->TxSendMessage (msg, wparam, lparam, plresult); + } + return S_FALSE; + } + + IDropTarget* CRichEditUI::GetTxDropTarget () { + IDropTarget *pdt = nullptr; + if (m_pTwh->GetTextServices ()->TxGetDropTarget (&pdt) == NOERROR) return pdt; + return nullptr; + } + + bool CRichEditUI::OnTxViewChanged () { + return true; + } + + bool CRichEditUI::SetDropAcceptFile (bool bAccept) { + LRESULT lResult; + TxSendMessage (EM_SETEVENTMASK, 0, ENM_DROPFILES | ENM_LINK, // ENM_CHANGE| ENM_CORRECTTEXT | ENM_DRAGDROPDONE | ENM_DROPFILES | ENM_IMECHANGE | ENM_LINK | ENM_OBJECTPOSITIONS | ENM_PROTECTED | ENM_REQUESTRESIZE | ENM_SCROLL | ENM_SELCHANGE | ENM_UPDATE, + &lResult); + return (BOOL) lResult == FALSE; + } + + void CRichEditUI::OnTxNotify (DWORD iNotify, void *pv) { + switch (iNotify) { + case EN_CHANGE: + { + GetManager ()->SendNotify (this, DUI_MSGTYPE_TEXTCHANGED); + break; + } + case EN_DROPFILES: + case EN_MSGFILTER: + case EN_OLEOPFAILED: + case EN_PROTECTED: + case EN_SAVECLIPBOARD: + case EN_SELCHANGE: + case EN_STOPNOUNDO: + case EN_LINK: + case EN_OBJECTPOSITIONS: + case EN_DRAGDROPDONE: + { + if (pv) // Fill out NMHDR portion of pv + { + LONG nId = GetWindowLong (this->GetManager ()->GetPaintWindow (), GWL_ID); + NMHDR *phdr = (NMHDR *) pv; + phdr->hwndFrom = this->GetManager ()->GetPaintWindow (); + phdr->idFrom = nId; + phdr->code = iNotify; + + if (SendMessage (this->GetManager ()->GetPaintWindow (), WM_NOTIFY, (WPARAM) nId, (LPARAM) pv)) { + //hr = S_FALSE; + } + } + } + break; + } + } + + // зrichʽricheditһbugһǿʱLineDownSetScrollPos޷ + // iPosΪbug + void CRichEditUI::SetScrollPos (SIZE szPos, bool bMsg) { + int cx = 0; + int cy = 0; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + int iLastScrollPos = m_pVerticalScrollBar->GetScrollPos (); + m_pVerticalScrollBar->SetScrollPos (szPos.cy); + cy = m_pVerticalScrollBar->GetScrollPos () - iLastScrollPos; + } + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + int iLastScrollPos = m_pHorizontalScrollBar->GetScrollPos (); + m_pHorizontalScrollBar->SetScrollPos (szPos.cx); + cx = m_pHorizontalScrollBar->GetScrollPos () - iLastScrollPos; + } + if (cy != 0) { + int iPos = 0; + if (m_pTwh && !m_bRich && m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) + iPos = m_pVerticalScrollBar->GetScrollPos (); + WPARAM wParam = MAKEWPARAM (SB_THUMBPOSITION, m_pVerticalScrollBar->GetScrollPos ()); + TxSendMessage (WM_VSCROLL, wParam, 0L, 0); + if (m_pTwh && !m_bRich && m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + if (cy > 0 && m_pVerticalScrollBar->GetScrollPos () <= iPos) + m_pVerticalScrollBar->SetScrollPos (iPos); + } + } + if (cx != 0) { + WPARAM wParam = MAKEWPARAM (SB_THUMBPOSITION, m_pHorizontalScrollBar->GetScrollPos ()); + TxSendMessage (WM_HSCROLL, wParam, 0L, 0); + } + } + + void CRichEditUI::LineUp () { + TxSendMessage (WM_VSCROLL, SB_LINEUP, 0L, 0); + } + + void CRichEditUI::LineDown () { + int iPos = 0; + if (m_pTwh && !m_bRich && m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) + iPos = m_pVerticalScrollBar->GetScrollPos (); + TxSendMessage (WM_VSCROLL, SB_LINEDOWN, 0L, 0); + if (m_pTwh && !m_bRich && m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + if (m_pVerticalScrollBar->GetScrollPos () <= iPos) + m_pVerticalScrollBar->SetScrollPos (m_pVerticalScrollBar->GetScrollRange ()); + } + } + + void CRichEditUI::PageUp () { + TxSendMessage (WM_VSCROLL, SB_PAGEUP, 0L, 0); + } + + void CRichEditUI::PageDown () { + TxSendMessage (WM_VSCROLL, SB_PAGEDOWN, 0L, 0); + } + + void CRichEditUI::HomeUp () { + TxSendMessage (WM_VSCROLL, SB_TOP, 0L, 0); + } + + void CRichEditUI::EndDown () { + TxSendMessage (WM_VSCROLL, SB_BOTTOM, 0L, 0); + } + + void CRichEditUI::LineLeft () { + TxSendMessage (WM_HSCROLL, SB_LINELEFT, 0L, 0); + } + + void CRichEditUI::LineRight () { + TxSendMessage (WM_HSCROLL, SB_LINERIGHT, 0L, 0); + } + + void CRichEditUI::PageLeft () { + TxSendMessage (WM_HSCROLL, SB_PAGELEFT, 0L, 0); + } + + void CRichEditUI::PageRight () { + TxSendMessage (WM_HSCROLL, SB_PAGERIGHT, 0L, 0); + } + + void CRichEditUI::HomeLeft () { + TxSendMessage (WM_HSCROLL, SB_LEFT, 0L, 0); + } + + void CRichEditUI::EndRight () { + TxSendMessage (WM_HSCROLL, SB_RIGHT, 0L, 0); + } + + void CRichEditUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CControlUI::DoEvent (event); + return; + } + + if (event.Type == UIEVENT_SETCURSOR && IsEnabled ()) { + if (m_pTwh && m_pTwh->DoSetCursor (nullptr, &event.ptMouse)) { + return; + } + } else if (event.Type == UIEVENT_WINDOWSIZE) { + if (m_pTwh) m_pTwh->NeedFreshCaret (); + } else if (event.Type == UIEVENT_SETFOCUS) { + if (m_pTwh) { + m_pTwh->OnTxInPlaceActivate (nullptr); + m_pTwh->GetTextServices ()->TxSendMessage (WM_SETFOCUS, 0, 0, 0); + } + m_bFocused = true; + Invalidate (); + return; + } + if (event.Type == UIEVENT_KILLFOCUS) { + if (m_pTwh) { + m_pTwh->OnTxInPlaceActivate (nullptr); + m_pTwh->GetTextServices ()->TxSendMessage (WM_KILLFOCUS, 0, 0, 0); + } + m_bFocused = false; + Invalidate (); + return; + } else if (event.Type == UIEVENT_TIMER) { + if (event.wParam == DEFAULT_TIMERID) { + if (m_pTwh && m_pManager->IsLayered () && IsFocused ()) { + if (::GetFocus () != m_pManager->GetPaintWindow ()) return; + m_bDrawCaret = !m_bDrawCaret; + POINT ptCaret = { 0 }; + ::GetCaretPos (&ptCaret); + RECT rcCaret = { ptCaret.x, ptCaret.y, ptCaret.x + m_pTwh->GetCaretWidth (), + ptCaret.y + m_pTwh->GetCaretHeight () }; + RECT rcTemp = rcCaret; + if (!::IntersectRect (&rcCaret, &rcTemp, &m_rcItem)) return; + CControlUI* pParent = this; + RECT rcParent = { 0 }; + while (!!(pParent = pParent->GetParent ())) { + rcTemp = rcCaret; + rcParent = pParent->GetPos (); + if (!::IntersectRect (&rcCaret, &rcTemp, &rcParent)) + return; + } + m_pManager->Invalidate (rcCaret); + } + return; + } + if (m_pTwh) { + m_pTwh->GetTextServices ()->TxSendMessage (WM_TIMER, event.wParam, event.lParam, 0); + } + return; + } + if (event.Type == UIEVENT_SCROLLWHEEL) { + if ((event.wKeyState & MK_CONTROL) != 0) { + return; + } + } + if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK) { + return; + } + if (event.Type == UIEVENT_MOUSEMOVE) { + return; + } + if (event.Type == UIEVENT_BUTTONUP) { + return; + } + if (event.Type > UIEVENT__KEYBEGIN && event.Type < UIEVENT__KEYEND) { + return; + } + CContainerUI::DoEvent (event); + } + + SIZE CRichEditUI::EstimateSize (SIZE szAvailable) { + //return CDuiSize(m_rcItem); // ַʽڵһôС֮ʹС + return CContainerUI::EstimateSize (szAvailable); + } + + void CRichEditUI::SetPos (RECT rc, bool bNeedInvalidate) { + CControlUI::SetPos (rc, bNeedInvalidate); + rc = m_rcItem; + + rc.left += m_rcInset.left; + rc.top += m_rcInset.top; + rc.right -= m_rcInset.right; + rc.bottom -= m_rcInset.bottom; + + RECT rcScrollView = rc; + + bool bVScrollBarVisiable = false; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + bVScrollBarVisiable = true; + rc.top -= m_pVerticalScrollBar->GetScrollPos (); + rc.bottom -= m_pVerticalScrollBar->GetScrollPos (); + rc.bottom += m_pVerticalScrollBar->GetScrollRange (); + rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + rcScrollView.right -= m_pVerticalScrollBar->GetFixedWidth (); + } + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + rc.left -= m_pHorizontalScrollBar->GetScrollPos (); + rc.right -= m_pHorizontalScrollBar->GetScrollPos (); + rc.right += m_pHorizontalScrollBar->GetScrollRange (); + rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + rcScrollView.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + } + + if (m_pTwh) { + RECT rcScrollTextView = rcScrollView; + rcScrollTextView.left += m_rcTextPadding.left; + rcScrollTextView.right -= m_rcTextPadding.right; + rcScrollTextView.top += m_rcTextPadding.top; + rcScrollTextView.bottom -= m_rcTextPadding.bottom; + RECT rcText = rc; + rcText.left += m_rcTextPadding.left; + rcText.right -= m_rcTextPadding.right; + rcText.top += m_rcTextPadding.top; + rcText.bottom -= m_rcTextPadding.bottom; + m_pTwh->SetClientRect (&rcScrollTextView); + if (bVScrollBarVisiable && (!m_pVerticalScrollBar->IsVisible () || m_bVScrollBarFixing)) { + LONG lWidth = rcText.right - rcText.left + m_pVerticalScrollBar->GetFixedWidth (); + LONG lHeight = 0; + SIZEL szExtent = { -1, -1 }; + m_pTwh->GetTextServices ()->TxGetNaturalSize ( + DVASPECT_CONTENT, + GetManager ()->GetPaintDC (), + nullptr, + nullptr, + TXTNS_FITTOCONTENT, + &szExtent, + &lWidth, + &lHeight); + if (lHeight > rcText.bottom - rcText.top) { + m_pVerticalScrollBar->SetVisible (true); + m_pVerticalScrollBar->SetScrollPos (0); + m_bVScrollBarFixing = true; + } else { + if (m_bVScrollBarFixing) { + m_pVerticalScrollBar->SetVisible (false); + m_bVScrollBarFixing = false; + } + } + } + } + + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + RECT rcScrollBarPos = { rcScrollView.right, rcScrollView.top, + rcScrollView.right + m_pVerticalScrollBar->GetFixedWidth (), rcScrollView.bottom }; + m_pVerticalScrollBar->SetPos (rcScrollBarPos, false); + } + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + RECT rcScrollBarPos = { rcScrollView.left, rcScrollView.bottom, rcScrollView.right, + rcScrollView.bottom + m_pHorizontalScrollBar->GetFixedHeight () }; + m_pHorizontalScrollBar->SetPos (rcScrollBarPos, false); + } + + for (int it = 0; it < m_items.GetSize (); it++) { + CControlUI* pControl = static_cast(m_items[it]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) { + SetFloatPos (it); + } else { + SIZE sz = { rc.right - rc.left, rc.bottom - rc.top }; + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + if (sz.cx > pControl->GetMaxWidth ()) sz.cx = pControl->GetMaxWidth (); + if (sz.cy < pControl->GetMinHeight ()) sz.cy = pControl->GetMinHeight (); + if (sz.cy > pControl->GetMaxHeight ()) sz.cy = pControl->GetMaxHeight (); + RECT rcCtrl = { rc.left, rc.top, rc.left + sz.cx, rc.top + sz.cy }; + pControl->SetPos (rcCtrl, false); + } + } + } + + void CRichEditUI::Move (SIZE szOffset, bool bNeedInvalidate) { + CContainerUI::Move (szOffset, bNeedInvalidate); + if (m_pTwh) { + RECT rc = m_rcItem; + rc.left += m_rcInset.left; + rc.top += m_rcInset.top; + rc.right -= m_rcInset.right; + rc.bottom -= m_rcInset.bottom; + + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + m_pTwh->SetClientRect (&rc); + } + } + + bool CRichEditUI::DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + RECT rcTemp = { 0 }; + if (!::IntersectRect (&rcTemp, &rcPaint, &m_rcItem)) return true; + + CRenderClip clip; + CRenderClip::GenerateClip (hDC, rcTemp, clip); + CControlUI::DoPaint (hDC, rcPaint, pStopControl); + + if (m_pTwh) { + RECT rc = { 0 }; + m_pTwh->GetControlRect (&rc); + // Remember wparam is actually the hdc and lparam is the update + // rect because this message has been preprocessed by the window. + m_pTwh->GetTextServices ()->TxDraw ( + DVASPECT_CONTENT, // Draw Aspect + /*-1*/0, // Lindex + nullptr, // Info for drawing optimazation + nullptr, // target device information + hDC, // Draw device HDC + nullptr, // Target device HDC + (RECTL*) &rc, // Bounding client rectangle + nullptr, // Clipping rectangle for metafiles + (RECT*) &rcPaint, // Update rectangle + nullptr, // Call back function + 0, // Call back parameter + 0); // What view of the object + if (m_bVScrollBarFixing) { + LONG lWidth = rc.right - rc.left + m_pVerticalScrollBar->GetFixedWidth (); + LONG lHeight = 0; + SIZEL szExtent = { -1, -1 }; + m_pTwh->GetTextServices ()->TxGetNaturalSize ( + DVASPECT_CONTENT, + GetManager ()->GetPaintDC (), + nullptr, + nullptr, + TXTNS_FITTOCONTENT, + &szExtent, + &lWidth, + &lHeight); + if (lHeight <= rc.bottom - rc.top) { + NeedUpdate (); + } + } + } + + if (m_items.GetSize () > 0) { + RECT rc = m_rcItem; + rc.left += m_rcInset.left; + rc.top += m_rcInset.top; + rc.right -= m_rcInset.right; + rc.bottom -= m_rcInset.bottom; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + + if (!::IntersectRect (&rcTemp, &rcPaint, &rc)) { + for (int it = 0; it < m_items.GetSize (); it++) { + CControlUI* pControl = static_cast(m_items[it]); + if (pControl == pStopControl) return false; + if (!pControl->IsVisible ()) continue; + if (!::IntersectRect (&rcTemp, &rcPaint, &pControl->GetPos ())) continue; + if (pControl->IsFloat ()) { + if (!::IntersectRect (&rcTemp, &m_rcItem, &pControl->GetPos ())) continue; + if (!pControl->Paint (hDC, rcPaint, pStopControl)) return false; + } + } + } else { + CRenderClip childClip; + CRenderClip::GenerateClip (hDC, rcTemp, childClip); + for (int it = 0; it < m_items.GetSize (); it++) { + CControlUI* pControl = static_cast(m_items[it]); + if (pControl == pStopControl) return false; + if (!pControl->IsVisible ()) continue; + if (!::IntersectRect (&rcTemp, &rcPaint, &pControl->GetPos ())) continue; + if (pControl->IsFloat ()) { + if (!::IntersectRect (&rcTemp, &m_rcItem, &pControl->GetPos ())) continue; + CRenderClip::UseOldClipBegin (hDC, childClip); + if (!pControl->Paint (hDC, rcPaint, pStopControl)) return false; + CRenderClip::UseOldClipEnd (hDC, childClip); + } else { + if (!::IntersectRect (&rcTemp, &rc, &pControl->GetPos ())) continue; + if (!pControl->Paint (hDC, rcPaint, pStopControl)) return false; + } + } + } + } + + if (m_pTwh && m_pTwh->IsShowCaret () && m_pManager->IsLayered () && IsFocused () && m_bDrawCaret) { + POINT ptCaret = { 0 }; + ::GetCaretPos (&ptCaret); + if (::PtInRect (&m_rcItem, ptCaret)) { + RECT rcCaret = { ptCaret.x, ptCaret.y, ptCaret.x, ptCaret.y + m_pTwh->GetCaretHeight () }; + CRenderEngine::DrawLine (hDC, rcCaret, m_pTwh->GetCaretWidth (), 0xFF000000); + } + } + + if (m_pVerticalScrollBar) { + if (m_pVerticalScrollBar == pStopControl) return false; + if (m_pVerticalScrollBar->IsVisible ()) { + if (::IntersectRect (&rcTemp, &rcPaint, &m_pVerticalScrollBar->GetPos ())) { + if (!m_pVerticalScrollBar->Paint (hDC, rcPaint, pStopControl)) return false; + } + } + } + + if (m_pHorizontalScrollBar) { + if (m_pHorizontalScrollBar == pStopControl) return false; + if (m_pHorizontalScrollBar->IsVisible ()) { + if (::IntersectRect (&rcTemp, &rcPaint, &m_pHorizontalScrollBar->GetPos ())) { + if (!m_pHorizontalScrollBar->Paint (hDC, rcPaint, pStopControl)) return false; + } + } + } + // ʾ + CDuiString sDrawText = GetText (); + if (sDrawText.empty () && !m_bFocused) { + DWORD dwTextColor = GetTipValueColor (); + CDuiString sTipValue = GetTipValue (); + RECT rc = m_rcItem; + rc.left += m_rcTextPadding.left; + rc.right -= m_rcTextPadding.right; + rc.top += m_rcTextPadding.top; + rc.bottom -= m_rcTextPadding.bottom; + UINT uTextAlign = GetTipValueAlign (); + if (IsMultiLine ()) uTextAlign |= DT_TOP; + else uTextAlign |= DT_VCENTER; + CRenderEngine::DrawText (hDC, m_pManager, rc, sTipValue, dwTextColor, m_iFont, uTextAlign); + } + return true; + } + + string_view_t CRichEditUI::GetNormalImage () { + return m_sNormalImage; + } + + void CRichEditUI::SetNormalImage (string_view_t pStrImage) { + m_sNormalImage = pStrImage; + Invalidate (); + } + + string_view_t CRichEditUI::GetHotImage () { + return m_sHotImage; + } + + void CRichEditUI::SetHotImage (string_view_t pStrImage) { + m_sHotImage = pStrImage; + Invalidate (); + } + + string_view_t CRichEditUI::GetFocusedImage () { + return m_sFocusedImage; + } + + void CRichEditUI::SetFocusedImage (string_view_t pStrImage) { + m_sFocusedImage = pStrImage; + Invalidate (); + } + + string_view_t CRichEditUI::GetDisabledImage () { + return m_sDisabledImage; + } + + void CRichEditUI::SetDisabledImage (string_view_t pStrImage) { + m_sDisabledImage = pStrImage; + Invalidate (); + } + + RECT CRichEditUI::GetTextPadding () const { + return m_rcTextPadding; + } + + void CRichEditUI::SetTextPadding (RECT rc) { + m_rcTextPadding = rc; + Invalidate (); + } + + void CRichEditUI::SetTipValue (string_view_t pStrTipValue) { + m_sTipValue = pStrTipValue; + } + + string_view_t CRichEditUI::GetTipValue () { + return m_sTipValue; + } + + void CRichEditUI::SetTipValueColor (string_view_t pStrColor) { + m_dwTipValueColor = (DWORD) FawTools::parse_hex (pStrColor); + } + + DWORD CRichEditUI::GetTipValueColor () { + return m_dwTipValueColor; + } + + void CRichEditUI::SetTipValueAlign (UINT uAlign) { + m_uTipValueAlign = uAlign; + if (GetText ().empty ()) Invalidate (); + } + + UINT CRichEditUI::GetTipValueAlign () { + return m_uTipValueAlign; + } + + void CRichEditUI::PaintStatusImage (HDC hDC) { + if (IsFocused ()) m_uButtonState |= UISTATE_FOCUSED; + else m_uButtonState &= ~UISTATE_FOCUSED; + if (!IsEnabled ()) m_uButtonState |= UISTATE_DISABLED; + else m_uButtonState &= ~UISTATE_DISABLED; + + if ((m_uButtonState & UISTATE_DISABLED) != 0) { + if (!m_sDisabledImage.empty ()) { + if (!DrawImage (hDC, m_sDisabledImage)) { + } else return; + } + } else if ((m_uButtonState & UISTATE_FOCUSED) != 0) { + if (!m_sFocusedImage.empty ()) { + if (!DrawImage (hDC, m_sFocusedImage)) { + } else return; + } + } else if ((m_uButtonState & UISTATE_HOT) != 0) { + if (!m_sHotImage.empty ()) { + if (!DrawImage (hDC, m_sHotImage)) { + } else return; + } + } + + if (!m_sNormalImage.empty ()) { + if (!DrawImage (hDC, m_sNormalImage)) { + } else return; + } + } + + void CRichEditUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("vscrollbar")) { + if (FawTools::parse_bool (pstrValue)) m_lTwhStyle |= ES_DISABLENOSCROLL | WS_VSCROLL; + } + if (pstrName == _T ("autovscroll")) { + if (FawTools::parse_bool (pstrValue)) m_lTwhStyle |= ES_AUTOVSCROLL; + } else if (pstrName == _T ("hscrollbar")) { + if (FawTools::parse_bool (pstrValue)) m_lTwhStyle |= ES_DISABLENOSCROLL | WS_HSCROLL; + } + if (pstrName == _T ("autohscroll")) { + if (FawTools::parse_bool (pstrValue)) + m_lTwhStyle |= ES_AUTOHSCROLL; + else + m_lTwhStyle &= ~ES_AUTOHSCROLL; + } else if (pstrName == _T ("multiline")) { + SetMultiLine (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("wanttab")) { + SetWantTab (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("wantreturn")) { + SetWantReturn (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("wantctrlreturn")) { + SetWantCtrlReturn (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("transparent")) { + SetTransparent (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("rich")) { + SetRich (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("multiline")) { + if (!FawTools::parse_bool (pstrValue)) m_lTwhStyle &= ~ES_MULTILINE; + } else if (pstrName == _T ("readonly")) { + if (FawTools::parse_bool (pstrValue)) { + m_lTwhStyle |= ES_READONLY; m_bReadOnly = true; + } + } else if (pstrName == _T ("password")) { + if (FawTools::parse_bool (pstrValue)) m_lTwhStyle |= ES_PASSWORD; + } else if (pstrName == _T ("align")) { + if (pstrValue.find (_T ("left")) != string_t::npos) { + m_lTwhStyle &= ~(ES_CENTER | ES_RIGHT); + m_lTwhStyle |= ES_LEFT; + } + if (pstrValue.find (_T ("center")) != string_t::npos) { + m_lTwhStyle &= ~(ES_LEFT | ES_RIGHT); + m_lTwhStyle |= ES_CENTER; + } + if (pstrValue.find (_T ("right")) != string_t::npos) { + m_lTwhStyle &= ~(ES_LEFT | ES_CENTER); + m_lTwhStyle |= ES_RIGHT; + } + } else if (pstrName == _T ("font")) SetFont (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("textcolor")) SetTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + else if (pstrName == _T ("maxchar")) SetLimitText (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("normalimage")) SetNormalImage (pstrValue); + else if (pstrName == _T ("hotimage")) SetHotImage (pstrValue); + else if (pstrName == _T ("focusedimage")) SetFocusedImage (pstrValue); + else if (pstrName == _T ("disabledimage")) SetDisabledImage (pstrValue); + else if (pstrName == _T ("textpadding")) SetTextPadding (FawTools::parse_rect (pstrValue)); + else if (pstrName == _T ("tipvalue")) SetTipValue (pstrValue); + else if (pstrName == _T ("tipvaluecolor")) SetTipValueColor (pstrValue); + else if (pstrName == _T ("tipvaluealign")) { + if (pstrValue.find (_T ("left")) != string_t::npos) { + m_uTipValueAlign = DT_SINGLELINE | DT_LEFT; + } + if (pstrValue.find (_T ("center")) != string_t::npos) { + m_uTipValueAlign = DT_SINGLELINE | DT_CENTER; + } + if (pstrValue.find (_T ("right")) != string_t::npos) { + m_uTipValueAlign = DT_SINGLELINE | DT_RIGHT; + } + } else CContainerUI::SetAttribute (pstrName, pstrValue); + } + + LRESULT CRichEditUI::MessageHandler (UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled) { + if (!IsVisible () || !IsEnabled ()) return 0; + if (!IsMouseEnabled () && uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST) return 0; + if (uMsg == WM_MOUSEWHEEL && (LOWORD (wParam) & MK_CONTROL) == 0) return 0; + + if (uMsg == WM_IME_COMPOSITION) { + // ΢뷨λ쳣 + HIMC hIMC = ImmGetContext (GetManager ()->GetPaintWindow ()); + if (hIMC) { + POINT point = { 0 }; + GetCaretPos (&point); + + COMPOSITIONFORM Composition; + Composition.dwStyle = CFS_POINT; + Composition.ptCurrentPos.x = point.x; + Composition.ptCurrentPos.y = point.y; + ImmSetCompositionWindow (hIMC, &Composition); + + ImmReleaseContext (GetManager ()->GetPaintWindow (), hIMC); + } + + return 0; + } + bool bWasHandled = true; + if ((uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST) || uMsg == WM_SETCURSOR) { + if (!m_pTwh->IsCaptured ()) { + switch (uMsg) { + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MOUSEMOVE: + { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + CControlUI* pHover = GetManager ()->FindControl (pt); + if (pHover != this) { + bWasHandled = false; + return 0; + } + } + break; + } + } + // Mouse message only go when captured or inside rect + DWORD dwHitResult = m_pTwh->IsCaptured () ? HITRESULT_HIT : HITRESULT_OUTSIDE; + if (dwHitResult == HITRESULT_OUTSIDE) { + RECT rc = { 0 }; + m_pTwh->GetControlRect (&rc); + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + if (uMsg == WM_SETCURSOR) { + ::GetCursorPos (&pt); + ::ScreenToClient (GetManager ()->GetPaintWindow (), &pt); + } else if (uMsg == WM_MOUSEWHEEL) ::ScreenToClient (GetManager ()->GetPaintWindow (), &pt); + if (::PtInRect (&rc, pt) && !GetManager ()->IsCaptured ()) dwHitResult = HITRESULT_HIT; + } + if (dwHitResult != HITRESULT_HIT) return 0; + if (uMsg == WM_SETCURSOR) bWasHandled = false; + else if (uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONDBLCLK || uMsg == WM_RBUTTONDOWN) { + if (!GetManager ()->IsNoActivate ()) ::SetFocus (GetManager ()->GetPaintWindow ()); + SetFocus (); + } + } +#ifdef _UNICODE + else if (uMsg >= WM_KEYFIRST && uMsg <= WM_KEYLAST) { +#else + else if ((uMsg >= WM_KEYFIRST && uMsg <= WM_KEYLAST) || uMsg == WM_CHAR || uMsg == WM_IME_CHAR) { +#endif + if (!IsFocused ()) return 0; + } +#ifdef _USEIMM + else if (uMsg == WM_IME_STARTCOMPOSITION) { + if (IsFocused ()) { + POINT ptCaret = { 0 }; + ::GetCaretPos (&ptCaret); + HIMC hMic = ::ImmGetContext (GetManager ()->GetPaintWindow ()); + COMPOSITIONFORM cpf; + cpf.dwStyle = CFS_FORCE_POSITION; + cpf.ptCurrentPos.x = ptCaret.x + m_pTwh->GetCaretWidth (); + cpf.ptCurrentPos.y = ptCaret.y; + ::ImmSetCompositionWindow (hMic, &cpf); + + HFONT hFont = GetManager ()->GetFont (m_iFont); + LOGFONT lf; + ::GetObject (hFont, sizeof (LOGFONT), &lf); + ::ImmSetCompositionFont (hMic, &lf); + + ::ImmReleaseContext (GetManager ()->GetPaintWindow (), hMic); + } + bWasHandled = false; + return 0; + } +#endif + else if (uMsg == WM_CONTEXTMENU) { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + ::ScreenToClient (GetManager ()->GetPaintWindow (), &pt); + CControlUI* pHover = GetManager ()->FindControl (pt); + if (pHover != this) { + bWasHandled = false; + return 0; + } + //һʽ˵ + HMENU hPopMenu = CreatePopupMenu (); + AppendMenu (hPopMenu, 0, ID_RICH_UNDO, _T ("(&U)")); + AppendMenu (hPopMenu, 0, ID_RICH_REDO, _T ("(&R)")); + AppendMenu (hPopMenu, MF_SEPARATOR, 0, _T ("")); + AppendMenu (hPopMenu, 0, ID_RICH_CUT, _T ("(&X)")); + AppendMenu (hPopMenu, 0, ID_RICH_COPY, _T ("(&C)")); + AppendMenu (hPopMenu, 0, ID_RICH_PASTE, _T ("ճ(&V)")); + AppendMenu (hPopMenu, 0, ID_RICH_CLEAR, _T ("(&L)")); + AppendMenu (hPopMenu, MF_SEPARATOR, 0, _T ("")); + AppendMenu (hPopMenu, 0, ID_RICH_SELECTALL, _T ("ȫѡ(&A)")); + + //ʼ˵ + UINT uUndo = (CanUndo () ? 0 : MF_GRAYED); + EnableMenuItem (hPopMenu, ID_RICH_UNDO, MF_BYCOMMAND | uUndo); + UINT uRedo = (CanRedo () ? 0 : MF_GRAYED); + EnableMenuItem (hPopMenu, ID_RICH_REDO, MF_BYCOMMAND | uRedo); + UINT uSel = ((GetSelectionType () != SEL_EMPTY) ? 0 : MF_GRAYED); + UINT uReadonly = IsReadOnly () ? MF_GRAYED : 0; + EnableMenuItem (hPopMenu, ID_RICH_CUT, MF_BYCOMMAND | uSel | uReadonly); + EnableMenuItem (hPopMenu, ID_RICH_COPY, MF_BYCOMMAND | uSel); + EnableMenuItem (hPopMenu, ID_RICH_CLEAR, MF_BYCOMMAND | uSel | uReadonly); + EnableMenuItem (hPopMenu, ID_RICH_PASTE, MF_BYCOMMAND | uReadonly); + ::ClientToScreen (GetManager ()->GetPaintWindow (), &pt); + TrackPopupMenu (hPopMenu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, GetManager ()->GetPaintWindow (), nullptr); + DestroyMenu (hPopMenu); + } else if (uMsg == WM_COMMAND) { + bHandled = FALSE; + if (!IsFocused ()) return 0; + UINT uCmd = (UINT) wParam; + switch (uCmd) { + case ID_RICH_UNDO: + { + Undo (); + break; + } + case ID_RICH_REDO: + { + Redo (); + break; + } + case ID_RICH_CUT: + { + Cut (); + break; + } + case ID_RICH_COPY: + { + Copy (); + break; + } + case ID_RICH_PASTE: + { + Paste (); + break; + } + case ID_RICH_CLEAR: + { + Clear (); + break; + } + case ID_RICH_SELECTALL: + { + SetSelAll (); + break; + } + default:break; + } + } else { + switch (uMsg) { + case WM_HELP: + bWasHandled = false; + break; + default: + return 0; + } + } + if (WM_CHAR == uMsg) { +#ifndef _UNICODE + // check if we are waiting for 2 consecutive WM_CHAR messages + if (IsAccumulateDBCMode ()) { + if ((GetKeyState (VK_KANA) & 0x1)) { + // turn off accumulate mode + SetAccumulateDBCMode (false); + m_chLeadByte = 0; + } else { + if (!m_chLeadByte) { + // This is the first WM_CHAR message, + // accumulate it if this is a LeadByte. Otherwise, fall thru to + // regular WM_CHAR processing. + if (IsDBCSLeadByte ((WORD) wParam)) { + // save the Lead Byte and don't process this message + m_chLeadByte = (WORD) wParam << 8; + + //TCHAR a = (WORD)wParam << 8 ; + return 0; + } + } else { + // This is the second WM_CHAR message, + // combine the current byte with previous byte. + // This DBC will be handled as WM_IME_CHAR. + wParam |= m_chLeadByte; + uMsg = WM_IME_CHAR; + + // setup to accumulate more WM_CHAR + m_chLeadByte = 0; + } + } + } +#endif + } + LRESULT lResult = 0; + HRESULT Hr = TxSendMessage (uMsg, wParam, lParam, &lResult); + if (Hr == S_OK) bHandled = bWasHandled; + else if ((uMsg >= WM_KEYFIRST && uMsg <= WM_KEYLAST) || uMsg == WM_CHAR || uMsg == WM_IME_CHAR) + bHandled = bWasHandled; + else if (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST) { + if (m_pTwh->IsCaptured ()) bHandled = bWasHandled; + } + return lResult; + } + + } // namespace DuiLib diff --git a/DuiLib/Control/UIRichEdit.h b/DuiLib/Control/UIRichEdit.h new file mode 100644 index 0000000..bee83e6 --- /dev/null +++ b/DuiLib/Control/UIRichEdit.h @@ -0,0 +1,194 @@ +#ifndef __UIRICHEDIT_H__ +#define __UIRICHEDIT_H__ + +#pragma once + +namespace DuiLib { + + class CTxtWinHost; + + class UILIB_API CRichEditUI: public CContainerUI, public IMessageFilterUI { + DECLARE_DUICONTROL (CRichEditUI) + public: + CRichEditUI (); + virtual ~CRichEditUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetControlFlags () const; + + bool IsMultiLine (); + void SetMultiLine (bool bMultiLine); + bool IsWantTab (); + void SetWantTab (bool bWantTab = true); + bool IsWantReturn (); + void SetWantReturn (bool bWantReturn = true); + bool IsWantCtrlReturn (); + void SetWantCtrlReturn (bool bWantCtrlReturn = true); + bool IsTransparent (); + void SetTransparent (bool bTransparent = true); + bool IsRich (); + void SetRich (bool bRich = true); + bool IsReadOnly (); + void SetReadOnly (bool bReadOnly = true); + bool IsWordWrap (); + void SetWordWrap (bool bWordWrap = true); + int GetFont (); + void SetFont (int index); + void SetFont (string_view_t pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic); + LONG GetWinStyle (); + void SetWinStyle (LONG lStyle); + DWORD GetTextColor (); + void SetTextColor (DWORD dwTextColor); + int GetLimitText (); + void SetLimitText (int iChars); + long GetTextLength (DWORD dwFlags = GTL_DEFAULT) const; + CDuiString GetText () const; + void SetText (string_view_t pstrText); + bool IsModify () const; + void SetModify (bool bModified = true) const; + void GetSel (CHARRANGE &cr) const; + void GetSel (long& nStartChar, long& nEndChar) const; + int SetSel (CHARRANGE &cr); + int SetSel (long nStartChar, long nEndChar); + void ReplaceSel (string_view_t lpszNewText, bool bCanUndo); + void ReplaceSelW (LPCWSTR lpszNewText, bool bCanUndo = false); + CDuiString GetSelText () const; + int SetSelAll (); + int SetSelNone (); + WORD GetSelectionType () const; + bool GetZoom (int& nNum, int& nDen) const; + bool SetZoom (int nNum, int nDen); + bool SetZoomOff (); + bool GetAutoURLDetect () const; + bool SetAutoURLDetect (bool bAutoDetect = true); + DWORD GetEventMask () const; + DWORD SetEventMask (DWORD dwEventMask); + CDuiString GetTextRange (long nStartChar, long nEndChar) const; + void HideSelection (bool bHide = true, bool bChangeStyle = false); + void ScrollCaret (); + int InsertText (long nInsertAfterChar, string_view_t lpstrText, bool bCanUndo = false); + int AppendText (string_view_t lpstrText, bool bCanUndo = false); + DWORD GetDefaultCharFormat (CHARFORMAT2 &cf) const; + bool SetDefaultCharFormat (CHARFORMAT2 &cf); + DWORD GetSelectionCharFormat (CHARFORMAT2 &cf) const; + bool SetSelectionCharFormat (CHARFORMAT2 &cf); + bool SetWordCharFormat (CHARFORMAT2 &cf); + DWORD GetParaFormat (PARAFORMAT2 &pf) const; + bool SetParaFormat (PARAFORMAT2 &pf); + bool CanUndo (); + bool CanRedo (); + bool CanPaste (); + bool Redo (); + bool Undo (); + void Clear (); + void Copy (); + void Cut (); + void Paste (); + int GetLineCount () const; + CDuiString GetLine (int nIndex, int nMaxLength) const; + int LineIndex (int nLine = -1) const; + int LineLength (int nLine = -1) const; + bool LineScroll (int nLines, int nChars = 0); + POINT GetCharPos (long lChar) const; + long LineFromChar (long nIndex) const; + POINT PosFromChar (UINT nChar) const; + int CharFromPos (POINT pt) const; + void EmptyUndoBuffer (); + UINT SetUndoLimit (UINT nLimit); + long StreamIn (int nFormat, EDITSTREAM &es); + long StreamOut (int nFormat, EDITSTREAM &es); + void SetAccumulateDBCMode (bool bDBCMode); + bool IsAccumulateDBCMode (); + + RECT GetTextPadding () const; + void SetTextPadding (RECT rc); + string_view_t GetNormalImage (); + void SetNormalImage (string_view_t pStrImage); + string_view_t GetHotImage (); + void SetHotImage (string_view_t pStrImage); + string_view_t GetFocusedImage (); + void SetFocusedImage (string_view_t pStrImage); + string_view_t GetDisabledImage (); + void SetDisabledImage (string_view_t pStrImage); + void PaintStatusImage (HDC hDC); + + void SetTipValue (string_view_t pStrTipValue); + string_view_t GetTipValue (); + void SetTipValueColor (string_view_t pStrColor); + DWORD GetTipValueColor (); + void SetTipValueAlign (UINT uAlign); + UINT GetTipValueAlign (); + + void DoInit (); + bool SetDropAcceptFile (bool bAccept); + // ע⣺TxSendMessageSendMessageģTxSendMessageûmultibyteunicodeԶתĹܣ + // richedit2.0ڲunicodeʵֵģmultibyteУԼunicodemultibyteת + virtual HRESULT TxSendMessage (UINT msg, WPARAM wparam, LPARAM lparam, LRESULT *plresult) const; + IDropTarget* GetTxDropTarget (); + virtual bool OnTxViewChanged (); + virtual void OnTxNotify (DWORD iNotify, void *pv); + + void SetScrollPos (SIZE szPos, bool bMsg = true); + void LineUp (); + void LineDown (); + void PageUp (); + void PageDown (); + void HomeUp (); + void EndDown (); + void LineLeft (); + void LineRight (); + void PageLeft (); + void PageRight (); + void HomeLeft (); + void EndRight (); + + SIZE EstimateSize (SIZE szAvailable); + void SetPos (RECT rc, bool bNeedInvalidate = true); + void Move (SIZE szOffset, bool bNeedInvalidate = true); + void DoEvent (TEventUI& event); + bool DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl); + + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + LRESULT MessageHandler (UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled); + + protected: + enum { + DEFAULT_TIMERID = 20, + }; + + CTxtWinHost *m_pTwh = nullptr; + bool m_bVScrollBarFixing = false; + bool m_bWantTab = true; + bool m_bWantReturn = true; + bool m_bWantCtrlReturn = true; + bool m_bTransparent = true; + bool m_bRich = true; + bool m_bReadOnly = false; + bool m_bWordWrap = false; + DWORD m_dwTextColor = 0; + int m_iFont = -1; + int m_iLimitText; + LONG m_lTwhStyle; + bool m_bDrawCaret = true; + bool m_bInited = false; + + bool m_fAccumulateDBC; // TRUE - need to cumulate ytes from 2 WM_CHAR msgs + // we are in this mode when we receive VK_PROCESSKEY + UINT m_chLeadByte = 0; // use when we are in _fAccumulateDBC mode + + RECT m_rcTextPadding; + UINT m_uButtonState = 0; + CDuiString m_sNormalImage; + CDuiString m_sHotImage; + CDuiString m_sFocusedImage; + CDuiString m_sDisabledImage; + CDuiString m_sTipValue; + DWORD m_dwTipValueColor = 0xFFBAC0C5; + UINT m_uTipValueAlign = DT_SINGLELINE | DT_LEFT; + }; + +} // namespace DuiLib + +#endif // __UIRICHEDIT_H__ diff --git a/DuiLib/Control/UIRing.cpp b/DuiLib/Control/UIRing.cpp new file mode 100644 index 0000000..50ed74d --- /dev/null +++ b/DuiLib/Control/UIRing.cpp @@ -0,0 +1,78 @@ +#include "StdAfx.h" +#include "UIRing.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CRingUI) + + CRingUI::CRingUI () {} + + CRingUI::~CRingUI () { + if (m_pManager) m_pManager->KillTimer (this, RING_TIMERID); + + DeleteImage (); + } + + string_view_t CRingUI::GetClass () const { + return _T ("RingUI"); + } + + LPVOID CRingUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("Ring")) return static_cast(this); + return CLabelUI::GetInterface (pstrName); + } + + void CRingUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("bkimage")) SetBkImage (pstrValue); + else CLabelUI::SetAttribute (pstrName, pstrValue); + } + + void CRingUI::SetBkImage (string_view_t pStrImage) { + if (m_sBkImage == pStrImage) return; + m_sBkImage = pStrImage; + DeleteImage (); + Invalidate (); + } + + void CRingUI::PaintBkImage (HDC hDC) { + if (!m_pBkimage) + InitImage (); + if (!m_pBkimage) + return; + + RECT rcItem = m_rcItem; + int iWidth = rcItem.right - rcItem.left; + int iHeight = rcItem.bottom - rcItem.top; + Gdiplus::PointF centerPos ((Gdiplus::REAL) (rcItem.left + iWidth / 2), (Gdiplus::REAL) (rcItem.top + iHeight / 2)); + + Gdiplus::Graphics graphics (hDC); + graphics.TranslateTransform (centerPos.X, centerPos.Y); + graphics.RotateTransform (m_fCurAngle); + graphics.TranslateTransform (-centerPos.X, -centerPos.Y);//ԭԴ + graphics.DrawImage (m_pBkimage, rcItem.left, rcItem.top, iWidth, iHeight); + } + + void CRingUI::DoEvent (TEventUI& event) { + if (event.Type == UIEVENT_TIMER && event.wParam == RING_TIMERID) { + if (m_fCurAngle > 359) { + m_fCurAngle = 0; + } + m_fCurAngle += 36.0; + Invalidate (); + } else { + CLabelUI::DoEvent (event); + } + } + + void CRingUI::InitImage () { + m_pBkimage = CRenderEngine::GdiplusLoadImage (GetBkImage ()); + if (nullptr == m_pBkimage) return; + if (m_pManager) m_pManager->SetTimer (this, RING_TIMERID, 100); + } + + void CRingUI::DeleteImage () { + if (m_pBkimage) { + delete m_pBkimage; + m_pBkimage = nullptr; + } + } +} diff --git a/DuiLib/Control/UIRing.h b/DuiLib/Control/UIRing.h new file mode 100644 index 0000000..db29f73 --- /dev/null +++ b/DuiLib/Control/UIRing.h @@ -0,0 +1,33 @@ +#ifndef __UIROTATE_H__ +#define __UIROTATE_H__ + +#pragma once + +namespace DuiLib { + class CRingUI: public CLabelUI { + enum { + RING_TIMERID = 100, + }; + DECLARE_DUICONTROL (CRingUI) + public: + CRingUI (); + virtual ~CRingUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + void SetBkImage (string_view_t pStrImage); + virtual void DoEvent (TEventUI& event); + virtual void PaintBkImage (HDC hDC); + + private: + void InitImage (); + void DeleteImage (); + + public: + float m_fCurAngle = 0.0f; + Gdiplus::Image *m_pBkimage = nullptr; + }; +} + +#endif // __UIROTATE_H__ \ No newline at end of file diff --git a/DuiLib/Control/UIRollText.cpp b/DuiLib/Control/UIRollText.cpp new file mode 100644 index 0000000..e50f88b --- /dev/null +++ b/DuiLib/Control/UIRollText.cpp @@ -0,0 +1,132 @@ +#include "stdafx.h" +#include "UIRollText.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CRollTextUI) + + CRollTextUI::CRollTextUI (void) {} + + CRollTextUI::~CRollTextUI (void) { + m_pManager->KillTimer (this, ROLLTEXT_ROLL_END); + m_pManager->KillTimer (this, ROLLTEXT_TIMERID); + } + + string_view_t CRollTextUI::GetClass () const { + return _T ("RollTextUI"); + } + + LPVOID CRollTextUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("RollText")) return static_cast(this); + return CLabelUI::GetInterface (pstrName); + } + + void CRollTextUI::BeginRoll (int nDirect, LONG lTimeSpan, LONG lMaxTimeLimited) { + m_nRollDirection = nDirect; + if (m_bUseRoll) { + EndRoll (); + } + m_nText_W_H = 0; + + m_pManager->KillTimer (this, ROLLTEXT_TIMERID); + m_pManager->SetTimer (this, ROLLTEXT_TIMERID, lTimeSpan); + + m_pManager->KillTimer (this, ROLLTEXT_ROLL_END); + m_pManager->SetTimer (this, ROLLTEXT_ROLL_END, lMaxTimeLimited * 1000); + + m_bUseRoll = TRUE; + } + + void CRollTextUI::EndRoll () { + if (!m_bUseRoll) return; + + m_pManager->KillTimer (this, ROLLTEXT_ROLL_END); + m_pManager->KillTimer (this, ROLLTEXT_TIMERID); + + m_bUseRoll = FALSE; + } + + void CRollTextUI::SetPos (RECT rc) { + CLabelUI::SetPos (rc); + m_nText_W_H = 0; //ֱ仯¼ + } + + void CRollTextUI::SetText (string_view_t pstrText) { + CLabelUI::SetText (pstrText); + m_nText_W_H = 0; //ı仯¼ + } + + void CRollTextUI::DoEvent (TEventUI& event) { + if (event.Type == UIEVENT_TIMER && event.wParam == ROLLTEXT_ROLL_END) { + m_pManager->KillTimer (this, ROLLTEXT_ROLL_END); + m_pManager->SendNotify (this, DUI_MSGTYPE_TEXTROLLEND); + } else if (event.Type == UIEVENT_TIMER && event.wParam == ROLLTEXT_TIMERID) { + Invalidate (); + return; + } + CLabelUI::DoEvent (event); + } + + void CRollTextUI::PaintText (HDC hDC) { + if (m_dwTextColor == 0) m_dwTextColor = m_pManager->GetDefaultFontColor (); + if (m_dwDisabledTextColor == 0) m_dwDisabledTextColor = m_pManager->GetDefaultDisabledColor (); + DWORD dwTextColor = IsEnabled () ? m_dwTextColor : m_dwDisabledTextColor; + CDuiString sText = GetText (); + if (sText.empty ()) return; + RECT rcTextPadding = GetTextPadding (); + RECT rcClient; + rcClient = m_rcItem; + rcClient.left += rcTextPadding.left; + rcClient.right -= rcTextPadding.right; + rcClient.top += rcTextPadding.top; + rcClient.bottom -= rcTextPadding.bottom; + + if (m_nText_W_H > 0) { + int nScrollRange = 0; + + if (m_nRollDirection == ROLLTEXT_LEFT || m_nRollDirection == ROLLTEXT_RIGHT) { //ƶ + nScrollRange = m_nText_W_H + rcClient.right - rcClient.left; + LONG off_cx = (m_nRollDirection == ROLLTEXT_LEFT ? rcClient.right - rcClient.left : rcClient.left - rcClient.right); + ::OffsetRect (&rcClient, off_cx, 0); + off_cx = (m_nRollDirection == ROLLTEXT_LEFT ? -m_nScrollPos : m_nScrollPos); + ::OffsetRect (&rcClient, off_cx, 0); + rcClient.right += (m_nText_W_H - (rcClient.right - rcClient.left)); + } else { //ƶ + nScrollRange = m_nText_W_H + rcClient.bottom - rcClient.top; + LONG off_cy = (m_nRollDirection == ROLLTEXT_UP ? rcClient.bottom - rcClient.top : rcClient.top - rcClient.bottom); + ::OffsetRect (&rcClient, 0, off_cy); + off_cy = (m_nRollDirection == ROLLTEXT_UP ? -m_nScrollPos : m_nScrollPos); + ::OffsetRect (&rcClient, 0, off_cy); + rcClient.bottom += (m_nText_W_H - (rcClient.bottom - rcClient.top)); + } + + m_nScrollPos += m_nStep; + if (m_nScrollPos > nScrollRange) { + m_nScrollPos = 0; + } + } + + RECT rc = rcClient; + + UINT uTextStyle = DT_WORDBREAK | DT_EDITCONTROL; + + if (m_nText_W_H == 0) { + uTextStyle |= DT_CALCRECT; //һμıȻ߶ + if (m_nRollDirection == ROLLTEXT_LEFT || m_nRollDirection == ROLLTEXT_RIGHT) { //ƶ + rc.right += 10000; + } else { //ƶ + rc.bottom += 10000; + } + } + + if (m_bShowHtml) { + int nLinks = 0; + CRenderEngine::DrawHtmlText (hDC, m_pManager, rc, sText, dwTextColor, nullptr, nullptr, nLinks, m_iFont, uTextStyle); + } else { + CRenderEngine::DrawText (hDC, m_pManager, rc, sText, dwTextColor, m_iFont, uTextStyle); + } + + if (m_nText_W_H == 0) { + m_nText_W_H = (m_nRollDirection == ROLLTEXT_LEFT || m_nRollDirection == ROLLTEXT_RIGHT) ? (rc.right - rc.left) : (rc.bottom - rc.top); //ıȻ߶ + } + } +} \ No newline at end of file diff --git a/DuiLib/Control/UIRollText.h b/DuiLib/Control/UIRollText.h new file mode 100644 index 0000000..9f80649 --- /dev/null +++ b/DuiLib/Control/UIRollText.h @@ -0,0 +1,48 @@ +#ifndef __UITEXTSCROLLH__ +#define __UITEXTSCROLLH__ + +#pragma once + +namespace DuiLib { +#define ROLLTEXT_LEFT 0 +#define ROLLTEXT_RIGHT 1 +#define ROLLTEXT_UP 2 +#define ROLLTEXT_DOWN 3 + +#define ROLLTEXT_TIMERID 20 +#define ROLLTEXT_TIMERID_SPAN 50U + +#define ROLLTEXT_ROLL_END 21 +#define ROLLTEXT_ROLL_END_SPAN 1000*6U + + class UILIB_API CRollTextUI: public CLabelUI { + DECLARE_DUICONTROL (CRollTextUI) + public: + CRollTextUI (void); + virtual ~CRollTextUI (void); + + public: + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + public: + virtual void PaintText (HDC hDC); + virtual void DoEvent (TEventUI& event); + virtual void SetPos (RECT rc); + virtual void SetText (string_view_t pstrText); + + public: + void BeginRoll (int nDirect = ROLLTEXT_RIGHT, LONG lTimeSpan = ROLLTEXT_TIMERID_SPAN, LONG lMaxTimeLimited = 60); + void EndRoll (); + + private: + int m_nStep = 5; + int m_nScrollPos = 0; + BOOL m_bUseRoll = FALSE; + int m_nRollDirection = ROLLTEXT_LEFT; + int m_nText_W_H = 0; + }; + +} // namespace DuiLib + +#endif // __UITEXTSCROLLH__ \ No newline at end of file diff --git a/DuiLib/Control/UIScrollBar.cpp b/DuiLib/Control/UIScrollBar.cpp new file mode 100644 index 0000000..ca0f2f4 --- /dev/null +++ b/DuiLib/Control/UIScrollBar.cpp @@ -0,0 +1,863 @@ +#include "StdAfx.h" +#include "UIScrollBar.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CScrollBarUI) + + CScrollBarUI::CScrollBarUI () { + m_cxyFixed.cx = DEFAULT_SCROLLBAR_SIZE; + ptLastMouse.x = ptLastMouse.y = 0; + } + + string_view_t CScrollBarUI::GetClass () const { + return _T ("ScrollBarUI"); + } + + LPVOID CScrollBarUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_SCROLLBAR) return static_cast(this); + return CControlUI::GetInterface (pstrName); + } + + CContainerUI* CScrollBarUI::GetOwner () const { + return m_pOwner; + } + + void CScrollBarUI::SetOwner (CContainerUI* pOwner) { + m_pOwner = pOwner; + } + + void CScrollBarUI::SetVisible (bool bVisible) { + if (m_bVisible == bVisible) return; + + bool v = IsVisible (); + m_bVisible = bVisible; + if (m_bFocused) m_bFocused = false; + + } + + void CScrollBarUI::SetEnabled (bool bEnable) { + CControlUI::SetEnabled (bEnable); + if (!IsEnabled ()) { + m_uButton1State = 0; + m_uButton2State = 0; + m_uThumbState = 0; + } + } + + void CScrollBarUI::SetFocus () { + if (m_pOwner) m_pOwner->SetFocus (); + else CControlUI::SetFocus (); + } + + bool CScrollBarUI::IsHorizontal () { + return m_bHorizontal; + } + + void CScrollBarUI::SetHorizontal (bool bHorizontal) { + if (m_bHorizontal == bHorizontal) return; + + m_bHorizontal = bHorizontal; + if (m_bHorizontal) { + if (m_cxyFixed.cy == 0) { + m_cxyFixed.cx = 0; + m_cxyFixed.cy = DEFAULT_SCROLLBAR_SIZE; + } + } else { + if (m_cxyFixed.cx == 0) { + m_cxyFixed.cx = DEFAULT_SCROLLBAR_SIZE; + m_cxyFixed.cy = 0; + } + } + + if (m_pOwner) m_pOwner->NeedUpdate (); else NeedParentUpdate (); + } + + int CScrollBarUI::GetScrollRange () const { + return m_nRange; + } + + void CScrollBarUI::SetScrollRange (int nRange) { + if (m_nRange == nRange) return; + + m_nRange = nRange; + if (m_nRange < 0) m_nRange = 0; + if (m_nScrollPos > m_nRange) m_nScrollPos = m_nRange; + SetPos (m_rcItem); + } + + int CScrollBarUI::GetScrollPos () const { + return m_nScrollPos; + } + + void CScrollBarUI::SetScrollPos (int nPos) { + if (m_nScrollPos == nPos) return; + + m_nScrollPos = nPos; + if (m_nScrollPos < 0) m_nScrollPos = 0; + if (m_nScrollPos > m_nRange) m_nScrollPos = m_nRange; + SetPos (m_rcItem); + } + + int CScrollBarUI::GetLineSize () const { + return m_nLineSize; + } + + void CScrollBarUI::SetLineSize (int nSize) { + m_nLineSize = nSize; + } + + bool CScrollBarUI::GetShowButton1 () { + return m_bShowButton1; + } + + void CScrollBarUI::SetShowButton1 (bool bShow) { + m_bShowButton1 = bShow; + SetPos (m_rcItem); + } + + string_view_t CScrollBarUI::GetButton1NormalImage () { + return m_sButton1NormalImage; + } + + void CScrollBarUI::SetButton1NormalImage (string_view_t pStrImage) { + m_sButton1NormalImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetButton1HotImage () { + return m_sButton1HotImage; + } + + void CScrollBarUI::SetButton1HotImage (string_view_t pStrImage) { + m_sButton1HotImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetButton1PushedImage () { + return m_sButton1PushedImage; + } + + void CScrollBarUI::SetButton1PushedImage (string_view_t pStrImage) { + m_sButton1PushedImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetButton1DisabledImage () { + return m_sButton1DisabledImage; + } + + void CScrollBarUI::SetButton1DisabledImage (string_view_t pStrImage) { + m_sButton1DisabledImage = pStrImage; + Invalidate (); + } + + bool CScrollBarUI::GetShowButton2 () { + return m_bShowButton2; + } + + void CScrollBarUI::SetShowButton2 (bool bShow) { + m_bShowButton2 = bShow; + SetPos (m_rcItem); + } + + string_view_t CScrollBarUI::GetButton2NormalImage () { + return m_sButton2NormalImage; + } + + void CScrollBarUI::SetButton2NormalImage (string_view_t pStrImage) { + m_sButton2NormalImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetButton2HotImage () { + return m_sButton2HotImage; + } + + void CScrollBarUI::SetButton2HotImage (string_view_t pStrImage) { + m_sButton2HotImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetButton2PushedImage () { + return m_sButton2PushedImage; + } + + void CScrollBarUI::SetButton2PushedImage (string_view_t pStrImage) { + m_sButton2PushedImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetButton2DisabledImage () { + return m_sButton2DisabledImage; + } + + void CScrollBarUI::SetButton2DisabledImage (string_view_t pStrImage) { + m_sButton2DisabledImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetThumbNormalImage () { + return m_sThumbNormalImage; + } + + void CScrollBarUI::SetThumbNormalImage (string_view_t pStrImage) { + m_sThumbNormalImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetThumbHotImage () { + return m_sThumbHotImage; + } + + void CScrollBarUI::SetThumbHotImage (string_view_t pStrImage) { + m_sThumbHotImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetThumbPushedImage () { + return m_sThumbPushedImage; + } + + void CScrollBarUI::SetThumbPushedImage (string_view_t pStrImage) { + m_sThumbPushedImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetThumbDisabledImage () { + return m_sThumbDisabledImage; + } + + void CScrollBarUI::SetThumbDisabledImage (string_view_t pStrImage) { + m_sThumbDisabledImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetRailNormalImage () { + return m_sRailNormalImage; + } + + void CScrollBarUI::SetRailNormalImage (string_view_t pStrImage) { + m_sRailNormalImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetRailHotImage () { + return m_sRailHotImage; + } + + void CScrollBarUI::SetRailHotImage (string_view_t pStrImage) { + m_sRailHotImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetRailPushedImage () { + return m_sRailPushedImage; + } + + void CScrollBarUI::SetRailPushedImage (string_view_t pStrImage) { + m_sRailPushedImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetRailDisabledImage () { + return m_sRailDisabledImage; + } + + void CScrollBarUI::SetRailDisabledImage (string_view_t pStrImage) { + m_sRailDisabledImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetBkNormalImage () { + return m_sBkNormalImage; + } + + void CScrollBarUI::SetBkNormalImage (string_view_t pStrImage) { + m_sBkNormalImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetBkHotImage () { + return m_sBkHotImage; + } + + void CScrollBarUI::SetBkHotImage (string_view_t pStrImage) { + m_sBkHotImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetBkPushedImage () { + return m_sBkPushedImage; + } + + void CScrollBarUI::SetBkPushedImage (string_view_t pStrImage) { + m_sBkPushedImage = pStrImage; + Invalidate (); + } + + string_view_t CScrollBarUI::GetBkDisabledImage () { + return m_sBkDisabledImage; + } + + void CScrollBarUI::SetBkDisabledImage (string_view_t pStrImage) { + m_sBkDisabledImage = pStrImage; + Invalidate (); + } + + void CScrollBarUI::SetPos (RECT rc, bool bNeedInvalidate) { + CControlUI::SetPos (rc, bNeedInvalidate); + SIZE cxyFixed = m_cxyFixed; + if (m_pManager) { + GetManager ()->GetDPIObj ()->Scale (&cxyFixed); + } + rc = m_rcItem; + if (m_bHorizontal) { + int cx = rc.right - rc.left; + if (m_bShowButton1) cx -= cxyFixed.cy; + if (m_bShowButton2) cx -= cxyFixed.cy; + if (cx > cxyFixed.cy) { + m_rcButton1.left = rc.left; + m_rcButton1.top = rc.top; + if (m_bShowButton1) { + m_rcButton1.right = rc.left + cxyFixed.cy; + m_rcButton1.bottom = rc.top + cxyFixed.cy; + } else { + m_rcButton1.right = m_rcButton1.left; + m_rcButton1.bottom = m_rcButton1.top; + } + + m_rcButton2.top = rc.top; + m_rcButton2.right = rc.right; + if (m_bShowButton2) { + m_rcButton2.left = rc.right - cxyFixed.cy; + m_rcButton2.bottom = rc.top + cxyFixed.cy; + } else { + m_rcButton2.left = m_rcButton2.right; + m_rcButton2.bottom = m_rcButton2.top; + } + + m_rcThumb.top = rc.top; + m_rcThumb.bottom = rc.top + cxyFixed.cy; + if (m_nRange > 0) { + int cxThumb = cx * (rc.right - rc.left) / (m_nRange + rc.right - rc.left); + if (cxThumb < cxyFixed.cy) cxThumb = cxyFixed.cy; + + m_rcThumb.left = m_nScrollPos * (cx - cxThumb) / m_nRange + m_rcButton1.right; + m_rcThumb.right = m_rcThumb.left + cxThumb; + if (m_rcThumb.right > m_rcButton2.left) { + m_rcThumb.left = m_rcButton2.left - cxThumb; + m_rcThumb.right = m_rcButton2.left; + } + } else { + m_rcThumb.left = m_rcButton1.right; + m_rcThumb.right = m_rcButton2.left; + } + } else { + int cxButton = (rc.right - rc.left) / 2; + if (cxButton > cxyFixed.cy) cxButton = cxyFixed.cy; + m_rcButton1.left = rc.left; + m_rcButton1.top = rc.top; + if (m_bShowButton1) { + m_rcButton1.right = rc.left + cxButton; + m_rcButton1.bottom = rc.top + cxyFixed.cy; + } else { + m_rcButton1.right = m_rcButton1.left; + m_rcButton1.bottom = m_rcButton1.top; + } + + m_rcButton2.top = rc.top; + m_rcButton2.right = rc.right; + if (m_bShowButton2) { + m_rcButton2.left = rc.right - cxButton; + m_rcButton2.bottom = rc.top + cxyFixed.cy; + } else { + m_rcButton2.left = m_rcButton2.right; + m_rcButton2.bottom = m_rcButton2.top; + } + + ::ZeroMemory (&m_rcThumb, sizeof (m_rcThumb)); + } + } else { + int cy = rc.bottom - rc.top; + if (m_bShowButton1) cy -= cxyFixed.cx; + if (m_bShowButton2) cy -= cxyFixed.cx; + if (cy > cxyFixed.cx) { + m_rcButton1.left = rc.left; + m_rcButton1.top = rc.top; + if (m_bShowButton1) { + m_rcButton1.right = rc.left + cxyFixed.cx; + m_rcButton1.bottom = rc.top + cxyFixed.cx; + } else { + m_rcButton1.right = m_rcButton1.left; + m_rcButton1.bottom = m_rcButton1.top; + } + + m_rcButton2.left = rc.left; + m_rcButton2.bottom = rc.bottom; + if (m_bShowButton2) { + m_rcButton2.top = rc.bottom - cxyFixed.cx; + m_rcButton2.right = rc.left + cxyFixed.cx; + } else { + m_rcButton2.top = m_rcButton2.bottom; + m_rcButton2.right = m_rcButton2.left; + } + + m_rcThumb.left = rc.left; + m_rcThumb.right = rc.left + cxyFixed.cx; + if (m_nRange > 0) { + int cyThumb = cy * (rc.bottom - rc.top) / (m_nRange + rc.bottom - rc.top); + if (cyThumb < cxyFixed.cx) cyThumb = cxyFixed.cx; + + m_rcThumb.top = m_nScrollPos * (cy - cyThumb) / m_nRange + m_rcButton1.bottom; + m_rcThumb.bottom = m_rcThumb.top + cyThumb; + if (m_rcThumb.bottom > m_rcButton2.top) { + m_rcThumb.top = m_rcButton2.top - cyThumb; + m_rcThumb.bottom = m_rcButton2.top; + } + } else { + m_rcThumb.top = m_rcButton1.bottom; + m_rcThumb.bottom = m_rcButton2.top; + } + } else { + int cyButton = (rc.bottom - rc.top) / 2; + if (cyButton > cxyFixed.cx) cyButton = cxyFixed.cx; + m_rcButton1.left = rc.left; + m_rcButton1.top = rc.top; + if (m_bShowButton1) { + m_rcButton1.right = rc.left + cxyFixed.cx; + m_rcButton1.bottom = rc.top + cyButton; + } else { + m_rcButton1.right = m_rcButton1.left; + m_rcButton1.bottom = m_rcButton1.top; + } + + m_rcButton2.left = rc.left; + m_rcButton2.bottom = rc.bottom; + if (m_bShowButton2) { + m_rcButton2.top = rc.bottom - cyButton; + m_rcButton2.right = rc.left + cxyFixed.cx; + } else { + m_rcButton2.top = m_rcButton2.bottom; + m_rcButton2.right = m_rcButton2.left; + } + + ::ZeroMemory (&m_rcThumb, sizeof (m_rcThumb)); + } + } + } + + void CScrollBarUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pOwner) m_pOwner->DoEvent (event); + else CControlUI::DoEvent (event); + return; + } + + if (event.Type == UIEVENT_SETFOCUS) { + return; + } + if (event.Type == UIEVENT_KILLFOCUS) { + return; + } + if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK) { + if (!IsEnabled ()) return; + + m_nLastScrollOffset = 0; + m_nScrollRepeatDelay = 0; + m_pManager->SetTimer (this, DEFAULT_TIMERID, 50U); + + if (::PtInRect (&m_rcButton1, event.ptMouse)) { + m_uButton1State |= UISTATE_PUSHED; + if (!m_bHorizontal) { + if (m_pOwner) m_pOwner->LineUp (); + else SetScrollPos (m_nScrollPos - m_nLineSize); + } else { + if (m_pOwner) m_pOwner->LineLeft (); + else SetScrollPos (m_nScrollPos - m_nLineSize); + } + } else if (::PtInRect (&m_rcButton2, event.ptMouse)) { + m_uButton2State |= UISTATE_PUSHED; + if (!m_bHorizontal) { + if (m_pOwner) m_pOwner->LineDown (); + else SetScrollPos (m_nScrollPos + m_nLineSize); + } else { + if (m_pOwner) m_pOwner->LineRight (); + else SetScrollPos (m_nScrollPos + m_nLineSize); + } + } else if (::PtInRect (&m_rcThumb, event.ptMouse)) { + m_uThumbState |= UISTATE_CAPTURED | UISTATE_PUSHED; + ptLastMouse = event.ptMouse; + m_nLastScrollPos = m_nScrollPos; + } else { + if (!m_bHorizontal) { + if (event.ptMouse.y < m_rcThumb.top) { + if (m_pOwner) m_pOwner->PageUp (); + else SetScrollPos (m_nScrollPos + m_rcItem.top - m_rcItem.bottom); + } else if (event.ptMouse.y > m_rcThumb.bottom) { + if (m_pOwner) m_pOwner->PageDown (); + else SetScrollPos (m_nScrollPos - m_rcItem.top + m_rcItem.bottom); + } + } else { + if (event.ptMouse.x < m_rcThumb.left) { + if (m_pOwner) m_pOwner->PageLeft (); + else SetScrollPos (m_nScrollPos + m_rcItem.left - m_rcItem.right); + } else if (event.ptMouse.x > m_rcThumb.right) { + if (m_pOwner) m_pOwner->PageRight (); + else SetScrollPos (m_nScrollPos - m_rcItem.left + m_rcItem.right); + } + } + } + if (m_pManager) m_pManager->SendNotify (this, DUI_MSGTYPE_SCROLL); + return; + } + if (event.Type == UIEVENT_BUTTONUP) { + m_nScrollRepeatDelay = 0; + m_nLastScrollOffset = 0; + m_pManager->KillTimer (this, DEFAULT_TIMERID); + + if ((m_uThumbState & UISTATE_CAPTURED) != 0) { + m_uThumbState &= ~(UISTATE_CAPTURED | UISTATE_PUSHED); + Invalidate (); + } else if ((m_uButton1State & UISTATE_PUSHED) != 0) { + m_uButton1State &= ~UISTATE_PUSHED; + Invalidate (); + } else if ((m_uButton2State & UISTATE_PUSHED) != 0) { + m_uButton2State &= ~UISTATE_PUSHED; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_MOUSEMOVE) { + if ((m_uThumbState & UISTATE_CAPTURED) != 0) { + if (!m_bHorizontal) { + int vRange = m_rcItem.bottom - m_rcItem.top - m_rcThumb.bottom + m_rcThumb.top - 2 * m_cxyFixed.cx; + if (vRange != 0) m_nLastScrollOffset = (event.ptMouse.y - ptLastMouse.y) * m_nRange / abs (vRange); + } else { + int hRange = m_rcItem.right - m_rcItem.left - m_rcThumb.right + m_rcThumb.left - 2 * m_cxyFixed.cy; + if (hRange != 0) m_nLastScrollOffset = (event.ptMouse.x - ptLastMouse.x) * m_nRange / abs (hRange); + } + } else { + if ((m_uThumbState & UISTATE_HOT) != 0) { + if (!::PtInRect (&m_rcThumb, event.ptMouse)) { + m_uThumbState &= ~UISTATE_HOT; + Invalidate (); + } + } else { + if (!IsEnabled ()) return; + if (::PtInRect (&m_rcThumb, event.ptMouse)) { + m_uThumbState |= UISTATE_HOT; + Invalidate (); + } + } + } + return; + } + if (event.Type == UIEVENT_CONTEXTMENU) { + return; + } + if (event.Type == UIEVENT_TIMER && event.wParam == DEFAULT_TIMERID) { + ++m_nScrollRepeatDelay; + if ((m_uThumbState & UISTATE_CAPTURED) != 0) { + if (!m_bHorizontal) { + if (m_pOwner) m_pOwner->SetScrollPos ({ m_pOwner->GetScrollPos ().cx, m_nLastScrollPos + m_nLastScrollOffset }); + else SetScrollPos (m_nLastScrollPos + m_nLastScrollOffset); + } else { + if (m_pOwner) m_pOwner->SetScrollPos ({ m_nLastScrollPos + m_nLastScrollOffset, m_pOwner->GetScrollPos ().cy }); + else SetScrollPos (m_nLastScrollPos + m_nLastScrollOffset); + } + Invalidate (); + } else if ((m_uButton1State & UISTATE_PUSHED) != 0) { + if (m_nScrollRepeatDelay <= 5) return; + if (!m_bHorizontal) { + if (m_pOwner) m_pOwner->LineUp (); + else SetScrollPos (m_nScrollPos - m_nLineSize); + } else { + if (m_pOwner) m_pOwner->LineLeft (); + else SetScrollPos (m_nScrollPos - m_nLineSize); + } + } else if ((m_uButton2State & UISTATE_PUSHED) != 0) { + if (m_nScrollRepeatDelay <= 5) return; + if (!m_bHorizontal) { + if (m_pOwner) m_pOwner->LineDown (); + else SetScrollPos (m_nScrollPos + m_nLineSize); + } else { + if (m_pOwner) m_pOwner->LineRight (); + else SetScrollPos (m_nScrollPos + m_nLineSize); + } + } else { + if (m_nScrollRepeatDelay <= 5) return; + POINT pt = { 0 }; + ::GetCursorPos (&pt); + ::ScreenToClient (m_pManager->GetPaintWindow (), &pt); + if (!m_bHorizontal) { + if (pt.y < m_rcThumb.top) { + if (m_pOwner) m_pOwner->PageUp (); + else SetScrollPos (m_nScrollPos + m_rcItem.top - m_rcItem.bottom); + } else if (pt.y > m_rcThumb.bottom) { + if (m_pOwner) m_pOwner->PageDown (); + else SetScrollPos (m_nScrollPos - m_rcItem.top + m_rcItem.bottom); + } + } else { + if (pt.x < m_rcThumb.left) { + if (m_pOwner) m_pOwner->PageLeft (); + else SetScrollPos (m_nScrollPos + m_rcItem.left - m_rcItem.right); + } else if (pt.x > m_rcThumb.right) { + if (m_pOwner) m_pOwner->PageRight (); + else SetScrollPos (m_nScrollPos - m_rcItem.left + m_rcItem.right); + } + } + } + if (m_pManager) m_pManager->SendNotify (this, DUI_MSGTYPE_SCROLL); + return; + } + if (event.Type == UIEVENT_MOUSEENTER) { + if (IsEnabled ()) { + m_uButton1State |= UISTATE_HOT; + m_uButton2State |= UISTATE_HOT; + if (::PtInRect (&m_rcThumb, event.ptMouse)) m_uThumbState |= UISTATE_HOT; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_MOUSELEAVE) { + if (IsEnabled ()) { + m_uButton1State &= ~UISTATE_HOT; + m_uButton2State &= ~UISTATE_HOT; + m_uThumbState &= ~UISTATE_HOT; + Invalidate (); + } + return; + } + + if (m_pOwner) m_pOwner->DoEvent (event); else CControlUI::DoEvent (event); + } + + void CScrollBarUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("button1normalimage")) SetButton1NormalImage (pstrValue); + else if (pstrName == _T ("button1hotimage")) SetButton1HotImage (pstrValue); + else if (pstrName == _T ("button1pushedimage")) SetButton1PushedImage (pstrValue); + else if (pstrName == _T ("button1disabledimage")) SetButton1DisabledImage (pstrValue); + else if (pstrName == _T ("button2normalimage")) SetButton2NormalImage (pstrValue); + else if (pstrName == _T ("button2hotimage")) SetButton2HotImage (pstrValue); + else if (pstrName == _T ("button2pushedimage")) SetButton2PushedImage (pstrValue); + else if (pstrName == _T ("button2disabledimage")) SetButton2DisabledImage (pstrValue); + else if (pstrName == _T ("thumbnormalimage")) SetThumbNormalImage (pstrValue); + else if (pstrName == _T ("thumbhotimage")) SetThumbHotImage (pstrValue); + else if (pstrName == _T ("thumbpushedimage")) SetThumbPushedImage (pstrValue); + else if (pstrName == _T ("thumbdisabledimage")) SetThumbDisabledImage (pstrValue); + else if (pstrName == _T ("railnormalimage")) SetRailNormalImage (pstrValue); + else if (pstrName == _T ("railhotimage")) SetRailHotImage (pstrValue); + else if (pstrName == _T ("railpushedimage")) SetRailPushedImage (pstrValue); + else if (pstrName == _T ("raildisabledimage")) SetRailDisabledImage (pstrValue); + else if (pstrName == _T ("bknormalimage")) SetBkNormalImage (pstrValue); + else if (pstrName == _T ("bkhotimage")) SetBkHotImage (pstrValue); + else if (pstrName == _T ("bkpushedimage")) SetBkPushedImage (pstrValue); + else if (pstrName == _T ("bkdisabledimage")) SetBkDisabledImage (pstrValue); + else if (pstrName == _T ("hor")) SetHorizontal (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("linesize")) SetLineSize (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("range")) SetScrollRange (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("value")) SetScrollPos (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("showbutton1")) SetShowButton1 (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("showbutton2")) SetShowButton2 (FawTools::parse_bool (pstrValue)); + else CControlUI::SetAttribute (pstrName, pstrValue); + } + + bool CScrollBarUI::DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + PaintBkColor (hDC); + PaintBkImage (hDC); + PaintBk (hDC); + PaintButton1 (hDC); + PaintButton2 (hDC); + PaintThumb (hDC); + PaintRail (hDC); + PaintBorder (hDC); + return true; + } + + void CScrollBarUI::PaintBk (HDC hDC) { + if (!IsEnabled ()) m_uThumbState |= UISTATE_DISABLED; + else m_uThumbState &= ~UISTATE_DISABLED; + + if ((m_uThumbState & UISTATE_DISABLED) != 0) { + if (!m_sBkDisabledImage.empty ()) { + if (!DrawImage (hDC, m_sBkDisabledImage)) { + } else return; + } + } else if ((m_uThumbState & UISTATE_PUSHED) != 0) { + if (!m_sBkPushedImage.empty ()) { + if (!DrawImage (hDC, m_sBkPushedImage)) { + } else return; + } + } else if ((m_uThumbState & UISTATE_HOT) != 0) { + if (!m_sBkHotImage.empty ()) { + if (!DrawImage (hDC, m_sBkHotImage)) { + } else return; + } + } + + if (!m_sBkNormalImage.empty ()) { + if (!DrawImage (hDC, m_sBkNormalImage)) { + } else return; + } + } + + void CScrollBarUI::PaintButton1 (HDC hDC) { + if (!m_bShowButton1) return; + + if (!IsEnabled ()) m_uButton1State |= UISTATE_DISABLED; + else m_uButton1State &= ~UISTATE_DISABLED; + + int d1 = MulDiv (m_rcButton1.left - m_rcItem.left, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d2 = MulDiv (m_rcButton1.top - m_rcItem.top, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d3 = MulDiv (m_rcButton1.right - m_rcItem.left, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d4 = MulDiv (m_rcButton1.bottom - m_rcItem.top, 100, GetManager ()->GetDPIObj ()->GetScale ()); + m_sImageModify.clear (); + m_sImageModify.Format (_T ("dest='%d,%d,%d,%d'"), d1, d2, d3, d4); + + if ((m_uButton1State & UISTATE_DISABLED) != 0) { + if (!m_sButton1DisabledImage.empty ()) { + if (!DrawImage (hDC, m_sButton1DisabledImage, m_sImageModify)) { + } else return; + } + } else if ((m_uButton1State & UISTATE_PUSHED) != 0) { + if (!m_sButton1PushedImage.empty ()) { + if (!DrawImage (hDC, m_sButton1PushedImage, m_sImageModify)) { + } else return; + } + } else if ((m_uButton1State & UISTATE_HOT) != 0) { + if (!m_sButton1HotImage.empty ()) { + if (!DrawImage (hDC, m_sButton1HotImage, m_sImageModify)) { + } else return; + } + } + + if (!m_sButton1NormalImage.empty ()) { + if (!DrawImage (hDC, m_sButton1NormalImage, m_sImageModify)) { + } else return; + } + + DWORD dwBorderColor = 0xFF85E4FF; + int nBorderSize = 2; + CRenderEngine::DrawRect (hDC, m_rcButton1, nBorderSize, dwBorderColor); + } + + void CScrollBarUI::PaintButton2 (HDC hDC) { + if (!m_bShowButton2) return; + + if (!IsEnabled ()) m_uButton2State |= UISTATE_DISABLED; + else m_uButton2State &= ~UISTATE_DISABLED; + int d1 = MulDiv (m_rcButton2.left - m_rcItem.left, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d2 = MulDiv (m_rcButton2.top - m_rcItem.top, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d3 = MulDiv (m_rcButton2.right - m_rcItem.left, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d4 = MulDiv (m_rcButton2.bottom - m_rcItem.top, 100, GetManager ()->GetDPIObj ()->GetScale ()); + m_sImageModify.clear (); + m_sImageModify.Format (_T ("dest='%d,%d,%d,%d'"), d1, d2, d3, d4); + + if ((m_uButton2State & UISTATE_DISABLED) != 0) { + if (!m_sButton2DisabledImage.empty ()) { + if (!DrawImage (hDC, m_sButton2DisabledImage, m_sImageModify)) { + } else return; + } + } else if ((m_uButton2State & UISTATE_PUSHED) != 0) { + if (!m_sButton2PushedImage.empty ()) { + if (!DrawImage (hDC, m_sButton2PushedImage, m_sImageModify)) { + } else return; + } + } else if ((m_uButton2State & UISTATE_HOT) != 0) { + if (!m_sButton2HotImage.empty ()) { + if (!DrawImage (hDC, m_sButton2HotImage, m_sImageModify)) { + } else return; + } + } + + if (!m_sButton2NormalImage.empty ()) { + if (!DrawImage (hDC, m_sButton2NormalImage, m_sImageModify)) { + } else return; + } + + DWORD dwBorderColor = 0xFF85E4FF; + int nBorderSize = 2; + CRenderEngine::DrawRect (hDC, m_rcButton2, nBorderSize, dwBorderColor); + } + + void CScrollBarUI::PaintThumb (HDC hDC) { + if (m_rcThumb.left == 0 && m_rcThumb.top == 0 && m_rcThumb.right == 0 && m_rcThumb.bottom == 0) return; + if (!IsEnabled ()) m_uThumbState |= UISTATE_DISABLED; + else m_uThumbState &= ~UISTATE_DISABLED; + int d1 = MulDiv (m_rcThumb.left - m_rcItem.left, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d2 = MulDiv (m_rcThumb.top - m_rcItem.top, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d3 = MulDiv (m_rcThumb.right - m_rcItem.left, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d4 = MulDiv (m_rcThumb.bottom - m_rcItem.top, 100, GetManager ()->GetDPIObj ()->GetScale ()); + m_sImageModify.clear (); + m_sImageModify.Format (_T ("dest='%d,%d,%d,%d'"), d1, d2, d3, d4); + + if ((m_uThumbState & UISTATE_DISABLED) != 0) { + if (!m_sThumbDisabledImage.empty ()) { + if (!DrawImage (hDC, m_sThumbDisabledImage, m_sImageModify)) { + } else return; + } + } else if ((m_uThumbState & UISTATE_PUSHED) != 0) { + if (!m_sThumbPushedImage.empty ()) { + if (!DrawImage (hDC, m_sThumbPushedImage, m_sImageModify)) { + } else return; + } + } else if ((m_uThumbState & UISTATE_HOT) != 0) { + if (!m_sThumbHotImage.empty ()) { + if (!DrawImage (hDC, m_sThumbHotImage, m_sImageModify)) { + } else return; + } + } + + if (!m_sThumbNormalImage.empty ()) { + if (!DrawImage (hDC, m_sThumbNormalImage, m_sImageModify)) { + } else return; + } + + DWORD dwBorderColor = 0xFF85E4FF; + int nBorderSize = 2; + CRenderEngine::DrawRect (hDC, m_rcThumb, nBorderSize, dwBorderColor); + } + + void CScrollBarUI::PaintRail (HDC hDC) { + if (m_rcThumb.left == 0 && m_rcThumb.top == 0 && m_rcThumb.right == 0 && m_rcThumb.bottom == 0) return; + if (!IsEnabled ()) m_uThumbState |= UISTATE_DISABLED; + else m_uThumbState &= ~UISTATE_DISABLED; + + m_sImageModify.clear (); + if (!m_bHorizontal) { + int d1 = MulDiv (m_rcThumb.left - m_rcItem.left, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d2 = MulDiv ((m_rcThumb.top + m_rcThumb.bottom) / 2 - m_rcItem.top - m_cxyFixed.cx / 2, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d3 = MulDiv (m_rcThumb.right - m_rcItem.left, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d4 = MulDiv ((m_rcThumb.top + m_rcThumb.bottom) / 2 - m_rcItem.top + m_cxyFixed.cx - m_cxyFixed.cx / 2, 100, GetManager ()->GetDPIObj ()->GetScale ()); + m_sImageModify.Format (_T ("dest='%d,%d,%d,%d'"), d1, d2, d3, d4); + } else { + int d1 = MulDiv ((m_rcThumb.left + m_rcThumb.right) / 2 - m_rcItem.left - m_cxyFixed.cy / 2, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d2 = MulDiv (m_rcThumb.top - m_rcItem.top, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d3 = MulDiv ((m_rcThumb.left + m_rcThumb.right) / 2 - m_rcItem.left + m_cxyFixed.cy - m_cxyFixed.cy / 2, 100, GetManager ()->GetDPIObj ()->GetScale ()); + int d4 = MulDiv (m_rcThumb.bottom - m_rcItem.top, 100, GetManager ()->GetDPIObj ()->GetScale ()); + m_sImageModify.Format (_T ("dest='%d,%d,%d,%d'"), d1, d2, d3, d4); + } + + if ((m_uThumbState & UISTATE_DISABLED) != 0) { + if (!m_sRailDisabledImage.empty ()) { + if (!DrawImage (hDC, m_sRailDisabledImage, m_sImageModify)) { + } else return; + } + } else if ((m_uThumbState & UISTATE_PUSHED) != 0) { + if (!m_sRailPushedImage.empty ()) { + if (!DrawImage (hDC, m_sRailPushedImage, m_sImageModify)) { + } else return; + } + } else if ((m_uThumbState & UISTATE_HOT) != 0) { + if (!m_sRailHotImage.empty ()) { + if (!DrawImage (hDC, m_sRailHotImage, m_sImageModify)) { + } else return; + } + } + + if (!m_sRailNormalImage.empty ()) { + if (!DrawImage (hDC, m_sRailNormalImage, m_sImageModify)) { + } else return; + } + } +} diff --git a/DuiLib/Control/UIScrollBar.h b/DuiLib/Control/UIScrollBar.h new file mode 100644 index 0000000..0530af4 --- /dev/null +++ b/DuiLib/Control/UIScrollBar.h @@ -0,0 +1,146 @@ +#ifndef __UISCROLLBAR_H__ +#define __UISCROLLBAR_H__ + +#pragma once + +namespace DuiLib { + class UILIB_API CScrollBarUI: public CControlUI { + DECLARE_DUICONTROL (CScrollBarUI) + public: + CScrollBarUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + CContainerUI* GetOwner () const; + void SetOwner (CContainerUI* pOwner); + + void SetVisible (bool bVisible = true); + void SetEnabled (bool bEnable = true); + void SetFocus (); + + bool IsHorizontal (); + void SetHorizontal (bool bHorizontal = true); + int GetScrollRange () const; + void SetScrollRange (int nRange); + int GetScrollPos () const; + void SetScrollPos (int nPos); + int GetLineSize () const; + void SetLineSize (int nSize); + + bool GetShowButton1 (); + void SetShowButton1 (bool bShow); + string_view_t GetButton1NormalImage (); + void SetButton1NormalImage (string_view_t pStrImage); + string_view_t GetButton1HotImage (); + void SetButton1HotImage (string_view_t pStrImage); + string_view_t GetButton1PushedImage (); + void SetButton1PushedImage (string_view_t pStrImage); + string_view_t GetButton1DisabledImage (); + void SetButton1DisabledImage (string_view_t pStrImage); + + bool GetShowButton2 (); + void SetShowButton2 (bool bShow); + string_view_t GetButton2NormalImage (); + void SetButton2NormalImage (string_view_t pStrImage); + string_view_t GetButton2HotImage (); + void SetButton2HotImage (string_view_t pStrImage); + string_view_t GetButton2PushedImage (); + void SetButton2PushedImage (string_view_t pStrImage); + string_view_t GetButton2DisabledImage (); + void SetButton2DisabledImage (string_view_t pStrImage); + + string_view_t GetThumbNormalImage (); + void SetThumbNormalImage (string_view_t pStrImage); + string_view_t GetThumbHotImage (); + void SetThumbHotImage (string_view_t pStrImage); + string_view_t GetThumbPushedImage (); + void SetThumbPushedImage (string_view_t pStrImage); + string_view_t GetThumbDisabledImage (); + void SetThumbDisabledImage (string_view_t pStrImage); + + string_view_t GetRailNormalImage (); + void SetRailNormalImage (string_view_t pStrImage); + string_view_t GetRailHotImage (); + void SetRailHotImage (string_view_t pStrImage); + string_view_t GetRailPushedImage (); + void SetRailPushedImage (string_view_t pStrImage); + string_view_t GetRailDisabledImage (); + void SetRailDisabledImage (string_view_t pStrImage); + + string_view_t GetBkNormalImage (); + void SetBkNormalImage (string_view_t pStrImage); + string_view_t GetBkHotImage (); + void SetBkHotImage (string_view_t pStrImage); + string_view_t GetBkPushedImage (); + void SetBkPushedImage (string_view_t pStrImage); + string_view_t GetBkDisabledImage (); + void SetBkDisabledImage (string_view_t pStrImage); + + void SetPos (RECT rc, bool bNeedInvalidate = true); + void DoEvent (TEventUI& event); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + bool DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl); + + void PaintBk (HDC hDC); + void PaintButton1 (HDC hDC); + void PaintButton2 (HDC hDC); + void PaintThumb (HDC hDC); + void PaintRail (HDC hDC); + + protected: + + enum { + DEFAULT_SCROLLBAR_SIZE = 16, + DEFAULT_TIMERID = 10, + }; + + bool m_bHorizontal = false; + int m_nRange = 100; + int m_nScrollPos = 0; + int m_nLineSize = 8; + CContainerUI *m_pOwner = nullptr; + POINT ptLastMouse; + int m_nLastScrollPos = 0; + int m_nLastScrollOffset = 0; + int m_nScrollRepeatDelay = 0; + + CDuiString m_sBkNormalImage; + CDuiString m_sBkHotImage; + CDuiString m_sBkPushedImage; + CDuiString m_sBkDisabledImage; + + bool m_bShowButton1 = true; + RECT m_rcButton1 = { 0 }; + UINT m_uButton1State = 0; + CDuiString m_sButton1NormalImage; + CDuiString m_sButton1HotImage; + CDuiString m_sButton1PushedImage; + CDuiString m_sButton1DisabledImage; + + bool m_bShowButton2 = true; + RECT m_rcButton2 = { 0 }; + UINT m_uButton2State = 0; + CDuiString m_sButton2NormalImage; + CDuiString m_sButton2HotImage; + CDuiString m_sButton2PushedImage; + CDuiString m_sButton2DisabledImage; + + RECT m_rcThumb = { 0 }; + UINT m_uThumbState = 0; + CDuiString m_sThumbNormalImage; + CDuiString m_sThumbHotImage; + CDuiString m_sThumbPushedImage; + CDuiString m_sThumbDisabledImage; + + CDuiString m_sRailNormalImage; + CDuiString m_sRailHotImage; + CDuiString m_sRailPushedImage; + CDuiString m_sRailDisabledImage; + + CDuiString m_sImageModify; + }; +} + +#endif // __UISCROLLBAR_H__ \ No newline at end of file diff --git a/DuiLib/Control/UISlider.cpp b/DuiLib/Control/UISlider.cpp new file mode 100644 index 0000000..a037860 --- /dev/null +++ b/DuiLib/Control/UISlider.cpp @@ -0,0 +1,269 @@ +#include "StdAfx.h" +#include "UISlider.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CSliderUI) + CSliderUI::CSliderUI () { + m_uTextStyle = DT_SINGLELINE | DT_CENTER; + m_szThumb.cx = m_szThumb.cy = 10; + } + + string_view_t CSliderUI::GetClass () const { + return _T ("SliderUI"); + } + + UINT CSliderUI::GetControlFlags () const { + if (IsEnabled ()) return UIFLAG_SETCURSOR; + else return 0; + } + + LPVOID CSliderUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_SLIDER) return static_cast(this); + return CProgressUI::GetInterface (pstrName); + } + + void CSliderUI::SetEnabled (bool bEnable) { + CControlUI::SetEnabled (bEnable); + if (!IsEnabled ()) { + m_uButtonState = 0; + } + } + + int CSliderUI::GetChangeStep () { + return m_nStep; + } + + void CSliderUI::SetChangeStep (int step) { + m_nStep = step; + } + + void CSliderUI::SetThumbSize (SIZE szXY) { + m_szThumb = szXY; + } + + RECT CSliderUI::GetThumbRect () const { + RECT rcThumb = { 0 }; + SIZE _szThumb = CSliderUI::m_szThumb; + if (GetManager ()) { + GetManager ()->GetDPIObj ()->Scale (&_szThumb); + } + if (m_bHorizontal) { + int left = m_rcItem.left + (m_rcItem.right - m_rcItem.left - _szThumb.cx) * (m_nValue - m_nMin) / (m_nMax - m_nMin); + int top = (m_rcItem.bottom + m_rcItem.top - _szThumb.cy) / 2; + rcThumb = { left, top, left + _szThumb.cx, top + _szThumb.cy }; + } else { + int left = (m_rcItem.right + m_rcItem.left - _szThumb.cx) / 2; + int top = m_rcItem.bottom - _szThumb.cy - (m_rcItem.bottom - m_rcItem.top - _szThumb.cy) * (m_nValue - m_nMin) / (m_nMax - m_nMin); + rcThumb = { left, top, left + _szThumb.cx, top + _szThumb.cy }; + } + if (m_pManager) { + //m_pManager->GetDPIObj()->Scale(&rcThumb); + } + return rcThumb; + } + + string_view_t CSliderUI::GetThumbImage () const { + return m_sThumbImage; + } + + void CSliderUI::SetThumbImage (string_view_t pStrImage) { + m_sThumbImage = pStrImage; + Invalidate (); + } + + string_view_t CSliderUI::GetThumbHotImage () const { + return m_sThumbHotImage; + } + + void CSliderUI::SetThumbHotImage (string_view_t pStrImage) { + m_sThumbHotImage = pStrImage; + Invalidate (); + } + + string_view_t CSliderUI::GetThumbPushedImage () const { + return m_sThumbPushedImage; + } + + void CSliderUI::SetThumbPushedImage (string_view_t pStrImage) { + m_sThumbPushedImage = pStrImage; + Invalidate (); + } + + void CSliderUI::SetValue (int nValue) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) return; + CProgressUI::SetValue (nValue); + } + + void CSliderUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CProgressUI::DoEvent (event); + return; + } + + if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK) { + if (IsEnabled ()) { + m_uButtonState |= UISTATE_CAPTURED; + + int nValue; + if (m_bHorizontal) { + if (event.ptMouse.x >= m_rcItem.right - m_szThumb.cx / 2) nValue = m_nMax; + else if (event.ptMouse.x <= m_rcItem.left + m_szThumb.cx / 2) nValue = m_nMin; + else nValue = m_nMin + (m_nMax - m_nMin) * (event.ptMouse.x - m_rcItem.left - m_szThumb.cx / 2) / (m_rcItem.right - m_rcItem.left - m_szThumb.cx); + } else { + if (event.ptMouse.y >= m_rcItem.bottom - m_szThumb.cy / 2) nValue = m_nMin; + else if (event.ptMouse.y <= m_rcItem.top + m_szThumb.cy / 2) nValue = m_nMax; + else nValue = m_nMin + (m_nMax - m_nMin) * (m_rcItem.bottom - event.ptMouse.y - m_szThumb.cy / 2) / (m_rcItem.bottom - m_rcItem.top - m_szThumb.cy); + } + if (m_nValue != nValue && nValue >= m_nMin && nValue <= m_nMax) { + m_nValue = nValue; + Invalidate (); + } + UpdateText (); + } + return; + } + + if (event.Type == UIEVENT_BUTTONUP || event.Type == UIEVENT_RBUTTONUP) { + if (IsEnabled ()) { + int nValue = 0; + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + m_uButtonState &= ~UISTATE_CAPTURED; + } + if (m_bHorizontal) { + if (event.ptMouse.x >= m_rcItem.right - m_szThumb.cx / 2) nValue = m_nMax; + else if (event.ptMouse.x <= m_rcItem.left + m_szThumb.cx / 2) nValue = m_nMin; + else nValue = m_nMin + (m_nMax - m_nMin) * (event.ptMouse.x - m_rcItem.left - m_szThumb.cx / 2) / (m_rcItem.right - m_rcItem.left - m_szThumb.cx); + } else { + if (event.ptMouse.y >= m_rcItem.bottom - m_szThumb.cy / 2) nValue = m_nMin; + else if (event.ptMouse.y <= m_rcItem.top + m_szThumb.cy / 2) nValue = m_nMax; + else nValue = m_nMin + (m_nMax - m_nMin) * (m_rcItem.bottom - event.ptMouse.y - m_szThumb.cy / 2) / (m_rcItem.bottom - m_rcItem.top - m_szThumb.cy); + } + if (nValue >= m_nMin && nValue <= m_nMax) { + m_nValue = nValue; + m_pManager->SendNotify (this, DUI_MSGTYPE_VALUECHANGED); + Invalidate (); + } + UpdateText (); + return; + } + } + if (event.Type == UIEVENT_CONTEXTMENU) { + return; + } + if (event.Type == UIEVENT_SCROLLWHEEL) { + if (IsEnabled ()) { + switch (LOWORD (event.wParam)) { + case SB_LINEUP: + SetValue (GetValue () + GetChangeStep ()); + m_pManager->SendNotify (this, DUI_MSGTYPE_VALUECHANGED); + return; + case SB_LINEDOWN: + SetValue (GetValue () - GetChangeStep ()); + m_pManager->SendNotify (this, DUI_MSGTYPE_VALUECHANGED); + return; + } + } + } + if (event.Type == UIEVENT_MOUSEMOVE) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + if (m_bHorizontal) { + if (event.ptMouse.x >= m_rcItem.right - m_szThumb.cx / 2) m_nValue = m_nMax; + else if (event.ptMouse.x <= m_rcItem.left + m_szThumb.cx / 2) m_nValue = m_nMin; + else m_nValue = m_nMin + (m_nMax - m_nMin) * (event.ptMouse.x - m_rcItem.left - m_szThumb.cx / 2) / (m_rcItem.right - m_rcItem.left - m_szThumb.cx); + } else { + if (event.ptMouse.y >= m_rcItem.bottom - m_szThumb.cy / 2) m_nValue = m_nMin; + else if (event.ptMouse.y <= m_rcItem.top + m_szThumb.cy / 2) m_nValue = m_nMax; + else m_nValue = m_nMin + (m_nMax - m_nMin) * (m_rcItem.bottom - event.ptMouse.y - m_szThumb.cy / 2) / (m_rcItem.bottom - m_rcItem.top - m_szThumb.cy); + } + if (m_bSendMove) { + UpdateText (); + m_pManager->SendNotify (this, DUI_MSGTYPE_VALUECHANGED_MOVE); + } + Invalidate (); + } + + POINT pt = event.ptMouse; + RECT rcThumb = GetThumbRect (); + if (IsEnabled () && ::PtInRect (&rcThumb, event.ptMouse)) { + m_uButtonState |= UISTATE_HOT; + Invalidate (); + } else { + m_uButtonState &= ~UISTATE_HOT; + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_SETCURSOR) { + RECT rcThumb = GetThumbRect (); + if (IsEnabled ()) { + ::SetCursor (::LoadCursor (nullptr, IDC_HAND)); + return; + } + } + if (event.Type == UIEVENT_MOUSELEAVE) { + if (IsEnabled ()) { + m_uButtonState &= ~UISTATE_HOT; + Invalidate (); + } + return; + } + CControlUI::DoEvent (event); + } + + void CSliderUI::SetCanSendMove (bool bCanSend) { + m_bSendMove = bCanSend; + } + bool CSliderUI::GetCanSendMove () const { + return m_bSendMove; + } + + void CSliderUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("thumbimage")) SetThumbImage (pstrValue); + else if (pstrName == _T ("thumbhotimage")) SetThumbHotImage (pstrValue); + else if (pstrName == _T ("thumbpushedimage")) SetThumbPushedImage (pstrValue); + else if (pstrName == _T ("thumbsize")) { + SIZE szXY = FawTools::parse_size (pstrValue); + SetThumbSize (szXY); + } else if (pstrName == _T ("step")) { + SetChangeStep (FawTools::parse_dec (pstrValue)); + } else if (pstrName == _T ("sendmove")) { + SetCanSendMove (FawTools::parse_bool (pstrValue)); + } else CProgressUI::SetAttribute (pstrName, pstrValue); + } + + void CSliderUI::PaintForeImage (HDC hDC) { + CProgressUI::PaintForeImage (hDC); + + RECT rcThumb = GetThumbRect (); + rcThumb.left -= m_rcItem.left; + rcThumb.top -= m_rcItem.top; + rcThumb.right -= m_rcItem.left; + rcThumb.bottom -= m_rcItem.top; + + GetManager ()->GetDPIObj ()->ScaleBack (&rcThumb); + + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + if (!m_sThumbPushedImage.empty ()) { + m_sImageModify.clear (); + m_sImageModify.Format (_T ("dest='%d,%d,%d,%d'"), rcThumb.left, rcThumb.top, rcThumb.right, rcThumb.bottom); + if (!DrawImage (hDC, m_sThumbPushedImage, m_sImageModify)) { + } else return; + } + } else if ((m_uButtonState & UISTATE_HOT) != 0) { + if (!m_sThumbHotImage.empty ()) { + m_sImageModify.clear (); + m_sImageModify.Format (_T ("dest='%d,%d,%d,%d'"), rcThumb.left, rcThumb.top, rcThumb.right, rcThumb.bottom); + if (!DrawImage (hDC, m_sThumbHotImage, m_sImageModify)) { + } else return; + } + } + + if (!m_sThumbImage.empty ()) { + m_sImageModify.clear (); + m_sImageModify.Format (_T ("dest='%d,%d,%d,%d'"), rcThumb.left, rcThumb.top, rcThumb.right, rcThumb.bottom); + if (!DrawImage (hDC, m_sThumbImage, m_sImageModify)) { + } else return; + } + } +} diff --git a/DuiLib/Control/UISlider.h b/DuiLib/Control/UISlider.h new file mode 100644 index 0000000..b9f5aba --- /dev/null +++ b/DuiLib/Control/UISlider.h @@ -0,0 +1,50 @@ +#ifndef __UISLIDER_H__ +#define __UISLIDER_H__ + +#pragma once + +namespace DuiLib { + class UILIB_API CSliderUI: public CProgressUI { + DECLARE_DUICONTROL (CSliderUI) + public: + CSliderUI (); + + string_view_t GetClass () const; + UINT GetControlFlags () const; + LPVOID GetInterface (string_view_t pstrName); + + void SetEnabled (bool bEnable = true); + + int GetChangeStep (); + void SetChangeStep (int step); + void SetThumbSize (SIZE szXY); + RECT GetThumbRect () const; + string_view_t GetThumbImage () const; + void SetThumbImage (string_view_t pStrImage); + string_view_t GetThumbHotImage () const; + void SetThumbHotImage (string_view_t pStrImage); + string_view_t GetThumbPushedImage () const; + void SetThumbPushedImage (string_view_t pStrImage); + + void DoEvent (TEventUI& event); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + void PaintForeImage (HDC hDC); + + void SetValue (int nValue); + void SetCanSendMove (bool bCanSend); + bool GetCanSendMove () const; + protected: + SIZE m_szThumb = { 0 }; + UINT m_uButtonState = 0; + int m_nStep = 1; + + CDuiString m_sThumbImage; + CDuiString m_sThumbHotImage; + CDuiString m_sThumbPushedImage; + + CDuiString m_sImageModify; + bool m_bSendMove = false; + }; +} + +#endif // __UISLIDER_H__ \ No newline at end of file diff --git a/DuiLib/Control/UIText.cpp b/DuiLib/Control/UIText.cpp new file mode 100644 index 0000000..c995fdf --- /dev/null +++ b/DuiLib/Control/UIText.cpp @@ -0,0 +1,149 @@ +#include "StdAfx.h" +#include "UIText.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CTextUI) + + CTextUI::CTextUI () { + m_uTextStyle = DT_WORDBREAK; + m_rcTextPadding.left = 2; + m_rcTextPadding.right = 2; + ::ZeroMemory (m_rcLinks, sizeof (m_rcLinks)); + } + + CTextUI::~CTextUI () {} + + string_view_t CTextUI::GetClass () const { + return _T ("TextUI"); + } + + LPVOID CTextUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_TEXT) return static_cast(this); + return CLabelUI::GetInterface (pstrName); + } + + UINT CTextUI::GetControlFlags () const { + if (IsEnabled () && m_nLinks > 0) return UIFLAG_SETCURSOR; + else return 0; + } + + CDuiString* CTextUI::GetLinkContent (int iIndex) { + if (iIndex >= 0 && iIndex < m_nLinks) return &m_sLinks[iIndex]; + return nullptr; + } + + void CTextUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CLabelUI::DoEvent (event); + return; + } else if (event.Type == UIEVENT_SETCURSOR) { + for (int i = 0; i < m_nLinks; i++) { + if (::PtInRect (&m_rcLinks[i], event.ptMouse)) { + ::SetCursor (::LoadCursor (nullptr, IDC_HAND)); + return; + } + } + } else if (event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK && IsEnabled ()) { + for (int i = 0; i < m_nLinks; i++) { + if (::PtInRect (&m_rcLinks[i], event.ptMouse)) { + Invalidate (); + return; + } + } + } else if (event.Type == UIEVENT_BUTTONUP && IsEnabled ()) { + for (int i = 0; i < m_nLinks; i++) { + if (::PtInRect (&m_rcLinks[i], event.ptMouse)) { + m_pManager->SendNotify (this, DUI_MSGTYPE_LINK, i); + return; + } + } + } else if (event.Type == UIEVENT_CONTEXTMENU) { + return; + } else if (m_nLinks > 0 && event.Type == UIEVENT_MOUSEMOVE && IsEnabled ()) { + int nHoverLink = -1; + for (int i = 0; i < m_nLinks; i++) { + if (::PtInRect (&m_rcLinks[i], event.ptMouse)) { + nHoverLink = i; + break; + } + } + + if (m_nHoverLink != nHoverLink) { + m_nHoverLink = nHoverLink; + Invalidate (); + return; + } + } else if (event.Type == UIEVENT_MOUSELEAVE) { + if (m_nLinks > 0 && IsEnabled ()) { + if (m_nHoverLink != -1) { + m_nHoverLink = -1; + Invalidate (); + return; + } + } + } + + CLabelUI::DoEvent (event); + } + + SIZE CTextUI::EstimateSize (SIZE szAvailable) { + CDuiString sText = GetText (); + RECT _rcTextPadding = GetTextPadding (); + + RECT rcText = { 0, 0, m_bAutoCalcWidth ? 9999 : GetManager ()->GetDPIObj ()->Scale (m_cxyFixed.cx), 9999 }; + rcText.left += _rcTextPadding.left; + rcText.right -= _rcTextPadding.right; + + if (m_bShowHtml) { + int nLinks = 0; + CRenderEngine::DrawHtmlText (m_pManager->GetPaintDC (), m_pManager, rcText, sText, m_dwTextColor, nullptr, nullptr, nLinks, m_iFont, DT_CALCRECT | m_uTextStyle); + } else { + CRenderEngine::DrawText (m_pManager->GetPaintDC (), m_pManager, rcText, sText, m_dwTextColor, m_iFont, DT_CALCRECT | m_uTextStyle); + } + SIZE cXY = { rcText.right - rcText.left + _rcTextPadding.left + _rcTextPadding.right, + rcText.bottom - rcText.top + _rcTextPadding.top + _rcTextPadding.bottom }; + + if (m_bAutoCalcWidth) { + m_cxyFixed.cx = MulDiv (cXY.cx, 100, GetManager ()->GetDPIObj ()->GetScale ()); + } + if (m_bAutoCalcHeight) { + m_cxyFixed.cy = MulDiv (cXY.cy, 100, GetManager ()->GetDPIObj ()->GetScale ()); + } + + return CControlUI::EstimateSize (szAvailable); + } + + void CTextUI::PaintText (HDC hDC) { + CDuiString sText = GetText (); + if (sText.empty ()) { + m_nLinks = 0; + return; + } + + if (m_dwTextColor == 0) m_dwTextColor = m_pManager->GetDefaultFontColor (); + if (m_dwDisabledTextColor == 0) m_dwDisabledTextColor = m_pManager->GetDefaultDisabledColor (); + + m_nLinks = lengthof (m_rcLinks); + RECT rc = m_rcItem; + rc.left += m_rcTextPadding.left; + rc.right -= m_rcTextPadding.right; + rc.top += m_rcTextPadding.top; + rc.bottom -= m_rcTextPadding.bottom; + if (IsEnabled ()) { + if (m_bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rc, sText, m_dwTextColor, \ + m_rcLinks, m_sLinks, m_nLinks, m_iFont, m_uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rc, sText, m_dwTextColor, \ + m_iFont, m_uTextStyle); + } else { + if (m_bShowHtml) + CRenderEngine::DrawHtmlText (hDC, m_pManager, rc, sText, m_dwDisabledTextColor, \ + m_rcLinks, m_sLinks, m_nLinks, m_iFont, m_uTextStyle); + else + CRenderEngine::DrawText (hDC, m_pManager, rc, sText, m_dwDisabledTextColor, \ + m_iFont, m_uTextStyle); + } + } +} diff --git a/DuiLib/Control/UIText.h b/DuiLib/Control/UIText.h new file mode 100644 index 0000000..bbc2eb2 --- /dev/null +++ b/DuiLib/Control/UIText.h @@ -0,0 +1,34 @@ +#ifndef __UITEXT_H__ +#define __UITEXT_H__ + +#pragma once + +namespace DuiLib { + class UILIB_API CTextUI: public CLabelUI { + DECLARE_DUICONTROL (CTextUI) + public: + CTextUI (); + virtual ~CTextUI (); + + string_view_t GetClass () const; + UINT GetControlFlags () const; + LPVOID GetInterface (string_view_t pstrName); + + CDuiString* GetLinkContent (int iIndex); + + void DoEvent (TEventUI& event); + SIZE EstimateSize (SIZE szAvailable); + + void PaintText (HDC hDC); + + protected: + enum { MAX_LINK = 8 }; + int m_nLinks = 0; + RECT m_rcLinks[MAX_LINK]; + CDuiString m_sLinks[MAX_LINK]; + int m_nHoverLink = -1; + }; + +} // namespace DuiLib + +#endif //__UITEXT_H__ \ No newline at end of file diff --git a/DuiLib/Control/UITreeView.cpp b/DuiLib/Control/UITreeView.cpp new file mode 100644 index 0000000..d577e8e --- /dev/null +++ b/DuiLib/Control/UITreeView.cpp @@ -0,0 +1,1119 @@ +#include "StdAfx.h" +#include "UITreeView.h" + +#pragma warning( disable: 4251 ) +namespace DuiLib { + IMPLEMENT_DUICONTROL (CTreeNodeUI) + + //************************************ + // : CTreeNodeUI + // : + // Ϣ: CTreeNodeUI * _ParentNode + // ˵: + //************************************ + CTreeNodeUI::CTreeNodeUI (CTreeNodeUI* _ParentNode /*= nullptr*/) { + pHoriz = new CHorizontalLayoutUI (); + pFolderButton = new CCheckBoxUI (); + pDottedLine = new CLabelUI (); + pCheckBox = new CCheckBoxUI (); + pItemButton = new COptionUI (); + + this->SetFixedHeight (18); + this->SetFixedWidth (250); + pFolderButton->SetFixedWidth (GetFixedHeight ()); + pDottedLine->SetFixedWidth (2); + pCheckBox->SetFixedWidth (GetFixedHeight ()); + pItemButton->SetAttribute (_T ("align"), _T ("left")); + pDottedLine->SetVisible (FALSE); + pCheckBox->SetVisible (FALSE); + pItemButton->SetMouseEnabled (FALSE); + + if (_ParentNode) { + if (_ParentNode->GetClass () != _T ("TreeNodeUI")) return; + pDottedLine->SetVisible (_ParentNode->IsVisible ()); + pDottedLine->SetFixedWidth (_ParentNode->GetDottedLine ()->GetFixedWidth () + 16); + this->SetParentNode (_ParentNode); + } + pHoriz->SetChildVAlign (DT_VCENTER); + pHoriz->Add (pDottedLine); + pHoriz->Add (pFolderButton); + pHoriz->Add (pCheckBox); + pHoriz->Add (pItemButton); + Add (pHoriz); + } + + //************************************ + // : ~CTreeNodeUI + // : + // Ϣ: void + // ˵: + //************************************ + CTreeNodeUI::~CTreeNodeUI (void) { + + } + + //************************************ + // : GetClass + // : LPCTstring_view_t + // ˵: + //************************************ + string_view_t CTreeNodeUI::GetClass () const { + return _T ("TreeNodeUI"); + } + + //************************************ + // : GetInterface + // : LPVOID + // Ϣ: LPCstring_view_ttrName + // ˵: + //************************************ + LPVOID CTreeNodeUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("TreeNode")) + return static_cast(this); + return CListContainerElementUI::GetInterface (pstrName); + } + + //************************************ + // : DoEvent + // : void + // Ϣ: TEventUI & event + // ˵: + //************************************ + void CTreeNodeUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pOwner) m_pOwner->DoEvent (event); + else CContainerUI::DoEvent (event); + return; + } + CListContainerElementUI::DoEvent (event); + if (event.Type == UIEVENT_DBLCLICK) { + if (IsEnabled ()) { + m_pManager->SendNotify (this, DUI_MSGTYPE_TREEITEMDBCLICK); + Invalidate (); + } + return; + } + if (event.Type == UIEVENT_MOUSEENTER) { + if (IsEnabled ()) { + if (m_bSelected && GetSelItemHotTextColor ()) + pItemButton->SetTextColor (GetSelItemHotTextColor ()); + else + pItemButton->SetTextColor (GetItemHotTextColor ()); + } else + pItemButton->SetTextColor (pItemButton->GetDisabledTextColor ()); + + return; + } + if (event.Type == UIEVENT_MOUSELEAVE) { + if (IsEnabled ()) { + if (m_bSelected && GetSelItemTextColor ()) + pItemButton->SetTextColor (GetSelItemTextColor ()); + else if (!m_bSelected) + pItemButton->SetTextColor (GetItemTextColor ()); + } else + pItemButton->SetTextColor (pItemButton->GetDisabledTextColor ()); + + return; + } + } + + //************************************ + // : Invalidate + // : void + // ˵: + //************************************ + void CTreeNodeUI::Invalidate () { + if (!IsVisible ()) + return; + + if (GetParent ()) { + CContainerUI* pParentContainer = static_cast(GetParent ()->GetInterface (_T ("Container"))); + if (pParentContainer) { + RECT rc = pParentContainer->GetPos (); + RECT rcInset = pParentContainer->GetInset (); + rc.left += rcInset.left; + rc.top += rcInset.top; + rc.right -= rcInset.right; + rc.bottom -= rcInset.bottom; + CScrollBarUI* pVerticalScrollBar = pParentContainer->GetVerticalScrollBar (); + if (pVerticalScrollBar && pVerticalScrollBar->IsVisible ()) rc.right -= pVerticalScrollBar->GetFixedWidth (); + CScrollBarUI* pHorizontalScrollBar = pParentContainer->GetHorizontalScrollBar (); + if (pHorizontalScrollBar && pHorizontalScrollBar->IsVisible ()) rc.bottom -= pHorizontalScrollBar->GetFixedHeight (); + + RECT invalidateRc = m_rcItem; + if (!::IntersectRect (&invalidateRc, &m_rcItem, &rc)) + return; + + CControlUI* pParent = GetParent (); + RECT rcTemp = { 0 }; + RECT rcParent = { 0 }; + while (!!(pParent = pParent->GetParent ())) { + rcTemp = invalidateRc; + rcParent = pParent->GetPos (); + if (!::IntersectRect (&invalidateRc, &rcTemp, &rcParent)) + return; + } + + if (m_pManager) m_pManager->Invalidate (invalidateRc); + } else { + CContainerUI::Invalidate (); + } + } else { + CContainerUI::Invalidate (); + } + } + + //************************************ + // : Select + // : bool + // Ϣ: bool bSelect + // ˵: + //************************************ + bool CTreeNodeUI::Select (bool bSelect /*= true*/) { + bool nRet = CListContainerElementUI::Select (bSelect); + if (m_bSelected) + pItemButton->SetTextColor (GetSelItemTextColor ()); + else + pItemButton->SetTextColor (GetItemTextColor ()); + + return nRet; + } + + bool CTreeNodeUI::SelectMulti (bool bSelect) { + bool nRet = CListContainerElementUI::SelectMulti (bSelect); + if (m_bSelected) + pItemButton->SetTextColor (GetSelItemTextColor ()); + else + pItemButton->SetTextColor (GetItemTextColor ()); + + return nRet; + } + //************************************ + // : Add + // : bool + // Ϣ: CControlUI * _pTreeNodeUI + // ˵: ͨڵӽڵ + //************************************ + bool CTreeNodeUI::Add (CControlUI* _pTreeNodeUI) { + if (nullptr != static_cast(_pTreeNodeUI->GetInterface (_T ("TreeNode")))) + return AddChildNode ((CTreeNodeUI*) _pTreeNodeUI); + + return CListContainerElementUI::Add (_pTreeNodeUI); + } + + //************************************ + // : AddAt + // : bool + // Ϣ: CControlUI * pControl + // Ϣ: int iIndex òԵǰڵµֵбͼ + // ˵: + //************************************ + bool CTreeNodeUI::AddAt (CControlUI* pControl, int iIndex) { + if (nullptr == static_cast(pControl->GetInterface (_T ("TreeNode")))) + return FALSE; + + CTreeNodeUI* pIndexNode = static_cast(mTreeNodes.GetAt (iIndex)); + if (!pIndexNode) { + if (!mTreeNodes.Add (pControl)) + return FALSE; + } else if (pIndexNode && !mTreeNodes.InsertAt (iIndex, pControl)) + return FALSE; + + if (!pIndexNode && pTreeView && pTreeView->GetItemAt (GetTreeIndex () + 1)) + pIndexNode = static_cast(pTreeView->GetItemAt (GetTreeIndex () + 1)->GetInterface (_T ("TreeNode"))); + + pControl = CalLocation ((CTreeNodeUI*) pControl); + + if (pTreeView && pIndexNode) + return pTreeView->AddAt ((CTreeNodeUI*) pControl, pIndexNode); + else + return pTreeView->Add ((CTreeNodeUI*) pControl); + + return TRUE; + } + + //************************************ + // : Remove + // : bool + // Ϣ: CControlUI * pControl + // ˵: + //************************************ + bool CTreeNodeUI::Remove (CControlUI* pControl) { + return RemoveAt ((CTreeNodeUI*) pControl); + } + + //************************************ + // : SetVisibleTag + // : void + // Ϣ: bool _IsVisible + // ˵: + //************************************ + void CTreeNodeUI::SetVisibleTag (bool _IsVisible) { + m_bIsVisable = _IsVisible; + } + + //************************************ + // : GetVisibleTag + // : bool + // ˵: + //************************************ + bool CTreeNodeUI::GetVisibleTag () { + return m_bIsVisable; + } + + //************************************ + // : SetItemText + // : void + // Ϣ: LPCstring_view_ttrValue + // ˵: + //************************************ + void CTreeNodeUI::SetItemText (string_view_t pstrValue) { + pItemButton->SetText (pstrValue); + } + + //************************************ + // : GetItemText + // : CDuiString + // ˵: + //************************************ + CDuiString CTreeNodeUI::GetItemText () { + return pItemButton->GetText (); + } + + //************************************ + // : CheckBoxSelected + // : void + // Ϣ: bool _Selected + // ˵: + //************************************ + void CTreeNodeUI::CheckBoxSelected (bool _Selected) { + pCheckBox->Selected (_Selected); + } + + //************************************ + // : IsCheckBoxSelected + // : bool + // ˵: + //************************************ + bool CTreeNodeUI::IsCheckBoxSelected () const { + return pCheckBox->IsSelected (); + } + + //************************************ + // : IsHasChild + // : bool + // ˵: + //************************************ + bool CTreeNodeUI::IsHasChild () const { + return !mTreeNodes.empty (); + } + + //************************************ + // : AddChildNode + // : bool + // Ϣ: CTreeNodeUI * _pTreeNodeUI + // ˵: + //************************************ + bool CTreeNodeUI::AddChildNode (CTreeNodeUI* _pTreeNodeUI) { + if (!_pTreeNodeUI) + return FALSE; + + if (nullptr == static_cast(_pTreeNodeUI->GetInterface (_T ("TreeNode")))) + return FALSE; + + _pTreeNodeUI = CalLocation (_pTreeNodeUI); + + bool nRet = TRUE; + + if (pTreeView) { + CTreeNodeUI* pNode = static_cast(mTreeNodes.GetAt (mTreeNodes.GetSize () - 1)); + if (!pNode || !pNode->GetLastNode ()) + nRet = pTreeView->AddAt (_pTreeNodeUI, GetTreeIndex () + 1) >= 0; + else nRet = pTreeView->AddAt (_pTreeNodeUI, pNode->GetLastNode ()->GetTreeIndex () + 1) >= 0; + } + + if (nRet) + mTreeNodes.Add (_pTreeNodeUI); + + return nRet; + } + + //************************************ + // : RemoveAt + // : bool + // Ϣ: CTreeNodeUI * _pTreeNodeUI + // ˵: + //************************************ + bool CTreeNodeUI::RemoveAt (CTreeNodeUI* _pTreeNodeUI) { + int nIndex = mTreeNodes.Find (_pTreeNodeUI); + CTreeNodeUI* pNode = static_cast(mTreeNodes.GetAt (nIndex)); + if (pNode && pNode == _pTreeNodeUI) { + while (pNode->IsHasChild ()) + pNode->RemoveAt (static_cast(pNode->mTreeNodes.GetAt (0))); + + mTreeNodes.Remove (nIndex); + + if (pTreeView) + pTreeView->Remove (_pTreeNodeUI); + + return TRUE; + } + return FALSE; + } + + //************************************ + // : SetParentNode + // : void + // Ϣ: CTreeNodeUI * _pParentTreeNode + // ˵: + //************************************ + void CTreeNodeUI::SetParentNode (CTreeNodeUI* _pParentTreeNode) { + pParentTreeNode = _pParentTreeNode; + } + + //************************************ + // : GetParentNode + // : CTreeNodeUI* + // ˵: + //************************************ + CTreeNodeUI* CTreeNodeUI::GetParentNode () { + return pParentTreeNode; + } + + //************************************ + // : GetCountChild + // : long + // ˵: + //************************************ + long CTreeNodeUI::GetCountChild () { + return mTreeNodes.GetSize (); + } + + //************************************ + // : SetTreeView + // : void + // Ϣ: CTreeViewUI * _CTreeViewUI + // ˵: + //************************************ + void CTreeNodeUI::SetTreeView (CTreeViewUI* _CTreeViewUI) { + pTreeView = _CTreeViewUI; + } + + //************************************ + // : GetTreeView + // : CTreeViewUI* + // ˵: + //************************************ + CTreeViewUI* CTreeNodeUI::GetTreeView () { + return pTreeView; + } + + //************************************ + // : SetAttribute + // : void + // Ϣ: LPCstring_view_ttrName + // Ϣ: LPCstring_view_ttrValue + // ˵: + //************************************ + void CTreeNodeUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("text")) + pItemButton->SetText (pstrValue); + else if (pstrName == _T ("horizattr")) + pHoriz->ApplyAttributeList (pstrValue); + else if (pstrName == _T ("dotlineattr")) + pDottedLine->ApplyAttributeList (pstrValue); + else if (pstrName == _T ("folderattr")) + pFolderButton->ApplyAttributeList (pstrValue); + else if (pstrName == _T ("checkboxattr")) + pCheckBox->ApplyAttributeList (pstrValue); + else if (pstrName == _T ("itemattr")) + pItemButton->ApplyAttributeList (pstrValue); + else if (pstrName == _T ("itemtextcolor")) { + SetItemTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("itemhottextcolor")) { + SetItemHotTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("selitemtextcolor")) { + SetSelItemTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("selitemhottextcolor")) { + SetSelItemHotTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else CListContainerElementUI::SetAttribute (pstrName, pstrValue); + } + + //************************************ + // : GetTreeNodes + // : DuiLib::CStdPtrArray + // ˵: + //************************************ + CStdPtrArray CTreeNodeUI::GetTreeNodes () { + return mTreeNodes; + } + + //************************************ + // : GetChildNode + // : CTreeNodeUI* + // Ϣ: int _nIndex + // ˵: + //************************************ + CTreeNodeUI* CTreeNodeUI::GetChildNode (int _nIndex) { + return static_cast(mTreeNodes.GetAt (_nIndex)); + } + + //************************************ + // : SetVisibleFolderBtn + // : void + // Ϣ: bool _IsVisibled + // ˵: + //************************************ + void CTreeNodeUI::SetVisibleFolderBtn (bool _IsVisibled) { + pFolderButton->SetVisible (_IsVisibled); + } + + //************************************ + // : GetVisibleFolderBtn + // : bool + // ˵: + //************************************ + bool CTreeNodeUI::GetVisibleFolderBtn () { + return pFolderButton->IsVisible (); + } + + //************************************ + // : SetVisibleCheckBtn + // : void + // Ϣ: bool _IsVisibled + // ˵: + //************************************ + void CTreeNodeUI::SetVisibleCheckBtn (bool _IsVisibled) { + pCheckBox->SetVisible (_IsVisibled); + } + + //************************************ + // : GetVisibleCheckBtn + // : bool + // ˵: + //************************************ + bool CTreeNodeUI::GetVisibleCheckBtn () { + return pCheckBox->IsVisible (); + } + + //************************************ + // : GetNodeIndex + // : int + // ˵: ȡȫͼ + //************************************ + int CTreeNodeUI::GetTreeIndex () { + if (!pTreeView) + return -1; + + for (int nIndex = 0; nIndex < pTreeView->GetCount (); nIndex++) { + if (this == pTreeView->GetItemAt (nIndex)) + return nIndex; + } + + return -1; + } + + //************************************ + // : GetNodeIndex + // : int + // ˵: ȡֵܽڵĵǰ + //************************************ + int CTreeNodeUI::GetNodeIndex () { + if (!GetParentNode () && !pTreeView) + return -1; + + if (!GetParentNode () && pTreeView) + return GetTreeIndex (); + + return GetParentNode ()->GetTreeNodes ().Find (this); + } + + //************************************ + // : GetLastNode + // : CTreeNodeUI* + // ˵: + //************************************ + CTreeNodeUI* CTreeNodeUI::GetLastNode () { + if (!IsHasChild ()) return this; + + CTreeNodeUI* nRetNode = nullptr; + for (int nIndex = 0; nIndex < GetTreeNodes ().GetSize (); nIndex++) { + CTreeNodeUI* pNode = static_cast(GetTreeNodes ().GetAt (nIndex)); + if (!pNode) continue; + if (pNode->IsHasChild ()) + nRetNode = pNode->GetLastNode (); + else + nRetNode = pNode; + } + + return nRetNode; + } + + //************************************ + // : CalLocation + // : CTreeNodeUI* + // Ϣ: CTreeNodeUI * _pTreeNodeUI + // ˵: + //************************************ + CTreeNodeUI* CTreeNodeUI::CalLocation (CTreeNodeUI* _pTreeNodeUI) { + _pTreeNodeUI->GetDottedLine ()->SetVisible (TRUE); + _pTreeNodeUI->GetDottedLine ()->SetFixedWidth (pDottedLine->GetFixedWidth () + 16); + _pTreeNodeUI->SetParentNode (this); + _pTreeNodeUI->GetItemButton ()->SetGroup (pItemButton->GetGroup ()); + _pTreeNodeUI->SetTreeView (pTreeView); + + return _pTreeNodeUI; + } + + //************************************ + // : SetTextColor + // : void + // Ϣ: DWORD _dwTextColor + // ˵: + //************************************ + void CTreeNodeUI::SetItemTextColor (DWORD _dwItemTextColor) { + m_dwItemTextColor = _dwItemTextColor; + pItemButton->SetTextColor (m_dwItemTextColor); + } + + //************************************ + // : GetTextColor + // : DWORD + // ˵: + //************************************ + DWORD CTreeNodeUI::GetItemTextColor () const { + return m_dwItemTextColor; + } + + //************************************ + // : SetTextHotColor + // : void + // Ϣ: DWORD _dwTextHotColor + // ˵: + //************************************ + void CTreeNodeUI::SetItemHotTextColor (DWORD _dwItemHotTextColor) { + m_dwItemHotTextColor = _dwItemHotTextColor; + Invalidate (); + } + + //************************************ + // : GetTextHotColor + // : DWORD + // ˵: + //************************************ + DWORD CTreeNodeUI::GetItemHotTextColor () const { + return m_dwItemHotTextColor; + } + + //************************************ + // : SetSelItemTextColor + // : void + // Ϣ: DWORD _dwSelItemTextColor + // ˵: + //************************************ + void CTreeNodeUI::SetSelItemTextColor (DWORD _dwSelItemTextColor) { + m_dwSelItemTextColor = _dwSelItemTextColor; + Invalidate (); + } + + //************************************ + // : GetSelItemTextColor + // : DWORD + // ˵: + //************************************ + DWORD CTreeNodeUI::GetSelItemTextColor () const { + return m_dwSelItemTextColor; + } + + //************************************ + // : SetSelHotItemTextColor + // : void + // Ϣ: DWORD _dwSelHotItemTextColor + // ˵: + //************************************ + void CTreeNodeUI::SetSelItemHotTextColor (DWORD _dwSelHotItemTextColor) { + m_dwSelItemHotTextColor = _dwSelHotItemTextColor; + Invalidate (); + } + + //************************************ + // : GetSelHotItemTextColor + // : DWORD + // ˵: + //************************************ + DWORD CTreeNodeUI::GetSelItemHotTextColor () const { + return m_dwSelItemHotTextColor; + } + + /*****************************************************************************/ + /*****************************************************************************/ + /*****************************************************************************/ + IMPLEMENT_DUICONTROL (CTreeViewUI) + + //************************************ + // : CTreeViewUI + // : + // Ϣ: void + // ˵: + //************************************ + CTreeViewUI::CTreeViewUI (void): m_bVisibleFolderBtn (TRUE), m_bVisibleCheckBtn (FALSE), m_uItemMinWidth (0) { + this->GetHeader ()->SetVisible (FALSE); + } + + //************************************ + // : ~CTreeViewUI + // : + // Ϣ: void + // ˵: + //************************************ + CTreeViewUI::~CTreeViewUI (void) { + + } + + //************************************ + // : GetClass + // : LPCTstring_view_t + // ˵: + //************************************ + string_view_t CTreeViewUI::GetClass () const { + return _T ("TreeViewUI"); + } + + + UINT CTreeViewUI::GetListType () { + return LT_TREE; + } + + //************************************ + // : GetInterface + // : LPVOID + // Ϣ: LPCstring_view_ttrName + // ˵: + //************************************ + LPVOID CTreeViewUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("TreeView")) return static_cast(this); + return CListUI::GetInterface (pstrName); + } + + //************************************ + // : Add + // : bool + // Ϣ: CTreeNodeUI * pControl + // ˵: + //************************************ + bool CTreeViewUI::Add (CTreeNodeUI* pControl) { + if (!pControl) return false; + if (nullptr == static_cast(pControl->GetInterface (_T ("TreeNode")))) return false; + + pControl->OnNotify += MakeDelegate (this, &CTreeViewUI::OnDBClickItem); + pControl->GetFolderButton ()->OnNotify += MakeDelegate (this, &CTreeViewUI::OnFolderChanged); + pControl->GetCheckBox ()->OnNotify += MakeDelegate (this, &CTreeViewUI::OnCheckBoxChanged); + + pControl->SetVisibleFolderBtn (m_bVisibleFolderBtn); + pControl->SetVisibleCheckBtn (m_bVisibleCheckBtn); + if (m_uItemMinWidth > 0) + pControl->SetMinWidth (m_uItemMinWidth); + + CListUI::Add (pControl); + + if (pControl->GetCountChild () > 0) { + int nCount = pControl->GetCountChild (); + for (int nIndex = 0; nIndex < nCount; nIndex++) { + CTreeNodeUI* pNode = pControl->GetChildNode (nIndex); + if (pNode) Add (pNode); + } + } + + pControl->SetTreeView (this); + return true; + } + + //************************************ + // : AddAt + // : long + // Ϣ: CTreeNodeUI * pControl + // Ϣ: int iIndex + // ˵: ÷ὫĽڵλĽڵΪǸڵ㣬ʹAddAt(CTreeNodeUI* pControl,CTreeNodeUI* _IndexNode) + //************************************ + long CTreeViewUI::AddAt (CTreeNodeUI* pControl, int iIndex) { + if (!pControl) return -1; + if (nullptr == static_cast(pControl->GetInterface (_T ("TreeNode")))) return -1; + pControl->OnNotify += MakeDelegate (this, &CTreeViewUI::OnDBClickItem); + pControl->GetFolderButton ()->OnNotify += MakeDelegate (this, &CTreeViewUI::OnFolderChanged); + pControl->GetCheckBox ()->OnNotify += MakeDelegate (this, &CTreeViewUI::OnCheckBoxChanged); + pControl->SetVisibleFolderBtn (m_bVisibleFolderBtn); + pControl->SetVisibleCheckBtn (m_bVisibleCheckBtn); + + if (m_uItemMinWidth > 0) { + pControl->SetMinWidth (m_uItemMinWidth); + } + CListUI::AddAt (pControl, iIndex); + if (pControl->GetCountChild () > 0) { + int nCount = pControl->GetCountChild (); + for (int nIndex = 0; nIndex < nCount; nIndex++) { + CTreeNodeUI* pNode = pControl->GetChildNode (nIndex); + if (pNode) + return AddAt (pNode, iIndex + 1); + } + } else { + return iIndex + 1; + } + + return -1; + } + + //************************************ + // : AddAt + // : bool + // Ϣ: CTreeNodeUI * pControl + // Ϣ: CTreeNodeUI * _IndexNode + // ˵: + //************************************ + bool CTreeViewUI::AddAt (CTreeNodeUI* pControl, CTreeNodeUI* _IndexNode) { + if (!_IndexNode && !pControl) + return FALSE; + + int nItemIndex = -1; + for (int nIndex = 0; nIndex < GetCount (); nIndex++) { + if (_IndexNode == GetItemAt (nIndex)) { + nItemIndex = nIndex; + break; + } + } + + if (nItemIndex == -1) + return FALSE; + + return AddAt (pControl, nItemIndex) >= 0; + } + + //************************************ + // : Remove + // : bool + // Ϣ: CTreeNodeUI * pControl + // ˵: pControl Լµнڵ㽫һƳ + //************************************ + bool CTreeViewUI::Remove (CTreeNodeUI* pControl) { + if (pControl->GetCountChild () > 0) { + int nCount = pControl->GetCountChild (); + for (int nIndex = nCount - 1; nIndex >= 0; nIndex--) { + CTreeNodeUI* pNode = pControl->GetChildNode (nIndex); + if (pNode) { + pControl->Remove (pNode); + } + } + } + CListUI::Remove (pControl); + return TRUE; + } + + //************************************ + // : RemoveAt + // : bool + // Ϣ: int iIndex + // ˵: iIndex Լµнڵ㽫һƳ + //************************************ + bool CTreeViewUI::RemoveAt (int iIndex) { + CTreeNodeUI* pItem = (CTreeNodeUI*) GetItemAt (iIndex); + if (pItem->GetCountChild ()) + Remove (pItem); + return TRUE; + } + + void CTreeViewUI::RemoveAll () { + CListUI::RemoveAll (); + } + + //************************************ + // : Notify + // : void + // Ϣ: TNotifyUI & msg + // ˵: + //************************************ + void CTreeViewUI::Notify (TNotifyUI& msg) { + + } + + //************************************ + // : OnCheckBoxChanged + // : bool + // Ϣ: void * param + // ˵: + //************************************ + bool CTreeViewUI::OnCheckBoxChanged (void* param) { + TNotifyUI* pMsg = (TNotifyUI*) param; + if (pMsg->sType == DUI_MSGTYPE_SELECTCHANGED) { + CCheckBoxUI* pCheckBox = (CCheckBoxUI*) pMsg->pSender; + CTreeNodeUI* pItem = (CTreeNodeUI*) pCheckBox->GetParent ()->GetParent (); + SetItemCheckBox (pCheckBox->GetCheck (), pItem); + return TRUE; + } + return TRUE; + } + + //************************************ + // : OnFolderChanged + // : bool + // Ϣ: void * param + // ˵: + //************************************ + bool CTreeViewUI::OnFolderChanged (void* param) { + TNotifyUI* pMsg = (TNotifyUI*) param; + if (pMsg->sType == DUI_MSGTYPE_SELECTCHANGED) { + CCheckBoxUI* pFolder = (CCheckBoxUI*) pMsg->pSender; + CTreeNodeUI* pItem = (CTreeNodeUI*) pFolder->GetParent ()->GetParent (); + pItem->SetVisibleTag (!pFolder->GetCheck ()); + SetItemExpand (!pFolder->GetCheck (), pItem); + return TRUE; + } + return TRUE; + } + + //************************************ + // : OnDBClickItem + // : bool + // Ϣ: void * param + // ˵: + //************************************ + bool CTreeViewUI::OnDBClickItem (void* param) { + TNotifyUI* pMsg = (TNotifyUI*) param; + if (pMsg->sType == DUI_MSGTYPE_TREEITEMDBCLICK) { + CTreeNodeUI* pItem = static_cast(pMsg->pSender); + CCheckBoxUI* pFolder = pItem->GetFolderButton (); + pFolder->Selected (!pFolder->IsSelected ()); + pItem->SetVisibleTag (!pFolder->GetCheck ()); + SetItemExpand (!pFolder->GetCheck (), pItem); + return TRUE; + } + return FALSE; + } + + //************************************ + // : SetItemCheckBox + // : bool + // Ϣ: bool _Selected + // Ϣ: CTreeNodeUI * _TreeNode + // ˵: + //************************************ + bool CTreeViewUI::SetItemCheckBox (bool _Selected, CTreeNodeUI* _TreeNode /*= nullptr*/) { + if (_TreeNode) { + if (_TreeNode->GetCountChild () > 0) { + int nCount = _TreeNode->GetCountChild (); + for (int nIndex = 0; nIndex < nCount; nIndex++) { + CTreeNodeUI* pItem = _TreeNode->GetChildNode (nIndex); + pItem->GetCheckBox ()->Selected (_Selected); + if (pItem->GetCountChild ()) + SetItemCheckBox (_Selected, pItem); + } + } + return TRUE; + } else { + int nIndex = 0; + int nCount = GetCount (); + while (nIndex < nCount) { + CTreeNodeUI* pItem = (CTreeNodeUI*) GetItemAt (nIndex); + pItem->GetCheckBox ()->Selected (_Selected); + if (pItem->GetCountChild ()) + SetItemCheckBox (_Selected, pItem); + + nIndex++; + } + return TRUE; + } + return FALSE; + } + + //************************************ + // : SetItemExpand + // : void + // Ϣ: bool _Expanded + // Ϣ: CTreeNodeUI * _TreeNode + // ˵: + //************************************ + void CTreeViewUI::SetItemExpand (bool _Expanded, CTreeNodeUI* _TreeNode /*= nullptr*/) { + if (_TreeNode) { + if (_TreeNode->GetCountChild () > 0) { + int nCount = _TreeNode->GetCountChild (); + for (int nIndex = 0; nIndex < nCount; nIndex++) { + CTreeNodeUI* pItem = _TreeNode->GetChildNode (nIndex); + pItem->SetVisible (_Expanded); + if (pItem->GetCountChild () && !pItem->GetFolderButton ()->IsSelected ()) { + SetItemExpand (_Expanded, pItem); + } + } + } + } else { + int nIndex = 0; + int nCount = GetCount (); + while (nIndex < nCount) { + CTreeNodeUI* pItem = (CTreeNodeUI*) GetItemAt (nIndex); + pItem->SetVisible (_Expanded); + if (pItem->GetCountChild () && !pItem->GetFolderButton ()->IsSelected ()) { + SetItemExpand (_Expanded, pItem); + } + nIndex++; + } + } + } + + //************************************ + // : SetVisibleFolderBtn + // : void + // Ϣ: bool _IsVisibled + // ˵: + //************************************ + void CTreeViewUI::SetVisibleFolderBtn (bool _IsVisibled) { + m_bVisibleFolderBtn = _IsVisibled; + int nCount = this->GetCount (); + for (int nIndex = 0; nIndex < nCount; nIndex++) { + CTreeNodeUI* pItem = static_cast(this->GetItemAt (nIndex)); + pItem->GetFolderButton ()->SetVisible (m_bVisibleFolderBtn); + } + } + + //************************************ + // : GetVisibleFolderBtn + // : bool + // ˵: + //************************************ + bool CTreeViewUI::GetVisibleFolderBtn () { + return m_bVisibleFolderBtn; + } + + //************************************ + // : SetVisibleCheckBtn + // : void + // Ϣ: bool _IsVisibled + // ˵: + //************************************ + void CTreeViewUI::SetVisibleCheckBtn (bool _IsVisibled) { + m_bVisibleCheckBtn = _IsVisibled; + int nCount = this->GetCount (); + for (int nIndex = 0; nIndex < nCount; nIndex++) { + CTreeNodeUI* pItem = static_cast(this->GetItemAt (nIndex)); + pItem->GetCheckBox ()->SetVisible (m_bVisibleCheckBtn); + } + } + + //************************************ + // : GetVisibleCheckBtn + // : bool + // ˵: + //************************************ + bool CTreeViewUI::GetVisibleCheckBtn () { + return m_bVisibleCheckBtn; + } + + //************************************ + // : SetItemMinWidth + // : void + // Ϣ: UINT _ItemMinWidth + // ˵: + //************************************ + void CTreeViewUI::SetItemMinWidth (UINT _ItemMinWidth) { + m_uItemMinWidth = _ItemMinWidth; + + for (int nIndex = 0; nIndex < GetCount (); nIndex++) { + CTreeNodeUI* pTreeNode = static_cast(GetItemAt (nIndex)); + if (pTreeNode) { + pTreeNode->SetMinWidth (GetItemMinWidth ()); + } + } + Invalidate (); + } + + //************************************ + // : GetItemMinWidth + // : UINT + // ˵: + //************************************ + UINT CTreeViewUI::GetItemMinWidth () { + return m_uItemMinWidth; + } + + //************************************ + // : SetItemTextColor + // : void + // Ϣ: DWORD _dwItemTextColor + // ˵: + //************************************ + void CTreeViewUI::SetItemTextColor (DWORD _dwItemTextColor) { + for (int nIndex = 0; nIndex < GetCount (); nIndex++) { + CTreeNodeUI* pTreeNode = static_cast(GetItemAt (nIndex)); + if (pTreeNode) { + pTreeNode->SetItemTextColor (_dwItemTextColor); + } + } + } + + //************************************ + // : SetItemHotTextColor + // : void + // Ϣ: DWORD _dwItemHotTextColor + // ˵: + //************************************ + void CTreeViewUI::SetItemHotTextColor (DWORD _dwItemHotTextColor) { + for (int nIndex = 0; nIndex < GetCount (); nIndex++) { + CTreeNodeUI* pTreeNode = static_cast(GetItemAt (nIndex)); + if (pTreeNode) { + pTreeNode->SetItemHotTextColor (_dwItemHotTextColor); + } + } + } + + //************************************ + // : SetSelItemTextColor + // : void + // Ϣ: DWORD _dwSelItemTextColor + // ˵: + //************************************ + void CTreeViewUI::SetSelItemTextColor (DWORD _dwSelItemTextColor) { + for (int nIndex = 0; nIndex < GetCount (); nIndex++) { + CTreeNodeUI* pTreeNode = static_cast(GetItemAt (nIndex)); + if (pTreeNode) { + pTreeNode->SetSelItemTextColor (_dwSelItemTextColor); + } + } + } + + //************************************ + // : SetSelItemHotTextColor + // : void + // Ϣ: DWORD _dwSelHotItemTextColor + // ˵: + //************************************ + void CTreeViewUI::SetSelItemHotTextColor (DWORD _dwSelHotItemTextColor) { + for (int nIndex = 0; nIndex < GetCount (); nIndex++) { + CTreeNodeUI* pTreeNode = static_cast(GetItemAt (nIndex)); + if (pTreeNode) { + pTreeNode->SetSelItemHotTextColor (_dwSelHotItemTextColor); + } + } + } + + //************************************ + // : SetAttribute + // : void + // Ϣ: LPCstring_view_ttrName + // Ϣ: LPCstring_view_ttrValue + // ˵: + //************************************ + void CTreeViewUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("visiblefolderbtn")) + SetVisibleFolderBtn (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("visiblecheckbtn")) + SetVisibleCheckBtn (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("itemminwidth")) + SetItemMinWidth (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("itemtextcolor")) { + SetItemTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("itemhottextcolor")) { + SetItemHotTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("selitemtextcolor")) { + SetSelItemTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("selitemhottextcolor")) { + SetSelItemHotTextColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else CListUI::SetAttribute (pstrName, pstrValue); + } + +} \ No newline at end of file diff --git a/DuiLib/Control/UITreeView.h b/DuiLib/Control/UITreeView.h new file mode 100644 index 0000000..01481da --- /dev/null +++ b/DuiLib/Control/UITreeView.h @@ -0,0 +1,149 @@ +#ifndef UITreeView_h__ +#define UITreeView_h__ + +#pragma once + +#include + +namespace DuiLib { + class CTreeViewUI; + class CCheckBoxUI; + class CLabelUI; + class COptionUI; + + class UILIB_API CTreeNodeUI: public CListContainerElementUI { + DECLARE_DUICONTROL (CTreeNodeUI) + public: + CTreeNodeUI (CTreeNodeUI* _ParentNode = nullptr); + virtual ~CTreeNodeUI (void); + + public: + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + void DoEvent (TEventUI& event); + void Invalidate (); + bool Select (bool bSelect = true); + bool SelectMulti (bool bSelect = true); + + bool Add (CControlUI* _pTreeNodeUI); + bool AddAt (CControlUI* pControl, int iIndex); + bool Remove (CControlUI* pControl); + + void SetVisibleTag (bool _IsVisible); + bool GetVisibleTag (); + void SetItemText (string_view_t pstrValue); + CDuiString GetItemText (); + void CheckBoxSelected (bool _Selected); + bool IsCheckBoxSelected () const; + bool IsHasChild () const; + long GetTreeLevel () const; + bool AddChildNode (CTreeNodeUI* _pTreeNodeUI); + bool RemoveAt (CTreeNodeUI* _pTreeNodeUI); + void SetParentNode (CTreeNodeUI* _pParentTreeNode); + CTreeNodeUI* GetParentNode (); + long GetCountChild (); + void SetTreeView (CTreeViewUI* _CTreeViewUI); + CTreeViewUI* GetTreeView (); + CTreeNodeUI* GetChildNode (int _nIndex); + void SetVisibleFolderBtn (bool _IsVisibled); + bool GetVisibleFolderBtn (); + void SetVisibleCheckBtn (bool _IsVisibled); + bool GetVisibleCheckBtn (); + void SetItemTextColor (DWORD _dwItemTextColor); + DWORD GetItemTextColor () const; + void SetItemHotTextColor (DWORD _dwItemHotTextColor); + DWORD GetItemHotTextColor () const; + void SetSelItemTextColor (DWORD _dwSelItemTextColor); + DWORD GetSelItemTextColor () const; + void SetSelItemHotTextColor (DWORD _dwSelHotItemTextColor); + DWORD GetSelItemHotTextColor () const; + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + CStdPtrArray GetTreeNodes (); + int GetTreeIndex (); + int GetNodeIndex (); + + public: + CHorizontalLayoutUI *GetTreeNodeHoriznotal () const { + return pHoriz; + }; + CCheckBoxUI *GetFolderButton () const { + return pFolderButton; + }; + CLabelUI *GetDottedLine () const { + return pDottedLine; + }; + CCheckBoxUI *GetCheckBox () const { + return pCheckBox; + }; + COptionUI *GetItemButton () const { + return pItemButton; + }; + + private: + CTreeNodeUI* GetLastNode (); + CTreeNodeUI* CalLocation (CTreeNodeUI* _pTreeNodeUI); + + private: + long m_iTreeLavel = 0; + bool m_bIsVisable = true; + bool m_bIsCheckBox = false; + DWORD m_dwItemTextColor = 0x00000000; + DWORD m_dwItemHotTextColor = 0; + DWORD m_dwSelItemTextColor = 0; + DWORD m_dwSelItemHotTextColor = 0; + + CTreeViewUI *pTreeView = nullptr; + CHorizontalLayoutUI *pHoriz; + CCheckBoxUI *pFolderButton; + CLabelUI *pDottedLine; + CCheckBoxUI *pCheckBox; + COptionUI *pItemButton; + CTreeNodeUI *pParentTreeNode = nullptr; + CStdPtrArray mTreeNodes; + }; + + class UILIB_API CTreeViewUI: public CListUI, public INotifyUI { + DECLARE_DUICONTROL (CTreeViewUI) + public: + CTreeViewUI (void); + virtual ~CTreeViewUI (void); + + public: + virtual string_view_t GetClass () const; + virtual LPVOID GetInterface (string_view_t pstrName); + + virtual UINT GetListType (); + virtual bool Add (CTreeNodeUI* pControl); + virtual long AddAt (CTreeNodeUI* pControl, int iIndex); + virtual bool AddAt (CTreeNodeUI* pControl, CTreeNodeUI* _IndexNode); + virtual bool Remove (CTreeNodeUI* pControl); + virtual bool RemoveAt (int iIndex); + virtual void RemoveAll (); + virtual bool OnCheckBoxChanged (void* param); + virtual bool OnFolderChanged (void* param); + virtual bool OnDBClickItem (void* param); + virtual bool SetItemCheckBox (bool _Selected, CTreeNodeUI* _TreeNode = nullptr); + virtual void SetItemExpand (bool _Expanded, CTreeNodeUI* _TreeNode = nullptr); + virtual void Notify (TNotifyUI& msg); + virtual void SetVisibleFolderBtn (bool _IsVisibled); + virtual bool GetVisibleFolderBtn (); + virtual void SetVisibleCheckBtn (bool _IsVisibled); + virtual bool GetVisibleCheckBtn (); + virtual void SetItemMinWidth (UINT _ItemMinWidth); + virtual UINT GetItemMinWidth (); + virtual void SetItemTextColor (DWORD _dwItemTextColor); + virtual void SetItemHotTextColor (DWORD _dwItemHotTextColor); + virtual void SetSelItemTextColor (DWORD _dwSelItemTextColor); + virtual void SetSelItemHotTextColor (DWORD _dwSelHotItemTextColor); + + virtual void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + private: + UINT m_uItemMinWidth; + bool m_bVisibleFolderBtn; + bool m_bVisibleCheckBtn; + }; +} + + +#endif // UITreeView_h__ diff --git a/DuiLib/Control/UIWebBrowser.cpp b/DuiLib/Control/UIWebBrowser.cpp new file mode 100644 index 0000000..695fd80 --- /dev/null +++ b/DuiLib/Control/UIWebBrowser.cpp @@ -0,0 +1,632 @@ +#include "StdAfx.h" +#include "UIWebBrowser.h" +#include +#include +#include "../Utils/downloadmgr.h" +#include + +namespace DuiLib { + //////////////////////////////////////////////////////////////////////// + // + IMPLEMENT_DUICONTROL (CWebBrowserUI) + + CWebBrowserUI::CWebBrowserUI () { + m_clsid = CLSID_WebBrowser; + m_sHomePage.clear (); + } + + bool CWebBrowserUI::DoCreateControl () { + if (!CActiveXUI::DoCreateControl ()) + return false; + GetManager ()->AddTranslateAccelerator (this); + GetControl (IID_IWebBrowser2, (LPVOID*) &m_pWebBrowser2); + if (m_bAutoNavi && !m_sHomePage.empty ()) { + this->Navigate2 (m_sHomePage); + } + RegisterEventHandler (TRUE); + return true; + } + + void CWebBrowserUI::ReleaseControl () { + m_bCreated = false; + GetManager ()->RemoveTranslateAccelerator (this); + RegisterEventHandler (FALSE); + } + + CWebBrowserUI::~CWebBrowserUI () { + ReleaseControl (); + } + + STDMETHODIMP CWebBrowserUI::GetTypeInfoCount (UINT *iTInfo) { + *iTInfo = 0; + return E_NOTIMPL; + } + + STDMETHODIMP CWebBrowserUI::GetTypeInfo (UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { + return E_NOTIMPL; + } + + STDMETHODIMP CWebBrowserUI::GetIDsOfNames (REFIID riid, OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { + return E_NOTIMPL; + } + + STDMETHODIMP CWebBrowserUI::Invoke (DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) { + if ((riid != IID_NULL)) + return E_INVALIDARG; + + switch (dispIdMember) { + case DISPID_BEFORENAVIGATE2: + BeforeNavigate2 ( + pDispParams->rgvarg[6].pdispVal, + pDispParams->rgvarg[5].pvarVal, + pDispParams->rgvarg[4].pvarVal, + pDispParams->rgvarg[3].pvarVal, + pDispParams->rgvarg[2].pvarVal, + pDispParams->rgvarg[1].pvarVal, + pDispParams->rgvarg[0].pboolVal); + break; + case DISPID_COMMANDSTATECHANGE: + CommandStateChange ( + pDispParams->rgvarg[1].lVal, + pDispParams->rgvarg[0].boolVal); + break; + case DISPID_NAVIGATECOMPLETE2: + NavigateComplete2 ( + pDispParams->rgvarg[1].pdispVal, + pDispParams->rgvarg[0].pvarVal); + break; + case DISPID_NAVIGATEERROR: + NavigateError ( + pDispParams->rgvarg[4].pdispVal, + pDispParams->rgvarg[3].pvarVal, + pDispParams->rgvarg[2].pvarVal, + pDispParams->rgvarg[1].pvarVal, + pDispParams->rgvarg[0].pboolVal); + break; + case DISPID_STATUSTEXTCHANGE: + break; + // case DISPID_NEWWINDOW2: + // break; + case DISPID_NEWWINDOW3: + NewWindow3 ( + pDispParams->rgvarg[4].ppdispVal, + pDispParams->rgvarg[3].pboolVal, + pDispParams->rgvarg[2].uintVal, + pDispParams->rgvarg[1].bstrVal, + pDispParams->rgvarg[0].bstrVal); + break; + case DISPID_TITLECHANGE: + { + TitleChange (pDispParams->rgvarg[0].bstrVal); + break; + } + case DISPID_DOCUMENTCOMPLETE: + { + DocumentComplete ( + pDispParams->rgvarg[1].pdispVal, + pDispParams->rgvarg[0].pvarVal); + + break; + } + default: + return DISP_E_MEMBERNOTFOUND; + } + return S_OK; + } + + STDMETHODIMP CWebBrowserUI::QueryInterface (REFIID riid, LPVOID *ppvObject) { + *ppvObject = nullptr; + + if (riid == IID_IDocHostUIHandler) + *ppvObject = static_cast(this); + else if (riid == IID_IDispatch) + *ppvObject = static_cast(this); + else if (riid == IID_IServiceProvider) + *ppvObject = static_cast(this); + else if (riid == IID_IInternetSecurityManager) { + *ppvObject = static_cast(this); + } else if (riid == IID_IOleCommandTarget) + *ppvObject = static_cast(this); + + if (*ppvObject) + AddRef (); + return (!*ppvObject) ? E_NOINTERFACE : S_OK; + } + + STDMETHODIMP_ (ULONG) CWebBrowserUI::AddRef () { + InterlockedIncrement (&m_dwRef); + return m_dwRef; + } + + STDMETHODIMP_ (ULONG) CWebBrowserUI::Release () { + ULONG ulRefCount = InterlockedDecrement (&m_dwRef); + return ulRefCount; + } + + void CWebBrowserUI::Navigate2 (string_view_t lpszUrl) { + if (lpszUrl.empty ()) + return; + + if (m_pWebBrowser2) { + CDuiVariant url; + url.vt = VT_BSTR; + url.bstrVal = _bstr_t (FawTools::get_utf16 (lpszUrl).c_str ()); + HRESULT hr = m_pWebBrowser2->Navigate2 (&url, nullptr, nullptr, nullptr, nullptr); + } + } + + void CWebBrowserUI::Refresh () { + if (m_pWebBrowser2) { + m_pWebBrowser2->Refresh (); + } + } + void CWebBrowserUI::GoBack () { + if (m_pWebBrowser2) { + m_pWebBrowser2->GoBack (); + } + } + void CWebBrowserUI::GoForward () { + if (m_pWebBrowser2) { + m_pWebBrowser2->GoForward (); + } + } + /// DWebBrowserEvents2 + void CWebBrowserUI::BeforeNavigate2 (IDispatch *pDisp, VARIANT *&url, VARIANT *&Flags, VARIANT *&TargetFrameName, VARIANT *&PostData, VARIANT *&Headers, VARIANT_BOOL *&Cancel) { + if (m_pWebBrowserEventHandler) { + m_pWebBrowserEventHandler->BeforeNavigate2 (this, pDisp, url, Flags, TargetFrameName, PostData, Headers, Cancel); + } + } + + void CWebBrowserUI::NavigateError (IDispatch *pDisp, VARIANT * &url, VARIANT *&TargetFrameName, VARIANT *&StatusCode, VARIANT_BOOL *&Cancel) { + if (m_pWebBrowserEventHandler) { + m_pWebBrowserEventHandler->NavigateError (this, pDisp, url, TargetFrameName, StatusCode, Cancel); + } + } + + void CWebBrowserUI::NavigateComplete2 (IDispatch *pDisp, VARIANT *&url) { + CComPtr spDoc; + m_pWebBrowser2->get_Document (&spDoc); + + if (spDoc) { + CComQIPtr spCustomDoc (spDoc); + if (spCustomDoc) + spCustomDoc->SetUIHandler (this); + } + + if (m_pWebBrowserEventHandler) { + m_pWebBrowserEventHandler->NavigateComplete2 (this, pDisp, url); + } + } + + void CWebBrowserUI::ProgressChange (LONG nProgress, LONG nProgressMax) { + if (m_pWebBrowserEventHandler) { + m_pWebBrowserEventHandler->ProgressChange (this, nProgress, nProgressMax); + } + } + + void CWebBrowserUI::NewWindow3 (IDispatch **pDisp, VARIANT_BOOL *&Cancel, DWORD dwFlags, BSTR bstrUrlContext, BSTR bstrUrl) { + if (m_pWebBrowserEventHandler) { + m_pWebBrowserEventHandler->NewWindow3 (this, pDisp, Cancel, dwFlags, bstrUrlContext, bstrUrl); + } + } + void CWebBrowserUI::CommandStateChange (long Command, VARIANT_BOOL Enable) { + if (m_pWebBrowserEventHandler) { + m_pWebBrowserEventHandler->CommandStateChange (this, Command, Enable); + } + } + + void CWebBrowserUI::TitleChange (BSTR bstrTitle) { + if (m_pWebBrowserEventHandler) { + m_pWebBrowserEventHandler->TitleChange (this, bstrTitle); + } + } + + void CWebBrowserUI::DocumentComplete (IDispatch *pDisp, VARIANT *&url) { + if (m_pWebBrowserEventHandler) { + m_pWebBrowserEventHandler->DocumentComplete (this, pDisp, url); + } + } + + // IDownloadManager + STDMETHODIMP CWebBrowserUI::Download ( /* [in] */ IMoniker *pmk, /* [in] */ IBindCtx *pbc, /* [in] */ DWORD dwBindVerb, /* [in] */ LONG grfBINDF, /* [in] */ BINDINFO *pBindInfo, /* [in] */ LPCOLESTR pszHeaders, /* [in] */ LPCOLESTR pszRedir, /* [in] */ UINT uiCP) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->Download (this, pmk, pbc, dwBindVerb, grfBINDF, pBindInfo, pszHeaders, pszRedir, uiCP); + } + return S_OK; + } + + // IDocHostUIHandler + STDMETHODIMP CWebBrowserUI::ShowContextMenu (DWORD dwID, POINT* pptPosition, IUnknown* pCommandTarget, IDispatch* pDispatchObjectHit) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->ShowContextMenu (this, dwID, pptPosition, pCommandTarget, pDispatchObjectHit); + } + return S_FALSE; + } + + STDMETHODIMP CWebBrowserUI::GetHostInfo (DOCHOSTUIINFO* pInfo) { + if (pInfo) { + pInfo->dwFlags |= DOCHOSTUIFLAG_NO3DBORDER | DOCHOSTUIFLAG_NO3DOUTERBORDER; + } + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->GetHostInfo (this, pInfo); + } + return S_OK; + } + + STDMETHODIMP CWebBrowserUI::ShowUI (DWORD dwID, IOleInPlaceActiveObject* pActiveObject, IOleCommandTarget* pCommandTarget, IOleInPlaceFrame* pFrame, IOleInPlaceUIWindow* pDoc) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->ShowUI (this, dwID, pActiveObject, pCommandTarget, pFrame, pDoc); + } + return S_OK; + } + + STDMETHODIMP CWebBrowserUI::HideUI () { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->HideUI (this); + } + return S_OK; + } + + STDMETHODIMP CWebBrowserUI::UpdateUI () { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->UpdateUI (this); + } + return S_OK; + } + + STDMETHODIMP CWebBrowserUI::EnableModeless (BOOL fEnable) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->EnableModeless (this, fEnable); + } + return E_NOTIMPL; + } + + STDMETHODIMP CWebBrowserUI::OnDocWindowActivate (BOOL fActivate) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->OnDocWindowActivate (this, fActivate); + } + return E_NOTIMPL; + } + + STDMETHODIMP CWebBrowserUI::OnFrameWindowActivate (BOOL fActivate) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->OnFrameWindowActivate (this, fActivate); + } + return E_NOTIMPL; + } + + STDMETHODIMP CWebBrowserUI::ResizeBorder (LPCRECT prcBorder, IOleInPlaceUIWindow* pUIWindow, BOOL fFrameWindow) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->ResizeBorder (this, prcBorder, pUIWindow, fFrameWindow); + } + return E_NOTIMPL; + } + + STDMETHODIMP CWebBrowserUI::TranslateAccelerator (LPMSG lpMsg, const GUID* pguidCmdGroup, DWORD nCmdID) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->TranslateAccelerator (this, lpMsg, pguidCmdGroup, nCmdID); + } + return S_FALSE; + } + + LRESULT CWebBrowserUI::TranslateAccelerator (MSG *pMsg) { + if (pMsg->message < WM_KEYFIRST || pMsg->message > WM_KEYLAST) + return S_FALSE; + + if (!m_pWebBrowser2) + return E_NOTIMPL; + + // ǰWebڲǽ,ټ + BOOL bIsChild = FALSE; + HWND hTempWnd = NULL; + HWND hWndFocus = ::GetFocus (); + + hTempWnd = hWndFocus; + while (hTempWnd) { + if (hTempWnd == m_hwndHost) { + bIsChild = TRUE; + break; + } + hTempWnd = ::GetParent (hTempWnd); + } + if (!bIsChild) + return S_FALSE; + + IOleInPlaceActiveObject *pObj; + if (FAILED (m_pWebBrowser2->QueryInterface (IID_IOleInPlaceActiveObject, (LPVOID *) &pObj))) + return S_FALSE; + + HRESULT hResult = pObj->TranslateAccelerator (pMsg); + pObj->Release (); + return hResult; + } + + STDMETHODIMP CWebBrowserUI::GetOptionKeyPath (LPOLESTR* pchKey, DWORD dwReserved) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->GetOptionKeyPath (this, pchKey, dwReserved); + } + return E_NOTIMPL; + } + + STDMETHODIMP CWebBrowserUI::GetDropTarget (IDropTarget* pDropTarget, IDropTarget** ppDropTarget) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->GetDropTarget (this, pDropTarget, ppDropTarget); + } + return S_FALSE; // ʹϵͳק + } + + STDMETHODIMP CWebBrowserUI::GetExternal (IDispatch** ppDispatch) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->GetExternal (this, ppDispatch); + } + return S_FALSE; + } + + STDMETHODIMP CWebBrowserUI::TranslateUrl (DWORD dwTranslate, OLECHAR* pchURLIn, OLECHAR** ppchURLOut) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->TranslateUrl (this, dwTranslate, pchURLIn, ppchURLOut); + } else { + *ppchURLOut = 0; + return E_NOTIMPL; + } + } + + STDMETHODIMP CWebBrowserUI::FilterDataObject (IDataObject* pDO, IDataObject** ppDORet) { + if (m_pWebBrowserEventHandler) { + return m_pWebBrowserEventHandler->FilterDataObject (this, pDO, ppDORet); + } else { + *ppDORet = 0; + return E_NOTIMPL; + } + } + + void CWebBrowserUI::SetWebBrowserEventHandler (CWebBrowserEventHandler* pEventHandler) { + if (pEventHandler && m_pWebBrowserEventHandler != pEventHandler) + m_pWebBrowserEventHandler = pEventHandler; + } + + void CWebBrowserUI::Refresh2 (int Level) { + CDuiVariant vLevel; + vLevel.vt = VT_I4; + vLevel.intVal = Level; + m_pWebBrowser2->Refresh2 (&vLevel); + } + + void CWebBrowserUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("homepage")) { + m_sHomePage = pstrValue; + } else if (pstrName == _T ("autonavi")) { + m_bAutoNavi = (FawTools::parse_bool (pstrValue)); + } else + CActiveXUI::SetAttribute (pstrName, pstrValue); + } + + void CWebBrowserUI::NavigateHomePage () { + if (!m_sHomePage.empty ()) + this->NavigateUrl (m_sHomePage); + } + + void CWebBrowserUI::NavigateUrl (string_view_t lpszUrl) { + if (m_pWebBrowser2 && !lpszUrl.empty ()) { + m_pWebBrowser2->Navigate ((BSTR) SysAllocString (_bstr_t (FawTools::get_utf16 (lpszUrl).c_str ())), nullptr, nullptr, nullptr, nullptr); + } + } + + string_view_t CWebBrowserUI::GetClass () const { + return _T ("WebBrowserUI"); + } + + LPVOID CWebBrowserUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_WEBBROWSER) return static_cast(this); + return CActiveXUI::GetInterface (pstrName); + } + + void CWebBrowserUI::SetHomePage (string_view_t lpszUrl) { + m_sHomePage.Format (_T ("%s"), lpszUrl.data ()); + } + + string_view_t CWebBrowserUI::GetHomePage () { + return m_sHomePage; + } + + void CWebBrowserUI::SetAutoNavigation (bool bAuto /*= TRUE*/) { + if (m_bAutoNavi == bAuto) return; + + m_bAutoNavi = bAuto; + } + + bool CWebBrowserUI::IsAutoNavigation () { + return m_bAutoNavi; + } + + STDMETHODIMP CWebBrowserUI::QueryService (REFGUID guidService, REFIID riid, void** ppvObject) { + HRESULT hr = E_NOINTERFACE; + *ppvObject = nullptr; + + if (guidService == SID_SDownloadManager && riid == IID_IDownloadManager) { + *ppvObject = this; + return S_OK; + } + if (guidService == SID_SInternetSecurityManager && riid == IID_IInternetSecurityManager) { + *ppvObject = this; + return S_OK; + } + return hr; + } + + HRESULT CWebBrowserUI::RegisterEventHandler (BOOL inAdvise) { + CComPtr pWebBrowser; + CComPtr pCPC; + CComPtr pCP; + HRESULT hr = GetControl (IID_IWebBrowser2, (void**) &pWebBrowser); + if (FAILED (hr)) + return hr; + hr = pWebBrowser->QueryInterface (IID_IConnectionPointContainer, (void **) &pCPC); + if (FAILED (hr)) + return hr; + hr = pCPC->FindConnectionPoint (DIID_DWebBrowserEvents2, &pCP); + if (FAILED (hr)) + return hr; + + if (inAdvise) { + hr = pCP->Advise ((IDispatch*) this, &m_dwCookie); + } else { + hr = pCP->Unadvise (m_dwCookie); + } + return hr; + } + + DISPID CWebBrowserUI::FindId (IDispatch *pObj, LPOLESTR pName) { + DISPID id = 0; + if (FAILED (pObj->GetIDsOfNames (IID_NULL, &pName, 1, LOCALE_SYSTEM_DEFAULT, &id))) id = -1; + return id; + } + + HRESULT CWebBrowserUI::InvokeMethod (IDispatch *pObj, LPOLESTR pMehtod, VARIANT *pVarResult, VARIANT *ps, int cArgs) { + DISPID dispid = FindId (pObj, pMehtod); + if (dispid == -1) return E_FAIL; + + DISPPARAMS dispparams; + dispparams.cArgs = cArgs; + dispparams.rgvarg = ps; + dispparams.cNamedArgs = 0; + dispparams.rgdispidNamedArgs = nullptr; + + return pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, pVarResult, nullptr, nullptr); + } + + HRESULT CWebBrowserUI::GetProperty (IDispatch *pObj, LPOLESTR pName, VARIANT *pValue) { + DISPID dispid = FindId (pObj, pName); + if (dispid == -1) return E_FAIL; + + DISPPARAMS ps; + ps.cArgs = 0; + ps.rgvarg = nullptr; + ps.cNamedArgs = 0; + ps.rgdispidNamedArgs = nullptr; + + return pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &ps, pValue, nullptr, nullptr); + } + + HRESULT CWebBrowserUI::SetProperty (IDispatch *pObj, LPOLESTR pName, VARIANT *pValue) { + DISPID dispid = FindId (pObj, pName); + if (dispid == -1) return E_FAIL; + + DISPPARAMS ps; + ps.cArgs = 1; + ps.rgvarg = pValue; + ps.cNamedArgs = 0; + ps.rgdispidNamedArgs = nullptr; + + return pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &ps, nullptr, nullptr, nullptr); + } + + IDispatch* CWebBrowserUI::GetHtmlWindow () { + IDispatch* pDp = nullptr; + HRESULT hr = 0; + if (m_pWebBrowser2) + hr = m_pWebBrowser2->get_Document (&pDp); + + if (FAILED (hr)) + return nullptr; + + CComQIPtr pHtmlDoc2 = pDp; + + if (!pHtmlDoc2) + return nullptr; + + hr = pHtmlDoc2->get_parentWindow (&_pHtmlWnd2); + + if (FAILED (hr)) + return nullptr; + + IDispatch *pHtmlWindown = nullptr; + hr = _pHtmlWnd2->QueryInterface (IID_IDispatch, (void**) &pHtmlWindown); + if (FAILED (hr)) + return nullptr; + + return pHtmlWindown; + } + + IWebBrowser2* CWebBrowserUI::GetWebBrowser2 (void) { + return m_pWebBrowser2; + } + + HRESULT STDMETHODCALLTYPE CWebBrowserUI::QueryStatus (__RPC__in_opt const GUID *pguidCmdGroup, ULONG cCmds, __RPC__inout_ecount_full (cCmds) OLECMD prgCmds[], __RPC__inout_opt OLECMDTEXT *pCmdText) { + HRESULT hr = OLECMDERR_E_NOTSUPPORTED; + return hr; + } + + HRESULT STDMETHODCALLTYPE CWebBrowserUI::Exec (__RPC__in_opt const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, __RPC__in_opt VARIANT *pvaIn, __RPC__inout_opt VARIANT *pvaOut) { + HRESULT hr = S_OK; + + if (pguidCmdGroup && IsEqualGUID (*pguidCmdGroup, CGID_DocHostCommandHandler)) { + + switch (nCmdID) { + + case OLECMDID_SHOWSCRIPTERROR: + { + IHTMLDocument2* pDoc = nullptr; + IHTMLWindow2* pWindow = nullptr; + IHTMLEventObj* pEventObj = nullptr; + BSTR rgwszNames[5] = + { + SysAllocString (L"errorLine"), + SysAllocString (L"errorCharacter"), + SysAllocString (L"errorCode"), + SysAllocString (L"errorMessage"), + SysAllocString (L"errorUrl") + }; + DISPID rgDispIDs[5]; + VARIANT rgvaEventInfo[5]; + DISPPARAMS params; + BOOL fContinueRunningScripts = true; + int i; + + params.cArgs = 0; + params.cNamedArgs = 0; + + // Get the document that is currently being viewed. + hr = pvaIn->punkVal->QueryInterface (IID_IHTMLDocument2, (void **) &pDoc); + // Get document.parentWindow. + hr = pDoc->get_parentWindow (&pWindow); + pDoc->Release (); + // Get the window.event object. + hr = pWindow->get_event (&pEventObj); + // Get the error info from the window.event object. + for (i = 0; i < 5; i++) { + // Get the property's dispID. + hr = pEventObj->GetIDsOfNames (IID_NULL, &rgwszNames[i], 1, + LOCALE_SYSTEM_DEFAULT, &rgDispIDs[i]); + // Get the value of the property. + hr = pEventObj->Invoke (rgDispIDs[i], IID_NULL, + LOCALE_SYSTEM_DEFAULT, + DISPATCH_PROPERTYGET, ¶ms, &rgvaEventInfo[i], + nullptr, nullptr); + SysFreeString (rgwszNames[i]); + } + + // At this point, you would normally alert the user with + // the information about the error, which is now contained + // in rgvaEventInfo[]. Or, you could just exit silently. + + (*pvaOut).vt = VT_BOOL; + if (fContinueRunningScripts) { + // Continue running scripts on the page. + (*pvaOut).boolVal = VARIANT_TRUE; + } else { + // Stop running scripts on the page. + (*pvaOut).boolVal = VARIANT_FALSE; + } + break; + } + default: + hr = OLECMDERR_E_NOTSUPPORTED; + break; + } + } else { + hr = OLECMDERR_E_UNKNOWNGROUP; + } + return (hr); + } +} \ No newline at end of file diff --git a/DuiLib/Control/UIWebBrowser.h b/DuiLib/Control/UIWebBrowser.h new file mode 100644 index 0000000..0739053 --- /dev/null +++ b/DuiLib/Control/UIWebBrowser.h @@ -0,0 +1,185 @@ +#ifndef __UIWEBBROWSER_H__ +#define __UIWEBBROWSER_H__ + +#pragma once + +#include +#include "../Utils/WebBrowserEventHandler.h" +#include + +namespace DuiLib { + class UILIB_API CWebBrowserUI + : public CActiveXUI + , public IDocHostUIHandler + , public IServiceProvider + , public IOleCommandTarget + , public IDispatch + , public ITranslateAccelerator + , public IInternetSecurityManager { + DECLARE_DUICONTROL (CWebBrowserUI) + public: + /// 캯 + CWebBrowserUI (); + virtual ~CWebBrowserUI (); + + void SetHomePage (string_view_t lpszUrl); + string_view_t GetHomePage (); + + void SetAutoNavigation (bool bAuto = TRUE); + bool IsAutoNavigation (); + + void SetWebBrowserEventHandler (CWebBrowserEventHandler* pEventHandler); + void Navigate2 (string_view_t lpszUrl); + void Refresh (); + void Refresh2 (int Level); + void GoBack (); + void GoForward (); + void NavigateHomePage (); + void NavigateUrl (string_view_t lpszUrl); + virtual bool DoCreateControl (); + IWebBrowser2* GetWebBrowser2 (void); + IDispatch *GetHtmlWindow (); + static DISPID FindId (IDispatch *pObj, LPOLESTR pName); + static HRESULT InvokeMethod (IDispatch *pObj, LPOLESTR pMehtod, VARIANT *pVarResult, VARIANT *ps, int cArgs); + static HRESULT GetProperty (IDispatch *pObj, LPOLESTR pName, VARIANT *pValue); + static HRESULT SetProperty (IDispatch *pObj, LPOLESTR pName, VARIANT *pValue); + + protected: + IWebBrowser2 *m_pWebBrowser2 = nullptr; //ָ + IHTMLWindow2 *_pHtmlWnd2 = nullptr; + LONG m_dwRef = 0; + DWORD m_dwCookie = 0; + virtual void ReleaseControl (); + HRESULT RegisterEventHandler (BOOL inAdvise); + virtual void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + CDuiString m_sHomePage; // Ĭҳ + bool m_bAutoNavi = false; // ǷʱĬҳ + CWebBrowserEventHandler *m_pWebBrowserEventHandler = nullptr; //¼ + + // DWebBrowserEvents2 + void BeforeNavigate2 (IDispatch *pDisp, VARIANT *&url, VARIANT *&Flags, VARIANT *&TargetFrameName, VARIANT *&PostData, VARIANT *&Headers, VARIANT_BOOL *&Cancel); + void NavigateError (IDispatch *pDisp, VARIANT * &url, VARIANT *&TargetFrameName, VARIANT *&StatusCode, VARIANT_BOOL *&Cancel); + void NavigateComplete2 (IDispatch *pDisp, VARIANT *&url); + void ProgressChange (LONG nProgress, LONG nProgressMax); + void NewWindow3 (IDispatch **pDisp, VARIANT_BOOL *&Cancel, DWORD dwFlags, BSTR bstrUrlContext, BSTR bstrUrl); + void CommandStateChange (long Command, VARIANT_BOOL Enable); + void TitleChange (BSTR bstrTitle); + void DocumentComplete (IDispatch *pDisp, VARIANT *&url); + + public: + virtual string_view_t GetClass () const; + virtual LPVOID GetInterface (string_view_t pstrName); + + // IUnknown + STDMETHOD_ (ULONG, AddRef)(); + STDMETHOD_ (ULONG, Release)(); + STDMETHOD (QueryInterface)(REFIID riid, LPVOID *ppvObject); + + // IDispatch + virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount (__RPC__out UINT *pctinfo); + virtual HRESULT STDMETHODCALLTYPE GetTypeInfo (UINT iTInfo, LCID lcid, __RPC__deref_out_opt ITypeInfo **ppTInfo); + virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames (__RPC__in REFIID riid, __RPC__in_ecount_full (cNames) LPOLESTR *rgszNames, UINT cNames, LCID lcid, __RPC__out_ecount_full (cNames) DISPID *rgDispId); + virtual HRESULT STDMETHODCALLTYPE Invoke (DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr); + + // IDocHostUIHandler + STDMETHOD (ShowContextMenu)(DWORD dwID, POINT* pptPosition, IUnknown* pCommandTarget, IDispatch* pDispatchObjectHit); + STDMETHOD (GetHostInfo)(DOCHOSTUIINFO* pInfo); + STDMETHOD (ShowUI)(DWORD dwID, IOleInPlaceActiveObject* pActiveObject, IOleCommandTarget* pCommandTarget, IOleInPlaceFrame* pFrame, IOleInPlaceUIWindow* pDoc); + STDMETHOD (HideUI)(); + STDMETHOD (UpdateUI)(); + STDMETHOD (EnableModeless)(BOOL fEnable); + STDMETHOD (OnDocWindowActivate)(BOOL fActivate); + STDMETHOD (OnFrameWindowActivate)(BOOL fActivate); + STDMETHOD (ResizeBorder)(LPCRECT prcBorder, IOleInPlaceUIWindow* pUIWindow, BOOL fFrameWindow); + STDMETHOD (TranslateAccelerator)(LPMSG lpMsg, const GUID* pguidCmdGroup, DWORD nCmdID); //Ϣ + STDMETHOD (GetOptionKeyPath)(LPOLESTR* pchKey, DWORD dwReserved); + STDMETHOD (GetDropTarget)(IDropTarget* pDropTarget, IDropTarget** ppDropTarget); + STDMETHOD (GetExternal)(IDispatch** ppDispatch); + STDMETHOD (TranslateUrl)(DWORD dwTranslate, OLECHAR* pchURLIn, OLECHAR** ppchURLOut); + STDMETHOD (FilterDataObject)(IDataObject* pDO, IDataObject** ppDORet); + + // IServiceProvider + STDMETHOD (QueryService)(REFGUID guidService, REFIID riid, void** ppvObject); + + // IOleCommandTarget + virtual HRESULT STDMETHODCALLTYPE QueryStatus (__RPC__in_opt const GUID *pguidCmdGroup, ULONG cCmds, __RPC__inout_ecount_full (cCmds) OLECMD prgCmds[], __RPC__inout_opt OLECMDTEXT *pCmdText); + virtual HRESULT STDMETHODCALLTYPE Exec (__RPC__in_opt const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, __RPC__in_opt VARIANT *pvaIn, __RPC__inout_opt VARIANT *pvaOut); + + // IDownloadManager + STDMETHOD (Download)( + /* [in] */ IMoniker *pmk, + /* [in] */ IBindCtx *pbc, + /* [in] */ DWORD dwBindVerb, + /* [in] */ LONG grfBINDF, + /* [in] */ BINDINFO *pBindInfo, + /* [in] */ LPCOLESTR pszHeaders, + /* [in] */ LPCOLESTR pszRedir, + /* [in] */ UINT uiCP); + + virtual HRESULT STDMETHODCALLTYPE SetSecuritySite ( + /* [unique][in] */ __RPC__in_opt IInternetSecurityMgrSite *pSite) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE GetSecuritySite ( + /* [out] */ __RPC__deref_out_opt IInternetSecurityMgrSite **ppSite) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE MapUrlToZone ( + /* [in] */ __RPC__in LPCWSTR pwszUrl, + /* [out] */ __RPC__out DWORD *pdwZone, + /* [in] */ DWORD dwFlags) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE GetSecurityId ( + /* [in] */ __RPC__in LPCWSTR pwszUrl, + /* [size_is][out] */ __RPC__out_ecount_full (*pcbSecurityId) BYTE *pbSecurityId, + /* [out][in] */ __RPC__inout DWORD *pcbSecurityId, + /* [in] */ DWORD_PTR dwReserved) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE ProcessUrlAction ( + /* [in] */ __RPC__in LPCWSTR pwszUrl, + /* [in] */ DWORD dwAction, + /* [size_is][out] */ __RPC__out_ecount_full (cbPolicy) BYTE *pPolicy, + /* [in] */ DWORD cbPolicy, + /* [unique][in] */ __RPC__in_opt BYTE *pContext, + /* [in] */ DWORD cbContext, + /* [in] */ DWORD dwFlags, + /* [in] */ DWORD dwReserved) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE QueryCustomPolicy ( + /* [in] */ __RPC__in LPCWSTR pwszUrl, + /* [in] */ __RPC__in REFGUID guidKey, + /* [size_is][size_is][out] */ __RPC__deref_out_ecount_full_opt (*pcbPolicy) BYTE **ppPolicy, + /* [out] */ __RPC__out DWORD *pcbPolicy, + /* [in] */ __RPC__in BYTE *pContext, + /* [in] */ DWORD cbContext, + /* [in] */ DWORD dwReserved) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE SetZoneMapping ( + /* [in] */ DWORD dwZone, + /* [in] */ __RPC__in LPCWSTR lpszPattern, + /* [in] */ DWORD dwFlags) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE GetZoneMappings ( + /* [in] */ DWORD dwZone, + /* [out] */ __RPC__deref_out_opt IEnumString **ppenumString, + /* [in] */ DWORD dwFlags) { + return S_OK; + } + // ITranslateAccelerator + // DuilibϢַWebBrowser + virtual LRESULT TranslateAccelerator (MSG *pMsg); + }; +} // namespace DuiLib +#endif // __UIWEBBROWSER_H__ \ No newline at end of file diff --git a/DuiLib/Core/ControlFactory.cpp b/DuiLib/Core/ControlFactory.cpp new file mode 100644 index 0000000..63578df --- /dev/null +++ b/DuiLib/Core/ControlFactory.cpp @@ -0,0 +1,82 @@ +#include "StdAfx.h" +#include "ControlFactory.h" + +namespace DuiLib { + CControlFactory::CControlFactory () { + INNER_REGISTER_DUICONTROL (CControlUI); + INNER_REGISTER_DUICONTROL (CContainerUI); + INNER_REGISTER_DUICONTROL (CButtonUI); + INNER_REGISTER_DUICONTROL (CComboUI); + INNER_REGISTER_DUICONTROL (CComboBoxUI); + INNER_REGISTER_DUICONTROL (CDateTimeUI); + INNER_REGISTER_DUICONTROL (CEditUI); + INNER_REGISTER_DUICONTROL (CActiveXUI); + INNER_REGISTER_DUICONTROL (CFlashUI); + INNER_REGISTER_DUICONTROL (CGifAnimUI); +#ifdef USE_XIMAGE_EFFECT + INNER_REGISTER_DUICONTROL (CGifAnimExUI); +#endif + INNER_REGISTER_DUICONTROL (CGroupBoxUI); + INNER_REGISTER_DUICONTROL (CIPAddressUI); + INNER_REGISTER_DUICONTROL (CIPAddressExUI); + INNER_REGISTER_DUICONTROL (CLabelUI); + INNER_REGISTER_DUICONTROL (CListUI); + INNER_REGISTER_DUICONTROL (CListHeaderUI); + INNER_REGISTER_DUICONTROL (CListHeaderItemUI); + INNER_REGISTER_DUICONTROL (CListLabelElementUI); + INNER_REGISTER_DUICONTROL (CListTextElementUI); + INNER_REGISTER_DUICONTROL (CListContainerElementUI); + INNER_REGISTER_DUICONTROL (CMenuUI); + INNER_REGISTER_DUICONTROL (CMenuElementUI); + INNER_REGISTER_DUICONTROL (COptionUI); + INNER_REGISTER_DUICONTROL (CCheckBoxUI); + INNER_REGISTER_DUICONTROL (CProgressUI); + INNER_REGISTER_DUICONTROL (CRichEditUI); + INNER_REGISTER_DUICONTROL (CScrollBarUI); + INNER_REGISTER_DUICONTROL (CSliderUI); + INNER_REGISTER_DUICONTROL (CTextUI); + INNER_REGISTER_DUICONTROL (CTreeNodeUI); + INNER_REGISTER_DUICONTROL (CTreeViewUI); + INNER_REGISTER_DUICONTROL (CWebBrowserUI); + INNER_REGISTER_DUICONTROL (CAnimationTabLayoutUI); + INNER_REGISTER_DUICONTROL (CChildLayoutUI); + INNER_REGISTER_DUICONTROL (CHorizontalLayoutUI); + INNER_REGISTER_DUICONTROL (CTabLayoutUI); + INNER_REGISTER_DUICONTROL (CTileLayoutUI); + INNER_REGISTER_DUICONTROL (CVerticalLayoutUI); + INNER_REGISTER_DUICONTROL (CRollTextUI); + INNER_REGISTER_DUICONTROL (CColorPaletteUI); + INNER_REGISTER_DUICONTROL (CListExUI); + INNER_REGISTER_DUICONTROL (CListContainerHeaderItemUI); + INNER_REGISTER_DUICONTROL (CListTextExtElementUI); + INNER_REGISTER_DUICONTROL (CHotKeyUI); + INNER_REGISTER_DUICONTROL (CFadeButtonUI); + INNER_REGISTER_DUICONTROL (CRingUI); + } + + CControlFactory::~CControlFactory () {} + + CControlUI* CControlFactory::CreateControl (CDuiString strClassName) { + strClassName.MakeLower (); + MAP_DUI_CTRATECLASS::iterator iter = m_mapControl.find (strClassName); + if (iter == m_mapControl.end ()) { + return nullptr; + } else { + return (CControlUI*) (iter->second ()); + } + } + + void CControlFactory::RegistControl (CDuiString strClassName, CreateClass pFunc) { + strClassName.MakeLower (); + m_mapControl.insert (MAP_DUI_CTRATECLASS::value_type (strClassName, pFunc)); + } + + CControlFactory* CControlFactory::GetInstance () { + static CControlFactory* pInstance = new CControlFactory; + return pInstance; + } + + void CControlFactory::Release () { + delete this; + } +} \ No newline at end of file diff --git a/DuiLib/Core/ControlFactory.h b/DuiLib/Core/ControlFactory.h new file mode 100644 index 0000000..4e02956 --- /dev/null +++ b/DuiLib/Core/ControlFactory.h @@ -0,0 +1,36 @@ +#pragma once +#include +namespace DuiLib { + typedef CControlUI* (*CreateClass)(); + typedef std::map MAP_DUI_CTRATECLASS; + + class UILIB_API CControlFactory { + public: + CControlUI* CreateControl (CDuiString strClassName); + void RegistControl (CDuiString strClassName, CreateClass pFunc); + + static CControlFactory* GetInstance (); + void Release (); + + private: + CControlFactory (); + virtual ~CControlFactory (); + + private: + MAP_DUI_CTRATECLASS m_mapControl; + }; + +#define DECLARE_DUICONTROL(class_name)\ +public:\ + static CControlUI* CreateControl(); + +#define IMPLEMENT_DUICONTROL(class_name)\ + CControlUI* class_name::CreateControl()\ + { return new class_name; } + +#define REGIST_DUICONTROL(class_name)\ + CControlFactory::GetInstance()->RegistControl(_T(#class_name), (CreateClass)class_name::CreateControl); + +#define INNER_REGISTER_DUICONTROL(class_name)\ + RegistControl(_T(#class_name), (CreateClass)class_name::CreateControl); +} \ No newline at end of file diff --git a/DuiLib/Core/StdAfx.h b/DuiLib/Core/StdAfx.h new file mode 100644 index 0000000..c07aaba --- /dev/null +++ b/DuiLib/Core/StdAfx.h @@ -0,0 +1 @@ +#include "../StdAfx.h" \ No newline at end of file diff --git a/DuiLib/Core/UIBase.cpp b/DuiLib/Core/UIBase.cpp new file mode 100644 index 0000000..3080ad2 --- /dev/null +++ b/DuiLib/Core/UIBase.cpp @@ -0,0 +1,437 @@ +#include "StdAfx.h" + +#ifdef _DEBUG +#include +#pragma comment(lib, "shlwapi.lib") +#endif + +namespace DuiLib { + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + void UILIB_API DUI__Trace (LPCTSTR pstrFormat, ...) { +#ifdef _DEBUG + TCHAR szBuffer[2048] = { 0 }; + va_list args; + va_start (args, pstrFormat); + _vsntprintf (szBuffer, 2048, pstrFormat, args); + va_end (args); + + lstrcat (szBuffer, _T ("\n")); + OutputDebugString (szBuffer); +#endif + } + + LPCTSTR DUI__TraceMsg (UINT uMsg) { +#define MSGDEF(x) if(uMsg==x) return _T(#x) + MSGDEF (WM_SETCURSOR); + MSGDEF (WM_NCHITTEST); + MSGDEF (WM_NCPAINT); + MSGDEF (WM_PAINT); + MSGDEF (WM_ERASEBKGND); + MSGDEF (WM_NCMOUSEMOVE); + MSGDEF (WM_MOUSEMOVE); + MSGDEF (WM_MOUSELEAVE); + MSGDEF (WM_MOUSEHOVER); + MSGDEF (WM_NOTIFY); + MSGDEF (WM_COMMAND); + MSGDEF (WM_MEASUREITEM); + MSGDEF (WM_DRAWITEM); + MSGDEF (WM_LBUTTONDOWN); + MSGDEF (WM_LBUTTONUP); + MSGDEF (WM_LBUTTONDBLCLK); + MSGDEF (WM_RBUTTONDOWN); + MSGDEF (WM_RBUTTONUP); + MSGDEF (WM_RBUTTONDBLCLK); + MSGDEF (WM_SETFOCUS); + MSGDEF (WM_KILLFOCUS); + MSGDEF (WM_MOVE); + MSGDEF (WM_SIZE); + MSGDEF (WM_SIZING); + MSGDEF (WM_MOVING); + MSGDEF (WM_GETMINMAXINFO); + MSGDEF (WM_CAPTURECHANGED); + MSGDEF (WM_WINDOWPOSCHANGED); + MSGDEF (WM_WINDOWPOSCHANGING); + MSGDEF (WM_NCCALCSIZE); + MSGDEF (WM_NCCREATE); + MSGDEF (WM_NCDESTROY); + MSGDEF (WM_TIMER); + MSGDEF (WM_KEYDOWN); + MSGDEF (WM_KEYUP); + MSGDEF (WM_CHAR); + MSGDEF (WM_SYSKEYDOWN); + MSGDEF (WM_SYSKEYUP); + MSGDEF (WM_SYSCOMMAND); + MSGDEF (WM_SYSCHAR); + MSGDEF (WM_VSCROLL); + MSGDEF (WM_HSCROLL); + MSGDEF (WM_CHAR); + MSGDEF (WM_SHOWWINDOW); + MSGDEF (WM_PARENTNOTIFY); + MSGDEF (WM_CREATE); + MSGDEF (WM_NCACTIVATE); + MSGDEF (WM_ACTIVATE); + MSGDEF (WM_ACTIVATEAPP); + MSGDEF (WM_CLOSE); + MSGDEF (WM_DESTROY); + MSGDEF (WM_GETICON); + MSGDEF (WM_GETTEXT); + MSGDEF (WM_GETTEXTLENGTH); + static TCHAR szMsg[10]; + ::wsprintf (szMsg, _T ("0x%04X"), uMsg); + return szMsg; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + ////////////////////////////////////////////////////////////////////////// + // + DUI_BASE_BEGIN_MESSAGE_MAP (CNotifyPump) + DUI_END_MESSAGE_MAP () + + static const DUI_MSGMAP_ENTRY* DuiFindMessageEntry (const DUI_MSGMAP_ENTRY* lpEntry, TNotifyUI& msg) { + CDuiString sMsgType = msg.sType; + CDuiString sCtrlName = msg.pSender->GetName (); + const DUI_MSGMAP_ENTRY* pMsgTypeEntry = nullptr; + while (lpEntry->nSig != DuiSig_end) { + if (lpEntry->sMsgType == sMsgType) { + if (!lpEntry->sCtrlName.empty ()) { + if (lpEntry->sCtrlName == sCtrlName) { + return lpEntry; + } + } else { + pMsgTypeEntry = lpEntry; + } + } + lpEntry++; + } + return pMsgTypeEntry; + } + + bool CNotifyPump::AddVirtualWnd (string_view_t strName, CNotifyPump* pObject) { + if (!m_VirtualWndMap.Find (strName)) { + m_VirtualWndMap.Insert (strName, (LPVOID) pObject); + return true; + } + return false; + } + + bool CNotifyPump::RemoveVirtualWnd (string_view_t strName) { + if (m_VirtualWndMap.Find (strName)) { + m_VirtualWndMap.Remove (strName); + return true; + } + return false; + } + + bool CNotifyPump::LoopDispatch (TNotifyUI& msg) { + const DUI_MSGMAP_ENTRY* lpEntry = nullptr; + const DUI_MSGMAP* pMessageMap = nullptr; + +#ifndef UILIB_STATIC + for (pMessageMap = GetMessageMap (); pMessageMap; pMessageMap = (*pMessageMap->pfnGetBaseMap)()) +#else + for (pMessageMap = GetMessageMap (); pMessageMap; pMessageMap = pMessageMap->pBaseMap) +#endif + { +#ifndef UILIB_STATIC + ASSERT (pMessageMap != (*pMessageMap->pfnGetBaseMap)()); +#else + ASSERT (pMessageMap != pMessageMap->pBaseMap); +#endif + if ((lpEntry = DuiFindMessageEntry (pMessageMap->lpEntries, msg))) { + goto LDispatch; + } + } + return false; + + LDispatch: + union DuiMessageMapFunctions mmf; + mmf.pfn = lpEntry->pfn; + + bool bRet = false; + int nSig; + nSig = lpEntry->nSig; + switch (nSig) { + default: + ASSERT (FALSE); + break; + case DuiSig_lwl: + (this->*mmf.pfn_Notify_lwl)(msg.wParam, msg.lParam); + bRet = true; + break; + case DuiSig_vn: + (this->*mmf.pfn_Notify_vn)(msg); + bRet = true; + break; + } + return bRet; + } + + void CNotifyPump::NotifyPump (TNotifyUI& msg) { + ///ⴰ + if (!msg.sVirtualWnd.empty ()) { + for (int i = 0; i < m_VirtualWndMap.GetSize (); i++) { + string_view_t key = m_VirtualWndMap.GetAt (i)->Key; + if (!key.empty () && key == msg.sVirtualWnd) { + CNotifyPump* pObject = static_cast(m_VirtualWndMap.Find (key, false)); + if (pObject && pObject->LoopDispatch (msg)) + return; + } + } + } + + /// + // + LoopDispatch (msg); + } + + ////////////////////////////////////////////////////////////////////////// + /// + CWindowWnd::CWindowWnd (): m_hWnd (nullptr), m_OldWndProc (::DefWindowProc), m_bSubclassed (false) {} + + HWND CWindowWnd::CreateDuiWindow (HWND hwndParent, string_view_t pstrWindowName, DWORD dwStyle /*=0*/, DWORD dwExStyle /*=0*/) { + return Create (hwndParent, pstrWindowName, dwStyle, dwExStyle, 0, 0, 0, 0, nullptr); + } + + HWND CWindowWnd::Create (HWND hwndParent, string_view_t pstrName, DWORD dwStyle, DWORD dwExStyle, const RECT rc, HMENU hMenu) { + return Create (hwndParent, pstrName, dwStyle, dwExStyle, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, hMenu); + } + + HWND CWindowWnd::Create (HWND hwndParent, string_view_t pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu) { + if (!GetSuperClassName ().empty () && !RegisterSuperclass ()) return nullptr; + if (GetSuperClassName ().empty () && !RegisterWindowClass ()) return nullptr; + m_hWnd = ::CreateWindowEx (dwExStyle, GetWindowClassName ().data (), pstrName.data (), dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance (), this); + ASSERT (m_hWnd); + return m_hWnd; + } + + HWND CWindowWnd::Subclass (HWND hWnd) { + ASSERT (::IsWindow (hWnd)); + ASSERT (!m_hWnd); + m_OldWndProc = SubclassWindow (hWnd, __WndProc); + if (!m_OldWndProc) return nullptr; + m_bSubclassed = true; + m_hWnd = hWnd; + ::SetWindowLongPtr (hWnd, GWLP_USERDATA, reinterpret_cast(this)); + return m_hWnd; + } + + void CWindowWnd::Unsubclass () { + ASSERT (::IsWindow (m_hWnd)); + if (!::IsWindow (m_hWnd)) return; + if (!m_bSubclassed) return; + SubclassWindow (m_hWnd, m_OldWndProc); + m_OldWndProc = ::DefWindowProc; + m_bSubclassed = false; + } + + void CWindowWnd::ShowWindow (bool bShow /*= true*/, bool bTakeFocus /*= false*/) { + ASSERT (::IsWindow (m_hWnd)); + if (!::IsWindow (m_hWnd)) return; + ::ShowWindow (m_hWnd, bShow ? (bTakeFocus ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE) : SW_HIDE); + } + + UINT CWindowWnd::ShowModal () { + ASSERT (::IsWindow (m_hWnd)); + UINT nRet = 0; + HWND hWndParent = GetWindowOwner (m_hWnd); + ::ShowWindow (m_hWnd, SW_SHOWNORMAL); + ::EnableWindow (hWndParent, FALSE); + MSG msg = { 0 }; + while (::IsWindow (m_hWnd) && ::GetMessage (&msg, nullptr, 0, 0)) { + if (msg.message == WM_CLOSE && msg.hwnd == m_hWnd) { + nRet = (UINT) msg.wParam; + ::EnableWindow (hWndParent, TRUE); + ::SetFocus (hWndParent); + } + if (!CPaintManagerUI::TranslateMessage (&msg)) { + ::TranslateMessage (&msg); + ::DispatchMessage (&msg); + } + if (msg.message == WM_QUIT) break; + } + ::EnableWindow (hWndParent, TRUE); + ::SetFocus (hWndParent); + if (msg.message == WM_QUIT) ::PostQuitMessage ((int) msg.wParam); + return nRet; + } + + void CWindowWnd::Close (UINT nRet) { + ASSERT (::IsWindow (m_hWnd)); + if (!::IsWindow (m_hWnd)) return; + PostMessage (WM_CLOSE, (WPARAM) nRet, 0L); + } + + void CWindowWnd::CenterWindow () { + ASSERT (::IsWindow (m_hWnd)); + ASSERT ((GetWindowStyle (m_hWnd)&WS_CHILD) == 0); + RECT rcDlg = { 0 }; + ::GetWindowRect (m_hWnd, &rcDlg); + RECT rcArea = { 0 }; + RECT rcCenter = { 0 }; + HWND hWnd = GetHWND (); + HWND hWndParent = ::GetParent (m_hWnd); + HWND hWndCenter = ::GetWindowOwner (m_hWnd); + if (hWndCenter) + hWnd = hWndCenter; + + // ʾģʽĻ + MONITORINFO oMonitor = {}; + oMonitor.cbSize = sizeof (oMonitor); + ::GetMonitorInfo (::MonitorFromWindow (hWnd, MONITOR_DEFAULTTONEAREST), &oMonitor); + rcArea = oMonitor.rcWork; + + if (!hWndCenter) + rcCenter = rcArea; + else + ::GetWindowRect (hWndCenter, &rcCenter); + + int DlgWidth = rcDlg.right - rcDlg.left; + int DlgHeight = rcDlg.bottom - rcDlg.top; + + // Find dialog's upper left based on rcCenter + int xLeft = (rcCenter.left + rcCenter.right) / 2 - DlgWidth / 2; + int yTop = (rcCenter.top + rcCenter.bottom) / 2 - DlgHeight / 2; + + // The dialog is outside the screen, move it inside + if (xLeft < rcArea.left) xLeft = rcArea.left; + else if (xLeft + DlgWidth > rcArea.right) xLeft = rcArea.right - DlgWidth; + if (yTop < rcArea.top) yTop = rcArea.top; + else if (yTop + DlgHeight > rcArea.bottom) yTop = rcArea.bottom - DlgHeight; + ::SetWindowPos (m_hWnd, nullptr, xLeft, yTop, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + } + + void CWindowWnd::SetIcon (UINT nRes) { + HICON hIcon = (HICON)::LoadImage (CPaintManagerUI::GetInstance (), MAKEINTRESOURCE (nRes), IMAGE_ICON, + (::GetSystemMetrics (SM_CXICON) + 15) & ~15, (::GetSystemMetrics (SM_CYICON) + 15) & ~15, // ֹDPIͼģ + LR_DEFAULTCOLOR); + ASSERT (hIcon); + ::SendMessage (m_hWnd, WM_SETICON, (WPARAM) TRUE, (LPARAM) hIcon); + + hIcon = (HICON)::LoadImage (CPaintManagerUI::GetInstance (), MAKEINTRESOURCE (nRes), IMAGE_ICON, + (::GetSystemMetrics (SM_CXICON) + 15) & ~15, (::GetSystemMetrics (SM_CYICON) + 15) & ~15, // ֹDPIͼģ + LR_DEFAULTCOLOR); + ASSERT (hIcon); + ::SendMessage (m_hWnd, WM_SETICON, (WPARAM) FALSE, (LPARAM) hIcon); + } + + bool CWindowWnd::RegisterWindowClass () { + WNDCLASS wc = { 0 }; + wc.style = GetClassStyle (); + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hIcon = nullptr; + wc.lpfnWndProc = CWindowWnd::__WndProc; + wc.hInstance = CPaintManagerUI::GetInstance (); + wc.hCursor = ::LoadCursor (nullptr, IDC_ARROW); + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = GetWindowClassName ().data (); + ATOM ret = ::RegisterClass (&wc); + ASSERT (ret != 0 || ::GetLastError () == ERROR_CLASS_ALREADY_EXISTS); + return ret != 0 || ::GetLastError () == ERROR_CLASS_ALREADY_EXISTS; + } + + bool CWindowWnd::RegisterSuperclass () { + // Get the class information from an existing + // window so we can subclass it later on... + WNDCLASSEX wc = { 0 }; + wc.cbSize = sizeof (WNDCLASSEX); + if (!::GetClassInfoEx (nullptr, GetSuperClassName ().data (), &wc)) { + if (!::GetClassInfoEx (CPaintManagerUI::GetInstance (), GetSuperClassName ().data (), &wc)) { + ASSERT (!"Unable to locate window class"); + return nullptr; + } + } + m_OldWndProc = wc.lpfnWndProc; + wc.lpfnWndProc = CWindowWnd::__ControlProc; + wc.hInstance = CPaintManagerUI::GetInstance (); + wc.lpszClassName = GetWindowClassName ().data (); + ATOM ret = ::RegisterClassEx (&wc); + ASSERT (ret != 0 || ::GetLastError () == ERROR_CLASS_ALREADY_EXISTS); + return ret != 0 || ::GetLastError () == ERROR_CLASS_ALREADY_EXISTS; + } + + LRESULT CALLBACK CWindowWnd::__WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + CWindowWnd* pThis = nullptr; + if (uMsg == WM_NCCREATE) { + LPCREATESTRUCT lpcs = reinterpret_cast(lParam); + pThis = static_cast(lpcs->lpCreateParams); + pThis->m_hWnd = hWnd; + ::SetWindowLongPtr (hWnd, GWLP_USERDATA, reinterpret_cast(pThis)); + } else { + pThis = reinterpret_cast(::GetWindowLongPtr (hWnd, GWLP_USERDATA)); + if (uMsg == WM_NCDESTROY && pThis) { + LRESULT lRes = ::CallWindowProc (pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam); + ::SetWindowLongPtr (pThis->m_hWnd, GWLP_USERDATA, 0L); + if (pThis->m_bSubclassed) pThis->Unsubclass (); + pThis->m_hWnd = nullptr; + pThis->OnFinalMessage (hWnd); + return lRes; + } + } + if (pThis) { + return pThis->HandleMessage (uMsg, wParam, lParam); + } else { + return ::DefWindowProc (hWnd, uMsg, wParam, lParam); + } + } + + LRESULT CALLBACK CWindowWnd::__ControlProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + CWindowWnd* pThis = nullptr; + if (uMsg == WM_NCCREATE) { + LPCREATESTRUCT lpcs = reinterpret_cast(lParam); + pThis = static_cast(lpcs->lpCreateParams); + ::SetProp (hWnd, _T ("WndX"), (HANDLE) pThis); + pThis->m_hWnd = hWnd; + } else { + pThis = reinterpret_cast(::GetProp (hWnd, _T ("WndX"))); + if (uMsg == WM_NCDESTROY && pThis) { + LRESULT lRes = ::CallWindowProc (pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam); + if (pThis->m_bSubclassed) pThis->Unsubclass (); + ::SetProp (hWnd, _T ("WndX"), nullptr); + pThis->m_hWnd = nullptr; + pThis->OnFinalMessage (hWnd); + return lRes; + } + } + if (pThis) { + return pThis->HandleMessage (uMsg, wParam, lParam); + } else { + return ::DefWindowProc (hWnd, uMsg, wParam, lParam); + } + } + + LRESULT CWindowWnd::SendMessage (UINT uMsg, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/) { + ASSERT (::IsWindow (m_hWnd)); + return ::SendMessage (m_hWnd, uMsg, wParam, lParam); + } + + LRESULT CWindowWnd::PostMessage (UINT uMsg, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/) { + ASSERT (::IsWindow (m_hWnd)); + return ::PostMessage (m_hWnd, uMsg, wParam, lParam); + } + + void CWindowWnd::ResizeClient (int cx /*= -1*/, int cy /*= -1*/) { + ASSERT (::IsWindow (m_hWnd)); + RECT rc = { 0 }; + if (!::GetClientRect (m_hWnd, &rc)) return; + if (cx != -1) rc.right = cx; + if (cy != -1) rc.bottom = cy; + if (!::AdjustWindowRectEx (&rc, GetWindowStyle (m_hWnd), (!(GetWindowStyle (m_hWnd) & WS_CHILD) && (::GetMenu (m_hWnd))), GetWindowExStyle (m_hWnd))) return; + ::SetWindowPos (m_hWnd, nullptr, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); + } + + LRESULT CWindowWnd::HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam) { + return ::CallWindowProc (m_OldWndProc, m_hWnd, uMsg, wParam, lParam); + } + + void CWindowWnd::OnFinalMessage (HWND /*hWnd*/) {} + +} // namespace DuiLib diff --git a/DuiLib/Core/UIBase.h b/DuiLib/Core/UIBase.h new file mode 100644 index 0000000..c8a6ddc --- /dev/null +++ b/DuiLib/Core/UIBase.h @@ -0,0 +1,102 @@ +#ifndef __UIBASE_H__ +#define __UIBASE_H__ + + +#pragma once + +namespace DuiLib { + ///////////////////////////////////////////////////////////////////////////////////// + // + +#define UI_WNDSTYLE_CONTAINER (0) +#define UI_WNDSTYLE_FRAME (WS_VISIBLE | WS_OVERLAPPEDWINDOW) +#define UI_WNDSTYLE_CHILD (WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN) +#define UI_WNDSTYLE_DIALOG (WS_VISIBLE | WS_POPUPWINDOW | WS_CAPTION | WS_DLGFRAME | WS_CLIPSIBLINGS | WS_CLIPCHILDREN) + +#define UI_WNDSTYLE_EX_FRAME (WS_EX_WINDOWEDGE) +#define UI_WNDSTYLE_EX_DIALOG (WS_EX_TOOLWINDOW | WS_EX_DLGMODALFRAME) + +#define UI_CLASSSTYLE_CONTAINER (0) +#define UI_CLASSSTYLE_FRAME (CS_VREDRAW | CS_HREDRAW) +#define UI_CLASSSTYLE_CHILD (CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS | CS_SAVEBITS) +#define UI_CLASSSTYLE_DIALOG (CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS | CS_SAVEBITS) + + + ///////////////////////////////////////////////////////////////////////////////////// + // +#ifndef ASSERT +#define ASSERT(expr) _ASSERTE(expr) +#endif + +#ifdef _DEBUG +#ifndef DUITRACE +#define DUITRACE DUI__Trace +#endif +#define DUITRACEMSG DUI__TraceMsg +#else +#ifndef DUITRACE +#define DUITRACE +#endif +#define DUITRACEMSG _T("") +#endif + + void UILIB_API DUI__Trace (LPCTSTR pstrFormat, ...); + LPCTSTR UILIB_API DUI__TraceMsg (UINT uMsg); + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CNotifyPump { + public: + bool AddVirtualWnd (string_view_t strName, CNotifyPump* pObject); + bool RemoveVirtualWnd (string_view_t strName); + void NotifyPump (TNotifyUI& msg); + bool LoopDispatch (TNotifyUI& msg); + DUI_DECLARE_MESSAGE_MAP () + private: + CStdStringPtrMap m_VirtualWndMap; + }; + + class UILIB_API CWindowWnd { + public: + CWindowWnd (); + + HWND GetHWND () const { return m_hWnd; } + bool RegisterWindowClass (); + bool RegisterSuperclass (); + + HWND Create (HWND hwndParent, string_view_t pstrName, DWORD dwStyle, DWORD dwExStyle, const RECT rc, HMENU hMenu = NULL); + HWND Create (HWND hwndParent, string_view_t pstrName, DWORD dwStyle, DWORD dwExStyle, int x = CW_USEDEFAULT, int y = CW_USEDEFAULT, int cx = CW_USEDEFAULT, int cy = CW_USEDEFAULT, HMENU hMenu = NULL); + HWND CreateDuiWindow (HWND hwndParent, string_view_t pstrWindowName, DWORD dwStyle = 0, DWORD dwExStyle = 0); + HWND Subclass (HWND hWnd); + void Unsubclass (); + void ShowWindow (bool bShow = true, bool bTakeFocus = true); + UINT ShowModal (); + void Close (UINT nRet = IDOK); + void CenterWindow (); // У֧չĻ + void SetIcon (UINT nRes); + + LRESULT SendMessage (UINT uMsg, WPARAM wParam = 0, LPARAM lParam = 0L); + LRESULT PostMessage (UINT uMsg, WPARAM wParam = 0, LPARAM lParam = 0L); + void ResizeClient (int cx = -1, int cy = -1); + + protected: + virtual string_view_t GetWindowClassName () const = 0; + virtual string_view_t GetSuperClassName () const { return _T (""); } + virtual UINT GetClassStyle () const { return 0; } + + virtual LRESULT HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam); + virtual void OnFinalMessage (HWND hWnd); + + static LRESULT CALLBACK __WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK __ControlProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + protected: + HWND m_hWnd; + WNDPROC m_OldWndProc; + bool m_bSubclassed; + }; + +} // namespace DuiLib + +#endif // __UIBASE_H__ diff --git a/DuiLib/Core/UIContainer.cpp b/DuiLib/Core/UIContainer.cpp new file mode 100644 index 0000000..7f817ab --- /dev/null +++ b/DuiLib/Core/UIContainer.cpp @@ -0,0 +1,993 @@ +#include "StdAfx.h" + +namespace DuiLib { + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + IMPLEMENT_DUICONTROL (CContainerUI) + + CContainerUI::CContainerUI () {} + + CContainerUI::~CContainerUI () { + m_bDelayedDestroy = false; + RemoveAll (); + if (m_pVerticalScrollBar) { + delete m_pVerticalScrollBar; + m_pVerticalScrollBar = nullptr; + } + if (m_pHorizontalScrollBar) { + delete m_pHorizontalScrollBar; + m_pHorizontalScrollBar = nullptr; + } + } + + string_view_t CContainerUI::GetClass () const { + return _T ("ContainerUI"); + } + + LPVOID CContainerUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("IContainer")) return static_cast(this); + else if (pstrName == DUI_CTRL_CONTAINER) return static_cast(this); + return CControlUI::GetInterface (pstrName); + } + + CControlUI* CContainerUI::GetItemAt (int iIndex) const { + if (iIndex < 0 || iIndex >= m_items.GetSize ()) return nullptr; + return static_cast(m_items[iIndex]); + } + + int CContainerUI::GetItemIndex (CControlUI* pControl) const { + for (int it = 0; it < m_items.GetSize (); it++) { + if (static_cast(m_items[it]) == pControl) { + return it; + } + } + + return -1; + } + + bool CContainerUI::SetItemIndex (CControlUI* pControl, int iIndex) { + for (int it = 0; it < m_items.GetSize (); it++) { + if (static_cast(m_items[it]) == pControl) { + NeedUpdate (); + m_items.Remove (it); + return m_items.InsertAt (iIndex, pControl); + } + } + + return false; + } + + int CContainerUI::GetCount () const { + return m_items.GetSize (); + } + + bool CContainerUI::Add (CControlUI* pControl) { + if (!pControl) return false; + + if (m_pManager) m_pManager->InitControls (pControl, this); + if (IsVisible ()) NeedUpdate (); + else pControl->SetInternVisible (false); + return m_items.Add (pControl); + } + + bool CContainerUI::AddAt (CControlUI* pControl, int iIndex) { + if (!pControl) return false; + + if (m_pManager) m_pManager->InitControls (pControl, this); + if (IsVisible ()) NeedUpdate (); + else pControl->SetInternVisible (false); + return m_items.InsertAt (iIndex, pControl); + } + + bool CContainerUI::Remove (CControlUI* pControl) { + if (!pControl) return false; + + for (int it = 0; it < m_items.GetSize (); it++) { + if (static_cast(m_items[it]) == pControl) { + NeedUpdate (); + if (m_bAutoDestroy) { + if (m_bDelayedDestroy && m_pManager) m_pManager->AddDelayedCleanup (pControl); + else delete pControl; + } + return m_items.Remove (it); + } + } + return false; + } + + bool CContainerUI::RemoveAt (int iIndex) { + CControlUI* pControl = GetItemAt (iIndex); + if (pControl) { + return CContainerUI::Remove (pControl); + } + + return false; + } + + void CContainerUI::RemoveAll () { + for (int it = 0; m_bAutoDestroy && it < m_items.GetSize (); it++) { + CControlUI* pItem = static_cast(m_items[it]); + if (m_bDelayedDestroy && m_pManager) { + m_pManager->AddDelayedCleanup (pItem); + } else { + delete pItem; + pItem = nullptr; + } + } + m_items.Empty (); + NeedUpdate (); + } + + bool CContainerUI::IsAutoDestroy () const { + return m_bAutoDestroy; + } + + void CContainerUI::SetAutoDestroy (bool bAuto) { + m_bAutoDestroy = bAuto; + } + + bool CContainerUI::IsDelayedDestroy () const { + return m_bDelayedDestroy; + } + + void CContainerUI::SetDelayedDestroy (bool bDelayed) { + m_bDelayedDestroy = bDelayed; + } + + RECT CContainerUI::GetInset () const { + if (m_pManager) return m_pManager->GetDPIObj ()->Scale (m_rcInset); + return m_rcInset; + } + + void CContainerUI::SetInset (RECT rcInset) { + m_rcInset = rcInset; + NeedUpdate (); + } + + int CContainerUI::GetChildPadding () const { + if (m_pManager) return m_pManager->GetDPIObj ()->Scale (m_iChildPadding); + return m_iChildPadding; + } + + + void CContainerUI::SetChildPadding (int iPadding) { + m_iChildPadding = iPadding; + NeedUpdate (); + } + + UINT CContainerUI::GetChildAlign (CControlUI *pControl) const { + if (pControl) { + UINT iChildFloatAlign = pControl->GetFloatAlign (); + return (iChildFloatAlign == DT_LEFT ? m_iChildAlign : iChildFloatAlign); + } + return m_iChildAlign; + } + + void CContainerUI::SetChildAlign (UINT iAlign) { + m_iChildAlign = iAlign; + NeedUpdate (); + } + + UINT CContainerUI::GetChildVAlign () const { + return m_iChildVAlign; + } + + void CContainerUI::SetChildVAlign (UINT iVAlign) { + m_iChildVAlign = iVAlign; + NeedUpdate (); + } + + bool CContainerUI::IsMouseChildEnabled () const { + return m_bMouseChildEnabled; + } + + void CContainerUI::SetMouseChildEnabled (bool bEnable) { + m_bMouseChildEnabled = bEnable; + } + + void CContainerUI::SetVisible (bool bVisible) { + if (m_bVisible == bVisible) return; + CControlUI::SetVisible (bVisible); + for (int it = 0; it < m_items.GetSize (); it++) { + static_cast(m_items[it])->SetInternVisible (IsVisible ()); + } + } + + // ߼ϣContainerؼ˷ + // ô˷ĽǣڲӿؼأؼȻʾЧ + void CContainerUI::SetInternVisible (bool bVisible) { + CControlUI::SetInternVisible (bVisible); + if (m_items.empty ()) return; + for (int it = 0; it < m_items.GetSize (); it++) { + // ӿؼʾ״̬ + // InternVisible״̬ӦӿؼԼ + static_cast(m_items[it])->SetInternVisible (IsVisible ()); + } + } + + void CContainerUI::SetEnabled (bool bEnabled) { + if (m_bEnabled == bEnabled) return; + + m_bEnabled = bEnabled; + + for (int it = 0; it < m_items.GetSize (); it++) { + static_cast(m_items[it])->SetEnabled (m_bEnabled); + } + + Invalidate (); + } + + void CContainerUI::SetMouseEnabled (bool bEnabled) { + if (m_pVerticalScrollBar) m_pVerticalScrollBar->SetMouseEnabled (bEnabled); + if (m_pHorizontalScrollBar) m_pHorizontalScrollBar->SetMouseEnabled (bEnabled); + CControlUI::SetMouseEnabled (bEnabled); + } + + void CContainerUI::DoEvent (TEventUI& event) { + if (!IsMouseEnabled () && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) { + if (m_pParent) m_pParent->DoEvent (event); + else CControlUI::DoEvent (event); + return; + } + + if (event.Type == UIEVENT_SETFOCUS) { + m_bFocused = true; + return; + } + if (event.Type == UIEVENT_KILLFOCUS) { + m_bFocused = false; + return; + } + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible () && m_pVerticalScrollBar->IsEnabled ()) { + if (event.Type == UIEVENT_KEYDOWN) { + switch (event.chKey) { + case VK_DOWN: + LineDown (); + return; + case VK_UP: + LineUp (); + return; + case VK_NEXT: + PageDown (); + return; + case VK_PRIOR: + PageUp (); + return; + case VK_HOME: + HomeUp (); + return; + case VK_END: + EndDown (); + return; + } + } else if (event.Type == UIEVENT_SCROLLWHEEL) { + switch (LOWORD (event.wParam)) { + case SB_LINEUP: + LineUp (); + return; + case SB_LINEDOWN: + LineDown (); + return; + } + } + } + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible () && m_pHorizontalScrollBar->IsEnabled ()) { + if (event.Type == UIEVENT_KEYDOWN) { + switch (event.chKey) { + case VK_DOWN: + LineRight (); + return; + case VK_UP: + LineLeft (); + return; + case VK_NEXT: + PageRight (); + return; + case VK_PRIOR: + PageLeft (); + return; + case VK_HOME: + HomeLeft (); + return; + case VK_END: + EndRight (); + return; + } + } else if (event.Type == UIEVENT_SCROLLWHEEL) { + switch (LOWORD (event.wParam)) { + case SB_LINEUP: + LineLeft (); + return; + case SB_LINEDOWN: + LineRight (); + return; + } + } + } + CControlUI::DoEvent (event); + } + + SIZE CContainerUI::GetScrollPos () const { + SIZE sz = { 0, 0 }; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) sz.cy = m_pVerticalScrollBar->GetScrollPos (); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) sz.cx = m_pHorizontalScrollBar->GetScrollPos (); + return sz; + } + + SIZE CContainerUI::GetScrollRange () const { + SIZE sz = { 0, 0 }; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) sz.cy = m_pVerticalScrollBar->GetScrollRange (); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) sz.cx = m_pHorizontalScrollBar->GetScrollRange (); + return sz; + } + + void CContainerUI::SetScrollPos (SIZE szPos, bool bMsg) { + int cx = 0; + int cy = 0; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + int iLastScrollPos = m_pVerticalScrollBar->GetScrollPos (); + m_pVerticalScrollBar->SetScrollPos (szPos.cy); + cy = m_pVerticalScrollBar->GetScrollPos () - iLastScrollPos; + } + + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + int iLastScrollPos = m_pHorizontalScrollBar->GetScrollPos (); + m_pHorizontalScrollBar->SetScrollPos (szPos.cx); + cx = m_pHorizontalScrollBar->GetScrollPos () - iLastScrollPos; + } + + if (cx == 0 && cy == 0) return; + + RECT rcPos = { 0 }; + for (int it2 = 0; it2 < m_items.GetSize (); it2++) { + CControlUI* pControl = static_cast(m_items[it2]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) continue; + + rcPos = pControl->GetPos (); + rcPos.left -= cx; + rcPos.right -= cx; + rcPos.top -= cy; + rcPos.bottom -= cy; + pControl->SetPos (rcPos); + } + + Invalidate (); + + if (m_pVerticalScrollBar) { + // ͹Ϣ + if (m_pManager && bMsg) { + int nPage = (m_pVerticalScrollBar->GetScrollPos () + m_pVerticalScrollBar->GetLineSize ()) / m_pVerticalScrollBar->GetLineSize (); + m_pManager->SendNotify (this, DUI_MSGTYPE_SCROLL, (WPARAM) nPage); + } + } + } + + void CContainerUI::SetScrollStepSize (int nSize) { + if (nSize > 0) + m_nScrollStepSize = nSize; + } + + int CContainerUI::GetScrollStepSize () const { + if (m_pManager)return m_pManager->GetDPIObj ()->Scale (m_nScrollStepSize); + + return m_nScrollStepSize; + } + + void CContainerUI::LineUp () { + int cyLine = GetScrollStepSize (); + if (cyLine == 0) { + cyLine = 8; + if (m_pManager) cyLine = m_pManager->GetDefaultFontInfo ()->tm.tmHeight + 8; + } + + SIZE sz = GetScrollPos (); + sz.cy -= cyLine; + SetScrollPos (sz); + } + + void CContainerUI::LineDown () { + int cyLine = GetScrollStepSize (); + if (cyLine == 0) { + cyLine = 8; + if (m_pManager) cyLine = m_pManager->GetDefaultFontInfo ()->tm.tmHeight + 8; + } + + SIZE sz = GetScrollPos (); + sz.cy += cyLine; + SetScrollPos (sz); + } + + void CContainerUI::PageUp () { + SIZE sz = GetScrollPos (); + int iOffset = m_rcItem.bottom - m_rcItem.top - m_rcInset.top - m_rcInset.bottom; + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) iOffset -= m_pHorizontalScrollBar->GetFixedHeight (); + sz.cy -= iOffset; + SetScrollPos (sz); + } + + void CContainerUI::PageDown () { + SIZE sz = GetScrollPos (); + int iOffset = m_rcItem.bottom - m_rcItem.top - m_rcInset.top - m_rcInset.bottom; + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) iOffset -= m_pHorizontalScrollBar->GetFixedHeight (); + sz.cy += iOffset; + SetScrollPos (sz); + } + + void CContainerUI::HomeUp () { + SIZE sz = GetScrollPos (); + sz.cy = 0; + SetScrollPos (sz); + } + + void CContainerUI::EndDown () { + SIZE sz = GetScrollPos (); + sz.cy = GetScrollRange ().cy; + SetScrollPos (sz); + } + + void CContainerUI::LineLeft () { + int nScrollStepSize = GetScrollStepSize (); + int cxLine = nScrollStepSize == 0 ? 8 : nScrollStepSize; + + SIZE sz = GetScrollPos (); + sz.cx -= cxLine; + SetScrollPos (sz); + } + + void CContainerUI::LineRight () { + int nScrollStepSize = GetScrollStepSize (); + int cxLine = nScrollStepSize == 0 ? 8 : nScrollStepSize; + + SIZE sz = GetScrollPos (); + sz.cx += cxLine; + SetScrollPos (sz); + } + + void CContainerUI::PageLeft () { + SIZE sz = GetScrollPos (); + int iOffset = m_rcItem.right - m_rcItem.left - m_rcInset.left - m_rcInset.right; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) iOffset -= m_pVerticalScrollBar->GetFixedWidth (); + sz.cx -= iOffset; + SetScrollPos (sz); + } + + void CContainerUI::PageRight () { + SIZE sz = GetScrollPos (); + int iOffset = m_rcItem.right - m_rcItem.left - m_rcInset.left - m_rcInset.right; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) iOffset -= m_pVerticalScrollBar->GetFixedWidth (); + sz.cx += iOffset; + SetScrollPos (sz); + } + + void CContainerUI::HomeLeft () { + SIZE sz = GetScrollPos (); + sz.cx = 0; + SetScrollPos (sz); + } + + void CContainerUI::EndRight () { + SIZE sz = GetScrollPos (); + sz.cx = GetScrollRange ().cx; + SetScrollPos (sz); + } + + void CContainerUI::EnableScrollBar (bool bEnableVertical, bool bEnableHorizontal) { + if (bEnableVertical && !m_pVerticalScrollBar) { + m_pVerticalScrollBar = new CScrollBarUI; + m_pVerticalScrollBar->SetOwner (this); + m_pVerticalScrollBar->SetManager (m_pManager, nullptr, false); + if (m_pManager) { + string_view_t pDefaultAttributes = m_pManager->GetDefaultAttributeList (_T ("VScrollBar")); + if (!pDefaultAttributes.empty ()) { + m_pVerticalScrollBar->ApplyAttributeList (pDefaultAttributes); + } + } + } else if (!bEnableVertical && m_pVerticalScrollBar) { + delete m_pVerticalScrollBar; + m_pVerticalScrollBar = nullptr; + } + + if (bEnableHorizontal && !m_pHorizontalScrollBar) { + m_pHorizontalScrollBar = new CScrollBarUI; + m_pHorizontalScrollBar->SetHorizontal (true); + m_pHorizontalScrollBar->SetOwner (this); + m_pHorizontalScrollBar->SetManager (m_pManager, nullptr, false); + + if (m_pManager) { + string_view_t pDefaultAttributes = m_pManager->GetDefaultAttributeList (_T ("HScrollBar")); + if (!pDefaultAttributes.empty ()) { + m_pHorizontalScrollBar->ApplyAttributeList (pDefaultAttributes); + } + } + } else if (!bEnableHorizontal && m_pHorizontalScrollBar) { + delete m_pHorizontalScrollBar; + m_pHorizontalScrollBar = nullptr; + } + + NeedUpdate (); + } + + CScrollBarUI* CContainerUI::GetVerticalScrollBar () const { + return m_pVerticalScrollBar; + } + + CScrollBarUI* CContainerUI::GetHorizontalScrollBar () const { + return m_pHorizontalScrollBar; + } + + int CContainerUI::FindSelectable (int iIndex, bool bForward /*= true*/) const { + // NOTE: This is actually a helper-function for the list/combo/ect controls + // that allow them to find the next enabled/available selectable item + if (GetCount () == 0) return -1; + iIndex = CLAMP (iIndex, 0, GetCount () - 1); + if (bForward) { + for (int i = iIndex; i < GetCount (); i++) { + if (GetItemAt (i)->GetInterface (_T ("ListItem")) + && GetItemAt (i)->IsVisible () + && GetItemAt (i)->IsEnabled ()) return i; + } + return -1; + } else { + for (int i = iIndex; i >= 0; --i) { + if (GetItemAt (i)->GetInterface (_T ("ListItem")) + && GetItemAt (i)->IsVisible () + && GetItemAt (i)->IsEnabled ()) return i; + } + return FindSelectable (0, true); + } + } + + RECT CContainerUI::GetClientPos () const { + RECT rc = m_rcItem; + rc.left += m_rcInset.left; + rc.top += m_rcInset.top; + rc.right -= m_rcInset.right; + rc.bottom -= m_rcInset.bottom; + + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + rc.top -= m_pVerticalScrollBar->GetScrollPos (); + rc.bottom -= m_pVerticalScrollBar->GetScrollPos (); + rc.bottom += m_pVerticalScrollBar->GetScrollRange (); + rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + } + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + rc.left -= m_pHorizontalScrollBar->GetScrollPos (); + rc.right -= m_pHorizontalScrollBar->GetScrollPos (); + rc.right += m_pHorizontalScrollBar->GetScrollRange (); + rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + } + return rc; + } + + void CContainerUI::Move (SIZE szOffset, bool bNeedInvalidate) { + CControlUI::Move (szOffset, bNeedInvalidate); + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) m_pVerticalScrollBar->Move (szOffset, false); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) m_pHorizontalScrollBar->Move (szOffset, false); + for (int it = 0; it < m_items.GetSize (); it++) { + CControlUI* pControl = static_cast(m_items[it]); + if (pControl && pControl->IsVisible ()) pControl->Move (szOffset, false); + } + } + + void CContainerUI::SetPos (RECT rc, bool bNeedInvalidate) { + CControlUI::SetPos (rc, bNeedInvalidate); + if (m_items.empty ()) return; + + rc = m_rcItem; + rc.left += m_rcInset.left; + rc.top += m_rcInset.top; + rc.right -= m_rcInset.right; + rc.bottom -= m_rcInset.bottom; + + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + rc.top -= m_pVerticalScrollBar->GetScrollPos (); + rc.bottom -= m_pVerticalScrollBar->GetScrollPos (); + rc.bottom += m_pVerticalScrollBar->GetScrollRange (); + rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + } + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + rc.left -= m_pHorizontalScrollBar->GetScrollPos (); + rc.right -= m_pHorizontalScrollBar->GetScrollPos (); + rc.right += m_pHorizontalScrollBar->GetScrollRange (); + rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + } + + for (int it = 0; it < m_items.GetSize (); it++) { + CControlUI* pControl = static_cast(m_items[it]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) { + SetFloatPos (it); + } else { + SIZE sz = { rc.right - rc.left, rc.bottom - rc.top }; + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + if (sz.cx > pControl->GetMaxWidth ()) sz.cx = pControl->GetMaxWidth (); + if (sz.cy < pControl->GetMinHeight ()) sz.cy = pControl->GetMinHeight (); + if (sz.cy > pControl->GetMaxHeight ()) sz.cy = pControl->GetMaxHeight (); + RECT rcCtrl = { rc.left, rc.top, rc.left + sz.cx, rc.top + sz.cy }; + pControl->SetPos (rcCtrl, false); + } + } + } + + void CContainerUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("inset")) { + RECT rcInset = FawTools::parse_rect (pstrValue); + SetInset (rcInset); + } else if (pstrName == _T ("mousechild")) SetMouseChildEnabled (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("vscrollbar")) { + EnableScrollBar (FawTools::parse_bool (pstrValue), GetHorizontalScrollBar ()); + } else if (pstrName == _T ("vscrollbarstyle")) { + m_sVerticalScrollBarStyle = pstrValue; + EnableScrollBar (TRUE, GetHorizontalScrollBar ()); + if (GetVerticalScrollBar ()) { + string_view_t pStyle = m_pManager->GetStyle (m_sVerticalScrollBarStyle); + if (!pStyle.empty ()) { + GetVerticalScrollBar ()->ApplyAttributeList (pStyle); + } else { + GetVerticalScrollBar ()->ApplyAttributeList (pstrValue); + } + } + } else if (pstrName == _T ("hscrollbar")) { + EnableScrollBar (GetVerticalScrollBar (), FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("hscrollbarstyle")) { + m_sHorizontalScrollBarStyle = pstrValue; + EnableScrollBar (TRUE, GetHorizontalScrollBar ()); + if (GetHorizontalScrollBar ()) { + string_view_t pStyle = m_pManager->GetStyle (m_sHorizontalScrollBarStyle); + if (!pStyle.empty ()) { + GetHorizontalScrollBar ()->ApplyAttributeList (pStyle); + } else { + GetHorizontalScrollBar ()->ApplyAttributeList (pstrValue); + } + } + } else if (pstrName == _T ("childpadding")) SetChildPadding (_ttoi (pstrValue.data ())); + else if (pstrName == _T ("childalign")) { + if (pstrValue == _T ("left")) m_iChildAlign = DT_LEFT; + else if (pstrValue == _T ("center")) m_iChildAlign = DT_CENTER; + else if (pstrValue == _T ("right")) m_iChildAlign = DT_RIGHT; + } else if (pstrName == _T ("childvalign")) { + if (pstrValue == _T ("top")) m_iChildVAlign = DT_TOP; + else if (pstrValue == _T ("vcenter")) m_iChildVAlign = DT_VCENTER; + else if (pstrValue == _T ("bottom")) m_iChildVAlign = DT_BOTTOM; + } else if (pstrName == _T ("scrollstepsize")) SetScrollStepSize (_ttoi (pstrValue.data ())); + else CControlUI::SetAttribute (pstrName, pstrValue); + } + + void CContainerUI::SetManager (CPaintManagerUI* pManager, CControlUI* pParent, bool bInit) { + for (int it = 0; it < m_items.GetSize (); it++) { + static_cast(m_items[it])->SetManager (pManager, this, bInit); + } + + if (m_pVerticalScrollBar) m_pVerticalScrollBar->SetManager (pManager, this, bInit); + if (m_pHorizontalScrollBar) m_pHorizontalScrollBar->SetManager (pManager, this, bInit); + CControlUI::SetManager (pManager, pParent, bInit); + } + + CControlUI* CContainerUI::FindControl (FINDCONTROLPROC Proc, LPVOID pData, UINT uFlags) { + if ((uFlags & UIFIND_VISIBLE) != 0 && !IsVisible ()) return nullptr; + if ((uFlags & UIFIND_ENABLED) != 0 && !IsEnabled ()) return nullptr; + if ((uFlags & UIFIND_HITTEST) != 0 && !::PtInRect (&m_rcItem, *(static_cast(pData)))) return nullptr; + if ((uFlags & UIFIND_UPDATETEST) != 0 && Proc (this, pData)) return nullptr; + + CControlUI* pResult = nullptr; + if ((uFlags & UIFIND_ME_FIRST) != 0) { + if ((uFlags & UIFIND_HITTEST) == 0 || IsMouseEnabled ()) pResult = Proc (this, pData); + } + if (!pResult && m_pVerticalScrollBar) { + if ((uFlags & UIFIND_HITTEST) == 0 || IsMouseEnabled ()) pResult = m_pVerticalScrollBar->FindControl (Proc, pData, uFlags); + } + if (!pResult && m_pHorizontalScrollBar) { + if ((uFlags & UIFIND_HITTEST) == 0 || IsMouseEnabled ()) pResult = m_pHorizontalScrollBar->FindControl (Proc, pData, uFlags); + } + if (pResult) return pResult; + + if ((uFlags & UIFIND_HITTEST) == 0 || IsMouseChildEnabled ()) { + RECT rc = m_rcItem; + rc.left += m_rcInset.left; + rc.top += m_rcInset.top; + rc.right -= m_rcInset.right; + rc.bottom -= m_rcInset.bottom; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + if ((uFlags & UIFIND_TOP_FIRST) != 0) { + for (int it = m_items.GetSize () - 1; it >= 0; it--) { + pResult = static_cast(m_items[it])->FindControl (Proc, pData, uFlags); + if (pResult) { + if ((uFlags & UIFIND_HITTEST) != 0 && !pResult->IsFloat () && !::PtInRect (&rc, *(static_cast(pData)))) + continue; + else + return pResult; + } + } + } else { + for (int it = 0; it < m_items.GetSize (); it++) { + pResult = static_cast(m_items[it])->FindControl (Proc, pData, uFlags); + if (pResult) { + if ((uFlags & UIFIND_HITTEST) != 0 && !pResult->IsFloat () && !::PtInRect (&rc, *(static_cast(pData)))) + continue; + else + return pResult; + } + } + } + } + + pResult = nullptr; + if (!pResult && (uFlags & UIFIND_ME_FIRST) == 0) { + if ((uFlags & UIFIND_HITTEST) == 0 || IsMouseEnabled ()) pResult = Proc (this, pData); + } + return pResult; + } + + bool CContainerUI::DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + RECT rcTemp = { 0 }; + if (!::IntersectRect (&rcTemp, &rcPaint, &m_rcItem)) return true; + + CRenderClip clip; + CRenderClip::GenerateClip (hDC, rcTemp, clip); + CControlUI::DoPaint (hDC, rcPaint, pStopControl); + + if (m_items.GetSize () > 0) { + RECT rcInset = GetInset (); + RECT rc = m_rcItem; + rc.left += rcInset.left; + rc.top += rcInset.top; + rc.right -= rcInset.right; + rc.bottom -= rcInset.bottom; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + + if (!::IntersectRect (&rcTemp, &rcPaint, &rc)) { + for (int it = 0; it < m_items.GetSize (); it++) { + CControlUI* pControl = static_cast(m_items[it]); + if (pControl == pStopControl) return false; + if (!pControl->IsVisible ()) continue; + if (!::IntersectRect (&rcTemp, &rcPaint, &pControl->GetPos ())) continue; + if (pControl->IsFloat ()) { + if (!::IntersectRect (&rcTemp, &m_rcItem, &pControl->GetPos ())) continue; + if (!pControl->Paint (hDC, rcPaint, pStopControl)) return false; + } + } + } else { + CRenderClip childClip; + CRenderClip::GenerateClip (hDC, rcTemp, childClip); + for (int it = 0; it < m_items.GetSize (); it++) { + CControlUI* pControl = static_cast(m_items[it]); + if (pControl == pStopControl) return false; + if (!pControl->IsVisible ()) continue; + if (!::IntersectRect (&rcTemp, &rcPaint, &pControl->GetPos ())) continue; + if (pControl->IsFloat ()) { + if (!::IntersectRect (&rcTemp, &m_rcItem, &pControl->GetPos ())) continue; + CRenderClip::UseOldClipBegin (hDC, childClip); + if (!pControl->Paint (hDC, rcPaint, pStopControl)) return false; + CRenderClip::UseOldClipEnd (hDC, childClip); + } else { + if (!::IntersectRect (&rcTemp, &rc, &pControl->GetPos ())) continue; + if (!pControl->Paint (hDC, rcPaint, pStopControl)) return false; + } + } + } + } + + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + if (m_pVerticalScrollBar == pStopControl) return false; + if (::IntersectRect (&rcTemp, &rcPaint, &m_pVerticalScrollBar->GetPos ())) { + if (!m_pVerticalScrollBar->Paint (hDC, rcPaint, pStopControl)) return false; + } + } + + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + if (m_pHorizontalScrollBar == pStopControl) return false; + if (::IntersectRect (&rcTemp, &rcPaint, &m_pHorizontalScrollBar->GetPos ())) { + if (!m_pHorizontalScrollBar->Paint (hDC, rcPaint, pStopControl)) return false; + } + } + return true; + } + + void CContainerUI::SetFloatPos (int iIndex) { + // ΪCControlUI::SetPosfloatIJӰ죬ﲻܶfloatӹӰ + if (iIndex < 0 || iIndex >= m_items.GetSize ()) return; + + CControlUI* pControl = static_cast(m_items[iIndex]); + + if (!pControl->IsVisible ()) return; + if (!pControl->IsFloat ()) return; + + SIZE szXY = pControl->GetFixedXY (); + SIZE sz = { pControl->GetFixedWidth (), pControl->GetFixedHeight () }; + + int nParentWidth = m_rcItem.right - m_rcItem.left; + int nParentHeight = m_rcItem.bottom - m_rcItem.top; + + UINT uAlign = pControl->GetFloatAlign (); + if (uAlign != 0) { + RECT rcCtrl = { 0, 0, sz.cx, sz.cy }; + if ((uAlign & DT_CENTER) != 0) { + ::OffsetRect (&rcCtrl, (nParentWidth - sz.cx) / 2, 0); + } else if ((uAlign & DT_RIGHT) != 0) { + ::OffsetRect (&rcCtrl, nParentWidth - sz.cx, 0); + } else { + ::OffsetRect (&rcCtrl, szXY.cx, 0); + } + + if ((uAlign & DT_VCENTER) != 0) { + ::OffsetRect (&rcCtrl, 0, (nParentHeight - sz.cy) / 2); + } else if ((uAlign & DT_BOTTOM) != 0) { + ::OffsetRect (&rcCtrl, 0, nParentHeight - sz.cy); + } else { + ::OffsetRect (&rcCtrl, 0, szXY.cy); + } + + ::OffsetRect (&rcCtrl, m_rcItem.left, m_rcItem.top); + pControl->SetPos (rcCtrl, false); + } else { + TPercentInfo rcPercent = pControl->GetFloatPercent (); + LONG width = m_rcItem.right - m_rcItem.left; + LONG height = m_rcItem.bottom - m_rcItem.top; + RECT rcCtrl = { 0 }; + rcCtrl.left = (LONG) (width*rcPercent.left) + szXY.cx + m_rcItem.left; + rcCtrl.top = (LONG) (height*rcPercent.top) + szXY.cy + m_rcItem.top; + rcCtrl.right = (LONG) (width*rcPercent.right) + szXY.cx + sz.cx + m_rcItem.left; + rcCtrl.bottom = (LONG) (height*rcPercent.bottom) + szXY.cy + sz.cy + m_rcItem.top; + pControl->SetPos (rcCtrl, false); + } + } + + void CContainerUI::ProcessScrollBar (RECT rc, int cxRequired, int cyRequired) { + while (m_pHorizontalScrollBar) { + // Scroll needed + if (cxRequired > rc.right - rc.left && !m_pHorizontalScrollBar->IsVisible ()) { + m_pHorizontalScrollBar->SetVisible (true); + m_pHorizontalScrollBar->SetScrollRange (cxRequired - (rc.right - rc.left)); + m_pHorizontalScrollBar->SetScrollPos (0); + SetPos (m_rcItem); + break; + } + + // No scrollbar required + if (!m_pHorizontalScrollBar->IsVisible ()) break; + + // Scroll not needed anymore? + int cxScroll = cxRequired - (rc.right - rc.left); + if (cxScroll <= 0) { + m_pHorizontalScrollBar->SetVisible (false); + m_pHorizontalScrollBar->SetScrollPos (0); + m_pHorizontalScrollBar->SetScrollRange (0); + SetPos (m_rcItem); + } else { + RECT rcScrollBarPos = { rc.left, rc.bottom, rc.right, rc.bottom + m_pHorizontalScrollBar->GetFixedHeight () }; + m_pHorizontalScrollBar->SetPos (rcScrollBarPos); + + if (m_pHorizontalScrollBar->GetScrollRange () != cxScroll) { + int iScrollPos = m_pHorizontalScrollBar->GetScrollPos (); + m_pHorizontalScrollBar->SetScrollRange (::abs (cxScroll)); // if scrollpos>range then scrollpos=range + if (iScrollPos > m_pHorizontalScrollBar->GetScrollPos ()) { + SetPos (m_rcItem); + } + } + } + break; + } + + while (m_pVerticalScrollBar) { + // Scroll needed + if (cyRequired > rc.bottom - rc.top && !m_pVerticalScrollBar->IsVisible ()) { + m_pVerticalScrollBar->SetVisible (true); + m_pVerticalScrollBar->SetScrollRange (cyRequired - (rc.bottom - rc.top)); + m_pVerticalScrollBar->SetScrollPos (0); + SetPos (m_rcItem); + break; + } + + // No scrollbar required + if (!m_pVerticalScrollBar->IsVisible ()) break; + + // Scroll not needed anymore? + int cyScroll = cyRequired - (rc.bottom - rc.top); + if (cyScroll <= 0) { + m_pVerticalScrollBar->SetVisible (false); + m_pVerticalScrollBar->SetScrollPos (0); + m_pVerticalScrollBar->SetScrollRange (0); + SetPos (m_rcItem); + break; + } + + RECT rcScrollBarPos = { rc.right, rc.top, rc.right + m_pVerticalScrollBar->GetFixedWidth (), rc.bottom }; + m_pVerticalScrollBar->SetPos (rcScrollBarPos); + + if (m_pVerticalScrollBar->GetScrollRange () != cyScroll) { + int iScrollPos = m_pVerticalScrollBar->GetScrollPos (); + m_pVerticalScrollBar->SetScrollRange (::abs (cyScroll)); // if scrollpos>range then scrollpos=range + if (iScrollPos > m_pVerticalScrollBar->GetScrollPos ()) { + SetPos (m_rcItem); + } + } + break; + } + } + + bool CContainerUI::SetSubControlText (string_view_t pstrSubControlName, string_view_t pstrText) { + CControlUI* pSubControl = nullptr; + pSubControl = this->FindSubControl (pstrSubControlName); + if (pSubControl) { + pSubControl->SetText (pstrText); + return TRUE; + } else + return FALSE; + } + + bool CContainerUI::SetSubControlFixedHeight (string_view_t pstrSubControlName, int cy) { + CControlUI* pSubControl = nullptr; + pSubControl = this->FindSubControl (pstrSubControlName); + if (pSubControl) { + pSubControl->SetFixedHeight (cy); + return TRUE; + } else + return FALSE; + } + + bool CContainerUI::SetSubControlFixedWdith (string_view_t pstrSubControlName, int cx) { + CControlUI* pSubControl = nullptr; + pSubControl = this->FindSubControl (pstrSubControlName); + if (pSubControl) { + pSubControl->SetFixedWidth (cx); + return TRUE; + } else + return FALSE; + } + + bool CContainerUI::SetSubControlUserData (string_view_t pstrSubControlName, string_view_t pstrText) { + CControlUI* pSubControl = nullptr; + pSubControl = this->FindSubControl (pstrSubControlName); + if (pSubControl) { + pSubControl->SetUserData (pstrText); + return TRUE; + } else + return FALSE; + } + + CDuiString CContainerUI::GetSubControlText (string_view_t pstrSubControlName) { + CControlUI* pSubControl = nullptr; + pSubControl = this->FindSubControl (pstrSubControlName); + if (!pSubControl) + return _T (""); + else + return pSubControl->GetText (); + } + + int CContainerUI::GetSubControlFixedHeight (string_view_t pstrSubControlName) { + CControlUI* pSubControl = nullptr; + pSubControl = this->FindSubControl (pstrSubControlName); + if (!pSubControl) + return -1; + else + return pSubControl->GetFixedHeight (); + } + + int CContainerUI::GetSubControlFixedWdith (string_view_t pstrSubControlName) { + CControlUI* pSubControl = nullptr; + pSubControl = this->FindSubControl (pstrSubControlName); + if (!pSubControl) + return -1; + else + return pSubControl->GetFixedWidth (); + } + + const CDuiString CContainerUI::GetSubControlUserData (string_view_t pstrSubControlName) { + CControlUI* pSubControl = nullptr; + pSubControl = this->FindSubControl (pstrSubControlName); + if (!pSubControl) + return _T (""); + else + return pSubControl->GetUserData (); + } + + CControlUI* CContainerUI::FindSubControl (string_view_t pstrSubControlName) { + return static_cast(GetManager ()->FindSubControlByName (this, pstrSubControlName)); + } + +} // namespace DuiLib diff --git a/DuiLib/Core/UIContainer.h b/DuiLib/Core/UIContainer.h new file mode 100644 index 0000000..b163793 --- /dev/null +++ b/DuiLib/Core/UIContainer.h @@ -0,0 +1,137 @@ +#ifndef __UICONTAINER_H__ +#define __UICONTAINER_H__ + +#pragma once + +namespace DuiLib { + ///////////////////////////////////////////////////////////////////////////////////// + // + + class IContainerUI { + public: + virtual CControlUI* GetItemAt (int iIndex) const = 0; + virtual int GetItemIndex (CControlUI* pControl) const = 0; + virtual bool SetItemIndex (CControlUI* pControl, int iIndex) = 0; + virtual int GetCount () const = 0; + virtual bool Add (CControlUI* pControl) = 0; + virtual bool AddAt (CControlUI* pControl, int iIndex) = 0; + virtual bool Remove (CControlUI* pControl) = 0; + virtual bool RemoveAt (int iIndex) = 0; + virtual void RemoveAll () = 0; + }; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + class CScrollBarUI; + + class UILIB_API CContainerUI: public CControlUI, public IContainerUI { + DECLARE_DUICONTROL (CContainerUI) + + public: + CContainerUI (); + virtual ~CContainerUI (); + + public: + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + CControlUI* GetItemAt (int iIndex) const; + int GetItemIndex (CControlUI* pControl) const; + bool SetItemIndex (CControlUI* pControl, int iIndex); + int GetCount () const; + bool Add (CControlUI* pControl); + bool AddAt (CControlUI* pControl, int iIndex); + bool Remove (CControlUI* pControl); + bool RemoveAt (int iIndex); + void RemoveAll (); + + void DoEvent (TEventUI& event); + void SetVisible (bool bVisible = true); + void SetInternVisible (bool bVisible = true); + void SetEnabled (bool bEnabled); + void SetMouseEnabled (bool bEnable = true); + + virtual RECT GetInset () const; + virtual void SetInset (RECT rcInset); // ڱ߾࣬൱ÿͻ + virtual int GetChildPadding () const; + virtual void SetChildPadding (int iPadding); + virtual UINT GetChildAlign (CControlUI *pControl = nullptr) const; + virtual void SetChildAlign (UINT iAlign); + virtual UINT GetChildVAlign () const; + virtual void SetChildVAlign (UINT iVAlign); + virtual bool IsAutoDestroy () const; + virtual void SetAutoDestroy (bool bAuto); + virtual bool IsDelayedDestroy () const; + virtual void SetDelayedDestroy (bool bDelayed); + virtual bool IsMouseChildEnabled () const; + virtual void SetMouseChildEnabled (bool bEnable = true); + + virtual int FindSelectable (int iIndex, bool bForward = true) const; + + RECT GetClientPos () const; + void SetPos (RECT rc, bool bNeedInvalidate = true); + void Move (SIZE szOffset, bool bNeedInvalidate = true); + bool DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl); + + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + void SetManager (CPaintManagerUI* pManager, CControlUI* pParent, bool bInit = true); + CControlUI* FindControl (FINDCONTROLPROC Proc, LPVOID pData, UINT uFlags); + + bool SetSubControlText (string_view_t pstrSubControlName, string_view_t pstrText); + bool SetSubControlFixedHeight (string_view_t pstrSubControlName, int cy); + bool SetSubControlFixedWdith (string_view_t pstrSubControlName, int cx); + bool SetSubControlUserData (string_view_t pstrSubControlName, string_view_t pstrText); + + CDuiString GetSubControlText (string_view_t pstrSubControlName); + int GetSubControlFixedHeight (string_view_t pstrSubControlName); + int GetSubControlFixedWdith (string_view_t pstrSubControlName); + const CDuiString GetSubControlUserData (string_view_t pstrSubControlName); + CControlUI* FindSubControl (string_view_t pstrSubControlName); + + virtual SIZE GetScrollPos () const; + virtual SIZE GetScrollRange () const; + virtual void SetScrollPos (SIZE szPos, bool bMsg = true); + virtual void SetScrollStepSize (int nSize); + virtual int GetScrollStepSize () const; + virtual void LineUp (); + virtual void LineDown (); + virtual void PageUp (); + virtual void PageDown (); + virtual void HomeUp (); + virtual void EndDown (); + virtual void LineLeft (); + virtual void LineRight (); + virtual void PageLeft (); + virtual void PageRight (); + virtual void HomeLeft (); + virtual void EndRight (); + virtual void EnableScrollBar (bool bEnableVertical = true, bool bEnableHorizontal = false); + virtual CScrollBarUI* GetVerticalScrollBar () const; + virtual CScrollBarUI* GetHorizontalScrollBar () const; + + protected: + virtual void SetFloatPos (int iIndex); + virtual void ProcessScrollBar (RECT rc, int cxRequired, int cyRequired); + + protected: + CStdPtrArray m_items; + RECT m_rcInset = { 0 }; + int m_iChildPadding = 0; + UINT m_iChildAlign = DT_LEFT; + UINT m_iChildVAlign = DT_TOP; + bool m_bAutoDestroy = true; + bool m_bDelayedDestroy = true; + bool m_bMouseChildEnabled = true; + int m_nScrollStepSize = 60; + + CScrollBarUI *m_pVerticalScrollBar = nullptr; + CScrollBarUI *m_pHorizontalScrollBar = nullptr; + CDuiString m_sVerticalScrollBarStyle; + CDuiString m_sHorizontalScrollBarStyle; + }; + +} // namespace DuiLib + +#endif // __UICONTAINER_H__ diff --git a/DuiLib/Core/UIControl.cpp b/DuiLib/Core/UIControl.cpp new file mode 100644 index 0000000..f36609f --- /dev/null +++ b/DuiLib/Core/UIControl.cpp @@ -0,0 +1,1145 @@ +#include "StdAfx.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CControlUI) + + CControlUI::CControlUI () { + m_cXY.cx = m_cXY.cy = 0; + m_cxyFixed.cx = m_cxyFixed.cy = 0; + m_cxyMin.cx = m_cxyMin.cy = 0; + m_cxyMax.cx = m_cxyMax.cy = 9999; + m_cxyBorderRound.cx = m_cxyBorderRound.cy = 0; + + ::ZeroMemory (&m_rcPadding, sizeof (RECT)); + ::ZeroMemory (&m_rcItem, sizeof (RECT)); + ::ZeroMemory (&m_rcPaint, sizeof (RECT)); + ::ZeroMemory (&m_rcBorderSize, sizeof (RECT)); + m_piFloatPercent.left = m_piFloatPercent.top = m_piFloatPercent.right = m_piFloatPercent.bottom = 0.0f; + } + + CControlUI::~CControlUI () { + if (OnDestroy) OnDestroy (this); + RemoveAllCustomAttribute (); + if (m_pManager) m_pManager->ReapObjects (this); + } + + string_view_t CControlUI::GetName () const { + return m_sName; + } + + void CControlUI::SetName (string_view_t pstrName) { + m_sName = pstrName; + } + + LPVOID CControlUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_CONTROL) return this; + return nullptr; + } + + string_view_t CControlUI::GetClass () const { + return _T ("ControlUI"); + } + + UINT CControlUI::GetControlFlags () const { + return 0; + } + + bool CControlUI::Activate () { + if (!IsVisible ()) return false; + if (!IsEnabled ()) return false; + return true; + } + + CPaintManagerUI* CControlUI::GetManager () const { + return m_pManager; + } + + void CControlUI::SetManager (CPaintManagerUI* pManager, CControlUI* pParent, bool bInit) { + m_pManager = pManager; + m_pParent = pParent; + if (bInit && m_pParent) Init (); + } + + CControlUI* CControlUI::GetParent () const { + return m_pParent; + } + + bool CControlUI::SetTimer (UINT nTimerID, UINT nElapse) { + if (!m_pManager) return false; + + return m_pManager->SetTimer (this, nTimerID, nElapse); + } + + void CControlUI::KillTimer (UINT nTimerID) { + if (!m_pManager) return; + + m_pManager->KillTimer (this, nTimerID); + } + + CDuiString CControlUI::GetText () const { + if (!IsResourceText ()) return m_sText; + return CResourceManager::GetInstance ()->GetText (m_sText); + } + + void CControlUI::SetText (string_view_t pstrText) { + if (m_sText == pstrText) return; + + m_sText = pstrText; + // xmlз + m_sText.Replace (_T ("{\\n}"), _T ("\r\n")); + Invalidate (); + } + + bool CControlUI::IsResourceText () const { + return m_bResourceText; + } + + void CControlUI::SetResourceText (bool bResource) { + if (m_bResourceText == bResource) return; + m_bResourceText = bResource; + Invalidate (); + } + + bool CControlUI::IsDragEnabled () const { + return m_bDragEnabled; + } + + void CControlUI::SetDragEnable (bool bDrag) { + m_bDragEnabled = bDrag; + } + + bool CControlUI::IsDropEnabled () const { + return m_bDropEnabled; + } + + void CControlUI::SetDropEnable (bool bDrop) { + m_bDropEnabled = bDrop; + } + + string_view_t CControlUI::GetGradient () { + return m_sGradient; + } + + void CControlUI::SetGradient (string_view_t pStrImage) { + if (m_sGradient == pStrImage) return; + + m_sGradient = pStrImage; + Invalidate (); + } + + DWORD CControlUI::GetBkColor () const { + return m_dwBackColor; + } + + void CControlUI::SetBkColor (DWORD dwBackColor) { + if (m_dwBackColor == dwBackColor) return; + + m_dwBackColor = dwBackColor; + Invalidate (); + } + + DWORD CControlUI::GetBkColor2 () const { + return m_dwBackColor2; + } + + void CControlUI::SetBkColor2 (DWORD dwBackColor) { + if (m_dwBackColor2 == dwBackColor) return; + + m_dwBackColor2 = dwBackColor; + Invalidate (); + } + + DWORD CControlUI::GetBkColor3 () const { + return m_dwBackColor3; + } + + void CControlUI::SetBkColor3 (DWORD dwBackColor) { + if (m_dwBackColor3 == dwBackColor) return; + + m_dwBackColor3 = dwBackColor; + Invalidate (); + } + + DWORD CControlUI::GetForeColor () const { + return m_dwForeColor; + } + + void CControlUI::SetForeColor (DWORD dwForeColor) { + if (m_dwForeColor == dwForeColor) return; + + m_dwForeColor = dwForeColor; + Invalidate (); + } + + string_view_t CControlUI::GetBkImage () { + return m_sBkImage; + } + + void CControlUI::SetBkImage (string_view_t pStrImage) { + //if (m_pManager) m_pManager->RemoveImage (pStrImage); + if (m_sBkImage == pStrImage) return; + + m_sBkImage = pStrImage; + Invalidate (); + } + + string_view_t CControlUI::GetForeImage () const { + return m_sForeImage; + } + + void CControlUI::SetForeImage (string_view_t pStrImage) { + if (m_sForeImage == pStrImage) return; + + m_sForeImage = pStrImage; + Invalidate (); + } + + DWORD CControlUI::GetBorderColor () const { + return m_dwBorderColor; + } + + void CControlUI::SetBorderColor (DWORD dwBorderColor) { + if (m_dwBorderColor == dwBorderColor) return; + + m_dwBorderColor = dwBorderColor; + Invalidate (); + } + + DWORD CControlUI::GetFocusBorderColor () const { + return m_dwFocusBorderColor; + } + + void CControlUI::SetFocusBorderColor (DWORD dwBorderColor) { + if (m_dwFocusBorderColor == dwBorderColor) return; + + m_dwFocusBorderColor = dwBorderColor; + Invalidate (); + } + + bool CControlUI::IsColorHSL () const { + return m_bColorHSL; + } + + void CControlUI::SetColorHSL (bool bColorHSL) { + if (m_bColorHSL == bColorHSL) return; + + m_bColorHSL = bColorHSL; + Invalidate (); + } + + int CControlUI::GetBorderSize () const { + if (m_pManager) return m_pManager->GetDPIObj ()->Scale (m_nBorderSize); + return m_nBorderSize; + } + + void CControlUI::SetBorderSize (int nSize) { + if (m_nBorderSize == nSize) return; + + m_nBorderSize = nSize; + Invalidate (); + } + + void CControlUI::SetBorderSize (RECT rc) { + m_rcBorderSize = rc; + Invalidate (); + } + + SIZE CControlUI::GetBorderRound () const { + if (m_pManager) return m_pManager->GetDPIObj ()->Scale (m_cxyBorderRound); + return m_cxyBorderRound; + } + + void CControlUI::SetBorderRound (SIZE cxyRound) { + m_cxyBorderRound = cxyRound; + Invalidate (); + } + + bool CControlUI::DrawImage (HDC hDC, string_view_t pStrImage, string_view_t pStrModify) { + return CRenderEngine::DrawImageString (hDC, m_pManager, m_rcItem, m_rcPaint, pStrImage, pStrModify, m_instance); + } + + const RECT& CControlUI::GetPos () const { + return m_rcItem; + } + + RECT CControlUI::GetRelativePos () const { + CControlUI* pParent = GetParent (); + if (pParent) { + RECT rcParentPos = pParent->GetPos (); + RECT rcRelativePos (m_rcItem); + ::OffsetRect (&rcRelativePos, -rcParentPos.left, -rcParentPos.top); + return rcRelativePos; + } else { + return { 0, 0, 0, 0 }; + } + } + + RECT CControlUI::GetClientPos () const { + return m_rcItem; + } + void CControlUI::SetPos (RECT rc, bool bNeedInvalidate) { + if (rc.right < rc.left) rc.right = rc.left; + if (rc.bottom < rc.top) rc.bottom = rc.top; + + RECT invalidateRc = m_rcItem; + if (::IsRectEmpty (&invalidateRc)) invalidateRc = rc; + + m_rcItem = rc; + if (!m_pManager) return; + + if (!m_bSetPos) { + m_bSetPos = true; + if (OnSize) OnSize (this); + m_bSetPos = false; + } + + m_bUpdateNeeded = false; + + if (bNeedInvalidate && IsVisible ()) { + invalidateRc.left = min (invalidateRc.left, m_rcItem.left); + invalidateRc.top = min (invalidateRc.top, m_rcItem.top); + invalidateRc.right = max (invalidateRc.right, m_rcItem.right); + invalidateRc.bottom = max (invalidateRc.bottom, m_rcItem.bottom); + CControlUI* pParent = this; + RECT rcTemp = { 0 }; + RECT rcParent = { 0 }; + while (!!(pParent = pParent->GetParent ())) { + if (!pParent->IsVisible ()) return; + rcTemp = invalidateRc; + rcParent = pParent->GetPos (); + if (!::IntersectRect (&invalidateRc, &rcTemp, &rcParent)) return; + } + m_pManager->Invalidate (invalidateRc); + } + } + + void CControlUI::Move (SIZE szOffset, bool bNeedInvalidate) { + m_cXY.cx += szOffset.cx; + m_cXY.cy += szOffset.cy; + NeedParentUpdate (); + } + + int CControlUI::GetWidth () const { + return m_rcItem.right - m_rcItem.left; + } + + int CControlUI::GetHeight () const { + return m_rcItem.bottom - m_rcItem.top; + } + + int CControlUI::GetX () const { + return m_rcItem.left; + } + + int CControlUI::GetY () const { + return m_rcItem.top; + } + + RECT CControlUI::GetPadding () const { + if (m_pManager) return m_pManager->GetDPIObj ()->Scale (m_rcPadding); + return m_rcPadding; + } + + void CControlUI::SetPadding (RECT rcPadding) { + m_rcPadding = rcPadding; + NeedParentUpdate (); + } + + SIZE CControlUI::GetFixedXY () const { + if (m_pManager) return m_pManager->GetDPIObj ()->Scale (m_cXY); + return m_cXY; + } + + void CControlUI::SetFixedXY (SIZE szXY) { + m_cXY.cx = szXY.cx; + m_cXY.cy = szXY.cy; + NeedParentUpdate (); + } + + int CControlUI::GetFixedWidth () const { + if (m_pManager) { + return m_pManager->GetDPIObj ()->Scale (m_cxyFixed.cx); + } + + return m_cxyFixed.cx; + } + + void CControlUI::SetFixedWidth (int cx) { + if (cx < 0) return; + m_cxyFixed.cx = cx; + NeedParentUpdate (); + } + + int CControlUI::GetFixedHeight () const { + if (m_pManager) { + return m_pManager->GetDPIObj ()->Scale (m_cxyFixed.cy); + } + + return m_cxyFixed.cy; + } + + void CControlUI::SetFixedHeight (int cy) { + if (cy < 0) return; + m_cxyFixed.cy = cy; + NeedParentUpdate (); + } + + int CControlUI::GetMinWidth () const { + if (m_pManager) { + return m_pManager->GetDPIObj ()->Scale (m_cxyMin.cx); + } + return m_cxyMin.cx; + } + + void CControlUI::SetMinWidth (int cx) { + if (m_cxyMin.cx == cx) return; + + if (cx < 0) return; + m_cxyMin.cx = cx; + NeedParentUpdate (); + } + + int CControlUI::GetMaxWidth () const { + if (m_pManager) { + return m_pManager->GetDPIObj ()->Scale (m_cxyMax.cx); + } + return m_cxyMax.cx; + } + + void CControlUI::SetMaxWidth (int cx) { + if (m_cxyMax.cx == cx) return; + + if (cx < 0) return; + m_cxyMax.cx = cx; + NeedParentUpdate (); + } + + int CControlUI::GetMinHeight () const { + if (m_pManager) { + return m_pManager->GetDPIObj ()->Scale (m_cxyMin.cy); + } + + return m_cxyMin.cy; + } + + void CControlUI::SetMinHeight (int cy) { + if (m_cxyMin.cy == cy) return; + + if (cy < 0) return; + m_cxyMin.cy = cy; + NeedParentUpdate (); + } + + int CControlUI::GetMaxHeight () const { + if (m_pManager) { + return m_pManager->GetDPIObj ()->Scale (m_cxyMax.cy); + } + + return m_cxyMax.cy; + } + + void CControlUI::SetMaxHeight (int cy) { + if (m_cxyMax.cy == cy) return; + + if (cy < 0) return; + m_cxyMax.cy = cy; + NeedParentUpdate (); + } + + TPercentInfo CControlUI::GetFloatPercent () const { + return m_piFloatPercent; + } + + void CControlUI::SetFloatPercent (TPercentInfo piFloatPercent) { + m_piFloatPercent = piFloatPercent; + NeedParentUpdate (); + } + + void CControlUI::SetFloatAlign (UINT uAlign) { + m_uFloatAlign = uAlign; + NeedParentUpdate (); + } + + UINT CControlUI::GetFloatAlign () const { + return m_uFloatAlign; + } + + CDuiString CControlUI::GetToolTip () const { + if (!IsResourceText ()) return m_sToolTip; + return CResourceManager::GetInstance ()->GetText (m_sToolTip); + } + + void CControlUI::SetToolTip (string_view_t pstrText) { + CDuiString strTemp (pstrText); + strTemp.Replace (_T (""), _T ("\r\n")); + m_sToolTip = strTemp; + } + + void CControlUI::SetToolTipWidth (int nWidth) { + m_nTooltipWidth = nWidth; + } + + int CControlUI::GetToolTipWidth (void) { + if (m_pManager) return m_pManager->GetDPIObj ()->Scale (m_nTooltipWidth); + return m_nTooltipWidth; + } + + WORD CControlUI::GetCursor () { + return m_wCursor; + } + + void CControlUI::SetCursor (WORD wCursor) { + m_wCursor = wCursor; + Invalidate (); + } + + TCHAR CControlUI::GetShortcut () const { + return m_chShortcut; + } + + void CControlUI::SetShortcut (TCHAR ch) { + m_chShortcut = ch; + } + + bool CControlUI::IsContextMenuUsed () const { + return m_bMenuUsed; + } + + void CControlUI::SetContextMenuUsed (bool bMenuUsed) { + m_bMenuUsed = bMenuUsed; + } + + const CDuiString& CControlUI::GetUserData () { + return m_sUserData; + } + + void CControlUI::SetUserData (string_view_t pstrText) { + m_sUserData = pstrText; + } + + UINT_PTR CControlUI::GetTag () const { + return m_pTag; + } + + void CControlUI::SetTag (UINT_PTR pTag) { + m_pTag = pTag; + } + + bool CControlUI::IsVisible () const { + + return m_bVisible && m_bInternVisible; + } + + void CControlUI::SetVisible (bool bVisible) { + if (m_bVisible == bVisible) return; + + bool v = IsVisible (); + m_bVisible = bVisible; + if (m_bFocused) m_bFocused = false; + if (!bVisible && m_pManager && m_pManager->GetFocus () == this) { + m_pManager->SetFocus (nullptr); + } + if (IsVisible () != v) { + NeedParentUpdate (); + } + } + + void CControlUI::SetInternVisible (bool bVisible) { + m_bInternVisible = bVisible; + if (!bVisible && m_pManager && m_pManager->GetFocus () == this) { + m_pManager->SetFocus (nullptr); + } + } + + bool CControlUI::IsEnabled () const { + return m_bEnabled; + } + + void CControlUI::SetEnabled (bool bEnabled) { + if (m_bEnabled == bEnabled) return; + + m_bEnabled = bEnabled; + Invalidate (); + } + + bool CControlUI::IsMouseEnabled () const { + return m_bMouseEnabled; + } + + void CControlUI::SetMouseEnabled (bool bEnabled) { + m_bMouseEnabled = bEnabled; + } + + bool CControlUI::IsKeyboardEnabled () const { + return m_bKeyboardEnabled; + } + void CControlUI::SetKeyboardEnabled (bool bEnabled) { + m_bKeyboardEnabled = bEnabled; + } + + bool CControlUI::IsFocused () const { + return m_bFocused; + } + + void CControlUI::SetFocus () { + if (m_pManager) m_pManager->SetFocus (this); + } + + bool CControlUI::IsFloat () const { + return m_bFloat; + } + + void CControlUI::SetFloat (bool bFloat) { + if (m_bFloat == bFloat) return; + + m_bFloat = bFloat; + NeedParentUpdate (); + } + + bool CControlUI::IsDynamic (POINT &pt) const { + return m_isDynamic; + } + + void CControlUI::SetIsDynamic (bool bIsDynamic) { + m_isDynamic = bIsDynamic; + } + + CControlUI* CControlUI::FindControl (FINDCONTROLPROC Proc, LPVOID pData, UINT uFlags) { + if ((uFlags & UIFIND_VISIBLE) != 0 && !IsVisible ()) return nullptr; + if ((uFlags & UIFIND_ENABLED) != 0 && !IsEnabled ()) return nullptr; + if ((uFlags & UIFIND_HITTEST) != 0 && (!m_bMouseEnabled || !::PtInRect (&m_rcItem, *static_cast(pData)))) return nullptr; + return Proc (this, pData); + } + + void CControlUI::Invalidate () { + if (!IsVisible ()) return; + + RECT invalidateRc = m_rcItem; + + CControlUI* pParent = this; + RECT rcTemp = { 0 }; + RECT rcParent = { 0 }; + while (!!(pParent = pParent->GetParent ())) { + rcTemp = invalidateRc; + rcParent = pParent->GetPos (); + if (!::IntersectRect (&invalidateRc, &rcTemp, &rcParent)) { + return; + } + } + + if (m_pManager) m_pManager->Invalidate (invalidateRc); + } + + bool CControlUI::IsUpdateNeeded () const { + return m_bUpdateNeeded; + } + + void CControlUI::NeedUpdate () { + if (!IsVisible ()) return; + m_bUpdateNeeded = true; + Invalidate (); + + if (m_pManager) m_pManager->NeedUpdate (); + } + + void CControlUI::NeedParentUpdate () { + if (GetParent ()) { + GetParent ()->NeedUpdate (); + GetParent ()->Invalidate (); + } else { + NeedUpdate (); + } + + if (m_pManager) m_pManager->NeedUpdate (); + } + + DWORD CControlUI::GetAdjustColor (DWORD dwColor) { + if (!m_bColorHSL) return dwColor; + short H, S, L; + CPaintManagerUI::GetHSL (&H, &S, &L); + return CRenderEngine::AdjustColor (dwColor, H, S, L); + } + + void CControlUI::Init () { + DoInit (); + if (OnInit) OnInit (this); + } + + void CControlUI::DoInit () { + + } + + void CControlUI::Event (TEventUI& event) { + if (OnEvent (&event)) DoEvent (event); + } + + void CControlUI::DoEvent (TEventUI& event) { + if (event.Type == UIEVENT_SETCURSOR) { + if (GetCursor ()) { + ::SetCursor (::LoadCursor (nullptr, MAKEINTRESOURCE (GetCursor ()))); + } else { + ::SetCursor (::LoadCursor (nullptr, IDC_ARROW)); + } + return; + } + + if (event.Type == UIEVENT_SETFOCUS) { + m_bFocused = true; + Invalidate (); + return; + } + if (event.Type == UIEVENT_KILLFOCUS) { + m_bFocused = false; + Invalidate (); + return; + } + if (event.Type == UIEVENT_TIMER) { + m_pManager->SendNotify (this, DUI_MSGTYPE_TIMER, event.wParam, event.lParam); + return; + } + if (event.Type == UIEVENT_CONTEXTMENU) { + if (IsContextMenuUsed ()) { + m_pManager->SendNotify (this, DUI_MSGTYPE_MENU, event.wParam, event.lParam); + return; + } + } + + if (m_pParent) m_pParent->DoEvent (event); + } + + + void CControlUI::SetVirtualWnd (string_view_t pstrValue) { + m_sVirtualWnd = pstrValue; + m_pManager->UsedVirtualWnd (true); + } + + CDuiString CControlUI::GetVirtualWnd () const { + if (!m_sVirtualWnd.empty ()) + return m_sVirtualWnd; + CControlUI* pParent = GetParent (); + if (pParent) { + return pParent->GetVirtualWnd (); + } else { + return _T (""); + } + } + + void CControlUI::AddCustomAttribute (string_view_t pstrName, string_view_t pstrAttr) { + m_mCustomAttrs[pstrName.data ()] = pstrAttr; + } + + string_view_t CControlUI::GetCustomAttribute (string_view_t pstrName) { + return m_mCustomAttrs[pstrName.data ()]; + } + + bool CControlUI::RemoveCustomAttribute (string_view_t pstrName) { + m_mCustomAttrs.erase (pstrName.data ()); + return true; + } + + void CControlUI::RemoveAllCustomAttribute () { + m_mCustomAttrs.clear (); + } + + void CControlUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + // Ƿʽ + if (m_pManager) { + string_view_t pStyle = m_pManager->GetStyle (pstrValue); + if (!pStyle.empty ()) { + ApplyAttributeList (pStyle); + return; + } + } + if (pstrName == _T ("pos")) { + RECT rcPos = FawTools::parse_rect (pstrValue); + SIZE szXY = { rcPos.left >= 0 ? rcPos.left : rcPos.right, rcPos.top >= 0 ? rcPos.top : rcPos.bottom }; + SetFixedXY (szXY); + SetFixedWidth (rcPos.right - rcPos.left); + SetFixedHeight (rcPos.bottom - rcPos.top); + } else if (pstrName == _T ("float")) { + CDuiString nValue = pstrValue; + // ̬Ա + if (nValue.find (',') == string_t::npos) { + SetFloat (FawTools::parse_bool (pstrValue)); + } else { + TPercentInfo piFloatPercent = FawTools::parse_TPercentInfo (pstrValue); + SetFloatPercent (piFloatPercent); + SetFloat (true); + } + } else if (pstrName == _T ("floatalign")) { + UINT uAlign = GetFloatAlign (); + // + while (!pstrValue.empty ()) { + CDuiString sValue; + while (pstrValue[0] == _T (',') || pstrValue[0] == _T (' ')) pstrValue = pstrValue.substr (1); + + while (!pstrValue.empty () && pstrValue[0] != _T (',') && pstrValue[0] != _T (' ')) { + string_view_t pstrTemp = pstrValue.substr (1); + while (pstrValue.length () > pstrTemp.length ()) { + sValue += pstrValue[0]; + pstrValue = pstrValue.substr (1); + } + } + if (sValue == _T ("nullptr")) { + uAlign = 0; + } else if (sValue == _T ("left")) { + uAlign &= ~(DT_CENTER | DT_RIGHT); + uAlign |= DT_LEFT; + } else if (sValue == _T ("center")) { + uAlign &= ~(DT_LEFT | DT_RIGHT); + uAlign |= DT_CENTER; + } else if (sValue == _T ("right")) { + uAlign &= ~(DT_LEFT | DT_CENTER); + uAlign |= DT_RIGHT; + } else if (sValue == _T ("top")) { + uAlign &= ~(DT_BOTTOM | DT_VCENTER); + uAlign |= DT_TOP; + } else if (sValue == _T ("vcenter")) { + uAlign &= ~(DT_TOP | DT_BOTTOM); + uAlign |= DT_VCENTER; + } else if (sValue == _T ("bottom")) { + uAlign &= ~(DT_TOP | DT_VCENTER); + uAlign |= DT_BOTTOM; + } + } + SetFloatAlign (uAlign); + } else if (pstrName == _T ("padding")) { + RECT rcPadding = FawTools::parse_rect (pstrValue); + SetPadding (rcPadding); + } else if (pstrName == _T ("gradient")) { + SetGradient (pstrValue); + } else if (pstrName == _T ("bkcolor") || pstrName == _T ("bkcolor1")) { + SetBkColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("bkcolor2")) { + SetBkColor2 ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("bkcolor3")) { + SetBkColor3 ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("forecolor")) { + SetForeColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("bordercolor")) { + SetBorderColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("focusbordercolor")) { + SetFocusBorderColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("colorhsl")) { + SetColorHSL (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("bordersize")) { + if (pstrValue.find (',') == string_t::npos) { + SetBorderSize (FawTools::parse_dec (pstrValue)); + } else { + RECT rcPadding = FawTools::parse_rect (pstrValue); + SetBorderSize (rcPadding); + } + } else if (pstrName == _T ("leftbordersize")) { + SetLeftBorderSize (_ttoi (pstrValue.data ())); + } else if (pstrName == _T ("topbordersize")) { + SetTopBorderSize (_ttoi (pstrValue.data ())); + } else if (pstrName == _T ("rightbordersize")) { + SetRightBorderSize (_ttoi (pstrValue.data ())); + } else if (pstrName == _T ("bottombordersize")) { + SetBottomBorderSize (_ttoi (pstrValue.data ())); + } else if (pstrName == _T ("borderstyle")) { + SetBorderStyle (_ttoi (pstrValue.data ())); + } else if (pstrName == _T ("borderround")) { + SIZE cxyRound = FawTools::parse_size (pstrValue); + SetBorderRound (cxyRound); + } else if (pstrName == _T ("bkimage")) SetBkImage (pstrValue); + else if (pstrName == _T ("foreimage")) SetForeImage (pstrValue); + else if (pstrName == _T ("width")) SetFixedWidth (_ttoi (pstrValue.data ())); + else if (pstrName == _T ("height")) SetFixedHeight (_ttoi (pstrValue.data ())); + else if (pstrName == _T ("minwidth")) SetMinWidth (_ttoi (pstrValue.data ())); + else if (pstrName == _T ("minheight")) SetMinHeight (_ttoi (pstrValue.data ())); + else if (pstrName == _T ("maxwidth")) SetMaxWidth (_ttoi (pstrValue.data ())); + else if (pstrName == _T ("maxheight")) SetMaxHeight (_ttoi (pstrValue.data ())); + else if (pstrName == _T ("name")) SetName (pstrValue); + else if (pstrName == _T ("drag")) SetDragEnable (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("drop")) SetDropEnable (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("resourcetext")) SetResourceText (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("text")) SetText (pstrValue); + else if (pstrName == _T ("tooltip")) SetToolTip (pstrValue); + else if (pstrName == _T ("userdata")) SetUserData (pstrValue); + else if (pstrName == _T ("enabled")) SetEnabled (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("mouse")) SetMouseEnabled (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("keyboard")) SetKeyboardEnabled (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("visible")) SetVisible (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("float")) SetFloat (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("shortcut")) SetShortcut (pstrValue[0]); + else if (pstrName == _T ("menu")) SetContextMenuUsed (FawTools::parse_bool (pstrValue)); + else if (pstrName == _T ("cursor") && !pstrValue.empty ()) { + if (pstrValue == _T ("arrow")) SetCursor (DUI_ARROW); + else if (pstrValue == _T ("ibeam")) SetCursor (DUI_IBEAM); + else if (pstrValue == _T ("wait")) SetCursor (DUI_WAIT); + else if (pstrValue == _T ("cross")) SetCursor (DUI_CROSS); + else if (pstrValue == _T ("uparrow")) SetCursor (DUI_UPARROW); + else if (pstrValue == _T ("size")) SetCursor (DUI_SIZE); + else if (pstrValue == _T ("icon")) SetCursor (DUI_ICON); + else if (pstrValue == _T ("sizenwse")) SetCursor (DUI_SIZENWSE); + else if (pstrValue == _T ("sizenesw")) SetCursor (DUI_SIZENESW); + else if (pstrValue == _T ("sizewe")) SetCursor (DUI_SIZEWE); + else if (pstrValue == _T ("sizens")) SetCursor (DUI_SIZENS); + else if (pstrValue == _T ("sizeall")) SetCursor (DUI_SIZEALL); + else if (pstrValue == _T ("no")) SetCursor (DUI_NO); + else if (pstrValue == _T ("hand")) SetCursor (DUI_HAND); + } else if (pstrName == _T ("virtualwnd")) SetVirtualWnd (pstrValue); + else if (pstrName == _T ("innerstyle")) { + CDuiString sXmlData = pstrValue; + sXmlData.Replace (_T ("""), _T ("\"")); + string_view_t pstrList = sXmlData; + CDuiString sItem; + CDuiString sValue; + while (!pstrList.empty ()) { + sItem.clear (); + sValue.clear (); + while (!pstrList.empty () && pstrList[0] != _T ('=')) { + string_view_t pstrTemp = pstrList.substr (1); + while (pstrList.length () > pstrTemp.length ()) { + sItem += pstrList[0]; + pstrList = pstrList.substr (1); + } + } + ASSERT (pstrList[0] == _T ('=')); + if (pstrList[0] != _T ('=')) return; + pstrList = pstrList.substr (1); + ASSERT (pstrList[0] == _T ('\"')); + if (pstrList[0] != _T ('\"')) return; + pstrList = pstrList.substr (1); + while (!pstrList.empty () && pstrList[0] != _T ('\"')) { + string_view_t pstrTemp = pstrList.substr (1); + while (pstrList.length () > pstrTemp.length ()) { + sValue += pstrList[0]; + pstrList = pstrList.substr (1); + } + } + ASSERT (pstrList[0] == _T ('\"')); + if (pstrList[0] != _T ('\"')) return; + pstrList = pstrList.substr (1); + SetAttribute (sItem, sValue); + if (pstrList[0] != _T (' ')) return; + pstrList = pstrList.substr (1); + if (pstrList[0] != _T (',')) return; + pstrList = pstrList.substr (1); + } + } else if (pstrName == _T ("isdynamic")) { + SetIsDynamic (FawTools::parse_bool (pstrValue)); + } else { + AddCustomAttribute (pstrName, pstrValue); + } + } + + CControlUI* CControlUI::ApplyAttributeList (string_view_t pstrValue) { + // ʽ + if (m_pManager) { + string_view_t pStyle = m_pManager->GetStyle (pstrValue); + if (!pStyle.empty ()) { + return ApplyAttributeList (pStyle); + } + } + CDuiString sXmlData = pstrValue; + sXmlData.Replace (_T ("""), _T ("\"")); + auto pairs = FawTools::parse_keyvalue_pairs (sXmlData); + for (auto[str_key, str_value] : pairs) + SetAttribute (str_key, str_value); + return this; + } + + SIZE CControlUI::EstimateSize (SIZE szAvailable) { + if (m_pManager) + return m_pManager->GetDPIObj ()->Scale (m_cxyFixed); + return m_cxyFixed; + } + + bool CControlUI::Paint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + if (pStopControl == this) return false; + if (!::IntersectRect (&m_rcPaint, &rcPaint, &m_rcItem)) return true; + //if( OnPaint ) { + // if( !OnPaint(this) ) return true; + //} + if (!DoPaint (hDC, m_rcPaint, pStopControl)) return false; + return true; + } + + bool CControlUI::DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl) { + // ѭ򣺱ɫ->ͼ->״̬ͼ->ı->߿ + SIZE cxyBorderRound = { 0 }; + RECT rcBorderSize = { 0 }; + if (m_pManager) { + cxyBorderRound = GetManager ()->GetDPIObj ()->Scale (m_cxyBorderRound); + rcBorderSize = GetManager ()->GetDPIObj ()->Scale (m_rcBorderSize); + } else { + cxyBorderRound = m_cxyBorderRound; + rcBorderSize = m_rcBorderSize; + } + + if (cxyBorderRound.cx > 0 || cxyBorderRound.cy > 0) { + CRenderClip roundClip; + CRenderClip::GenerateRoundClip (hDC, m_rcPaint, m_rcItem, cxyBorderRound.cx, cxyBorderRound.cy, roundClip); + PaintBkColor (hDC); + PaintBkImage (hDC); + PaintStatusImage (hDC); + PaintForeColor (hDC); + PaintForeImage (hDC); + PaintText (hDC); + PaintBorder (hDC); + } else { + PaintBkColor (hDC); + PaintBkImage (hDC); + PaintStatusImage (hDC); + PaintForeColor (hDC); + PaintForeImage (hDC); + PaintText (hDC); + PaintBorder (hDC); + } + return true; + } + + void CControlUI::PaintBkColor (HDC hDC) { + if (m_dwBackColor != 0) { + bool bVer = (m_sGradient.CompareNoCase (_T ("hor")) != 0); + if (m_dwBackColor2 != 0) { + if (m_dwBackColor3 != 0) { + RECT rc = m_rcItem; + rc.bottom = (rc.bottom + rc.top) / 2; + CRenderEngine::DrawGradient (hDC, rc, GetAdjustColor (m_dwBackColor), GetAdjustColor (m_dwBackColor2), bVer, 8); + rc.top = rc.bottom; + rc.bottom = m_rcItem.bottom; + CRenderEngine::DrawGradient (hDC, rc, GetAdjustColor (m_dwBackColor2), GetAdjustColor (m_dwBackColor3), bVer, 8); + } else { + CRenderEngine::DrawGradient (hDC, m_rcItem, GetAdjustColor (m_dwBackColor), GetAdjustColor (m_dwBackColor2), bVer, 16); + } + } else if (m_dwBackColor >= 0xFF000000) CRenderEngine::DrawColor (hDC, m_rcPaint, GetAdjustColor (m_dwBackColor)); + else CRenderEngine::DrawColor (hDC, m_rcItem, GetAdjustColor (m_dwBackColor)); + } + } + + void CControlUI::PaintBkImage (HDC hDC) { + if (m_sBkImage.empty ()) return; + if (!DrawImage (hDC, m_sBkImage)) { + } + } + + void CControlUI::PaintStatusImage (HDC hDC) { + return; + } + + void CControlUI::PaintForeColor (HDC hDC) { + CRenderEngine::DrawColor (hDC, m_rcItem, GetAdjustColor (m_dwForeColor)); + } + + void CControlUI::PaintForeImage (HDC hDC) { + if (m_sForeImage.empty ()) return; + DrawImage (hDC, m_sForeImage); + } + + void CControlUI::PaintText (HDC hDC) { + return; + } + + void CControlUI::PaintBorder (HDC hDC) { + int nBorderSize; + SIZE cxyBorderRound = { 0 }; + RECT rcBorderSize = { 0 }; + if (m_pManager) { + nBorderSize = GetManager ()->GetDPIObj ()->Scale (m_nBorderSize); + cxyBorderRound = GetManager ()->GetDPIObj ()->Scale (m_cxyBorderRound); + rcBorderSize = GetManager ()->GetDPIObj ()->Scale (m_rcBorderSize); + } else { + nBorderSize = m_nBorderSize; + cxyBorderRound = m_cxyBorderRound; + rcBorderSize = m_rcBorderSize; + } + + if (m_dwBorderColor != 0 || m_dwFocusBorderColor != 0) { + //ԲDZ߿ + if (nBorderSize > 0 && (cxyBorderRound.cx > 0 || cxyBorderRound.cy > 0)) { + if (IsFocused () && m_dwFocusBorderColor != 0) + CRenderEngine::DrawRoundRect (hDC, m_rcItem, nBorderSize, cxyBorderRound.cx, cxyBorderRound.cy, GetAdjustColor (m_dwFocusBorderColor), m_nBorderStyle); + else + CRenderEngine::DrawRoundRect (hDC, m_rcItem, nBorderSize, cxyBorderRound.cx, cxyBorderRound.cy, GetAdjustColor (m_dwBorderColor), m_nBorderStyle); + } else { + if (IsFocused () && m_dwFocusBorderColor != 0 && nBorderSize > 0) { + CRenderEngine::DrawRect (hDC, m_rcItem, nBorderSize, GetAdjustColor (m_dwFocusBorderColor), m_nBorderStyle); + } else if (rcBorderSize.left > 0 || rcBorderSize.top > 0 || rcBorderSize.right > 0 || rcBorderSize.bottom > 0) { + RECT rcBorder = { 0 }; + + if (rcBorderSize.left > 0) { + rcBorder = m_rcItem; + rcBorder.right = rcBorder.left; + CRenderEngine::DrawLine (hDC, rcBorder, rcBorderSize.left, GetAdjustColor (m_dwBorderColor), m_nBorderStyle); + } + if (rcBorderSize.top > 0) { + rcBorder = m_rcItem; + rcBorder.bottom = rcBorder.top; + CRenderEngine::DrawLine (hDC, rcBorder, rcBorderSize.top, GetAdjustColor (m_dwBorderColor), m_nBorderStyle); + } + if (rcBorderSize.right > 0) { + rcBorder = m_rcItem; + rcBorder.right -= 1; + rcBorder.left = rcBorder.right; + CRenderEngine::DrawLine (hDC, rcBorder, rcBorderSize.right, GetAdjustColor (m_dwBorderColor), m_nBorderStyle); + } + if (rcBorderSize.bottom > 0) { + rcBorder = m_rcItem; + rcBorder.bottom -= 1; + rcBorder.top = rcBorder.bottom; + CRenderEngine::DrawLine (hDC, rcBorder, rcBorderSize.bottom, GetAdjustColor (m_dwBorderColor), m_nBorderStyle); + } + } else if (nBorderSize > 0) { + CRenderEngine::DrawRect (hDC, m_rcItem, nBorderSize, GetAdjustColor (m_dwBorderColor), m_nBorderStyle); + } + } + } + } + + void CControlUI::DoPostPaint (HDC hDC, const RECT& rcPaint) { + return; + } + + int CControlUI::GetLeftBorderSize () const { + if (m_pManager) return m_pManager->GetDPIObj ()->Scale (m_rcBorderSize.left); + return m_rcBorderSize.left; + } + + void CControlUI::SetLeftBorderSize (int nSize) { + m_rcBorderSize.left = nSize; + Invalidate (); + } + + int CControlUI::GetTopBorderSize () const { + if (m_pManager) return m_pManager->GetDPIObj ()->Scale (m_rcBorderSize.top); + return m_rcBorderSize.top; + } + + void CControlUI::SetTopBorderSize (int nSize) { + m_rcBorderSize.top = nSize; + Invalidate (); + } + + int CControlUI::GetRightBorderSize () const { + if (m_pManager) return m_pManager->GetDPIObj ()->Scale (m_rcBorderSize.right); + return m_rcBorderSize.right; + } + + void CControlUI::SetRightBorderSize (int nSize) { + m_rcBorderSize.right = nSize; + Invalidate (); + } + + int CControlUI::GetBottomBorderSize () const { + if (m_pManager) return m_pManager->GetDPIObj ()->Scale (m_rcBorderSize.bottom); + return m_rcBorderSize.bottom; + } + + void CControlUI::SetBottomBorderSize (int nSize) { + m_rcBorderSize.bottom = nSize; + Invalidate (); + } + + int CControlUI::GetBorderStyle () const { + return m_nBorderStyle; + } + + void CControlUI::SetBorderStyle (int nStyle) { + m_nBorderStyle = nStyle; + Invalidate (); + } + +} // namespace DuiLib diff --git a/DuiLib/Core/UIControl.h b/DuiLib/Core/UIControl.h new file mode 100644 index 0000000..acd93e3 --- /dev/null +++ b/DuiLib/Core/UIControl.h @@ -0,0 +1,267 @@ +#ifndef __UICONTROL_H__ +#define __UICONTROL_H__ + +#pragma once + +namespace DuiLib { + + ///////////////////////////////////////////////////////////////////////////////////// + // + + typedef CControlUI* (CALLBACK* FINDCONTROLPROC)(CControlUI*, LPVOID); + + class UILIB_API CControlUI { + DECLARE_DUICONTROL (CControlUI) + public: + CControlUI (); + virtual ~CControlUI (); + + public: + virtual string_view_t GetName () const; + virtual void SetName (string_view_t pstrName); + virtual string_view_t GetClass () const; + virtual LPVOID GetInterface (string_view_t pstrName); + virtual UINT GetControlFlags () const; + + virtual bool Activate (); + virtual CPaintManagerUI* GetManager () const; + virtual void SetManager (CPaintManagerUI* pManager, CControlUI* pParent, bool bInit = true); + virtual CControlUI* GetParent () const; + void setInstance (HINSTANCE instance) { m_instance = instance; }; + + // ʱ + bool SetTimer (UINT nTimerID, UINT nElapse); + void KillTimer (UINT nTimerID); + + // ı + virtual CDuiString GetText () const; + virtual void SetText (string_view_t pstrText); + + virtual bool IsResourceText () const; + virtual void SetResourceText (bool bResource); + + virtual bool IsDragEnabled () const; + virtual void SetDragEnable (bool bDrag); + + virtual bool IsDropEnabled () const; + virtual void SetDropEnable (bool bDrop); + + // ͼ + string_view_t GetGradient (); + void SetGradient (string_view_t pStrImage); + DWORD GetBkColor () const; + void SetBkColor (DWORD dwBackColor); + DWORD GetBkColor2 () const; + void SetBkColor2 (DWORD dwBackColor); + DWORD GetBkColor3 () const; + void SetBkColor3 (DWORD dwBackColor); + DWORD GetForeColor () const; + void SetForeColor (DWORD dwForeColor); + string_view_t GetBkImage (); + void SetBkImage (string_view_t pStrImage); + string_view_t GetForeImage () const; + void SetForeImage (string_view_t pStrImage); + + DWORD GetFocusBorderColor () const; + void SetFocusBorderColor (DWORD dwBorderColor); + bool IsColorHSL () const; + void SetColorHSL (bool bColorHSL); + SIZE GetBorderRound () const; + void SetBorderRound (SIZE cxyRound); + bool DrawImage (HDC hDC, string_view_t pStrImage, string_view_t pStrModify = _T ("")); + + //߿ + int GetBorderSize () const; + void SetBorderSize (int nSize); + DWORD GetBorderColor () const; + void SetBorderColor (DWORD dwBorderColor); + void SetBorderSize (RECT rc); + int GetLeftBorderSize () const; + void SetLeftBorderSize (int nSize); + int GetTopBorderSize () const; + void SetTopBorderSize (int nSize); + int GetRightBorderSize () const; + void SetRightBorderSize (int nSize); + int GetBottomBorderSize () const; + void SetBottomBorderSize (int nSize); + int GetBorderStyle () const; + void SetBorderStyle (int nStyle); + + // λ + virtual RECT GetRelativePos () const; // (ؼ)λ + virtual RECT GetClientPos () const; // ͻ򣨳ȥscrollbarinset + virtual const RECT& GetPos () const; + virtual void SetPos (RECT rc, bool bNeedInvalidate = true); + virtual void Move (SIZE szOffset, bool bNeedInvalidate = true); + virtual int GetWidth () const; + virtual int GetHeight () const; + virtual int GetX () const; + virtual int GetY () const; + virtual RECT GetPadding () const; + virtual void SetPadding (RECT rcPadding); // ߾࣬ϲ㴰ڻ + virtual SIZE GetFixedXY () const; // ʵʴСλʹGetPosȡõԤIJοֵ + virtual void SetFixedXY (SIZE szXY); // floatΪtrueʱЧ + virtual int GetFixedWidth () const; // ʵʴСλʹGetPosȡõԤIJοֵ + virtual void SetFixedWidth (int cx); // ԤIJοֵ + virtual int GetFixedHeight () const; // ʵʴСλʹGetPosȡõԤIJοֵ + virtual void SetFixedHeight (int cy); // ԤIJοֵ + virtual int GetMinWidth () const; + virtual void SetMinWidth (int cx); + virtual int GetMaxWidth () const; + virtual void SetMaxWidth (int cx); + virtual int GetMinHeight () const; + virtual void SetMinHeight (int cy); + virtual int GetMaxHeight () const; + virtual void SetMaxHeight (int cy); + virtual TPercentInfo GetFloatPercent () const; + virtual void SetFloatPercent (TPercentInfo piFloatPercent); + virtual void SetFloatAlign (UINT uAlign); + virtual UINT GetFloatAlign () const; + // ʾ + virtual CDuiString GetToolTip () const; + virtual void SetToolTip (string_view_t pstrText); + virtual void SetToolTipWidth (int nWidth); + virtual int GetToolTipWidth (void); // ToolTip + + // + virtual WORD GetCursor (); + virtual void SetCursor (WORD wCursor); + + // ݼ + virtual TCHAR GetShortcut () const; + virtual void SetShortcut (TCHAR ch); + + // ˵ + virtual bool IsContextMenuUsed () const; + virtual void SetContextMenuUsed (bool bMenuUsed); + + // û + virtual const CDuiString& GetUserData (); // ûʹ + virtual void SetUserData (string_view_t pstrText); // ûʹ + virtual UINT_PTR GetTag () const; // ûʹ + virtual void SetTag (UINT_PTR pTag); // ûʹ + + // һЩҪ + virtual bool IsVisible () const; + virtual void SetVisible (bool bVisible = true); + virtual void SetInternVisible (bool bVisible = true); // ڲãЩUIӵдھҪд˺ + virtual bool IsEnabled () const; + virtual void SetEnabled (bool bEnable = true); + virtual bool IsMouseEnabled () const; + virtual void SetMouseEnabled (bool bEnable = true); + virtual bool IsKeyboardEnabled () const; + virtual void SetKeyboardEnabled (bool bEnable = true); + virtual bool IsFocused () const; + virtual void SetFocus (); + virtual bool IsFloat () const; + virtual void SetFloat (bool bFloat = true); + virtual bool IsDynamic (POINT &pt) const; + virtual void SetIsDynamic (bool bIsDynamic = true); + + virtual CControlUI* FindControl (FINDCONTROLPROC Proc, LPVOID pData, UINT uFlags); + + void Invalidate (); + bool IsUpdateNeeded () const; + void NeedUpdate (); + void NeedParentUpdate (); + DWORD GetAdjustColor (DWORD dwColor); + + virtual void Init (); + virtual void DoInit (); + + virtual void Event (TEventUI& event); + virtual void DoEvent (TEventUI& event); + + // Զ(δ) + void AddCustomAttribute (string_view_t pstrName, string_view_t pstrAttr); + string_view_t GetCustomAttribute (string_view_t pstrName); + bool RemoveCustomAttribute (string_view_t pstrName); + void RemoveAllCustomAttribute (); + + virtual void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + CControlUI* ApplyAttributeList (string_view_t pstrList); + + virtual SIZE EstimateSize (SIZE szAvailable); + virtual bool Paint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl = nullptr); // ҪҪ + virtual bool DoPaint (HDC hDC, const RECT& rcPaint, CControlUI* pStopControl); + virtual void PaintBkColor (HDC hDC); + virtual void PaintBkImage (HDC hDC); + virtual void PaintStatusImage (HDC hDC); + virtual void PaintForeColor (HDC hDC); + virtual void PaintForeImage (HDC hDC); + virtual void PaintText (HDC hDC); + virtual void PaintBorder (HDC hDC); + + virtual void DoPostPaint (HDC hDC, const RECT& rcPaint); + + //ⴰڲ + void SetVirtualWnd (string_view_t pstrValue); + CDuiString GetVirtualWnd () const; + + public: + CEventSource OnInit; + CEventSource OnDestroy; + CEventSource OnSize; + CEventSource OnEvent; + CEventSource OnNotify; + + protected: + CPaintManagerUI *m_pManager = nullptr; + CControlUI *m_pParent = nullptr; + CDuiString m_sVirtualWnd; + CDuiString m_sName; + bool m_bUpdateNeeded = true; + bool m_bMenuUsed = false; + RECT m_rcItem; + RECT m_rcPadding; + SIZE m_cXY; + SIZE m_cxyFixed; + SIZE m_cxyMin; + SIZE m_cxyMax; + bool m_bVisible = true; + bool m_bInternVisible = true; + bool m_bEnabled = true; + bool m_bMouseEnabled = true; + bool m_bKeyboardEnabled = true; + bool m_bFocused = false; + bool m_bFloat = false; + TPercentInfo m_piFloatPercent; + UINT m_uFloatAlign = DT_LEFT; + bool m_bSetPos = false; // ֹSetPosѭ + + bool m_bDragEnabled = false; + bool m_bDropEnabled = false; + + bool m_bResourceText = false; + CDuiString m_sText; + CDuiString m_sToolTip; + TCHAR m_chShortcut = _T ('\0'); + CDuiString m_sUserData; + UINT_PTR m_pTag = NULL; + + CDuiString m_sGradient; + DWORD m_dwBackColor = 0; + DWORD m_dwBackColor2 = 0; + DWORD m_dwBackColor3 = 0; + DWORD m_dwForeColor = 0; + CDuiString m_sBkImage; + CDuiString m_sForeImage; + DWORD m_dwBorderColor = 0; + DWORD m_dwFocusBorderColor = 0; + bool m_bColorHSL = false; + int m_nBorderSize = 0; + int m_nBorderStyle = PS_SOLID; + int m_nTooltipWidth = 300; + WORD m_wCursor = 0; + SIZE m_cxyBorderRound; + RECT m_rcPaint; + RECT m_rcBorderSize; + bool m_isDynamic = false; + HINSTANCE m_instance = NULL; + + std::map m_mCustomAttrs; + }; + +} // namespace DuiLib + +#endif // __UICONTROL_H__ diff --git a/DuiLib/Core/UIDefine.h b/DuiLib/Core/UIDefine.h new file mode 100644 index 0000000..9aa2fca --- /dev/null +++ b/DuiLib/Core/UIDefine.h @@ -0,0 +1,313 @@ +#pragma once + +namespace DuiLib { +#define MAX_FONT_ID 30000 +#define CARET_TIMERID 0x1999 + + // б + enum ListType { + LT_LIST = 0, + LT_COMBO, + LT_TREE, + LT_MENU, + }; + + // 궨 +#define DUI_ARROW 32512 +#define DUI_IBEAM 32513 +#define DUI_WAIT 32514 +#define DUI_CROSS 32515 +#define DUI_UPARROW 32516 +#define DUI_SIZE 32640 +#define DUI_ICON 32641 +#define DUI_SIZENWSE 32642 +#define DUI_SIZENESW 32643 +#define DUI_SIZEWE 32644 +#define DUI_SIZENS 32645 +#define DUI_SIZEALL 32646 +#define DUI_NO 32648 +#define DUI_HAND 32649 + + // Ϣ + enum DuiSig { + DuiSig_end = 0, // [marks end of message map] + DuiSig_lwl, // LRESULT (WPARAM, LPARAM) + DuiSig_vn, // void (TNotifyUI) + }; + + // Ŀؼ + class CControlUI; + + // Structure for notifications to the outside world + typedef struct tagTNotifyUI { + CDuiString sType; + CDuiString sVirtualWnd; + CControlUI* pSender; + DWORD dwTimestamp; + POINT ptMouse = { 0 }; + WPARAM wParam; + LPARAM lParam; + } TNotifyUI; + + class CNotifyPump; + typedef void (CNotifyPump::*DUI_PMSG)(TNotifyUI& msg); //ָ + + union DuiMessageMapFunctions { + DUI_PMSG pfn; // generic member function pointer + LRESULT (CNotifyPump::*pfn_Notify_lwl)(WPARAM, LPARAM); + void (CNotifyPump::*pfn_Notify_vn)(TNotifyUI&); + }; + + //Ϣ + ////////////////////////////////////////////////////////////////////////// + +#define DUI_MSGTYPE_MENU (_T("menu")) +#define DUI_MSGTYPE_LINK (_T("link")) + +#define DUI_MSGTYPE_TIMER (_T("timer")) +#define DUI_MSGTYPE_CLICK (_T("click")) +#define DUI_MSGTYPE_DBCLICK (_T("dbclick")) + +#define DUI_MSGTYPE_RETURN (_T("return")) +#define DUI_MSGTYPE_SCROLL (_T("scroll")) + +#define DUI_MSGTYPE_PREDROPDOWN (_T("predropdown")) +#define DUI_MSGTYPE_DROPDOWN (_T("dropdown")) +#define DUI_MSGTYPE_SETFOCUS (_T("setfocus")) + +#define DUI_MSGTYPE_KILLFOCUS (_T("killfocus")) +#define DUI_MSGTYPE_ITEMCLICK (_T("itemclick")) +#define DUI_MSGTYPE_ITEMRCLICK (_T("itemrclick")) +#define DUI_MSGTYPE_TABSELECT (_T("tabselect")) + +#define DUI_MSGTYPE_ITEMSELECT (_T("itemselect")) +#define DUI_MSGTYPE_ITEMEXPAND (_T("itemexpand")) +#define DUI_MSGTYPE_WINDOWINIT (_T("windowinit")) +#define DUI_MSGTYPE_WINDOWSIZE (_T("windowsize")) +#define DUI_MSGTYPE_BUTTONDOWN (_T("buttondown")) +#define DUI_MSGTYPE_MOUSEENTER (_T("mouseenter")) +#define DUI_MSGTYPE_MOUSELEAVE (_T("mouseleave")) + +#define DUI_MSGTYPE_TEXTCHANGED (_T("textchanged")) +#define DUI_MSGTYPE_HEADERCLICK (_T("headerclick")) +#define DUI_MSGTYPE_ITEMDBCLICK (_T("itemdbclick")) +#define DUI_MSGTYPE_SHOWACTIVEX (_T("showactivex")) + +#define DUI_MSGTYPE_ITEMCOLLAPSE (_T("itemcollapse")) +#define DUI_MSGTYPE_ITEMACTIVATE (_T("itemactivate")) +#define DUI_MSGTYPE_VALUECHANGED (_T("valuechanged")) +#define DUI_MSGTYPE_VALUECHANGED_MOVE (_T("movevaluechanged")) + +#define DUI_MSGTYPE_SELECTCHANGED (_T("selectchanged")) +#define DUI_MSGTYPE_UNSELECTED (_T("unselected")) + +#define DUI_MSGTYPE_TREEITEMDBCLICK (_T("treeitemdbclick")) +#define DUI_MSGTYPE_CHECKCLICK (_T("checkclick")) +#define DUI_MSGTYPE_TEXTROLLEND (_T("textrollend")) +#define DUI_MSGTYPE_COLORCHANGED (_T("colorchanged")) + +#define DUI_MSGTYPE_LISTITEMSELECT (_T("listitemselect")) +#define DUI_MSGTYPE_LISTITEMCHECKED (_T("listitemchecked")) +#define DUI_MSGTYPE_COMBOITEMSELECT (_T("comboitemselect")) +#define DUI_MSGTYPE_LISTHEADERCLICK (_T("listheaderclick")) +#define DUI_MSGTYPE_LISTHEADITEMCHECKED (_T("listheaditemchecked")) +#define DUI_MSGTYPE_LISTPAGECHANGED (_T("listpagechanged")) + + ////////////////////////////////////////////////////////////////////////// + + struct DUI_MSGMAP_ENTRY; + struct DUI_MSGMAP { +#ifndef UILIB_STATIC + const DUI_MSGMAP* (PASCAL* pfnGetBaseMap)(); +#else + const DUI_MSGMAP* pBaseMap; +#endif + const DUI_MSGMAP_ENTRY* lpEntries; + }; + + //ṹ + struct DUI_MSGMAP_ENTRY //һṹ壬ϢϢ + { + CDuiString sMsgType; // DUIϢ + CDuiString sCtrlName; // ؼ + UINT nSig; // Ǻָ + DUI_PMSG pfn; // ָָ + }; + + // +#ifndef UILIB_STATIC +#define DUI_DECLARE_MESSAGE_MAP() \ +private: \ + static const DUI_MSGMAP_ENTRY _messageEntries[]; \ +protected: \ + static const DUI_MSGMAP messageMap; \ + static const DUI_MSGMAP* PASCAL _GetBaseMessageMap(); \ + virtual const DUI_MSGMAP* GetMessageMap() const; \ + +#else +#define DUI_DECLARE_MESSAGE_MAP() \ +private: \ + static const DUI_MSGMAP_ENTRY _messageEntries[]; \ +protected: \ + static const DUI_MSGMAP messageMap; \ + virtual const DUI_MSGMAP* GetMessageMap() const; \ + +#endif + + + //ʼ +#ifndef UILIB_STATIC +#define DUI_BASE_BEGIN_MESSAGE_MAP(theClass) \ + const DUI_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \ + { return nullptr; } \ + const DUI_MSGMAP* theClass::GetMessageMap() const \ + { return &theClass::messageMap; } \ + UILIB_COMDAT const DUI_MSGMAP theClass::messageMap = \ + { &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] };\ + UILIB_COMDAT const DUI_MSGMAP_ENTRY theClass::_messageEntries[] = \ + { \ + +#else +#define DUI_BASE_BEGIN_MESSAGE_MAP(theClass) \ + const DUI_MSGMAP* theClass::GetMessageMap() const \ + { return &theClass::messageMap; } \ + UILIB_COMDAT const DUI_MSGMAP theClass::messageMap = \ + { nullptr, &theClass::_messageEntries[0] }; \ + UILIB_COMDAT const DUI_MSGMAP_ENTRY theClass::_messageEntries[] = \ + { \ + +#endif + + + //ʼ +#ifndef UILIB_STATIC +#define DUI_BEGIN_MESSAGE_MAP(theClass, baseClass) \ + const DUI_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \ + { return &baseClass::messageMap; } \ + const DUI_MSGMAP* theClass::GetMessageMap() const \ + { return &theClass::messageMap; } \ + UILIB_COMDAT const DUI_MSGMAP theClass::messageMap = \ + { &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \ + UILIB_COMDAT const DUI_MSGMAP_ENTRY theClass::_messageEntries[] = \ + { \ + +#else +#define DUI_BEGIN_MESSAGE_MAP(theClass, baseClass) \ + const DUI_MSGMAP* theClass::GetMessageMap() const \ + { return &theClass::messageMap; } \ + UILIB_COMDAT const DUI_MSGMAP theClass::messageMap = \ + { &baseClass::messageMap, &theClass::_messageEntries[0] }; \ + UILIB_COMDAT const DUI_MSGMAP_ENTRY theClass::_messageEntries[] = \ + { \ + +#endif + + + // +#define DUI_END_MESSAGE_MAP() \ + { _T(""), _T(""), DuiSig_end, (DUI_PMSG)0 } \ + }; \ + + + //Ϣ--ִк +#define DUI_ON_MSGTYPE(msgtype, memberFxn) \ + { msgtype, _T(""), DuiSig_vn, (DUI_PMSG)&memberFxn}, \ + + + //Ϣ--ؼ--ִк +#define DUI_ON_MSGTYPE_CTRNAME(msgtype,ctrname,memberFxn) \ + { msgtype, ctrname, DuiSig_vn, (DUI_PMSG)&memberFxn }, \ + + + //clickϢĿؼ--ִк +#define DUI_ON_CLICK_CTRNAME(ctrname,memberFxn) \ + { DUI_MSGTYPE_CLICK, ctrname, DuiSig_vn, (DUI_PMSG)&memberFxn }, \ + + + //selectchangedϢĿؼ--ִк +#define DUI_ON_SELECTCHANGED_CTRNAME(ctrname,memberFxn) \ + { DUI_MSGTYPE_SELECTCHANGED,ctrname,DuiSig_vn,(DUI_PMSG)&memberFxn }, \ + + + //killfocusϢĿؼ--ִк +#define DUI_ON_KILLFOCUS_CTRNAME(ctrname,memberFxn) \ + { DUI_MSGTYPE_KILLFOCUS,ctrname,DuiSig_vn,(DUI_PMSG)&memberFxn }, \ + + + //menuϢĿؼ--ִк +#define DUI_ON_MENU_CTRNAME(ctrname,memberFxn) \ + { DUI_MSGTYPE_MENU,ctrname,DuiSig_vn,(DUI_PMSG)&memberFxn }, \ + + + //ؼ޹صϢ + + //timerϢ--ִк +#define DUI_ON_TIMER() \ + { DUI_MSGTYPE_TIMER, _T(""), DuiSig_vn,(DUI_PMSG)&OnTimer }, \ + + + /// + //////////////ENDϢӳ궨//////////////////////////////////////////////////// + + + //////////////BEGINؼƺ궨////////////////////////////////////////////////// + /// + +#define DUI_CTRL_EDIT (_T("Edit")) +#define DUI_CTRL_LIST (_T("List")) +#define DUI_CTRL_TEXT (_T("Text")) + +#define DUI_CTRL_COMBO (_T("Combo")) +#define DUI_CTRL_LABEL (_T("Label")) +#define DUI_CTRL_FLASH (_T("Flash")) + +#define DUI_CTRL_BUTTON (_T("Button")) +#define DUI_CTRL_OPTION (_T("Option")) +#define DUI_CTRL_SLIDER (_T("Slider")) + +#define DUI_CTRL_CONTROL (_T("Control")) +#define DUI_CTRL_ACTIVEX (_T("ActiveX")) +#define DUI_CTRL_GIFANIM (_T("GifAnim")) + +#define DUI_CTRL_LISTITEM (_T("ListItem")) +#define DUI_CTRL_PROGRESS (_T("Progress")) +#define DUI_CTRL_RICHEDIT (_T("RichEdit")) +#define DUI_CTRL_CHECKBOX (_T("CheckBox")) +#define DUI_CTRL_COMBOBOX (_T("ComboBox")) +#define DUI_CTRL_DATETIME (_T("DateTime")) +#define DUI_CTRL_TREEVIEW (_T("TreeView")) +#define DUI_CTRL_TREENODE (_T("TreeNode")) + +#define DUI_CTRL_CONTAINER (_T("Container")) +#define DUI_CTRL_TABLAYOUT (_T("TabLayout")) +#define DUI_CTRL_SCROLLBAR (_T("ScrollBar")) +#define DUI_CTRL_IPADDRESS (_T("IPAddress")) + +#define DUI_CTRL_LISTHEADER (_T("ListHeader")) +#define DUI_CTRL_LISTFOOTER (_T("ListFooter")) +#define DUI_CTRL_TILELAYOUT (_T("TileLayout")) +#define DUI_CTRL_WEBBROWSER (_T("WebBrowser")) + +#define DUI_CTRL_CHILDLAYOUT (_T("ChildLayout")) +#define DUI_CTRL_LISTELEMENT (_T("ListElement")) + +#define DUI_CTRL_VERTICALLAYOUT (_T("VerticalLayout")) +#define DUI_CTRL_LISTHEADERITEM (_T("ListHeaderItem")) + +#define DUI_CTRL_LISTTEXTELEMENT (_T("ListTextElement")) + +#define DUI_CTRL_HORIZONTALLAYOUT (_T("HorizontalLayout")) +#define DUI_CTRL_LISTLABELELEMENT (_T("ListLabelElement")) + +#define DUI_CTRL_ANIMATIONTABLAYOUT (_T("AnimationTabLayout")) + +#define DUI_CTRL_LISTCONTAINERELEMENT (_T("ListContainerElement")) + +#define DUI_CTRL_TEXTSCROLL (_T("TextScroll")) + +#define DUI_CTRL_COLORPALETTE (_T("ColorPalette")) +/// +//////////////ENDؼƺ궨////////////////////////////////////////////////// + +}// namespace DuiLib + diff --git a/DuiLib/Core/UIDlgBuilder.cpp b/DuiLib/Core/UIDlgBuilder.cpp new file mode 100644 index 0000000..5091dba --- /dev/null +++ b/DuiLib/Core/UIDlgBuilder.cpp @@ -0,0 +1,401 @@ +#include "StdAfx.h" + +namespace DuiLib { + + CDialogBuilder::CDialogBuilder () {} + + CControlUI* CDialogBuilder::Create (std::variant xml, string_view_t type, IDialogBuilderCallback* pCallback, + CPaintManagerUI* pManager, CControlUI* pParent) { + //ԴIDΪ0-65535ֽڣַָΪ4ֽ + //ַ<ͷΪXMLַΪXMLļ + if (xml.index () == 1 && std::get<1> (xml)[0] != _T ('<')) { + string_view_t xmlpath = CResourceManager::GetInstance ()->GetXmlPath (std::get<1> (xml)); + if (!xmlpath.empty ()) { + xml = string_t (xmlpath); + } + } + + if (xml.index () == 1) { + if (std::get<1> (xml)[0] == _T ('<')) { + if (!m_xml.Load (std::get<1> (xml).c_str ())) return nullptr; + } else { + if (!m_xml.LoadFromFile (std::get<1> (xml).c_str ())) return nullptr; + } + } else { + HINSTANCE dll_instence = nullptr; + if (m_instance) { + dll_instence = m_instance; + } else { + dll_instence = CPaintManagerUI::GetResourceDll (); + } + HRSRC hResource = ::FindResource (dll_instence, MAKEINTRESOURCE (std::get<0> (xml)), type.data ()); + if (!hResource) return nullptr; + HGLOBAL hGlobal = ::LoadResource (dll_instence, hResource); + if (!hGlobal) { + FreeResource (hResource); + return nullptr; + } + + m_pCallback = pCallback; + if (!m_xml.LoadFromMem ((BYTE*)::LockResource (hGlobal), ::SizeofResource (dll_instence, hResource))) return nullptr; + ::FreeResource (hResource); + m_pstrtype = type; + } + + return Create (pCallback, pManager, pParent); + } + + CControlUI* CDialogBuilder::Create (IDialogBuilderCallback* pCallback, CPaintManagerUI* pManager, CControlUI* pParent) { + m_pCallback = pCallback; + CMarkupNode root = m_xml.GetRoot (); + if (!root.IsValid ()) return nullptr; + + if (pManager) { + string_t pstrClass = _T (""); + int nAttributes = 0; + string_t pstrName = _T (""); + string_t pstrValue = _T (""); + for (CMarkupNode node = root.GetChild (); node.IsValid (); node = node.GetSibling ()) { + pstrClass = node.GetName (); + if (pstrClass == _T ("Image")) { + nAttributes = node.GetAttributeCount (); + string_t pImageName = _T (""); + string_t pImageResType = _T (""); + bool shared = false; + DWORD mask = 0; + for (int i = 0; i < nAttributes; i++) { + pstrName = node.GetAttributeName (i); + pstrValue = node.GetAttributeValue (i); + if (pstrName == _T ("name")) { + pImageName = pstrValue; + } else if (pstrName == _T ("restype")) { + pImageResType = pstrValue; + } else if (pstrName == _T ("mask")) { + mask = (DWORD) FawTools::parse_hex (pstrValue); + } else if (pstrName == _T ("shared")) { + shared = FawTools::parse_bool (pstrValue); + } + } + if (!pImageName.empty ()) pManager->AddImage (pImageName, pImageResType, mask, false, shared); + } else if (pstrClass == _T ("Font")) { + nAttributes = node.GetAttributeCount (); + int id = -1; + string_t pFontName = _T (""); + int size = 12; + bool bold = false; + bool underline = false; + bool italic = false; + bool defaultfont = false; + bool shared = false; + for (int i = 0; i < nAttributes; i++) { + pstrName = node.GetAttributeName (i); + pstrValue = node.GetAttributeValue (i); + if (pstrName == _T ("id")) { + id = FawTools::parse_dec (pstrValue); + } else if (pstrName == _T ("name")) { + pFontName = pstrValue; + } else if (pstrName == _T ("size")) { + size = FawTools::parse_dec (pstrValue); + } else if (pstrName == _T ("bold")) { + bold = FawTools::parse_bool (pstrValue); + } else if (pstrName == _T ("underline")) { + underline = FawTools::parse_bool (pstrValue); + } else if (pstrName == _T ("italic")) { + italic = FawTools::parse_bool (pstrValue); + } else if (pstrName == _T ("default")) { + defaultfont = FawTools::parse_bool (pstrValue); + } else if (pstrName == _T ("shared")) { + shared = FawTools::parse_bool (pstrValue); + } + } + if (id >= 0) { + pManager->AddFont (id, pFontName, size, bold, underline, italic, shared); + if (defaultfont) pManager->SetDefaultFont (pFontName, pManager->GetDPIObj ()->Scale (size), bold, underline, italic, shared); + } + } else if (pstrClass == _T ("Default")) { + nAttributes = node.GetAttributeCount (); + string_t pControlName = _T (""); + string_t pControlValue = _T (""); + bool shared = false; + for (int i = 0; i < nAttributes; i++) { + pstrName = node.GetAttributeName (i); + pstrValue = node.GetAttributeValue (i); + if (pstrName == _T ("name")) { + pControlName = pstrValue; + } else if (pstrName == _T ("value")) { + pControlValue = pstrValue; + } else if (pstrName == _T ("shared")) { + shared = FawTools::parse_bool (pstrValue); + } + } + if (!pControlName.empty ()) { + pManager->AddDefaultAttributeList (pControlName, pControlValue, shared); + } + } else if (pstrClass == _T ("Style")) { + nAttributes = node.GetAttributeCount (); + string_t pName = _T (""); + string_t pStyle = _T (""); + bool shared = false; + for (int i = 0; i < nAttributes; i++) { + pstrName = node.GetAttributeName (i); + pstrValue = node.GetAttributeValue (i); + if (pstrName == _T ("name")) { + pName = pstrValue; + } else if (pstrName == _T ("value")) { + pStyle = pstrValue; + } else if (pstrName == _T ("shared")) { + shared = FawTools::parse_bool (pstrValue); + } + } + if (!pName.empty ()) { + pManager->AddStyle (pName, pStyle, shared); + } + } else if (pstrClass == _T ("Import")) { + nAttributes = node.GetAttributeCount (); + string_t pstrPath = _T (""); + for (int i = 0; i < nAttributes; i++) { + pstrName = node.GetAttributeName (i); + pstrValue = node.GetAttributeValue (i); + if (pstrName == _T ("fontfile")) { + pstrPath = pstrValue; + } + } + if (!pstrPath.empty ()) { + pManager->AddFontArray (pstrPath); + } + } + } + + pstrClass = root.GetName (); + if (pstrClass == _T ("Window")) { + if (pManager->GetPaintWindow ()) { + nAttributes = root.GetAttributeCount (); + for (int i = 0; i < nAttributes; i++) { + pstrName = root.GetAttributeName (i); + pstrValue = root.GetAttributeValue (i); + if (pstrName == _T ("size")) { + SIZE sz = FawTools::parse_size (pstrValue); + pManager->SetInitSize (pManager->GetDPIObj ()->Scale (sz.cx), pManager->GetDPIObj ()->Scale (sz.cy)); + } else if (pstrName == _T ("sizebox")) { + RECT rcSizeBox = FawTools::parse_rect (pstrValue); + pManager->SetSizeBox (rcSizeBox); + } else if (pstrName == _T ("caption")) { + RECT rcCaption = FawTools::parse_rect (pstrValue); + pManager->SetCaptionRect (rcCaption); + } else if (pstrName == _T ("roundcorner")) { + SIZE sz = FawTools::parse_size (pstrValue); + pManager->SetRoundCorner (sz.cx, sz.cy); + } else if (pstrName == _T ("mininfo")) { + SIZE sz = FawTools::parse_size (pstrValue); + pManager->SetMinInfo (sz.cx, sz.cy); + } else if (pstrName == _T ("maxinfo")) { + SIZE sz = FawTools::parse_size (pstrValue); + pManager->SetMaxInfo (sz.cx, sz.cy); + } else if (pstrName == _T ("showdirty")) { + pManager->SetShowUpdateRect (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("opacity") || pstrName == _T ("alpha")) { + pManager->SetOpacity ((BYTE) FawTools::parse_dec (pstrValue)); + } else if (pstrName == _T ("layeredopacity")) { + pManager->SetLayeredOpacity ((BYTE) FawTools::parse_dec (pstrValue)); + } else if (pstrName == _T ("layered") || pstrName == _T ("bktrans")) { + pManager->SetLayered (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("layeredimage")) { + pManager->SetLayered (true); + pManager->SetLayeredImage (pstrValue); + } else if (pstrName == _T ("noactivate")) { + pManager->SetNoActivate (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("disabledfontcolor")) { + pManager->SetDefaultDisabledColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("defaultfontcolor")) { + pManager->SetDefaultFontColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("linkfontcolor")) { + pManager->SetDefaultLinkFontColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("linkhoverfontcolor")) { + pManager->SetDefaultLinkHoverFontColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("selectedcolor")) { + pManager->SetDefaultSelectedBkColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("shadowsize")) { + pManager->GetShadow ()->SetSize (FawTools::parse_dec (pstrValue)); + } else if (pstrName == _T ("shadowsharpness")) { + pManager->GetShadow ()->SetSharpness (FawTools::parse_dec (pstrValue)); + } else if (pstrName == _T ("shadowdarkness")) { + pManager->GetShadow ()->SetDarkness (FawTools::parse_dec (pstrValue)); + } else if (pstrName == _T ("shadowposition")) { + SIZE sz = FawTools::parse_size (pstrValue); + pManager->GetShadow ()->SetPosition (sz.cx, sz.cy); + } else if (pstrName == _T ("shadowcolor")) { + pManager->GetShadow ()->SetColor ((DWORD) FawTools::parse_hex (pstrValue)); + } else if (pstrName == _T ("shadowcorner")) { + RECT rcCorner = FawTools::parse_rect (pstrValue); + pManager->GetShadow ()->SetShadowCorner (rcCorner); + } else if (pstrName == _T ("shadowimage")) { + pManager->GetShadow ()->SetImage (pstrValue); + } else if (pstrName == _T ("showshadow")) { + pManager->GetShadow ()->ShowShadow (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("gdiplustext")) { + pManager->SetUseGdiplusText (FawTools::parse_bool (pstrValue)); + } else if (pstrName == _T ("textrenderinghint")) { + pManager->SetGdiplusTextRenderingHint (FawTools::parse_dec (pstrValue)); + } else if (pstrName == _T ("tooltiphovertime")) { + pManager->SetHoverTime (FawTools::parse_dec (pstrValue)); + } + } + } + } + } + return _Parse (&root, pParent, pManager); + } + + CMarkup* CDialogBuilder::GetMarkup () { + return &m_xml; + } + + string_view_t CDialogBuilder::GetLastErrorMessage () const { + return m_xml.GetLastErrorMessage (); + } + + string_view_t CDialogBuilder::GetLastErrorLocation () const { + return m_xml.GetLastErrorLocation (); + } + + CControlUI* CDialogBuilder::_Parse (CMarkupNode* pRoot, CControlUI* pParent, CPaintManagerUI* pManager) { + IContainerUI* pContainer = nullptr; + CControlUI* pReturn = nullptr; + for (CMarkupNode node = pRoot->GetChild (); node.IsValid (); node = node.GetSibling ()) { + string_t pstrClass = node.GetName (); + if (pstrClass == _T ("Image") || pstrClass == _T ("Font") || pstrClass == _T ("Default") || pstrClass == _T ("Style")) continue; + + CControlUI* pControl = nullptr; + if (pstrClass == _T ("Import")) continue; + if (pstrClass == _T ("Include")) { + if (!node.HasAttributes ()) continue; + int count = 1; + //string_t szValue (500, _T ('\0')); + //SIZE_T cchLen = szValue.length () - 1; + string_t szValue = node.GetAttributeValue (_T ("count")); + if (!szValue.empty ()) + count = _ttoi (szValue.c_str ()); + szValue = node.GetAttributeValue (_T ("source")); + if (!szValue.empty ()) continue; + for (int i = 0; i < count; i++) { + CDialogBuilder builder; + if (!m_pstrtype.empty ()) { // ʹԴdllԴжȡ + WORD id = (WORD) FawTools::parse_dec (szValue); + pControl = builder.Create ((UINT) id, m_pstrtype, m_pCallback, pManager, pParent); + } else { + pControl = builder.Create (szValue, (UINT) 0, m_pCallback, pManager, pParent); + } + } + continue; + } else { + CDuiString strClass; + strClass.Format (_T ("C%sUI"), pstrClass.c_str ()); + pControl = dynamic_cast(CControlFactory::GetInstance ()->CreateControl (strClass)); + + // + if (!pControl) { + CStdPtrArray* pPlugins = CPaintManagerUI::GetPlugins (); + LPCREATECONTROL lpCreateControl = nullptr; + for (int i = 0; i < pPlugins->GetSize (); ++i) { + lpCreateControl = (LPCREATECONTROL) pPlugins->GetAt (i); + if (lpCreateControl) { + pControl = lpCreateControl (pstrClass); + if (pControl) break; + } + } + } + // ص + if (!pControl && m_pCallback) { + pControl = m_pCallback->CreateControl (pstrClass); + } + } + + if (!pControl) { +#ifdef _DEBUG + DUITRACE (_T ("δ֪ؼ:%s"), pstrClass); +#else + continue; +#endif + } + + // Add children + if (node.HasChildren ()) { + _Parse (&node, pControl, pManager); + } + // Attach to parent + // ΪijЩԺ͸أselectedAdd + CTreeViewUI* pTreeView = nullptr; + if (pParent && pControl) { + CTreeNodeUI* pParentTreeNode = static_cast(pParent->GetInterface (_T ("TreeNode"))); + CTreeNodeUI* pTreeNode = static_cast(pControl->GetInterface (_T ("TreeNode"))); + pTreeView = static_cast(pParent->GetInterface (_T ("TreeView"))); + // TreeNodeӽڵ + if (pTreeNode) { + if (pParentTreeNode) { + pTreeView = pParentTreeNode->GetTreeView (); + if (!pParentTreeNode->Add (pTreeNode)) { + delete pTreeNode; + pTreeNode = nullptr; + continue; + } + } else { + if (pTreeView) { + if (!pTreeView->Add (pTreeNode)) { + delete pTreeNode; + pTreeNode = nullptr; + continue; + } + } + } + } + // TreeNodeӿؼ + else if (pParentTreeNode) { + pParentTreeNode->GetTreeNodeHoriznotal ()->Add (pControl); + } + // ͨؼ + else { + if (!pContainer) pContainer = static_cast(pParent->GetInterface (_T ("IContainer"))); + ASSERT (pContainer); + if (!pContainer) return nullptr; + if (!pContainer->Add (pControl)) { + delete pControl; + continue; + } + } + } + if (!pControl) continue; + + // Init default attributes + if (pManager) { + if (pTreeView) { + pControl->SetManager (pManager, pTreeView, true); + } else { + pControl->SetManager (pManager, nullptr, false); + } + string_view_t pDefaultAttributes = pManager->GetDefaultAttributeList (pstrClass); + if (!pDefaultAttributes.empty ()) { + pControl->ApplyAttributeList (pDefaultAttributes); + } + } + // Process attributes + if (node.HasAttributes ()) { + TCHAR szValue[500] = { 0 }; + SIZE_T cchLen = lengthof (szValue) - 1; + // Set ordinary attributes + int nAttributes = node.GetAttributeCount (); + for (int i = 0; i < nAttributes; i++) { + pControl->SetAttribute (node.GetAttributeName (i), node.GetAttributeValue (i)); + } + } + if (pManager) { + if (!pTreeView) { + pControl->SetManager (nullptr, nullptr, false); + } + } + // Return first item + if (!pReturn) pReturn = pControl; + } + return pReturn; + } + +} // namespace DuiLib diff --git a/DuiLib/Core/UIDlgBuilder.h b/DuiLib/Core/UIDlgBuilder.h new file mode 100644 index 0000000..da11be1 --- /dev/null +++ b/DuiLib/Core/UIDlgBuilder.h @@ -0,0 +1,36 @@ +#ifndef __UIDLGBUILDER_H__ +#define __UIDLGBUILDER_H__ + +#pragma once + +namespace DuiLib { + + class IDialogBuilderCallback { + public: + virtual CControlUI* CreateControl (string_view_t pstrClass) = 0; + }; + + + class UILIB_API CDialogBuilder { + public: + CDialogBuilder (); + CControlUI* Create (std::variant xml, string_view_t type = _T (""), IDialogBuilderCallback* pCallback = nullptr, CPaintManagerUI* pManager = nullptr, CControlUI* pParent = nullptr); + CControlUI* Create (IDialogBuilderCallback* pCallback = nullptr, CPaintManagerUI* pManager = nullptr, CControlUI* pParent = nullptr); + + CMarkup* GetMarkup (); + + string_view_t GetLastErrorMessage () const; + string_view_t GetLastErrorLocation () const; + void SetInstance (HINSTANCE instance) { m_instance = instance; }; + private: + CControlUI* _Parse (CMarkupNode* parent, CControlUI* pParent = nullptr, CPaintManagerUI* pManager = nullptr); + + CMarkup m_xml; + IDialogBuilderCallback *m_pCallback = nullptr; + CDuiString m_pstrtype = _T (""); + HINSTANCE m_instance = NULL; + }; + +} // namespace DuiLib + +#endif // __UIDLGBUILDER_H__ diff --git a/DuiLib/Core/UIManager.cpp b/DuiLib/Core/UIManager.cpp new file mode 100644 index 0000000..ba8024a --- /dev/null +++ b/DuiLib/Core/UIManager.cpp @@ -0,0 +1,3732 @@ +#include "StdAfx.h" +#include + +namespace DuiLib { + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + static void GetChildWndRect (HWND hWnd, HWND hChildWnd, RECT& rcChildWnd) { + ::GetWindowRect (hChildWnd, &rcChildWnd); + + POINT pt = { 0 }; + pt.x = rcChildWnd.left; + pt.y = rcChildWnd.top; + ::ScreenToClient (hWnd, &pt); + rcChildWnd.left = pt.x; + rcChildWnd.top = pt.y; + + pt.x = rcChildWnd.right; + pt.y = rcChildWnd.bottom; + ::ScreenToClient (hWnd, &pt); + rcChildWnd.right = pt.x; + rcChildWnd.bottom = pt.y; + } + + static UINT MapKeyState () { + UINT uState = 0; + if (::GetKeyState (VK_CONTROL) < 0) uState |= MK_CONTROL; + if (::GetKeyState (VK_LBUTTON) < 0) uState |= MK_LBUTTON; + if (::GetKeyState (VK_RBUTTON) < 0) uState |= MK_RBUTTON; + if (::GetKeyState (VK_SHIFT) < 0) uState |= MK_SHIFT; + if (::GetKeyState (VK_MENU) < 0) uState |= MK_ALT; + return uState; + } + + typedef struct tagFINDTABINFO { + CControlUI* pFocus; + CControlUI* pLast; + bool bForward; + bool bNextIsIt; + } FINDTABINFO; + + typedef struct tagFINDSHORTCUT { + TCHAR ch; + bool bPickNext; + } FINDSHORTCUT; + + typedef struct tagTIMERINFO { + CControlUI* pSender; + UINT nLocalID; + HWND hWnd; + UINT uWinTimer; + bool bKilled; + } TIMERINFO; + + + tagTDrawInfo::tagTDrawInfo () { + Clear (); + } + + void tagTDrawInfo::Parse (string_view_t pStrImage, string_view_t pStrModify, CPaintManagerUI *paintManager) { + // 1aaa.jpg + // 2file='aaa.jpg' res='' restype='0' dest='0,0,0,0' source='0,0,0,0' corner='0,0,0,0' + // mask='#FF0000' fade='255' hole='false' xtiled='false' ytiled='false' + sDrawString = pStrImage; + sDrawModify = pStrModify; + sImageName = pStrImage; + + for (size_t i = 0; i < 2; ++i) { + std::map m = FawTools::parse_keyvalue_pairs (i == 0 ? pStrImage : pStrModify); + for (auto[str_key, str_value] : m) { + if (str_key == _T ("file") || str_key == _T ("res")) { + sImageName = str_value; + } else if (str_key == _T ("restype")) { + sResType = str_value; + } else if (str_key == _T ("dest")) { + rcDest = FawTools::parse_rect (str_value); + paintManager->GetDPIObj ()->Scale (&rcDest); + } else if (str_key == _T ("source")) { + rcSource = FawTools::parse_rect (str_value); + paintManager->GetDPIObj ()->Scale (&rcSource); + } else if (str_key == _T ("corner")) { + rcCorner = FawTools::parse_rect (str_value); + paintManager->GetDPIObj ()->Scale (&rcCorner); + } else if (str_key == _T ("mask")) { + dwMask = (DWORD) FawTools::parse_hex (str_value); + } else if (str_key == _T ("fade")) { + uFade = (BYTE) _ttoi (str_value.c_str ()); + } else if (str_key == _T ("hole")) { + bHole = FawTools::parse_bool (str_value); + } else if (str_key == _T ("xtiled")) { + bTiledX = FawTools::parse_bool (str_value); + } else if (str_key == _T ("ytiled")) { + bTiledY = FawTools::parse_bool (str_value); + } else if (str_key == _T ("hsl")) { + bHSL = FawTools::parse_bool (str_value); + } + } + } + + // DPIԴ + if (paintManager->GetDPIObj ()->GetScale () != 100) { + CDuiString sScale; + sScale.Format (_T ("@%d."), paintManager->GetDPIObj ()->GetScale ()); + sImageName.Replace (_T ("."), sScale); + } + } + void tagTDrawInfo::Clear () { + sDrawString.clear (); + sDrawModify.clear (); + sImageName.clear (); + + memset (&rcDest, 0, sizeof (RECT)); + memset (&rcSource, 0, sizeof (RECT)); + memset (&rcCorner, 0, sizeof (RECT)); + dwMask = 0; + uFade = 255; + bHole = false; + bTiledX = false; + bTiledY = false; + bHSL = false; + } + + ///////////////////////////////////////////////////////////////////////////////////// + typedef BOOL (__stdcall *PFUNCUPDATELAYEREDWINDOW)(HWND, HDC, POINT*, SIZE*, HDC, POINT*, COLORREF, BLENDFUNCTION*, DWORD); + PFUNCUPDATELAYEREDWINDOW g_fUpdateLayeredWindow = nullptr; + + HPEN m_hUpdateRectPen = nullptr; + + HINSTANCE CPaintManagerUI::m_hResourceInstance = nullptr; + CDuiString CPaintManagerUI::m_pStrResourcePath; + CDuiString CPaintManagerUI::m_pStrResourceZip; + CDuiString CPaintManagerUI::m_pStrResourceZipPwd; //Garfield 20160325 zip + HANDLE CPaintManagerUI::m_hResourceZip = nullptr; + bool CPaintManagerUI::m_bCachedResourceZip = true; + int CPaintManagerUI::m_nResType = UILIB_FILE; + TResInfo CPaintManagerUI::m_SharedResInfo; + HINSTANCE CPaintManagerUI::m_hInstance = nullptr; + bool CPaintManagerUI::m_bUseHSL = false; + short CPaintManagerUI::m_H = 180; + short CPaintManagerUI::m_S = 100; + short CPaintManagerUI::m_L = 100; + CStdPtrArray CPaintManagerUI::m_aPreMessages; + CStdPtrArray CPaintManagerUI::m_aPlugins; + + CPaintManagerUI::CPaintManagerUI (): + m_hWndPaint (nullptr), + m_hDcPaint (nullptr), + m_hDcOffscreen (nullptr), + m_hDcBackground (nullptr), + m_bOffscreenPaint (true), + m_hbmpOffscreen (nullptr), + m_pOffscreenBits (nullptr), + m_hbmpBackground (nullptr), + m_pBackgroundBits (nullptr), + m_hwndTooltip (nullptr), + m_uTimerID (0x1000), + m_pRoot (nullptr), + m_pFocus (nullptr), + m_pEventHover (nullptr), + m_pEventClick (nullptr), + m_pEventKey (nullptr), + m_bFirstLayout (true), + m_bFocusNeeded (false), + m_bUpdateNeeded (false), + m_bMouseTracking (false), + m_bMouseCapture (false), + m_bUsedVirtualWnd (false), + m_bForceUseSharedRes (false), + m_nOpacity (0xFF), + m_bLayered (false), + m_bLayeredChanged (false), + m_bShowUpdateRect (false), + m_bUseGdiplusText (true), + m_trh (0), + m_bDragMode (false), + m_hDragBitmap (nullptr), + m_pDPI (nullptr), + m_iHoverTime (400UL) { + if (m_SharedResInfo.m_DefaultFontInfo.sFontName.empty ()) { + m_SharedResInfo.m_dwDefaultDisabledColor = 0xFFA7A6AA; + m_SharedResInfo.m_dwDefaultFontColor = 0xFF000000; + m_SharedResInfo.m_dwDefaultLinkFontColor = 0xFF0000FF; + m_SharedResInfo.m_dwDefaultLinkHoverFontColor = 0xFFD3215F; + m_SharedResInfo.m_dwDefaultSelectedBkColor = 0xFFBAE4FF; + + LOGFONT lf = { 0 }; + ::GetObject (::GetStockObject (DEFAULT_GUI_FONT), sizeof (LOGFONT), &lf); + lf.lfCharSet = DEFAULT_CHARSET; + HFONT hDefaultFont = ::CreateFontIndirect (&lf); + m_SharedResInfo.m_DefaultFontInfo.hFont = hDefaultFont; + m_SharedResInfo.m_DefaultFontInfo.sFontName = lf.lfFaceName; + m_SharedResInfo.m_DefaultFontInfo.iSize = -lf.lfHeight; + m_SharedResInfo.m_DefaultFontInfo.bBold = (lf.lfWeight >= FW_BOLD); + m_SharedResInfo.m_DefaultFontInfo.bUnderline = (lf.lfUnderline == TRUE); + m_SharedResInfo.m_DefaultFontInfo.bItalic = (lf.lfItalic == TRUE); + ::ZeroMemory (&m_SharedResInfo.m_DefaultFontInfo.tm, sizeof (m_SharedResInfo.m_DefaultFontInfo.tm)); + } + + m_ResInfo.m_dwDefaultDisabledColor = m_SharedResInfo.m_dwDefaultDisabledColor; + m_ResInfo.m_dwDefaultFontColor = m_SharedResInfo.m_dwDefaultFontColor; + m_ResInfo.m_dwDefaultLinkFontColor = m_SharedResInfo.m_dwDefaultLinkFontColor; + m_ResInfo.m_dwDefaultLinkHoverFontColor = m_SharedResInfo.m_dwDefaultLinkHoverFontColor; + m_ResInfo.m_dwDefaultSelectedBkColor = m_SharedResInfo.m_dwDefaultSelectedBkColor; + + if (!m_hUpdateRectPen) { + m_hUpdateRectPen = ::CreatePen (PS_SOLID, 1, RGB (220, 0, 0)); + // Boot Windows Common Controls (for the ToolTip control) + ::InitCommonControls (); + ::LoadLibrary (_T ("msimg32.dll")); + } + + m_szMinWindow.cx = 0; + m_szMinWindow.cy = 0; + m_szMaxWindow.cx = 0; + m_szMaxWindow.cy = 0; + m_szInitWindowSize.cx = 0; + m_szInitWindowSize.cy = 0; + m_szRoundCorner.cx = m_szRoundCorner.cy = 0; + ::ZeroMemory (&m_rcSizeBox, sizeof (m_rcSizeBox)); + ::ZeroMemory (&m_rcCaption, sizeof (m_rcCaption)); + ::ZeroMemory (&m_rcLayeredInset, sizeof (m_rcLayeredInset)); + ::ZeroMemory (&m_rcLayeredUpdate, sizeof (m_rcLayeredUpdate)); + m_ptLastMousePos.x = m_ptLastMousePos.y = -1; + + m_pGdiplusStartupInput = new Gdiplus::GdiplusStartupInput; + Gdiplus::GdiplusStartup (&m_gdiplusToken, m_pGdiplusStartupInput, nullptr); // GDIӿ + + CShadowUI::Initialize (m_hInstance); + } + + CPaintManagerUI::~CPaintManagerUI () { + // Delete the control-tree structures + for (int i = 0; i < m_aDelayedCleanup.GetSize (); i++) delete static_cast(m_aDelayedCleanup[i]); + m_aDelayedCleanup.Resize (0); + for (int i = 0; i < m_aAsyncNotify.GetSize (); i++) delete static_cast(m_aAsyncNotify[i]); + m_aAsyncNotify.Resize (0); + + m_mNameHash.Resize (0); + if (m_pRoot) delete m_pRoot; + + ::DeleteObject (m_ResInfo.m_DefaultFontInfo.hFont); + RemoveAllFonts (); + RemoveAllImages (); + RemoveAllStyle (); + RemoveAllDefaultAttributeList (); + RemoveAllWindowCustomAttribute (); + RemoveAllOptionGroups (); + RemoveAllTimers (); + RemoveAllDrawInfos (); + + if (m_hwndTooltip) { + ::DestroyWindow (m_hwndTooltip); + m_hwndTooltip = nullptr; + } + if (!m_aFonts.empty ()) { + for (int i = 0; i < m_aFonts.GetSize (); ++i) { + HANDLE handle = static_cast(m_aFonts.GetAt (i)); + ::RemoveFontMemResourceEx (handle); + } + } + if (m_hDcOffscreen) ::DeleteDC (m_hDcOffscreen); + if (m_hDcBackground) ::DeleteDC (m_hDcBackground); + if (m_hbmpOffscreen) ::DeleteObject (m_hbmpOffscreen); + if (m_hbmpBackground) ::DeleteObject (m_hbmpBackground); + if (m_hDcPaint) ::ReleaseDC (m_hWndPaint, m_hDcPaint); + m_aPreMessages.Remove (m_aPreMessages.Find (this)); + // קͼƬ + if (m_hDragBitmap) ::DeleteObject (m_hDragBitmap); + //жGDIPlus + Gdiplus::GdiplusShutdown (m_gdiplusToken); + delete m_pGdiplusStartupInput; + // DPI + if (m_pDPI) { + delete m_pDPI; + m_pDPI = nullptr; + } + } + + void CPaintManagerUI::Init (HWND hWnd, string_view_t pstrName) { + ASSERT (::IsWindow (hWnd)); + + m_mNameHash.Resize (); + RemoveAllFonts (); + RemoveAllImages (); + RemoveAllStyle (); + RemoveAllDefaultAttributeList (); + RemoveAllWindowCustomAttribute (); + RemoveAllOptionGroups (); + RemoveAllTimers (); + + m_sName.clear (); + if (!pstrName.empty ()) m_sName = pstrName; + + if (m_hWndPaint != hWnd) { + m_hWndPaint = hWnd; + m_hDcPaint = ::GetDC (hWnd); + m_aPreMessages.Add (this); + } + + SetTargetWnd (hWnd); + InitDragDrop (); + } + + void CPaintManagerUI::DeletePtr (void* ptr) { + if (ptr) { + delete ptr; ptr = nullptr; + } + } + + HINSTANCE CPaintManagerUI::GetInstance () { + return m_hInstance; + } + + CDuiString CPaintManagerUI::GetInstancePath () { + if (!m_hInstance) return _T ('\0'); + + TCHAR tszModule[MAX_PATH + 1] = { 0 }; + ::GetModuleFileName (m_hInstance, tszModule, MAX_PATH); + CDuiString sInstancePath = tszModule; + size_t pos = sInstancePath.rfind (_T ('\\')); + if (pos != string_t::npos) sInstancePath = sInstancePath.Left (pos + 1); + return sInstancePath; + } + + CDuiString CPaintManagerUI::GetCurrentPath () { + TCHAR tszModule[MAX_PATH + 1] = { 0 }; + ::GetCurrentDirectory (MAX_PATH, tszModule); + return tszModule; + } + + HINSTANCE CPaintManagerUI::GetResourceDll () { + return m_hResourceInstance ? m_hResourceInstance : m_hInstance; + } + + const string_view_t CPaintManagerUI::GetResourcePath () { + return m_pStrResourcePath; + } + + const string_view_t CPaintManagerUI::GetResourceZip () { + return m_pStrResourceZip; + } + + const string_view_t CPaintManagerUI::GetResourceZipPwd () { + return m_pStrResourceZipPwd; + } + + bool CPaintManagerUI::IsCachedResourceZip () { + return m_bCachedResourceZip; + } + + HANDLE CPaintManagerUI::GetResourceZipHandle () { + return m_hResourceZip; + } + + void CPaintManagerUI::SetInstance (HINSTANCE hInst) { + m_hInstance = hInst; + } + + void CPaintManagerUI::SetCurrentPath (string_view_t pStrPath) { + ::SetCurrentDirectory (pStrPath.data ()); + } + + void CPaintManagerUI::SetResourceDll (HINSTANCE hInst) { + m_hResourceInstance = hInst; + } + + void CPaintManagerUI::SetResourcePath (string_view_t pStrPath) { + m_pStrResourcePath = pStrPath; + if (m_pStrResourcePath.empty ()) return; + TCHAR cEnd = m_pStrResourcePath[m_pStrResourcePath.length () - 1]; + if (cEnd != _T ('\\') && cEnd != _T ('/')) m_pStrResourcePath += _T ('\\'); + } + + void CPaintManagerUI::SetResourceZip (LPVOID pVoid, unsigned int len, string_view_t password) { + if (m_pStrResourceZip == _T ("membuffer")) return; + if (m_bCachedResourceZip && m_hResourceZip) { + CloseZip ((HZIP) m_hResourceZip); + m_hResourceZip = nullptr; + } + m_pStrResourceZip = _T ("membuffer"); + m_bCachedResourceZip = true; + m_pStrResourceZipPwd = password; //Garfield 20160325 zip + if (m_bCachedResourceZip) { + std::string pwd = FawTools::get_gb18030 (password); + m_hResourceZip = (HANDLE) OpenZip (pVoid, len, pwd.c_str ()); + } + } + + void CPaintManagerUI::SetResourceZip (string_view_t pStrPath, bool bCachedResourceZip, string_view_t password) { + if (m_pStrResourceZip == pStrPath && m_bCachedResourceZip == bCachedResourceZip) return; + if (m_bCachedResourceZip && m_hResourceZip) { + CloseZip ((HZIP) m_hResourceZip); + m_hResourceZip = nullptr; + } + m_pStrResourceZip = pStrPath; + m_bCachedResourceZip = bCachedResourceZip; + m_pStrResourceZipPwd = password; + if (m_bCachedResourceZip) { + CDuiString sFile = CPaintManagerUI::GetResourcePath (); + sFile += CPaintManagerUI::GetResourceZip (); + std::string pwd = FawTools::get_gb18030 (password); + m_hResourceZip = (HANDLE) OpenZip (sFile.c_str (), pwd.c_str ()); + } + } + + void CPaintManagerUI::SetResourceType (int nType) { + m_nResType = nType; + } + + int CPaintManagerUI::GetResourceType () { + return m_nResType; + } + + bool CPaintManagerUI::GetHSL (short* H, short* S, short* L) { + *H = m_H; + *S = m_S; + *L = m_L; + return m_bUseHSL; + } + + void CPaintManagerUI::SetHSL (bool bUseHSL, short H, short S, short L) { + if (m_bUseHSL || m_bUseHSL != bUseHSL) { + m_bUseHSL = bUseHSL; + if (H == m_H && S == m_S && L == m_L) return; + m_H = CLAMP (H, 0, 360); + m_S = CLAMP (S, 0, 200); + m_L = CLAMP (L, 0, 200); + AdjustSharedImagesHSL (); + for (int i = 0; i < m_aPreMessages.GetSize (); i++) { + CPaintManagerUI* pManager = static_cast(m_aPreMessages[i]); + if (pManager) pManager->AdjustImagesHSL (); + } + } + } + + void CPaintManagerUI::ReloadSkin () { + ReloadSharedImages (); + for (int i = 0; i < m_aPreMessages.GetSize (); i++) { + CPaintManagerUI* pManager = static_cast(m_aPreMessages[i]); + pManager->ReloadImages (); + } + } + + CPaintManagerUI* CPaintManagerUI::GetPaintManager (string_view_t pstrName) { + //if (pstrName.empty ()) return nullptr; + for (int i = 0; i < m_aPreMessages.GetSize (); i++) { + CPaintManagerUI* pManager = static_cast(m_aPreMessages[i]); + if (pManager && pstrName == pManager->GetName ()) return pManager; + } + return nullptr; + } + + CStdPtrArray* CPaintManagerUI::GetPaintManagers () { + return &m_aPreMessages; + } + + bool CPaintManagerUI::LoadPlugin (string_view_t pstrModuleName) { + ASSERT (!pstrModuleName.empty ()); + if (pstrModuleName.empty ()) return false; + HMODULE hModule = ::LoadLibrary (pstrModuleName.data ()); + if (hModule) { + LPCREATECONTROL lpCreateControl = (LPCREATECONTROL)::GetProcAddress (hModule, "CreateControl"); + if (lpCreateControl) { + if (m_aPlugins.Find (lpCreateControl) >= 0) return true; + m_aPlugins.Add (lpCreateControl); + return true; + } + } + return false; + } + + CStdPtrArray* CPaintManagerUI::GetPlugins () { + return &m_aPlugins; + } + + HWND CPaintManagerUI::GetPaintWindow () const { + return m_hWndPaint; + } + + HWND CPaintManagerUI::GetTooltipWindow () const { + return m_hwndTooltip; + } + int CPaintManagerUI::GetHoverTime () const { + return m_iHoverTime; + } + + void CPaintManagerUI::SetHoverTime (int iTime) { + m_iHoverTime = iTime; + } + + string_view_t CPaintManagerUI::GetName () const { + return m_sName; + } + + HDC CPaintManagerUI::GetPaintDC () const { + return m_hDcPaint; + } + + POINT CPaintManagerUI::GetMousePos () const { + return m_ptLastMousePos; + } + + SIZE CPaintManagerUI::GetClientSize () const { + RECT rcClient = { 0 }; + ::GetClientRect (m_hWndPaint, &rcClient); + return { rcClient.right - rcClient.left, rcClient.bottom - rcClient.top }; + } + + SIZE CPaintManagerUI::GetInitSize () { + return m_szInitWindowSize; + } + + void CPaintManagerUI::SetInitSize (int cx, int cy) { + m_szInitWindowSize.cx = cx; + m_szInitWindowSize.cy = cy; + if (!m_pRoot && m_hWndPaint) { + ::SetWindowPos (m_hWndPaint, nullptr, 0, 0, cx, cy, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); + } + } + + RECT& CPaintManagerUI::GetSizeBox () { + return m_rcSizeBox; + } + + void CPaintManagerUI::SetSizeBox (RECT& rcSizeBox) { + m_rcSizeBox = rcSizeBox; + } + + RECT& CPaintManagerUI::GetCaptionRect () { + return m_rcCaption; + } + + void CPaintManagerUI::SetCaptionRect (RECT& rcCaption) { + m_rcCaption = rcCaption; + } + + SIZE CPaintManagerUI::GetRoundCorner () const { + return m_szRoundCorner; + } + + void CPaintManagerUI::SetRoundCorner (int cx, int cy) { + m_szRoundCorner.cx = cx; + m_szRoundCorner.cy = cy; + } + + SIZE CPaintManagerUI::GetMinInfo () const { + return m_szMinWindow; + } + + void CPaintManagerUI::SetMinInfo (int cx, int cy) { + ASSERT (cx >= 0 && cy >= 0); + m_szMinWindow.cx = cx; + m_szMinWindow.cy = cy; + } + + SIZE CPaintManagerUI::GetMaxInfo () const { + return m_szMaxWindow; + } + + void CPaintManagerUI::SetMaxInfo (int cx, int cy) { + ASSERT (cx >= 0 && cy >= 0); + m_szMaxWindow.cx = cx; + m_szMaxWindow.cy = cy; + } + + bool CPaintManagerUI::IsShowUpdateRect () const { + return m_bShowUpdateRect; + } + + void CPaintManagerUI::SetShowUpdateRect (bool show) { + m_bShowUpdateRect = show; + } + + bool CPaintManagerUI::IsNoActivate () { + return m_bNoActivate; + } + + void CPaintManagerUI::SetNoActivate (bool bNoActivate) { + m_bNoActivate = bNoActivate; + } + + BYTE CPaintManagerUI::GetOpacity () const { + return m_nOpacity; + } + + void CPaintManagerUI::SetOpacity (BYTE nOpacity) { + m_nOpacity = nOpacity; + if (m_hWndPaint) { + typedef BOOL (__stdcall *PFUNCSETLAYEREDWINDOWATTR)(HWND, COLORREF, BYTE, DWORD); + PFUNCSETLAYEREDWINDOWATTR fSetLayeredWindowAttributes = nullptr; + + HMODULE hUser32 = ::GetModuleHandle (_T ("User32.dll")); + if (hUser32) { + fSetLayeredWindowAttributes = + (PFUNCSETLAYEREDWINDOWATTR)::GetProcAddress (hUser32, "SetLayeredWindowAttributes"); + if (!fSetLayeredWindowAttributes) return; + } + + DWORD dwStyle = ::GetWindowLong (m_hWndPaint, GWL_EXSTYLE); + DWORD dwNewStyle = dwStyle; + if (nOpacity >= 0 && nOpacity < 256) dwNewStyle |= WS_EX_LAYERED; + else dwNewStyle &= ~WS_EX_LAYERED; + if (dwStyle != dwNewStyle) ::SetWindowLong (m_hWndPaint, GWL_EXSTYLE, dwNewStyle); + fSetLayeredWindowAttributes (m_hWndPaint, 0, nOpacity, LWA_ALPHA); + } + } + + bool CPaintManagerUI::IsLayered () { + return m_bLayered; + } + + void CPaintManagerUI::SetLayered (bool bLayered) { + if (m_hWndPaint && bLayered != m_bLayered) { + UINT uStyle = GetWindowStyle (m_hWndPaint); + if ((uStyle & WS_CHILD) != 0) return; + if (!g_fUpdateLayeredWindow) { + HMODULE hUser32 = ::GetModuleHandle (_T ("User32.dll")); + if (hUser32) { + g_fUpdateLayeredWindow = + (PFUNCUPDATELAYEREDWINDOW)::GetProcAddress (hUser32, "UpdateLayeredWindow"); + if (!g_fUpdateLayeredWindow) return; + } + } + m_bLayered = bLayered; + if (m_pRoot) m_pRoot->NeedUpdate (); + Invalidate (); + } + } + + RECT& CPaintManagerUI::GetLayeredInset () { + return m_rcLayeredInset; + } + + void CPaintManagerUI::SetLayeredInset (RECT& rcLayeredInset) { + m_rcLayeredInset = rcLayeredInset; + m_bLayeredChanged = true; + Invalidate (); + } + + BYTE CPaintManagerUI::GetLayeredOpacity () { + return m_nOpacity; + } + + void CPaintManagerUI::SetLayeredOpacity (BYTE nOpacity) { + m_nOpacity = nOpacity; + m_bLayeredChanged = true; + Invalidate (); + } + + string_view_t CPaintManagerUI::GetLayeredImage () { + return m_diLayered.sDrawString; + } + + void CPaintManagerUI::SetLayeredImage (string_view_t pstrImage) { + m_diLayered.sDrawString = pstrImage; + RECT rcnullptr = { 0 }; + CRenderEngine::DrawImageInfo (nullptr, this, rcnullptr, rcnullptr, &m_diLayered); + m_bLayeredChanged = true; + Invalidate (); + } + + CShadowUI* CPaintManagerUI::GetShadow () { + return &m_shadow; + } + + void CPaintManagerUI::SetUseGdiplusText (bool bUse) { + m_bUseGdiplusText = bUse; + } + + bool CPaintManagerUI::IsUseGdiplusText () const { + return m_bUseGdiplusText; + } + + void CPaintManagerUI::SetGdiplusTextRenderingHint (int trh) { + m_trh = trh; + } + + int CPaintManagerUI::GetGdiplusTextRenderingHint () const { + return m_trh; + } + + bool CPaintManagerUI::PreMessageHandler (UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& /*lRes*/) { + for (int i = 0; i < m_aPreMessageFilters.GetSize (); i++) { + bool bHandled = false; + LRESULT lResult = static_cast(m_aPreMessageFilters[i])->MessageHandler (uMsg, wParam, lParam, bHandled); + if (bHandled) { + return true; + } + } + switch (uMsg) { + case WM_KEYDOWN: + { + // Tabbing between controls + if (wParam == VK_TAB) { + if (m_pFocus && m_pFocus->IsVisible () && m_pFocus->IsEnabled () && m_pFocus->GetClass ().find (_T ("RichEditUI")) != string_t::npos) { + if (static_cast(m_pFocus)->IsWantTab ()) return false; + } + if (m_pFocus && m_pFocus->IsVisible () && m_pFocus->IsEnabled () && m_pFocus->GetClass ().find (_T ("WkeWebkitUI")) != string_t::npos) { + return false; + } + SetNextTabControl (::GetKeyState (VK_SHIFT) >= 0); + return true; + } + } + break; + case WM_SYSCHAR: + { + // Handle ALT-shortcut key-combinations + FINDSHORTCUT fs = { 0 }; + fs.ch = (TCHAR) toupper ((int) wParam); + CControlUI* pControl = m_pRoot->FindControl (__FindControlFromShortcut, &fs, UIFIND_ENABLED | UIFIND_ME_FIRST | UIFIND_TOP_FIRST); + if (pControl) { + pControl->SetFocus (); + pControl->Activate (); + return true; + } + } + break; + case WM_SYSKEYDOWN: + { + if (m_pFocus) { + TEventUI event = { 0 }; + event.Type = UIEVENT_SYSKEY; + event.chKey = (TCHAR) wParam; + event.ptMouse = m_ptLastMousePos; + event.wKeyState = (WORD) MapKeyState (); + event.dwTimestamp = ::GetTickCount (); + m_pFocus->Event (event); + } + } + break; + } + return false; + } + + bool CPaintManagerUI::MessageHandler (UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes) { + if (!m_hWndPaint) return false; + // Cycle through listeners + for (int i = 0; i < m_aMessageFilters.GetSize (); i++) { + bool bHandled = false; + LRESULT lResult = static_cast(m_aMessageFilters[i])->MessageHandler (uMsg, wParam, lParam, bHandled); + if (bHandled) { + lRes = lResult; + switch (uMsg) { + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_LBUTTONUP: + { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + m_ptLastMousePos = pt; + } + break; + case WM_CONTEXTMENU: + case WM_MOUSEWHEEL: + { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + ::ScreenToClient (m_hWndPaint, &pt); + m_ptLastMousePos = pt; + } + break; + } + return true; + } + } + + if (m_bLayered) { + switch (uMsg) { + case WM_NCACTIVATE: + if (!::IsIconic (m_hWndPaint)) { + lRes = (wParam == 0) ? TRUE : FALSE; + return true; + } + break; + case WM_NCCALCSIZE: + case WM_NCPAINT: + lRes = 0; + return true; + } + } + // Custom handling of events + switch (uMsg) { + case WM_APP + 1: + { + for (int i = 0; i < m_aDelayedCleanup.GetSize (); i++) + delete static_cast(m_aDelayedCleanup[i]); + m_aDelayedCleanup.Empty (); + + m_bAsyncNotifyPosted = false; + + TNotifyUI* pMsg = nullptr; + while (!!(pMsg = static_cast(m_aAsyncNotify.GetAt (0)))) { + m_aAsyncNotify.Remove (0); + if (pMsg->pSender) { + if (pMsg->pSender->OnNotify) pMsg->pSender->OnNotify (pMsg); + } + for (int j = 0; j < m_aNotifiers.GetSize (); j++) { + static_cast(m_aNotifiers[j])->Notify (*pMsg); + } + delete pMsg; + } + } + break; + case WM_CLOSE: + { + // Make sure all matching "closing" events are sent + TEventUI event = { 0 }; + event.ptMouse = m_ptLastMousePos; + event.wKeyState = (WORD) MapKeyState (); + event.dwTimestamp = ::GetTickCount (); + if (m_pEventHover) { + event.Type = UIEVENT_MOUSELEAVE; + event.pSender = m_pEventHover; + m_pEventHover->Event (event); + } + if (m_pEventClick) { + event.Type = UIEVENT_BUTTONUP; + event.pSender = m_pEventClick; + m_pEventClick->Event (event); + } + + SetFocus (nullptr); + + if (::GetActiveWindow () == m_hWndPaint) { + HWND hwndParent = GetWindowOwner (m_hWndPaint); + if (hwndParent) ::SetFocus (hwndParent); + } + + if (m_hwndTooltip) { + ::DestroyWindow (m_hwndTooltip); + m_hwndTooltip = nullptr; + } + } + break; + case WM_ERASEBKGND: + { + // We'll do the painting here... + lRes = 1; + } + return true; + case WM_PAINT: + { + if (!m_pRoot) { + PAINTSTRUCT ps = { 0 }; + ::BeginPaint (m_hWndPaint, &ps); + CRenderEngine::DrawColor (m_hDcPaint, ps.rcPaint, 0xFF000000); + ::EndPaint (m_hWndPaint, &ps); + return true; + } + + RECT rcClient = { 0 }; + ::GetClientRect (m_hWndPaint, &rcClient); + + RECT rcPaint = { 0 }; + if (!::GetUpdateRect (m_hWndPaint, &rcPaint, FALSE)) return true; + + //if( m_bLayered ) { + // m_bOffscreenPaint = true; + // rcPaint = m_rcLayeredUpdate; + // if( ::IsRectEmpty(&m_rcLayeredUpdate) ) { + // PAINTSTRUCT ps = { 0 }; + // ::BeginPaint(m_hWndPaint, &ps); + // ::EndPaint(m_hWndPaint, &ps); + // return true; + // } + // if( rcPaint.right > rcClient.right ) rcPaint.right = rcClient.right; + // if( rcPaint.bottom > rcClient.bottom ) rcPaint.bottom = rcClient.bottom; + // ::ZeroMemory(&m_rcLayeredUpdate, sizeof(m_rcLayeredUpdate)); + //} + //else { + // if( !::GetUpdateRect(m_hWndPaint, &rcPaint, FALSE) ) return true; + //} + + // Set focus to first control? + if (m_bFocusNeeded) { + SetNextTabControl (); + } + + SetPainting (true); + + bool bNeedSizeMsg = false; + DWORD dwWidth = rcClient.right - rcClient.left; + DWORD dwHeight = rcClient.bottom - rcClient.top; + + SetPainting (true); + if (m_bUpdateNeeded) { + m_bUpdateNeeded = false; + if (!::IsRectEmpty (&rcClient) && !::IsIconic (m_hWndPaint)) { + if (m_pRoot->IsUpdateNeeded ()) { + RECT rcRoot = rcClient; + if (m_hDcOffscreen) ::DeleteDC (m_hDcOffscreen); + if (m_hDcBackground) ::DeleteDC (m_hDcBackground); + if (m_hbmpOffscreen) ::DeleteObject (m_hbmpOffscreen); + if (m_hbmpBackground) ::DeleteObject (m_hbmpBackground); + m_hDcOffscreen = nullptr; + m_hDcBackground = nullptr; + m_hbmpOffscreen = nullptr; + m_hbmpBackground = nullptr; + if (m_bLayered) { + rcRoot.left += m_rcLayeredInset.left; + rcRoot.top += m_rcLayeredInset.top; + rcRoot.right -= m_rcLayeredInset.right; + rcRoot.bottom -= m_rcLayeredInset.bottom; + } + m_pRoot->SetPos (rcRoot, true); + bNeedSizeMsg = true; + } else { + CControlUI* pControl = nullptr; + m_aFoundControls.Empty (); + m_pRoot->FindControl (__FindControlsFromUpdate, nullptr, UIFIND_VISIBLE | UIFIND_ME_FIRST | UIFIND_UPDATETEST); + for (int it = 0; it < m_aFoundControls.GetSize (); it++) { + pControl = static_cast(m_aFoundControls[it]); + if (!pControl->IsFloat ()) pControl->SetPos (pControl->GetPos (), true); + else pControl->SetPos (pControl->GetRelativePos (), true); + } + bNeedSizeMsg = true; + } + // We'll want to notify the window when it is first initialized + // with the correct layout. The window form would take the time + // to submit swipes/animations. + if (m_bFirstLayout) { + m_bFirstLayout = false; + SendNotify (m_pRoot, DUI_MSGTYPE_WINDOWINIT, 0, 0, false); + if (m_bLayered && m_bLayeredChanged) { + Invalidate (); + SetPainting (false); + return true; + } + // Ӱʾ + m_shadow.Update (m_hWndPaint); + } + } + } else if (m_bLayered && m_bLayeredChanged) { + RECT rcRoot = rcClient; + if (m_pOffscreenBits) ::ZeroMemory (m_pOffscreenBits, (rcRoot.right - rcRoot.left) + * (rcRoot.bottom - rcRoot.top) * 4); + rcRoot.left += m_rcLayeredInset.left; + rcRoot.top += m_rcLayeredInset.top; + rcRoot.right -= m_rcLayeredInset.right; + rcRoot.bottom -= m_rcLayeredInset.bottom; + m_pRoot->SetPos (rcRoot, true); + } + + if (m_bLayered) { + DWORD dwExStyle = ::GetWindowLong (m_hWndPaint, GWL_EXSTYLE); + DWORD dwNewExStyle = dwExStyle | WS_EX_LAYERED; + if (dwExStyle != dwNewExStyle) ::SetWindowLong (m_hWndPaint, GWL_EXSTYLE, dwNewExStyle); + m_bOffscreenPaint = true; + UnionRect (&rcPaint, &rcPaint, &m_rcLayeredUpdate); + if (rcPaint.right > rcClient.right) rcPaint.right = rcClient.right; + if (rcPaint.bottom > rcClient.bottom) rcPaint.bottom = rcClient.bottom; + ::ZeroMemory (&m_rcLayeredUpdate, sizeof (m_rcLayeredUpdate)); + } + + // + // Render screen + // + // Prepare offscreen bitmap + if (m_bOffscreenPaint && !m_hbmpOffscreen) { + m_hDcOffscreen = ::CreateCompatibleDC (m_hDcPaint); + m_hbmpOffscreen = CRenderEngine::CreateARGB32Bitmap (m_hDcPaint, dwWidth, dwHeight, (LPBYTE*) &m_pOffscreenBits); + ASSERT (m_hDcOffscreen); + ASSERT (m_hbmpOffscreen); + } + // Begin Windows paint + PAINTSTRUCT ps = { 0 }; + ::BeginPaint (m_hWndPaint, &ps); + if (m_bOffscreenPaint) { + HBITMAP hOldBitmap = (HBITMAP) ::SelectObject (m_hDcOffscreen, m_hbmpOffscreen); + int iSaveDC = ::SaveDC (m_hDcOffscreen); + if (m_bLayered) { + for (LONG y = rcClient.bottom - rcPaint.bottom; y < rcClient.bottom - rcPaint.top; ++y) { + for (LONG x = rcPaint.left; x < rcPaint.right; ++x) { + int i = (y * dwWidth + x) * 4; + *(DWORD*) (&m_pOffscreenBits[i]) = 0; + } + } + } + m_pRoot->Paint (m_hDcOffscreen, rcPaint, nullptr); + + if (m_bLayered) { + for (int i = 0; i < m_aNativeWindow.GetSize (); ) { + HWND hChildWnd = static_cast(m_aNativeWindow[i]); + if (!::IsWindow (hChildWnd)) { + m_aNativeWindow.Remove (i); + m_aNativeWindowControl.Remove (i); + continue; + } + ++i; + if (!::IsWindowVisible (hChildWnd)) continue; + RECT rcChildWnd = GetNativeWindowRect (hChildWnd); + RECT rcTemp = { 0 }; + if (!::IntersectRect (&rcTemp, &rcPaint, &rcChildWnd)) continue; + + COLORREF* pChildBitmapBits = nullptr; + HDC hChildMemDC = ::CreateCompatibleDC (m_hDcOffscreen); + HBITMAP hChildBitmap = CRenderEngine::CreateARGB32Bitmap (hChildMemDC, rcChildWnd.right - rcChildWnd.left, rcChildWnd.bottom - rcChildWnd.top, (BYTE**) &pChildBitmapBits); + ::ZeroMemory (pChildBitmapBits, (rcChildWnd.right - rcChildWnd.left)*(rcChildWnd.bottom - rcChildWnd.top) * 4); + HBITMAP hOldChildBitmap = (HBITMAP) ::SelectObject (hChildMemDC, hChildBitmap); + ::SendMessage (hChildWnd, WM_PRINT, (WPARAM) hChildMemDC, (LPARAM) (PRF_CHECKVISIBLE | PRF_CHILDREN | PRF_CLIENT | PRF_OWNED)); + COLORREF* pChildBitmapBit; + for (LONG y = 0; y < rcChildWnd.bottom - rcChildWnd.top; y++) { + for (LONG x = 0; x < rcChildWnd.right - rcChildWnd.left; x++) { + pChildBitmapBit = pChildBitmapBits + y * (rcChildWnd.right - rcChildWnd.left) + x; + if (*pChildBitmapBit != 0x00000000) *pChildBitmapBit |= 0xff000000; + } + } + ::BitBlt (m_hDcOffscreen, rcChildWnd.left, rcChildWnd.top, rcChildWnd.right - rcChildWnd.left, + rcChildWnd.bottom - rcChildWnd.top, hChildMemDC, 0, 0, SRCCOPY); + ::SelectObject (hChildMemDC, hOldChildBitmap); + ::DeleteObject (hChildBitmap); + ::DeleteDC (hChildMemDC); + } + } + + for (int i = 0; i < m_aPostPaintControls.GetSize (); i++) { + CControlUI* pPostPaintControl = static_cast(m_aPostPaintControls[i]); + pPostPaintControl->DoPostPaint (m_hDcOffscreen, rcPaint); + } + + ::RestoreDC (m_hDcOffscreen, iSaveDC); + + if (m_bLayered) { + RECT rcWnd = { 0 }; + ::GetWindowRect (m_hWndPaint, &rcWnd); + if (!m_diLayered.sDrawString.empty ()) { + DWORD _dwWidth = rcClient.right - rcClient.left; + DWORD _dwHeight = rcClient.bottom - rcClient.top; + RECT rcLayeredClient = rcClient; + rcLayeredClient.left += m_rcLayeredInset.left; + rcLayeredClient.top += m_rcLayeredInset.top; + rcLayeredClient.right -= m_rcLayeredInset.right; + rcLayeredClient.bottom -= m_rcLayeredInset.bottom; + + COLORREF* pOffscreenBits = (COLORREF*) m_pOffscreenBits; + COLORREF* pBackgroundBits = m_pBackgroundBits; + BYTE A = 0; + BYTE R = 0; + BYTE G = 0; + BYTE B = 0; + if (!m_diLayered.sDrawString.empty ()) { + if (!m_hbmpBackground) { + m_hDcBackground = ::CreateCompatibleDC (m_hDcPaint); + m_hbmpBackground = CRenderEngine::CreateARGB32Bitmap (m_hDcPaint, _dwWidth, _dwHeight, (BYTE**) &m_pBackgroundBits); + ::ZeroMemory (m_pBackgroundBits, _dwWidth * _dwHeight * 4); + ::SelectObject (m_hDcBackground, m_hbmpBackground); + CRenderClip clip; + CRenderClip::GenerateClip (m_hDcBackground, rcLayeredClient, clip); + CRenderEngine::DrawImageInfo (m_hDcBackground, this, rcLayeredClient, rcLayeredClient, &m_diLayered); + } else if (m_bLayeredChanged) { + ::ZeroMemory (m_pBackgroundBits, _dwWidth * _dwHeight * 4); + CRenderClip clip; + CRenderClip::GenerateClip (m_hDcBackground, rcLayeredClient, clip); + CRenderEngine::DrawImageInfo (m_hDcBackground, this, rcLayeredClient, rcLayeredClient, &m_diLayered); + } + for (LONG y = rcClient.bottom - rcPaint.bottom; y < rcClient.bottom - rcPaint.top; ++y) { + for (LONG x = rcPaint.left; x < rcPaint.right; ++x) { + pOffscreenBits = (COLORREF*) (m_pOffscreenBits + y * _dwWidth + x); + pBackgroundBits = m_pBackgroundBits + y * _dwWidth + x; + A = (BYTE) ((*pBackgroundBits) >> 24); + R = (BYTE) ((*pOffscreenBits) >> 16) * A / 255; + G = (BYTE) ((*pOffscreenBits) >> 8) * A / 255; + B = (BYTE) (*pOffscreenBits) * A / 255; + *pOffscreenBits = RGB (B, G, R) + ((DWORD) A << 24); + } + } + } + } else { + for (LONG y = rcClient.bottom - rcPaint.bottom; y < rcClient.bottom - rcPaint.top; ++y) { + for (LONG x = rcPaint.left; x < rcPaint.right; ++x) { + int i = (y * dwWidth + x) * 4; + if ((m_pOffscreenBits[i + 3] == 0) && (m_pOffscreenBits[i + 0] != 0 || m_pOffscreenBits[i + 1] != 0 || m_pOffscreenBits[i + 2] != 0)) + m_pOffscreenBits[i + 3] = 255; + } + } + } + + BLENDFUNCTION bf = { AC_SRC_OVER, 0, m_nOpacity, AC_SRC_ALPHA }; + POINT ptPos = { rcWnd.left, rcWnd.top }; + SIZE sizeWnd = { (LONG) dwWidth, (LONG) dwHeight }; + POINT ptSrc = { 0, 0 }; + g_fUpdateLayeredWindow (m_hWndPaint, m_hDcPaint, &ptPos, &sizeWnd, m_hDcOffscreen, &ptSrc, 0, &bf, ULW_ALPHA); + } else { + ::BitBlt (m_hDcPaint, rcPaint.left, rcPaint.top, rcPaint.right - rcPaint.left, rcPaint.bottom - rcPaint.top, m_hDcOffscreen, rcPaint.left, rcPaint.top, SRCCOPY); + } + ::SelectObject (m_hDcOffscreen, hOldBitmap); + + if (m_bShowUpdateRect && !m_bLayered) { + HPEN hOldPen = (HPEN)::SelectObject (m_hDcPaint, m_hUpdateRectPen); + ::SelectObject (m_hDcPaint, ::GetStockObject (HOLLOW_BRUSH)); + ::Rectangle (m_hDcPaint, rcPaint.left, rcPaint.top, rcPaint.right, rcPaint.bottom); + ::SelectObject (m_hDcPaint, hOldPen); + } + } else { + // A standard paint job + int iSaveDC = ::SaveDC (m_hDcPaint); + m_pRoot->Paint (m_hDcPaint, rcPaint, nullptr); + for (int i = 0; i < m_aPostPaintControls.GetSize (); i++) { + CControlUI* pPostPaintControl = static_cast(m_aPostPaintControls[i]); + pPostPaintControl->DoPostPaint (m_hDcPaint, rcPaint); + } + ::RestoreDC (m_hDcPaint, iSaveDC); + } + // All Done! + ::EndPaint (m_hWndPaint, &ps); + + // ƽ + SetPainting (false); + m_bLayeredChanged = false; + if (m_bUpdateNeeded) Invalidate (); + + // ʹڴСıϢ + if (bNeedSizeMsg) { + this->SendNotify (m_pRoot, DUI_MSGTYPE_WINDOWSIZE, 0, 0, true); + } + return true; + } + case WM_PRINTCLIENT: + { + if (!m_pRoot) break; + RECT rcClient = { 0 }; + ::GetClientRect (m_hWndPaint, &rcClient); + HDC hDC = (HDC) wParam; + int save = ::SaveDC (hDC); + m_pRoot->Paint (hDC, rcClient, nullptr); + if ((lParam & PRF_CHILDREN) != 0) { + HWND hWndChild = ::GetWindow (m_hWndPaint, GW_CHILD); + while (hWndChild) { + RECT rcPos = { 0 }; + ::GetWindowRect (hWndChild, &rcPos); + ::MapWindowPoints (HWND_DESKTOP, m_hWndPaint, reinterpret_cast(&rcPos), 2); + ::SetWindowOrgEx (hDC, -rcPos.left, -rcPos.top, nullptr); + ::SendMessage (hWndChild, WM_PRINT, wParam, lParam | PRF_NONCLIENT); + hWndChild = ::GetWindow (hWndChild, GW_HWNDNEXT); + } + } + ::RestoreDC (hDC, save); + } + break; + case WM_GETMINMAXINFO: + { + MONITORINFO Monitor = {}; + Monitor.cbSize = sizeof (Monitor); + ::GetMonitorInfo (::MonitorFromWindow (m_hWndPaint, MONITOR_DEFAULTTOPRIMARY), &Monitor); + RECT rcWork = Monitor.rcWork; + if (Monitor.dwFlags != MONITORINFOF_PRIMARY) { + ::OffsetRect (&rcWork, -rcWork.left, -rcWork.top); + } + + LPMINMAXINFO lpMMI = (LPMINMAXINFO) lParam; + if (m_szMinWindow.cx > 0) lpMMI->ptMinTrackSize.x = m_szMinWindow.cx; + if (m_szMinWindow.cy > 0) lpMMI->ptMinTrackSize.y = m_szMinWindow.cy; + if (m_szMaxWindow.cx > 0) lpMMI->ptMaxTrackSize.x = m_szMaxWindow.cx; + if (m_szMaxWindow.cy > 0) lpMMI->ptMaxTrackSize.y = m_szMaxWindow.cy; + if (m_szMaxWindow.cx > 0) lpMMI->ptMaxSize.x = m_szMaxWindow.cx; + if (m_szMaxWindow.cy > 0) lpMMI->ptMaxSize.y = m_szMaxWindow.cy; + } + break; + case WM_SIZE: + { + if (m_pFocus) { + TEventUI event = { 0 }; + event.Type = UIEVENT_WINDOWSIZE; + event.pSender = m_pFocus; + event.dwTimestamp = ::GetTickCount (); + m_pFocus->Event (event); + } + if (m_pRoot) m_pRoot->NeedUpdate (); + } + return true; + case WM_TIMER: + { + for (int i = 0; i < m_aTimers.GetSize (); i++) { + const TIMERINFO* pTimer = static_cast(m_aTimers[i]); + if (pTimer->hWnd == m_hWndPaint && + pTimer->uWinTimer == LOWORD (wParam) && + pTimer->bKilled == false) { + TEventUI event = { 0 }; + event.Type = UIEVENT_TIMER; + event.pSender = pTimer->pSender; + event.dwTimestamp = ::GetTickCount (); + event.ptMouse = m_ptLastMousePos; + event.wKeyState = (WORD) MapKeyState (); + event.wParam = pTimer->nLocalID; + event.lParam = lParam; + pTimer->pSender->Event (event); + break; + } + } + } + break; + case WM_MOUSEHOVER: + { + m_bMouseTracking = false; + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + CControlUI* pHover = FindControl (pt); + if (!pHover) break; + // Generate mouse hover event + if (m_pEventHover) { + TEventUI event = { 0 }; + event.Type = UIEVENT_MOUSEHOVER; + event.pSender = m_pEventHover; + event.wParam = wParam; + event.lParam = lParam; + event.dwTimestamp = ::GetTickCount (); + event.ptMouse = pt; + event.wKeyState = (WORD) MapKeyState (); + m_pEventHover->Event (event); + } + // Create tooltip information + CDuiString sToolTip = pHover->GetToolTip (); + if (sToolTip.empty ()) return true; + ::ZeroMemory (&m_ToolTip, sizeof (TOOLINFO)); + m_ToolTip.cbSize = sizeof (TOOLINFO); + m_ToolTip.uFlags = TTF_IDISHWND; + m_ToolTip.hwnd = m_hWndPaint; + m_ToolTip.uId = (UINT_PTR) m_hWndPaint; + m_ToolTip.hinst = m_hInstance; + m_ToolTip.lpszText = &sToolTip[0]; + m_ToolTip.rect = pHover->GetPos (); + if (!m_hwndTooltip) { + m_hwndTooltip = ::CreateWindowEx (0, TOOLTIPS_CLASS, nullptr, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, m_hWndPaint, NULL, m_hInstance, nullptr); + ::SendMessage (m_hwndTooltip, TTM_ADDTOOL, 0, (LPARAM) &m_ToolTip); + ::SendMessage (m_hwndTooltip, TTM_SETMAXTIPWIDTH, 0, pHover->GetToolTipWidth ()); + } + if (!::IsWindowVisible (m_hwndTooltip)) { + ::SendMessage (m_hwndTooltip, TTM_SETTOOLINFO, 0, (LPARAM) &m_ToolTip); + ::SendMessage (m_hwndTooltip, TTM_TRACKACTIVATE, TRUE, (LPARAM) &m_ToolTip); + } + } + return true; + case WM_MOUSELEAVE: + { + if (m_hwndTooltip) ::SendMessage (m_hwndTooltip, TTM_TRACKACTIVATE, FALSE, (LPARAM) &m_ToolTip); + if (m_bMouseTracking) { + POINT pt = { 0 }; + RECT rcWnd = { 0 }; + ::GetCursorPos (&pt); + ::GetWindowRect (m_hWndPaint, &rcWnd); + if (!::IsIconic (m_hWndPaint) && ::GetActiveWindow () == m_hWndPaint && ::PtInRect (&rcWnd, pt)) { + if (::SendMessage (m_hWndPaint, WM_NCHITTEST, 0, MAKELPARAM (pt.x, pt.y)) == HTCLIENT) { + ::ScreenToClient (m_hWndPaint, &pt); + ::SendMessage (m_hWndPaint, WM_MOUSEMOVE, 0, MAKELPARAM (pt.x, pt.y)); + } else + ::SendMessage (m_hWndPaint, WM_MOUSEMOVE, 0, (LPARAM) -1); + } else + ::SendMessage (m_hWndPaint, WM_MOUSEMOVE, 0, (LPARAM) -1); + } + m_bMouseTracking = false; + } + break; + case WM_MOUSEMOVE: + { + // Start tracking this entire window again... + if (!m_bMouseTracking) { + TRACKMOUSEEVENT tme = { 0 }; + tme.cbSize = sizeof (TRACKMOUSEEVENT); + tme.dwFlags = TME_HOVER | TME_LEAVE; + tme.hwndTrack = m_hWndPaint; + tme.dwHoverTime = !m_hwndTooltip ? m_iHoverTime : (DWORD) ::SendMessage (m_hwndTooltip, TTM_GETDELAYTIME, TTDT_INITIAL, 0L); + _TrackMouseEvent (&tme); + m_bMouseTracking = true; + } + + // Generate the appropriate mouse messages + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + // Ƿƶ + bool bNeedDrag = true; + if (m_ptLastMousePos.x == pt.x && m_ptLastMousePos.y == pt.y) { + bNeedDrag = false; + } + // ¼λ + m_ptLastMousePos = pt; + CControlUI* pNewHover = FindControl (pt); + if (pNewHover && pNewHover->GetManager () != this) break; + + // ק¼ + if (bNeedDrag && m_bDragMode && wParam == MK_LBUTTON) { + ::ReleaseCapture (); + CIDropSource* pdsrc = new CIDropSource; + if (!pdsrc) return 0; + pdsrc->AddRef (); + + CIDataObject* pdobj = new CIDataObject (pdsrc); + if (!pdobj) return 0; + pdobj->AddRef (); + + FORMATETC fmtetc = { 0 }; + STGMEDIUM medium = { 0 }; + fmtetc.dwAspect = DVASPECT_CONTENT; + fmtetc.lindex = -1; + ////////////////////////////////////// + fmtetc.cfFormat = CF_BITMAP; + fmtetc.tymed = TYMED_GDI; + medium.tymed = TYMED_GDI; + HBITMAP hBitmap = (HBITMAP) OleDuplicateData (m_hDragBitmap, fmtetc.cfFormat, NULL); + medium.hBitmap = hBitmap; + pdobj->SetData (&fmtetc, &medium, FALSE); + ////////////////////////////////////// + BITMAP bmap; + GetObject (hBitmap, sizeof (BITMAP), &bmap); + RECT rc = { 0, 0, bmap.bmWidth, bmap.bmHeight }; + fmtetc.cfFormat = CF_ENHMETAFILE; + fmtetc.tymed = TYMED_ENHMF; + HDC hMetaDC = CreateEnhMetaFile (m_hDcPaint, nullptr, nullptr, nullptr); + HDC hdcMem = CreateCompatibleDC (m_hDcPaint); + HGDIOBJ hOldBmp = ::SelectObject (hdcMem, hBitmap); + ::BitBlt (hMetaDC, 0, 0, rc.right, rc.bottom, hdcMem, 0, 0, SRCCOPY); + ::SelectObject (hdcMem, hOldBmp); + medium.hEnhMetaFile = CloseEnhMetaFile (hMetaDC); + DeleteDC (hdcMem); + medium.tymed = TYMED_ENHMF; + pdobj->SetData (&fmtetc, &medium, TRUE); + ////////////////////////////////////// + CDragSourceHelper dragSrcHelper; + POINT ptDrag = { 0 }; + ptDrag.x = bmap.bmWidth / 2; + ptDrag.y = bmap.bmHeight / 2; + dragSrcHelper.InitializeFromBitmap (hBitmap, ptDrag, rc, pdobj); //will own the bmp + DWORD dwEffect; + HRESULT hr = ::DoDragDrop (pdobj, pdsrc, DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect); + if (dwEffect) + pdsrc->Release (); + delete pdsrc; + pdobj->Release (); + m_bDragMode = false; + break; + } + TEventUI event = { 0 }; + event.ptMouse = pt; + event.wParam = wParam; + event.lParam = lParam; + event.dwTimestamp = ::GetTickCount (); + event.wKeyState = (WORD) MapKeyState (); + if (!IsCaptured ()) { + pNewHover = FindControl (pt); + if (pNewHover && pNewHover->GetManager () != this) break; + if (pNewHover != m_pEventHover && m_pEventHover) { + event.Type = UIEVENT_MOUSELEAVE; + event.pSender = m_pEventHover; + + CStdPtrArray aNeedMouseLeaveNeeded (m_aNeedMouseLeaveNeeded.GetSize ()); + aNeedMouseLeaveNeeded.Resize (m_aNeedMouseLeaveNeeded.GetSize ()); + ::CopyMemory (aNeedMouseLeaveNeeded.GetData (), m_aNeedMouseLeaveNeeded.GetData (), m_aNeedMouseLeaveNeeded.GetSize () * sizeof (LPVOID)); + for (int i = 0; i < aNeedMouseLeaveNeeded.GetSize (); i++) { + static_cast(aNeedMouseLeaveNeeded[i])->Event (event); + } + + m_pEventHover->Event (event); + m_pEventHover = nullptr; + if (m_hwndTooltip) ::SendMessage (m_hwndTooltip, TTM_TRACKACTIVATE, FALSE, (LPARAM) &m_ToolTip); + } + if (pNewHover != m_pEventHover && pNewHover) { + event.Type = UIEVENT_MOUSEENTER; + event.pSender = pNewHover; + pNewHover->Event (event); + m_pEventHover = pNewHover; + } + } + if (m_pEventClick) { + event.Type = UIEVENT_MOUSEMOVE; + event.pSender = m_pEventClick; + m_pEventClick->Event (event); + } else if (pNewHover) { + event.Type = UIEVENT_MOUSEMOVE; + event.pSender = pNewHover; + pNewHover->Event (event); + } + } + break; + case WM_LBUTTONDOWN: + { + // We alway set focus back to our app (this helps + // when Win32 child windows are placed on the dialog + // and we need to remove them on focus change). + if (!m_bNoActivate) ::SetFocus (m_hWndPaint); + if (!m_pRoot) break; + // ҿؼ + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + m_ptLastMousePos = pt; + CControlUI* pControl = FindControl (pt); + if (!pControl) break; + if (pControl->GetManager () != this) break; + + // ׼ק + if (pControl->IsDragEnabled ()) { + m_bDragMode = true; + if (m_hDragBitmap) { + ::DeleteObject (m_hDragBitmap); + m_hDragBitmap = nullptr; + } + m_hDragBitmap = CRenderEngine::GenerateBitmap (this, pControl, pControl->GetPos ()); + } + + // + SetCapture (); + // ¼ + m_pEventClick = pControl; + pControl->SetFocus (); + + TEventUI event = { 0 }; + event.Type = UIEVENT_BUTTONDOWN; + event.pSender = pControl; + event.wParam = wParam; + event.lParam = lParam; + event.ptMouse = pt; + event.wKeyState = (WORD) wParam; + event.dwTimestamp = ::GetTickCount (); + pControl->Event (event); + } + break; + case WM_LBUTTONDBLCLK: + { + if (!m_bNoActivate) ::SetFocus (m_hWndPaint); + + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + m_ptLastMousePos = pt; + CControlUI* pControl = FindControl (pt); + if (!pControl) break; + if (pControl->GetManager () != this) break; + SetCapture (); + TEventUI event = { 0 }; + event.Type = UIEVENT_DBLCLICK; + event.pSender = pControl; + event.ptMouse = pt; + event.wParam = wParam; + event.lParam = lParam; + event.wKeyState = (WORD) wParam; + event.dwTimestamp = ::GetTickCount (); + pControl->Event (event); + m_pEventClick = pControl; + } + break; + case WM_LBUTTONUP: + { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + m_ptLastMousePos = pt; + if (!m_pEventClick) break; + ReleaseCapture (); + TEventUI event = { 0 }; + event.Type = UIEVENT_BUTTONUP; + event.pSender = m_pEventClick; + event.wParam = wParam; + event.lParam = lParam; + event.ptMouse = pt; + event.wKeyState = (WORD) wParam; + event.dwTimestamp = ::GetTickCount (); + + CControlUI* pClick = m_pEventClick; + m_pEventClick = nullptr; + pClick->Event (event); + } + break; + case WM_RBUTTONDOWN: + { + if (!m_bNoActivate) ::SetFocus (m_hWndPaint); + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + m_ptLastMousePos = pt; + CControlUI* pControl = FindControl (pt); + if (!pControl) break; + if (pControl->GetManager () != this) break; + pControl->SetFocus (); + SetCapture (); + TEventUI event = { 0 }; + event.Type = UIEVENT_RBUTTONDOWN; + event.pSender = pControl; + event.wParam = wParam; + event.lParam = lParam; + event.ptMouse = pt; + event.wKeyState = (WORD) wParam; + event.dwTimestamp = ::GetTickCount (); + pControl->Event (event); + m_pEventClick = pControl; + } + break; + case WM_RBUTTONUP: + { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + m_ptLastMousePos = pt; + m_pEventClick = FindControl (pt); + if (!m_pEventClick) break; + ReleaseCapture (); + TEventUI event = { 0 }; + event.Type = UIEVENT_RBUTTONUP; + event.pSender = m_pEventClick; + event.wParam = wParam; + event.lParam = lParam; + event.ptMouse = pt; + event.wKeyState = (WORD) wParam; + event.dwTimestamp = ::GetTickCount (); + m_pEventClick->Event (event); + } + break; + case WM_MBUTTONDOWN: + { + if (!m_bNoActivate) ::SetFocus (m_hWndPaint); + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + m_ptLastMousePos = pt; + CControlUI* pControl = FindControl (pt); + if (!pControl) break; + if (pControl->GetManager () != this) break; + pControl->SetFocus (); + SetCapture (); + TEventUI event = { 0 }; + event.Type = UIEVENT_MBUTTONDOWN; + event.pSender = pControl; + event.wParam = wParam; + event.lParam = lParam; + event.ptMouse = pt; + event.wKeyState = (WORD) wParam; + event.dwTimestamp = ::GetTickCount (); + pControl->Event (event); + m_pEventClick = pControl; + } + break; + case WM_MBUTTONUP: + { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + m_ptLastMousePos = pt; + m_pEventClick = FindControl (pt); + if (!m_pEventClick) break; + ReleaseCapture (); + + TEventUI event = { 0 }; + event.Type = UIEVENT_MBUTTONUP; + event.pSender = m_pEventClick; + event.wParam = wParam; + event.lParam = lParam; + event.ptMouse = pt; + event.wKeyState = (WORD) wParam; + event.dwTimestamp = ::GetTickCount (); + m_pEventClick->Event (event); + } + break; + case WM_CONTEXTMENU: + { + if (!m_pRoot) break; + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + ::ScreenToClient (m_hWndPaint, &pt); + m_ptLastMousePos = pt; + if (!m_pEventClick) break; + ReleaseCapture (); + TEventUI event = { 0 }; + event.Type = UIEVENT_CONTEXTMENU; + event.pSender = m_pEventClick; + event.wParam = wParam; + event.lParam = lParam; + event.ptMouse = pt; + event.wKeyState = (WORD) wParam; + event.lParam = (LPARAM) m_pEventClick; + event.dwTimestamp = ::GetTickCount (); + m_pEventClick->Event (event); + m_pEventClick = nullptr; + } + break; + case WM_MOUSEWHEEL: + { + if (!m_pRoot) break; + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + ::ScreenToClient (m_hWndPaint, &pt); + m_ptLastMousePos = pt; + CControlUI* pControl = FindControl (pt); + if (!pControl) break; + if (pControl->GetManager () != this) break; + int zDelta = (int) (short) HIWORD (wParam); + TEventUI event = { 0 }; + event.Type = UIEVENT_SCROLLWHEEL; + event.pSender = pControl; + event.wParam = MAKELPARAM (zDelta < 0 ? SB_LINEDOWN : SB_LINEUP, 0); + event.lParam = lParam; + event.ptMouse = pt; + event.wKeyState = (WORD) MapKeyState (); + event.dwTimestamp = ::GetTickCount (); + pControl->Event (event); + + // Let's make sure that the scroll item below the cursor is the same as before... + ::SendMessage (m_hWndPaint, WM_MOUSEMOVE, 0, (LPARAM) MAKELPARAM (m_ptLastMousePos.x, m_ptLastMousePos.y)); + } + break; + case WM_CHAR: + { + if (!m_pRoot) break; + if (!m_pFocus) break; + TEventUI event = { 0 }; + event.Type = UIEVENT_CHAR; + event.pSender = m_pFocus; + event.wParam = wParam; + event.lParam = lParam; + event.chKey = (TCHAR) wParam; + event.ptMouse = m_ptLastMousePos; + event.wKeyState = (WORD) MapKeyState (); + event.dwTimestamp = ::GetTickCount (); + m_pFocus->Event (event); + } + break; + case WM_KEYDOWN: + { + if (!m_pRoot) break; + if (!m_pFocus) break; + TEventUI event = { 0 }; + event.Type = UIEVENT_KEYDOWN; + event.pSender = m_pFocus; + event.wParam = wParam; + event.lParam = lParam; + event.chKey = (TCHAR) wParam; + event.ptMouse = m_ptLastMousePos; + event.wKeyState = (WORD) MapKeyState (); + event.dwTimestamp = ::GetTickCount (); + m_pFocus->Event (event); + m_pEventKey = m_pFocus; + } + break; + case WM_KEYUP: + { + if (!m_pRoot) break; + if (!m_pEventKey) break; + TEventUI event = { 0 }; + event.Type = UIEVENT_KEYUP; + event.pSender = m_pEventKey; + event.wParam = wParam; + event.lParam = lParam; + event.chKey = (TCHAR) wParam; + event.ptMouse = m_ptLastMousePos; + event.wKeyState = (WORD) MapKeyState (); + event.dwTimestamp = ::GetTickCount (); + m_pEventKey->Event (event); + m_pEventKey = nullptr; + } + break; + case WM_SETCURSOR: + { + if (!m_pRoot) break; + if (LOWORD (lParam) != HTCLIENT) break; + if (m_bMouseCapture) return true; + + POINT pt = { 0 }; + ::GetCursorPos (&pt); + ::ScreenToClient (m_hWndPaint, &pt); + CControlUI* pControl = FindControl (pt); + if (!pControl) break; + if ((pControl->GetControlFlags () & UIFLAG_SETCURSOR) == 0) break; + TEventUI event = { 0 }; + event.Type = UIEVENT_SETCURSOR; + event.pSender = pControl; + event.wParam = wParam; + event.lParam = lParam; + event.ptMouse = pt; + event.wKeyState = (WORD) MapKeyState (); + event.dwTimestamp = ::GetTickCount (); + pControl->Event (event); + } + return true; + case WM_SETFOCUS: + { + if (m_pFocus) { + TEventUI event = { 0 }; + event.Type = UIEVENT_SETFOCUS; + event.wParam = wParam; + event.lParam = lParam; + event.pSender = m_pFocus; + event.dwTimestamp = ::GetTickCount (); + m_pFocus->Event (event); + } + break; + } + case WM_KILLFOCUS: + { + if (IsCaptured ()) ReleaseCapture (); + break; + } + case WM_NOTIFY: + { + if (lParam == 0) break; + LPNMHDR lpNMHDR = (LPNMHDR) lParam; + if (lpNMHDR) lRes = ::SendMessage (lpNMHDR->hwndFrom, OCM__BASE + uMsg, wParam, lParam); + return true; + } + break; + case WM_COMMAND: + { + if (lParam == 0) break; + HWND hWndChild = (HWND) lParam; + lRes = ::SendMessage (hWndChild, OCM__BASE + uMsg, wParam, lParam); + if (lRes != 0) return true; + } + break; + case WM_CTLCOLOREDIT: + case WM_CTLCOLORSTATIC: + { + // Refer To: http://msdn.microsoft.com/en-us/library/bb761691(v=vs.85).aspx + // Read-only or disabled edit controls do not send the WM_CTLCOLOREDIT message; instead, they send the WM_CTLCOLORSTATIC message. + if (lParam == 0) break; + HWND hWndChild = (HWND) lParam; + lRes = ::SendMessage (hWndChild, OCM__BASE + uMsg, wParam, lParam); + if (lRes != 0) return true; + } + break; + default: + break; + } + return false; + } + + bool CPaintManagerUI::IsUpdateNeeded () const { + return m_bUpdateNeeded; + } + + void CPaintManagerUI::NeedUpdate () { + m_bUpdateNeeded = true; + } + + void CPaintManagerUI::Invalidate () { + RECT rcClient = { 0 }; + ::GetClientRect (m_hWndPaint, &rcClient); + ::UnionRect (&m_rcLayeredUpdate, &m_rcLayeredUpdate, &rcClient); + ::InvalidateRect (m_hWndPaint, nullptr, FALSE); + } + + void CPaintManagerUI::Invalidate (RECT& rcItem) { + if (rcItem.left < 0) rcItem.left = 0; + if (rcItem.top < 0) rcItem.top = 0; + if (rcItem.right < rcItem.left) rcItem.right = rcItem.left; + if (rcItem.bottom < rcItem.top) rcItem.bottom = rcItem.top; + ::UnionRect (&m_rcLayeredUpdate, &m_rcLayeredUpdate, &rcItem); + ::InvalidateRect (m_hWndPaint, &rcItem, FALSE); + } + + bool CPaintManagerUI::AttachDialog (CControlUI* pControl) { + ASSERT (::IsWindow (m_hWndPaint)); + // Ӱ + m_shadow.Create (this); + + // Reset any previous attachment + SetFocus (nullptr); + m_pEventKey = nullptr; + m_pEventHover = nullptr; + m_pEventClick = nullptr; + // Remove the existing control-tree. We might have gotten inside this function as + // a result of an event fired or similar, so we cannot just delete the objects and + // pull the internal memory of the calling code. We'll delay the cleanup. + if (m_pRoot) { + m_aPostPaintControls.Empty (); + AddDelayedCleanup (m_pRoot); + } + // Set the dialog root element + m_pRoot = pControl; + // Go ahead... + m_bUpdateNeeded = true; + m_bFirstLayout = true; + m_bFocusNeeded = true; + // Initiate all control + return InitControls (pControl); + } + + bool CPaintManagerUI::InitControls (CControlUI* pControl, CControlUI* pParent /*= nullptr*/) { + ASSERT (pControl); + if (!pControl) return false; + pControl->SetManager (this, pParent ? pParent : pControl->GetParent (), true); + pControl->FindControl (__FindControlFromNameHash, this, UIFIND_ALL); + return true; + } + + void CPaintManagerUI::ReapObjects (CControlUI* pControl) { + if (pControl == m_pEventKey) m_pEventKey = nullptr; + if (pControl == m_pEventHover) m_pEventHover = nullptr; + if (pControl == m_pEventClick) m_pEventClick = nullptr; + if (pControl == m_pFocus) m_pFocus = nullptr; + KillTimer (pControl); + const string_view_t sName = pControl->GetName (); + if (!sName.empty ()) { + if (pControl == FindControl (sName)) m_mNameHash.Remove (sName); + } + for (int i = 0; i < m_aAsyncNotify.GetSize (); i++) { + TNotifyUI* pMsg = static_cast(m_aAsyncNotify[i]); + if (pMsg->pSender == pControl) pMsg->pSender = nullptr; + } + } + + bool CPaintManagerUI::AddOptionGroup (string_view_t pStrGroupName, CControlUI* pControl) { + LPVOID lp = m_mOptionGroup.Find (pStrGroupName); + if (lp) { + CStdPtrArray* aOptionGroup = static_cast(lp); + for (int i = 0; i < aOptionGroup->GetSize (); i++) { + if (static_cast(aOptionGroup->GetAt (i)) == pControl) { + return false; + } + } + aOptionGroup->Add (pControl); + } else { + CStdPtrArray* aOptionGroup = new CStdPtrArray (6); + aOptionGroup->Add (pControl); + m_mOptionGroup.Insert (pStrGroupName, aOptionGroup); + } + return true; + } + + CStdPtrArray* CPaintManagerUI::GetOptionGroup (string_view_t pStrGroupName) { + LPVOID lp = m_mOptionGroup.Find (pStrGroupName); + if (lp) return static_cast(lp); + return nullptr; + } + + void CPaintManagerUI::RemoveOptionGroup (string_view_t pStrGroupName, CControlUI* pControl) { + LPVOID lp = m_mOptionGroup.Find (pStrGroupName); + if (lp) { + CStdPtrArray* aOptionGroup = static_cast(lp); + if (!aOptionGroup) return; + for (int i = 0; i < aOptionGroup->GetSize (); i++) { + if (static_cast(aOptionGroup->GetAt (i)) == pControl) { + aOptionGroup->Remove (i); + break; + } + } + if (aOptionGroup->empty ()) { + delete aOptionGroup; + m_mOptionGroup.Remove (pStrGroupName); + } + } + } + + void CPaintManagerUI::RemoveAllOptionGroups () { + CStdPtrArray* aOptionGroup; + for (int i = 0; i < m_mOptionGroup.GetSize (); i++) { + string_view_t key = m_mOptionGroup.GetAt (i)->Key; + if (!key.empty ()) { + aOptionGroup = static_cast(m_mOptionGroup.Find (key)); + delete aOptionGroup; + } + } + m_mOptionGroup.RemoveAll (); + } + + void CPaintManagerUI::MessageLoop () { + MSG msg = { 0 }; + while (::GetMessage (&msg, nullptr, 0, 0)) { + if (!CPaintManagerUI::TranslateMessage (&msg)) { + ::TranslateMessage (&msg); + try { + ::DispatchMessage (&msg); + } catch (...) { + DUITRACE (_T ("EXCEPTION: %s(%d)\n"), __FILET__, __LINE__); +#ifdef _DEBUG + throw "CPaintManagerUI::MessageLoop"; +#endif + } + } + } + } + + void CPaintManagerUI::Term () { + // Դ + CResourceManager::GetInstance ()->Release (); + CControlFactory::GetInstance ()->Release (); + //CMenuWnd::DestroyMenu(); + + // Դ + // ͼƬ + TImageInfo* data; + for (int i = 0; i < m_SharedResInfo.m_ImageHash.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_ImageHash.GetAt (i)->Key; + if (!key.empty ()) { + data = static_cast(m_SharedResInfo.m_ImageHash.Find (key, false)); + if (data) { + CRenderEngine::FreeImage (data); + data = nullptr; + } + } + } + m_SharedResInfo.m_ImageHash.RemoveAll (); + // + TFontInfo* pFontInfo; + for (int i = 0; i < m_SharedResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_SharedResInfo.m_CustomFonts.Find (key, false)); + if (pFontInfo) { + ::DeleteObject (pFontInfo->hFont); + delete pFontInfo; + pFontInfo = nullptr; + } + } + } + m_SharedResInfo.m_CustomFonts.RemoveAll (); + // Ĭ + if (m_SharedResInfo.m_DefaultFontInfo.hFont) { + ::DeleteObject (m_SharedResInfo.m_DefaultFontInfo.hFont); + } + // ʽ + CDuiString* pStyle; + for (int i = 0; i < m_SharedResInfo.m_StyleHash.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_StyleHash.GetAt (i)->Key; + if (!key.empty ()) { + pStyle = static_cast(m_SharedResInfo.m_StyleHash.Find (key, false)); + if (pStyle) { + delete pStyle; + pStyle = nullptr; + } + } + } + m_SharedResInfo.m_StyleHash.RemoveAll (); + + // ʽ + CDuiString* pAttr; + for (int i = 0; i < m_SharedResInfo.m_AttrHash.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_AttrHash.GetAt (i)->Key; + if (!key.empty ()) { + pAttr = static_cast(m_SharedResInfo.m_AttrHash.Find (key, false)); + if (pAttr) { + delete pAttr; + pAttr = nullptr; + } + } + } + m_SharedResInfo.m_AttrHash.RemoveAll (); + + // رZIP + if (m_bCachedResourceZip && m_hResourceZip) { + CloseZip ((HZIP) m_hResourceZip); + m_hResourceZip = nullptr; + } + } + + CDPI * DuiLib::CPaintManagerUI::GetDPIObj () { + if (!m_pDPI) { + m_pDPI = new CDPI; + } + return m_pDPI; + } + + void DuiLib::CPaintManagerUI::SetDPI (int iDPI) { + int scale1 = GetDPIObj ()->GetScale (); + GetDPIObj ()->SetScale (iDPI); + int scale2 = GetDPIObj ()->GetScale (); + ResetDPIAssets (); + RECT rcWnd = { 0 }; + ::GetWindowRect (GetPaintWindow (), &rcWnd); + RECT* prcNewWindow = &rcWnd; + if (!::IsZoomed (GetPaintWindow ())) { + RECT rc = rcWnd; + rc.right = rcWnd.left + (rcWnd.right - rcWnd.left) * scale2 / scale1; + rc.bottom = rcWnd.top + (rcWnd.bottom - rcWnd.top) * scale2 / scale1; + prcNewWindow = &rc; + } + SetWindowPos (GetPaintWindow (), NULL, prcNewWindow->left, prcNewWindow->top, prcNewWindow->right - prcNewWindow->left, prcNewWindow->bottom - prcNewWindow->top, SWP_NOZORDER | SWP_NOACTIVATE); + if (GetRoot ()) GetRoot ()->NeedUpdate (); + ::PostMessage (GetPaintWindow (), WM_USER_SET_DPI, 0, 0); + } + + void DuiLib::CPaintManagerUI::SetAllDPI (int iDPI) { + for (int i = 0; i < m_aPreMessages.GetSize (); i++) { + CPaintManagerUI* pManager = static_cast(m_aPreMessages[i]); + pManager->SetDPI (iDPI); + } + } + + void DuiLib::CPaintManagerUI::ResetDPIAssets () { + RemoveAllDrawInfos (); + RemoveAllImages ();; + + for (int it = 0; it < m_ResInfo.m_CustomFonts.GetSize (); it++) { + TFontInfo* pFontInfo = static_cast(m_ResInfo.m_CustomFonts[it]->Data); + RebuildFont (pFontInfo); + } + RebuildFont (&m_ResInfo.m_DefaultFontInfo); + + for (int it = 0; it < m_SharedResInfo.m_CustomFonts.GetSize (); it++) { + TFontInfo* pFontInfo = static_cast(m_SharedResInfo.m_CustomFonts[it]->Data); + RebuildFont (pFontInfo); + } + RebuildFont (&m_SharedResInfo.m_DefaultFontInfo); + + CStdPtrArray *richEditList = FindSubControlsByClass (GetRoot (), _T ("RichEditUI")); + for (int i = 0; i < richEditList->GetSize (); i++) { + CRichEditUI* pT = static_cast((*richEditList)[i]); + pT->SetFont (pT->GetFont ()); + + } + } + + void DuiLib::CPaintManagerUI::RebuildFont (TFontInfo * pFontInfo) { + ::DeleteObject (pFontInfo->hFont); + LOGFONT lf = { 0 }; + ::GetObject (::GetStockObject (DEFAULT_GUI_FONT), sizeof (LOGFONT), &lf); + _tcsncpy (lf.lfFaceName, pFontInfo->sFontName.data (), LF_FACESIZE); + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfHeight = -GetDPIObj ()->Scale (pFontInfo->iSize); + lf.lfQuality = CLEARTYPE_QUALITY; + if (pFontInfo->bBold) lf.lfWeight += FW_BOLD; + if (pFontInfo->bUnderline) lf.lfUnderline = TRUE; + if (pFontInfo->bItalic) lf.lfItalic = TRUE; + HFONT hFont = ::CreateFontIndirect (&lf); + pFontInfo->hFont = hFont; + ::ZeroMemory (&(pFontInfo->tm), sizeof (pFontInfo->tm)); + if (m_hDcPaint) { + HFONT hOldFont = (HFONT) ::SelectObject (m_hDcPaint, hFont); + ::GetTextMetrics (m_hDcPaint, &pFontInfo->tm); + ::SelectObject (m_hDcPaint, hOldFont); + } + } + + CControlUI* CPaintManagerUI::GetFocus () const { + return m_pFocus; + } + + void CPaintManagerUI::SetFocus (CControlUI* pControl) { + // Paint manager window has focus? + HWND hFocusWnd = ::GetFocus (); + if (hFocusWnd != m_hWndPaint && pControl != m_pFocus) ::SetFocus (m_hWndPaint); + // Already has focus? + if (pControl == m_pFocus) return; + // Remove focus from old control + if (m_pFocus) { + TEventUI event = { 0 }; + event.Type = UIEVENT_KILLFOCUS; + event.pSender = pControl; + event.dwTimestamp = ::GetTickCount (); + m_pFocus->Event (event); + SendNotify (m_pFocus, DUI_MSGTYPE_KILLFOCUS); + m_pFocus = nullptr; + } + if (!pControl) return; + // Set focus to new control + if (pControl + && pControl->GetManager () == this + && pControl->IsVisible () + && pControl->IsEnabled ()) { + m_pFocus = pControl; + TEventUI event = { 0 }; + event.Type = UIEVENT_SETFOCUS; + event.pSender = pControl; + event.dwTimestamp = ::GetTickCount (); + m_pFocus->Event (event); + SendNotify (m_pFocus, DUI_MSGTYPE_SETFOCUS); + } + } + + void CPaintManagerUI::SetFocusNeeded (CControlUI* pControl) { + ::SetFocus (m_hWndPaint); + if (!pControl) return; + if (m_pFocus) { + TEventUI event = { 0 }; + event.Type = UIEVENT_KILLFOCUS; + event.pSender = pControl; + event.dwTimestamp = ::GetTickCount (); + m_pFocus->Event (event); + SendNotify (m_pFocus, DUI_MSGTYPE_KILLFOCUS); + m_pFocus = nullptr; + } + FINDTABINFO info = { 0 }; + info.pFocus = pControl; + info.bForward = false; + m_pFocus = m_pRoot->FindControl (__FindControlFromTab, &info, UIFIND_VISIBLE | UIFIND_ENABLED | UIFIND_ME_FIRST); + m_bFocusNeeded = true; + if (m_pRoot) m_pRoot->NeedUpdate (); + } + + bool CPaintManagerUI::SetTimer (CControlUI* pControl, UINT nTimerID, UINT uElapse) { + ASSERT (pControl); + ASSERT (uElapse > 0); + for (int i = 0; i < m_aTimers.GetSize (); i++) { + TIMERINFO* pTimer = static_cast(m_aTimers[i]); + if (pTimer->pSender == pControl + && pTimer->hWnd == m_hWndPaint + && pTimer->nLocalID == nTimerID) { + if (pTimer->bKilled == true) { + if (::SetTimer (m_hWndPaint, pTimer->uWinTimer, uElapse, nullptr)) { + pTimer->bKilled = false; + return true; + } + return false; + } + return false; + } + } + + m_uTimerID = (++m_uTimerID) % 0xF0; //0xf1-0xfe; + if (!::SetTimer (m_hWndPaint, m_uTimerID, uElapse, nullptr)) return FALSE; + TIMERINFO* pTimer = new TIMERINFO; + if (!pTimer) return FALSE; + pTimer->hWnd = m_hWndPaint; + pTimer->pSender = pControl; + pTimer->nLocalID = nTimerID; + pTimer->uWinTimer = m_uTimerID; + pTimer->bKilled = false; + return m_aTimers.Add (pTimer); + } + + bool CPaintManagerUI::KillTimer (CControlUI* pControl, UINT nTimerID) { + ASSERT (pControl); + for (int i = 0; i < m_aTimers.GetSize (); i++) { + TIMERINFO* pTimer = static_cast(m_aTimers[i]); + if (pTimer->pSender == pControl + && pTimer->hWnd == m_hWndPaint + && pTimer->nLocalID == nTimerID) { + if (pTimer->bKilled == false) { + if (::IsWindow (m_hWndPaint)) ::KillTimer (pTimer->hWnd, pTimer->uWinTimer); + pTimer->bKilled = true; + return true; + } + } + } + return false; + } + + void CPaintManagerUI::KillTimer (CControlUI* pControl) { + ASSERT (pControl); + int count = m_aTimers.GetSize (); + for (int i = 0, j = 0; i < count; i++) { + TIMERINFO* pTimer = static_cast(m_aTimers[i - j]); + if (pTimer->pSender == pControl && pTimer->hWnd == m_hWndPaint) { + if (pTimer->bKilled == false) ::KillTimer (pTimer->hWnd, pTimer->uWinTimer); + delete pTimer; + m_aTimers.Remove (i - j); + j++; + } + } + } + + void CPaintManagerUI::RemoveAllTimers () { + for (int i = 0; i < m_aTimers.GetSize (); i++) { + TIMERINFO* pTimer = static_cast(m_aTimers[i]); + if (pTimer->hWnd == m_hWndPaint) { + if (pTimer->bKilled == false) { + if (::IsWindow (m_hWndPaint)) ::KillTimer (m_hWndPaint, pTimer->uWinTimer); + } + delete pTimer; + } + } + + m_aTimers.Empty (); + } + + void CPaintManagerUI::SetCapture () { + ::SetCapture (m_hWndPaint); + m_bMouseCapture = true; + } + + void CPaintManagerUI::ReleaseCapture () { + ::ReleaseCapture (); + m_bMouseCapture = false; + m_bDragMode = false; + } + + bool CPaintManagerUI::IsCaptured () { + return m_bMouseCapture; + } + + bool CPaintManagerUI::IsPainting () { + return m_bIsPainting; + } + + void CPaintManagerUI::SetPainting (bool bIsPainting) { + m_bIsPainting = bIsPainting; + } + + bool CPaintManagerUI::SetNextTabControl (bool bForward) { + // If we're in the process of restructuring the layout we can delay the + // focus calulation until the next repaint. + if (m_bUpdateNeeded && bForward) { + m_bFocusNeeded = true; + ::InvalidateRect (m_hWndPaint, nullptr, FALSE); + return true; + } + // Find next/previous tabbable control + FINDTABINFO info1 = { 0 }; + info1.pFocus = m_pFocus; + info1.bForward = bForward; + CControlUI* pControl = m_pRoot->FindControl (__FindControlFromTab, &info1, UIFIND_VISIBLE | UIFIND_ENABLED | UIFIND_ME_FIRST); + if (!pControl) { + if (bForward) { + // Wrap around + FINDTABINFO info2 = { 0 }; + info2.pFocus = bForward ? nullptr : info1.pLast; + info2.bForward = bForward; + pControl = m_pRoot->FindControl (__FindControlFromTab, &info2, UIFIND_VISIBLE | UIFIND_ENABLED | UIFIND_ME_FIRST); + } else { + pControl = info1.pLast; + } + } + if (pControl) SetFocus (pControl); + m_bFocusNeeded = false; + return true; + } + + bool CPaintManagerUI::AddNotifier (INotifyUI* pNotifier) { + ASSERT (m_aNotifiers.Find (pNotifier) < 0); + return m_aNotifiers.Add (pNotifier); + } + + bool CPaintManagerUI::RemoveNotifier (INotifyUI* pNotifier) { + for (int i = 0; i < m_aNotifiers.GetSize (); i++) { + if (static_cast(m_aNotifiers[i]) == pNotifier) { + return m_aNotifiers.Remove (i); + } + } + return false; + } + + bool CPaintManagerUI::AddPreMessageFilter (IMessageFilterUI* pFilter) { + ASSERT (m_aPreMessageFilters.Find (pFilter) < 0); + return m_aPreMessageFilters.Add (pFilter); + } + + bool CPaintManagerUI::RemovePreMessageFilter (IMessageFilterUI* pFilter) { + for (int i = 0; i < m_aPreMessageFilters.GetSize (); i++) { + if (static_cast(m_aPreMessageFilters[i]) == pFilter) { + return m_aPreMessageFilters.Remove (i); + } + } + return false; + } + + bool CPaintManagerUI::AddMessageFilter (IMessageFilterUI* pFilter) { + ASSERT (m_aMessageFilters.Find (pFilter) < 0); + return m_aMessageFilters.Add (pFilter); + } + + bool CPaintManagerUI::RemoveMessageFilter (IMessageFilterUI* pFilter) { + for (int i = 0; i < m_aMessageFilters.GetSize (); i++) { + if (static_cast(m_aMessageFilters[i]) == pFilter) { + return m_aMessageFilters.Remove (i); + } + } + return false; + } + + int CPaintManagerUI::GetPostPaintCount () const { + return m_aPostPaintControls.GetSize (); + } + + bool CPaintManagerUI::IsPostPaint (CControlUI* pControl) { + return m_aPostPaintControls.Find (pControl) >= 0; + } + + bool CPaintManagerUI::AddPostPaint (CControlUI* pControl) { + ASSERT (m_aPostPaintControls.Find (pControl) < 0); + return m_aPostPaintControls.Add (pControl); + } + + bool CPaintManagerUI::RemovePostPaint (CControlUI* pControl) { + for (int i = 0; i < m_aPostPaintControls.GetSize (); i++) { + if (static_cast(m_aPostPaintControls[i]) == pControl) { + return m_aPostPaintControls.Remove (i); + } + } + return false; + } + + bool CPaintManagerUI::SetPostPaintIndex (CControlUI* pControl, int iIndex) { + RemovePostPaint (pControl); + return m_aPostPaintControls.InsertAt (iIndex, pControl); + } + + int CPaintManagerUI::GetNativeWindowCount () const { + return m_aNativeWindow.GetSize (); + } + + bool CPaintManagerUI::AddNativeWindow (CControlUI* pControl, HWND hChildWnd) { + if (!pControl || !hChildWnd) return false; + + RECT rcChildWnd = GetNativeWindowRect (hChildWnd); + Invalidate (rcChildWnd); + + if (m_aNativeWindow.Find (hChildWnd) >= 0) return false; + if (m_aNativeWindow.Add (hChildWnd)) { + m_aNativeWindowControl.Add (pControl); + return true; + } + return false; + } + + bool CPaintManagerUI::RemoveNativeWindow (HWND hChildWnd) { + for (int i = 0; i < m_aNativeWindow.GetSize (); i++) { + if (static_cast(m_aNativeWindow[i]) == hChildWnd) { + if (m_aNativeWindow.Remove (i)) { + m_aNativeWindowControl.Remove (i); + return true; + } + return false; + } + } + return false; + } + + RECT CPaintManagerUI::GetNativeWindowRect (HWND hChildWnd) { + RECT rcChildWnd = { 0 }; + ::GetWindowRect (hChildWnd, &rcChildWnd); + ::ScreenToClient (m_hWndPaint, (LPPOINT) (&rcChildWnd)); + ::ScreenToClient (m_hWndPaint, (LPPOINT) (&rcChildWnd) + 1); + return rcChildWnd; + } + + void CPaintManagerUI::AddDelayedCleanup (CControlUI* pControl) { + if (!pControl) return; + pControl->SetManager (this, nullptr, false); + m_aDelayedCleanup.Add (pControl); + PostAsyncNotify (); + } + + void CPaintManagerUI::AddMouseLeaveNeeded (CControlUI* pControl) { + if (!pControl) return; + for (int i = 0; i < m_aNeedMouseLeaveNeeded.GetSize (); i++) { + if (static_cast(m_aNeedMouseLeaveNeeded[i]) == pControl) { + return; + } + } + m_aNeedMouseLeaveNeeded.Add (pControl); + } + + bool CPaintManagerUI::RemoveMouseLeaveNeeded (CControlUI* pControl) { + if (!pControl) return false; + for (int i = 0; i < m_aNeedMouseLeaveNeeded.GetSize (); i++) { + if (static_cast(m_aNeedMouseLeaveNeeded[i]) == pControl) { + return m_aNeedMouseLeaveNeeded.Remove (i); + } + } + return false; + } + + void CPaintManagerUI::SendNotify (CControlUI* pControl, string_view_t pstrMessage, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/, bool bAsync /*= false*/) { + TNotifyUI Msg; + Msg.pSender = pControl; + Msg.sType = pstrMessage; + Msg.wParam = wParam; + Msg.lParam = lParam; + SendNotify (Msg, bAsync); + } + + void CPaintManagerUI::SendNotify (TNotifyUI& Msg, bool bAsync /*= false*/) { + Msg.ptMouse = m_ptLastMousePos; + Msg.dwTimestamp = ::GetTickCount (); + if (m_bUsedVirtualWnd) { + Msg.sVirtualWnd = Msg.pSender->GetVirtualWnd (); + } + + if (!bAsync) { + // Send to all listeners + if (Msg.pSender) { + if (Msg.pSender->OnNotify) Msg.pSender->OnNotify (&Msg); + } + for (int i = 0; i < m_aNotifiers.GetSize (); i++) { + static_cast(m_aNotifiers[i])->Notify (Msg); + } + } else { + TNotifyUI *pMsg = new TNotifyUI; + pMsg->pSender = Msg.pSender; + pMsg->sType = Msg.sType; + pMsg->wParam = Msg.wParam; + pMsg->lParam = Msg.lParam; + pMsg->ptMouse = Msg.ptMouse; + pMsg->dwTimestamp = Msg.dwTimestamp; + m_aAsyncNotify.Add (pMsg); + + PostAsyncNotify (); + } + } + + bool CPaintManagerUI::IsForceUseSharedRes () const { + return m_bForceUseSharedRes; + } + + void CPaintManagerUI::SetForceUseSharedRes (bool bForce) { + m_bForceUseSharedRes = bForce; + } + + DWORD CPaintManagerUI::GetDefaultDisabledColor () const { + return m_ResInfo.m_dwDefaultDisabledColor; + } + + void CPaintManagerUI::SetDefaultDisabledColor (DWORD dwColor, bool bShared) { + if (bShared) { + if (m_ResInfo.m_dwDefaultDisabledColor == m_SharedResInfo.m_dwDefaultDisabledColor) + m_ResInfo.m_dwDefaultDisabledColor = dwColor; + m_SharedResInfo.m_dwDefaultDisabledColor = dwColor; + } else { + m_ResInfo.m_dwDefaultDisabledColor = dwColor; + } + } + + DWORD CPaintManagerUI::GetDefaultFontColor () const { + return m_ResInfo.m_dwDefaultFontColor; + } + + void CPaintManagerUI::SetDefaultFontColor (DWORD dwColor, bool bShared) { + if (bShared) { + if (m_ResInfo.m_dwDefaultFontColor == m_SharedResInfo.m_dwDefaultFontColor) + m_ResInfo.m_dwDefaultFontColor = dwColor; + m_SharedResInfo.m_dwDefaultFontColor = dwColor; + } else { + m_ResInfo.m_dwDefaultFontColor = dwColor; + } + } + + DWORD CPaintManagerUI::GetDefaultLinkFontColor () const { + return m_ResInfo.m_dwDefaultLinkFontColor; + } + + void CPaintManagerUI::SetDefaultLinkFontColor (DWORD dwColor, bool bShared) { + if (bShared) { + if (m_ResInfo.m_dwDefaultLinkFontColor == m_SharedResInfo.m_dwDefaultLinkFontColor) + m_ResInfo.m_dwDefaultLinkFontColor = dwColor; + m_SharedResInfo.m_dwDefaultLinkFontColor = dwColor; + } else { + m_ResInfo.m_dwDefaultLinkFontColor = dwColor; + } + } + + DWORD CPaintManagerUI::GetDefaultLinkHoverFontColor () const { + return m_ResInfo.m_dwDefaultLinkHoverFontColor; + } + + void CPaintManagerUI::SetDefaultLinkHoverFontColor (DWORD dwColor, bool bShared) { + if (bShared) { + if (m_ResInfo.m_dwDefaultLinkHoverFontColor == m_SharedResInfo.m_dwDefaultLinkHoverFontColor) + m_ResInfo.m_dwDefaultLinkHoverFontColor = dwColor; + m_SharedResInfo.m_dwDefaultLinkHoverFontColor = dwColor; + } else { + m_ResInfo.m_dwDefaultLinkHoverFontColor = dwColor; + } + } + + DWORD CPaintManagerUI::GetDefaultSelectedBkColor () const { + return m_ResInfo.m_dwDefaultSelectedBkColor; + } + + void CPaintManagerUI::SetDefaultSelectedBkColor (DWORD dwColor, bool bShared) { + if (bShared) { + if (m_ResInfo.m_dwDefaultSelectedBkColor == m_SharedResInfo.m_dwDefaultSelectedBkColor) + m_ResInfo.m_dwDefaultSelectedBkColor = dwColor; + m_SharedResInfo.m_dwDefaultSelectedBkColor = dwColor; + } else { + m_ResInfo.m_dwDefaultSelectedBkColor = dwColor; + } + } + + TFontInfo* CPaintManagerUI::GetDefaultFontInfo () { + if (m_ResInfo.m_DefaultFontInfo.sFontName.empty ()) { + if (m_SharedResInfo.m_DefaultFontInfo.tm.tmHeight == 0) { + HFONT hOldFont = (HFONT) ::SelectObject (m_hDcPaint, m_SharedResInfo.m_DefaultFontInfo.hFont); + ::GetTextMetrics (m_hDcPaint, &m_SharedResInfo.m_DefaultFontInfo.tm); + ::SelectObject (m_hDcPaint, hOldFont); + } + return &m_SharedResInfo.m_DefaultFontInfo; + } else { + if (m_ResInfo.m_DefaultFontInfo.tm.tmHeight == 0) { + HFONT hOldFont = (HFONT) ::SelectObject (m_hDcPaint, m_ResInfo.m_DefaultFontInfo.hFont); + ::GetTextMetrics (m_hDcPaint, &m_ResInfo.m_DefaultFontInfo.tm); + ::SelectObject (m_hDcPaint, hOldFont); + } + return &m_ResInfo.m_DefaultFontInfo; + } + } + + void CPaintManagerUI::SetDefaultFont (string_view_t pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic, bool bShared) { + LOGFONT lf = { 0 }; + ::GetObject (::GetStockObject (DEFAULT_GUI_FONT), sizeof (LOGFONT), &lf); + if (pStrFontName.length () > 0) { + _tcsncpy (lf.lfFaceName, pStrFontName.data (), lengthof (lf.lfFaceName)); + } + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfHeight = -GetDPIObj ()->Scale (nSize);; + if (bBold) lf.lfWeight += FW_BOLD; + if (bUnderline) lf.lfUnderline = TRUE; + if (bItalic) lf.lfItalic = TRUE; + + HFONT hFont = ::CreateFontIndirect (&lf); + if (!hFont) return; + + if (bShared) { + ::DeleteObject (m_SharedResInfo.m_DefaultFontInfo.hFont); + m_SharedResInfo.m_DefaultFontInfo.hFont = hFont; + m_SharedResInfo.m_DefaultFontInfo.sFontName = lf.lfFaceName; + m_SharedResInfo.m_DefaultFontInfo.iSize = nSize; + m_SharedResInfo.m_DefaultFontInfo.bBold = bBold; + m_SharedResInfo.m_DefaultFontInfo.bUnderline = bUnderline; + m_SharedResInfo.m_DefaultFontInfo.bItalic = bItalic; + ::ZeroMemory (&m_SharedResInfo.m_DefaultFontInfo.tm, sizeof (m_SharedResInfo.m_DefaultFontInfo.tm)); + if (m_hDcPaint) { + HFONT hOldFont = (HFONT) ::SelectObject (m_hDcPaint, hFont); + ::GetTextMetrics (m_hDcPaint, &m_SharedResInfo.m_DefaultFontInfo.tm); + ::SelectObject (m_hDcPaint, hOldFont); + } + } else { + ::DeleteObject (m_ResInfo.m_DefaultFontInfo.hFont); + m_ResInfo.m_DefaultFontInfo.hFont = hFont; + m_ResInfo.m_DefaultFontInfo.sFontName = lf.lfFaceName; + m_ResInfo.m_DefaultFontInfo.iSize = nSize; + m_ResInfo.m_DefaultFontInfo.bBold = bBold; + m_ResInfo.m_DefaultFontInfo.bUnderline = bUnderline; + m_ResInfo.m_DefaultFontInfo.bItalic = bItalic; + ::ZeroMemory (&m_ResInfo.m_DefaultFontInfo.tm, sizeof (m_ResInfo.m_DefaultFontInfo.tm)); + if (m_hDcPaint) { + HFONT hOldFont = (HFONT) ::SelectObject (m_hDcPaint, hFont); + ::GetTextMetrics (m_hDcPaint, &m_ResInfo.m_DefaultFontInfo.tm); + ::SelectObject (m_hDcPaint, hOldFont); + } + } + } + + DWORD CPaintManagerUI::GetCustomFontCount (bool bShared) const { + if (bShared) + return m_SharedResInfo.m_CustomFonts.GetSize (); + else + return m_ResInfo.m_CustomFonts.GetSize (); + } + + HFONT CPaintManagerUI::AddFont (int id, string_view_t pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic, bool bShared) { + LOGFONT lf = { 0 }; + ::GetObject (::GetStockObject (DEFAULT_GUI_FONT), sizeof (LOGFONT), &lf); + if (pStrFontName.length () > 0) { + _tcsncpy (lf.lfFaceName, pStrFontName.data (), lengthof (lf.lfFaceName)); + } + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfHeight = -GetDPIObj ()->Scale (nSize); + if (bBold) lf.lfWeight = FW_BOLD; + if (bUnderline) lf.lfUnderline = TRUE; + if (bItalic) lf.lfItalic = TRUE; + HFONT hFont = ::CreateFontIndirect (&lf); + if (!hFont) return nullptr; + + TFontInfo* pFontInfo = new TFontInfo; + if (!pFontInfo) return false; + ::ZeroMemory (pFontInfo, sizeof (TFontInfo)); + pFontInfo->hFont = hFont; + pFontInfo->sFontName = lf.lfFaceName; + pFontInfo->iSize = nSize; + pFontInfo->bBold = bBold; + pFontInfo->bUnderline = bUnderline; + pFontInfo->bItalic = bItalic; + if (m_hDcPaint) { + HFONT hOldFont = (HFONT) ::SelectObject (m_hDcPaint, hFont); + ::GetTextMetrics (m_hDcPaint, &pFontInfo->tm); + ::SelectObject (m_hDcPaint, hOldFont); + } + TCHAR idBuffer[16]; + ::ZeroMemory (idBuffer, sizeof (idBuffer)); + _itot (id, idBuffer, 10); + if (bShared || m_bForceUseSharedRes) { + TFontInfo* pOldFontInfo = static_cast(m_SharedResInfo.m_CustomFonts.Find (idBuffer)); + if (pOldFontInfo) { + ::DeleteObject (pOldFontInfo->hFont); + delete pOldFontInfo; + m_SharedResInfo.m_CustomFonts.Remove (idBuffer); + } + + if (!m_SharedResInfo.m_CustomFonts.Insert (idBuffer, pFontInfo)) { + ::DeleteObject (hFont); + delete pFontInfo; + return nullptr; + } + } else { + TFontInfo* pOldFontInfo = static_cast(m_ResInfo.m_CustomFonts.Find (idBuffer)); + if (pOldFontInfo) { + ::DeleteObject (pOldFontInfo->hFont); + delete pOldFontInfo; + m_ResInfo.m_CustomFonts.Remove (idBuffer); + } + + if (!m_ResInfo.m_CustomFonts.Insert (idBuffer, pFontInfo)) { + ::DeleteObject (hFont); + delete pFontInfo; + return nullptr; + } + } + + return hFont; + } + void CPaintManagerUI::AddFontArray (string_view_t pstrPath) { + LPBYTE pData = nullptr; + DWORD dwSize = 0; + do { + CDuiString sFile = CPaintManagerUI::GetResourcePath (); + if (CPaintManagerUI::GetResourceZip ().empty ()) { + sFile += pstrPath; + HANDLE hFile = ::CreateFile (sFile.c_str (), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) break; + dwSize = ::GetFileSize (hFile, nullptr); + if (dwSize == 0) break; + + DWORD dwRead = 0; + pData = new BYTE[dwSize]; + ::ReadFile (hFile, pData, dwSize, &dwRead, nullptr); + ::CloseHandle (hFile); + + if (dwRead != dwSize) { + delete[] pData; + pData = nullptr; + break; + } + } else { + sFile += CPaintManagerUI::GetResourceZip (); + HZIP hz = nullptr; + if (CPaintManagerUI::IsCachedResourceZip ()) hz = (HZIP) CPaintManagerUI::GetResourceZipHandle (); + else { + CDuiString sFilePwd = CPaintManagerUI::GetResourceZipPwd (); + std::string pwd = FawTools::get_gb18030 (sFilePwd); + hz = OpenZip (sFile.c_str (), pwd.c_str ()); + } + if (!hz) break; + ZIPENTRY ze; + int i = 0; + CDuiString key = pstrPath; + key.Replace (_T ("\\"), _T ("/")); + if (FindZipItem (hz, key.c_str (), true, &i, &ze) != 0) break; + dwSize = ze.unc_size; + if (dwSize == 0) break; + pData = new BYTE[dwSize]; + int res = UnzipItem (hz, i, pData, dwSize); + if (res != 0x00000000 && res != 0x00000600) { + delete[] pData; + pData = nullptr; + if (!CPaintManagerUI::IsCachedResourceZip ()) CloseZip (hz); + break; + } + if (!CPaintManagerUI::IsCachedResourceZip ()) CloseZip (hz); + } + + } while (0); + + while (!pData) { + //ͼƬ, ֱȥȡbitmap.m_lpstrָ· + HANDLE hFile = ::CreateFile (pstrPath.data (), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) break; + dwSize = ::GetFileSize (hFile, nullptr); + if (dwSize == 0) break; + + DWORD dwRead = 0; + pData = new BYTE[dwSize]; + ::ReadFile (hFile, pData, dwSize, &dwRead, nullptr); + ::CloseHandle (hFile); + + if (dwRead != dwSize) { + delete[] pData; + pData = nullptr; + } + break; + } + DWORD nFonts; + HANDLE hFont = ::AddFontMemResourceEx (pData, dwSize, nullptr, &nFonts); + delete[] pData; + pData = nullptr; + m_aFonts.Add (hFont); + } + HFONT CPaintManagerUI::GetFont (int id) { + if (id < 0) return GetDefaultFontInfo ()->hFont; + + TCHAR idBuffer[16]; + ::ZeroMemory (idBuffer, sizeof (idBuffer)); + _itot (id, idBuffer, 10); + TFontInfo* pFontInfo = static_cast(m_ResInfo.m_CustomFonts.Find (idBuffer)); + if (!pFontInfo) pFontInfo = static_cast(m_SharedResInfo.m_CustomFonts.Find (idBuffer)); + if (!pFontInfo) return GetDefaultFontInfo ()->hFont; + return pFontInfo->hFont; + } + + HFONT CPaintManagerUI::GetFont (string_view_t pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic) { + TFontInfo* pFontInfo = nullptr; + for (int i = 0; i < m_ResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_ResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_ResInfo.m_CustomFonts.Find (key)); + if (pFontInfo && pFontInfo->sFontName == pStrFontName && pFontInfo->iSize == nSize && + pFontInfo->bBold == bBold && pFontInfo->bUnderline == bUnderline && pFontInfo->bItalic == bItalic) + return pFontInfo->hFont; + } + } + for (int i = 0; i < m_SharedResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_SharedResInfo.m_CustomFonts.Find (key)); + if (pFontInfo && pFontInfo->sFontName == pStrFontName && pFontInfo->iSize == nSize && + pFontInfo->bBold == bBold && pFontInfo->bUnderline == bUnderline && pFontInfo->bItalic == bItalic) + return pFontInfo->hFont; + } + } + + return nullptr; + } + + int CPaintManagerUI::GetFontIndex (HFONT hFont, bool bShared) { + TFontInfo* pFontInfo = nullptr; + if (bShared) { + for (int i = 0; i < m_SharedResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_SharedResInfo.m_CustomFonts.Find (key)); + if (pFontInfo && pFontInfo->hFont == hFont) return _ttoi (key.data ()); + } + } + } else { + for (int i = 0; i < m_ResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_ResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_ResInfo.m_CustomFonts.Find (key)); + if (pFontInfo && pFontInfo->hFont == hFont) return _ttoi (key.data ()); + } + } + } + + return -1; + } + + int CPaintManagerUI::GetFontIndex (string_view_t pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic, bool bShared) { + TFontInfo* pFontInfo = nullptr; + if (bShared) { + for (int i = 0; i < m_SharedResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_SharedResInfo.m_CustomFonts.Find (key)); + if (pFontInfo && pFontInfo->sFontName == pStrFontName && pFontInfo->iSize == nSize && + pFontInfo->bBold == bBold && pFontInfo->bUnderline == bUnderline && pFontInfo->bItalic == bItalic) + return _ttoi (key.data ()); + } + } + } else { + for (int i = 0; i < m_ResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_ResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_ResInfo.m_CustomFonts.Find (key)); + if (pFontInfo && pFontInfo->sFontName == pStrFontName && pFontInfo->iSize == nSize && + pFontInfo->bBold == bBold && pFontInfo->bUnderline == bUnderline && pFontInfo->bItalic == bItalic) + return _ttoi (key.data ()); + } + } + } + + return -1; + } + + void CPaintManagerUI::RemoveFont (HFONT hFont, bool bShared) { + TFontInfo* pFontInfo = nullptr; + if (bShared) { + for (int i = 0; i < m_SharedResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_SharedResInfo.m_CustomFonts.Find (key)); + if (pFontInfo && pFontInfo->hFont == hFont) { + ::DeleteObject (pFontInfo->hFont); + delete pFontInfo; + m_SharedResInfo.m_CustomFonts.Remove (key.data ()); + return; + } + } + } + } else { + for (int i = 0; i < m_ResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_ResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_ResInfo.m_CustomFonts.Find (key)); + if (pFontInfo && pFontInfo->hFont == hFont) { + ::DeleteObject (pFontInfo->hFont); + delete pFontInfo; + m_ResInfo.m_CustomFonts.Remove (key); + return; + } + } + } + } + } + + void CPaintManagerUI::RemoveFont (int id, bool bShared) { + TCHAR idBuffer[16]; + ::ZeroMemory (idBuffer, sizeof (idBuffer)); + _itot (id, idBuffer, 10); + + TFontInfo* pFontInfo = nullptr; + if (bShared) { + pFontInfo = static_cast(m_SharedResInfo.m_CustomFonts.Find (idBuffer)); + if (pFontInfo) { + ::DeleteObject (pFontInfo->hFont); + delete pFontInfo; + m_SharedResInfo.m_CustomFonts.Remove (idBuffer); + } + } else { + pFontInfo = static_cast(m_ResInfo.m_CustomFonts.Find (idBuffer)); + if (pFontInfo) { + ::DeleteObject (pFontInfo->hFont); + delete pFontInfo; + m_ResInfo.m_CustomFonts.Remove (idBuffer); + } + } + } + + void CPaintManagerUI::RemoveAllFonts (bool bShared) { + TFontInfo* pFontInfo; + if (bShared) { + for (int i = 0; i < m_SharedResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_SharedResInfo.m_CustomFonts.Find (key, false)); + if (pFontInfo) { + ::DeleteObject (pFontInfo->hFont); + delete pFontInfo; + } + } + } + m_SharedResInfo.m_CustomFonts.RemoveAll (); + } else { + for (int i = 0; i < m_ResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_ResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_ResInfo.m_CustomFonts.Find (key, false)); + if (pFontInfo) { + ::DeleteObject (pFontInfo->hFont); + delete pFontInfo; + } + } + } + m_ResInfo.m_CustomFonts.RemoveAll (); + } + } + + TFontInfo* CPaintManagerUI::GetFontInfo (int id) { + if (id < 0) return GetDefaultFontInfo (); + + TCHAR idBuffer[16]; + ::ZeroMemory (idBuffer, sizeof (idBuffer)); + _itot (id, idBuffer, 10); + TFontInfo* pFontInfo = static_cast(m_ResInfo.m_CustomFonts.Find (idBuffer)); + if (!pFontInfo) pFontInfo = static_cast(m_SharedResInfo.m_CustomFonts.Find (idBuffer)); + if (!pFontInfo) pFontInfo = GetDefaultFontInfo (); + if (pFontInfo->tm.tmHeight == 0) { + HFONT hOldFont = (HFONT) ::SelectObject (m_hDcPaint, pFontInfo->hFont); + ::GetTextMetrics (m_hDcPaint, &pFontInfo->tm); + ::SelectObject (m_hDcPaint, hOldFont); + } + return pFontInfo; + } + + TFontInfo* CPaintManagerUI::GetFontInfo (HFONT hFont) { + TFontInfo* pFontInfo = nullptr; + for (int i = 0; i < m_ResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_ResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_ResInfo.m_CustomFonts.Find (key)); + if (pFontInfo && pFontInfo->hFont == hFont) break; + } + } + if (!pFontInfo) { + for (int i = 0; i < m_SharedResInfo.m_CustomFonts.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_CustomFonts.GetAt (i)->Key; + if (!key.empty ()) { + pFontInfo = static_cast(m_SharedResInfo.m_CustomFonts.Find (key)); + if (pFontInfo && pFontInfo->hFont == hFont) break; + } + } + } + if (!pFontInfo) pFontInfo = GetDefaultFontInfo (); + if (pFontInfo->tm.tmHeight == 0) { + HFONT hOldFont = (HFONT) ::SelectObject (m_hDcPaint, pFontInfo->hFont); + ::GetTextMetrics (m_hDcPaint, &pFontInfo->tm); + ::SelectObject (m_hDcPaint, hOldFont); + } + return pFontInfo; + } + + const TImageInfo* CPaintManagerUI::GetImage (string_view_t bitmap) { + TImageInfo* data = static_cast(m_ResInfo.m_ImageHash.Find (bitmap)); + if (!data) data = static_cast(m_SharedResInfo.m_ImageHash.Find (bitmap)); + return data; + } + + const TImageInfo* CPaintManagerUI::GetImageEx (string_view_t bitmap, string_view_t type, DWORD mask, bool bUseHSL, HINSTANCE instance) { + const TImageInfo* data = GetImage (bitmap); + if (!data) { + if (AddImage (bitmap, type, mask, bUseHSL, false, instance)) { + if (m_bForceUseSharedRes) data = static_cast(m_SharedResInfo.m_ImageHash.Find (bitmap)); + else data = static_cast(m_ResInfo.m_ImageHash.Find (bitmap)); + } + } + + return data; + } + + const TImageInfo* CPaintManagerUI::AddImage (string_view_t bitmap, string_view_t type, DWORD mask, bool bUseHSL, bool bShared, HINSTANCE instance) { + if (bitmap.empty ()) return nullptr; + + TImageInfo* data = nullptr; + if (type.length () > 0) { + if (isdigit (bitmap[0])) { + int iIndex = _ttoi (bitmap.data ()); + data = CRenderEngine::LoadImage (iIndex, type.data (), mask, instance); + } + } else { + data = CRenderEngine::LoadImage (bitmap, _T (""), mask, instance); + } + + if (!data) { + return nullptr; + } + data->bUseHSL = bUseHSL; + if (!type.empty ()) data->sResType = type; + data->dwMask = mask; + if (data->bUseHSL) { + data->pSrcBits = new BYTE[data->nX * data->nY * 4]; + ::CopyMemory (data->pSrcBits, data->pBits, data->nX * data->nY * 4); + } else data->pSrcBits = nullptr; + if (m_bUseHSL) CRenderEngine::AdjustImage (true, data, m_H, m_S, m_L); + if (data) { + if (bShared || m_bForceUseSharedRes) { + TImageInfo* pOldImageInfo = static_cast(m_SharedResInfo.m_ImageHash.Find (bitmap)); + if (pOldImageInfo) { + CRenderEngine::FreeImage (pOldImageInfo); + m_SharedResInfo.m_ImageHash.Remove (bitmap); + } + + if (!m_SharedResInfo.m_ImageHash.Insert (bitmap, data)) { + CRenderEngine::FreeImage (data); + data = nullptr; + } + } else { + TImageInfo* pOldImageInfo = static_cast(m_ResInfo.m_ImageHash.Find (bitmap)); + if (pOldImageInfo) { + CRenderEngine::FreeImage (pOldImageInfo); + m_ResInfo.m_ImageHash.Remove (bitmap); + } + + if (!m_ResInfo.m_ImageHash.Insert (bitmap, data)) { + CRenderEngine::FreeImage (data); + data = nullptr; + } + } + } + + return data; + } + + const TImageInfo* CPaintManagerUI::AddImage (string_view_t bitmap, HBITMAP hBitmap, int iWidth, int iHeight, bool bAlpha, bool bShared) { + // ޷ȷⲿHBITMAPʽʹhsl + if (bitmap.empty ()) return nullptr; + if (!hBitmap || iWidth <= 0 || iHeight <= 0) return nullptr; + + TImageInfo* data = new TImageInfo; + data->pBits = nullptr; + data->pSrcBits = nullptr; + data->hBitmap = hBitmap; + data->pBits = nullptr; + data->nX = iWidth; + data->nY = iHeight; + data->bAlpha = bAlpha; + data->bUseHSL = false; + data->pSrcBits = nullptr; + data->dwMask = 0; + + if (bShared || m_bForceUseSharedRes) { + if (!m_SharedResInfo.m_ImageHash.Insert (bitmap, data)) { + CRenderEngine::FreeImage (data); + data = nullptr; + } + } else { + if (!m_ResInfo.m_ImageHash.Insert (bitmap, data)) { + CRenderEngine::FreeImage (data); + data = nullptr; + } + } + + return data; + } + + const TImageInfo* CPaintManagerUI::AddImage (string_view_t bitmap, HBITMAP *phBitmap, int iWidth, int iHeight, bool bAlpha, bool bShared) { + // ޷ȷⲿHBITMAPʽʹhsl + if (bitmap.empty ()) return nullptr; + if (!phBitmap || !*phBitmap || iWidth <= 0 || iHeight <= 0) return nullptr; + + TImageInfo* data = new TImageInfo; + data->pBits = nullptr; + data->pSrcBits = nullptr; + data->phBitmap = phBitmap; + data->pBits = nullptr; + data->nX = iWidth; + data->nY = iHeight; + data->bAlpha = bAlpha; + data->bUseHSL = false; + data->pSrcBits = nullptr; + data->dwMask = 0; + + if (bShared || m_bForceUseSharedRes) { + if (!m_SharedResInfo.m_ImageHash.Insert (bitmap, data)) { + CRenderEngine::FreeImage (data); + data = nullptr; + } + } else { + if (!m_ResInfo.m_ImageHash.Insert (bitmap, data)) { + CRenderEngine::FreeImage (data); + data = nullptr; + } + } + + return data; + } + + void CPaintManagerUI::RemoveImage (string_view_t bitmap, bool bShared) { + TImageInfo* data = nullptr; + if (bShared) { + data = static_cast(m_SharedResInfo.m_ImageHash.Find (bitmap)); + if (data) { + CRenderEngine::FreeImage (data); + m_SharedResInfo.m_ImageHash.Remove (bitmap); + } + } else { + data = static_cast(m_ResInfo.m_ImageHash.Find (bitmap)); + if (data) { + CRenderEngine::FreeImage (data); + m_ResInfo.m_ImageHash.Remove (bitmap); + } + } + } + + void CPaintManagerUI::RemoveAllImages (bool bShared) { + if (bShared) { + TImageInfo* data; + for (int i = 0; i < m_SharedResInfo.m_ImageHash.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_ImageHash.GetAt (i)->Key; + if (!key.empty ()) { + data = static_cast(m_SharedResInfo.m_ImageHash.Find (key, false)); + if (data) { + CRenderEngine::FreeImage (data); + } + } + } + m_SharedResInfo.m_ImageHash.RemoveAll (); + } else { + TImageInfo* data; + for (int i = 0; i < m_ResInfo.m_ImageHash.GetSize (); i++) { + string_view_t key = m_ResInfo.m_ImageHash.GetAt (i)->Key; + if (!key.empty ()) { + data = static_cast(m_ResInfo.m_ImageHash.Find (key, false)); + if (data) { + CRenderEngine::FreeImage (data); + } + } + } + m_ResInfo.m_ImageHash.RemoveAll (); + } + } + + void CPaintManagerUI::AdjustSharedImagesHSL () { + TImageInfo* data; + for (int i = 0; i < m_SharedResInfo.m_ImageHash.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_ImageHash.GetAt (i)->Key; + if (!key.empty ()) { + data = static_cast(m_SharedResInfo.m_ImageHash.Find (key)); + if (data && data->bUseHSL) { + CRenderEngine::AdjustImage (m_bUseHSL, data, m_H, m_S, m_L); + } + } + } + } + + void CPaintManagerUI::AdjustImagesHSL () { + TImageInfo* data; + for (int i = 0; i < m_ResInfo.m_ImageHash.GetSize (); i++) { + string_view_t key = m_ResInfo.m_ImageHash.GetAt (i)->Key; + if (!key.empty ()) { + data = static_cast(m_ResInfo.m_ImageHash.Find (key)); + if (data && data->bUseHSL) { + CRenderEngine::AdjustImage (m_bUseHSL, data, m_H, m_S, m_L); + } + } + } + Invalidate (); + } + + void CPaintManagerUI::PostAsyncNotify () { + if (!m_bAsyncNotifyPosted) { + ::PostMessage (m_hWndPaint, WM_APP + 1, 0, 0L); + m_bAsyncNotifyPosted = true; + } + } + void CPaintManagerUI::ReloadSharedImages () { + TImageInfo *data = nullptr; + TImageInfo *pNewData = nullptr; + for (int i = 0; i < m_SharedResInfo.m_ImageHash.GetSize (); i++) { + string_view_t bitmap = m_SharedResInfo.m_ImageHash.GetAt (i)->Key; + if (!bitmap.empty ()) { + data = static_cast(m_SharedResInfo.m_ImageHash.Find (bitmap)); + if (data) { + if (!data->sResType.empty ()) { + if (isdigit (bitmap[0])) { + int iIndex = _ttoi (bitmap.data ()); + pNewData = CRenderEngine::LoadImage (iIndex, data->sResType.c_str (), data->dwMask); + } + } else { + pNewData = CRenderEngine::LoadImage (bitmap, nullptr, data->dwMask); + } + if (!pNewData) continue; + + CRenderEngine::FreeImage (data, false); + data->hBitmap = pNewData->hBitmap; + data->phBitmap = pNewData->phBitmap; + data->pBits = pNewData->pBits; + data->nX = pNewData->nX; + data->nY = pNewData->nY; + data->bAlpha = pNewData->bAlpha; + data->pSrcBits = nullptr; + if (data->bUseHSL) { + data->pSrcBits = new BYTE[data->nX * data->nY * 4]; + ::CopyMemory (data->pSrcBits, data->pBits, data->nX * data->nY * 4); + } else data->pSrcBits = nullptr; + if (m_bUseHSL) CRenderEngine::AdjustImage (true, data, m_H, m_S, m_L); + + delete pNewData; + } + } + } + } + + void CPaintManagerUI::ReloadImages () { + RemoveAllDrawInfos (); + + TImageInfo *data = nullptr; + TImageInfo *pNewData = nullptr; + for (int i = 0; i < m_ResInfo.m_ImageHash.GetSize (); i++) { + string_view_t bitmap = m_ResInfo.m_ImageHash.GetAt (i)->Key; + if (!bitmap.empty ()) { + data = static_cast(m_ResInfo.m_ImageHash.Find (bitmap)); + if (data) { + if (!data->sResType.empty ()) { + if (isdigit (bitmap[0])) { + int iIndex = _ttoi (bitmap.data ()); + pNewData = CRenderEngine::LoadImage (iIndex, data->sResType.data (), data->dwMask); + } + } else { + pNewData = CRenderEngine::LoadImage (bitmap, nullptr, data->dwMask); + } + + CRenderEngine::FreeImage (data, false); + if (!pNewData) { + m_ResInfo.m_ImageHash.Remove (bitmap); + continue; + } + data->hBitmap = pNewData->hBitmap; + data->phBitmap = pNewData->phBitmap; + data->pBits = pNewData->pBits; + data->nX = pNewData->nX; + data->nY = pNewData->nY; + data->bAlpha = pNewData->bAlpha; + data->pSrcBits = nullptr; + if (data->bUseHSL) { + data->pSrcBits = new BYTE[data->nX * data->nY * 4]; + ::CopyMemory (data->pSrcBits, data->pBits, data->nX * data->nY * 4); + } else data->pSrcBits = nullptr; + if (m_bUseHSL) CRenderEngine::AdjustImage (true, data, m_H, m_S, m_L); + + delete pNewData; + } + } + } + + if (m_pRoot) m_pRoot->Invalidate (); + } + + const TDrawInfo* CPaintManagerUI::GetDrawInfo (string_view_t pStrImage, string_view_t pStrModify) { + CDuiString sStrImage = pStrImage; + CDuiString sStrModify = pStrModify; + CDuiString sKey = sStrImage + sStrModify; + TDrawInfo* pDrawInfo = static_cast(m_ResInfo.m_DrawInfoHash.Find (sKey)); + if (!pDrawInfo && !sKey.empty ()) { + pDrawInfo = new TDrawInfo (); + pDrawInfo->Parse (pStrImage, pStrModify, this); + m_ResInfo.m_DrawInfoHash.Insert (sKey, pDrawInfo); + } + return pDrawInfo; + } + + void CPaintManagerUI::RemoveDrawInfo (string_view_t pStrImage, string_view_t pStrModify) { + CDuiString sStrImage = pStrImage; + CDuiString sStrModify = pStrModify; + CDuiString sKey = sStrImage + sStrModify; + TDrawInfo* pDrawInfo = static_cast(m_ResInfo.m_DrawInfoHash.Find (sKey)); + if (pDrawInfo) { + m_ResInfo.m_DrawInfoHash.Remove (sKey); + delete pDrawInfo; + pDrawInfo = nullptr; + } + } + + void CPaintManagerUI::RemoveAllDrawInfos () { + TDrawInfo* pDrawInfo = nullptr; + for (int i = 0; i < m_ResInfo.m_DrawInfoHash.GetSize (); i++) { + string_view_t key = m_ResInfo.m_DrawInfoHash.GetAt (i)->Key; + if (!key.empty ()) { + pDrawInfo = static_cast(m_ResInfo.m_DrawInfoHash.Find (key, false)); + if (pDrawInfo) { + delete pDrawInfo; + pDrawInfo = nullptr; + } + } + } + m_ResInfo.m_DrawInfoHash.RemoveAll (); + } + + void CPaintManagerUI::AddDefaultAttributeList (string_view_t pStrControlName, string_view_t pStrControlAttrList, bool bShared) { + if (bShared || m_bForceUseSharedRes) { + CDuiString* pDefaultAttr = new CDuiString (pStrControlAttrList); + if (pDefaultAttr) { + CDuiString* pOldDefaultAttr = static_cast(m_SharedResInfo.m_AttrHash.Set (pStrControlName, (LPVOID) pDefaultAttr)); + if (pOldDefaultAttr) delete pOldDefaultAttr; + } + } else { + CDuiString* pDefaultAttr = new CDuiString (pStrControlAttrList); + if (pDefaultAttr) { + CDuiString* pOldDefaultAttr = static_cast(m_ResInfo.m_AttrHash.Set (pStrControlName, (LPVOID) pDefaultAttr)); + if (pOldDefaultAttr) delete pOldDefaultAttr; + } + } + } + + string_view_t CPaintManagerUI::GetDefaultAttributeList (string_view_t pStrControlName) const { + CDuiString* pDefaultAttr = static_cast(m_ResInfo.m_AttrHash.Find (pStrControlName)); + if (!pDefaultAttr) pDefaultAttr = static_cast(m_SharedResInfo.m_AttrHash.Find (pStrControlName)); + if (pDefaultAttr) return *pDefaultAttr; + return _T (""); + } + + bool CPaintManagerUI::RemoveDefaultAttributeList (string_view_t pStrControlName, bool bShared) { + if (bShared) { + CDuiString* pDefaultAttr = static_cast(m_SharedResInfo.m_AttrHash.Find (pStrControlName)); + if (!pDefaultAttr) return false; + + delete pDefaultAttr; + return m_SharedResInfo.m_AttrHash.Remove (pStrControlName); + } else { + CDuiString* pDefaultAttr = static_cast(m_ResInfo.m_AttrHash.Find (pStrControlName)); + if (!pDefaultAttr) return false; + + delete pDefaultAttr; + return m_ResInfo.m_AttrHash.Remove (pStrControlName); + } + } + + void CPaintManagerUI::RemoveAllDefaultAttributeList (bool bShared) { + if (bShared) { + CDuiString* pDefaultAttr; + for (int i = 0; i < m_SharedResInfo.m_AttrHash.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_AttrHash.GetAt (i)->Key; + if (!key.empty ()) { + pDefaultAttr = static_cast(m_SharedResInfo.m_AttrHash.Find (key)); + if (pDefaultAttr) delete pDefaultAttr; + } + } + m_SharedResInfo.m_AttrHash.RemoveAll (); + } else { + CDuiString* pDefaultAttr; + for (int i = 0; i < m_ResInfo.m_AttrHash.GetSize (); i++) { + string_view_t key = m_ResInfo.m_AttrHash.GetAt (i)->Key; + if (!key.empty ()) { + pDefaultAttr = static_cast(m_ResInfo.m_AttrHash.Find (key)); + if (pDefaultAttr) delete pDefaultAttr; + } + } + m_ResInfo.m_AttrHash.RemoveAll (); + } + } + + void CPaintManagerUI::AddWindowCustomAttribute (string_view_t pstrName, string_view_t pstrAttr) { + if (pstrName.empty () || pstrAttr.empty ()) return; + CDuiString* pCostomAttr = new CDuiString (pstrAttr); + if (!m_mWindowCustomAttrHash.Find (pstrName)) + m_mWindowCustomAttrHash.Set (pstrName, (LPVOID) pCostomAttr); + else + delete pCostomAttr; + } + + string_view_t CPaintManagerUI::GetWindowCustomAttribute (string_view_t pstrName) const { + if (pstrName.empty ()) return _T (""); + CDuiString* pCostomAttr = static_cast(m_mWindowCustomAttrHash.Find (pstrName)); + if (pCostomAttr) return *pCostomAttr; + return _T (""); + } + + bool CPaintManagerUI::RemoveWindowCustomAttribute (string_view_t pstrName) { + if (pstrName.empty ()) return false; + CDuiString* pCostomAttr = static_cast(m_mWindowCustomAttrHash.Find (pstrName)); + if (!pCostomAttr) return false; + + delete pCostomAttr; + return m_mWindowCustomAttrHash.Remove (pstrName); + } + + void CPaintManagerUI::RemoveAllWindowCustomAttribute () { + CDuiString* pCostomAttr; + for (int i = 0; i < m_mWindowCustomAttrHash.GetSize (); i++) { + string_view_t key = m_mWindowCustomAttrHash.GetAt (i)->Key; + if (!key.empty ()) { + pCostomAttr = static_cast(m_mWindowCustomAttrHash.Find (key)); + delete pCostomAttr; + } + } + m_mWindowCustomAttrHash.Resize (); + } + + CControlUI* CPaintManagerUI::GetRoot () const { + ASSERT (m_pRoot); + return m_pRoot; + } + + CControlUI* CPaintManagerUI::FindControl (POINT pt) const { + ASSERT (m_pRoot); + return m_pRoot->FindControl (__FindControlFromPoint, &pt, UIFIND_VISIBLE | UIFIND_HITTEST | UIFIND_TOP_FIRST); + } + + CControlUI* CPaintManagerUI::FindControl (string_view_t pstrName) const { + ASSERT (m_pRoot); + return static_cast(m_mNameHash.Find (pstrName)); + } + + CControlUI* CPaintManagerUI::FindSubControlByPoint (CControlUI* pParent, POINT pt) const { + if (!pParent) pParent = GetRoot (); + ASSERT (pParent); + return pParent->FindControl (__FindControlFromPoint, &pt, UIFIND_VISIBLE | UIFIND_HITTEST | UIFIND_TOP_FIRST); + } + + CControlUI* CPaintManagerUI::FindSubControlByName (CControlUI* pParent, string_view_t pstrName) const { + if (!pParent) pParent = GetRoot (); + ASSERT (pParent); + return pParent->FindControl (__FindControlFromName, (LPVOID) pstrName.data (), UIFIND_ALL); + } + + CControlUI* CPaintManagerUI::FindSubControlByClass (CControlUI* pParent, string_view_t pstrClass, int iIndex) { + if (!pParent) pParent = GetRoot (); + ASSERT (pParent); + m_aFoundControls.Resize (iIndex + 1); + return pParent->FindControl (__FindControlFromClass, (LPVOID) pstrClass.data (), UIFIND_ALL); + } + + CStdPtrArray* CPaintManagerUI::FindSubControlsByClass (CControlUI* pParent, string_view_t pstrClass) { + if (!pParent) pParent = GetRoot (); + ASSERT (pParent); + m_aFoundControls.Empty (); + pParent->FindControl (__FindControlsFromClass, (LPVOID) pstrClass.data (), UIFIND_ALL); + return &m_aFoundControls; + } + + CStdPtrArray* CPaintManagerUI::GetFoundControls () { + return &m_aFoundControls; + } + + CControlUI* CALLBACK CPaintManagerUI::__FindControlFromNameHash (CControlUI* pThis, LPVOID pData) { + CPaintManagerUI* pManager = static_cast(pData); + const string_view_t sName = pThis->GetName (); + if (sName.empty ()) return nullptr; + // Add this control to the hash list + pManager->m_mNameHash.Set (sName, pThis); + return nullptr; // Attempt to add all controls + } + + CControlUI* CALLBACK CPaintManagerUI::__FindControlFromCount (CControlUI* /*pThis*/, LPVOID pData) { + int* pnCount = static_cast(pData); + (*pnCount)++; + return nullptr; // Count all controls + } + + CControlUI* CALLBACK CPaintManagerUI::__FindControlFromPoint (CControlUI* pThis, LPVOID pData) { + LPPOINT pPoint = static_cast(pData); + return ::PtInRect (&pThis->GetPos (), *pPoint) ? pThis : nullptr; + } + + CControlUI* CALLBACK CPaintManagerUI::__FindControlFromTab (CControlUI* pThis, LPVOID pData) { + FINDTABINFO* pInfo = static_cast(pData); + if (pInfo->pFocus == pThis) { + if (pInfo->bForward) pInfo->bNextIsIt = true; + return pInfo->bForward ? nullptr : pInfo->pLast; + } + if ((pThis->GetControlFlags () & UIFLAG_TABSTOP) == 0) return nullptr; + pInfo->pLast = pThis; + if (pInfo->bNextIsIt) return pThis; + if (!pInfo->pFocus) return pThis; + return nullptr; // Examine all controls + } + + CControlUI* CALLBACK CPaintManagerUI::__FindControlFromShortcut (CControlUI* pThis, LPVOID pData) { + if (!pThis->IsVisible ()) return nullptr; + FINDSHORTCUT* pFS = static_cast(pData); + if (pFS->ch == toupper (pThis->GetShortcut ())) pFS->bPickNext = true; + if (pThis->GetClass ().find (_T ("LabelUI")) != string_t::npos) return nullptr; // Labels never get focus! + return pFS->bPickNext ? pThis : nullptr; + } + + CControlUI* CALLBACK CPaintManagerUI::__FindControlFromName (CControlUI* pThis, LPVOID pData) { + string_view_t pstrName = static_cast(pData); + const string_view_t sName = pThis->GetName (); + if (sName.empty ()) return nullptr; + return (sName == pstrName ? pThis : nullptr); + } + + CControlUI* CALLBACK CPaintManagerUI::__FindControlFromClass (CControlUI* pThis, LPVOID pData) { + string_view_t pstrType = static_cast(pData); + string_view_t pType = pThis->GetClass (); + CStdPtrArray* pFoundControls = pThis->GetManager ()->GetFoundControls (); + if (pstrType == _T ("*") || pstrType == pType) { + int iIndex = -1; + while (pFoundControls->GetAt (++iIndex)); + if (iIndex < pFoundControls->GetSize ()) pFoundControls->SetAt (iIndex, pThis); + } + if (pFoundControls->GetAt (pFoundControls->GetSize () - 1)) return pThis; + return nullptr; + } + + CControlUI* CALLBACK CPaintManagerUI::__FindControlsFromClass (CControlUI* pThis, LPVOID pData) { + string_view_t pstrType = static_cast(pData); + string_view_t pType = pThis->GetClass (); + if (pstrType == _T ("*") || pstrType == pType) + pThis->GetManager ()->GetFoundControls ()->Add ((LPVOID) pThis); + return nullptr; + } + + CControlUI* CALLBACK CPaintManagerUI::__FindControlsFromUpdate (CControlUI* pThis, LPVOID pData) { + if (pThis->IsUpdateNeeded ()) { + pThis->GetManager ()->GetFoundControls ()->Add ((LPVOID) pThis); + return pThis; + } + return nullptr; + } + + bool CPaintManagerUI::TranslateAccelerator (LPMSG pMsg) { + for (int i = 0; i < m_aTranslateAccelerator.GetSize (); i++) { + LRESULT lResult = static_cast(m_aTranslateAccelerator[i])->TranslateAccelerator (pMsg); + if (lResult == S_OK) return true; + } + return false; + } + + bool CPaintManagerUI::TranslateMessage (const LPMSG pMsg) { + // Pretranslate Message takes care of system-wide messages, such as + // tabbing and shortcut key-combos. We'll look for all messages for + // each window and any child control attached. + UINT uStyle = GetWindowStyle (pMsg->hwnd); + UINT uChildRes = uStyle & WS_CHILD; + LRESULT lRes = 0; + if (uChildRes != 0) { + HWND hWndParent = ::GetParent (pMsg->hwnd); + + for (int i = 0; i < m_aPreMessages.GetSize (); i++) { + CPaintManagerUI* pT = static_cast(m_aPreMessages[i]); + HWND hTempParent = hWndParent; + while (hTempParent) { + if (pMsg->hwnd == pT->GetPaintWindow () || hTempParent == pT->GetPaintWindow ()) { + if (pT->TranslateAccelerator (pMsg)) + return true; + + pT->PreMessageHandler (pMsg->message, pMsg->wParam, pMsg->lParam, lRes); + } + hTempParent = GetParent (hTempParent); + } + } + } else { + for (int i = 0; i < m_aPreMessages.GetSize (); i++) { + CPaintManagerUI* pT = static_cast(m_aPreMessages[i]); + if (pMsg->hwnd == pT->GetPaintWindow ()) { + if (pT->TranslateAccelerator (pMsg)) + return true; + + if (pT->PreMessageHandler (pMsg->message, pMsg->wParam, pMsg->lParam, lRes)) + return true; + + return false; + } + } + } + return false; + } + + bool CPaintManagerUI::AddTranslateAccelerator (ITranslateAccelerator *pTranslateAccelerator) { + ASSERT (m_aTranslateAccelerator.Find (pTranslateAccelerator) < 0); + return m_aTranslateAccelerator.Add (pTranslateAccelerator); + } + + bool CPaintManagerUI::RemoveTranslateAccelerator (ITranslateAccelerator *pTranslateAccelerator) { + for (int i = 0; i < m_aTranslateAccelerator.GetSize (); i++) { + if (static_cast(m_aTranslateAccelerator[i]) == pTranslateAccelerator) { + return m_aTranslateAccelerator.Remove (i); + } + } + return false; + } + + void CPaintManagerUI::UsedVirtualWnd (bool bUsed) { + m_bUsedVirtualWnd = bUsed; + } + + // ʽ + void CPaintManagerUI::AddStyle (string_view_t pName, string_view_t pDeclarationList, bool bShared) { + CDuiString* pStyle = new CDuiString (pDeclarationList); + + if (bShared || m_bForceUseSharedRes) { + if (!m_SharedResInfo.m_StyleHash.Insert (pName, pStyle)) { + delete pStyle; + } + } else { + if (!m_ResInfo.m_StyleHash.Insert (pName, pStyle)) { + delete pStyle; + } + } + } + + string_view_t CPaintManagerUI::GetStyle (string_view_t pName) const { + CDuiString* pStyle = static_cast(m_ResInfo.m_StyleHash.Find (pName)); + if (!pStyle) pStyle = static_cast(m_SharedResInfo.m_StyleHash.Find (pName)); + if (pStyle) return pStyle->c_str (); + else return _T (""); + } + + BOOL CPaintManagerUI::RemoveStyle (string_view_t pName, bool bShared) { + CDuiString* pStyle = nullptr; + if (bShared) { + pStyle = static_cast(m_SharedResInfo.m_StyleHash.Find (pName)); + if (pStyle) { + delete pStyle; + m_SharedResInfo.m_StyleHash.Remove (pName); + } + } else { + pStyle = static_cast(m_ResInfo.m_StyleHash.Find (pName)); + if (pStyle) { + delete pStyle; + m_ResInfo.m_StyleHash.Remove (pName); + } + } + return true; + } + + const CStdStringPtrMap& CPaintManagerUI::GetStyles (bool bShared) const { + if (bShared) return m_SharedResInfo.m_StyleHash; + else return m_ResInfo.m_StyleHash; + } + + void CPaintManagerUI::RemoveAllStyle (bool bShared) { + if (bShared) { + CDuiString* pStyle; + for (int i = 0; i < m_SharedResInfo.m_StyleHash.GetSize (); i++) { + string_view_t key = m_SharedResInfo.m_StyleHash.GetAt (i)->Key; + if (!key.empty ()) { + pStyle = static_cast(m_SharedResInfo.m_StyleHash.Find (key)); + delete pStyle; + } + } + m_SharedResInfo.m_StyleHash.RemoveAll (); + } else { + CDuiString* pStyle; + for (int i = 0; i < m_ResInfo.m_StyleHash.GetSize (); i++) { + string_view_t key = m_ResInfo.m_StyleHash.GetAt (i)->Key; + if (!key.empty ()) { + pStyle = static_cast(m_ResInfo.m_StyleHash.Find (key)); + delete pStyle; + } + } + m_ResInfo.m_StyleHash.RemoveAll (); + } + } + + const TImageInfo* CPaintManagerUI::GetImageString (string_view_t pStrImage, string_view_t pStrModify) { + CDuiString sImageName = pStrImage; + CDuiString sImageResType = _T (""); + DWORD dwMask = 0; + for (size_t i = 0; i < 2; ++i) { + std::map m = FawTools::parse_keyvalue_pairs (i == 0 ? pStrImage : pStrModify); + for (auto[str_key, str_value] : m) { + if (str_key == _T ("file") || str_key == _T ("res")) { + sImageName = str_value; + } else if (str_key == _T ("restype")) { + sImageResType = str_value; + } else if (str_key == _T ("mask")) { + dwMask = static_cast (FawTools::parse_hex (str_value)); + } + } + } + return GetImageEx (sImageName, sImageResType, dwMask); + } + + bool CPaintManagerUI::InitDragDrop () { + AddRef (); + + if (FAILED (RegisterDragDrop (m_hWndPaint, this))) //calls addref + { + DWORD dwError = GetLastError (); + return false; + } else Release (); //i decided to AddRef explicitly after new + + FORMATETC ftetc = { 0 }; + ftetc.cfFormat = CF_BITMAP; + ftetc.dwAspect = DVASPECT_CONTENT; + ftetc.lindex = -1; + ftetc.tymed = TYMED_GDI; + AddSuportedFormat (ftetc); + ftetc.cfFormat = CF_DIB; + ftetc.tymed = TYMED_HGLOBAL; + AddSuportedFormat (ftetc); + ftetc.cfFormat = CF_HDROP; + ftetc.tymed = TYMED_HGLOBAL; + AddSuportedFormat (ftetc); + ftetc.cfFormat = CF_ENHMETAFILE; + ftetc.tymed = TYMED_ENHMF; + AddSuportedFormat (ftetc); + return true; + } + static WORD DIBNumColors (void* pv) { + int bits; + LPBITMAPINFOHEADER lpbi; + LPBITMAPCOREHEADER lpbc; + lpbi = ((LPBITMAPINFOHEADER) pv); + lpbc = ((LPBITMAPCOREHEADER) pv); + /* With the BITMAPINFO format headers, the size of the palette + * is in biClrUsed, whereas in the BITMAPCORE - style headers, it + * is dependent on the bits per pixel ( = 2 raised to the power of + * bits/pixel). + */ + if (lpbi->biSize != sizeof (BITMAPCOREHEADER)) { + if (lpbi->biClrUsed != 0) + return (WORD) lpbi->biClrUsed; + bits = lpbi->biBitCount; + } else + bits = lpbc->bcBitCount; + switch (bits) { + case 1: + return 2; + case 4: + return 16; + case 8: + return 256; + default: + /* A 24 bitcount DIB has no color table */ + return 0; + } + } + //code taken from SEEDIB MSDN sample + static WORD ColorTableSize (LPVOID lpv) { + LPBITMAPINFOHEADER lpbih = (LPBITMAPINFOHEADER) lpv; + + if (lpbih->biSize != sizeof (BITMAPCOREHEADER)) { + if (((LPBITMAPINFOHEADER) (lpbih))->biCompression == BI_BITFIELDS) + /* Remember that 16/32bpp dibs can still have a color table */ + return (sizeof (DWORD) * 3) + (DIBNumColors (lpbih) * sizeof (RGBQUAD)); + else + return (WORD) (DIBNumColors (lpbih) * sizeof (RGBQUAD)); + } else + return (WORD) (DIBNumColors (lpbih) * sizeof (RGBTRIPLE)); + } + + bool CPaintManagerUI::OnDrop (FORMATETC* pFmtEtc, STGMEDIUM& medium, DWORD *pdwEffect) { + POINT ptMouse = { 0 }; + GetCursorPos (&ptMouse); + ::SendMessage (m_hTargetWnd, WM_LBUTTONUP, 0, MAKELPARAM (ptMouse.x, ptMouse.y)); + + if (pFmtEtc->cfFormat == CF_DIB && medium.tymed == TYMED_HGLOBAL) { + if (medium.hGlobal) { + LPBITMAPINFOHEADER lpbi = (BITMAPINFOHEADER*) GlobalLock (medium.hGlobal); + if (lpbi) { + HBITMAP hbm = NULL; + HDC hdc = GetDC (nullptr); + if (hdc) { + int i = ((BITMAPFILEHEADER *) lpbi)->bfOffBits; + hbm = CreateDIBitmap (hdc, (LPBITMAPINFOHEADER) lpbi, + (LONG) CBM_INIT, + (LPSTR) lpbi + lpbi->biSize + ColorTableSize (lpbi), + (LPBITMAPINFO) lpbi, DIB_RGB_COLORS); + + ::ReleaseDC (nullptr, hdc); + } + GlobalUnlock (medium.hGlobal); + if (hbm != NULL) + hbm = (HBITMAP) SendMessage (m_hTargetWnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hbm); + if (hbm != NULL) + DeleteObject (hbm); + return true; //release the medium + } + } + } + if (pFmtEtc->cfFormat == CF_BITMAP && medium.tymed == TYMED_GDI) { + if (medium.hBitmap) { + HBITMAP hBmp = (HBITMAP) SendMessage (m_hTargetWnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) medium.hBitmap); + if (hBmp) + DeleteObject (hBmp); + return false; //don't free the bitmap + } + } + if (pFmtEtc->cfFormat == CF_ENHMETAFILE && medium.tymed == TYMED_ENHMF) { + ENHMETAHEADER emh; + GetEnhMetaFileHeader (medium.hEnhMetaFile, sizeof (ENHMETAHEADER), &emh); + RECT rc = { 0 };//={0,0,EnhMetaHdr.rclBounds.right-EnhMetaHdr.rclBounds.left, EnhMetaHdr.rclBounds.bottom-EnhMetaHdr.rclBounds.top}; + HDC hDC = GetDC (m_hTargetWnd); + //start code: taken from ENHMETA.EXE MSDN Sample + //*ALSO NEED to GET the pallete (select and RealizePalette it, but i was too lazy* + // Get the characteristics of the output device + float PixelsX = (float) GetDeviceCaps (hDC, HORZRES); + float PixelsY = (float) GetDeviceCaps (hDC, VERTRES); + float MMX = (float) GetDeviceCaps (hDC, HORZSIZE); + float MMY = (float) GetDeviceCaps (hDC, VERTSIZE); + // Calculate the rect in which to draw the metafile based on the + // intended size and the current output device resolution + // Remember that the intended size is given in 0.01mm units, so + // convert those to device units on the target device + rc.top = (int) ((float) (emh.rclFrame.top) * PixelsY / (MMY*100.0f)); + rc.left = (int) ((float) (emh.rclFrame.left) * PixelsX / (MMX*100.0f)); + rc.right = (int) ((float) (emh.rclFrame.right) * PixelsX / (MMX*100.0f)); + rc.bottom = (int) ((float) (emh.rclFrame.bottom) * PixelsY / (MMY*100.0f)); + //end code: taken from ENHMETA.EXE MSDN Sample + + HDC hdcMem = CreateCompatibleDC (hDC); + HGDIOBJ hBmpMem = CreateCompatibleBitmap (hDC, emh.rclBounds.right, emh.rclBounds.bottom); + HGDIOBJ hOldBmp = ::SelectObject (hdcMem, hBmpMem); + PlayEnhMetaFile (hdcMem, medium.hEnhMetaFile, &rc); + HBITMAP hBmp = (HBITMAP)::SelectObject (hdcMem, hOldBmp); + DeleteDC (hdcMem); + ReleaseDC (m_hTargetWnd, hDC); + hBmp = (HBITMAP) SendMessage (m_hTargetWnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hBmp); + if (hBmp) + DeleteObject (hBmp); + return true; + } + if (pFmtEtc->cfFormat == CF_HDROP && medium.tymed == TYMED_HGLOBAL) { + HDROP hDrop = (HDROP) GlobalLock (medium.hGlobal); + if (hDrop) { + TCHAR szFileName[MAX_PATH]; + UINT cFiles = DragQueryFile (hDrop, 0xFFFFFFFF, nullptr, 0); + if (cFiles > 0) { + DragQueryFile (hDrop, 0, szFileName, sizeof (szFileName)); + HBITMAP hBitmap = (HBITMAP) LoadImage (nullptr, szFileName, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE); + if (hBitmap) { + HBITMAP hBmp = (HBITMAP) SendMessage (m_hTargetWnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hBitmap); + if (hBmp) + DeleteObject (hBmp); + } + } + //DragFinish(hDrop); // base class calls ReleaseStgMedium + } + GlobalUnlock (medium.hGlobal); + } + return true; //let base free the medium + } +} // namespace DuiLib diff --git a/DuiLib/Core/UIManager.h b/DuiLib/Core/UIManager.h new file mode 100644 index 0000000..d210818 --- /dev/null +++ b/DuiLib/Core/UIManager.h @@ -0,0 +1,551 @@ +#ifndef __UIMANAGER_H__ +#define __UIMANAGER_H__ + +#pragma once +#define WM_USER_SET_DPI WM_USER + 200 +namespace DuiLib { + ///////////////////////////////////////////////////////////////////////////////////// + // + + class CControlUI; + class CRichEditUI; + class CIDropTarget; + + ///////////////////////////////////////////////////////////////////////////////////// + // + enum UILIB_RESTYPE { + UILIB_FILE = 1, // Դļ + UILIB_ZIP, // Դzipѹ + UILIB_RESOURCE, // Դ + UILIB_ZIPRESOURCE, // Դzipѹ + }; + ///////////////////////////////////////////////////////////////////////////////////// + // + + enum EVENTTYPE_UI { + UIEVENT__FIRST = 1, + UIEVENT__KEYBEGIN, + UIEVENT_KEYDOWN, + UIEVENT_KEYUP, + UIEVENT_CHAR, + UIEVENT_SYSKEY, + UIEVENT__KEYEND, + UIEVENT__MOUSEBEGIN, + UIEVENT_MOUSEMOVE, + UIEVENT_MOUSELEAVE, + UIEVENT_MOUSEENTER, + UIEVENT_MOUSEHOVER, + UIEVENT_BUTTONDOWN, + UIEVENT_BUTTONUP, + UIEVENT_RBUTTONDOWN, + UIEVENT_RBUTTONUP, + UIEVENT_MBUTTONDOWN, + UIEVENT_MBUTTONUP, + UIEVENT_DBLCLICK, + UIEVENT_CONTEXTMENU, + UIEVENT_SCROLLWHEEL, + UIEVENT__MOUSEEND, + UIEVENT_KILLFOCUS, + UIEVENT_SETFOCUS, + UIEVENT_WINDOWSIZE, + UIEVENT_SETCURSOR, + UIEVENT_TIMER, + UIEVENT__LAST, + }; + + enum MSGTYPE_UI { + // ڲϢ + UIMSG_TRAYICON = WM_USER + 1, + // ԶϢ + UIMSG_USER = WM_USER + 100, + }; + ///////////////////////////////////////////////////////////////////////////////////// + // + + // Flags for CControlUI::GetControlFlags() +#define UIFLAG_TABSTOP 0x00000001 +#define UIFLAG_SETCURSOR 0x00000002 +#define UIFLAG_WANTRETURN 0x00000004 + + // Flags for FindControl() +#define UIFIND_ALL 0x00000000 +#define UIFIND_VISIBLE 0x00000001 +#define UIFIND_ENABLED 0x00000002 +#define UIFIND_HITTEST 0x00000004 +#define UIFIND_UPDATETEST 0x00000008 +#define UIFIND_TOP_FIRST 0x00000010 +#define UIFIND_ME_FIRST 0x80000000 + + // Flags used for controlling the paint +#define UISTATE_FOCUSED 0x00000001 +#define UISTATE_SELECTED 0x00000002 +#define UISTATE_DISABLED 0x00000004 +#define UISTATE_HOT 0x00000008 +#define UISTATE_PUSHED 0x00000010 +#define UISTATE_READONLY 0x00000020 +#define UISTATE_CAPTURED 0x00000040 + + + + ///////////////////////////////////////////////////////////////////////////////////// + // + + typedef struct UILIB_API tagTFontInfo { + HFONT hFont; + CDuiString sFontName; + int iSize; + bool bBold; + bool bUnderline; + bool bItalic; + TEXTMETRIC tm; + } TFontInfo; + + typedef struct UILIB_API tagTImageInfo { + HBITMAP hBitmap = NULL; + HBITMAP *phBitmap = nullptr; + LPBYTE pBits = nullptr; + LPBYTE pSrcBits = nullptr; + int nX = 0; + int nY = 0; + bool bAlpha = false; + bool bUseHSL = false; + CDuiString sResType = _T (""); + DWORD dwMask = 0; + } TImageInfo; + + typedef struct UILIB_API tagTDrawInfo { + tagTDrawInfo (); + void Parse (string_view_t pStrImage, string_view_t pStrModify, CPaintManagerUI *paintManager); + void Clear (); + + CDuiString sDrawString; + CDuiString sDrawModify; + CDuiString sImageName; + CDuiString sResType; + RECT rcDest = { 0 }; + RECT rcSource = { 0 }; + RECT rcCorner = { 0 }; + DWORD dwMask; + BYTE uFade; + bool bHole; + bool bTiledX; + bool bTiledY; + bool bHSL; + } TDrawInfo; + + typedef struct UILIB_API tagTPercentInfo { + double left; + double top; + double right; + double bottom; + } TPercentInfo; + + typedef struct UILIB_API tagTResInfo { + DWORD m_dwDefaultDisabledColor; + DWORD m_dwDefaultFontColor; + DWORD m_dwDefaultLinkFontColor; + DWORD m_dwDefaultLinkHoverFontColor; + DWORD m_dwDefaultSelectedBkColor; + TFontInfo m_DefaultFontInfo; + CStdStringPtrMap m_CustomFonts; + CStdStringPtrMap m_ImageHash; + CStdStringPtrMap m_AttrHash; + CStdStringPtrMap m_StyleHash; + CStdStringPtrMap m_DrawInfoHash; + } TResInfo; + + // Structure for notifications from the system + // to the control implementation. + typedef struct UILIB_API tagTEventUI { + int Type; + CControlUI* pSender; + DWORD dwTimestamp; + POINT ptMouse = { 0 }; + TCHAR chKey; + WORD wKeyState; + WPARAM wParam; + LPARAM lParam; + } TEventUI; + + // Drag&Drop control + const TCHAR* const CF_MOVECONTROL = _T ("CF_MOVECONTROL"); + + typedef struct UILIB_API tagTCFMoveUI { + CControlUI* pControl; + } TCFMoveUI; + + // Listener interface + class INotifyUI { + public: + virtual void Notify (TNotifyUI& msg) = 0; + }; + + // MessageFilter interface + class IMessageFilterUI { + public: + virtual LRESULT MessageHandler (UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled) = 0; + }; + + class ITranslateAccelerator { + public: + virtual LRESULT TranslateAccelerator (MSG *pMsg) = 0; + }; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + typedef CControlUI* (*LPCREATECONTROL)(string_view_t pstrType); + + class UILIB_API CPaintManagerUI: public CIDropTarget { + public: + CPaintManagerUI (); + virtual ~CPaintManagerUI (); + + public: + void Init (HWND hWnd, string_view_t pstrName = _T ("")); + bool IsUpdateNeeded () const; + void NeedUpdate (); + void Invalidate (); + void Invalidate (RECT& rcItem); + + string_view_t GetName () const; + HDC GetPaintDC () const; + HWND GetPaintWindow () const; + HWND GetTooltipWindow () const; + int GetHoverTime () const; + void SetHoverTime (int iTime); + + POINT GetMousePos () const; + SIZE GetClientSize () const; + SIZE GetInitSize (); + void SetInitSize (int cx, int cy); + RECT& GetSizeBox (); + void SetSizeBox (RECT& rcSizeBox); + RECT& GetCaptionRect (); + void SetCaptionRect (RECT& rcCaption); + SIZE GetRoundCorner () const; + void SetRoundCorner (int cx, int cy); + SIZE GetMinInfo () const; + void SetMinInfo (int cx, int cy); + SIZE GetMaxInfo () const; + void SetMaxInfo (int cx, int cy); + bool IsShowUpdateRect () const; + void SetShowUpdateRect (bool show); + bool IsNoActivate (); + void SetNoActivate (bool bNoActivate); + + BYTE GetOpacity () const; + void SetOpacity (BYTE nOpacity); + + bool IsLayered (); + void SetLayered (bool bLayered); + RECT& GetLayeredInset (); + void SetLayeredInset (RECT& rcLayeredInset); + BYTE GetLayeredOpacity (); + void SetLayeredOpacity (BYTE nOpacity); + string_view_t GetLayeredImage (); + void SetLayeredImage (string_view_t pstrImage); + + CShadowUI* GetShadow (); + + void SetUseGdiplusText (bool bUse); + bool IsUseGdiplusText () const; + void SetGdiplusTextRenderingHint (int trh); + int GetGdiplusTextRenderingHint () const; + + static HINSTANCE GetInstance (); + static CDuiString GetInstancePath (); + static CDuiString GetCurrentPath (); + static HINSTANCE GetResourceDll (); + static const string_view_t GetResourcePath (); + static const string_view_t GetResourceZip (); + static const string_view_t GetResourceZipPwd (); + static bool IsCachedResourceZip (); + static HANDLE GetResourceZipHandle (); + static void SetInstance (HINSTANCE hInst); + static void SetCurrentPath (string_view_t pStrPath); + static void SetResourceDll (HINSTANCE hInst); + static void SetResourcePath (string_view_t pStrPath); + static void SetResourceZip (LPVOID pVoid, unsigned int len, string_view_t password = _T ("")); + static void SetResourceZip (string_view_t pstrZip, bool bCachedResourceZip = false, string_view_t password = _T ("")); + static void SetResourceType (int nType); + static int GetResourceType (); + static bool GetHSL (short* H, short* S, short* L); + static void SetHSL (bool bUseHSL, short H, short S, short L); // H:0~360, S:0~200, L:0~200 + static void ReloadSkin (); + static CPaintManagerUI* GetPaintManager (string_view_t pstrName); + static CStdPtrArray* GetPaintManagers (); + static bool LoadPlugin (string_view_t pstrModuleName); + static CStdPtrArray* GetPlugins (); + + bool IsForceUseSharedRes () const; + void SetForceUseSharedRes (bool bForce); + + void DeletePtr (void* ptr); + + DWORD GetDefaultDisabledColor () const; + void SetDefaultDisabledColor (DWORD dwColor, bool bShared = false); + DWORD GetDefaultFontColor () const; + void SetDefaultFontColor (DWORD dwColor, bool bShared = false); + DWORD GetDefaultLinkFontColor () const; + void SetDefaultLinkFontColor (DWORD dwColor, bool bShared = false); + DWORD GetDefaultLinkHoverFontColor () const; + void SetDefaultLinkHoverFontColor (DWORD dwColor, bool bShared = false); + DWORD GetDefaultSelectedBkColor () const; + void SetDefaultSelectedBkColor (DWORD dwColor, bool bShared = false); + TFontInfo* GetDefaultFontInfo (); + void SetDefaultFont (string_view_t pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic, bool bShared = false); + DWORD GetCustomFontCount (bool bShared = false) const; + void AddFontArray (string_view_t pstrPath); + HFONT AddFont (int id, string_view_t pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic, bool bShared = false); + HFONT GetFont (int id); + HFONT GetFont (string_view_t pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic); + int GetFontIndex (HFONT hFont, bool bShared = false); + int GetFontIndex (string_view_t pStrFontName, int nSize, bool bBold, bool bUnderline, bool bItalic, bool bShared = false); + void RemoveFont (HFONT hFont, bool bShared = false); + void RemoveFont (int id, bool bShared = false); + void RemoveAllFonts (bool bShared = false); + TFontInfo* GetFontInfo (int id); + TFontInfo* GetFontInfo (HFONT hFont); + + const TImageInfo* GetImage (string_view_t bitmap); + const TImageInfo* GetImageEx (string_view_t bitmap, string_view_t type = _T (""), DWORD mask = 0, bool bUseHSL = false, HINSTANCE instance = NULL); + const TImageInfo* AddImage (string_view_t bitmap, string_view_t type = _T (""), DWORD mask = 0, bool bUseHSL = false, bool bShared = false, HINSTANCE instance = NULL); + const TImageInfo* AddImage (string_view_t bitmap, HBITMAP hBitmap, int iWidth, int iHeight, bool bAlpha, bool bShared = false); + const TImageInfo* AddImage (string_view_t bitmap, HBITMAP *phBitmap, int iWidth, int iHeight, bool bAlpha, bool bShared = false); + void RemoveImage (string_view_t bitmap, bool bShared = false); + void RemoveAllImages (bool bShared = false); + static void ReloadSharedImages (); + void ReloadImages (); + + const TDrawInfo* GetDrawInfo (string_view_t pStrImage, string_view_t pStrModify); + void RemoveDrawInfo (string_view_t pStrImage, string_view_t pStrModify); + void RemoveAllDrawInfos (); + + void AddDefaultAttributeList (string_view_t pStrControlName, string_view_t pStrControlAttrList, bool bShared = false); + string_view_t GetDefaultAttributeList (string_view_t pStrControlName) const; + bool RemoveDefaultAttributeList (string_view_t pStrControlName, bool bShared = false); + void RemoveAllDefaultAttributeList (bool bShared = false); + + void AddWindowCustomAttribute (string_view_t pstrName, string_view_t pstrAttr); + string_view_t GetWindowCustomAttribute (string_view_t pstrName) const; + bool RemoveWindowCustomAttribute (string_view_t pstrName); + void RemoveAllWindowCustomAttribute (); + + // ʽ + void AddStyle (string_view_t pName, string_view_t pStyle, bool bShared = false); + string_view_t GetStyle (string_view_t pName) const; + BOOL RemoveStyle (string_view_t pName, bool bShared = false); + const CStdStringPtrMap& GetStyles (bool bShared = false) const; + void RemoveAllStyle (bool bShared = false); + + const TImageInfo* GetImageString (string_view_t pStrImage, string_view_t pStrModify = _T ("")); + + // ʼק + bool InitDragDrop (); + virtual bool OnDrop (FORMATETC* pFmtEtc, STGMEDIUM& medium, DWORD *pdwEffect); + + bool AttachDialog (CControlUI* pControl); + bool InitControls (CControlUI* pControl, CControlUI* pParent = nullptr); + void ReapObjects (CControlUI* pControl); + + bool AddOptionGroup (string_view_t pStrGroupName, CControlUI* pControl); + CStdPtrArray* GetOptionGroup (string_view_t pStrGroupName); + void RemoveOptionGroup (string_view_t pStrGroupName, CControlUI* pControl); + void RemoveAllOptionGroups (); + + CControlUI* GetFocus () const; + void SetFocus (CControlUI* pControl); + void SetFocusNeeded (CControlUI* pControl); + + bool SetNextTabControl (bool bForward = true); + + bool SetTimer (CControlUI* pControl, UINT nTimerID, UINT uElapse); + bool KillTimer (CControlUI* pControl, UINT nTimerID); + void KillTimer (CControlUI* pControl); + void RemoveAllTimers (); + + void SetCapture (); + void ReleaseCapture (); + bool IsCaptured (); + + bool IsPainting (); + void SetPainting (bool bIsPainting); + + bool AddNotifier (INotifyUI* pControl); + bool RemoveNotifier (INotifyUI* pControl); + void SendNotify (TNotifyUI& Msg, bool bAsync = false); + void SendNotify (CControlUI* pControl, string_view_t pstrMessage, WPARAM wParam = 0, LPARAM lParam = 0, bool bAsync = false); + + bool AddPreMessageFilter (IMessageFilterUI* pFilter); + bool RemovePreMessageFilter (IMessageFilterUI* pFilter); + + bool AddMessageFilter (IMessageFilterUI* pFilter); + bool RemoveMessageFilter (IMessageFilterUI* pFilter); + + int GetPostPaintCount () const; + bool IsPostPaint (CControlUI* pControl); + bool AddPostPaint (CControlUI* pControl); + bool RemovePostPaint (CControlUI* pControl); + bool SetPostPaintIndex (CControlUI* pControl, int iIndex); + + int GetNativeWindowCount () const; + RECT GetNativeWindowRect (HWND hChildWnd); + bool AddNativeWindow (CControlUI* pControl, HWND hChildWnd); + bool RemoveNativeWindow (HWND hChildWnd); + + void AddDelayedCleanup (CControlUI* pControl); + void AddMouseLeaveNeeded (CControlUI* pControl); + bool RemoveMouseLeaveNeeded (CControlUI* pControl); + + bool AddTranslateAccelerator (ITranslateAccelerator *pTranslateAccelerator); + bool RemoveTranslateAccelerator (ITranslateAccelerator *pTranslateAccelerator); + bool TranslateAccelerator (LPMSG pMsg); + + CControlUI* GetRoot () const; + CControlUI* FindControl (POINT pt) const; + CControlUI* FindControl (string_view_t pstrName) const; + CControlUI* FindSubControlByPoint (CControlUI* pParent, POINT pt) const; + CControlUI* FindSubControlByName (CControlUI* pParent, string_view_t pstrName) const; + CControlUI* FindSubControlByClass (CControlUI* pParent, string_view_t pstrClass, int iIndex = 0); + CStdPtrArray* FindSubControlsByClass (CControlUI* pParent, string_view_t pstrClass); + + static void MessageLoop (); + static bool TranslateMessage (const LPMSG pMsg); + static void Term (); + + CDPI* GetDPIObj (); + void ResetDPIAssets (); + void RebuildFont (TFontInfo* pFontInfo); + void SetDPI (int iDPI); + static void SetAllDPI (int iDPI); + + bool MessageHandler (UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes); + bool PreMessageHandler (UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes); + void UsedVirtualWnd (bool bUsed); + + private: + CStdPtrArray* GetFoundControls (); + static CControlUI* CALLBACK __FindControlFromNameHash (CControlUI* pThis, LPVOID pData); + static CControlUI* CALLBACK __FindControlFromCount (CControlUI* pThis, LPVOID pData); + static CControlUI* CALLBACK __FindControlFromPoint (CControlUI* pThis, LPVOID pData); + static CControlUI* CALLBACK __FindControlFromTab (CControlUI* pThis, LPVOID pData); + static CControlUI* CALLBACK __FindControlFromShortcut (CControlUI* pThis, LPVOID pData); + static CControlUI* CALLBACK __FindControlFromName (CControlUI* pThis, LPVOID pData); + static CControlUI* CALLBACK __FindControlFromClass (CControlUI* pThis, LPVOID pData); + static CControlUI* CALLBACK __FindControlsFromClass (CControlUI* pThis, LPVOID pData); + static CControlUI* CALLBACK __FindControlsFromUpdate (CControlUI* pThis, LPVOID pData); + + static void AdjustSharedImagesHSL (); + void AdjustImagesHSL (); + void PostAsyncNotify (); + + private: + CDuiString m_sName; + HWND m_hWndPaint; //ӵĴľ + HDC m_hDcPaint; + HDC m_hDcOffscreen; + HDC m_hDcBackground; + HBITMAP m_hbmpOffscreen; + BYTE* m_pOffscreenBits; + HBITMAP m_hbmpBackground; + COLORREF* m_pBackgroundBits; + + // ʾϢ + HWND m_hwndTooltip; + TOOLINFO m_ToolTip; + int m_iHoverTime; + bool m_bNoActivate; + bool m_bShowUpdateRect; + + // + CControlUI* m_pRoot; + CControlUI* m_pFocus; + CControlUI* m_pEventHover; + CControlUI* m_pEventClick; + CControlUI* m_pEventKey; + CControlUI* m_pLastToolTip; + // + POINT m_ptLastMousePos = { 0 }; + SIZE m_szMinWindow = { 0 }; + SIZE m_szMaxWindow = { 0 }; + SIZE m_szInitWindowSize = { 0 }; + RECT m_rcSizeBox = { 0 }; + SIZE m_szRoundCorner = { 0 }; + RECT m_rcCaption = { 0 }; + UINT m_uTimerID; + bool m_bFirstLayout; + bool m_bUpdateNeeded; + bool m_bFocusNeeded; + bool m_bOffscreenPaint; + + BYTE m_nOpacity; + bool m_bLayered; + RECT m_rcLayeredInset = { 0 }; + bool m_bLayeredChanged; + RECT m_rcLayeredUpdate = { 0 }; + TDrawInfo m_diLayered; + + bool m_bMouseTracking; + bool m_bMouseCapture; + bool m_bIsPainting; + bool m_bUsedVirtualWnd; + bool m_bAsyncNotifyPosted; + + // + CStdPtrArray m_aNotifiers; + CStdPtrArray m_aTimers; + CStdPtrArray m_aTranslateAccelerator; + CStdPtrArray m_aPreMessageFilters; + CStdPtrArray m_aMessageFilters; + CStdPtrArray m_aPostPaintControls; + CStdPtrArray m_aNativeWindow; + CStdPtrArray m_aNativeWindowControl; + CStdPtrArray m_aDelayedCleanup; + CStdPtrArray m_aAsyncNotify; + CStdPtrArray m_aFoundControls; + CStdPtrArray m_aFonts; + CStdPtrArray m_aNeedMouseLeaveNeeded; + CStdStringPtrMap m_mNameHash; + CStdStringPtrMap m_mWindowCustomAttrHash; + CStdStringPtrMap m_mOptionGroup; + + bool m_bForceUseSharedRes; + TResInfo m_ResInfo; + + // Ӱ + CShadowUI m_shadow; + + // DPI + CDPI* m_pDPI; + // ǷGdiplus + bool m_bUseGdiplusText; + int m_trh; + ULONG_PTR m_gdiplusToken; + Gdiplus::GdiplusStartupInput *m_pGdiplusStartupInput; + + // ק + bool m_bDragMode; + HBITMAP m_hDragBitmap; + + // + static HINSTANCE m_hInstance; + static HINSTANCE m_hResourceInstance; + static CDuiString m_pStrResourcePath; + static CDuiString m_pStrResourceZip; + static CDuiString m_pStrResourceZipPwd; + static HANDLE m_hResourceZip; + static bool m_bCachedResourceZip; + static int m_nResType; + static TResInfo m_SharedResInfo; + static bool m_bUseHSL; + static short m_H; + static short m_S; + static short m_L; + static CStdPtrArray m_aPreMessages; + static CStdPtrArray m_aPlugins; + }; + +} // namespace DuiLib + +#endif // __UIMANAGER_H__ diff --git a/DuiLib/Core/UIMarkup.cpp b/DuiLib/Core/UIMarkup.cpp new file mode 100644 index 0000000..1baf723 --- /dev/null +++ b/DuiLib/Core/UIMarkup.cpp @@ -0,0 +1,485 @@ +#include "StdAfx.h" + +#ifndef TRACE +#define TRACE +#endif + +namespace DuiLib { + /////////////////////////////////////////////////////////////////////////////////////// + // + // + // + CMarkupNode::CMarkupNode (): m_pOwner (nullptr) {} + + CMarkupNode::CMarkupNode (CMarkup* pOwner, int iPos) : m_pOwner (pOwner), m_iPos (iPos), m_nAttributes (0) {} + + CMarkupNode CMarkupNode::GetSibling () { + if (!m_pOwner) return CMarkupNode (); + ULONG iPos = m_pOwner->m_pElements[m_iPos].iNext; + if (iPos == 0) return CMarkupNode (); + return CMarkupNode (m_pOwner, iPos); + } + + bool CMarkupNode::HasSiblings () const { + if (!m_pOwner) return false; + ULONG iPos = m_pOwner->m_pElements[m_iPos].iNext; + return iPos > 0; + } + + CMarkupNode CMarkupNode::GetChild () { + if (!m_pOwner) return CMarkupNode (); + ULONG iPos = m_pOwner->m_pElements[m_iPos].iChild; + if (iPos == 0) return CMarkupNode (); + return CMarkupNode (m_pOwner, iPos); + } + + CMarkupNode CMarkupNode::GetChild (string_view_t pstrName) { + if (!m_pOwner) return CMarkupNode (); + ULONG iPos = m_pOwner->m_pElements[m_iPos].iChild; + while (iPos != 0) { + if (pstrName == &m_pOwner->m_pstrXML[m_pOwner->m_pElements[iPos].iStart]) { + return CMarkupNode (m_pOwner, iPos); + } + iPos = m_pOwner->m_pElements[iPos].iNext; + } + return CMarkupNode (); + } + + bool CMarkupNode::HasChildren () const { + if (!m_pOwner) return false; + return m_pOwner->m_pElements[m_iPos].iChild != 0; + } + + CMarkupNode CMarkupNode::GetParent () { + if (!m_pOwner) return CMarkupNode (); + ULONG iPos = m_pOwner->m_pElements[m_iPos].iParent; + if (iPos == 0) return CMarkupNode (); + return CMarkupNode (m_pOwner, iPos); + } + + bool CMarkupNode::IsValid () const { + return m_pOwner; + } + + string_t CMarkupNode::GetName () const { + if (!m_pOwner) return _T (""); + return &m_pOwner->m_pstrXML[m_pOwner->m_pElements[m_iPos].iStart]; + } + + string_t CMarkupNode::GetValue () const { + if (!m_pOwner) return _T (""); + return &m_pOwner->m_pstrXML[m_pOwner->m_pElements[m_iPos].iData]; + } + + string_t CMarkupNode::GetAttributeName (int iIndex) { + if (!m_pOwner) return _T (""); + if (m_nAttributes == 0) _MapAttributes (); + if (iIndex < 0 || iIndex >= m_nAttributes) return _T (""); + return &m_pOwner->m_pstrXML[m_aAttributes[iIndex].iName]; + } + + string_t CMarkupNode::GetAttributeValue (int iIndex) { + if (!m_pOwner) return _T (""); + if (m_nAttributes == 0) _MapAttributes (); + if (iIndex < 0 || iIndex >= m_nAttributes) return _T (""); + return &m_pOwner->m_pstrXML[m_aAttributes[iIndex].iValue]; + } + + string_t CMarkupNode::GetAttributeValue (string_view_t pstrName) { + if (!m_pOwner) return _T (""); + if (m_nAttributes == 0) _MapAttributes (); + for (int i = 0; i < m_nAttributes; i++) { + if (pstrName == &m_pOwner->m_pstrXML[m_aAttributes[i].iName]) return &m_pOwner->m_pstrXML[m_aAttributes[i].iValue]; + } + return _T (""); + } + + int CMarkupNode::GetAttributeCount () { + if (!m_pOwner) return 0; + if (m_nAttributes == 0) _MapAttributes (); + return m_nAttributes; + } + + bool CMarkupNode::HasAttributes () { + if (!m_pOwner) return false; + if (m_nAttributes == 0) _MapAttributes (); + return m_nAttributes > 0; + } + + bool CMarkupNode::HasAttribute (string_view_t pstrName) { + if (!m_pOwner) return false; + if (m_nAttributes == 0) _MapAttributes (); + for (int i = 0; i < m_nAttributes; i++) { + if (pstrName == &m_pOwner->m_pstrXML[m_aAttributes[i].iName]) return true; + } + return false; + } + + void CMarkupNode::_MapAttributes () { + m_nAttributes = 0; + LPCTSTR pstr = &m_pOwner->m_pstrXML[m_pOwner->m_pElements[m_iPos].iStart]; + LPCTSTR pstrEnd = &m_pOwner->m_pstrXML[m_pOwner->m_pElements[m_iPos].iData]; + pstr += _tcslen (pstr) + 1; + while (pstr < pstrEnd) { + m_pOwner->_SkipWhitespace (pstr); + m_aAttributes[m_nAttributes].iName = (ULONG) (pstr - &m_pOwner->m_pstrXML[0]); + pstr += _tcslen (pstr) + 1; + m_pOwner->_SkipWhitespace (pstr); + if (*pstr++ != _T ('\"')) return; // if( *pstr != _T('\"') ) { pstr = ::CharNext(pstr); return; } + + m_aAttributes[m_nAttributes++].iValue = (ULONG) (pstr - &m_pOwner->m_pstrXML[0]); + if (m_nAttributes >= MAX_XML_ATTRIBUTES) return; + pstr += _tcslen (pstr) + 1; + } + } + + + /////////////////////////////////////////////////////////////////////////////////////// + // + // + // + + CMarkup::CMarkup (string_view_t pstrXML) { + m_pstrXML = _T (""); + m_pElements = nullptr; + m_nElements = 0; + m_bPreserveWhitespace = true; + if (!pstrXML.empty ()) Load (pstrXML); + } + + CMarkup::~CMarkup () { + Release (); + } + + bool CMarkup::IsValid () const { + return m_pElements; + } + + void CMarkup::SetPreserveWhitespace (bool bPreserve) { + m_bPreserveWhitespace = bPreserve; + } + + bool CMarkup::Load (string_view_t pstrXML) { + Release (); + m_pstrXML = pstrXML; + bool bRes = _Parse (); + if (!bRes) Release (); + return bRes; + } + + bool CMarkup::LoadFromMem (BYTE* pByte, DWORD dwSize, int encoding) { + if (encoding == XMLFILE_ENCODING_UTF8) { + if (dwSize >= 3 && pByte[0] == 0xEF && pByte[1] == 0xBB && pByte[2] == 0xBF) { + pByte += 3; dwSize -= 3; + } + std::string_view data ((const char*) pByte, (size_t) dwSize); + m_pstrXML = FawTools::get_T_from_utf8 (data); + } else if (encoding == XMLFILE_ENCODING_ASNI) { + std::string_view data ((const char*) pByte, (size_t) dwSize); + m_pstrXML = FawTools::get_T (data); + } else { + if (dwSize >= 2 && ((pByte[0] == 0xFE && pByte[1] == 0xFF) || (pByte[0] == 0xFF && pByte[1] == 0xFE))) { + dwSize = dwSize / 2 - 1; + if (pByte[0] == 0xFE && pByte[1] == 0xFF) { + pByte += 2; + for (DWORD nSwap = 0; nSwap < dwSize; nSwap++) { + CHAR nTemp = pByte[(nSwap << 1) + 0]; + pByte[(nSwap << 1) + 0] = pByte[(nSwap << 1) + 1]; + pByte[(nSwap << 1) + 1] = nTemp; + } + } else { + pByte += 2; + } + std::wstring_view data ((const wchar_t*) pByte, (size_t) dwSize); + m_pstrXML = FawTools::get_T (data); + pByte -= 2; + } + } + + bool bRes = _Parse (); + if (!bRes) Release (); + return bRes; + } + + bool CMarkup::LoadFromFile (string_view_t pstrFilename, int encoding) { + Release (); + CDuiString sFile = CPaintManagerUI::GetResourcePath (); + if (CPaintManagerUI::GetResourceZip ().empty ()) { + sFile += pstrFilename; + HANDLE hFile = ::CreateFile (sFile.c_str (), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) return _Failed (_T ("Error opening file")); + DWORD dwSize = ::GetFileSize (hFile, nullptr); + if (dwSize == 0) return _Failed (_T ("File is empty")); + if (dwSize > 4096 * 1024) return _Failed (_T ("File too large")); + + DWORD dwRead = 0; + BYTE* pByte = new BYTE[dwSize]; + ::ReadFile (hFile, pByte, dwSize, &dwRead, nullptr); + ::CloseHandle (hFile); + if (dwRead != dwSize) { + delete[] pByte; + pByte = nullptr; + Release (); + return _Failed (_T ("Could not read file")); + } + + bool ret = LoadFromMem (pByte, dwSize, encoding); + delete[] pByte; + pByte = nullptr; + + return ret; + } else { + sFile += CPaintManagerUI::GetResourceZip (); + HZIP hz = nullptr; + if (CPaintManagerUI::IsCachedResourceZip ()) hz = (HZIP) CPaintManagerUI::GetResourceZipHandle (); + else { + CDuiString sFilePwd = CPaintManagerUI::GetResourceZipPwd (); + std::string pwd = FawTools::get_gb18030 (sFilePwd); + hz = OpenZip (sFile.c_str (), pwd.c_str ()); + } + if (!hz) return _Failed (_T ("Error opening zip file")); + ZIPENTRY ze; + int i = 0; + CDuiString key = pstrFilename; + key.Replace (_T ("\\"), _T ("/")); + if (FindZipItem (hz, key.c_str (), true, &i, &ze) != 0) return _Failed (_T ("Could not find ziped file")); + DWORD dwSize = ze.unc_size; + if (dwSize == 0) return _Failed (_T ("File is empty")); + if (dwSize > 4096 * 1024) return _Failed (_T ("File too large")); + BYTE* pByte = new BYTE[dwSize]; + int res = UnzipItem (hz, i, pByte, dwSize); + if (res != 0x00000000 && res != 0x00000600) { + delete[] pByte; + if (!CPaintManagerUI::IsCachedResourceZip ()) CloseZip (hz); + return _Failed (_T ("Could not unzip file")); + } + if (!CPaintManagerUI::IsCachedResourceZip ()) CloseZip (hz); + bool ret = LoadFromMem (pByte, dwSize, encoding); + delete[] pByte; + pByte = nullptr; + return ret; + } + } + + void CMarkup::Release () { + if (m_pElements) free (m_pElements); + m_pstrXML = _T (""); + m_pElements = nullptr; + m_nElements = 0; + } + + string_view_t CMarkup::GetLastErrorMessage () const { + return m_szErrorMsg; + } + + string_view_t CMarkup::GetLastErrorLocation () const { + return m_szErrorXML; + } + + CMarkupNode CMarkup::GetRoot () { + if (m_nElements == 0) return CMarkupNode (); + return CMarkupNode (this, 1); + } + + bool CMarkup::_Parse () { + _ReserveElement (); // Reserve index 0 for errors + m_szErrorMsg = _T (""); + m_szErrorXML = _T (""); + LPTSTR pstrXML = &m_pstrXML[0]; + return _Parse (pstrXML, 0); + } + + bool CMarkup::_Parse (LPTSTR& pstrText, ULONG iParent) { + _SkipWhitespace (pstrText); + ULONG iPrevious = 0; + for (; ; ) { + if (*pstrText == _T ('\0') && iParent <= 1) + return true; + _SkipWhitespace (pstrText); + if (*pstrText != _T ('<')) + return _Failed (_T ("Expected start tag"), pstrText); + if (pstrText[1] == _T ('/')) + return true; + *pstrText++ = _T ('\0'); + _SkipWhitespace (pstrText); + // Skip comment or processing directive + if (*pstrText == _T ('!') || *pstrText == _T ('?')) { + TCHAR ch = *pstrText; + if (*pstrText == _T ('!')) ch = _T ('-'); + while (*pstrText != _T ('\0') && !(*pstrText == ch && *(pstrText + 1) == _T ('>'))) pstrText = ::CharNext (pstrText); + if (*pstrText != _T ('\0')) pstrText += 2; + _SkipWhitespace (pstrText); + continue; + } + _SkipWhitespace (pstrText); + // Fill out element structure + XMLELEMENT* pEl = _ReserveElement (); + ULONG iPos = pEl - m_pElements; + pEl->iStart = (ULONG) (pstrText - &m_pstrXML[0]); + pEl->iParent = iParent; + pEl->iNext = pEl->iChild = 0; + if (iPrevious != 0) m_pElements[iPrevious].iNext = iPos; + else if (iParent > 0) m_pElements[iParent].iChild = iPos; + iPrevious = iPos; + // Parse name + LPCTSTR pstrName = pstrText; + _SkipIdentifier (pstrText); + LPTSTR pstrNameEnd = pstrText; + if (*pstrText == _T ('\0')) + return _Failed (_T ("Error parsing element name"), pstrText); + // Parse attributes + if (!_ParseAttributes (pstrText)) + return false; + _SkipWhitespace (pstrText); + if (pstrText[0] == _T ('/') && pstrText[1] == _T ('>')) { + pEl->iData = (ULONG) (pstrText - &m_pstrXML[0]); + *pstrText = _T ('\0'); + pstrText += 2; + } else { + if (*pstrText != _T ('>')) + return _Failed (_T ("Expected start-tag closing"), pstrText); + // Parse node data + pEl->iData = (ULONG) (++pstrText - &m_pstrXML[0]); + LPTSTR pstrDest = pstrText; + if (!_ParseData (pstrText, pstrDest, _T ('<'))) + return false; + // Determine type of next element + if (*pstrText == _T ('\0') && iParent <= 1) + return true; + if (*pstrText != _T ('<')) + return _Failed (_T ("Expected end-tag start"), pstrText); + if (pstrText[0] == _T ('<') && pstrText[1] != _T ('/')) { + if (!_Parse (pstrText, iPos)) + return false; + } + if (pstrText[0] == _T ('<') && pstrText[1] == _T ('/')) { + *pstrDest = _T ('\0'); + *pstrText = _T ('\0'); + pstrText += 2; + _SkipWhitespace (pstrText); + SIZE_T cchName = pstrNameEnd - pstrName; + if (_tcsncmp (pstrText, pstrName, cchName) != 0) + return _Failed (_T ("Unmatched closing tag"), pstrText); + pstrText += cchName; + _SkipWhitespace (pstrText); + if (*pstrText++ != _T ('>')) + return _Failed (_T ("Unmatched closing tag"), pstrText); + } + } + *pstrNameEnd = _T ('\0'); + _SkipWhitespace (pstrText); + } + } + + CMarkup::XMLELEMENT* CMarkup::_ReserveElement () { + if (m_nElements == 0) m_nReservedElements = 0; + if (m_nElements >= m_nReservedElements) { + m_nReservedElements += (m_nReservedElements / 2) + 500; + m_pElements = static_cast(realloc (m_pElements, m_nReservedElements * sizeof (XMLELEMENT))); + } + return &m_pElements[m_nElements++]; + } + + void CMarkup::_SkipWhitespace (LPTSTR& pstr) const { + while (*pstr > _T ('\0') && *pstr <= _T (' ')) pstr = ::CharNext (pstr); + } + + void CMarkup::_SkipWhitespace (LPCTSTR& pstr) const { + while (*pstr > _T ('\0') && *pstr <= _T (' ')) pstr = ::CharNext (pstr); + } + + void CMarkup::_SkipIdentifier (LPTSTR& pstr) const { + // ֻӢģû + while (*pstr != _T ('\0') && (*pstr == _T ('_') || *pstr == _T (':') || _istalnum (*pstr))) pstr = ::CharNext (pstr); + } + + void CMarkup::_SkipIdentifier (LPCTSTR& pstr) const { + // ֻӢģû + while (*pstr != _T ('\0') && (*pstr == _T ('_') || *pstr == _T (':') || _istalnum (*pstr))) pstr = ::CharNext (pstr); + } + + bool CMarkup::_ParseAttributes (LPTSTR& pstrText) { + // + LPTSTR pstrIdentifier = pstrText; + if (*pstrIdentifier == _T ('/') && *++pstrIdentifier == _T ('>')) return true; + if (*pstrText == _T ('>')) return true; + *pstrText++ = _T ('\0'); + _SkipWhitespace (pstrText); + while (*pstrText != _T ('\0') && *pstrText != _T ('>') && *pstrText != _T ('/')) { + _SkipIdentifier (pstrText); + LPTSTR pstrIdentifierEnd = pstrText; + _SkipWhitespace (pstrText); + if (*pstrText != _T ('=')) return _Failed (_T ("Error while parsing attributes"), pstrText); + *pstrText++ = _T (' '); + *pstrIdentifierEnd = _T ('\0'); + _SkipWhitespace (pstrText); + if (*pstrText++ != _T ('\"')) return _Failed (_T ("Expected attribute value"), pstrText); + LPTSTR pstrDest = pstrText; + if (!_ParseData (pstrText, pstrDest, _T ('\"'))) return false; + if (*pstrText == _T ('\0')) return _Failed (_T ("Error while parsing attribute string"), pstrText); + *pstrDest = _T ('\0'); + if (pstrText != pstrDest) *pstrText = _T (' '); + pstrText++; + _SkipWhitespace (pstrText); + } + return true; + } + + bool CMarkup::_ParseData (LPTSTR& pstrText, LPTSTR& pstrDest, char cEnd) { + while (*pstrText != _T ('\0') && *pstrText != cEnd) { + if (*pstrText == _T ('&')) { + while (*pstrText == _T ('&')) { + _ParseMetaChar (++pstrText, pstrDest); + } + if (*pstrText == cEnd) + break; + } + + if (*pstrText == _T (' ')) { + *pstrDest++ = *pstrText++; + if (!m_bPreserveWhitespace) _SkipWhitespace (pstrText); + } else { + LPTSTR pstrTemp = ::CharNext (pstrText); + while (pstrText < pstrTemp) { + *pstrDest++ = *pstrText++; + } + } + } + // Make sure that MapAttributes() works correctly when it parses + // over a value that has been transformed. + LPTSTR pstrFill = pstrDest + 1; + while (pstrFill < pstrText) *pstrFill++ = _T (' '); + return true; + } + + void CMarkup::_ParseMetaChar (LPTSTR& pstrText, LPTSTR& pstrDest) { + if (pstrText[0] == _T ('a') && pstrText[1] == _T ('m') && pstrText[2] == _T ('p') && pstrText[3] == _T (';')) { + *pstrDest++ = _T ('&'); + pstrText += 4; + } else if (pstrText[0] == _T ('l') && pstrText[1] == _T ('t') && pstrText[2] == _T (';')) { + *pstrDest++ = _T ('<'); + pstrText += 3; + } else if (pstrText[0] == _T ('g') && pstrText[1] == _T ('t') && pstrText[2] == _T (';')) { + *pstrDest++ = _T ('>'); + pstrText += 3; + } else if (pstrText[0] == _T ('q') && pstrText[1] == _T ('u') && pstrText[2] == _T ('o') && pstrText[3] == _T ('t') && pstrText[4] == _T (';')) { + *pstrDest++ = _T ('\"'); + pstrText += 5; + } else if (pstrText[0] == _T ('a') && pstrText[1] == _T ('p') && pstrText[2] == _T ('o') && pstrText[3] == _T ('s') && pstrText[4] == _T (';')) { + *pstrDest++ = _T ('\''); + pstrText += 5; + } else { + *pstrDest++ = _T ('&'); + } + } + + bool CMarkup::_Failed (string_view_t pstrError, string_view_t pstrLocation) { + // Register last error + TRACE (_T ("XML Error: %s"), pstrError.data ()); + if (!pstrLocation.empty ()) TRACE (pstrLocation); + m_szErrorMsg = pstrError; + m_szErrorXML = pstrLocation; + return false; // Always return 'false' + } + +} // namespace DuiLib diff --git a/DuiLib/Core/UIMarkup.h b/DuiLib/Core/UIMarkup.h new file mode 100644 index 0000000..31544a6 --- /dev/null +++ b/DuiLib/Core/UIMarkup.h @@ -0,0 +1,112 @@ +#ifndef __UIMARKUP_H__ +#define __UIMARKUP_H__ + +#pragma once + +namespace DuiLib { + + enum { + XMLFILE_ENCODING_UTF8 = 0, + XMLFILE_ENCODING_UNICODE = 1, + XMLFILE_ENCODING_ASNI = 2, + }; + + class CMarkup; + class CMarkupNode; + + + class UILIB_API CMarkup { + friend class CMarkupNode; + public: + CMarkup (string_view_t pstrXML = _T ("")); + virtual ~CMarkup (); + + bool Load (string_view_t pstrXML); + bool LoadFromMem (BYTE* pByte, DWORD dwSize, int encoding = XMLFILE_ENCODING_UTF8); + bool LoadFromFile (string_view_t pstrFilename, int encoding = XMLFILE_ENCODING_UTF8); + void Release (); + bool IsValid () const; + + void SetPreserveWhitespace (bool bPreserve = true); + string_view_t GetLastErrorMessage () const; + string_view_t GetLastErrorLocation () const; + + CMarkupNode GetRoot (); + + private: + typedef struct tagXMLELEMENT { + ULONG iStart; + ULONG iChild; + ULONG iNext; + ULONG iParent; + ULONG iData; + } XMLELEMENT; + + string_t m_pstrXML; + XMLELEMENT* m_pElements; + ULONG m_nElements; + ULONG m_nReservedElements; + string_t m_szErrorMsg; + string_t m_szErrorXML; + bool m_bPreserveWhitespace; + + private: + bool _Parse (); + bool _Parse (LPTSTR& pstrText, ULONG iParent); + XMLELEMENT* _ReserveElement (); + inline void _SkipWhitespace (LPTSTR& pstr) const; + inline void _SkipWhitespace (LPCTSTR& pstr) const; + inline void _SkipIdentifier (LPTSTR& pstr) const; + inline void _SkipIdentifier (LPCTSTR& pstr) const; + bool _ParseData (LPTSTR& pstrText, LPTSTR& pstrData, char cEnd); + void _ParseMetaChar (LPTSTR& pstrText, LPTSTR& pstrDest); + bool _ParseAttributes (LPTSTR& pstrText); + bool _Failed (string_view_t pstrError, string_view_t pstrLocation = _T ("")); + }; + + + class UILIB_API CMarkupNode { + friend class CMarkup; + private: + CMarkupNode (); + CMarkupNode (CMarkup* pOwner, int iPos); + + public: + bool IsValid () const; + + CMarkupNode GetParent (); + CMarkupNode GetSibling (); + CMarkupNode GetChild (); + CMarkupNode GetChild (string_view_t pstrName); + + bool HasSiblings () const; + bool HasChildren () const; + string_t GetName () const; + string_t GetValue () const; + + bool HasAttributes (); + bool HasAttribute (string_view_t pstrName); + int GetAttributeCount (); + string_t GetAttributeName (int iIndex); + string_t GetAttributeValue (int iIndex); + string_t GetAttributeValue (string_view_t pstrName); + + private: + void _MapAttributes (); + + enum { MAX_XML_ATTRIBUTES = 64 }; + + typedef struct { + ULONG iName; + ULONG iValue; + } XMLATTRIBUTE; + + int m_iPos; + int m_nAttributes; + XMLATTRIBUTE m_aAttributes[MAX_XML_ATTRIBUTES]; + CMarkup* m_pOwner; + }; + +} // namespace DuiLib + +#endif // __UIMARKUP_H__ diff --git a/DuiLib/Core/UIRender.cpp b/DuiLib/Core/UIRender.cpp new file mode 100644 index 0000000..1bee18b --- /dev/null +++ b/DuiLib/Core/UIRender.cpp @@ -0,0 +1,2320 @@ +#include "StdAfx.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "../Utils/stb_image.h" + +#ifdef USE_XIMAGE_EFFECT +# include "../3rd/CxImage/ximage.h" +# include "../3rd/CxImage/ximage.cpp" +# include "../3rd/CxImage/ximaenc.cpp" +# include "../3rd/CxImage/ximagif.cpp" +# include "../3rd/CxImage/ximainfo.cpp" +# include "../3rd/CxImage/ximalpha.cpp" +# include "../3rd/CxImage/ximapal.cpp" +# include "../3rd/CxImage/ximatran.cpp" +# include "../3rd/CxImage/ximawnd.cpp" +# include "../3rd/CxImage/xmemfile.cpp" +#endif + +/////////////////////////////////////////////////////////////////////////////////////// +namespace DuiLib { + static int g_iFontID = MAX_FONT_ID; + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + CRenderClip::~CRenderClip () { + ASSERT (::GetObjectType (hDC) == OBJ_DC || ::GetObjectType (hDC) == OBJ_MEMDC); + ASSERT (::GetObjectType (hRgn) == OBJ_REGION); + ASSERT (::GetObjectType (hOldRgn) == OBJ_REGION); + ::SelectClipRgn (hDC, hOldRgn); + ::DeleteObject (hOldRgn); + ::DeleteObject (hRgn); + } + + void CRenderClip::GenerateClip (HDC hDC, RECT rc, CRenderClip& clip) { + RECT rcClip = { 0 }; + ::GetClipBox (hDC, &rcClip); + clip.hOldRgn = ::CreateRectRgnIndirect (&rcClip); + clip.hRgn = ::CreateRectRgnIndirect (&rc); + ::CombineRgn (clip.hRgn, clip.hRgn, clip.hOldRgn, RGN_AND); + ::SelectClipRgn (hDC, clip.hRgn); + clip.hDC = hDC; + clip.rcItem = rc; + } + + void CRenderClip::GenerateRoundClip (HDC hDC, RECT rc, RECT rcItem, int width, int height, CRenderClip& clip) { + RECT rcClip = { 0 }; + ::GetClipBox (hDC, &rcClip); + clip.hOldRgn = ::CreateRectRgnIndirect (&rcClip); + clip.hRgn = ::CreateRectRgnIndirect (&rc); + HRGN hRgnItem = ::CreateRoundRectRgn (rcItem.left, rcItem.top, rcItem.right + 1, rcItem.bottom + 1, width, height); + ::CombineRgn (clip.hRgn, clip.hRgn, hRgnItem, RGN_AND); + ::CombineRgn (clip.hRgn, clip.hRgn, clip.hOldRgn, RGN_AND); + ::SelectClipRgn (hDC, clip.hRgn); + clip.hDC = hDC; + clip.rcItem = rc; + ::DeleteObject (hRgnItem); + } + + void CRenderClip::UseOldClipBegin (HDC hDC, CRenderClip& clip) { + ::SelectClipRgn (hDC, clip.hOldRgn); + } + + void CRenderClip::UseOldClipEnd (HDC hDC, CRenderClip& clip) { + ::SelectClipRgn (hDC, clip.hRgn); + } + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + static const float OneThird = 1.0f / 3; + + static void RGBtoHSL (DWORD ARGB, float* H, float* S, float* L) { + const float + R = (float) GetRValue (ARGB), + G = (float) GetGValue (ARGB), + B = (float) GetBValue (ARGB), + nR = (R < 0 ? 0 : (R > 255 ? 255 : R)) / 255, + nG = (G < 0 ? 0 : (G > 255 ? 255 : G)) / 255, + nB = (B < 0 ? 0 : (B > 255 ? 255 : B)) / 255, + m = min (min (nR, nG), nB), + M = max (max (nR, nG), nB); + *L = (m + M) / 2; + if (M == m) *H = *S = 0; + else { + const float + f = (nR == m) ? (nG - nB) : ((nG == m) ? (nB - nR) : (nR - nG)), + i = (nR == m) ? 3.0f : ((nG == m) ? 5.0f : 1.0f); + *H = (i - f / (M - m)); + if (*H >= 6) *H -= 6; + *H *= 60; + *S = (2 * (*L) <= 1) ? ((M - m) / (M + m)) : ((M - m) / (2 - M - m)); + } + } + + static void HSLtoRGB (DWORD* ARGB, float H, float S, float L) { + const float + q = 2 * L < 1 ? L * (1 + S) : (L + S - L * S), + p = 2 * L - q, + h = H / 360, + tr = h + OneThird, + tg = h, + tb = h - OneThird, + ntr = tr < 0 ? tr + 1 : (tr > 1 ? tr - 1 : tr), + ntg = tg < 0 ? tg + 1 : (tg > 1 ? tg - 1 : tg), + ntb = tb < 0 ? tb + 1 : (tb > 1 ? tb - 1 : tb), + B = 255 * (6 * ntr < 1 ? p + (q - p) * 6 * ntr : (2 * ntr < 1 ? q : (3 * ntr < 2 ? p + (q - p) * 6 * (2.0f*OneThird - ntr) : p))), + G = 255 * (6 * ntg < 1 ? p + (q - p) * 6 * ntg : (2 * ntg < 1 ? q : (3 * ntg < 2 ? p + (q - p) * 6 * (2.0f*OneThird - ntg) : p))), + R = 255 * (6 * ntb < 1 ? p + (q - p) * 6 * ntb : (2 * ntb < 1 ? q : (3 * ntb < 2 ? p + (q - p) * 6 * (2.0f*OneThird - ntb) : p))); + *ARGB &= 0xFF000000; + *ARGB |= RGB ((BYTE) (R < 0 ? 0 : (R > 255 ? 255 : R)), (BYTE) (G < 0 ? 0 : (G > 255 ? 255 : G)), (BYTE) (B < 0 ? 0 : (B > 255 ? 255 : B))); + } + + static COLORREF PixelAlpha (COLORREF clrSrc, double src_darken, COLORREF clrDest, double dest_darken) { + return RGB (GetRValue (clrSrc) * src_darken + GetRValue (clrDest) * dest_darken, + GetGValue (clrSrc) * src_darken + GetGValue (clrDest) * dest_darken, + GetBValue (clrSrc) * src_darken + GetBValue (clrDest) * dest_darken); + } + + static BOOL WINAPI AlphaBitBlt (HDC hDC, int nDestX, int nDestY, int dwWidth, int dwHeight, HDC hSrcDC, \ + int nSrcX, int nSrcY, int wSrc, int hSrc, BLENDFUNCTION ftn) { + HDC hTempDC = ::CreateCompatibleDC (hDC); + if (!hTempDC) + return FALSE; + + //Creates Source DIB + LPBITMAPINFO lpbiSrc = nullptr; + // Fill in the BITMAPINFOHEADER + lpbiSrc = (LPBITMAPINFO) new BYTE[sizeof (BITMAPINFOHEADER)]; + if (!lpbiSrc) { + ::DeleteDC (hTempDC); + return FALSE; + } + lpbiSrc->bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + lpbiSrc->bmiHeader.biWidth = dwWidth; + lpbiSrc->bmiHeader.biHeight = dwHeight; + lpbiSrc->bmiHeader.biPlanes = 1; + lpbiSrc->bmiHeader.biBitCount = 32; + lpbiSrc->bmiHeader.biCompression = BI_RGB; + lpbiSrc->bmiHeader.biSizeImage = dwWidth * dwHeight; + lpbiSrc->bmiHeader.biXPelsPerMeter = 0; + lpbiSrc->bmiHeader.biYPelsPerMeter = 0; + lpbiSrc->bmiHeader.biClrUsed = 0; + lpbiSrc->bmiHeader.biClrImportant = 0; + + COLORREF* pSrcBits = nullptr; + HBITMAP hSrcDib = CreateDIBSection (hSrcDC, lpbiSrc, DIB_RGB_COLORS, (void **) &pSrcBits, nullptr, 0); + + if (!hSrcDib || !pSrcBits) { + delete[] lpbiSrc; + ::DeleteDC (hTempDC); + return FALSE; + } + + HBITMAP hOldTempBmp = (HBITMAP)::SelectObject (hTempDC, hSrcDib); + ::StretchBlt (hTempDC, 0, 0, dwWidth, dwHeight, hSrcDC, nSrcX, nSrcY, wSrc, hSrc, SRCCOPY); + ::SelectObject (hTempDC, hOldTempBmp); + + //Creates Destination DIB + LPBITMAPINFO lpbiDest = nullptr; + // Fill in the BITMAPINFOHEADER + lpbiDest = (LPBITMAPINFO) new BYTE[sizeof (BITMAPINFOHEADER)]; + if (!lpbiDest) { + delete[] lpbiSrc; + ::DeleteObject (hSrcDib); + ::DeleteDC (hTempDC); + return FALSE; + } + + lpbiDest->bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + lpbiDest->bmiHeader.biWidth = dwWidth; + lpbiDest->bmiHeader.biHeight = dwHeight; + lpbiDest->bmiHeader.biPlanes = 1; + lpbiDest->bmiHeader.biBitCount = 32; + lpbiDest->bmiHeader.biCompression = BI_RGB; + lpbiDest->bmiHeader.biSizeImage = dwWidth * dwHeight; + lpbiDest->bmiHeader.biXPelsPerMeter = 0; + lpbiDest->bmiHeader.biYPelsPerMeter = 0; + lpbiDest->bmiHeader.biClrUsed = 0; + lpbiDest->bmiHeader.biClrImportant = 0; + + COLORREF* pDestBits = nullptr; + HBITMAP hDestDib = CreateDIBSection ( + hDC, lpbiDest, DIB_RGB_COLORS, (void **) &pDestBits, + nullptr, 0); + + if (!hDestDib || !pDestBits) { + delete[] lpbiSrc; + ::DeleteObject (hSrcDib); + ::DeleteDC (hTempDC); + return FALSE; + } + + ::SelectObject (hTempDC, hDestDib); + ::BitBlt (hTempDC, 0, 0, dwWidth, dwHeight, hDC, nDestX, nDestY, SRCCOPY); + ::SelectObject (hTempDC, hOldTempBmp); + + double src_darken; + BYTE nAlpha; + + for (int pixel = 0; pixel < dwWidth * dwHeight; pixel++, pSrcBits++, pDestBits++) { + nAlpha = LOBYTE (*pSrcBits >> 24); + src_darken = (double) (nAlpha * ftn.SourceConstantAlpha) / 255.0 / 255.0; + if (src_darken < 0.0) src_darken = 0.0; + *pDestBits = PixelAlpha (*pSrcBits, src_darken, *pDestBits, 1.0 - src_darken); + } //for + + ::SelectObject (hTempDC, hDestDib); + ::BitBlt (hDC, nDestX, nDestY, dwWidth, dwHeight, hTempDC, 0, 0, SRCCOPY); + ::SelectObject (hTempDC, hOldTempBmp); + + delete[] lpbiDest; + ::DeleteObject (hDestDib); + + delete[] lpbiSrc; + ::DeleteObject (hSrcDib); + + ::DeleteDC (hTempDC); + return TRUE; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + bool DrawImage (HDC hDC, CPaintManagerUI* pManager, const RECT& rc, const RECT& rcPaint, const CDuiString& sImageName, \ + const CDuiString& sImageResType, RECT rcItem, RECT rcBmpPart, RECT rcCorner, DWORD dwMask, BYTE bFade, \ + bool bHole, bool bTiledX, bool bTiledY, HINSTANCE instance = nullptr) { + if (sImageName.empty ()) { + return false; + } + const TImageInfo* data = nullptr; + if (sImageResType.empty ()) { + data = pManager->GetImageEx (sImageName, _T (""), dwMask, false, instance); + } else { + data = pManager->GetImageEx (sImageName, sImageResType, dwMask, false, instance); + } + if (!data) return false; + + if (rcBmpPart.left == 0 && rcBmpPart.right == 0 && rcBmpPart.top == 0 && rcBmpPart.bottom == 0) { + rcBmpPart.right = data->nX; + rcBmpPart.bottom = data->nY; + } + if (rcBmpPart.right > data->nX) rcBmpPart.right = data->nX; + if (rcBmpPart.bottom > data->nY) rcBmpPart.bottom = data->nY; + + RECT rcTemp = { 0 }; + if (!::IntersectRect (&rcTemp, &rcItem, &rc)) return true; + if (!::IntersectRect (&rcTemp, &rcItem, &rcPaint)) return true; + + HBITMAP hBmp = (data->hBitmap ? data->hBitmap : *(data->phBitmap)); + CRenderEngine::DrawImage (hDC, hBmp, rcItem, rcPaint, rcBmpPart, rcCorner, pManager->IsLayered () ? true : data->bAlpha, bFade, bHole, bTiledX, bTiledY); + + return true; + } + + DWORD CRenderEngine::AdjustColor (DWORD dwColor, short H, short S, short L) { + if (H == 180 && S == 100 && L == 100) return dwColor; + float fH, fS, fL; + float S1 = S / 100.0f; + float L1 = L / 100.0f; + RGBtoHSL (dwColor, &fH, &fS, &fL); + fH += (H - 180); + fH = fH > 0 ? fH : fH + 360; + fS *= S1; + fL *= L1; + HSLtoRGB (&dwColor, fH, fS, fL); + return dwColor; + } + + TImageInfo* CRenderEngine::LoadImage (std::variant bitmap, string_view_t type, DWORD mask, HINSTANCE instance) { + LPBYTE pData = nullptr; + DWORD dwSize = 0; + do { + if (type.empty ()) { + CDuiString sFile = CPaintManagerUI::GetResourcePath (); + if (CPaintManagerUI::GetResourceZip ().empty ()) { + sFile += std::get<1> (bitmap); + HANDLE hFile = ::CreateFile (sFile.c_str (), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, \ + FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) break; + dwSize = ::GetFileSize (hFile, nullptr); + if (dwSize == 0) break; + + DWORD dwRead = 0; + pData = new BYTE[dwSize]; + ::ReadFile (hFile, pData, dwSize, &dwRead, nullptr); + ::CloseHandle (hFile); + + if (dwRead != dwSize) { + delete[] pData; + pData = nullptr; + break; + } + } else { + sFile += CPaintManagerUI::GetResourceZip (); + string_view_t sFilePwd = CPaintManagerUI::GetResourceZipPwd (); //Garfield 20160325 zip + HZIP hz = nullptr; + if (CPaintManagerUI::IsCachedResourceZip ()) hz = (HZIP) CPaintManagerUI::GetResourceZipHandle (); + else { + std::string_view pwd = FawTools::get_gb18030 (sFilePwd); + hz = OpenZip (sFile.c_str (), pwd.data ()); + } + if (!hz) break; + ZIPENTRY ze; + int i = 0; + CDuiString key = std::get<1> (bitmap); + key.Replace (_T ("\\"), _T ("/")); + if (FindZipItem (hz, key.c_str (), true, &i, &ze) != 0) break; + dwSize = ze.unc_size; + if (dwSize == 0) break; + pData = new BYTE[dwSize]; + int res = UnzipItem (hz, i, pData, dwSize); + if (res != 0x00000000 && res != 0x00000600) { + delete[] pData; + pData = nullptr; + if (!CPaintManagerUI::IsCachedResourceZip ()) CloseZip (hz); + break; + } + if (!CPaintManagerUI::IsCachedResourceZip ()) CloseZip (hz); + } + } else { + HINSTANCE dllinstance = nullptr; + if (instance) { + dllinstance = instance; + } else { + dllinstance = CPaintManagerUI::GetResourceDll (); + } + HRSRC hResource = ::FindResource (dllinstance, MAKEINTRESOURCE (std::get<0> (bitmap)), type.data ()); + if (!hResource) break; + HGLOBAL hGlobal = ::LoadResource (dllinstance, hResource); + if (!hGlobal) { + FreeResource (hResource); + break; + } + + dwSize = ::SizeofResource (dllinstance, hResource); + if (dwSize == 0) break; + pData = new BYTE[dwSize]; + ::CopyMemory (pData, (LPBYTE)::LockResource (hGlobal), dwSize); + ::FreeResource (hResource); + } + } while (0); + + while (!pData) { + //ͼƬ, ֱȥȡbitmap.m_lpstrָ· + HANDLE hFile = ::CreateFile (std::get<1> (bitmap).c_str (), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, \ + FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) break; + dwSize = ::GetFileSize (hFile, nullptr); + if (dwSize == 0) break; + + DWORD dwRead = 0; + pData = new BYTE[dwSize]; + ::ReadFile (hFile, pData, dwSize, &dwRead, nullptr); + ::CloseHandle (hFile); + + if (dwRead != dwSize) { + delete[] pData; + pData = nullptr; + } + break; + } + if (!pData) { + //::MessageBox(0, _T("ȡͼƬʧܣ"), _T("ץBUG"), MB_OK); + return nullptr; + } + + LPBYTE pImage = nullptr; + int x, y, n; + pImage = stbi_load_from_memory (pData, dwSize, &x, &y, &n, 4); + delete[] pData; + if (!pImage) { + //::MessageBox(0, _T("ͼƬʧ"), _T("ץBUG"), MB_OK); + return nullptr; + } + + BITMAPINFO bmi; + ::ZeroMemory (&bmi, sizeof (BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = x; + bmi.bmiHeader.biHeight = -y; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = x * y * 4; + + bool bAlphaChannel = false; + LPBYTE pDest = nullptr; + HBITMAP hBitmap = ::CreateDIBSection (nullptr, &bmi, DIB_RGB_COLORS, (void**) &pDest, nullptr, 0); + if (!hBitmap) { + //::MessageBox(0, _T("CreateDIBSectionʧ"), _T("ץBUG"), MB_OK); + return nullptr; + } + + for (int i = 0; i < x * y; i++) { + pDest[i * 4 + 3] = pImage[i * 4 + 3]; + if (pDest[i * 4 + 3] < 255) { + pDest[i * 4] = (BYTE) (DWORD (pImage[i * 4 + 2])*pImage[i * 4 + 3] / 255); + pDest[i * 4 + 1] = (BYTE) (DWORD (pImage[i * 4 + 1])*pImage[i * 4 + 3] / 255); + pDest[i * 4 + 2] = (BYTE) (DWORD (pImage[i * 4])*pImage[i * 4 + 3] / 255); + bAlphaChannel = true; + } else { + pDest[i * 4] = pImage[i * 4 + 2]; + pDest[i * 4 + 1] = pImage[i * 4 + 1]; + pDest[i * 4 + 2] = pImage[i * 4]; + } + + if (*(DWORD*) (&pDest[i * 4]) == mask) { + pDest[i * 4] = (BYTE) 0; + pDest[i * 4 + 1] = (BYTE) 0; + pDest[i * 4 + 2] = (BYTE) 0; + pDest[i * 4 + 3] = (BYTE) 0; + bAlphaChannel = true; + } + } + + stbi_image_free (pImage); + + TImageInfo* data = new TImageInfo; + data->pBits = nullptr; + data->pSrcBits = nullptr; + data->hBitmap = hBitmap; + data->phBitmap = nullptr; + data->nX = x; + data->nY = y; + data->bAlpha = bAlphaChannel; + return data; + } +#ifdef USE_XIMAGE_EFFECT + static DWORD LoadImage2Memory (const std::variant &bitmap, string_view_t type, LPBYTE &pData) { + assert (!pData); + pData = nullptr; + DWORD dwSize (0U); + do { + if (!type) { + CDuiString sFile = CPaintManagerUI::GetResourcePath (); + if (CPaintManagerUI::GetResourceZip ().empty ()) { + sFile += bitmap.m_lpstr; + HANDLE hFile = ::CreateFile (sFile, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, \ + FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) break; + dwSize = ::GetFileSize (hFile, nullptr); + if (dwSize == 0) break; + + DWORD dwRead = 0; + pData = new BYTE[dwSize + 1]; + memset (pData, 0, dwSize + 1); + ::ReadFile (hFile, pData, dwSize, &dwRead, nullptr); + ::CloseHandle (hFile); + + if (dwRead != dwSize) { + delete[] pData; + pData = nullptr; + dwSize = 0U; + break; + } + } else { + sFile += CPaintManagerUI::GetResourceZip (); + HZIP hz = nullptr; + if (CPaintManagerUI::IsCachedResourceZip ()) + hz = (HZIP) CPaintManagerUI::GetResourceZipHandle (); + else { + CDuiString sFilePwd = CPaintManagerUI::GetResourceZipPwd (); +#ifdef UNICODE + char* pwd = w2a ((wchar_t*) sFilePwd); + hz = OpenZip ((void*) sFile, pwd); + if (pwd) delete[] pwd; +#else + hz = OpenZip ((void*) sFile, sFilePwd); +#endif + } + if (!hz) break; + ZIPENTRY ze; + int i = 0; + CDuiString key = bitmap.m_lpstr; + key.Replace (_T ("\\"), _T ("/")); + if (FindZipItem (hz, key, true, &i, &ze) != 0) break; + dwSize = ze.unc_size; + if (dwSize == 0) break; + pData = new BYTE[dwSize]; + int res = UnzipItem (hz, i, pData, dwSize, 3); + if (res != 0x00000000 && res != 0x00000600) { + delete[] pData; + pData = nullptr; + dwSize = 0U; + if (!CPaintManagerUI::IsCachedResourceZip ()) + CloseZip (hz); + break; + } + if (!CPaintManagerUI::IsCachedResourceZip ()) + CloseZip (hz); + } + } else { + HINSTANCE hDll = CPaintManagerUI::GetResourceDll (); + HRSRC hResource = ::FindResource (hDll, bitmap.m_lpstr, type); + if (!hResource) break; + HGLOBAL hGlobal = ::LoadResource (hDll, hResource); + if (!hGlobal) { + FreeResource (hResource); + break; + } + + dwSize = ::SizeofResource (hDll, hResource); + if (dwSize == 0) break; + pData = new BYTE[dwSize]; + ::CopyMemory (pData, (LPBYTE)::LockResource (hGlobal), dwSize); + ::FreeResource (hResource); + } + } while (0); + + while (!pData) { + //ͼƬ, ֱȥȡbitmap.m_lpstrָ· + HANDLE hFile = ::CreateFile (bitmap.m_lpstr, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, \ + FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) break; + dwSize = ::GetFileSize (hFile, nullptr); + if (dwSize == 0) break; + + DWORD dwRead = 0; + pData = new BYTE[dwSize]; + ::ReadFile (hFile, pData, dwSize, &dwRead, nullptr); + ::CloseHandle (hFile); + + if (dwRead != dwSize) { + delete[] pData; + pData = nullptr; + dwSize = 0U; + } + break; + } + return dwSize; + } + CxImage* CRenderEngine::LoadGifImageX (std::variant bitmap, string_view_t type, DWORD mask) { + //write by wangji + LPBYTE pData = nullptr; + DWORD dwSize = LoadImage2Memory (bitmap, type, pData); + if (dwSize == 0U || !pData) + return nullptr; + CxImage * pImg = nullptr; + if (pImg = new CxImage ()) { + pImg->SetRetreiveAllFrames (TRUE); + if (!pImg->Decode (pData, dwSize, CXIMAGE_FORMAT_GIF)) { + delete pImg; + pImg = nullptrptr; + } + } + delete[] pData; + pData = nullptr; + return pImg; + } +#endif//USE_XIMAGE_EFFECT + + Gdiplus::Image* CRenderEngine::GdiplusLoadImage (string_view_t pstrPath) { + LPBYTE pData = nullptr; + DWORD dwSize = 0; + + do { + CDuiString sFile = CPaintManagerUI::GetResourcePath (); + if (CPaintManagerUI::GetResourceZip ().empty ()) { + sFile += pstrPath; + HANDLE hFile = ::CreateFile (sFile.c_str (), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, \ + FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) break; + dwSize = ::GetFileSize (hFile, nullptr); + if (dwSize == 0) break; + + DWORD dwRead = 0; + pData = new BYTE[dwSize]; + ::ReadFile (hFile, pData, dwSize, &dwRead, nullptr); + ::CloseHandle (hFile); + + if (dwRead != dwSize) { + delete[] pData; + pData = nullptr; + break; + } + } else { + sFile += CPaintManagerUI::GetResourceZip (); + HZIP hz = nullptr; + if (CPaintManagerUI::IsCachedResourceZip ()) hz = (HZIP) CPaintManagerUI::GetResourceZipHandle (); + else { + CDuiString sFilePwd = CPaintManagerUI::GetResourceZipPwd (); + std::string pwd = FawTools::get_gb18030 (sFilePwd); + hz = OpenZip (sFile.c_str (), pwd.c_str ()); + } + if (!hz) break; + ZIPENTRY ze; + int i = 0; + CDuiString key = pstrPath; + key.Replace (_T ("\\"), _T ("/")); + if (FindZipItem (hz, key.c_str (), true, &i, &ze) != 0) break; + dwSize = ze.unc_size; + if (dwSize == 0) break; + pData = new BYTE[dwSize]; + int res = UnzipItem (hz, i, pData, dwSize); + if (res != 0x00000000 && res != 0x00000600) { + delete[] pData; + pData = nullptr; + if (!CPaintManagerUI::IsCachedResourceZip ()) CloseZip (hz); + break; + } + if (!CPaintManagerUI::IsCachedResourceZip ()) CloseZip (hz); + } + + } while (0); + + while (!pData) { + //ͼƬ, ֱȥȡbitmap.m_lpstrָ· + HANDLE hFile = ::CreateFile (pstrPath.data (), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) break; + dwSize = ::GetFileSize (hFile, nullptr); + if (dwSize == 0) break; + + DWORD dwRead = 0; + pData = new BYTE[dwSize]; + ::ReadFile (hFile, pData, dwSize, &dwRead, nullptr); + ::CloseHandle (hFile); + + if (dwRead != dwSize) { + delete[] pData; + pData = nullptr; + } + break; + } + + Gdiplus::Image* pImage = nullptr; + if (pData) { + pImage = GdiplusLoadImage (pData, dwSize); + delete[] pData; + pData = nullptr; + } + return pImage; + } + + Gdiplus::Image* CRenderEngine::GdiplusLoadImage (LPVOID pBuf, size_t dwSize) { + HGLOBAL hMem = ::GlobalAlloc (GMEM_FIXED, dwSize); + BYTE* pMem = (BYTE*)::GlobalLock (hMem); + memcpy (pMem, pBuf, dwSize); + IStream* pStm = nullptr; + ::CreateStreamOnHGlobal (hMem, TRUE, &pStm); + Gdiplus::Image *pImg = Gdiplus::Image::FromStream (pStm); + if (!pImg || pImg->GetLastStatus () != Gdiplus::Ok) { + pStm->Release (); + ::GlobalUnlock (hMem); + return 0; + } + return pImg; + } + + void CRenderEngine::FreeImage (TImageInfo* bitmap, bool bDelete) { + if (!bitmap) return; + if (bitmap->hBitmap) { + ::DeleteObject (bitmap->hBitmap); + } + bitmap->hBitmap = nullptr; + // bitmap->phBitmap ͷ + bitmap->phBitmap = nullptr; + if (bitmap->pBits) { + delete[] bitmap->pBits; + } + bitmap->pBits = nullptr; + if (bitmap->pSrcBits) { + delete[] bitmap->pSrcBits; + } + bitmap->pSrcBits = nullptr; + if (bDelete) { + delete bitmap; + bitmap = nullptr; + } + } + + + bool CRenderEngine::DrawIconImageString (HDC hDC, CPaintManagerUI* pManager, const RECT& rc, const RECT& rcPaint, string_view_t pStrImage, string_view_t pStrModify) { + if (!pManager || !hDC) + return false; + + // 1aaa.jpg + // 2file='aaa.jpg' res='' restype='0' dest='0,0,0,0' source='0,0,0,0' corner='0,0,0,0' + // mask='#FF0000' fade='255' hole='FALSE' xtiled='FALSE' ytiled='FALSE' + + CDuiString sImageName = pStrImage; + CDuiString sImageResType; + RECT rcItem = rc; + RECT rcBmpPart = { 0 }; + RECT rcCorner = { 0 }; + DWORD dwMask = 0; + BYTE bFade = 0xFF; + bool bHole = false; + bool bTiledX = true; + bool bTiledY = true; + SIZE szIcon { 0, 0 }; + + int image_count = 0; + + for (int i = 0; i < 2; ++i, image_count = 0) { + std::map m = FawTools::parse_keyvalue_pairs (i == 0 ? pStrImage : pStrModify); + for (auto[str_key, str_value] : m) { + if (str_key == _T ("file") || str_key == _T ("res")) { + if (image_count > 0) + DuiLib::DrawImage (hDC, pManager, rc, rcPaint, sImageName, sImageResType, + rcItem, rcBmpPart, rcCorner, dwMask, bFade, bHole, bTiledX, bTiledY); + sImageName = str_value; + ++image_count; + } else if (str_key == _T ("restype")) { + if (image_count > 0) + DuiLib::DrawImage (hDC, pManager, rc, rcPaint, sImageName, sImageResType, + rcItem, rcBmpPart, rcCorner, dwMask, bFade, bHole, bTiledX, bTiledY); + sImageResType = str_value; + ++image_count; + } else if (str_key == _T ("dest")) { + rcItem = FawTools::parse_rect (str_value); + rcItem.left += rc.left; + rcItem.top += rc.top; + rcItem.right += rc.left; + if (rcItem.right > rc.right) + rcItem.right = rc.right; + rcItem.bottom += rc.top; + if (rcItem.bottom > rc.bottom) + rcItem.bottom = rc.bottom; + } else if (str_key == _T ("source")) { + rcBmpPart = FawTools::parse_rect (str_value); + } else if (str_key == _T ("corner")) { + rcCorner = FawTools::parse_rect (str_value); + } else if (str_key == _T ("mask")) { + dwMask = static_cast (FawTools::parse_hex (str_value)); + } else if (str_key == _T ("fade")) { + bFade = (BYTE) _ttoi (str_value.c_str ()); + } else if (str_key == _T ("hole")) { + bHole = FawTools::parse_bool (str_value); + } else if (str_key == _T ("xtiled")) { + bTiledX = FawTools::parse_bool (str_value); + } else if (str_key == _T ("ytiled")) { + bTiledY = FawTools::parse_bool (str_value); + } else if (str_key == _T ("iconsize")) { + szIcon = FawTools::parse_size (str_value); + } else if (str_key == _T ("iconalign")) { + MakeFitIconDest (rcItem, szIcon, str_value, rcItem); + } + } + } + + DuiLib::DrawImage (hDC, pManager, rc, rcPaint, sImageName, sImageResType, rcItem, rcBmpPart, rcCorner, dwMask, bFade, bHole, bTiledX, bTiledY); + + return true; + } + + bool CRenderEngine::MakeFitIconDest (const RECT& rcControl, const SIZE& szIcon, const CDuiString& sAlign, RECT& rcDest) { + ASSERT (!sAlign.empty ()); + if (sAlign == _T ("left")) { + rcDest.left = rcControl.left; + rcDest.top = rcControl.top; + rcDest.right = rcDest.left + szIcon.cx; + rcDest.bottom = rcDest.top + szIcon.cy; + } else if (sAlign == _T ("center")) { + rcDest.left = rcControl.left + ((rcControl.right - rcControl.left) - szIcon.cx) / 2; + rcDest.top = rcControl.top + ((rcControl.bottom - rcControl.top) - szIcon.cy) / 2; + rcDest.right = rcDest.left + szIcon.cx; + rcDest.bottom = rcDest.top + szIcon.cy; + } else if (sAlign == _T ("vcenter")) { + rcDest.left = rcControl.left; + rcDest.top = rcControl.top + ((rcControl.bottom - rcControl.top) - szIcon.cy) / 2; + rcDest.right = rcDest.left + szIcon.cx; + rcDest.bottom = rcDest.top + szIcon.cy; + } else if (sAlign == _T ("hcenter")) { + rcDest.left = rcControl.left + ((rcControl.right - rcControl.left) - szIcon.cx) / 2; + rcDest.top = rcControl.top; + rcDest.right = rcDest.left + szIcon.cx; + rcDest.bottom = rcDest.top + szIcon.cy; + } + + if (rcDest.right > rcControl.right) + rcDest.right = rcControl.right; + + if (rcDest.bottom > rcControl.bottom) + rcDest.bottom = rcControl.bottom; + + return true; + } + + TImageInfo* CRenderEngine::LoadImage (string_view_t pStrImage, string_view_t type, DWORD mask, HINSTANCE instance) { + if (pStrImage.empty ()) return nullptr; + + string_view_t sStrPath = pStrImage; + if (type.empty ()) { + sStrPath = CResourceManager::GetInstance ()->GetImagePath (pStrImage); + if (sStrPath.empty ()) sStrPath = pStrImage; + else { + /*if (CResourceManager::GetInstance()->GetScale() != 100) { + CDuiString sScale; + sScale.Format(_T("@%d."), CResourceManager::GetInstance()->GetScale()); + sStrPath.Replace(_T("."), sScale); + }*/ + } + } + return LoadImage (std::variant (sStrPath.data ()), type, mask, instance); + } + + TImageInfo* CRenderEngine::LoadImage (UINT nID, string_view_t type, DWORD mask, HINSTANCE instance) { + return LoadImage (std::variant (nID), type, mask, instance); + } + + void CRenderEngine::DrawText (HDC hDC, CPaintManagerUI* pManager, RECT& rc, string_view_t pstrText, DWORD dwTextColor, \ + int iFont, UINT uStyle, DWORD dwTextBKColor) { + ASSERT (::GetObjectType (hDC) == OBJ_DC || ::GetObjectType (hDC) == OBJ_MEMDC); + if (pstrText.empty () || !pManager) return; + DrawColor (hDC, rc, dwTextBKColor); + DrawText (hDC, pManager, rc, pstrText, dwTextColor, iFont, uStyle); + } + + void CRenderEngine::DrawImage (HDC hDC, HBITMAP hBitmap, const RECT& rc, const RECT& rcPaint, + const RECT& rcBmpPart, const RECT& rcCorners, bool bAlpha, + BYTE uFade, bool hole, bool xtiled, bool ytiled) { + ASSERT (::GetObjectType (hDC) == OBJ_DC || ::GetObjectType (hDC) == OBJ_MEMDC); + + typedef BOOL (WINAPI *LPALPHABLEND)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION); + static LPALPHABLEND lpAlphaBlend = (LPALPHABLEND) ::GetProcAddress (::GetModuleHandle (_T ("msimg32.dll")), "AlphaBlend"); + + if (!lpAlphaBlend) lpAlphaBlend = AlphaBitBlt; + if (!hBitmap) return; + + HDC hCloneDC = ::CreateCompatibleDC (hDC); + HBITMAP hOldBitmap = (HBITMAP) ::SelectObject (hCloneDC, hBitmap); + ::SetStretchBltMode (hDC, HALFTONE); + + RECT rcTemp = { 0 }; + RECT rcDest = { 0 }; + if (lpAlphaBlend && (bAlpha || uFade < 255)) { + BLENDFUNCTION bf = { AC_SRC_OVER, 0, uFade, AC_SRC_ALPHA }; + // middle + if (!hole) { + rcDest.left = rc.left + rcCorners.left; + rcDest.top = rc.top + rcCorners.top; + rcDest.right = rc.right - rc.left - rcCorners.left - rcCorners.right; + rcDest.bottom = rc.bottom - rc.top - rcCorners.top - rcCorners.bottom; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + if (!xtiled && !ytiled) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + lpAlphaBlend (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.left + rcCorners.left, rcBmpPart.top + rcCorners.top, \ + rcBmpPart.right - rcBmpPart.left - rcCorners.left - rcCorners.right, \ + rcBmpPart.bottom - rcBmpPart.top - rcCorners.top - rcCorners.bottom, bf); + } else if (xtiled && ytiled) { + LONG lWidth = rcBmpPart.right - rcBmpPart.left - rcCorners.left - rcCorners.right; + LONG lHeight = rcBmpPart.bottom - rcBmpPart.top - rcCorners.top - rcCorners.bottom; + int iTimesX = (rcDest.right - rcDest.left + lWidth - 1) / lWidth; + int iTimesY = (rcDest.bottom - rcDest.top + lHeight - 1) / lHeight; + for (int j = 0; j < iTimesY; ++j) { + LONG lDestTop = rcDest.top + lHeight * j; + LONG lDestBottom = rcDest.top + lHeight * (j + 1); + LONG lDrawHeight = lHeight; + if (lDestBottom > rcDest.bottom) { + lDrawHeight -= lDestBottom - rcDest.bottom; + lDestBottom = rcDest.bottom; + } + for (int i = 0; i < iTimesX; ++i) { + LONG lDestLeft = rcDest.left + lWidth * i; + LONG lDestRight = rcDest.left + lWidth * (i + 1); + LONG lDrawWidth = lWidth; + if (lDestRight > rcDest.right) { + lDrawWidth -= lDestRight - rcDest.right; + lDestRight = rcDest.right; + } + lpAlphaBlend (hDC, rcDest.left + lWidth * i, rcDest.top + lHeight * j, + lDestRight - lDestLeft, lDestBottom - lDestTop, hCloneDC, + rcBmpPart.left + rcCorners.left, rcBmpPart.top + rcCorners.top, lDrawWidth, lDrawHeight, bf); + } + } + } else if (xtiled) { + LONG lWidth = rcBmpPart.right - rcBmpPart.left - rcCorners.left - rcCorners.right; + int iTimes = (rcDest.right - rcDest.left + lWidth - 1) / lWidth; + for (int i = 0; i < iTimes; ++i) { + LONG lDestLeft = rcDest.left + lWidth * i; + LONG lDestRight = rcDest.left + lWidth * (i + 1); + LONG lDrawWidth = lWidth; + if (lDestRight > rcDest.right) { + lDrawWidth -= lDestRight - rcDest.right; + lDestRight = rcDest.right; + } + lpAlphaBlend (hDC, lDestLeft, rcDest.top, lDestRight - lDestLeft, rcDest.bottom, + hCloneDC, rcBmpPart.left + rcCorners.left, rcBmpPart.top + rcCorners.top, \ + lDrawWidth, rcBmpPart.bottom - rcBmpPart.top - rcCorners.top - rcCorners.bottom, bf); + } + } else { // ytiled + LONG lHeight = rcBmpPart.bottom - rcBmpPart.top - rcCorners.top - rcCorners.bottom; + int iTimes = (rcDest.bottom - rcDest.top + lHeight - 1) / lHeight; + for (int i = 0; i < iTimes; ++i) { + LONG lDestTop = rcDest.top + lHeight * i; + LONG lDestBottom = rcDest.top + lHeight * (i + 1); + LONG lDrawHeight = lHeight; + if (lDestBottom > rcDest.bottom) { + lDrawHeight -= lDestBottom - rcDest.bottom; + lDestBottom = rcDest.bottom; + } + lpAlphaBlend (hDC, rcDest.left, rcDest.top + lHeight * i, rcDest.right, lDestBottom - lDestTop, + hCloneDC, rcBmpPart.left + rcCorners.left, rcBmpPart.top + rcCorners.top, \ + rcBmpPart.right - rcBmpPart.left - rcCorners.left - rcCorners.right, lDrawHeight, bf); + } + } + } + } + + // left-top + if (rcCorners.left > 0 && rcCorners.top > 0) { + rcDest.left = rc.left; + rcDest.top = rc.top; + rcDest.right = rcCorners.left; + rcDest.bottom = rcCorners.top; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + lpAlphaBlend (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.left, rcBmpPart.top, rcCorners.left, rcCorners.top, bf); + } + } + // top + if (rcCorners.top > 0) { + rcDest.left = rc.left + rcCorners.left; + rcDest.top = rc.top; + rcDest.right = rc.right - rc.left - rcCorners.left - rcCorners.right; + rcDest.bottom = rcCorners.top; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + lpAlphaBlend (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.left + rcCorners.left, rcBmpPart.top, rcBmpPart.right - rcBmpPart.left - \ + rcCorners.left - rcCorners.right, rcCorners.top, bf); + } + } + // right-top + if (rcCorners.right > 0 && rcCorners.top > 0) { + rcDest.left = rc.right - rcCorners.right; + rcDest.top = rc.top; + rcDest.right = rcCorners.right; + rcDest.bottom = rcCorners.top; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + lpAlphaBlend (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.right - rcCorners.right, rcBmpPart.top, rcCorners.right, rcCorners.top, bf); + } + } + // left + if (rcCorners.left > 0) { + rcDest.left = rc.left; + rcDest.top = rc.top + rcCorners.top; + rcDest.right = rcCorners.left; + rcDest.bottom = rc.bottom - rc.top - rcCorners.top - rcCorners.bottom; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + lpAlphaBlend (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.left, rcBmpPart.top + rcCorners.top, rcCorners.left, rcBmpPart.bottom - \ + rcBmpPart.top - rcCorners.top - rcCorners.bottom, bf); + } + } + // right + if (rcCorners.right > 0) { + rcDest.left = rc.right - rcCorners.right; + rcDest.top = rc.top + rcCorners.top; + rcDest.right = rcCorners.right; + rcDest.bottom = rc.bottom - rc.top - rcCorners.top - rcCorners.bottom; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + lpAlphaBlend (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.right - rcCorners.right, rcBmpPart.top + rcCorners.top, rcCorners.right, \ + rcBmpPart.bottom - rcBmpPart.top - rcCorners.top - rcCorners.bottom, bf); + } + } + // left-bottom + if (rcCorners.left > 0 && rcCorners.bottom > 0) { + rcDest.left = rc.left; + rcDest.top = rc.bottom - rcCorners.bottom; + rcDest.right = rcCorners.left; + rcDest.bottom = rcCorners.bottom; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + lpAlphaBlend (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.left, rcBmpPart.bottom - rcCorners.bottom, rcCorners.left, rcCorners.bottom, bf); + } + } + // bottom + if (rcCorners.bottom > 0) { + rcDest.left = rc.left + rcCorners.left; + rcDest.top = rc.bottom - rcCorners.bottom; + rcDest.right = rc.right - rc.left - rcCorners.left - rcCorners.right; + rcDest.bottom = rcCorners.bottom; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + lpAlphaBlend (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.left + rcCorners.left, rcBmpPart.bottom - rcCorners.bottom, \ + rcBmpPart.right - rcBmpPart.left - rcCorners.left - rcCorners.right, rcCorners.bottom, bf); + } + } + // right-bottom + if (rcCorners.right > 0 && rcCorners.bottom > 0) { + rcDest.left = rc.right - rcCorners.right; + rcDest.top = rc.bottom - rcCorners.bottom; + rcDest.right = rcCorners.right; + rcDest.bottom = rcCorners.bottom; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + lpAlphaBlend (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.right - rcCorners.right, rcBmpPart.bottom - rcCorners.bottom, rcCorners.right, \ + rcCorners.bottom, bf); + } + } + } else { + if (rc.right - rc.left == rcBmpPart.right - rcBmpPart.left \ + && rc.bottom - rc.top == rcBmpPart.bottom - rcBmpPart.top \ + && rcCorners.left == 0 && rcCorners.right == 0 && rcCorners.top == 0 && rcCorners.bottom == 0) { + if (::IntersectRect (&rcTemp, &rcPaint, &rc)) { + ::BitBlt (hDC, rcTemp.left, rcTemp.top, rcTemp.right - rcTemp.left, rcTemp.bottom - rcTemp.top, \ + hCloneDC, rcBmpPart.left + rcTemp.left - rc.left, rcBmpPart.top + rcTemp.top - rc.top, SRCCOPY); + } + } else { + // middle + if (!hole) { + rcDest.left = rc.left + rcCorners.left; + rcDest.top = rc.top + rcCorners.top; + rcDest.right = rc.right - rc.left - rcCorners.left - rcCorners.right; + rcDest.bottom = rc.bottom - rc.top - rcCorners.top - rcCorners.bottom; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + if (!xtiled && !ytiled) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + ::StretchBlt (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.left + rcCorners.left, rcBmpPart.top + rcCorners.top, \ + rcBmpPart.right - rcBmpPart.left - rcCorners.left - rcCorners.right, \ + rcBmpPart.bottom - rcBmpPart.top - rcCorners.top - rcCorners.bottom, SRCCOPY); + } else if (xtiled && ytiled) { + LONG lWidth = rcBmpPart.right - rcBmpPart.left - rcCorners.left - rcCorners.right; + LONG lHeight = rcBmpPart.bottom - rcBmpPart.top - rcCorners.top - rcCorners.bottom; + int iTimesX = (rcDest.right - rcDest.left + lWidth - 1) / lWidth; + int iTimesY = (rcDest.bottom - rcDest.top + lHeight - 1) / lHeight; + for (int j = 0; j < iTimesY; ++j) { + LONG lDestTop = rcDest.top + lHeight * j; + LONG lDestBottom = rcDest.top + lHeight * (j + 1); + LONG lDrawHeight = lHeight; + if (lDestBottom > rcDest.bottom) { + lDrawHeight -= lDestBottom - rcDest.bottom; + lDestBottom = rcDest.bottom; + } + for (int i = 0; i < iTimesX; ++i) { + LONG lDestLeft = rcDest.left + lWidth * i; + LONG lDestRight = rcDest.left + lWidth * (i + 1); + LONG lDrawWidth = lWidth; + if (lDestRight > rcDest.right) { + lDrawWidth -= lDestRight - rcDest.right; + lDestRight = rcDest.right; + } + ::BitBlt (hDC, rcDest.left + lWidth * i, rcDest.top + lHeight * j, \ + lDestRight - lDestLeft, lDestBottom - lDestTop, hCloneDC, \ + rcBmpPart.left + rcCorners.left, rcBmpPart.top + rcCorners.top, SRCCOPY); + } + } + } else if (xtiled) { + LONG lWidth = rcBmpPart.right - rcBmpPart.left - rcCorners.left - rcCorners.right; + int iTimes = (rcDest.right - rcDest.left + lWidth - 1) / lWidth; + for (int i = 0; i < iTimes; ++i) { + LONG lDestLeft = rcDest.left + lWidth * i; + LONG lDestRight = rcDest.left + lWidth * (i + 1); + LONG lDrawWidth = lWidth; + if (lDestRight > rcDest.right) { + lDrawWidth -= lDestRight - rcDest.right; + lDestRight = rcDest.right; + } + ::StretchBlt (hDC, lDestLeft, rcDest.top, lDestRight - lDestLeft, rcDest.bottom, + hCloneDC, rcBmpPart.left + rcCorners.left, rcBmpPart.top + rcCorners.top, \ + lDrawWidth, rcBmpPart.bottom - rcBmpPart.top - rcCorners.top - rcCorners.bottom, SRCCOPY); + } + } else { // ytiled + LONG lHeight = rcBmpPart.bottom - rcBmpPart.top - rcCorners.top - rcCorners.bottom; + int iTimes = (rcDest.bottom - rcDest.top + lHeight - 1) / lHeight; + for (int i = 0; i < iTimes; ++i) { + LONG lDestTop = rcDest.top + lHeight * i; + LONG lDestBottom = rcDest.top + lHeight * (i + 1); + LONG lDrawHeight = lHeight; + if (lDestBottom > rcDest.bottom) { + lDrawHeight -= lDestBottom - rcDest.bottom; + lDestBottom = rcDest.bottom; + } + ::StretchBlt (hDC, rcDest.left, rcDest.top + lHeight * i, rcDest.right, lDestBottom - lDestTop, + hCloneDC, rcBmpPart.left + rcCorners.left, rcBmpPart.top + rcCorners.top, \ + rcBmpPart.right - rcBmpPart.left - rcCorners.left - rcCorners.right, lDrawHeight, SRCCOPY); + } + } + } + } + + // left-top + if (rcCorners.left > 0 && rcCorners.top > 0) { + rcDest.left = rc.left; + rcDest.top = rc.top; + rcDest.right = rcCorners.left; + rcDest.bottom = rcCorners.top; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + ::StretchBlt (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.left, rcBmpPart.top, rcCorners.left, rcCorners.top, SRCCOPY); + } + } + // top + if (rcCorners.top > 0) { + rcDest.left = rc.left + rcCorners.left; + rcDest.top = rc.top; + rcDest.right = rc.right - rc.left - rcCorners.left - rcCorners.right; + rcDest.bottom = rcCorners.top; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + ::StretchBlt (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.left + rcCorners.left, rcBmpPart.top, rcBmpPart.right - rcBmpPart.left - \ + rcCorners.left - rcCorners.right, rcCorners.top, SRCCOPY); + } + } + // right-top + if (rcCorners.right > 0 && rcCorners.top > 0) { + rcDest.left = rc.right - rcCorners.right; + rcDest.top = rc.top; + rcDest.right = rcCorners.right; + rcDest.bottom = rcCorners.top; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + ::StretchBlt (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.right - rcCorners.right, rcBmpPart.top, rcCorners.right, rcCorners.top, SRCCOPY); + } + } + // left + if (rcCorners.left > 0) { + rcDest.left = rc.left; + rcDest.top = rc.top + rcCorners.top; + rcDest.right = rcCorners.left; + rcDest.bottom = rc.bottom - rc.top - rcCorners.top - rcCorners.bottom; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + ::StretchBlt (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.left, rcBmpPart.top + rcCorners.top, rcCorners.left, rcBmpPart.bottom - \ + rcBmpPart.top - rcCorners.top - rcCorners.bottom, SRCCOPY); + } + } + // right + if (rcCorners.right > 0) { + rcDest.left = rc.right - rcCorners.right; + rcDest.top = rc.top + rcCorners.top; + rcDest.right = rcCorners.right; + rcDest.bottom = rc.bottom - rc.top - rcCorners.top - rcCorners.bottom; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + ::StretchBlt (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.right - rcCorners.right, rcBmpPart.top + rcCorners.top, rcCorners.right, \ + rcBmpPart.bottom - rcBmpPart.top - rcCorners.top - rcCorners.bottom, SRCCOPY); + } + } + // left-bottom + if (rcCorners.left > 0 && rcCorners.bottom > 0) { + rcDest.left = rc.left; + rcDest.top = rc.bottom - rcCorners.bottom; + rcDest.right = rcCorners.left; + rcDest.bottom = rcCorners.bottom; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + ::StretchBlt (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.left, rcBmpPart.bottom - rcCorners.bottom, rcCorners.left, rcCorners.bottom, SRCCOPY); + } + } + // bottom + if (rcCorners.bottom > 0) { + rcDest.left = rc.left + rcCorners.left; + rcDest.top = rc.bottom - rcCorners.bottom; + rcDest.right = rc.right - rc.left - rcCorners.left - rcCorners.right; + rcDest.bottom = rcCorners.bottom; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + ::StretchBlt (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.left + rcCorners.left, rcBmpPart.bottom - rcCorners.bottom, \ + rcBmpPart.right - rcBmpPart.left - rcCorners.left - rcCorners.right, rcCorners.bottom, SRCCOPY); + } + } + // right-bottom + if (rcCorners.right > 0 && rcCorners.bottom > 0) { + rcDest.left = rc.right - rcCorners.right; + rcDest.top = rc.bottom - rcCorners.bottom; + rcDest.right = rcCorners.right; + rcDest.bottom = rcCorners.bottom; + rcDest.right += rcDest.left; + rcDest.bottom += rcDest.top; + if (::IntersectRect (&rcTemp, &rcPaint, &rcDest)) { + rcDest.right -= rcDest.left; + rcDest.bottom -= rcDest.top; + ::StretchBlt (hDC, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom, hCloneDC, \ + rcBmpPart.right - rcCorners.right, rcBmpPart.bottom - rcCorners.bottom, rcCorners.right, \ + rcCorners.bottom, SRCCOPY); + } + } + } + } + + ::SelectObject (hCloneDC, hOldBitmap); + ::DeleteDC (hCloneDC); + } + + bool CRenderEngine::DrawImageInfo (HDC hDC, CPaintManagerUI* pManager, const RECT& rcItem, const RECT& rcPaint, const TDrawInfo* pDrawInfo, HINSTANCE instance) { + if (!pManager || !hDC || !pDrawInfo) return false; + RECT rcDest = rcItem; + if (pDrawInfo->rcDest.left != 0 || pDrawInfo->rcDest.top != 0 || + pDrawInfo->rcDest.right != 0 || pDrawInfo->rcDest.bottom != 0) { + rcDest.left = rcItem.left + pDrawInfo->rcDest.left; + rcDest.top = rcItem.top + pDrawInfo->rcDest.top; + rcDest.right = rcItem.left + pDrawInfo->rcDest.right; + if (rcDest.right > rcItem.right) rcDest.right = rcItem.right; + rcDest.bottom = rcItem.top + pDrawInfo->rcDest.bottom; + if (rcDest.bottom > rcItem.bottom) rcDest.bottom = rcItem.bottom; + + if (rcDest.right < rcItem.left) { + rcDest.right += (rcItem.right - rcItem.left); + rcDest.left += (rcItem.right - rcItem.left); + } + if (rcDest.bottom < rcItem.top) { + rcDest.bottom += (rcItem.bottom - rcItem.top); + rcDest.top += (rcItem.bottom - rcItem.top); + } + } + bool bRet = DuiLib::DrawImage (hDC, pManager, rcItem, rcPaint, pDrawInfo->sImageName, pDrawInfo->sResType, rcDest, \ + pDrawInfo->rcSource, pDrawInfo->rcCorner, pDrawInfo->dwMask, pDrawInfo->uFade, pDrawInfo->bHole, pDrawInfo->bTiledX, pDrawInfo->bTiledY, instance); + + return bRet; + } + + bool CRenderEngine::DrawImageString (HDC hDC, CPaintManagerUI* pManager, const RECT& rcItem, const RECT& rcPaint, string_view_t pStrImage, string_view_t pStrModify, HINSTANCE instance) { + if (!pManager || !hDC) return false; + const TDrawInfo* pDrawInfo = pManager->GetDrawInfo (pStrImage, pStrModify); + return DrawImageInfo (hDC, pManager, rcItem, rcPaint, pDrawInfo, instance); + } + + void CRenderEngine::DrawColor (HDC hDC, const RECT& rc, DWORD color) { + if (color <= 0x00FFFFFF) return; + + Gdiplus::Graphics graphics (hDC); + Gdiplus::SolidBrush brush (Gdiplus::Color ((LOBYTE ((color) >> 24)), GetBValue (color), GetGValue (color), GetRValue (color))); + graphics.FillRectangle (&brush, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top); + } + + void CRenderEngine::DrawGradient (HDC hDC, const RECT& rc, DWORD dwFirst, DWORD dwSecond, bool bVertical, int nSteps) { + typedef BOOL (WINAPI *LPALPHABLEND)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION); + static LPALPHABLEND lpAlphaBlend = (LPALPHABLEND) ::GetProcAddress (::GetModuleHandle (_T ("msimg32.dll")), "AlphaBlend"); + if (!lpAlphaBlend) lpAlphaBlend = AlphaBitBlt; + typedef BOOL (WINAPI *PGradientFill)(HDC, PTRIVERTEX, ULONG, PVOID, ULONG, ULONG); + static PGradientFill lpGradientFill = (PGradientFill) ::GetProcAddress (::GetModuleHandle (_T ("msimg32.dll")), "GradientFill"); + + BYTE bAlpha = (BYTE) (((dwFirst >> 24) + (dwSecond >> 24)) >> 1); + if (bAlpha == 0) return; + int cx = rc.right - rc.left; + int cy = rc.bottom - rc.top; + RECT rcPaint = rc; + HDC hPaintDC = hDC; + HBITMAP hPaintBitmap = nullptr; + HBITMAP hOldPaintBitmap = nullptr; + if (bAlpha < 255) { + rcPaint.left = rcPaint.top = 0; + rcPaint.right = cx; + rcPaint.bottom = cy; + hPaintDC = ::CreateCompatibleDC (hDC); + hPaintBitmap = ::CreateCompatibleBitmap (hDC, cx, cy); + ASSERT (hPaintDC); + ASSERT (hPaintBitmap); + hOldPaintBitmap = (HBITMAP) ::SelectObject (hPaintDC, hPaintBitmap); + } + if (lpGradientFill) { + TRIVERTEX triv[2] = + { + { rcPaint.left, rcPaint.top, + static_cast(GetBValue (dwFirst) << 8), + static_cast(GetGValue (dwFirst) << 8), + static_cast(GetRValue (dwFirst) << 8), 0xFF00 }, + { rcPaint.right, rcPaint.bottom, + static_cast(GetBValue (dwSecond) << 8), + static_cast(GetGValue (dwSecond) << 8), + static_cast(GetRValue (dwSecond) << 8), 0xFF00 } + }; + GRADIENT_RECT grc = { 0, 1 }; + lpGradientFill (hPaintDC, triv, 2, &grc, 1, bVertical ? GRADIENT_FILL_RECT_V : GRADIENT_FILL_RECT_H); + } else { + // Determine how many shades + int nShift = 1; + if (nSteps >= 64) nShift = 6; + else if (nSteps >= 32) nShift = 5; + else if (nSteps >= 16) nShift = 4; + else if (nSteps >= 8) nShift = 3; + else if (nSteps >= 4) nShift = 2; + int nLines = 1 << nShift; + for (int i = 0; i < nLines; i++) { + // Do a little alpha blending + BYTE bR = (BYTE) ((GetBValue (dwSecond) * (nLines - i) + GetBValue (dwFirst) * i) >> nShift); + BYTE bG = (BYTE) ((GetGValue (dwSecond) * (nLines - i) + GetGValue (dwFirst) * i) >> nShift); + BYTE bB = (BYTE) ((GetRValue (dwSecond) * (nLines - i) + GetRValue (dwFirst) * i) >> nShift); + // ... then paint with the resulting color + HBRUSH hBrush = ::CreateSolidBrush (RGB (bR, bG, bB)); + RECT r2 = rcPaint; + if (bVertical) { + r2.bottom = rc.bottom - ((i * (rc.bottom - rc.top)) >> nShift); + r2.top = rc.bottom - (((i + 1) * (rc.bottom - rc.top)) >> nShift); + if ((r2.bottom - r2.top) > 0) ::FillRect (hDC, &r2, hBrush); + } else { + r2.left = rc.right - (((i + 1) * (rc.right - rc.left)) >> nShift); + r2.right = rc.right - ((i * (rc.right - rc.left)) >> nShift); + if ((r2.right - r2.left) > 0) ::FillRect (hPaintDC, &r2, hBrush); + } + ::DeleteObject (hBrush); + } + } + if (bAlpha < 255) { + BLENDFUNCTION bf = { AC_SRC_OVER, 0, bAlpha, AC_SRC_ALPHA }; + lpAlphaBlend (hDC, rc.left, rc.top, cx, cy, hPaintDC, 0, 0, cx, cy, bf); + ::SelectObject (hPaintDC, hOldPaintBitmap); + ::DeleteObject (hPaintBitmap); + ::DeleteDC (hPaintDC); + } + } + + void CRenderEngine::DrawLine (HDC hDC, const RECT& rc, int nSize, DWORD dwPenColor, int nStyle /*= PS_SOLID*/) { + ASSERT (::GetObjectType (hDC) == OBJ_DC || ::GetObjectType (hDC) == OBJ_MEMDC); + + LOGPEN lg; + lg.lopnColor = RGB (GetBValue (dwPenColor), GetGValue (dwPenColor), GetRValue (dwPenColor)); + lg.lopnStyle = nStyle; + lg.lopnWidth.x = nSize; + HPEN hPen = CreatePenIndirect (&lg); + HPEN hOldPen = (HPEN)::SelectObject (hDC, hPen); + POINT ptTemp = { 0 }; + ::MoveToEx (hDC, rc.left, rc.top, &ptTemp); + ::LineTo (hDC, rc.right, rc.bottom); + ::SelectObject (hDC, hOldPen); + ::DeleteObject (hPen); + } + + void CRenderEngine::DrawRect (HDC hDC, const RECT& rc, int nSize, DWORD dwPenColor, int nStyle /*= PS_SOLID*/) { +#if USE_GDI_RENDER + ASSERT (::GetObjectType (hDC) == OBJ_DC || ::GetObjectType (hDC) == OBJ_MEMDC); + HPEN hPen = ::CreatePen (PS_SOLID | PS_INSIDEFRAME, nSize, RGB (GetBValue (dwPenColor), GetGValue (dwPenColor), GetRValue (dwPenColor))); + HPEN hOldPen = (HPEN)::SelectObject (hDC, hPen); + ::SelectObject (hDC, ::GetStockObject (HOLLOW_BRUSH)); + ::Rectangle (hDC, rc.left, rc.top, rc.right, rc.bottom); + ::SelectObject (hDC, hOldPen); + ::DeleteObject (hPen); +#else + ASSERT (::GetObjectType (hDC) == OBJ_DC || ::GetObjectType (hDC) == OBJ_MEMDC); + Gdiplus::Graphics graphics (hDC); + Gdiplus::Pen pen (Gdiplus::Color (dwPenColor), (Gdiplus::REAL)nSize); + pen.SetAlignment (Gdiplus::PenAlignmentInset); + + graphics.DrawRectangle (&pen, rc.left, rc.top, rc.right - rc.left - 1, rc.bottom - rc.top - 1); +#endif + } + + void CRenderEngine::DrawRoundRect (HDC hDC, const RECT& rc, int nSize, int width, int height, DWORD dwPenColor, int nStyle /*= PS_SOLID*/) { + ASSERT (::GetObjectType (hDC) == OBJ_DC || ::GetObjectType (hDC) == OBJ_MEMDC); + HPEN hPen = ::CreatePen (nStyle, nSize, RGB (GetBValue (dwPenColor), GetGValue (dwPenColor), GetRValue (dwPenColor))); + HPEN hOldPen = (HPEN)::SelectObject (hDC, hPen); + ::SelectObject (hDC, ::GetStockObject (HOLLOW_BRUSH)); + ::RoundRect (hDC, rc.left, rc.top, rc.right, rc.bottom, width, height); + ::SelectObject (hDC, hOldPen); + ::DeleteObject (hPen); + } + + void CRenderEngine::DrawText (HDC hDC, CPaintManagerUI* pManager, RECT& rc, string_view_t pstrText, DWORD dwTextColor, int iFont, UINT uStyle) { + ASSERT (::GetObjectType (hDC) == OBJ_DC || ::GetObjectType (hDC) == OBJ_MEMDC); + if (pstrText.empty () || !pManager) return; + + if (pManager->IsLayered () || pManager->IsUseGdiplusText ()) { + HFONT hOldFont = (HFONT)::SelectObject (hDC, pManager->GetFont (iFont)); + Gdiplus::Graphics graphics (hDC); + Gdiplus::Font font (hDC, pManager->GetFont (iFont)); + Gdiplus::TextRenderingHint trh = Gdiplus::TextRenderingHintSystemDefault; + switch (pManager->GetGdiplusTextRenderingHint ()) { + case 0: trh = Gdiplus::TextRenderingHintSystemDefault; break; + case 1: trh = Gdiplus::TextRenderingHintSingleBitPerPixelGridFit; break; + case 2: trh = Gdiplus::TextRenderingHintSingleBitPerPixel; break; + case 3: trh = Gdiplus::TextRenderingHintAntiAliasGridFit; break; + case 4: trh = Gdiplus::TextRenderingHintAntiAlias; break; + case 5: trh = Gdiplus::TextRenderingHintClearTypeGridFit; break; + } + graphics.SetTextRenderingHint (trh); + graphics.SetSmoothingMode (Gdiplus::SmoothingModeHighQuality); + graphics.SetInterpolationMode (Gdiplus::InterpolationModeHighQualityBicubic); + + Gdiplus::RectF rectF ((Gdiplus::REAL)rc.left, (Gdiplus::REAL)rc.top, (Gdiplus::REAL)(rc.right - rc.left), (Gdiplus::REAL)(rc.bottom - rc.top)); + Gdiplus::SolidBrush brush (Gdiplus::Color (254, GetBValue (dwTextColor), GetGValue (dwTextColor), GetRValue (dwTextColor))); + + Gdiplus::StringFormat stringFormat = Gdiplus::StringFormat::GenericTypographic (); + + if ((uStyle & DT_END_ELLIPSIS) != 0) { + stringFormat.SetTrimming (Gdiplus::StringTrimmingEllipsisCharacter); + } + + int formatFlags = 0; + if ((uStyle & DT_NOCLIP) != 0) { + formatFlags |= Gdiplus::StringFormatFlagsNoClip; + } + if ((uStyle & DT_SINGLELINE) != 0) { + formatFlags |= Gdiplus::StringFormatFlagsNoWrap; + } + + stringFormat.SetFormatFlags (formatFlags); + + if ((uStyle & DT_LEFT) != 0) { + stringFormat.SetAlignment (Gdiplus::StringAlignmentNear); + } else if ((uStyle & DT_CENTER) != 0) { + stringFormat.SetAlignment (Gdiplus::StringAlignmentCenter); + } else if ((uStyle & DT_RIGHT) != 0) { + stringFormat.SetAlignment (Gdiplus::StringAlignmentFar); + } else { + stringFormat.SetAlignment (Gdiplus::StringAlignmentNear); + } + stringFormat.GenericTypographic (); + if ((uStyle & DT_TOP) != 0) { + stringFormat.SetLineAlignment (Gdiplus::StringAlignmentNear); + } else if ((uStyle & DT_VCENTER) != 0) { + stringFormat.SetLineAlignment (Gdiplus::StringAlignmentCenter); + } else if ((uStyle & DT_BOTTOM) != 0) { + stringFormat.SetLineAlignment (Gdiplus::StringAlignmentFar); + } else { + stringFormat.SetLineAlignment (Gdiplus::StringAlignmentNear); + } + std::wstring pcwszDest = FawTools::get_utf16 (pstrText); + if ((uStyle & DT_CALCRECT) != 0) { + Gdiplus::RectF bounds; + graphics.MeasureString (pcwszDest.data (), -1, &font, rectF, &stringFormat, &bounds); + // MeasureStringڼһ + rc.bottom = rc.top + (long) bounds.Height + 1; + rc.right = rc.left + (long) bounds.Width + 1; + } else { + graphics.DrawString (pcwszDest.data (), -1, &font, rectF, &stringFormat, &brush); + } + ::SelectObject (hDC, hOldFont); + } else { + ::SetBkMode (hDC, TRANSPARENT); + ::SetTextColor (hDC, RGB (GetBValue (dwTextColor), GetGValue (dwTextColor), GetRValue (dwTextColor))); + HFONT hOldFont = (HFONT)::SelectObject (hDC, pManager->GetFont (iFont)); + size_t fonticonpos = CDuiString (pstrText).find (_T ("&#x")); + if (fonticonpos != string_t::npos) { + CDuiString strUnicode = CDuiString (pstrText).Mid (fonticonpos + 3); + if (strUnicode.length () > 4) strUnicode = strUnicode.Mid (0, 4); + if (strUnicode.Right (1) != _T (" ")) { + strUnicode = strUnicode.Mid (0, strUnicode.length () - 1); + } + if (strUnicode.Right (1) != _T (";")) { + strUnicode = strUnicode.Mid (0, strUnicode.length () - 1); + } + wchar_t wch[2] = { 0 }; + wch[0] = static_cast(FawTools::parse_hex (strUnicode)); + ::DrawTextW (hDC, wch, -1, &rc, uStyle); + } else { + ::DrawText (hDC, pstrText.data (), -1, &rc, uStyle); + } + ::SelectObject (hDC, hOldFont); + } + } + + void CRenderEngine::DrawHtmlText (HDC hDC, CPaintManagerUI* pManager, RECT& rc, string_view_t pstrText, DWORD dwTextColor, RECT* prcLinks, CDuiString* sLinks, int& nLinkRects, int iFont, UINT uStyle) { + // ǵxml༭ʹ<>Ų㣬ʹ{}Ŵ + // ֱ֧ǩǶףtextǽǶӦñģtext + // The string formatter supports a kind of "mini-html" that consists of various short tags: + // + // Bold: text + // Color: text where x = RGB in hex + // Font: text where x = font id + // Italic: text + // Image: where x = image name and y = imagelist num and z(optional) = imagelist id + // Link: text where x(optional) = link content, normal like app:notepad or http:www.xxx.com + // NewLine + // Paragraph:

text

where x = extra pixels indent in p + // Raw Text: text + // Selected: text + // Underline: text + // X Indent: where i = hor indent in pixels + // Y Indent: where i = ver indent in pixels + + ASSERT (::GetObjectType (hDC) == OBJ_DC || ::GetObjectType (hDC) == OBJ_MEMDC); + if (pstrText.empty () || !pManager) return; + if (::IsRectEmpty (&rc)) return; + + bool bDraw = (uStyle & DT_CALCRECT) == 0; + + CStdPtrArray aFontArray (10); + CStdPtrArray aColorArray (10); + CStdPtrArray aPIndentArray (10); + + RECT rcClip = { 0 }; + ::GetClipBox (hDC, &rcClip); + HRGN hOldRgn = ::CreateRectRgnIndirect (&rcClip); + HRGN hRgn = ::CreateRectRgnIndirect (&rc); + if (bDraw) ::ExtSelectClipRgn (hDC, hRgn, RGN_AND); + + TFontInfo* pDefFontInfo = pManager->GetFontInfo (iFont); + if (!pDefFontInfo) { + pDefFontInfo = pManager->GetDefaultFontInfo (); + } + TEXTMETRIC* pTm = &pDefFontInfo->tm; + HFONT hOldFont = (HFONT) ::SelectObject (hDC, pDefFontInfo->hFont); + ::SetBkMode (hDC, TRANSPARENT); + ::SetTextColor (hDC, RGB (GetBValue (dwTextColor), GetGValue (dwTextColor), GetRValue (dwTextColor))); + DWORD dwBkColor = pManager->GetDefaultSelectedBkColor (); + ::SetBkColor (hDC, RGB (GetBValue (dwBkColor), GetGValue (dwBkColor), GetRValue (dwBkColor))); + + // If the drawstyle include a alignment, we'll need to first determine the text-size so + // we can draw it at the correct position... + if (((uStyle & DT_CENTER) != 0 || (uStyle & DT_RIGHT) != 0 || (uStyle & DT_VCENTER) != 0 || (uStyle & DT_BOTTOM) != 0) && (uStyle & DT_CALCRECT) == 0) { + RECT rcText = { 0, 0, 9999, 100 }; + int nLinks = 0; + DrawHtmlText (hDC, pManager, rcText, pstrText, dwTextColor, nullptr, nullptr, nLinks, iFont, uStyle | DT_CALCRECT); + if ((uStyle & DT_SINGLELINE) != 0) { + if ((uStyle & DT_CENTER) != 0) { + rc.left = rc.left + ((rc.right - rc.left) / 2) - ((rcText.right - rcText.left) / 2); + rc.right = rc.left + (rcText.right - rcText.left); + } + if ((uStyle & DT_RIGHT) != 0) { + rc.left = rc.right - (rcText.right - rcText.left); + } + } + if ((uStyle & DT_VCENTER) != 0) { + rc.top = rc.top + ((rc.bottom - rc.top) / 2) - ((rcText.bottom - rcText.top) / 2); + rc.bottom = rc.top + (rcText.bottom - rcText.top); + } + if ((uStyle & DT_BOTTOM) != 0) { + rc.top = rc.bottom - (rcText.bottom - rcText.top); + } + } + + bool bHoverLink = false; + CDuiString sHoverLink; + POINT ptMouse = pManager->GetMousePos (); + for (int i = 0; !bHoverLink && i < nLinkRects; i++) { + if (::PtInRect (prcLinks + i, ptMouse)) { + sHoverLink = *(CDuiString*) (sLinks + i); + bHoverLink = true; + } + } + + POINT pt = { rc.left, rc.top }; + int iLinkIndex = 0; + int cyLine = pTm->tmHeight + pTm->tmExternalLeading + (int) aPIndentArray.GetAt (aPIndentArray.GetSize () - 1); + int cyMinHeight = 0; + int cxMaxWidth = 0; + POINT ptLinkStart = { 0 }; + bool bLineEnd = false; + bool bInRaw = false; + bool bInLink = false; + bool bInSelected = false; + int iLineLinkIndex = 0; + + // Űϰͼĵײ룬ÿлƶҪȼ߶ȣٻ + CStdPtrArray aLineFontArray; + CStdPtrArray aLineColorArray; + CStdPtrArray aLinePIndentArray; + string_view_t pstrLineBegin = pstrText; + bool bLineInRaw = false; + bool bLineInLink = false; + bool bLineInSelected = false; + int cyLineHeight = 0; + bool bLineDraw = false; // еĵڶ׶Σ + while (!pstrText.empty ()) { + if (pt.x >= rc.right || pstrText[0] == _T ('\n') || bLineEnd) { + if (pstrText[0] == _T ('\n')) pstrText = pstrText.substr (1); + if (bLineEnd) bLineEnd = false; + if (!bLineDraw) { + if (bInLink && iLinkIndex < nLinkRects) { + ::SetRect (&prcLinks[iLinkIndex++], ptLinkStart.x, ptLinkStart.y, MIN (pt.x, rc.right), pt.y + cyLine); + CDuiString *pStr1 = (CDuiString*) (sLinks + iLinkIndex - 1); + CDuiString *pStr2 = (CDuiString*) (sLinks + iLinkIndex); + *pStr2 = *pStr1; + } + for (int i = iLineLinkIndex; i < iLinkIndex; i++) { + prcLinks[i].bottom = pt.y + cyLine; + } + if (bDraw) { + bInLink = bLineInLink; + iLinkIndex = iLineLinkIndex; + } + } else { + if (bInLink && iLinkIndex < nLinkRects) iLinkIndex++; + bLineInLink = bInLink; + iLineLinkIndex = iLinkIndex; + } + if ((uStyle & DT_SINGLELINE) != 0 && (!bDraw || bLineDraw)) break; + if (bDraw) bLineDraw = !bLineDraw; // ! + pt.x = rc.left; + if (!bLineDraw) pt.y += cyLine; + if (pt.y > rc.bottom && bDraw) break; + ptLinkStart = pt; + cyLine = pTm->tmHeight + pTm->tmExternalLeading + (int) aPIndentArray.GetAt (aPIndentArray.GetSize () - 1); + if (pt.x >= rc.right) break; + } else if (!bInRaw && (pstrText[0] == _T ('<') || pstrText[0] == _T ('{')) + && (pstrText[1] >= _T ('a') && pstrText[1] <= _T ('z')) + && (pstrText[2] == _T (' ') || pstrText[2] == _T ('>') || pstrText[2] == _T ('}'))) { + pstrText = pstrText.substr (1); + string_view_t pstrNextStart = _T (""); + switch (pstrText[0]) { + case _T ('a'): // Link + { + pstrText = pstrText.substr (1); + if (iLinkIndex < nLinkRects && !bLineDraw) { + CDuiString *pStr = (CDuiString*) (sLinks + iLinkIndex); + pStr->clear (); + while (!pstrText.empty () && pstrText[0] != _T ('>') && pstrText[0] != _T ('}')) { + string_view_t pstrTemp = pstrText.substr (1); + while (pstrText < pstrTemp) { + *pStr += pstrText[0]; + pstrText = pstrText.substr (1); + } + } + } + + DWORD clrColor = dwTextColor; + if (clrColor == 0) pManager->GetDefaultLinkFontColor (); + if (bHoverLink && iLinkIndex < nLinkRects) { + CDuiString *pStr = (CDuiString*) (sLinks + iLinkIndex); + if (sHoverLink == *pStr) clrColor = pManager->GetDefaultLinkHoverFontColor (); + } + //else if (!prcLinks) { + // if (::PtInRect (&rc, ptMouse)) + // clrColor = pManager->GetDefaultLinkHoverFontColor (); + //} + aColorArray.Add ((LPVOID) clrColor); + ::SetTextColor (hDC, RGB (GetBValue (clrColor), GetGValue (clrColor), GetRValue (clrColor))); + TFontInfo* pFontInfo = pDefFontInfo; + if (aFontArray.GetSize () > 0) pFontInfo = (TFontInfo*) aFontArray.GetAt (aFontArray.GetSize () - 1); + if (pFontInfo->bUnderline == false) { + HFONT hFont = pManager->GetFont (pFontInfo->sFontName, pFontInfo->iSize, pFontInfo->bBold, true, pFontInfo->bItalic); + if (!hFont) hFont = pManager->AddFont (g_iFontID, pFontInfo->sFontName, pFontInfo->iSize, pFontInfo->bBold, true, pFontInfo->bItalic); + pFontInfo = pManager->GetFontInfo (hFont); + aFontArray.Add (pFontInfo); + pTm = &pFontInfo->tm; + ::SelectObject (hDC, pFontInfo->hFont); + cyLine = MAX (cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int) aPIndentArray.GetAt (aPIndentArray.GetSize () - 1)); + } + ptLinkStart = pt; + bInLink = true; + } + break; + case _T ('b'): // Bold + { + pstrText = pstrText.substr (1); + TFontInfo* pFontInfo = pDefFontInfo; + if (aFontArray.GetSize () > 0) pFontInfo = (TFontInfo*) aFontArray.GetAt (aFontArray.GetSize () - 1); + if (pFontInfo->bBold == false) { + HFONT hFont = pManager->GetFont (pFontInfo->sFontName, pFontInfo->iSize, true, pFontInfo->bUnderline, pFontInfo->bItalic); + if (!hFont) hFont = pManager->AddFont (g_iFontID, pFontInfo->sFontName, pFontInfo->iSize, true, pFontInfo->bUnderline, pFontInfo->bItalic); + pFontInfo = pManager->GetFontInfo (hFont); + aFontArray.Add (pFontInfo); + pTm = &pFontInfo->tm; + ::SelectObject (hDC, pFontInfo->hFont); + cyLine = MAX (cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int) aPIndentArray.GetAt (aPIndentArray.GetSize () - 1)); + } + } + break; + case _T ('c'): // Color + { + pstrText = pstrText.substr (1); + DWORD clrColor = (DWORD) (FawTools::parse_hex (pstrText)); + aColorArray.Add ((LPVOID) clrColor); + ::SetTextColor (hDC, RGB (GetBValue (clrColor), GetGValue (clrColor), GetRValue (clrColor))); + } + break; + case _T ('f'): // Font + { + pstrText = pstrText.substr (1); + string_view_t pstrTemp = pstrText; + int _iFont = _ttoi (pstrText.data ()); + if (pstrTemp != pstrText) { + TFontInfo* pFontInfo = pManager->GetFontInfo (_iFont); + aFontArray.Add (pFontInfo); + pTm = &pFontInfo->tm; + ::SelectObject (hDC, pFontInfo->hFont); + } else { + CDuiString sFontName; + int iFontSize = 10; + CDuiString sFontAttr; + bool bBold = false; + bool bUnderline = false; + bool bItalic = false; + while (!pstrText.empty () && pstrText[0] != _T ('>') && pstrText[0] != _T ('}') && pstrText[0] != _T (' ')) { + pstrTemp = pstrText.substr (1); + while (pstrText < pstrTemp) { + sFontName += pstrText[0]; + pstrText = pstrText.substr (1); + } + } + if (isdigit (pstrText[0])) { + iFontSize = FawTools::parse_dec (pstrText); + } + while (!pstrText.empty () && pstrText[0] != _T ('>') && pstrText[0] != _T ('}')) { + pstrTemp = pstrText.substr (1); + while (pstrText < pstrTemp) { + sFontAttr += pstrText[0]; + pstrText = pstrText.substr (1); + } + } + sFontAttr.MakeLower (); + if (sFontAttr.find (_T ("bold")) != string_t::npos) bBold = true; + if (sFontAttr.find (_T ("underline")) != string_t::npos) bUnderline = true; + if (sFontAttr.find (_T ("italic")) != string_t::npos) bItalic = true; + HFONT hFont = pManager->GetFont (sFontName, iFontSize, bBold, bUnderline, bItalic); + if (!hFont) hFont = pManager->AddFont (g_iFontID, sFontName, iFontSize, bBold, bUnderline, bItalic); + TFontInfo* pFontInfo = pManager->GetFontInfo (hFont); + aFontArray.Add (pFontInfo); + pTm = &pFontInfo->tm; + ::SelectObject (hDC, pFontInfo->hFont); + } + cyLine = MAX (cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int) aPIndentArray.GetAt (aPIndentArray.GetSize () - 1)); + } + break; + case _T ('i'): // Italic or Image + { + pstrNextStart = &pstrText.data ()[-1]; + pstrText = pstrText.substr (1); + CDuiString sImageString = pstrText; + int iWidth = 0; + int iHeight = 0; + const TImageInfo* pImageInfo = nullptr; + CDuiString sName; + while (!pstrText.empty () && pstrText[0] != _T ('>') && pstrText[0] != _T ('}') && pstrText[0] != _T (' ')) { + string_view_t pstrTemp = pstrText.substr (1); + while (pstrText < pstrTemp) { + sName += pstrText[0]; + pstrText = pstrText.substr (1); + } + } + if (sName.empty ()) { // Italic + pstrNextStart = _T (""); + TFontInfo* pFontInfo = pDefFontInfo; + if (aFontArray.GetSize () > 0) pFontInfo = (TFontInfo*) aFontArray.GetAt (aFontArray.GetSize () - 1); + if (pFontInfo->bItalic == false) { + HFONT hFont = pManager->GetFont (pFontInfo->sFontName, pFontInfo->iSize, pFontInfo->bBold, pFontInfo->bUnderline, true); + if (!hFont) hFont = pManager->AddFont (g_iFontID, pFontInfo->sFontName, pFontInfo->iSize, pFontInfo->bBold, pFontInfo->bUnderline, true); + pFontInfo = pManager->GetFontInfo (hFont); + aFontArray.Add (pFontInfo); + pTm = &pFontInfo->tm; + ::SelectObject (hDC, pFontInfo->hFont); + cyLine = MAX (cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int) aPIndentArray.GetAt (aPIndentArray.GetSize () - 1)); + } + } else { + int iImageListNum = (int) FawTools::parse_dec (pstrText); + if (iImageListNum <= 0) iImageListNum = 1; + int iImageListIndex = (int) FawTools::parse_dec (pstrText); + if (iImageListIndex < 0 || iImageListIndex >= iImageListNum) iImageListIndex = 0; + + if (sImageString.find (_T ("file=\'")) != string_t::npos || sImageString.find (_T ("res=\'")) != string_t::npos) { + CDuiString sImageResType; + CDuiString sImageName; + string_view_t pStrImage = sImageString; + CDuiString sItem; + CDuiString sValue; + while (!pStrImage.empty ()) { + sItem.clear (); + sValue.clear (); + while (pStrImage[0] != _T ('\0') && pStrImage[0] != _T ('=') && pStrImage[0] > _T (' ')) { + string_view_t pstrTemp = pStrImage.substr (1); + while (pStrImage < pstrTemp) { + sItem += pStrImage[0]; + pStrImage = pStrImage.substr (1); + } + } + if (pStrImage[0] != _T ('=')) break; + pStrImage = pStrImage.substr (1); + if (pStrImage[0] != _T ('\'')) break; + pStrImage = pStrImage.substr (1); + while (pStrImage[0] != _T ('\0') && pStrImage[0] != _T ('\'')) { + string_view_t pstrTemp = pStrImage.substr (1); + while (pStrImage < pstrTemp) { + sValue += pStrImage[0]; + pStrImage = pStrImage.substr (1); + } + } + if (pStrImage[0] != _T ('\'')) break; + pStrImage = pStrImage.substr (1); + if (!sValue.empty ()) { + if (sItem == _T ("file") || sItem == _T ("res")) { + sImageName = sValue; + } else if (sItem == _T ("restype")) { + sImageResType = sValue; + } + } + if (pStrImage[0] != _T (' ')) break; + pStrImage = pStrImage.substr (1); + } + + pImageInfo = pManager->GetImageEx (sImageName, sImageResType); + } else + pImageInfo = pManager->GetImageEx (sName); + + if (pImageInfo) { + iWidth = pImageInfo->nX; + iHeight = pImageInfo->nY; + if (iImageListNum > 1) iWidth /= iImageListNum; + + if (pt.x + iWidth > rc.right && pt.x > rc.left && (uStyle & DT_SINGLELINE) == 0) { + bLineEnd = true; + } else { + pstrNextStart = _T (""); + if (bDraw && bLineDraw) { + RECT rcImage { pt.x, pt.y + cyLineHeight - iHeight, pt.x + iWidth, pt.y + cyLineHeight }; + if (iHeight < cyLineHeight) { + rcImage.bottom -= (cyLineHeight - iHeight) / 2; + rcImage.top = rcImage.bottom - iHeight; + } + RECT rcBmpPart { 0, 0, iWidth, iHeight }; + rcBmpPart.left = iWidth * iImageListIndex; + rcBmpPart.right = iWidth * (iImageListIndex + 1); + RECT rcCorner { 0, 0, 0, 0 }; + HBITMAP hBmp = (pImageInfo->hBitmap ? pImageInfo->hBitmap : *(pImageInfo->phBitmap)); + DrawImage (hDC, hBmp, rcImage, rcImage, rcBmpPart, rcCorner, pImageInfo->bAlpha, 255); + } + + cyLine = MAX (iHeight, cyLine); + pt.x += iWidth; + cyMinHeight = pt.y + iHeight; + cxMaxWidth = MAX (cxMaxWidth, pt.x); + } + } else pstrNextStart = _T (""); + } + } + break; + case _T ('n'): // Newline + { + pstrText = pstrText.substr (1); + if ((uStyle & DT_SINGLELINE) != 0) break; + bLineEnd = true; + } + break; + case _T ('p'): // Paragraph + { + pstrText = pstrText.substr (1); + if (pt.x > rc.left) bLineEnd = true; + int cyLineExtra = (int) FawTools::parse_dec (pstrText); + aPIndentArray.Add ((LPVOID) cyLineExtra); + cyLine = MAX (cyLine, pTm->tmHeight + pTm->tmExternalLeading + cyLineExtra); + } + break; + case _T ('r'): // Raw Text + { + pstrText = pstrText.substr (1); + bInRaw = true; + } + break; + case _T ('s'): // Selected text background color + { + pstrText = pstrText.substr (1); + bInSelected = !bInSelected; + if (bDraw && bLineDraw) { + if (bInSelected) ::SetBkMode (hDC, OPAQUE); + else ::SetBkMode (hDC, TRANSPARENT); + } + } + break; + case _T ('u'): // Underline text + { + pstrText = pstrText.substr (1); + TFontInfo* pFontInfo = pDefFontInfo; + if (aFontArray.GetSize () > 0) pFontInfo = (TFontInfo*) aFontArray.GetAt (aFontArray.GetSize () - 1); + if (pFontInfo->bUnderline == false) { + HFONT hFont = pManager->GetFont (pFontInfo->sFontName, pFontInfo->iSize, pFontInfo->bBold, true, pFontInfo->bItalic); + if (!hFont) hFont = pManager->AddFont (g_iFontID, pFontInfo->sFontName, pFontInfo->iSize, pFontInfo->bBold, true, pFontInfo->bItalic); + pFontInfo = pManager->GetFontInfo (hFont); + aFontArray.Add (pFontInfo); + pTm = &pFontInfo->tm; + ::SelectObject (hDC, pFontInfo->hFont); + cyLine = MAX (cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int) aPIndentArray.GetAt (aPIndentArray.GetSize () - 1)); + } + } + break; + case _T ('x'): // X Indent + { + pstrText = pstrText.substr (1); + int iWidth = (int) FawTools::parse_dec (pstrText); + pt.x += iWidth; + cxMaxWidth = MAX (cxMaxWidth, pt.x); + } + break; + case _T ('y'): // Y Indent + { + pstrText = pstrText.substr (1); + int iWidth = (int) FawTools::parse_dec (pstrText); + } + break; + } + if (!pstrNextStart.empty ()) pstrText = pstrNextStart; + else { + while (!pstrText.empty () && pstrText[0] != _T ('>') && pstrText[0] != _T ('}')) pstrText = pstrText.substr (1); + pstrText = pstrText.substr (1); + } + } else if (!bInRaw && (pstrText[0] == _T ('<') || pstrText[0] == _T ('{')) && pstrText[1] == _T ('/')) { + pstrText = pstrText.substr (2); + switch (pstrText[0]) { + case _T ('c'): + { + pstrText = pstrText.substr (1); + aColorArray.Remove (aColorArray.GetSize () - 1); + DWORD clrColor = dwTextColor; + if (aColorArray.GetSize () > 0) clrColor = (int) aColorArray.GetAt (aColorArray.GetSize () - 1); + ::SetTextColor (hDC, RGB (GetBValue (clrColor), GetGValue (clrColor), GetRValue (clrColor))); + } + break; + case _T ('p'): + pstrText = pstrText.substr (1); + if (pt.x > rc.left) bLineEnd = true; + aPIndentArray.Remove (aPIndentArray.GetSize () - 1); + cyLine = MAX (cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int) aPIndentArray.GetAt (aPIndentArray.GetSize () - 1)); + break; + case _T ('s'): + { + pstrText = pstrText.substr (1); + bInSelected = !bInSelected; + if (bDraw && bLineDraw) { + if (bInSelected) ::SetBkMode (hDC, OPAQUE); + else ::SetBkMode (hDC, TRANSPARENT); + } + } + break; + case _T ('a'): + { + if (iLinkIndex < nLinkRects) { + if (!bLineDraw) ::SetRect (&prcLinks[iLinkIndex], ptLinkStart.x, ptLinkStart.y, MIN (pt.x, rc.right), pt.y + pTm->tmHeight + pTm->tmExternalLeading); + iLinkIndex++; + } + aColorArray.Remove (aColorArray.GetSize () - 1); + DWORD clrColor = dwTextColor; + if (aColorArray.GetSize () > 0) clrColor = (int) aColorArray.GetAt (aColorArray.GetSize () - 1); + ::SetTextColor (hDC, RGB (GetBValue (clrColor), GetGValue (clrColor), GetRValue (clrColor))); + bInLink = false; + } + case _T ('b'): + case _T ('f'): + case _T ('i'): + case _T ('u'): + { + pstrText = pstrText.substr (1); + aFontArray.Remove (aFontArray.GetSize () - 1); + TFontInfo* pFontInfo = (TFontInfo*) aFontArray.GetAt (aFontArray.GetSize () - 1); + if (!pFontInfo) pFontInfo = pDefFontInfo; + if (pTm->tmItalic && pFontInfo->bItalic == false) { + ABC abc; + ::GetCharABCWidths (hDC, _T (' '), _T (' '), &abc); + pt.x += abc.abcC / 2; // һбŵ, ȷӦhttp://support.microsoft.com/kb/244798/en-us + } + pTm = &pFontInfo->tm; + ::SelectObject (hDC, pFontInfo->hFont); + cyLine = MAX (cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int) aPIndentArray.GetAt (aPIndentArray.GetSize () - 1)); + } + break; + } + while (!pstrText.empty () && pstrText[0] != _T ('>') && pstrText[0] != _T ('}')) pstrText = pstrText.substr (1); + pstrText = pstrText.substr (1); + } else if (!bInRaw && pstrText[0] == _T ('<') && pstrText[2] == _T ('>') && (pstrText[1] == _T ('{') || pstrText[1] == _T ('}'))) { + SIZE szSpace = { 0 }; + ::GetTextExtentPoint32 (hDC, &pstrText[1], 1, &szSpace); + if (bDraw && bLineDraw) ::TextOut (hDC, pt.x, pt.y + cyLineHeight - pTm->tmHeight - pTm->tmExternalLeading, &pstrText[1], 1); + pt.x += szSpace.cx; + cxMaxWidth = MAX (cxMaxWidth, pt.x); + pstrText = pstrText.substr (3); + } else if (!bInRaw && pstrText[0] == _T ('{') && pstrText[2] == _T ('}') && (pstrText[1] == _T ('<') || pstrText[1] == _T ('>'))) { + SIZE szSpace = { 0 }; + ::GetTextExtentPoint32 (hDC, &pstrText[1], 1, &szSpace); + if (bDraw && bLineDraw) ::TextOut (hDC, pt.x, pt.y + cyLineHeight - pTm->tmHeight - pTm->tmExternalLeading, &pstrText[1], 1); + pt.x += szSpace.cx; + cxMaxWidth = MAX (cxMaxWidth, pt.x); + pstrText = pstrText.substr (3); + } else if (!bInRaw && pstrText[0] == _T (' ')) { + SIZE szSpace = { 0 }; + ::GetTextExtentPoint32 (hDC, _T (" "), 1, &szSpace); + // Still need to paint the space because the font might have + // underline formatting. + if (bDraw && bLineDraw) ::TextOut (hDC, pt.x, pt.y + cyLineHeight - pTm->tmHeight - pTm->tmExternalLeading, _T (" "), 1); + pt.x += szSpace.cx; + cxMaxWidth = MAX (cxMaxWidth, pt.x); + pstrText = pstrText.substr (1); + } else { + POINT ptPos = pt; + int cchChars = 0; + int cchSize = 0; + int cchLastGoodWord = 0; + int cchLastGoodSize = 0; + string_view_t p = pstrText; + string_view_t pstrNext; + SIZE szText = { 0 }; + if (!bInRaw && p[0] == _T ('<') || p[0] == _T ('{')) p = p.substr (1), cchChars++, cchSize++; + while (p[0] != _T ('\0') && p[0] != _T ('\n')) { + // This part makes sure that we're word-wrapping if needed or providing support + // for DT_END_ELLIPSIS. Unfortunately the GetTextExtentPoint32() call is pretty + // slow when repeated so often. + // TODO: Rewrite and use GetTextExtentExPoint() instead! + if (bInRaw) { + if ((p[0] == _T ('<') || p[0] == _T ('{')) && p[1] == _T ('/') + && p[2] == _T ('r') && (p[3] == _T ('>') || p[3] == _T ('}'))) { + p = p.substr (4); + bInRaw = false; + break; + } + } else { + if (p[0] == _T ('<') || p[0] == _T ('{')) break; + } + pstrNext = p.substr (1); + cchChars++; + cchSize += (int) (pstrNext.data () - p.data ()); + szText.cx = cchChars * pTm->tmMaxCharWidth; + if (pt.x + szText.cx >= rc.right) { + ::GetTextExtentPoint32 (hDC, pstrText.data (), cchSize, &szText); + } + if (pt.x + szText.cx > rc.right) { + if (pt.x + szText.cx > rc.right && pt.x != rc.left) { + cchChars--; + cchSize -= (int) (pstrNext.data () - p.data ()); + } + if ((uStyle & DT_WORDBREAK) != 0 && cchLastGoodWord > 0) { + cchChars = cchLastGoodWord; + cchSize = cchLastGoodSize; + } + if ((uStyle & DT_END_ELLIPSIS) != 0 && cchChars > 0) { + cchChars -= 1; + string_view_t pstrPrev = &p.data ()[-1]; + if (cchChars > 0) { + cchChars -= 1; + pstrPrev = &pstrPrev.data ()[-1]; + cchSize -= (int) (p.data () - pstrPrev.data ()); + } else + cchSize -= (int) (p.data () - pstrPrev.data ()); + pt.x = rc.right; + } + bLineEnd = true; + cxMaxWidth = MAX (cxMaxWidth, pt.x); + break; + } + if (!((p[0] >= _T ('a') && p[0] <= _T ('z')) || (p[0] >= _T ('A') && p[0] <= _T ('Z')))) { + cchLastGoodWord = cchChars; + cchLastGoodSize = cchSize; + } + if (p[0] == _T (' ')) { + cchLastGoodWord = cchChars; + cchLastGoodSize = cchSize; + } + p = p.substr (1); + } + + ::GetTextExtentPoint32 (hDC, pstrText.data (), cchSize, &szText); + if (bDraw && bLineDraw) { + if ((uStyle & DT_SINGLELINE) == 0 && (uStyle & DT_CENTER) != 0) { + ptPos.x += (rc.right - rc.left - szText.cx) / 2; + } else if ((uStyle & DT_SINGLELINE) == 0 && (uStyle & DT_RIGHT) != 0) { + ptPos.x += (rc.right - rc.left - szText.cx); + } + ::TextOut (hDC, ptPos.x, ptPos.y + cyLineHeight - pTm->tmHeight - pTm->tmExternalLeading, pstrText.data (), cchSize); + if (pt.x >= rc.right && (uStyle & DT_END_ELLIPSIS) != 0) + ::TextOut (hDC, ptPos.x + szText.cx, ptPos.y, _T ("..."), 3); + } + pt.x += szText.cx; + cxMaxWidth = MAX (cxMaxWidth, pt.x); + pstrText = pstrText.substr (cchSize); + } + + if (pt.x >= rc.right || pstrText.empty () || pstrText[0] == _T ('\n')) bLineEnd = true; + if (bDraw && bLineEnd) { + if (!bLineDraw) { + aFontArray.Resize (aLineFontArray.GetSize ()); + ::CopyMemory (aFontArray.GetData (), aLineFontArray.GetData (), aLineFontArray.GetSize () * sizeof (LPVOID)); + aColorArray.Resize (aLineColorArray.GetSize ()); + ::CopyMemory (aColorArray.GetData (), aLineColorArray.GetData (), aLineColorArray.GetSize () * sizeof (LPVOID)); + aPIndentArray.Resize (aLinePIndentArray.GetSize ()); + ::CopyMemory (aPIndentArray.GetData (), aLinePIndentArray.GetData (), aLinePIndentArray.GetSize () * sizeof (LPVOID)); + + cyLineHeight = cyLine; + pstrText = pstrLineBegin; + bInRaw = bLineInRaw; + bInSelected = bLineInSelected; + + DWORD clrColor = dwTextColor; + if (aColorArray.GetSize () > 0) clrColor = (int) aColorArray.GetAt (aColorArray.GetSize () - 1); + ::SetTextColor (hDC, RGB (GetBValue (clrColor), GetGValue (clrColor), GetRValue (clrColor))); + TFontInfo* pFontInfo = (TFontInfo*) aFontArray.GetAt (aFontArray.GetSize () - 1); + if (!pFontInfo) pFontInfo = pDefFontInfo; + pTm = &pFontInfo->tm; + ::SelectObject (hDC, pFontInfo->hFont); + if (bInSelected) ::SetBkMode (hDC, OPAQUE); + } else { + aLineFontArray.Resize (aFontArray.GetSize ()); + ::CopyMemory (aLineFontArray.GetData (), aFontArray.GetData (), aFontArray.GetSize () * sizeof (LPVOID)); + aLineColorArray.Resize (aColorArray.GetSize ()); + ::CopyMemory (aLineColorArray.GetData (), aColorArray.GetData (), aColorArray.GetSize () * sizeof (LPVOID)); + aLinePIndentArray.Resize (aPIndentArray.GetSize ()); + ::CopyMemory (aLinePIndentArray.GetData (), aPIndentArray.GetData (), aPIndentArray.GetSize () * sizeof (LPVOID)); + pstrLineBegin = pstrText; + bLineInSelected = bInSelected; + bLineInRaw = bInRaw; + } + } + + ASSERT (iLinkIndex <= nLinkRects); + } + + nLinkRects = iLinkIndex; + + // Return size of text when requested + if ((uStyle & DT_CALCRECT) != 0) { + rc.bottom = MAX (cyMinHeight, pt.y + cyLine); + rc.right = MIN (rc.right, cxMaxWidth); + } + + if (bDraw) ::SelectClipRgn (hDC, hOldRgn); + ::DeleteObject (hOldRgn); + ::DeleteObject (hRgn); + + ::SelectObject (hDC, hOldFont); + } + + HBITMAP CRenderEngine::GenerateBitmap (CPaintManagerUI* pManager, RECT rc, CControlUI* pStopControl, DWORD dwFilterColor) { + if (!pManager) return NULL; + int cx = rc.right - rc.left; + int cy = rc.bottom - rc.top; + + bool bUseOffscreenBitmap = true; + HDC hPaintDC = ::CreateCompatibleDC (pManager->GetPaintDC ()); + ASSERT (hPaintDC); + HBITMAP hPaintBitmap = NULL; + //if (!pStopControl && !pManager->IsLayered()) hPaintBitmap = pManager->Get (); + if (!hPaintBitmap) { + bUseOffscreenBitmap = false; + hPaintBitmap = ::CreateCompatibleBitmap (pManager->GetPaintDC (), rc.right, rc.bottom); + ASSERT (hPaintBitmap); + } + HBITMAP hOldPaintBitmap = (HBITMAP) ::SelectObject (hPaintDC, hPaintBitmap); + if (!bUseOffscreenBitmap) { + CControlUI* pRoot = pManager->GetRoot (); + pRoot->Paint (hPaintDC, rc, pStopControl); + } + + BITMAPINFO bmi = { 0 }; + bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = cx; + bmi.bmiHeader.biHeight = cy; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = cx * cy * sizeof (DWORD); + LPDWORD pDest = nullptr; + HDC hCloneDC = ::CreateCompatibleDC (pManager->GetPaintDC ()); + HBITMAP hBitmap = ::CreateDIBSection (pManager->GetPaintDC (), &bmi, DIB_RGB_COLORS, (LPVOID*) &pDest, nullptr, 0); + ASSERT (hCloneDC); + ASSERT (hBitmap); + if (hBitmap) { + HBITMAP hOldBitmap = (HBITMAP) ::SelectObject (hCloneDC, hBitmap); + ::BitBlt (hCloneDC, 0, 0, cx, cy, hPaintDC, rc.left, rc.top, SRCCOPY); + RECT rcClone = { 0, 0, cx, cy }; + if (dwFilterColor > 0x00FFFFFF) DrawColor (hCloneDC, rcClone, dwFilterColor); + ::SelectObject (hCloneDC, hOldBitmap); + ::DeleteDC (hCloneDC); + ::GdiFlush (); + } + + // Cleanup + ::SelectObject (hPaintDC, hOldPaintBitmap); + if (!bUseOffscreenBitmap) ::DeleteObject (hPaintBitmap); + ::DeleteDC (hPaintDC); + + return hBitmap; + } + + HBITMAP CRenderEngine::GenerateBitmap (CPaintManagerUI* pManager, CControlUI* pControl, RECT rc, DWORD dwFilterColor) { + if (!pManager || !pControl) return NULL; + int cx = rc.right - rc.left; + int cy = rc.bottom - rc.top; + + HDC hPaintDC = ::CreateCompatibleDC (pManager->GetPaintDC ()); + HBITMAP hPaintBitmap = ::CreateCompatibleBitmap (pManager->GetPaintDC (), rc.right, rc.bottom); + ASSERT (hPaintDC); + ASSERT (hPaintBitmap); + HBITMAP hOldPaintBitmap = (HBITMAP) ::SelectObject (hPaintDC, hPaintBitmap); + pControl->Paint (hPaintDC, rc, nullptr); + + BITMAPINFO bmi = { 0 }; + bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = cx; + bmi.bmiHeader.biHeight = cy; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = cx * cy * sizeof (DWORD); + LPDWORD pDest = nullptr; + HDC hCloneDC = ::CreateCompatibleDC (pManager->GetPaintDC ()); + HBITMAP hBitmap = ::CreateDIBSection (pManager->GetPaintDC (), &bmi, DIB_RGB_COLORS, (LPVOID*) &pDest, nullptr, 0); + ASSERT (hCloneDC); + ASSERT (hBitmap); + if (hBitmap) { + HBITMAP hOldBitmap = (HBITMAP) ::SelectObject (hCloneDC, hBitmap); + ::BitBlt (hCloneDC, 0, 0, cx, cy, hPaintDC, rc.left, rc.top, SRCCOPY); + RECT rcClone = { 0, 0, cx, cy }; + if (dwFilterColor > 0x00FFFFFF) DrawColor (hCloneDC, rcClone, dwFilterColor); + ::SelectObject (hCloneDC, hOldBitmap); + ::DeleteDC (hCloneDC); + ::GdiFlush (); + } + + // Cleanup + ::SelectObject (hPaintDC, hOldPaintBitmap); + ::DeleteObject (hPaintBitmap); + ::DeleteDC (hPaintDC); + + return hBitmap; + } + + SIZE CRenderEngine::GetTextSize (HDC hDC, CPaintManagerUI* pManager, string_view_t pstrText, int iFont, UINT uStyle) { + SIZE size = { 0, 0 }; + ASSERT (::GetObjectType (hDC) == OBJ_DC || ::GetObjectType (hDC) == OBJ_MEMDC); + if (pstrText.empty () || !pManager) return size; + ::SetBkMode (hDC, TRANSPARENT); + HFONT hOldFont = (HFONT)::SelectObject (hDC, pManager->GetFont (iFont)); + GetTextExtentPoint32 (hDC, pstrText.data (), (int) pstrText.length (), &size); + ::SelectObject (hDC, hOldFont); + return size; + } + + void CRenderEngine::CheckAlphaColor (DWORD& dwColor) { + //RestoreAlphaColorΪ0x00000000͸GDIƵµ + //GDIв0xFF000000ɫֵڴRGB(0,0,1) + //RGB(0,0,1)RGB(0,0,0)ѷֳ + if ((0x00FFFFFF & dwColor) == 0) { + dwColor += 1; + } + } + + HBITMAP CRenderEngine::CreateARGB32Bitmap (HDC hDC, int cx, int cy, BYTE** pBits) { + LPBITMAPINFO lpbiSrc = nullptr; + lpbiSrc = (LPBITMAPINFO) new BYTE[sizeof (BITMAPINFOHEADER)]; + if (!lpbiSrc) return nullptr; + + lpbiSrc->bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + lpbiSrc->bmiHeader.biWidth = cx; + lpbiSrc->bmiHeader.biHeight = cy; + lpbiSrc->bmiHeader.biPlanes = 1; + lpbiSrc->bmiHeader.biBitCount = 32; + lpbiSrc->bmiHeader.biCompression = BI_RGB; + lpbiSrc->bmiHeader.biSizeImage = cx * cy; + lpbiSrc->bmiHeader.biXPelsPerMeter = 0; + lpbiSrc->bmiHeader.biYPelsPerMeter = 0; + lpbiSrc->bmiHeader.biClrUsed = 0; + lpbiSrc->bmiHeader.biClrImportant = 0; + + HBITMAP hBitmap = CreateDIBSection (hDC, lpbiSrc, DIB_RGB_COLORS, (void **) pBits, nullptr, 0); + delete[] lpbiSrc; + return hBitmap; + } + + void CRenderEngine::AdjustImage (bool bUseHSL, TImageInfo* imageInfo, short H, short S, short L) { + if (!imageInfo || !imageInfo->bUseHSL || !imageInfo->pBits || !imageInfo->pSrcBits + || (!imageInfo->hBitmap && !imageInfo->phBitmap && !*(imageInfo->phBitmap))) + return; + if (bUseHSL == false || (H == 180 && S == 100 && L == 100)) { + ::CopyMemory (imageInfo->pBits, imageInfo->pSrcBits, imageInfo->nX * imageInfo->nY * 4); + return; + } + + float fH, fS, fL; + float S1 = S / 100.0f; + float L1 = L / 100.0f; + for (int i = 0; i < imageInfo->nX * imageInfo->nY; i++) { + RGBtoHSL (*(DWORD*) (imageInfo->pSrcBits + i * 4), &fH, &fS, &fL); + fH += (H - 180); + fH = fH > 0 ? fH : fH + 360; + fS *= S1; + fL *= L1; + HSLtoRGB ((DWORD*) (imageInfo->pBits + i * 4), fH, fS, fL); + } + } + +} // namespace DuiLib diff --git a/DuiLib/Core/UIRender.h b/DuiLib/Core/UIRender.h new file mode 100644 index 0000000..0843a32 --- /dev/null +++ b/DuiLib/Core/UIRender.h @@ -0,0 +1,81 @@ +#ifndef __UIRENDER_H__ +#define __UIRENDER_H__ + +#pragma once + +#ifdef USE_XIMAGE_EFFECT +class CxImage; +#endif +namespace DuiLib { + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CRenderClip { + public: + virtual ~CRenderClip (); + RECT rcItem = { 0 }; + HDC hDC; + HRGN hRgn; + HRGN hOldRgn; + + static void GenerateClip (HDC hDC, RECT rc, CRenderClip& clip); + static void GenerateRoundClip (HDC hDC, RECT rc, RECT rcItem, int width, int height, CRenderClip& clip); + static void UseOldClipBegin (HDC hDC, CRenderClip& clip); + static void UseOldClipEnd (HDC hDC, CRenderClip& clip); + }; + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CRenderEngine { + public: + static DWORD AdjustColor (DWORD dwColor, short H, short S, short L); + static HBITMAP CreateARGB32Bitmap (HDC hDC, int cx, int cy, BYTE** pBits); + static void AdjustImage (bool bUseHSL, TImageInfo* imageInfo, short H, short S, short L); + static TImageInfo* LoadImage (std::variant bitmap, string_view_t type = _T (""), DWORD mask = 0, HINSTANCE instance = NULL); +#ifdef USE_XIMAGE_EFFECT + static CxImage *LoadGifImageX (std::variant bitmap, string_view_t type = _T (""), DWORD mask = 0); +#endif + static void FreeImage (TImageInfo* bitmap, bool bDelete = true); + static TImageInfo* LoadImage (string_view_t pStrImage, string_view_t type = _T (""), DWORD mask = 0, HINSTANCE instance = NULL); + static TImageInfo* LoadImage (UINT nID, string_view_t type = _T (""), DWORD mask = 0, HINSTANCE instance = NULL); + + static Gdiplus::Image *GdiplusLoadImage (string_view_t pstrPath); + static Gdiplus::Image* GdiplusLoadImage (LPVOID pBuf, size_t dwSize); + + static bool DrawIconImageString (HDC hDC, CPaintManagerUI* pManager, const RECT& rcItem, const RECT& rcPaint, \ + string_view_t pStrImage, string_view_t pStrModify = _T ("")); + static bool MakeFitIconDest (const RECT& rcControl, const SIZE& szIcon, const CDuiString& sAlign, RECT& rcDest); + + static void DrawText (HDC hDC, CPaintManagerUI* pManager, RECT& rc, string_view_t pstrText, DWORD dwTextColor, \ + int iFont, UINT uStyle, DWORD dwTextBKColor); + + static void DrawImage (HDC hDC, HBITMAP hBitmap, const RECT& rc, const RECT& rcPaint, \ + const RECT& rcBmpPart, const RECT& rcCorners, bool bAlpha, BYTE uFade = 255, + bool hole = false, bool xtiled = false, bool ytiled = false); + + static bool DrawImageInfo (HDC hDC, CPaintManagerUI* pManager, const RECT& rcItem, const RECT& rcPaint, const TDrawInfo* pDrawInfo, HINSTANCE instance = NULL); + static bool DrawImageString (HDC hDC, CPaintManagerUI* pManager, const RECT& rcItem, const RECT& rcPaint, string_view_t pStrImage, string_view_t pStrModify = _T (""), HINSTANCE instance = NULL); + + static void DrawColor (HDC hDC, const RECT& rc, DWORD color); + static void DrawGradient (HDC hDC, const RECT& rc, DWORD dwFirst, DWORD dwSecond, bool bVertical, int nSteps); + + // ºеɫalphaֵЧ + static void DrawLine (HDC hDC, const RECT& rc, int nSize, DWORD dwPenColor, int nStyle = PS_SOLID); + static void DrawRect (HDC hDC, const RECT& rc, int nSize, DWORD dwPenColor, int nStyle = PS_SOLID); + static void DrawRoundRect (HDC hDC, const RECT& rc, int width, int height, int nSize, DWORD dwPenColor, int nStyle = PS_SOLID); + static void DrawText (HDC hDC, CPaintManagerUI* pManager, RECT& rc, string_view_t pstrText, \ + DWORD dwTextColor, int iFont, UINT uStyle); + static void DrawHtmlText (HDC hDC, CPaintManagerUI* pManager, RECT& rc, string_view_t pstrText, + DWORD dwTextColor, RECT* pLinks, CDuiString* sLinks, int& nLinkRects, int iFont, UINT uStyle); + static HBITMAP GenerateBitmap (CPaintManagerUI* pManager, RECT rc, CControlUI* pStopControl = nullptr, DWORD dwFilterColor = 0); + static HBITMAP GenerateBitmap (CPaintManagerUI* pManager, CControlUI* pControl, RECT rc, DWORD dwFilterColor = 0); + static SIZE GetTextSize (HDC hDC, CPaintManagerUI* pManager, string_view_t pstrText, int iFont, UINT uStyle); + + //alpha utilities + static void CheckAlphaColor (DWORD& dwColor); + }; + +} // namespace DuiLib + +#endif // __UIRENDER_H__ diff --git a/DuiLib/Core/UIResourceManager.cpp b/DuiLib/Core/UIResourceManager.cpp new file mode 100644 index 0000000..faa6d7a --- /dev/null +++ b/DuiLib/Core/UIResourceManager.cpp @@ -0,0 +1,203 @@ +#include "StdAfx.h" +#include "UIResourceManager.h" + +namespace DuiLib { + + CResourceManager::CResourceManager (void) { + m_pQuerypInterface = nullptr; + + } + + CResourceManager::~CResourceManager (void) { + ResetResourceMap (); + } + + BOOL CResourceManager::LoadResource (std::variant xml, string_view_t type) { + if (xml.index () == 1) { + if (std::get<1> (xml)[0] == _T ('<')) { + if (!m_xml.Load (std::get<1> (xml).data ())) return FALSE; + } else { + if (!m_xml.LoadFromFile (std::get<1> (xml).data ())) return FALSE; + } + } else { + HRSRC hResource = ::FindResource (CPaintManagerUI::GetResourceDll (), MAKEINTRESOURCE (std::get<0> (xml)), type.data ()); + if (hResource == NULL) return FALSE; + HGLOBAL hGlobal = ::LoadResource (CPaintManagerUI::GetResourceDll (), hResource); + if (hGlobal == NULL) { + FreeResource (hResource); + return FALSE; + } + + if (!m_xml.LoadFromMem ((BYTE*) ::LockResource (hGlobal), ::SizeofResource (CPaintManagerUI::GetResourceDll (), hResource))) { + ::FreeResource (hResource); + return FALSE; + } + ::FreeResource (hResource); + } + + return LoadResource (m_xml.GetRoot ()); + } + + BOOL CResourceManager::LoadResource (CMarkupNode Root) { + if (!Root.IsValid ()) return FALSE; + + int nAttributes = 0; + + //ͼƬԴ + string_view_t pstrId = _T (""); + string_view_t pstrPath = _T (""); + for (CMarkupNode node = Root.GetChild (); node.IsValid (); node = node.GetSibling ()) { + string_view_t pstrClass = node.GetName (), pstrName = _T (""), pstrValue = _T (""); + CMarkupNode ChildNode = node.GetChild (); + if (ChildNode.IsValid ()) LoadResource (node); + else if (pstrClass == _T ("Image") && node.HasAttributes ()) { + //ͼƬԴ + nAttributes = node.GetAttributeCount (); + for (int i = 0; i < nAttributes; i++) { + pstrName = node.GetAttributeName (i); + pstrValue = node.GetAttributeValue (i); + + if (pstrName == _T ("id")) { + pstrId = pstrValue; + } else if (pstrName == _T ("path")) { + pstrPath = pstrValue; + } + } + if (pstrId.empty () || pstrPath.empty ()) continue; + CDuiString * pstrFind = static_cast(m_mImageHashMap.Find (pstrId)); + if (pstrFind) continue; + m_mImageHashMap.Insert (pstrId, (LPVOID)new CDuiString (pstrPath)); + } else if (pstrClass == _T ("Xml") && node.HasAttributes ()) { + //XMLļ + nAttributes = node.GetAttributeCount (); + for (int i = 0; i < nAttributes; i++) { + pstrName = node.GetAttributeName (i); + pstrValue = node.GetAttributeValue (i); + + if (pstrName == _T ("id")) { + pstrId = pstrValue; + } else if (pstrName == _T ("path")) { + pstrPath = pstrValue; + } + } + if (pstrId.empty () || pstrPath.empty ()) continue; + CDuiString * pstrFind = static_cast(m_mXmlHashMap.Find (pstrId)); + if (pstrFind) continue; + m_mXmlHashMap.Insert (pstrId, (LPVOID)new CDuiString (pstrPath)); + } else continue; + } + return TRUE; + } + + string_view_t CResourceManager::GetImagePath (string_view_t lpstrId) { + CDuiString * lpStr = static_cast(m_mImageHashMap.Find (lpstrId)); + return !lpStr ? _T ("") : lpStr->c_str (); + } + + string_view_t CResourceManager::GetXmlPath (string_view_t lpstrId) { + CDuiString * lpStr = static_cast(m_mXmlHashMap.Find (lpstrId)); + return !lpStr ? _T ("") : lpStr->c_str (); + } + + void CResourceManager::ResetResourceMap () { + for (int i = 0; i < m_mImageHashMap.GetSize (); i++) { + string_view_t key = m_mImageHashMap.GetAt (i)->Key; + if (!key.empty ()) + delete static_cast(m_mImageHashMap.Find (key)); + } + for (int i = 0; i < m_mXmlHashMap.GetSize (); i++) { + string_view_t key = m_mXmlHashMap.GetAt (i)->Key; + if (!key.empty ()) + delete static_cast(m_mXmlHashMap.Find (key)); + } + for (int i = 0; i < m_mTextResourceHashMap.GetSize (); i++) { + string_view_t key = m_mTextResourceHashMap.GetAt (i)->Key; + if (!key.empty ()) + delete static_cast(m_mTextResourceHashMap.Find (key)); + } + } + + BOOL CResourceManager::LoadLanguage (string_view_t pstrXml) { + CMarkup xml; + if (pstrXml[0] == _T ('<')) { + if (!xml.Load (pstrXml.data ())) return FALSE; + } else { + if (!xml.LoadFromFile (pstrXml.data ())) return FALSE; + } + CMarkupNode Root = xml.GetRoot (); + if (!Root.IsValid ()) return FALSE; + + string_view_t pstrClass = _T (""); + int nAttributes = 0; + string_view_t pstrName = _T (""); + string_view_t pstrValue = _T (""); + + //ͼƬԴ + string_view_t pstrId = _T (""); + string_view_t pstrText = _T (""); + for (CMarkupNode node = Root.GetChild (); node.IsValid (); node = node.GetSibling ()) { + pstrClass = node.GetName (); + if (pstrClass == _T ("Text") && node.HasAttributes ()) { + //ͼƬԴ + nAttributes = node.GetAttributeCount (); + for (int i = 0; i < nAttributes; i++) { + pstrName = node.GetAttributeName (i); + pstrValue = node.GetAttributeValue (i); + + if (pstrName == _T ("id")) { + pstrId = pstrValue; + } else if (pstrName == _T ("value")) { + pstrText = pstrValue; + } + } + if (pstrId.empty () || pstrText.empty ()) continue; + + CDuiString *lpstrFind = static_cast(m_mTextResourceHashMap.Find (pstrId)); + if (lpstrFind) { + *lpstrFind = pstrText; + } else { + m_mTextResourceHashMap.Insert (pstrId, (LPVOID)new CDuiString (pstrText)); + } + } else continue; + } + + return TRUE; + } + + CDuiString CResourceManager::GetText (string_view_t lpstrId, string_view_t lpstrType) { + if (lpstrId.empty ()) return _T (""); + + CDuiString *lpstrFind = static_cast(m_mTextResourceHashMap.Find (lpstrId)); + if (!lpstrFind && m_pQuerypInterface) { + lpstrFind = new CDuiString (m_pQuerypInterface->QueryControlText (lpstrId, lpstrType)); + m_mTextResourceHashMap.Insert (lpstrId, (LPVOID) lpstrFind); + } + return !lpstrFind ? lpstrId : *lpstrFind; + } + + void CResourceManager::ReloadText () { + if (!m_pQuerypInterface) return; + // + for (int i = 0; i < m_mTextResourceHashMap.GetSize (); i++) { + string_view_t lpstrId = m_mTextResourceHashMap.GetAt (i)->Key; + if (lpstrId.empty ()) continue; + string_view_t lpstrText = m_pQuerypInterface->QueryControlText (lpstrId, nullptr); + if (!lpstrText.empty ()) { + CDuiString *lpStr = static_cast (m_mTextResourceHashMap.Find (lpstrId)); + *lpStr = lpstrText; + } + } + } + void CResourceManager::ResetTextMap () { + CDuiString * lpStr; + for (int i = 0; i < m_mTextResourceHashMap.GetSize (); i++) { + string_view_t key = m_mTextResourceHashMap.GetAt (i)->Key; + if (!key.empty ()) { + lpStr = static_cast(m_mTextResourceHashMap.Find (key)); + delete lpStr; + } + } + } + + +} // namespace DuiLib \ No newline at end of file diff --git a/DuiLib/Core/UIResourceManager.h b/DuiLib/Core/UIResourceManager.h new file mode 100644 index 0000000..ba7bb95 --- /dev/null +++ b/DuiLib/Core/UIResourceManager.h @@ -0,0 +1,58 @@ +#ifndef __UIRESOURCEMANAGER_H__ +#define __UIRESOURCEMANAGER_H__ +#pragma once + +namespace DuiLib { + // ؼֲѯӿ + class UILIB_API IQueryControlText { + public: + virtual string_view_t QueryControlText (string_view_t lpstrId, string_view_t lpstrType) = 0; + }; + + class UILIB_API CResourceManager { + private: + CResourceManager (void); + virtual ~CResourceManager (void); + + public: + static CResourceManager* GetInstance () { + static CResourceManager * p = new CResourceManager; + return p; + }; + void Release (void) { + delete this; + } + + public: + BOOL LoadResource (std::variant xml, string_view_t type = _T ("")); + BOOL LoadResource (CMarkupNode Root); + void ResetResourceMap (); + string_view_t GetImagePath (string_view_t lpstrId); + string_view_t GetXmlPath (string_view_t lpstrId); + + public: + void SetLanguage (string_view_t pstrLanguage) { m_sLauguage = pstrLanguage; } + string_view_t GetLanguage () { return m_sLauguage; } + BOOL LoadLanguage (string_view_t pstrXml); + + public: + void SetTextQueryInterface (IQueryControlText* pInterface) { + m_pQuerypInterface = pInterface; + } + CDuiString GetText (string_view_t lpstrId, string_view_t lpstrType = _T ("")); + void ReloadText (); + void ResetTextMap (); + + private: + CStdStringPtrMap m_mTextResourceHashMap; + IQueryControlText *m_pQuerypInterface; + CStdStringPtrMap m_mImageHashMap; + CStdStringPtrMap m_mXmlHashMap; + CMarkup m_xml; + CDuiString m_sLauguage; + CStdStringPtrMap m_mTextHashMap; + }; + +} // namespace DuiLib + +#endif // __UIRESOURCEMANAGER_H__ \ No newline at end of file diff --git a/DuiLib/DuiLib.vcxproj b/DuiLib/DuiLib.vcxproj new file mode 100644 index 0000000..3c9248f --- /dev/null +++ b/DuiLib/DuiLib.vcxproj @@ -0,0 +1,1251 @@ + + + + + DebugA + Win32 + + + DebugA + x64 + + + Debug + Win32 + + + Debug + x64 + + + LTL + Win32 + + + LTL + x64 + + + ReleaseA + Win32 + + + ReleaseA + x64 + + + Release + Win32 + + + Release + x64 + + + SDebugA + Win32 + + + SDebugA + x64 + + + SDebug + Win32 + + + SDebug + x64 + + + SReleaseA + Win32 + + + SReleaseA + x64 + + + SRelease + Win32 + + + SRelease + x64 + + + + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E} + DuiLib + 10.0.17134.0 + + + + DynamicLibrary + Unicode + v141 + + + DynamicLibrary + Unicode + v141 + + + StaticLibrary + Unicode + v141 + + + StaticLibrary + Unicode + v141 + + + StaticLibrary + Unicode + v141 + + + StaticLibrary + Unicode + v141 + + + StaticLibrary + MultiByte + v141 + + + StaticLibrary + MultiByte + v141 + + + DynamicLibrary + MultiByte + v141 + + + DynamicLibrary + MultiByte + v141 + + + DynamicLibrary + Unicode + v141 + + + DynamicLibrary + Unicode + v141 + + + StaticLibrary + Unicode + v141 + + + StaticLibrary + Unicode + v141 + + + StaticLibrary + MultiByte + v141 + + + StaticLibrary + MultiByte + v141 + + + DynamicLibrary + MultiByte + v141 + + + DynamicLibrary + MultiByte + v141 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + + + + + + + + + + + + + + + + + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + + + + + + + + + + + + + + + + + + + + + $(ProjectName)_d + $(ProjectName)_64d + $(ProjectName)_sd + $(ProjectName)_64sd + $(ProjectName)A_sd + $(ProjectName)A_64sd + $(ProjectName)A_d + $(ProjectName)A_64d + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)Temp\$(Configuration)\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(SolutionDir)lib\ + $(ProjectName)A + $(ProjectName)A_64 + $(ProjectName)A_s + $(ProjectName)A_64s + + + $(ProjectName)_64 + + + $(ProjectName)_s + + + $(ProjectName)_s + + + $(ProjectName)_64s + + + $(ProjectName)_64s + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Debug/DuiLib.tlb + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;UILIB_EXPORTS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + Use + Level4 + true + ProgramDatabase + 1Byte + stdcpp17 + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + true + $(OutDir)$(TargetName).pdb + 0x11000000 + MachineX86 + $(SolutionDir)Lib\$(ProjectName)_d.lib + + + true + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + .\Debug/DuiLib.tlb + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;UILIB_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + Use + Level3 + true + ProgramDatabase + stdcpp17 + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + true + $(OutDir)$(TargetName).pdb + 0x11000000 + $(SolutionDir)Lib\$(ProjectName)_d.lib + + + true + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Debug/DuiLib.tlb + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;UILIB_STATIC;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + Use + Level3 + true + ProgramDatabase + stdcpp17 + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + true + $(OutDir)$(TargetName).pdb + 0x11000000 + MachineX86 + $(SolutionDir)Lib\$(ProjectName)_d.lib + + + true + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + .\Debug/DuiLib.tlb + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;UILIB_STATIC;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Use + Level3 + true + ProgramDatabase + stdcpp17 + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + true + $(OutDir)$(TargetName).pdb + 0x11000000 + $(SolutionDir)Lib\$(ProjectName)_d.lib + + + true + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Debug/DuiLib.tlb + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;UILIB_STATIC;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + Use + Level3 + true + ProgramDatabase + stdcpp17 + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + true + $(OutDir)$(TargetName).pdb + 0x11000000 + MachineX86 + $(SolutionDir)Lib\$(ProjectName)_d.lib + + + true + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + .\Debug/DuiLib.tlb + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;UILIB_STATIC;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Use + Level3 + true + ProgramDatabase + stdcpp17 + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + true + $(OutDir)$(TargetName).pdb + 0x11000000 + $(SolutionDir)Lib\$(ProjectName)_d.lib + + + true + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Debug/DuiLib.tlb + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;UILIB_EXPORTS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + Use + Level3 + true + ProgramDatabase + stdcpp17 + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + true + $(OutDir)$(TargetName).pdb + 0x11000000 + MachineX86 + $(SolutionDir)Lib\$(ProjectName)A_d.lib + + + true + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + .\Debug/DuiLib.tlb + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;UILIB_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + Use + Level3 + true + ProgramDatabase + stdcpp17 + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + true + $(OutDir)$(TargetName).pdb + 0x11000000 + $(SolutionDir)Lib\$(ProjectName)A_d.lib + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/DuiLib.tlb + + + + + Disabled + OnlyExplicitInline + WIN32;NDEBUG;_WINDOWS;UILIB_EXPORTS;%(PreprocessorDefinitions) + true + true + Use + Level3 + true + MultiThreaded + stdcpp17 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0406 + + + true + 0x11000000 + MachineX86 + $(OutDir)$(TargetName).pdb + $(SolutionDir)Lib\$(ProjectName).lib + true + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + .\Release/DuiLib.tlb + + + + + MinSpace + OnlyExplicitInline + WIN32;NDEBUG;_WINDOWS;UILIB_EXPORTS;%(PreprocessorDefinitions) + true + true + Use + Level3 + true + MultiThreaded + stdcpp17 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0406 + + + true + 0x11000000 + $(OutDir)$(TargetName).pdb + $(SolutionDir)Lib\$(ProjectName).lib + true + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/DuiLib.tlb + + + + + MinSpace + OnlyExplicitInline + WIN32;NDEBUG;_WINDOWS;UILIB_STATIC;%(PreprocessorDefinitions) + true + true + Use + Level3 + true + MultiThreaded + stdcpp17 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0406 + + + true + 0x11000000 + MachineX86 + $(OutDir)$(TargetName).pdb + $(SolutionDir)Lib\$(ProjectName).lib + true + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/DuiLib.tlb + + + + + MinSpace + OnlyExplicitInline + WIN32;NDEBUG;_WINDOWS;UILIB_STATIC;%(PreprocessorDefinitions) + true + true + Use + Level3 + true + MultiThreaded + stdcpp17 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0406 + + + true + 0x11000000 + MachineX86 + $(OutDir)$(TargetName).pdb + $(SolutionDir)Lib\$(ProjectName).lib + true + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + .\Release/DuiLib.tlb + + + + + MinSpace + OnlyExplicitInline + WIN32;NDEBUG;_WINDOWS;UILIB_STATIC;%(PreprocessorDefinitions) + true + true + Use + Level3 + true + MultiThreaded + stdcpp17 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0406 + + + true + 0x11000000 + $(OutDir)$(TargetName).pdb + $(SolutionDir)Lib\$(ProjectName).lib + true + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + .\Release/DuiLib.tlb + + + + + MinSpace + OnlyExplicitInline + WIN32;NDEBUG;_WINDOWS;UILIB_STATIC;%(PreprocessorDefinitions) + true + true + Use + Level3 + true + MultiThreaded + stdcpp17 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0406 + + + true + 0x11000000 + $(OutDir)$(TargetName).pdb + $(SolutionDir)Lib\$(ProjectName).lib + true + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/DuiLib.tlb + + + + + MinSpace + OnlyExplicitInline + WIN32;NDEBUG;_WINDOWS;UILIB_STATIC;%(PreprocessorDefinitions) + true + true + Use + Level3 + true + MultiThreaded + stdcpp17 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0406 + + + true + 0x11000000 + MachineX86 + $(OutDir)$(TargetName).pdb + $(SolutionDir)Lib\$(ProjectName).lib + true + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + .\Release/DuiLib.tlb + + + + + MinSpace + OnlyExplicitInline + WIN32;NDEBUG;_WINDOWS;UILIB_STATIC;%(PreprocessorDefinitions) + true + true + Use + Level3 + true + MultiThreaded + stdcpp17 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0406 + + + true + 0x11000000 + $(OutDir)$(TargetName).pdb + $(SolutionDir)Lib\$(ProjectName).lib + true + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/DuiLib.tlb + + + + + MinSpace + OnlyExplicitInline + WIN32;NDEBUG;_WINDOWS;UILIB_EXPORTS;%(PreprocessorDefinitions) + true + true + Use + Level3 + true + MultiThreadedDLL + stdcpp17 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0406 + + + true + 0x11000000 + MachineX86 + $(OutDir)$(TargetName).pdb + $(SolutionDir)Lib\$(ProjectName)A.lib + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + .\Release/DuiLib.tlb + + + + + MinSpace + OnlyExplicitInline + WIN32;NDEBUG;_WINDOWS;UILIB_EXPORTS;%(PreprocessorDefinitions) + true + true + Use + Level3 + true + MultiThreadedDLL + stdcpp17 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0406 + + + true + 0x11000000 + $(OutDir)$(TargetName).pdb + $(SolutionDir)Lib\$(ProjectName)A.lib + + + true + + + + + Create + Create + Create + Create + Create + Create + Create + Create + Create + Create + Create + Create + Create + Create + Create + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DuiLib/DuiLib.vcxproj.filters b/DuiLib/DuiLib.vcxproj.filters new file mode 100644 index 0000000..1b7c691 --- /dev/null +++ b/DuiLib/DuiLib.vcxproj.filters @@ -0,0 +1,404 @@ + + + + + {64d71d85-4b00-457e-afb3-41e8143f5f4b} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {4e361078-6cc0-44ee-9671-df894ec1d0e3} + + + {3b45530c-ea84-4dc7-b478-4ff1466a252a} + + + {ede7728d-c1aa-4572-9dec-7688cb614cad} + + + {bb533e20-fb1d-4955-83b5-07b22790e264} + + + {a6233711-960e-41c9-888d-d8037ea47d50} + h;hpp;hxx;hm;inl + + + {234d7b68-8c83-42eb-a285-2365598e66f9} + + + {a844aec6-0bc6-44b5-be57-89d8bd6d16bc} + + + {1a50d606-2791-4fc8-8a6f-fd64b1ebc721} + + + {77af9153-b795-4ed6-bcab-50a6b1c6741c} + + + {abd5beb9-c75f-4d64-9af5-0bfb91ebbf5e} + + + + + 源文件 + + + 源文件 + + + 源文件\Utils + + + 源文件\Utils + + + 源文件\Core + + + 源文件\Core + + + 源文件\Core + + + 源文件\Core + + + 源文件\Core + + + 源文件\Core + + + 源文件\Core + + + 源文件\Layout + + + 源文件\Layout + + + 源文件\Layout + + + 源文件\Layout + + + 源文件\Layout + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Utils + + + 源文件\Control + + + 源文件\Control + + + 源文件\Layout + + + 源文件\Core + + + 源文件\Control + + + 源文件\Utils + + + 源文件\Core + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Utils + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Utils + + + 源文件\Utils + + + 源文件\Control + + + 源文件\Control + + + 源文件\Utils + + + + + 头文件 + + + 头文件 + + + 头文件\Utils + + + 头文件\Utils + + + 头文件\Utils + + + 头文件\Utils + + + 头文件\Utils + + + 头文件\Utils + + + 头文件\Core + + + 头文件\Core + + + 头文件\Core + + + 头文件\Core + + + 头文件\Core + + + 头文件\Core + + + 头文件\Core + + + 头文件\Core + + + 头文件\Layout + + + 头文件\Layout + + + 头文件\Layout + + + 头文件\Layout + + + 头文件\Layout + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Utils + + + 头文件\Control + + + 头文件\Utils + + + 头文件\Control + + + 头文件\Layout + + + 头文件\Core + + + 头文件\Control + + + 头文件\Utils + + + 头文件\Core + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Utils + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Utils + + + 头文件\Utils + + + 头文件\Control + + + 头文件\Control + + + 头文件\Utils + + + 头文件\Utils + + + 头文件\Utils + + + 头文件\Bind + + + 头文件\Bind + + + \ No newline at end of file diff --git a/DuiLib/Layout/StdAfx.h b/DuiLib/Layout/StdAfx.h new file mode 100644 index 0000000..c07aaba --- /dev/null +++ b/DuiLib/Layout/StdAfx.h @@ -0,0 +1 @@ +#include "../StdAfx.h" \ No newline at end of file diff --git a/DuiLib/Layout/UIAnimationTabLayout.cpp b/DuiLib/Layout/UIAnimationTabLayout.cpp new file mode 100644 index 0000000..cafc873 --- /dev/null +++ b/DuiLib/Layout/UIAnimationTabLayout.cpp @@ -0,0 +1,113 @@ +#include "StdAfx.h" +#include "UIAnimationTabLayout.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CAnimationTabLayoutUI) + + CAnimationTabLayoutUI::CAnimationTabLayoutUI (): CUIAnimation (this) {} + + string_view_t CAnimationTabLayoutUI::GetClass () const { + return _T ("AnimationTabLayoutUI"); + } + + LPVOID CAnimationTabLayoutUI::GetInterface (string_view_t pstrName) { + if (pstrName == _T ("AnimationTabLayout")) + return static_cast(this); + return CTabLayoutUI::GetInterface (pstrName); + } + + bool CAnimationTabLayoutUI::SelectItem (int iIndex) { + if (iIndex < 0 || iIndex >= m_items.GetSize ()) return false; + if (iIndex == m_iCurSel) return true; + if (iIndex > m_iCurSel) m_nPositiveDirection = -1; + if (iIndex < m_iCurSel) m_nPositiveDirection = 1; + + int iOldSel = m_iCurSel; + m_iCurSel = iIndex; + for (int it = 0; it < m_items.GetSize (); it++) { + if (it == iIndex) { + GetItemAt (it)->SetVisible (true); + GetItemAt (it)->SetFocus (); + m_bControlVisibleFlag = false; + m_pCurrentControl = static_cast(m_items[it]); + + } else GetItemAt (it)->SetVisible (false); + } + + NeedParentUpdate (); + if (nullptr != m_pCurrentControl) m_pCurrentControl->SetVisible (false); + AnimationSwitch (); + + if (m_pManager) { + m_pManager->SetNextTabControl (); + m_pManager->SendNotify (this, _T ("tabselect"), m_iCurSel, iOldSel); + } + return true; + } + + void CAnimationTabLayoutUI::AnimationSwitch () { + m_rcItemOld = m_rcItem; + if (!m_bIsVerticalDirection) { + m_rcCurPos.top = m_rcItem.top; + m_rcCurPos.bottom = m_rcItem.bottom; + m_rcCurPos.left = m_rcItem.left - (m_rcItem.right - m_rcItem.left) * m_nPositiveDirection + 52 * m_nPositiveDirection; + m_rcCurPos.right = m_rcItem.right - (m_rcItem.right - m_rcItem.left) * m_nPositiveDirection + 52 * m_nPositiveDirection; + } else { + m_rcCurPos.left = m_rcItem.left; + m_rcCurPos.right = m_rcItem.right; + m_rcCurPos.top = m_rcItem.top - (m_rcItem.bottom - m_rcItem.top) * m_nPositiveDirection; + m_rcCurPos.bottom = m_rcItem.bottom - (m_rcItem.bottom - m_rcItem.top) * m_nPositiveDirection; + } + + StopAnimation (TAB_ANIMATION_ID); + StartAnimation (TAB_ANIMATION_ELLAPSE, TAB_ANIMATION_FRAME_COUNT, TAB_ANIMATION_ID); + } + + void CAnimationTabLayoutUI::DoEvent (TEventUI& event) { + if (event.Type == UIEVENT_TIMER) { + OnTimer ((int) event.wParam); + } + __super::DoEvent (event); + } + + void CAnimationTabLayoutUI::OnTimer (int nTimerID) { + OnAnimationElapse (nTimerID); + } + + void CAnimationTabLayoutUI::OnAnimationStep (INT nTotalFrame, INT nCurFrame, INT nAnimationID) { + if (!m_bControlVisibleFlag) { + m_bControlVisibleFlag = true; + m_pCurrentControl->SetVisible (true); + } + + int iStepLen = 0; + if (!m_bIsVerticalDirection) { + iStepLen = (m_rcItemOld.right - m_rcItemOld.left) * m_nPositiveDirection / nTotalFrame; + if (nCurFrame != nTotalFrame) { + m_rcCurPos.left = m_rcCurPos.left + iStepLen; + m_rcCurPos.right = m_rcCurPos.right + iStepLen; + } else { + m_rcItem = m_rcCurPos = m_rcItemOld; + } + } else { + iStepLen = (m_rcItemOld.bottom - m_rcItemOld.top) * m_nPositiveDirection / nTotalFrame; + if (nCurFrame != nTotalFrame) { + m_rcCurPos.top = m_rcCurPos.top + iStepLen; + m_rcCurPos.bottom = m_rcCurPos.bottom + iStepLen; + } else { + m_rcItem = m_rcCurPos = m_rcItemOld; + } + } + SetPos (m_rcCurPos); + } + + void CAnimationTabLayoutUI::OnAnimationStop (INT nAnimationID) { + SetPos (m_rcItemOld); + NeedParentUpdate (); + } + + void CAnimationTabLayoutUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("animation_direction") && pstrValue == _T ("vertical")) m_bIsVerticalDirection = true; // pstrValue = "vertical" or "horizontal" + return CTabLayoutUI::SetAttribute (pstrName, pstrValue); + } +} // namespace DuiLib \ No newline at end of file diff --git a/DuiLib/Layout/UIAnimationTabLayout.h b/DuiLib/Layout/UIAnimationTabLayout.h new file mode 100644 index 0000000..fdbbc88 --- /dev/null +++ b/DuiLib/Layout/UIAnimationTabLayout.h @@ -0,0 +1,39 @@ +#ifndef __UIANIMATIONTABLAYOUT_H__ +#define __UIANIMATIONTABLAYOUT_H__ + +namespace DuiLib { + class UILIB_API CAnimationTabLayoutUI: public CTabLayoutUI, public CUIAnimation { + DECLARE_DUICONTROL (CAnimationTabLayoutUI) + public: + CAnimationTabLayoutUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + bool SelectItem (int iIndex); + void AnimationSwitch (); + void DoEvent (TEventUI& event); + void OnTimer (int nTimerID); + + virtual void OnAnimationStart (INT nAnimationID, BOOL bFirstLoop) {} + virtual void OnAnimationStep (INT nTotalFrame, INT nCurFrame, INT nAnimationID); + virtual void OnAnimationStop (INT nAnimationID); + + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + protected: + bool m_bIsVerticalDirection = false; + int m_nPositiveDirection = 1; + RECT m_rcCurPos = { 0 }; + RECT m_rcItemOld = { 0 }; + CControlUI *m_pCurrentControl = nullptr; + bool m_bControlVisibleFlag = false; + enum { + TAB_ANIMATION_ID = 1, + + TAB_ANIMATION_ELLAPSE = 10, + TAB_ANIMATION_FRAME_COUNT = 15, + }; + }; +} +#endif // __UIANIMATIONTABLAYOUT_H__ \ No newline at end of file diff --git a/DuiLib/Layout/UIChildLayout.cpp b/DuiLib/Layout/UIChildLayout.cpp new file mode 100644 index 0000000..43472da --- /dev/null +++ b/DuiLib/Layout/UIChildLayout.cpp @@ -0,0 +1,44 @@ +#include "StdAfx.h" +#include "UIChildLayout.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CChildLayoutUI) + + CChildLayoutUI::CChildLayoutUI () {} + + void CChildLayoutUI::Init () { + if (!m_pstrXMLFile.empty ()) { + CDialogBuilder builder; + CContainerUI* pChildWindow = static_cast(builder.Create (m_pstrXMLFile, (UINT) 0, nullptr, m_pManager)); + if (pChildWindow) { + this->Add (pChildWindow); + } else { + this->RemoveAll (); + } + } + } + + void CChildLayoutUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("xmlfile")) + SetChildLayoutXML (pstrValue); + else + CContainerUI::SetAttribute (pstrName, pstrValue); + } + + void CChildLayoutUI::SetChildLayoutXML (CDuiString pXML) { + m_pstrXMLFile = pXML; + } + + CDuiString CChildLayoutUI::GetChildLayoutXML () { + return m_pstrXMLFile; + } + + LPVOID CChildLayoutUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_CHILDLAYOUT) return static_cast(this); + return CControlUI::GetInterface (pstrName); + } + + string_view_t CChildLayoutUI::GetClass () const { + return _T ("ChildLayoutUI"); + } +} // namespace DuiLib diff --git a/DuiLib/Layout/UIChildLayout.h b/DuiLib/Layout/UIChildLayout.h new file mode 100644 index 0000000..49841b0 --- /dev/null +++ b/DuiLib/Layout/UIChildLayout.h @@ -0,0 +1,23 @@ +#ifndef __UICHILDLAYOUT_H__ +#define __UICHILDLAYOUT_H__ + +#pragma once + +namespace DuiLib { + class UILIB_API CChildLayoutUI: public CContainerUI { + DECLARE_DUICONTROL (CChildLayoutUI) + public: + CChildLayoutUI (); + + void Init (); + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + void SetChildLayoutXML (CDuiString pXML); + CDuiString GetChildLayoutXML (); + virtual LPVOID GetInterface (string_view_t pstrName); + virtual string_view_t GetClass () const; + + private: + CDuiString m_pstrXMLFile; + }; +} // namespace DuiLib +#endif // __UICHILDLAYOUT_H__ diff --git a/DuiLib/Layout/UIHorizontalLayout.cpp b/DuiLib/Layout/UIHorizontalLayout.cpp new file mode 100644 index 0000000..0a213f0 --- /dev/null +++ b/DuiLib/Layout/UIHorizontalLayout.cpp @@ -0,0 +1,314 @@ +#include "StdAfx.h" +#include "UIHorizontalLayout.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CHorizontalLayoutUI) + CHorizontalLayoutUI::CHorizontalLayoutUI () { + ::ZeroMemory (&m_rcNewPos, sizeof (m_rcNewPos)); + } + + string_view_t CHorizontalLayoutUI::GetClass () const { + return _T ("HorizontalLayoutUI"); + } + + LPVOID CHorizontalLayoutUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_HORIZONTALLAYOUT) return static_cast(this); + return CContainerUI::GetInterface (pstrName); + } + + UINT CHorizontalLayoutUI::GetControlFlags () const { + if (IsEnabled () && m_iSepWidth != 0) return UIFLAG_SETCURSOR; + else return 0; + } + + void CHorizontalLayoutUI::SetPos (RECT rc, bool bNeedInvalidate) { + CControlUI::SetPos (rc, bNeedInvalidate); + rc = m_rcItem; + + // Adjust for inset + RECT _rcInset = CHorizontalLayoutUI::m_rcInset; + GetManager ()->GetDPIObj ()->Scale (&_rcInset); + rc.left += _rcInset.left; + rc.top += _rcInset.top; + rc.right -= _rcInset.right; + rc.bottom -= _rcInset.bottom; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + + if (m_items.GetSize () == 0) { + ProcessScrollBar (rc, 0, 0); + return; + } + + // Determine the minimum size + SIZE szAvailable = { rc.right - rc.left, rc.bottom - rc.top }; + //if( m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible() ) + // szAvailable.cx += m_pHorizontalScrollBar->GetScrollRange(); + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) + szAvailable.cy += m_pVerticalScrollBar->GetScrollRange (); + + int cyNeeded = 0; + int nAdjustables = 0; + int cxFixed = 0; + int nEstimateNum = 0; + SIZE szControlAvailable = { 0 }; + int iControlMaxWidth = 0; + int iControlMaxHeight = 0; + for (int it1 = 0; it1 < m_items.GetSize (); it1++) { + CControlUI* pControl = static_cast(m_items[it1]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) continue; + szControlAvailable = szAvailable; + RECT rcPadding = pControl->GetPadding (); + szControlAvailable.cy -= rcPadding.top + rcPadding.bottom; + iControlMaxWidth = pControl->GetFixedWidth (); + iControlMaxHeight = pControl->GetFixedHeight (); + if (iControlMaxWidth <= 0) iControlMaxWidth = pControl->GetMaxWidth (); + if (iControlMaxHeight <= 0) iControlMaxHeight = pControl->GetMaxHeight (); + if (szControlAvailable.cx > iControlMaxWidth) szControlAvailable.cx = iControlMaxWidth; + if (szControlAvailable.cy > iControlMaxHeight) szControlAvailable.cy = iControlMaxHeight; + SIZE sz = pControl->EstimateSize (szControlAvailable); + if (sz.cx == 0) { + nAdjustables++; + } else { + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + if (sz.cx > pControl->GetMaxWidth ()) sz.cx = pControl->GetMaxWidth (); + } + cxFixed += sz.cx + pControl->GetPadding ().left + pControl->GetPadding ().right; + + sz.cy = MAX (sz.cy, 0); + if (sz.cy < pControl->GetMinHeight ()) sz.cy = pControl->GetMinHeight (); + if (sz.cy > pControl->GetMaxHeight ()) sz.cy = pControl->GetMaxHeight (); + cyNeeded = MAX (cyNeeded, sz.cy + rcPadding.top + rcPadding.bottom); + nEstimateNum++; + } + cxFixed += (nEstimateNum - 1) * m_iChildPadding; + + // Place elements + int cxNeeded = 0; + int cxExpand = 0; + if (nAdjustables > 0) cxExpand = MAX (0, (szAvailable.cx - cxFixed) / nAdjustables); + // Position the elements + SIZE szRemaining = szAvailable; + int iPosX = rc.left; + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + iPosX -= m_pHorizontalScrollBar->GetScrollPos (); + } + int iEstimate = 0; + int iAdjustable = 0; + int cxFixedRemaining = cxFixed; + for (int it2 = 0; it2 < m_items.GetSize (); it2++) { + CControlUI* pControl = static_cast(m_items[it2]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) { + SetFloatPos (it2); + continue; + } + + iEstimate += 1; + RECT rcPadding = pControl->GetPadding (); + szRemaining.cx -= rcPadding.left; + + szControlAvailable = szRemaining; + szControlAvailable.cy -= rcPadding.top + rcPadding.bottom; + iControlMaxWidth = pControl->GetFixedWidth (); + iControlMaxHeight = pControl->GetFixedHeight (); + if (iControlMaxWidth <= 0) iControlMaxWidth = pControl->GetMaxWidth (); + if (iControlMaxHeight <= 0) iControlMaxHeight = pControl->GetMaxHeight (); + if (szControlAvailable.cx > iControlMaxWidth) szControlAvailable.cx = iControlMaxWidth; + if (szControlAvailable.cy > iControlMaxHeight) szControlAvailable.cy = iControlMaxHeight; + cxFixedRemaining = cxFixedRemaining - (rcPadding.left + rcPadding.right); + if (iEstimate > 1) cxFixedRemaining = cxFixedRemaining - m_iChildPadding; + SIZE sz = pControl->EstimateSize (szControlAvailable); + if (sz.cx == 0) { + iAdjustable++; + sz.cx = cxExpand; + // Distribute remaining to last element (usually round-off left-overs) + if (iAdjustable == nAdjustables) { + sz.cx = MAX (0, szRemaining.cx - rcPadding.right - cxFixedRemaining); + } + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + if (sz.cx > pControl->GetMaxWidth ()) sz.cx = pControl->GetMaxWidth (); + } else { + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + if (sz.cx > pControl->GetMaxWidth ()) sz.cx = pControl->GetMaxWidth (); + cxFixedRemaining -= sz.cx; + } + + sz.cy = pControl->GetMaxHeight (); + if (sz.cy == 0) sz.cy = szAvailable.cy - rcPadding.top - rcPadding.bottom; + if (sz.cy < 0) sz.cy = 0; + if (sz.cy > szControlAvailable.cy) sz.cy = szControlAvailable.cy; + if (sz.cy < pControl->GetMinHeight ()) sz.cy = pControl->GetMinHeight (); + + UINT iChildAlign = GetChildVAlign (); + if (iChildAlign == DT_VCENTER) { + int iPosY = (rc.bottom + rc.top) / 2; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + iPosY += m_pVerticalScrollBar->GetScrollRange () / 2; + iPosY -= m_pVerticalScrollBar->GetScrollPos (); + } + RECT rcCtrl = { iPosX + rcPadding.left, iPosY - sz.cy / 2, iPosX + sz.cx + rcPadding.left, iPosY + sz.cy - sz.cy / 2 }; + pControl->SetPos (rcCtrl, false); + } else if (iChildAlign == DT_BOTTOM) { + int iPosY = rc.bottom; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + iPosY += m_pVerticalScrollBar->GetScrollRange (); + iPosY -= m_pVerticalScrollBar->GetScrollPos (); + } + RECT rcCtrl = { iPosX + rcPadding.left, iPosY - rcPadding.bottom - sz.cy, iPosX + sz.cx + rcPadding.left, iPosY - rcPadding.bottom }; + pControl->SetPos (rcCtrl, false); + } else { + int iPosY = rc.top; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + iPosY -= m_pVerticalScrollBar->GetScrollPos (); + } + RECT rcCtrl = { iPosX + rcPadding.left, iPosY + rcPadding.top, iPosX + sz.cx + rcPadding.left, iPosY + sz.cy + rcPadding.top }; + pControl->SetPos (rcCtrl, false); + } + + iPosX += sz.cx + m_iChildPadding + rcPadding.left + rcPadding.right; + cxNeeded += sz.cx + rcPadding.left + rcPadding.right; + szRemaining.cx -= sz.cx + m_iChildPadding + rcPadding.right; + } + cxNeeded += (nEstimateNum - 1) * m_iChildPadding; + + // Process the scrollbar + ProcessScrollBar (rc, cxNeeded, cyNeeded); + } + + void CHorizontalLayoutUI::DoPostPaint (HDC hDC, const RECT& rcPaint) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0 && !m_bImmMode) { + RECT rcSeparator = GetThumbRect (true); + CRenderEngine::DrawColor (hDC, rcSeparator, 0xAA000000); + } + } + + void CHorizontalLayoutUI::SetSepWidth (int iWidth) { + m_iSepWidth = iWidth; + } + + int CHorizontalLayoutUI::GetSepWidth () const { + return m_iSepWidth; + } + + void CHorizontalLayoutUI::SetSepImmMode (bool bImmediately) { + if (m_bImmMode == bImmediately) return; + if ((m_uButtonState & UISTATE_CAPTURED) != 0 && !m_bImmMode && m_pManager) { + m_pManager->RemovePostPaint (this); + } + + m_bImmMode = bImmediately; + } + + bool CHorizontalLayoutUI::IsSepImmMode () const { + return m_bImmMode; + } + + void CHorizontalLayoutUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("sepwidth")) SetSepWidth (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("sepimm")) SetSepImmMode (FawTools::parse_bool (pstrValue)); + else CContainerUI::SetAttribute (pstrName, pstrValue); + } + + void CHorizontalLayoutUI::DoEvent (TEventUI& event) { + if (m_iSepWidth != 0) { + if (event.Type == UIEVENT_BUTTONDOWN && IsEnabled ()) { + RECT rcSeparator = GetThumbRect (false); + if (::PtInRect (&rcSeparator, event.ptMouse)) { + m_uButtonState |= UISTATE_CAPTURED; + ptLastMouse = event.ptMouse; + m_rcNewPos = m_rcItem; + if (!m_bImmMode && m_pManager) m_pManager->AddPostPaint (this); + return; + } + } + if (event.Type == UIEVENT_BUTTONUP) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + m_uButtonState &= ~UISTATE_CAPTURED; + m_rcItem = m_rcNewPos; + if (!m_bImmMode && m_pManager) m_pManager->RemovePostPaint (this); + NeedParentUpdate (); + return; + } + } + if (event.Type == UIEVENT_MOUSEMOVE) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + LONG cx = event.ptMouse.x - ptLastMouse.x; + ptLastMouse = event.ptMouse; + RECT rc = m_rcNewPos; + if (m_iSepWidth >= 0) { + if (cx > 0 && event.ptMouse.x < m_rcNewPos.right - m_iSepWidth) return; + if (cx < 0 && event.ptMouse.x > m_rcNewPos.right) return; + rc.right += cx; + if (rc.right - rc.left <= GetMinWidth ()) { + if (m_rcNewPos.right - m_rcNewPos.left <= GetMinWidth ()) return; + rc.right = rc.left + GetMinWidth (); + } + if (rc.right - rc.left >= GetMaxWidth ()) { + if (m_rcNewPos.right - m_rcNewPos.left >= GetMaxWidth ()) return; + rc.right = rc.left + GetMaxWidth (); + } + } else { + if (cx > 0 && event.ptMouse.x < m_rcNewPos.left) return; + if (cx < 0 && event.ptMouse.x > m_rcNewPos.left - m_iSepWidth) return; + rc.left += cx; + if (rc.right - rc.left <= GetMinWidth ()) { + if (m_rcNewPos.right - m_rcNewPos.left <= GetMinWidth ()) return; + rc.left = rc.right - GetMinWidth (); + } + if (rc.right - rc.left >= GetMaxWidth ()) { + if (m_rcNewPos.right - m_rcNewPos.left >= GetMaxWidth ()) return; + rc.left = rc.right - GetMaxWidth (); + } + } + + RECT rcInvalidate = GetThumbRect (true); + m_rcNewPos = rc; + m_cxyFixed.cx = m_rcNewPos.right - m_rcNewPos.left; + + if (m_bImmMode) { + m_rcItem = m_rcNewPos; + NeedParentUpdate (); + } else { + RECT rc = GetThumbRect (true); + rcInvalidate.left = min (rcInvalidate.left, rc.left); + rcInvalidate.top = min (rcInvalidate.top, rc.top); + rcInvalidate.right = max (rcInvalidate.right, rc.right); + rcInvalidate.bottom = max (rcInvalidate.bottom, rc.bottom); + rc = GetThumbRect (false); + rcInvalidate.left = min (rcInvalidate.left, rc.left); + rcInvalidate.top = min (rcInvalidate.top, rc.top); + rcInvalidate.right = max (rcInvalidate.right, rc.right); + rcInvalidate.bottom = max (rcInvalidate.bottom, rc.bottom); + if (m_pManager) m_pManager->Invalidate (rcInvalidate); + } + return; + } + } + if (event.Type == UIEVENT_SETCURSOR) { + RECT rcSeparator = GetThumbRect (false); + if (IsEnabled () && ::PtInRect (&rcSeparator, event.ptMouse)) { + ::SetCursor (::LoadCursor (nullptr, IDC_SIZEWE)); + return; + } + } + } + CContainerUI::DoEvent (event); + } + + bool CHorizontalLayoutUI::IsDynamic (POINT &pt) const { + RECT rcSeparator = GetThumbRect (false); + return (IsEnabled () && ::PtInRect (&rcSeparator, pt)) || CControlUI::IsDynamic (pt); + } + + RECT CHorizontalLayoutUI::GetThumbRect (bool bUseNew) const { + if ((m_uButtonState & UISTATE_CAPTURED) != 0 && bUseNew) { + if (m_iSepWidth >= 0) return { m_rcNewPos.right - m_iSepWidth, m_rcNewPos.top, m_rcNewPos.right, m_rcNewPos.bottom }; + else return { m_rcNewPos.left, m_rcNewPos.top, m_rcNewPos.left - m_iSepWidth, m_rcNewPos.bottom }; + } else { + if (m_iSepWidth >= 0) return { m_rcItem.right - m_iSepWidth, m_rcItem.top, m_rcItem.right, m_rcItem.bottom }; + else return { m_rcItem.left, m_rcItem.top, m_rcItem.left - m_iSepWidth, m_rcItem.bottom }; + } + } +} diff --git a/DuiLib/Layout/UIHorizontalLayout.h b/DuiLib/Layout/UIHorizontalLayout.h new file mode 100644 index 0000000..277f74b --- /dev/null +++ b/DuiLib/Layout/UIHorizontalLayout.h @@ -0,0 +1,37 @@ +#ifndef __UIHORIZONTALLAYOUT_H__ +#define __UIHORIZONTALLAYOUT_H__ + +#pragma once + +namespace DuiLib { + class UILIB_API CHorizontalLayoutUI: public CContainerUI { + DECLARE_DUICONTROL (CHorizontalLayoutUI) + public: + CHorizontalLayoutUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetControlFlags () const; + + void SetSepWidth (int iWidth); + int GetSepWidth () const; + void SetSepImmMode (bool bImmediately); + bool IsSepImmMode () const; + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + void DoEvent (TEventUI& event); + + void SetPos (RECT rc, bool bNeedInvalidate = true); + void DoPostPaint (HDC hDC, const RECT& rcPaint); + bool IsDynamic (POINT &pt) const override; + + RECT GetThumbRect (bool bUseNew = false) const; + + protected: + int m_iSepWidth = 0; + UINT m_uButtonState = 0; + POINT ptLastMouse = { 0 }; + RECT m_rcNewPos; + bool m_bImmMode = false; + }; +} +#endif // __UIHORIZONTALLAYOUT_H__ diff --git a/DuiLib/Layout/UITabLayout.cpp b/DuiLib/Layout/UITabLayout.cpp new file mode 100644 index 0000000..203e58a --- /dev/null +++ b/DuiLib/Layout/UITabLayout.cpp @@ -0,0 +1,156 @@ +#include "StdAfx.h" +#include "UITabLayout.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CTabLayoutUI) + CTabLayoutUI::CTabLayoutUI () {} + + string_view_t CTabLayoutUI::GetClass () const { + return _T ("TabLayoutUI"); + } + + LPVOID CTabLayoutUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_TABLAYOUT) return static_cast(this); + return CContainerUI::GetInterface (pstrName); + } + + bool CTabLayoutUI::Add (CControlUI* pControl) { + bool ret = CContainerUI::Add (pControl); + if (!ret) return ret; + + if (m_iCurSel == -1 && pControl->IsVisible ()) { + m_iCurSel = GetItemIndex (pControl); + } else { + pControl->SetVisible (false); + } + + return ret; + } + + bool CTabLayoutUI::AddAt (CControlUI* pControl, int iIndex) { + bool ret = CContainerUI::AddAt (pControl, iIndex); + if (!ret) return ret; + + if (m_iCurSel == -1 && pControl->IsVisible ()) { + m_iCurSel = GetItemIndex (pControl); + } else if (m_iCurSel != -1 && iIndex <= m_iCurSel) { + m_iCurSel += 1; + } else { + pControl->SetVisible (false); + } + + return ret; + } + + bool CTabLayoutUI::Remove (CControlUI* pControl) { + if (!pControl) return false; + + int index = GetItemIndex (pControl); + bool ret = CContainerUI::Remove (pControl); + if (!ret) return false; + + if (m_iCurSel == index) { + if (GetCount () > 0) { + m_iCurSel = 0; + GetItemAt (m_iCurSel)->SetVisible (true); + } else + m_iCurSel = -1; + NeedParentUpdate (); + } else if (m_iCurSel > index) { + m_iCurSel -= 1; + } + + return ret; + } + + void CTabLayoutUI::RemoveAll () { + m_iCurSel = -1; + CContainerUI::RemoveAll (); + NeedParentUpdate (); + } + + int CTabLayoutUI::GetCurSel () const { + return m_iCurSel; + } + + bool CTabLayoutUI::SelectItem (int iIndex) { + if (iIndex < 0 || iIndex >= m_items.GetSize ()) return false; + if (iIndex == m_iCurSel) return true; + + int iOldSel = m_iCurSel; + m_iCurSel = iIndex; + for (int it = 0; it < m_items.GetSize (); it++) { + if (it == iIndex) { + GetItemAt (it)->SetVisible (true); + GetItemAt (it)->SetFocus (); + SetPos (m_rcItem); + } else GetItemAt (it)->SetVisible (false); + } + NeedParentUpdate (); + + if (m_pManager) { + m_pManager->SetNextTabControl (); + m_pManager->SendNotify (this, DUI_MSGTYPE_TABSELECT, m_iCurSel, iOldSel); + } + return true; + } + + bool CTabLayoutUI::SelectItem (CControlUI* pControl) { + int iIndex = GetItemIndex (pControl); + if (iIndex == -1) + return false; + else + return SelectItem (iIndex); + } + + void CTabLayoutUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("selectedid")) SelectItem (FawTools::parse_dec (pstrValue)); + return CContainerUI::SetAttribute (pstrName, pstrValue); + } + + void CTabLayoutUI::SetPos (RECT rc, bool bNeedInvalidate) { + CControlUI::SetPos (rc, bNeedInvalidate); + rc = m_rcItem; + + // Adjust for inset + rc.left += m_rcInset.left; + rc.top += m_rcInset.top; + rc.right -= m_rcInset.right; + rc.bottom -= m_rcInset.bottom; + + for (int it = 0; it < m_items.GetSize (); it++) { + CControlUI* pControl = static_cast(m_items[it]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) { + SetFloatPos (it); + continue; + } + + if (it != m_iCurSel) continue; + + RECT rcPadding = pControl->GetPadding (); + rc.left += rcPadding.left; + rc.top += rcPadding.top; + rc.right -= rcPadding.right; + rc.bottom -= rcPadding.bottom; + + SIZE szAvailable = { rc.right - rc.left, rc.bottom - rc.top }; + + SIZE sz = pControl->EstimateSize (szAvailable); + if (sz.cx == 0) { + sz.cx = MAX (0, szAvailable.cx); + } + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + if (sz.cx > pControl->GetMaxWidth ()) sz.cx = pControl->GetMaxWidth (); + + if (sz.cy == 0) { + sz.cy = MAX (0, szAvailable.cy); + } + if (sz.cy < pControl->GetMinHeight ()) sz.cy = pControl->GetMinHeight (); + if (sz.cy > pControl->GetMaxHeight ()) sz.cy = pControl->GetMaxHeight (); + + RECT rcCtrl = { rc.left, rc.top, rc.left + sz.cx, rc.top + sz.cy }; + pControl->SetPos (rcCtrl); + } + } +} diff --git a/DuiLib/Layout/UITabLayout.h b/DuiLib/Layout/UITabLayout.h new file mode 100644 index 0000000..8a2a437 --- /dev/null +++ b/DuiLib/Layout/UITabLayout.h @@ -0,0 +1,31 @@ +#ifndef __UITABLAYOUT_H__ +#define __UITABLAYOUT_H__ + +#pragma once + +namespace DuiLib { + class UILIB_API CTabLayoutUI: public CContainerUI { + DECLARE_DUICONTROL (CTabLayoutUI) + public: + CTabLayoutUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + bool Add (CControlUI* pControl); + bool AddAt (CControlUI* pControl, int iIndex); + bool Remove (CControlUI* pControl); + void RemoveAll (); + int GetCurSel () const; + virtual bool SelectItem (int iIndex); + virtual bool SelectItem (CControlUI* pControl); + + void SetPos (RECT rc, bool bNeedInvalidate = true); + + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + protected: + int m_iCurSel = -1; + }; +} +#endif // __UITABLAYOUT_H__ diff --git a/DuiLib/Layout/UITileLayout.cpp b/DuiLib/Layout/UITileLayout.cpp new file mode 100644 index 0000000..5fc984f --- /dev/null +++ b/DuiLib/Layout/UITileLayout.cpp @@ -0,0 +1,165 @@ +#include "StdAfx.h" +#include "UITileLayout.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CTileLayoutUI) + CTileLayoutUI::CTileLayoutUI () {} + + string_view_t CTileLayoutUI::GetClass () const { + return _T ("TileLayoutUI"); + } + + LPVOID CTileLayoutUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_TILELAYOUT) return static_cast(this); + return CContainerUI::GetInterface (pstrName); + } + + SIZE CTileLayoutUI::GetItemSize () const { + return m_szItem; + } + + void CTileLayoutUI::SetItemSize (SIZE szItem) { + if (m_szItem.cx != szItem.cx || m_szItem.cy != szItem.cy) { + m_szItem = szItem; + NeedUpdate (); + } + } + + int CTileLayoutUI::GetColumns () const { + return m_nColumns; + } + + void CTileLayoutUI::SetColumns (int nCols) { + if (nCols <= 0) return; + m_nColumns = nCols; + NeedUpdate (); + } + + void CTileLayoutUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("itemsize")) { + SIZE szItem = FawTools::parse_size (pstrValue); + SetItemSize (szItem); + } else if (pstrName == _T ("columns")) SetColumns (FawTools::parse_dec (pstrValue)); + else CContainerUI::SetAttribute (pstrName, pstrValue); + } + + void CTileLayoutUI::SetPos (RECT rc, bool bNeedInvalidate) { + CControlUI::SetPos (rc, bNeedInvalidate); + rc = m_rcItem; + + // Adjust for inset + rc.left += m_rcInset.left; + rc.top += m_rcInset.top; + rc.right -= m_rcInset.right; + rc.bottom -= m_rcInset.bottom; + + if (m_items.GetSize () == 0) { + ProcessScrollBar (rc, 0, 0); + return; + } + + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + + // Position the elements + if (m_szItem.cx > 0) m_nColumns = (rc.right - rc.left) / m_szItem.cx; + if (m_nColumns == 0) m_nColumns = 1; + + int cyNeeded = 0; + int cxWidth = (rc.right - rc.left) / m_nColumns; + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) + cxWidth = (rc.right - rc.left + m_pHorizontalScrollBar->GetScrollRange ()) / m_nColumns; ; + + int cyHeight = 0; + int iCount = 0; + POINT ptTile = { rc.left, rc.top }; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + ptTile.y -= m_pVerticalScrollBar->GetScrollPos (); + } + int iPosX = rc.left; + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + iPosX -= m_pHorizontalScrollBar->GetScrollPos (); + ptTile.x = iPosX; + } + for (int it1 = 0; it1 < m_items.GetSize (); it1++) { + CControlUI* pControl = static_cast(m_items[it1]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) { + SetFloatPos (it1); + continue; + } + + // Determine size + RECT rcTile = { ptTile.x, ptTile.y, ptTile.x + cxWidth, ptTile.y }; + if ((iCount % m_nColumns) == 0) { + int iIndex = iCount; + for (int it2 = it1; it2 < m_items.GetSize (); it2++) { + CControlUI* pLineControl = static_cast(m_items[it2]); + if (!pLineControl->IsVisible ()) continue; + if (pLineControl->IsFloat ()) continue; + + RECT rcPadding = pLineControl->GetPadding (); + SIZE szAvailable = { rcTile.right - rcTile.left - rcPadding.left - rcPadding.right, 9999 }; + if (iIndex == iCount || (iIndex + 1) % m_nColumns == 0) { + szAvailable.cx -= m_iChildPadding / 2; + } else { + szAvailable.cx -= m_iChildPadding; + } + + if (szAvailable.cx < pControl->GetMinWidth ()) szAvailable.cx = pControl->GetMinWidth (); + if (szAvailable.cx > pControl->GetMaxWidth ()) szAvailable.cx = pControl->GetMaxWidth (); + + SIZE szTile = pLineControl->EstimateSize (szAvailable); + if (szTile.cx < pControl->GetMinWidth ()) szTile.cx = pControl->GetMinWidth (); + if (szTile.cx > pControl->GetMaxWidth ()) szTile.cx = pControl->GetMaxWidth (); + if (szTile.cy < pControl->GetMinHeight ()) szTile.cy = pControl->GetMinHeight (); + if (szTile.cy > pControl->GetMaxHeight ()) szTile.cy = pControl->GetMaxHeight (); + + cyHeight = MAX (cyHeight, szTile.cy + rcPadding.top + rcPadding.bottom); + if ((++iIndex % m_nColumns) == 0) break; + } + } + + RECT rcPadding = pControl->GetPadding (); + + rcTile.left += rcPadding.left + m_iChildPadding / 2; + rcTile.right -= rcPadding.right + m_iChildPadding / 2; + if ((iCount % m_nColumns) == 0) { + rcTile.left -= m_iChildPadding / 2; + } + + if (((iCount + 1) % m_nColumns) == 0) { + rcTile.right += m_iChildPadding / 2; + } + + // Set position + rcTile.top = ptTile.y + rcPadding.top; + rcTile.bottom = ptTile.y + cyHeight; + + SIZE szAvailable = { rcTile.right - rcTile.left, rcTile.bottom - rcTile.top }; + SIZE szTile = pControl->EstimateSize (szAvailable); + if (szTile.cx == 0) szTile.cx = szAvailable.cx; + if (szTile.cy == 0) szTile.cy = szAvailable.cy; + if (szTile.cx < pControl->GetMinWidth ()) szTile.cx = pControl->GetMinWidth (); + if (szTile.cx > pControl->GetMaxWidth ()) szTile.cx = pControl->GetMaxWidth (); + if (szTile.cy < pControl->GetMinHeight ()) szTile.cy = pControl->GetMinHeight (); + if (szTile.cy > pControl->GetMaxHeight ()) szTile.cy = pControl->GetMaxHeight (); + RECT rcPos = { (rcTile.left + rcTile.right - szTile.cx) / 2, (rcTile.top + rcTile.bottom - szTile.cy) / 2, + (rcTile.left + rcTile.right - szTile.cx) / 2 + szTile.cx, (rcTile.top + rcTile.bottom - szTile.cy) / 2 + szTile.cy }; + pControl->SetPos (rcPos); + + if ((++iCount % m_nColumns) == 0) { + ptTile.x = iPosX; + ptTile.y += cyHeight + m_iChildPadding; + cyHeight = 0; + } else { + ptTile.x += cxWidth; + } + cyNeeded = rcTile.bottom - rc.top; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) cyNeeded += m_pVerticalScrollBar->GetScrollPos (); + } + + // Process the scrollbar + ProcessScrollBar (rc, 0, cyNeeded); + } +} diff --git a/DuiLib/Layout/UITileLayout.h b/DuiLib/Layout/UITileLayout.h new file mode 100644 index 0000000..cb2926c --- /dev/null +++ b/DuiLib/Layout/UITileLayout.h @@ -0,0 +1,29 @@ +#ifndef __UITILELAYOUT_H__ +#define __UITILELAYOUT_H__ + +#pragma once + +namespace DuiLib { + class UILIB_API CTileLayoutUI: public CContainerUI { + DECLARE_DUICONTROL (CTileLayoutUI) + public: + CTileLayoutUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + + void SetPos (RECT rc, bool bNeedInvalidate = true); + + SIZE GetItemSize () const; + void SetItemSize (SIZE szItem); + int GetColumns () const; + void SetColumns (int nCols); + + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + + protected: + SIZE m_szItem = { 0 }; + int m_nColumns = 1; + }; +} +#endif // __UITILELAYOUT_H__ diff --git a/DuiLib/Layout/UIVerticalLayout.cpp b/DuiLib/Layout/UIVerticalLayout.cpp new file mode 100644 index 0000000..ebcab00 --- /dev/null +++ b/DuiLib/Layout/UIVerticalLayout.cpp @@ -0,0 +1,319 @@ +#include "StdAfx.h" +#include "UIVerticalLayout.h" + +namespace DuiLib { + IMPLEMENT_DUICONTROL (CVerticalLayoutUI) + CVerticalLayoutUI::CVerticalLayoutUI () { + ::ZeroMemory (&m_rcNewPos, sizeof (m_rcNewPos)); + } + + string_view_t CVerticalLayoutUI::GetClass () const { + return _T ("VerticalLayoutUI"); + } + + LPVOID CVerticalLayoutUI::GetInterface (string_view_t pstrName) { + if (pstrName == DUI_CTRL_VERTICALLAYOUT) return static_cast(this); + return CContainerUI::GetInterface (pstrName); + } + + UINT CVerticalLayoutUI::GetControlFlags () const { + if (IsEnabled () && m_iSepHeight != 0) return UIFLAG_SETCURSOR; + else return 0; + } + + void CVerticalLayoutUI::SetPos (RECT rc, bool bNeedInvalidate) { + CControlUI::SetPos (rc, bNeedInvalidate); + rc = m_rcItem; + + // Adjust for inset + RECT _rcInset = CVerticalLayoutUI::m_rcInset; + GetManager ()->GetDPIObj ()->Scale (&_rcInset); + rc.left += _rcInset.left; + rc.top += _rcInset.top; + rc.right -= _rcInset.right; + rc.bottom -= _rcInset.bottom; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) rc.right -= m_pVerticalScrollBar->GetFixedWidth (); + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight (); + + if (m_items.GetSize () == 0) { + ProcessScrollBar (rc, 0, 0); + return; + } + + // Determine the minimum size + SIZE szAvailable = { rc.right - rc.left, rc.bottom - rc.top }; + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) + szAvailable.cx += m_pHorizontalScrollBar->GetScrollRange (); + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) + szAvailable.cy += m_pVerticalScrollBar->GetScrollRange (); + + int cxNeeded = 0; + int nAdjustables = 0; + int cyFixed = 0; + int nEstimateNum = 0; + SIZE szControlAvailable = { 0 }; + int iControlMaxWidth = 0; + int iControlMaxHeight = 0; + for (int it1 = 0; it1 < m_items.GetSize (); it1++) { + CControlUI* pControl = static_cast(m_items[it1]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) continue; + szControlAvailable = szAvailable; + RECT rcPadding = pControl->GetPadding (); + szControlAvailable.cx -= rcPadding.left + rcPadding.right; + iControlMaxWidth = pControl->GetFixedWidth (); + iControlMaxHeight = pControl->GetFixedHeight (); + if (iControlMaxWidth <= 0) iControlMaxWidth = pControl->GetMaxWidth (); + if (iControlMaxHeight <= 0) iControlMaxHeight = pControl->GetMaxHeight (); + if (szControlAvailable.cx > iControlMaxWidth) szControlAvailable.cx = iControlMaxWidth; + if (szControlAvailable.cy > iControlMaxHeight) szControlAvailable.cy = iControlMaxHeight; + SIZE sz = pControl->EstimateSize (szControlAvailable); + if (sz.cy == 0) { + nAdjustables++; + } else { + if (sz.cy < pControl->GetMinHeight ()) sz.cy = pControl->GetMinHeight (); + if (sz.cy > pControl->GetMaxHeight ()) sz.cy = pControl->GetMaxHeight (); + } + cyFixed += sz.cy + pControl->GetPadding ().top + pControl->GetPadding ().bottom; + + sz.cx = MAX (sz.cx, 0); + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + if (sz.cx > pControl->GetMaxWidth ()) sz.cx = pControl->GetMaxWidth (); + cxNeeded = MAX (cxNeeded, sz.cx + rcPadding.left + rcPadding.right); + nEstimateNum++; + } + cyFixed += (nEstimateNum - 1) * m_iChildPadding; + + // Place elements + int cyNeeded = 0; + int cyExpand = 0; + if (nAdjustables > 0) cyExpand = MAX (0, (szAvailable.cy - cyFixed) / nAdjustables); + // Position the elements + SIZE szRemaining = szAvailable; + int iPosY = rc.top; + if (m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible ()) { + iPosY -= m_pVerticalScrollBar->GetScrollPos (); + } + + int iEstimate = 0; + int iAdjustable = 0; + int cyFixedRemaining = cyFixed; + for (int it2 = 0; it2 < m_items.GetSize (); it2++) { + CControlUI* pControl = static_cast(m_items[it2]); + if (!pControl->IsVisible ()) continue; + if (pControl->IsFloat ()) { + SetFloatPos (it2); + continue; + } + + iEstimate += 1; + RECT rcPadding = pControl->GetPadding (); + szRemaining.cy -= rcPadding.top; + + szControlAvailable = szRemaining; + szControlAvailable.cx -= rcPadding.left + rcPadding.right; + iControlMaxWidth = pControl->GetFixedWidth (); + iControlMaxHeight = pControl->GetFixedHeight (); + if (iControlMaxWidth <= 0) iControlMaxWidth = pControl->GetMaxWidth (); + if (iControlMaxHeight <= 0) iControlMaxHeight = pControl->GetMaxHeight (); + if (szControlAvailable.cx > iControlMaxWidth) szControlAvailable.cx = iControlMaxWidth; + if (szControlAvailable.cy > iControlMaxHeight) szControlAvailable.cy = iControlMaxHeight; + cyFixedRemaining = cyFixedRemaining - (rcPadding.top + rcPadding.bottom); + if (iEstimate > 1) cyFixedRemaining = cyFixedRemaining - m_iChildPadding; + SIZE sz = pControl->EstimateSize (szControlAvailable); + if (sz.cy == 0) { + iAdjustable++; + sz.cy = cyExpand; + // Distribute remaining to last element (usually round-off left-overs) + if (iAdjustable == nAdjustables) { + sz.cy = MAX (0, szRemaining.cy - rcPadding.bottom - cyFixedRemaining); + } + if (sz.cy < pControl->GetMinHeight ()) sz.cy = pControl->GetMinHeight (); + if (sz.cy > pControl->GetMaxHeight ()) sz.cy = pControl->GetMaxHeight (); + } else { + if (sz.cy < pControl->GetMinHeight ()) sz.cy = pControl->GetMinHeight (); + if (sz.cy > pControl->GetMaxHeight ()) sz.cy = pControl->GetMaxHeight (); + cyFixedRemaining -= sz.cy; + } + + sz.cx = pControl->GetMaxWidth (); + if (sz.cx == 0) sz.cx = szAvailable.cx - rcPadding.left - rcPadding.right; + if (sz.cx < 0) sz.cx = 0; + if (sz.cx > szControlAvailable.cx) sz.cx = szControlAvailable.cx; + if (sz.cx < pControl->GetMinWidth ()) sz.cx = pControl->GetMinWidth (); + + UINT iChildAlign = GetChildAlign (pControl); + if (iChildAlign == DT_CENTER) { + int iPosX = (rc.right + rc.left) / 2; + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + iPosX += m_pHorizontalScrollBar->GetScrollRange () / 2; + iPosX -= m_pHorizontalScrollBar->GetScrollPos (); + } + RECT rcCtrl = { iPosX - sz.cx / 2, iPosY + rcPadding.top, iPosX + sz.cx - sz.cx / 2, iPosY + sz.cy + rcPadding.top }; + pControl->SetPos (rcCtrl, false); + } else if (iChildAlign == DT_RIGHT) { + int iPosX = rc.right; + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + iPosX += m_pHorizontalScrollBar->GetScrollRange (); + iPosX -= m_pHorizontalScrollBar->GetScrollPos (); + } + RECT rcCtrl = { iPosX - rcPadding.right - sz.cx, iPosY + rcPadding.top, iPosX - rcPadding.right, iPosY + sz.cy + rcPadding.top }; + pControl->SetPos (rcCtrl, false); + } else { + int iPosX = rc.left; + if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible ()) { + iPosX -= m_pHorizontalScrollBar->GetScrollPos (); + } + RECT rcCtrl = { iPosX + rcPadding.left, iPosY + rcPadding.top, iPosX + rcPadding.left + sz.cx, iPosY + sz.cy + rcPadding.top }; + pControl->SetPos (rcCtrl, false); + } + + iPosY += sz.cy + m_iChildPadding + rcPadding.top + rcPadding.bottom; + cyNeeded += sz.cy + rcPadding.top + rcPadding.bottom; + szRemaining.cy -= sz.cy + m_iChildPadding + rcPadding.bottom; + } + cyNeeded += (nEstimateNum - 1) * m_iChildPadding; + + // Process the scrollbar + ProcessScrollBar (rc, cxNeeded, cyNeeded); + } + + void CVerticalLayoutUI::DoPostPaint (HDC hDC, const RECT& rcPaint) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0 && !m_bImmMode) { + RECT rcSeparator = GetThumbRect (true); + CRenderEngine::DrawColor (hDC, rcSeparator, 0xAA000000); + } + } + + void CVerticalLayoutUI::SetSepHeight (int iHeight) { + m_iSepHeight = iHeight; + } + + int CVerticalLayoutUI::GetSepHeight () const { + return m_iSepHeight; + } + + void CVerticalLayoutUI::SetSepImmMode (bool bImmediately) { + if (m_bImmMode == bImmediately) return; + if ((m_uButtonState & UISTATE_CAPTURED) != 0 && !m_bImmMode && m_pManager) { + m_pManager->RemovePostPaint (this); + } + + m_bImmMode = bImmediately; + } + + bool CVerticalLayoutUI::IsSepImmMode () const { + return m_bImmMode; + } + + void CVerticalLayoutUI::SetAttribute (string_view_t pstrName, string_view_t pstrValue) { + if (pstrName == _T ("sepheight")) SetSepHeight (FawTools::parse_dec (pstrValue)); + else if (pstrName == _T ("sepimm")) SetSepImmMode (FawTools::parse_bool (pstrValue)); + else CContainerUI::SetAttribute (pstrName, pstrValue); + } + + void CVerticalLayoutUI::DoEvent (TEventUI& event) { + if (m_iSepHeight != 0) { + if (event.Type == UIEVENT_BUTTONDOWN && IsEnabled ()) { + RECT rcSeparator = GetThumbRect (false); + if (::PtInRect (&rcSeparator, event.ptMouse)) { + m_uButtonState |= UISTATE_CAPTURED; + ptLastMouse = event.ptMouse; + m_rcNewPos = m_rcItem; + if (!m_bImmMode && m_pManager) m_pManager->AddPostPaint (this); + return; + } + } + if (event.Type == UIEVENT_BUTTONUP) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + m_uButtonState &= ~UISTATE_CAPTURED; + m_rcItem = m_rcNewPos; + if (!m_bImmMode && m_pManager) m_pManager->RemovePostPaint (this); + NeedParentUpdate (); + return; + } + } + if (event.Type == UIEVENT_MOUSEMOVE) { + if ((m_uButtonState & UISTATE_CAPTURED) != 0) { + LONG cy = event.ptMouse.y - ptLastMouse.y; + ptLastMouse = event.ptMouse; + RECT rc = m_rcNewPos; + if (m_iSepHeight >= 0) { + if (cy > 0 && event.ptMouse.y < m_rcNewPos.bottom + m_iSepHeight) return; + if (cy < 0 && event.ptMouse.y > m_rcNewPos.bottom) return; + rc.bottom += cy; + if (rc.bottom - rc.top <= GetMinHeight ()) { + if (m_rcNewPos.bottom - m_rcNewPos.top <= GetMinHeight ()) return; + rc.bottom = rc.top + GetMinHeight (); + } + if (rc.bottom - rc.top >= GetMaxHeight ()) { + if (m_rcNewPos.bottom - m_rcNewPos.top >= GetMaxHeight ()) return; + rc.bottom = rc.top + GetMaxHeight (); + } + } else { + if (cy > 0 && event.ptMouse.y < m_rcNewPos.top) return; + if (cy < 0 && event.ptMouse.y > m_rcNewPos.top + m_iSepHeight) return; + rc.top += cy; + if (rc.bottom - rc.top <= GetMinHeight ()) { + if (m_rcNewPos.bottom - m_rcNewPos.top <= GetMinHeight ()) return; + rc.top = rc.bottom - GetMinHeight (); + } + if (rc.bottom - rc.top >= GetMaxHeight ()) { + if (m_rcNewPos.bottom - m_rcNewPos.top >= GetMaxHeight ()) return; + rc.top = rc.bottom - GetMaxHeight (); + } + } + + RECT rcInvalidate = GetThumbRect (true); + m_rcNewPos = rc; + m_cxyFixed.cy = m_rcNewPos.bottom - m_rcNewPos.top; + + if (m_bImmMode) { + m_rcItem = m_rcNewPos; + NeedParentUpdate (); + } else { + RECT rc = GetThumbRect (true); + rcInvalidate.left = min (rcInvalidate.left, rc.left); + rcInvalidate.top = min (rcInvalidate.top, rc.top); + rcInvalidate.right = max (rcInvalidate.right, rc.right); + rcInvalidate.bottom = max (rcInvalidate.bottom, rc.bottom); + rc = GetThumbRect (false); + rcInvalidate.left = min (rcInvalidate.left, rc.left); + rcInvalidate.top = min (rcInvalidate.top, rc.top); + rcInvalidate.right = max (rcInvalidate.right, rc.right); + rcInvalidate.bottom = max (rcInvalidate.bottom, rc.bottom); + if (m_pManager) m_pManager->Invalidate (rcInvalidate); + } + return; + } + } + if (event.Type == UIEVENT_SETCURSOR) { + RECT rcSeparator = GetThumbRect (false); + if (IsEnabled () && ::PtInRect (&rcSeparator, event.ptMouse)) { + ::SetCursor (::LoadCursor (nullptr, IDC_SIZENS)); + return; + } + } + } + CContainerUI::DoEvent (event); + } + + bool CVerticalLayoutUI::IsDynamic (POINT &pt) const { + RECT rcSeparator = GetThumbRect (false); + return (IsEnabled () && ::PtInRect (&rcSeparator, pt)) || CControlUI::IsDynamic (pt); + } + + RECT CVerticalLayoutUI::GetThumbRect (bool bUseNew) const { + if ((m_uButtonState & UISTATE_CAPTURED) != 0 && bUseNew) { + if (m_iSepHeight >= 0) + return { m_rcNewPos.left, MAX (m_rcNewPos.bottom - m_iSepHeight, m_rcNewPos.top), m_rcNewPos.right, m_rcNewPos.bottom }; + else + return { m_rcNewPos.left, m_rcNewPos.top, m_rcNewPos.right, MIN (m_rcNewPos.top - m_iSepHeight, m_rcNewPos.bottom) }; + } else { + if (m_iSepHeight >= 0) + return { m_rcItem.left, MAX (m_rcItem.bottom - m_iSepHeight, m_rcItem.top), m_rcItem.right, m_rcItem.bottom }; + else + return { m_rcItem.left, m_rcItem.top, m_rcItem.right, MIN (m_rcItem.top - m_iSepHeight, m_rcItem.bottom) }; + } + } +} diff --git a/DuiLib/Layout/UIVerticalLayout.h b/DuiLib/Layout/UIVerticalLayout.h new file mode 100644 index 0000000..a600a81 --- /dev/null +++ b/DuiLib/Layout/UIVerticalLayout.h @@ -0,0 +1,37 @@ +#ifndef __UIVERTICALLAYOUT_H__ +#define __UIVERTICALLAYOUT_H__ + +#pragma once + +namespace DuiLib { + class UILIB_API CVerticalLayoutUI: public CContainerUI { + DECLARE_DUICONTROL (CVerticalLayoutUI) + public: + CVerticalLayoutUI (); + + string_view_t GetClass () const; + LPVOID GetInterface (string_view_t pstrName); + UINT GetControlFlags () const; + + void SetSepHeight (int iHeight); + int GetSepHeight () const; + void SetSepImmMode (bool bImmediately); + bool IsSepImmMode () const; + void SetAttribute (string_view_t pstrName, string_view_t pstrValue); + void DoEvent (TEventUI& event); + + void SetPos (RECT rc, bool bNeedInvalidate = true); + void DoPostPaint (HDC hDC, const RECT& rcPaint); + bool IsDynamic (POINT &pt) const override; + + RECT GetThumbRect (bool bUseNew = false) const; + + protected: + int m_iSepHeight = 0; + UINT m_uButtonState = 0; + POINT ptLastMouse = { 0 }; + RECT m_rcNewPos; + bool m_bImmMode = false; + }; +} +#endif // __UIVERTICALLAYOUT_H__ diff --git a/DuiLib/StdAfx.cpp b/DuiLib/StdAfx.cpp new file mode 100644 index 0000000..7c1d1b6 --- /dev/null +++ b/DuiLib/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// UIlib.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "StdAfx.h" + +#pragma comment( lib, "winmm.lib" ) +#pragma comment( lib, "comctl32.lib" ) \ No newline at end of file diff --git a/DuiLib/StdAfx.h b/DuiLib/StdAfx.h new file mode 100644 index 0000000..8eb4e21 --- /dev/null +++ b/DuiLib/StdAfx.h @@ -0,0 +1,37 @@ +// StdAfx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#ifndef __DUILIB_FAW__STDAFX_H__ +#define __DUILIB_FAW__STDAFX_H__ + +#pragma once + +#define _CRT_SECURE_NO_WARNINGS + +#ifdef __GNUC__ +// 怎么都没找到min,max的头文件-_- +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#endif + +#ifndef __FILET__ +#include +#define __FILET__ _T (__FILE__) +#define __FUNCTIONT__ _T (__FUNCTION__) +#endif + +#include "UIlib.h" +#include + +#define lengthof(x) (sizeof(x)/sizeof(*x)) +#define MAX max +#define MIN min +#define CLAMP(x,a,b) (MIN(b,MAX(a,x))) + +#endif //__DUILIB_FAW__STDAFX_H__ diff --git a/DuiLib/UIlib.cpp b/DuiLib/UIlib.cpp new file mode 100644 index 0000000..f7d8fe5 --- /dev/null +++ b/DuiLib/UIlib.cpp @@ -0,0 +1,64 @@ +// Copyright (c) 2010-2011, duilib develop team(www.duilib.com). +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or +// without modification, are permitted provided that the +// following conditions are met. +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// +// DirectUI - UI Library +// +// Written by Bjarke Viksoe (bjarke@viksoe.dk) +// Copyright (c) 2006-2007 Bjarke Viksoe. +// +// This code may be used in compiled form in any way you desire. These +// source files may be redistributed by any means PROVIDING it is +// not sold for profit without the authors written consent, and +// providing that this notice and the authors name is included. +// +// This file is provided "as is" with no expressed or implied warranty. +// The author accepts no liability if it causes any damage to you or your +// computer whatsoever. It's free, so don't hassle me about it. +// +// Beware of bugs. +// +// + + +#include "StdAfx.h" +#include "UIlib.h" + +BOOL APIENTRY DllMain (HANDLE hModule, DWORD dwReason, LPVOID /*lpReserved*/) { + switch (dwReason) { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + ::DisableThreadLibraryCalls ((HMODULE) hModule); + break; + } + return TRUE; +} + diff --git a/DuiLib/UIlib.h b/DuiLib/UIlib.h new file mode 100644 index 0000000..26cb1fc --- /dev/null +++ b/DuiLib/UIlib.h @@ -0,0 +1,141 @@ +#ifdef UILIB_STATIC +#define UILIB_API +#else +#if defined(UILIB_EXPORTS) +# if defined(_MSC_VER) +# define UILIB_API __declspec(dllexport) +# else +# define UILIB_API +# endif +#else +# if defined(_MSC_VER) +# define UILIB_API __declspec(dllimport) +# else +# define UILIB_API +# endif +#endif +#endif +#define UILIB_COMDAT __declspec(selectany) + +#if defined _M_IX86 +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") +#elif defined _M_IA64 +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"") +#elif defined _M_X64 +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") +#else +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma warning (disable : 4100) +#pragma warning (disable : 4244) +#pragma warning (disable : 4302) +#pragma warning (disable : 4311) +#pragma warning (disable : 4312) +#pragma warning (disable : 4505) + +//#define USE_XIMAGE_EFFECT + +#ifdef _UNICODE +typedef std::wstring string_t; +typedef std::wstring_view string_view_t; +#else +typedef std::string string_t; +typedef std::string_view string_view_t; +#endif + +#include "Utils/Utils.h" +#include "Utils/unzip.h" +#include "Utils/VersionHelpers.h" +#include "Core/UIMarkup.h" +#include "Utils/observer_impl_base.h" +#include "Utils/UIShadow.h" +#include "Utils/UIDelegate.h" +#include "Utils/DragDropImpl.h" +#include "Utils/TrayIcon.h" +#include "Utils/DPI.h" + +#include "Core/UIDefine.h" +#include "Core/UIResourceManager.h" +#include "Core/UIManager.h" +#include "Core/UIBase.h" +#include "Core/ControlFactory.h" +#include "Core/UIControl.h" +#include "Core/UIContainer.h" + +#include "Core/UIDlgBuilder.h" +#include "Core/UIRender.h" +#include "Utils/WinImplBase.h" + +#include "Layout/UIVerticalLayout.h" +#include "Layout/UIHorizontalLayout.h" +#include "Layout/UITileLayout.h" +#include "Layout/UITabLayout.h" +#include "Layout/UIChildLayout.h" + +#include "Control/UIList.h" +#include "Control/UICombo.h" +#include "Control/UIScrollBar.h" +#include "Control/UITreeView.h" + +#include "Control/UILabel.h" +#include "Control/UIText.h" +#include "Control/UIEdit.h" +#include "Control/UIGifAnim.h" +#include "Control/UIGifAnimEx.h" + +#include "Control/UIAnimation.h" +#include "Layout/UIAnimationTabLayout.h" +#include "Control/UIButton.h" +#include "Control/UIOption.h" + +#include "Control/UIProgress.h" +#include "Control/UISlider.h" + +#include "Control/UIComboBox.h" +#include "Control/UIRichEdit.h" +#include "Control/UIDateTime.h" +#include "Control/UIIPAddress.h" +#include "Control/UIIPAddressEx.h" + +#include "Control/UIActiveX.h" +#include "Control/UIWebBrowser.h" +#include "Control/UIFlash.h" + +#include "Control/UIMenu.h" +#include "Control/UIGroupBox.h" +#include "Control/UIRollText.h" +#include "Control/UIColorPalette.h" +#include "Control/UIListEx.h" +#include "Control/UIHotKey.h" +#include "Control/UIFadeButton.h" +#include "Control/UIRing.h" + +#include "Utils/FawTools.hpp" +#include "Bind/BindBase.h" +#include "Bind/BindCtrls.hpp" + +#pragma comment (lib, "comctl32.lib") +#pragma comment (lib, "GdiPlus.lib") +#pragma comment (lib, "Imm32.lib") diff --git a/DuiLib/Utils/DPI.cpp b/DuiLib/Utils/DPI.cpp new file mode 100644 index 0000000..35d9ef4 --- /dev/null +++ b/DuiLib/Utils/DPI.cpp @@ -0,0 +1,208 @@ +#include "StdAfx.h" +#include "DPI.h" +#include "VersionHelpers.h" +namespace DuiLib { + //96 DPI = 100% scaling + //120 DPI = 125% scaling + //144 DPI = 150% scaling + //168 DPI = 175% scaling + //192 DPI = 200% scaling + + typedef HRESULT (WINAPI *LPSetProcessDpiAwareness)( + _In_ PROCESS_DPI_AWARENESS value + ); + + typedef HRESULT (WINAPI *LPGetProcessDpiAwareness)( + _In_ HANDLE hprocess, + _Out_ PROCESS_DPI_AWARENESS *value + ); + + + typedef HRESULT (WINAPI *LPGetDpiForMonitor)( + _In_ HMONITOR hmonitor, + _In_ MONITOR_DPI_TYPE dpiType, + _Out_ UINT *dpiX, + _Out_ UINT *dpiY + ); + + + CDPI::CDPI () { + m_nScaleFactor = 0; + m_nScaleFactorSDA = 0; + m_Awareness = PROCESS_PER_MONITOR_DPI_AWARE; + + SetScale (96); + + } + + int CDPI::GetDPIOfMonitor (HMONITOR hMonitor) { + UINT dpix = 96, dpiy = 96; + if (IsWindows8Point1OrGreater ()) { + HRESULT hr = E_FAIL; + HMODULE hModule = ::LoadLibrary (_T ("Shcore.dll")); + if (hModule) { + LPGetDpiForMonitor GetDpiForMonitor = (LPGetDpiForMonitor) GetProcAddress (hModule, "GetDpiForMonitor"); + if (GetDpiForMonitor && GetDpiForMonitor (hMonitor, MDT_EFFECTIVE_DPI, &dpix, &dpiy) != S_OK) { + MessageBox (nullptr, _T ("GetDpiForMonitor failed"), _T ("Notification"), MB_OK); + return 96; + } + } + } else { + HDC screen = GetDC (0); + dpix = GetDeviceCaps (screen, LOGPIXELSX); + ReleaseDC (0, screen); + } + return dpix; + } + + int CDPI::GetDPIOfMonitorNearestToPoint (POINT pt) { + HMONITOR hMonitor; + hMonitor = MonitorFromPoint (pt, MONITOR_DEFAULTTONEAREST); + return GetDPIOfMonitor (hMonitor); + } + + int CDPI::GetMainMonitorDPI () { + POINT pt; + // Get the DPI for the main monitor + pt.x = 1; + pt.y = 1; + return GetDPIOfMonitorNearestToPoint (pt); + } + + PROCESS_DPI_AWARENESS CDPI::GetDPIAwareness () { + if (IsWindows8Point1OrGreater ()) { + HMODULE hModule = ::LoadLibrary (_T ("Shcore.dll")); + if (hModule) { + LPGetProcessDpiAwareness GetProcessDpiAwareness = (LPGetProcessDpiAwareness) GetProcAddress (hModule, "GetProcessDpiAwareness"); + if (GetProcessDpiAwareness) { + HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, false, GetCurrentProcessId ()); + if (GetProcessDpiAwareness (hProcess, &m_Awareness) == S_OK) { + } + } + } + } + + return m_Awareness; + } + + BOOL CDPI::SetDPIAwareness (PROCESS_DPI_AWARENESS Awareness) { + BOOL bRet = FALSE; + if (IsWindows8Point1OrGreater ()) { + HMODULE hModule = ::LoadLibrary (_T ("Shcore.dll")); + if (hModule) { + LPSetProcessDpiAwareness SetProcessDpiAwareness = (LPSetProcessDpiAwareness) GetProcAddress (hModule, "SetProcessDpiAwareness"); + if (SetProcessDpiAwareness && SetProcessDpiAwareness (Awareness) == S_OK) { + m_Awareness = Awareness; + bRet = TRUE; + } + } + } else { + m_Awareness = Awareness; + } + return bRet; + } + + UINT DuiLib::CDPI::GetDPI () { + if (m_Awareness == PROCESS_DPI_UNAWARE) { + return 96; + } + + if (m_Awareness == PROCESS_SYSTEM_DPI_AWARE) { + return MulDiv (m_nScaleFactorSDA, 96, 100); + } + + return MulDiv (m_nScaleFactor, 96, 100); + } + + UINT CDPI::GetScale () { + if (m_Awareness == PROCESS_DPI_UNAWARE) { + return 100; + } + if (m_Awareness == PROCESS_SYSTEM_DPI_AWARE) { + return m_nScaleFactorSDA; + } + return m_nScaleFactor; + } + + + void CDPI::SetScale (UINT uDPI) { + m_nScaleFactor = MulDiv (uDPI, 100, 96); + if (m_nScaleFactorSDA == 0) { + m_nScaleFactorSDA = m_nScaleFactor; + } + } + + int CDPI::Scale (int iValue) { + if (m_Awareness == PROCESS_DPI_UNAWARE) { + return iValue; + } + if (m_Awareness == PROCESS_SYSTEM_DPI_AWARE) { + return MulDiv (iValue, m_nScaleFactorSDA, 100); + } + return MulDiv (iValue, m_nScaleFactor, 100); + } + + int CDPI::ScaleBack (int iValue) { + + if (m_Awareness == PROCESS_DPI_UNAWARE) { + return iValue; + } + if (m_Awareness == PROCESS_SYSTEM_DPI_AWARE) { + return MulDiv (iValue, 100, m_nScaleFactorSDA); + } + return MulDiv (iValue, 100, m_nScaleFactor); + } + + RECT CDPI::Scale (RECT rcRect) { + RECT rcScale = rcRect; + int sw = Scale (rcRect.right - rcRect.left); + int sh = Scale (rcRect.bottom - rcRect.top); + rcScale.left = Scale (rcRect.left); + rcScale.top = Scale (rcRect.top); + rcScale.right = rcScale.left + sw; + rcScale.bottom = rcScale.top + sh; + return rcScale; + } + + void CDPI::Scale (RECT *pRect) { + int sw = Scale (pRect->right - pRect->left); + int sh = Scale (pRect->bottom - pRect->top); + pRect->left = Scale (pRect->left); + pRect->top = Scale (pRect->top); + pRect->right = pRect->left + sw; + pRect->bottom = pRect->top + sh; + } + + void CDPI::ScaleBack (RECT *pRect) { + int sw = ScaleBack (pRect->right - pRect->left); + int sh = ScaleBack (pRect->bottom - pRect->top); + pRect->left = ScaleBack (pRect->left); + pRect->top = ScaleBack (pRect->top); + pRect->right = pRect->left + sw; + pRect->bottom = pRect->top + sh; + } + + void CDPI::Scale (POINT *pPoint) { + pPoint->x = Scale (pPoint->x); + pPoint->y = Scale (pPoint->y); + } + + POINT CDPI::Scale (POINT ptPoint) { + POINT ptScale = ptPoint; + ptScale.x = Scale (ptPoint.x); + ptScale.y = Scale (ptPoint.y); + return ptScale; + } + + void CDPI::Scale (SIZE *pSize) { + pSize->cx = Scale (pSize->cx); + pSize->cy = Scale (pSize->cy); + } + + SIZE CDPI::Scale (SIZE szSize) { + SIZE szScale = szSize; + szScale.cx = Scale (szSize.cx); + szScale.cy = Scale (szSize.cy); + return szScale; + } +} \ No newline at end of file diff --git a/DuiLib/Utils/DPI.h b/DuiLib/Utils/DPI.h new file mode 100644 index 0000000..8719f7a --- /dev/null +++ b/DuiLib/Utils/DPI.h @@ -0,0 +1,56 @@ +#ifndef __DPI_H__ +#define __DPI_H__ +#pragma once + +#ifndef DPI_ENUMS_DECLARED + +typedef enum PROCESS_DPI_AWARENESS { + PROCESS_DPI_UNAWARE = 0, + PROCESS_SYSTEM_DPI_AWARE = 1, + PROCESS_PER_MONITOR_DPI_AWARE = 2 +} PROCESS_DPI_AWARENESS; + +typedef enum MONITOR_DPI_TYPE { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT = MDT_EFFECTIVE_DPI +} MONITOR_DPI_TYPE; + +#define DPI_ENUMS_DECLARED +#endif // (DPI_ENUMS_DECLARED) + +namespace DuiLib { + class UILIB_API CDPI { + public: + CDPI (void); + + public: + static int GetMainMonitorDPI (); + static int GetDPIOfMonitor (HMONITOR hMonitor); + static int GetDPIOfMonitorNearestToPoint (POINT pt); + + public: + PROCESS_DPI_AWARENESS GetDPIAwareness (); + BOOL SetDPIAwareness (PROCESS_DPI_AWARENESS Awareness); + UINT GetDPI (); + UINT GetScale (); + void SetScale (UINT uDPI); + RECT Scale (RECT rcRect); + void Scale (RECT *pRect); + POINT Scale (POINT ptPoint); + void Scale (POINT *pPoint); + SIZE Scale (SIZE szSize); + void Scale (SIZE *pSize); + int Scale (int iValue); + + int ScaleBack (int iValue); + void ScaleBack (RECT *pRect); + + private: + int m_nScaleFactor; + int m_nScaleFactorSDA; + PROCESS_DPI_AWARENESS m_Awareness; + }; +} +#endif //__DPI_H__ \ No newline at end of file diff --git a/DuiLib/Utils/DragDropImpl.cpp b/DuiLib/Utils/DragDropImpl.cpp new file mode 100644 index 0000000..1e6c689 --- /dev/null +++ b/DuiLib/Utils/DragDropImpl.cpp @@ -0,0 +1,540 @@ +#include "StdAfx.h" +/************************************************************************** +THIS CODE AND INFORMATION IS PROVIDED 'AS IS' WITHOUT WARRANTY OF +ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +PARTICULAR PURPOSE. +Author: Leon Finker 1/2001 +**************************************************************************/ +// IDataObjectImpl.cpp: implementation of the CIDataObjectImpl class. +////////////////////////////////////////////////////////////////////// + +#include +#include "DragDropImpl.h" + +namespace DuiLib { + ////////////////////////////////////////////////////////////////////// + // CIDataObject Class + ////////////////////////////////////////////////////////////////////// + + CIDataObject::CIDataObject (CIDropSource* pDropSource) + :m_cRefCount (0) + , m_pDropSource (pDropSource) {} + + CIDataObject::~CIDataObject () { + for (size_t i = 0; i < m_StgMedium.size (); ++i) { + ReleaseStgMedium (m_StgMedium[i]); + delete m_StgMedium[i]; + } + for (size_t j = 0; j < m_ArrFormatEtc.size (); ++j) + delete m_ArrFormatEtc[j]; + } + + STDMETHODIMP CIDataObject::QueryInterface (/* [in] */ REFIID riid, + /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) { + *ppvObject = nullptr; + if (IID_IUnknown == riid || IID_IDataObject == riid) + *ppvObject = this; + /*if(riid == IID_IAsyncOperation) + *ppvObject=(IAsyncOperation*)this;*/ + if (nullptr != *ppvObject) { + ((LPUNKNOWN) *ppvObject)->AddRef (); + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHODIMP_ (ULONG) CIDataObject::AddRef (void) { + ATLTRACE ("CIDataObject::AddRef\n"); + return ++m_cRefCount; + } + + STDMETHODIMP_ (ULONG) CIDataObject::Release (void) { + ATLTRACE ("CIDataObject::Release\n"); + long nTemp; + nTemp = --m_cRefCount; + if (nTemp == 0) + delete this; + return nTemp; + } + + STDMETHODIMP CIDataObject::GetData ( + /* [unique][in] */ FORMATETC __RPC_FAR *pformatetcIn, + /* [out] */ STGMEDIUM __RPC_FAR *pmedium) { + ATLTRACE ("CIDataObject::c_str\n"); + if (!pformatetcIn || !pmedium) + return E_INVALIDARG; + pmedium->hGlobal = nullptr; + + ATLASSERT (m_StgMedium.size () == m_ArrFormatEtc.size ()); + for (size_t i = 0; i < m_ArrFormatEtc.size (); ++i) { + if (pformatetcIn->tymed & m_ArrFormatEtc[i]->tymed && + pformatetcIn->dwAspect == m_ArrFormatEtc[i]->dwAspect && + pformatetcIn->cfFormat == m_ArrFormatEtc[i]->cfFormat) { + CopyMedium (pmedium, m_StgMedium[i], m_ArrFormatEtc[i]); + return S_OK; + } + } + return DV_E_FORMATETC; + } + + STDMETHODIMP CIDataObject::GetDataHere ( + /* [unique][in] */ FORMATETC __RPC_FAR *pformatetc, + /* [out][in] */ STGMEDIUM __RPC_FAR *pmedium) { + ATLTRACE ("CIDataObject::GetDataHere\n"); + + return E_NOTIMPL; + } + + STDMETHODIMP CIDataObject::QueryGetData ( + /* [unique][in] */ FORMATETC __RPC_FAR *pformatetc) { + ATLTRACE ("CIDataObject::QueryGetData\n"); + if (!pformatetc) + return E_INVALIDARG; + + //support others if needed DVASPECT_THUMBNAIL //DVASPECT_ICON //DVASPECT_DOCPRINT + if (!(DVASPECT_CONTENT & pformatetc->dwAspect)) + return (DV_E_DVASPECT); + HRESULT hr = DV_E_TYMED; + for (size_t i = 0; i < m_ArrFormatEtc.size (); ++i) { + if (pformatetc->tymed & m_ArrFormatEtc[i]->tymed) { + if (pformatetc->cfFormat == m_ArrFormatEtc[i]->cfFormat) + return S_OK; + else + hr = DV_E_CLIPFORMAT; + } else + hr = DV_E_TYMED; + } + return hr; + } + + STDMETHODIMP CIDataObject::GetCanonicalFormatEtc ( + /* [unique][in] */ FORMATETC __RPC_FAR *pformatectIn, + /* [out] */ FORMATETC __RPC_FAR *pformatetcOut) { + ATLTRACE ("CIDataObject::GetCanonicalFormatEtc\n"); + if (!pformatetcOut) + return E_INVALIDARG; + return DATA_S_SAMEFORMATETC; + } + + STDMETHODIMP CIDataObject::SetData ( + /* [unique][in] */ FORMATETC __RPC_FAR *pformatetc, + /* [unique][in] */ STGMEDIUM __RPC_FAR *pmedium, + /* [in] */ BOOL fRelease) { + ATLTRACE ("CIDataObject::SetData\n"); + if (!pformatetc || !pmedium) + return E_INVALIDARG; + + ATLASSERT (pformatetc->tymed == pmedium->tymed); + FORMATETC* fetc = new FORMATETC; + STGMEDIUM* pStgMed = new STGMEDIUM; + + if (!fetc || !pStgMed) + return E_OUTOFMEMORY; + + ZeroMemory (fetc, sizeof (FORMATETC)); + ZeroMemory (pStgMed, sizeof (STGMEDIUM)); + + *fetc = *pformatetc; + m_ArrFormatEtc.push_back (fetc); + + if (fRelease) + *pStgMed = *pmedium; + else { + CopyMedium (pStgMed, pmedium, pformatetc); + } + m_StgMedium.push_back (pStgMed); + + return S_OK; + } + void CIDataObject::CopyMedium (STGMEDIUM* pMedDest, STGMEDIUM* pMedSrc, FORMATETC* pFmtSrc) { + switch (pMedSrc->tymed) { + case TYMED_HGLOBAL: + pMedDest->hGlobal = (HGLOBAL) OleDuplicateData (pMedSrc->hGlobal, pFmtSrc->cfFormat, 0); + break; + case TYMED_GDI: + pMedDest->hBitmap = (HBITMAP) OleDuplicateData (pMedSrc->hBitmap, pFmtSrc->cfFormat, 0); + break; + case TYMED_MFPICT: + pMedDest->hMetaFilePict = (HMETAFILEPICT) OleDuplicateData (pMedSrc->hMetaFilePict, pFmtSrc->cfFormat, 0); + break; + case TYMED_ENHMF: + pMedDest->hEnhMetaFile = (HENHMETAFILE) OleDuplicateData (pMedSrc->hEnhMetaFile, pFmtSrc->cfFormat, 0); + break; + case TYMED_FILE: + pMedSrc->lpszFileName = (LPOLESTR) OleDuplicateData (pMedSrc->lpszFileName, pFmtSrc->cfFormat, 0); + break; + case TYMED_ISTREAM: + pMedDest->pstm = pMedSrc->pstm; + pMedSrc->pstm->AddRef (); + break; + case TYMED_ISTORAGE: + pMedDest->pstg = pMedSrc->pstg; + pMedSrc->pstg->AddRef (); + break; + case TYMED_NULL: + default: + break; + } + pMedDest->tymed = pMedSrc->tymed; + pMedDest->pUnkForRelease = nullptr; + if (pMedSrc->pUnkForRelease) { + pMedDest->pUnkForRelease = pMedSrc->pUnkForRelease; + pMedSrc->pUnkForRelease->AddRef (); + } + } + STDMETHODIMP CIDataObject::EnumFormatEtc ( + /* [in] */ DWORD dwDirection, + /* [out] */ IEnumFORMATETC __RPC_FAR *__RPC_FAR *ppenumFormatEtc) { + ATLTRACE ("CIDataObject::EnumFormatEtc\n"); + if (!ppenumFormatEtc) + return E_POINTER; + + *ppenumFormatEtc = nullptr; + switch (dwDirection) { + case DATADIR_GET: + *ppenumFormatEtc = new CEnumFormatEtc (m_ArrFormatEtc); + if (!*ppenumFormatEtc) + return E_OUTOFMEMORY; + (*ppenumFormatEtc)->AddRef (); + break; + + case DATADIR_SET: + default: + return E_NOTIMPL; + break; + } + + return S_OK; + } + + STDMETHODIMP CIDataObject::DAdvise ( + /* [in] */ FORMATETC __RPC_FAR *pformatetc, + /* [in] */ DWORD advf, + /* [unique][in] */ IAdviseSink __RPC_FAR *pAdvSink, + /* [out] */ DWORD __RPC_FAR *pdwConnection) { + ATLTRACE ("CIDataObject::DAdvise\n"); + return OLE_E_ADVISENOTSUPPORTED; + } + + STDMETHODIMP CIDataObject::DUnadvise ( + /* [in] */ DWORD dwConnection) { + ATLTRACE ("CIDataObject::DUnadvise\n"); + return E_NOTIMPL; + } + + HRESULT STDMETHODCALLTYPE CIDataObject::EnumDAdvise ( + /* [out] */ IEnumSTATDATA __RPC_FAR *__RPC_FAR *ppenumAdvise) { + ATLTRACE ("CIDataObject::EnumDAdvise\n"); + return OLE_E_ADVISENOTSUPPORTED; + } + + ////////////////////////////////////////////////////////////////////// + // CIDropSource Class + ////////////////////////////////////////////////////////////////////// + + STDMETHODIMP CIDropSource::QueryInterface (/* [in] */ REFIID riid, + /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) { + *ppvObject = nullptr; + if (IID_IUnknown == riid || IID_IDropSource == riid) + *ppvObject = this; + + if (*ppvObject) { + ((LPUNKNOWN) *ppvObject)->AddRef (); + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHODIMP_ (ULONG) CIDropSource::AddRef (void) { + ATLTRACE ("CIDropSource::AddRef\n"); + return ++m_cRefCount; + } + + STDMETHODIMP_ (ULONG) CIDropSource::Release (void) { + ATLTRACE ("CIDropSource::Release\n"); + long nTemp; + nTemp = --m_cRefCount; + ATLASSERT (nTemp >= 0); + if (nTemp == 0) + delete this; + return nTemp; + } + + STDMETHODIMP CIDropSource::QueryContinueDrag ( + /* [in] */ BOOL fEscapePressed, + /* [in] */ DWORD grfKeyState) { + //ATLTRACE("CIDropSource::QueryContinueDrag\n"); + if (fEscapePressed) + return DRAGDROP_S_CANCEL; + if (!(grfKeyState & (MK_LBUTTON | MK_RBUTTON))) { + m_bDropped = true; + return DRAGDROP_S_DROP; + } + + return S_OK; + + } + + STDMETHODIMP CIDropSource::GiveFeedback ( + /* [in] */ DWORD dwEffect) { + //ATLTRACE("CIDropSource::GiveFeedback\n"); + return DRAGDROP_S_USEDEFAULTCURSORS; + } + + ////////////////////////////////////////////////////////////////////// + // CEnumFormatEtc Class + ////////////////////////////////////////////////////////////////////// + + CEnumFormatEtc::CEnumFormatEtc (const FormatEtcArray& ArrFE): + m_cRefCount (0), m_iCur (0) { + ATLTRACE ("CEnumFormatEtc::CEnumFormatEtc()\n"); + for (size_t i = 0; i < ArrFE.size (); ++i) + m_pFmtEtc.push_back (ArrFE[i]); + } + + CEnumFormatEtc::CEnumFormatEtc (const PFormatEtcArray& ArrFE): + m_cRefCount (0), m_iCur (0) { + for (size_t i = 0; i < ArrFE.size (); ++i) + m_pFmtEtc.push_back (*ArrFE[i]); + } + + STDMETHODIMP CEnumFormatEtc::QueryInterface (REFIID refiid, void FAR* FAR* ppv) { + ATLTRACE ("CEnumFormatEtc::QueryInterface()\n"); + *ppv = nullptr; + if (IID_IUnknown == refiid || IID_IEnumFORMATETC == refiid) + *ppv = this; + + if (*ppv) { + ((LPUNKNOWN) *ppv)->AddRef (); + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHODIMP_ (ULONG) CEnumFormatEtc::AddRef (void) { + ATLTRACE ("CEnumFormatEtc::AddRef()\n"); + return ++m_cRefCount; + } + + STDMETHODIMP_ (ULONG) CEnumFormatEtc::Release (void) { + ATLTRACE ("CEnumFormatEtc::Release()\n"); + long nTemp = --m_cRefCount; + ATLASSERT (nTemp >= 0); + if (nTemp == 0) + delete this; + + return nTemp; + } + + STDMETHODIMP CEnumFormatEtc::Next (ULONG celt, LPFORMATETC lpFormatEtc, ULONG FAR *pceltFetched) { + ATLTRACE ("CEnumFormatEtc::Next()\n"); + if (pceltFetched) + *pceltFetched = 0; + + ULONG cReturn = celt; + + if (celt <= 0 || !lpFormatEtc || (size_t) m_iCur >= m_pFmtEtc.size ()) + return S_FALSE; + + if (!pceltFetched && celt != 1) // pceltFetched can be nullptr only for 1 item request + return S_FALSE; + + while (m_iCur < (int) m_pFmtEtc.size () && cReturn > 0) { + *lpFormatEtc++ = m_pFmtEtc[m_iCur++]; + --cReturn; + } + if (pceltFetched) + *pceltFetched = celt - cReturn; + + return (cReturn == 0) ? S_OK : S_FALSE; + } + + STDMETHODIMP CEnumFormatEtc::Skip (ULONG celt) { + ATLTRACE ("CEnumFormatEtc::Skip()\n"); + if ((size_t) (m_iCur + int (celt)) >= m_pFmtEtc.size ()) + return S_FALSE; + m_iCur += celt; + return S_OK; + } + + STDMETHODIMP CEnumFormatEtc::Reset (void) { + ATLTRACE ("CEnumFormatEtc::Reset()\n"); + m_iCur = 0; + return S_OK; + } + + STDMETHODIMP CEnumFormatEtc::Clone (IEnumFORMATETC FAR * FAR*ppCloneEnumFormatEtc) { + ATLTRACE ("CEnumFormatEtc::Clone()\n"); + if (!ppCloneEnumFormatEtc) + return E_POINTER; + + CEnumFormatEtc *newEnum = new CEnumFormatEtc (m_pFmtEtc); + if (!newEnum) + return E_OUTOFMEMORY; + newEnum->AddRef (); + newEnum->m_iCur = m_iCur; + *ppCloneEnumFormatEtc = newEnum; + return S_OK; + } + + ////////////////////////////////////////////////////////////////////// + // CIDropTarget Class + ////////////////////////////////////////////////////////////////////// + CIDropTarget::CIDropTarget (HWND hTargetWnd): + m_hTargetWnd (hTargetWnd), + m_cRefCount (0), m_bAllowDrop (false), + m_pDropTargetHelper (nullptr), m_pSupportedFrmt (nullptr) { + if (FAILED (CoCreateInstance (CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER, + IID_IDropTargetHelper, (LPVOID*) &m_pDropTargetHelper))) + m_pDropTargetHelper = nullptr; + } + + CIDropTarget::~CIDropTarget () { + if (m_pDropTargetHelper) { + m_pDropTargetHelper->Release (); + m_pDropTargetHelper = nullptr; + } + } + + HRESULT STDMETHODCALLTYPE CIDropTarget::QueryInterface ( /* [in] */ REFIID riid, + /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) { + *ppvObject = nullptr; + if (IID_IUnknown == riid || IID_IDropTarget == riid) + *ppvObject = this; + + if (*ppvObject) { + ((LPUNKNOWN) *ppvObject)->AddRef (); + return S_OK; + } + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE CIDropTarget::Release (void) { + ATLTRACE ("CIDropTarget::Release\n"); + long nTemp; + nTemp = --m_cRefCount; + ATLASSERT (nTemp >= 0); + if (nTemp == 0) + delete this; + return nTemp; + } + + bool CIDropTarget::QueryDrop (DWORD grfKeyState, LPDWORD pdwEffect) { + ATLTRACE ("CIDropTarget::QueryDrop\n"); + DWORD dwOKEffects = *pdwEffect; + + if (!m_bAllowDrop) { + *pdwEffect = DROPEFFECT_NONE; + return false; + } + //CTRL+SHIFT -- DROPEFFECT_LINK + //CTRL -- DROPEFFECT_COPY + //SHIFT -- DROPEFFECT_MOVE + //no modifier -- DROPEFFECT_MOVE or whatever is allowed by src + *pdwEffect = (grfKeyState & MK_CONTROL) ? + ((grfKeyState & MK_SHIFT) ? DROPEFFECT_LINK : DROPEFFECT_COPY) : + ((grfKeyState & MK_SHIFT) ? DROPEFFECT_MOVE : 0); + if (*pdwEffect == 0) { + // No modifier keys used by user while dragging. + if (DROPEFFECT_COPY & dwOKEffects) + *pdwEffect = DROPEFFECT_COPY; + else if (DROPEFFECT_MOVE & dwOKEffects) + *pdwEffect = DROPEFFECT_MOVE; + else if (DROPEFFECT_LINK & dwOKEffects) + *pdwEffect = DROPEFFECT_LINK; + else { + *pdwEffect = DROPEFFECT_NONE; + } + } else { + // Check if the drag source application allows the drop effect desired by user. + // The drag source specifies this in DoDragDrop + if (!(*pdwEffect & dwOKEffects)) + *pdwEffect = DROPEFFECT_NONE; + } + + return (DROPEFFECT_NONE == *pdwEffect) ? false : true; + } + + HRESULT STDMETHODCALLTYPE CIDropTarget::DragEnter ( + /* [unique][in] */ IDataObject __RPC_FAR *pDataObj, + /* [in] */ DWORD grfKeyState, + /* [in] */ POINTL pt, + /* [out][in] */ DWORD __RPC_FAR *pdwEffect) { + ATLTRACE ("CIDropTarget::DragEnter\n"); + if (!pDataObj) + return E_INVALIDARG; + + if (m_pDropTargetHelper) + m_pDropTargetHelper->DragEnter (m_hTargetWnd, pDataObj, (LPPOINT) &pt, *pdwEffect); + //IEnumFORMATETC* pEnum; + //pDataObj->EnumFormatEtc(DATADIR_GET,&pEnum); + //FORMATETC ftm; + //for() + //pEnum->Next(1,&ftm,0); + //pEnum->Release(); + m_pSupportedFrmt = nullptr; + for (size_t i = 0; i < m_formatetc.size (); ++i) { + m_bAllowDrop = (pDataObj->QueryGetData (&m_formatetc[i]) == S_OK); + if (m_bAllowDrop) { + m_pSupportedFrmt = &m_formatetc[i]; + break; + } + } + + QueryDrop (grfKeyState, pdwEffect); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE CIDropTarget::DragOver ( + /* [in] */ DWORD grfKeyState, + /* [in] */ POINTL pt, + /* [out][in] */ DWORD __RPC_FAR *pdwEffect) { + ATLTRACE ("CIDropTarget::DragOver\n"); + if (m_pDropTargetHelper) + m_pDropTargetHelper->DragOver ((LPPOINT) &pt, *pdwEffect); + QueryDrop (grfKeyState, pdwEffect); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE CIDropTarget::DragLeave (void) { + ATLTRACE ("CIDropTarget::DragLeave\n"); + + if (m_pDropTargetHelper) + m_pDropTargetHelper->DragLeave (); + + m_bAllowDrop = false; + m_pSupportedFrmt = nullptr; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE CIDropTarget::Drop ( + /* [unique][in] */ IDataObject __RPC_FAR *pDataObj, + /* [in] */ DWORD grfKeyState, /* [in] */ POINTL pt, + /* [out][in] */ DWORD __RPC_FAR *pdwEffect) { + ATLTRACE ("CIDropTarget::Drop\n"); + if (!pDataObj) + return E_INVALIDARG; + + if (m_pDropTargetHelper) + m_pDropTargetHelper->Drop (pDataObj, (LPPOINT) &pt, *pdwEffect); + + if (QueryDrop (grfKeyState, pdwEffect)) { + if (m_bAllowDrop && m_pSupportedFrmt) { + STGMEDIUM medium; + if (pDataObj->GetData (m_pSupportedFrmt, &medium) == S_OK) { + if (OnDrop (m_pSupportedFrmt, medium, pdwEffect)) //does derive class wants us to free medium? + ReleaseStgMedium (&medium); + } + } + } + m_bAllowDrop = false; + *pdwEffect = DROPEFFECT_NONE; + m_pSupportedFrmt = nullptr; + return S_OK; + } + + ////////////////////////////////////////////////////////////////////// + // CIDragSourceHelper Class + ////////////////////////////////////////////////////////////////////// +} \ No newline at end of file diff --git a/DuiLib/Utils/DragDropImpl.h b/DuiLib/Utils/DragDropImpl.h new file mode 100644 index 0000000..f48745b --- /dev/null +++ b/DuiLib/Utils/DragDropImpl.h @@ -0,0 +1,248 @@ +// IDataObjectImpl.h: interface for the CIDataObjectImpl class. +/************************************************************************** +THIS CODE AND INFORMATION IS PROVIDED 'AS IS' WITHOUT WARRANTY OF +ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +PARTICULAR PURPOSE. +Author: Leon Finker 1/2001 +**************************************************************************/ +#ifndef __DRAGDROPIMPL_H__ +#define __DRAGDROPIMPL_H__ +#include +#include + +namespace DuiLib { + typedef std::vector FormatEtcArray; + typedef std::vector PFormatEtcArray; + typedef std::vector PStgMediumArray; + + /////////////////////////////////////////////////////////////////////////////////////////////// + class UILIB_API CEnumFormatEtc: public IEnumFORMATETC { + private: + ULONG m_cRefCount; + FormatEtcArray m_pFmtEtc; + int m_iCur; + + public: + CEnumFormatEtc (const FormatEtcArray& ArrFE); + CEnumFormatEtc (const PFormatEtcArray& ArrFE); + //IUnknown members + STDMETHOD (QueryInterface)(REFIID, void FAR* FAR*); + STDMETHOD_ (ULONG, AddRef)(void); + STDMETHOD_ (ULONG, Release)(void); + + //IEnumFORMATETC members + STDMETHOD (Next)(ULONG, LPFORMATETC, ULONG FAR *); + STDMETHOD (Skip)(ULONG); + STDMETHOD (Reset)(void); + STDMETHOD (Clone)(IEnumFORMATETC FAR * FAR*); + }; + + /////////////////////////////////////////////////////////////////////////////////////////////// + class UILIB_API CIDropSource: public IDropSource { + long m_cRefCount; + public: + bool m_bDropped; + + CIDropSource ():m_cRefCount (0), m_bDropped (false) {} + //IUnknown + virtual HRESULT STDMETHODCALLTYPE QueryInterface ( + /* [in] */ REFIID riid, + /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject); + virtual ULONG STDMETHODCALLTYPE AddRef (void); + virtual ULONG STDMETHODCALLTYPE Release (void); + //IDropSource + virtual HRESULT STDMETHODCALLTYPE QueryContinueDrag ( + /* [in] */ BOOL fEscapePressed, + /* [in] */ DWORD grfKeyState); + + virtual HRESULT STDMETHODCALLTYPE GiveFeedback ( + /* [in] */ DWORD dwEffect); + }; + + /////////////////////////////////////////////////////////////////////////////////////////////// + class UILIB_API CIDataObject: public IDataObject//,public IAsyncOperation + { + CIDropSource* m_pDropSource; + long m_cRefCount; + PFormatEtcArray m_ArrFormatEtc; + PStgMediumArray m_StgMedium; + + public: + CIDataObject (CIDropSource* pDropSource); + virtual ~CIDataObject (); + void CopyMedium (STGMEDIUM* pMedDest, STGMEDIUM* pMedSrc, FORMATETC* pFmtSrc); + //IUnknown + virtual HRESULT STDMETHODCALLTYPE QueryInterface ( + /* [in] */ REFIID riid, + /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject); + virtual ULONG STDMETHODCALLTYPE AddRef (void); + virtual ULONG STDMETHODCALLTYPE Release (void); + + //IDataObject + virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetData ( + /* [unique][in] */ FORMATETC __RPC_FAR *pformatetcIn, + /* [out] */ STGMEDIUM __RPC_FAR *pmedium); + + virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetDataHere ( + /* [unique][in] */ FORMATETC __RPC_FAR *pformatetc, + /* [out][in] */ STGMEDIUM __RPC_FAR *pmedium); + + virtual HRESULT STDMETHODCALLTYPE QueryGetData ( + /* [unique][in] */ FORMATETC __RPC_FAR *pformatetc); + + virtual HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc ( + /* [unique][in] */ FORMATETC __RPC_FAR *pformatectIn, + /* [out] */ FORMATETC __RPC_FAR *pformatetcOut); + + virtual /* [local] */ HRESULT STDMETHODCALLTYPE SetData ( + /* [unique][in] */ FORMATETC __RPC_FAR *pformatetc, + /* [unique][in] */ STGMEDIUM __RPC_FAR *pmedium, + /* [in] */ BOOL fRelease); + + virtual HRESULT STDMETHODCALLTYPE EnumFormatEtc ( + /* [in] */ DWORD dwDirection, + /* [out] */ IEnumFORMATETC __RPC_FAR *__RPC_FAR *ppenumFormatEtc); + + virtual HRESULT STDMETHODCALLTYPE DAdvise ( + /* [in] */ FORMATETC __RPC_FAR *pformatetc, + /* [in] */ DWORD advf, + /* [unique][in] */ IAdviseSink __RPC_FAR *pAdvSink, + /* [out] */ DWORD __RPC_FAR *pdwConnection); + + virtual HRESULT STDMETHODCALLTYPE DUnadvise ( + /* [in] */ DWORD dwConnection); + + virtual HRESULT STDMETHODCALLTYPE EnumDAdvise ( + /* [out] */ IEnumSTATDATA __RPC_FAR *__RPC_FAR *ppenumAdvise); + + //IAsyncOperation + //virtual HRESULT STDMETHODCALLTYPE SetAsyncMode( + // /* [in] */ BOOL fDoOpAsync) + //{ + // return E_NOTIMPL; + //} + // + //virtual HRESULT STDMETHODCALLTYPE GetAsyncMode( + // /* [out] */ BOOL __RPC_FAR *pfIsOpAsync) + //{ + // return E_NOTIMPL; + //} + // + //virtual HRESULT STDMETHODCALLTYPE StartOperation( + // /* [optional][unique][in] */ IBindCtx __RPC_FAR *pbcReserved) + //{ + // return E_NOTIMPL; + //} + // + //virtual HRESULT STDMETHODCALLTYPE InOperation( + // /* [out] */ BOOL __RPC_FAR *pfInAsyncOp) + //{ + // return E_NOTIMPL; + //} + // + //virtual HRESULT STDMETHODCALLTYPE EndOperation( + // /* [in] */ HRESULT hResult, + // /* [unique][in] */ IBindCtx __RPC_FAR *pbcReserved, + // /* [in] */ DWORD dwEffects) + //{ + // return E_NOTIMPL; + //} + }; + + /////////////////////////////////////////////////////////////////////////////////////////////// + class UILIB_API CIDropTarget: public IDropTarget { + DWORD m_cRefCount; + bool m_bAllowDrop; + struct IDropTargetHelper *m_pDropTargetHelper; + FormatEtcArray m_formatetc; + FORMATETC* m_pSupportedFrmt; + protected: + HWND m_hTargetWnd; + public: + + CIDropTarget (HWND m_hTargetWnd = nullptr); + virtual ~CIDropTarget (); + void AddSuportedFormat (FORMATETC& ftetc) { + m_formatetc.push_back (ftetc); + } + void SetTargetWnd (HWND hWnd) { + m_hTargetWnd = hWnd; + } + + //return values: true - release the medium. false - don't release the medium + virtual bool OnDrop (FORMATETC* pFmtEtc, STGMEDIUM& medium, DWORD *pdwEffect) = 0; + + virtual HRESULT STDMETHODCALLTYPE QueryInterface ( + /* [in] */ REFIID riid, + /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject); + virtual ULONG STDMETHODCALLTYPE AddRef (void) { + return ++m_cRefCount; + } + virtual ULONG STDMETHODCALLTYPE Release (void); + + bool QueryDrop (DWORD grfKeyState, LPDWORD pdwEffect); + virtual HRESULT STDMETHODCALLTYPE DragEnter ( + /* [unique][in] */ IDataObject __RPC_FAR *pDataObj, + /* [in] */ DWORD grfKeyState, + /* [in] */ POINTL pt, + /* [out][in] */ DWORD __RPC_FAR *pdwEffect); + virtual HRESULT STDMETHODCALLTYPE DragOver ( + /* [in] */ DWORD grfKeyState, + /* [in] */ POINTL pt, + /* [out][in] */ DWORD __RPC_FAR *pdwEffect); + virtual HRESULT STDMETHODCALLTYPE DragLeave (void); + virtual HRESULT STDMETHODCALLTYPE Drop ( + /* [unique][in] */ IDataObject __RPC_FAR *pDataObj, + /* [in] */ DWORD grfKeyState, + /* [in] */ POINTL pt, + /* [out][in] */ DWORD __RPC_FAR *pdwEffect); + }; + + class UILIB_API CDragSourceHelper { + IDragSourceHelper* pDragSourceHelper; + public: + CDragSourceHelper () { + if (FAILED (CoCreateInstance (CLSID_DragDropHelper, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IDragSourceHelper, + (void**) &pDragSourceHelper))) + pDragSourceHelper = nullptr; + } + virtual ~CDragSourceHelper () { + if (pDragSourceHelper) { + pDragSourceHelper->Release (); + pDragSourceHelper = nullptr; + } + } + + // IDragSourceHelper + HRESULT InitializeFromBitmap (HBITMAP hBitmap, + POINT& pt, // cursor position in client coords of the window + RECT& rc, // selected item's bounding rect + IDataObject* pDataObject, + COLORREF crColorKey = GetSysColor (COLOR_WINDOW)// color of the window used for transparent effect. + ) { + if (!pDragSourceHelper) + return E_FAIL; + + SHDRAGIMAGE di; + BITMAP bm; + GetObject (hBitmap, sizeof (bm), &bm); + di.sizeDragImage.cx = bm.bmWidth; + di.sizeDragImage.cy = bm.bmHeight; + di.hbmpDragImage = hBitmap; + di.crColorKey = crColorKey; + di.ptOffset.x = pt.x - rc.left; + di.ptOffset.y = pt.y - rc.top; + return pDragSourceHelper->InitializeFromBitmap (&di, pDataObject); + } + HRESULT InitializeFromWindow (HWND hwnd, POINT& pt, IDataObject* pDataObject) { + if (!pDragSourceHelper) + return E_FAIL; + return pDragSourceHelper->InitializeFromWindow (hwnd, &pt, pDataObject); + } + }; +} +#endif //__DRAGDROPIMPL_H__ \ No newline at end of file diff --git a/DuiLib/Utils/FawTools.hpp b/DuiLib/Utils/FawTools.hpp new file mode 100644 index 0000000..e0759d2 --- /dev/null +++ b/DuiLib/Utils/FawTools.hpp @@ -0,0 +1,312 @@ +#ifndef __FAW_TOOLS_HPP__ +#define __FAW_TOOLS_HPP__ + +#include "../Core/UIManager.h" + +namespace DuiLib { + class FawTools { + public: + // "1,2,3,4" -> RECT + template + static RECT parse_rect (T &str) { auto v = split_number (str, 4); return { (LONG) v[0], (LONG) v[1], (LONG) v[2], (LONG) v[3] }; } + + // "1.0,2.0,3.0,4.0" -> TPercentInfo + template + static TPercentInfo parse_TPercentInfo (T &str) { auto v = split_double (str, 4); return { v[0], v[1], v[2], v[3] }; } + + // "1,2" -> SIZE + template + static SIZE parse_size (T &str) { auto v = split_number (str, 2); return { (LONG) v[0], (LONG) v[1] }; } + + // "#FFFF0000" or "FF0000" -> size_t + template + static size_t parse_hex (T &str) { + size_t n = 0, i = (str.length () > 0 && str[0] == _T ('#')) ? 1 : 0; + for (; i < str.length (); ++i) { + TCHAR ch = str[i]; + if (ch >= _T ('0') && ch <= _T ('9')) { n = n * 16 + ch - _T ('0'); } + else if (ch >= _T ('A') && ch <= _T ('Z')) { n = n * 16 + ch - _T ('A') + 10; } + else if (ch >= _T ('a') && ch <= _T ('z')) { n = n * 16 + ch - _T ('a') + 10; } + else { break; } + } + if constexpr (!std::is_const::value) + str = str.substr (i); + return n; + } + + // "12345" -> size_t + template + static int parse_dec (T &str) { + int n = 0; + bool is_sign = (str.size () > 0 && str[0] == _T ('-')); + size_t i = is_sign ? 1 : 0; + for (; i < str.length (); ++i) { + TCHAR ch = str[i]; + if (ch < _T ('0') || ch > _T ('9')) + break; + n = n * 10 + ch - _T ('0'); + } + if constexpr (!std::is_const::value) + str = str.substr (i); + return is_sign ? 0 - n : n; + } + + // "true" or "True" or "TRUE" -> true + static bool parse_bool (string_view_t str) { + static std::map mbool { { _T ("true"), true }, { _T ("false"), false }, { _T ("on"), true }, { _T ("off"), false }, { _T ("yes"), true }, { _T ("no"), false }, { _T ("ok"), true }, { _T ("cancel"), false }, { _T ("1"), true }, { _T ("0"), false } }; + if (mbool.find (str) != mbool.end ()) + return mbool[str]; + return !str.empty (); + } + + // "a"="a" "b"=b c="c" d=d -> std::map + static std::map parse_keyvalue_pairs (string_view_t str) { + std::map m; + size_t begin = 0; + string_view_t str_key = _T (""), str_value = _T (""); + while (begin < str.size ()) { // parse str_key + TCHAR ch = str[begin]; + TCHAR sp = (ch == _T ('\"') || ch == _T ('\'') ? ch : _T ('=')); + if (ch == sp) ++begin; + size_t p = str.find (sp, begin); + if (p == string_t::npos) break; + str_key = str.substr (begin, p - begin); + if (p == string_t::npos) break; + // parse str_value + p = str.find (_T ('='), begin); + if (p == string_t::npos) break; + begin = p + 1; + if (begin >= str.size ()) break; + ch = str[begin]; + sp = (ch == _T ('\"') || ch == _T ('\'')) ? ch : _T (' '); + if (ch == sp) { + if (begin + 2 >= str.size ()) break; + ++begin; + } + p = str.find (sp, begin); + if (p == string_t::npos) break; + str_value = str.substr (begin, p - begin); + // reset + m[string_t (str_key)] = string_t (str_value); + if (p != string_t::npos) { + begin = p + 1; + if (begin >= str.size ()) break; + if (ch == sp && str[begin] == _T (' ')) ++begin; + } + str_key = str_value = _T (""); + } + if (!str_key.empty ()) + m[string_t (str_key)] = string_t (str_value); + return m; + } + + // + static std::string format_strA (std::string_view str, ...) { + if (str.empty ()) + return ""; + try { + va_list ap; + //Դhttp://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf + ptrdiff_t final_n, n = (str.length ()) * 2; + std::unique_ptr formatted; + while (true) { + formatted.reset (new char[n]); + //strcpy_s (&formatted [0], fmt_str.size (), fmt_str); + va_start (ap, str); + // _vsntprintf_s + final_n = _vsnprintf_s (formatted.get (), n, _TRUNCATE, str.data (), ap); + va_end (ap); + if (final_n < 0 || final_n >= n) + n += abs (final_n - n + 1); + else + break; + } + return formatted.get (); + } catch (...) { + } + return ""; + } + static std::wstring format_strW (std::wstring_view str, ...) { + if (str.empty ()) + return L""; + try { + va_list ap; + //Դhttp://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf + ptrdiff_t final_n, n = (str.length ()) * 2; + std::unique_ptr formatted; + while (true) { + formatted.reset (new wchar_t[n]); + //strcpy_s (&formatted [0], fmt_str.size (), fmt_str); + va_start (ap, str); + // _vsntprintf_s + final_n = _vsnwprintf_s (formatted.get (), n, _TRUNCATE, str.data (), ap); + va_end (ap); + if (final_n < 0 || final_n >= n) + n += abs (final_n - n + 1); + else + break; + } + return formatted.get (); + } catch (...) { + } + return L""; + } + static string_t format_str (string_view_t str, ...) { + if (str.empty ()) + return _T (""); + try { + va_list ap; + //Դhttp://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf + ptrdiff_t final_n, n = (str.length ()) * 2; + std::unique_ptr formatted; + while (true) { + formatted.reset (new TCHAR[n]); + //strcpy_s (&formatted [0], fmt_str.size (), fmt_str); + va_start (ap, str); + final_n = _vsntprintf_s (formatted.get (), n, _TRUNCATE, str.data (), ap); + va_end (ap); + if (final_n < 0 || final_n >= n) + n += abs (final_n - n + 1); + else + break; + } + return formatted.get (); + } catch (...) { + } + return _T (""); + } + + // + template + static size_t find (std::vector &v, T &item) { + for (int i = 0; i < v.size (); ++i) { + if (v[i] == item) + return i; + } + return string_t::npos; + } + + + + // + // ַ + // + + static std::wstring gb18030_to_utf16 (std::string_view _old) { return _conv_to_wide (_old, CP_ACP); } + static std::string utf16_to_gb18030 (std::wstring_view _old) { return _conv_to_multi (_old, CP_ACP); } + static std::wstring utf8_to_utf16 (std::string_view _old) { return _conv_to_wide (_old, CP_UTF8); } + static std::string utf16_to_utf8 (std::wstring_view _old) { return _conv_to_multi (_old, CP_UTF8); } + static std::string gb18030_to_utf8 (std::string_view _old) { return utf16_to_utf8 (gb18030_to_utf16 (_old)); } + static std::string utf8_to_gb18030 (std::string_view _old) { return utf16_to_gb18030 (utf8_to_utf16 (_old)); } +#ifdef UNICODE + static std::string get_gb18030 (string_view_t _old) { return utf16_to_gb18030 (_old); } + static std::string get_utf8 (string_view_t _old) { return utf16_to_utf8 (_old); } + static std::wstring get_utf16 (string_view_t _old) { return std::wstring (_old); } + static string_t get_T (std::string_view _old) { return gb18030_to_utf16 (_old); } + static string_t get_T_from_utf8 (std::string_view _old) { return utf8_to_utf16 (_old); } + static string_t get_T (std::wstring_view _old) { return std::wstring (_old); } +#else + static std::string get_gb18030 (string_view_t _old) { return std::string (_old); } + static std::string get_utf8 (string_view_t _old) { return gb18030_to_utf8 (_old); } + static std::wstring get_utf16 (string_view_t _old) { return gb18030_to_utf16 (_old); } + static string_t get_T (std::string_view _old) { return std::string (_old); } + static string_t get_T_from_utf8 (std::string_view _old) { return utf8_to_gb18030 (_old); } + static string_t get_T (std::wstring_view _old) { return utf16_to_gb18030 (_old); } +#endif + + private: + // "1,2,3,4" -> std::vector + template + static std::vector split_number (T &str, size_t expect = string_t::npos) { + std::vector v; + int64_t n = 0; + size_t i = 0; + bool has_num = false, is_sign = false; + for (; i < str.length (); ++i) { + TCHAR ch = str[i]; + if (ch >= _T ('0') && ch <= _T ('9')) { + n = n * 10 + ch - _T ('0'); + has_num = true; + } else if (ch == _T ('-')) { + is_sign = true; + } else { + v.push_back (is_sign ? -n : n); + n = 0; + has_num = false; + is_sign = false; + if (expect != string_t::npos && v.size () >= expect) + break; + } + } + if (has_num) + v.push_back (is_sign ? -n : n); + while (expect != string_t::npos && v.size () < expect) + v.push_back (0); + if constexpr (!std::is_const::value) + str = str.substr (i); + return v; + } + + template + static std::vector split_double (T &str, size_t expect = string_t::npos) { + std::vector v; + double n = 0.0, l = 1.0; + size_t i = 0; + bool has_num = false, is_sign = false; + for (; i < str.length (); ++i) { + TCHAR ch = str[i]; + if (ch >= _T ('0') && ch <= _T ('9')) { + if (l > 0.5) { + n = n * 10 + ch - _T ('0'); + } else { + n += (ch - _T ('0')) * l; + l /= 10; + } + has_num = true; + } else if (ch == _T ('.')) { + l /= 10; + } else if (ch == _T ('-')) { + is_sign = true; + } else { + v.push_back (is_sign ? -n : n); + n = 0.0; + has_num = false; + if (expect != string_t::npos && v.size () >= expect) + break; + } + } + if (has_num) + v.push_back (is_sign ? -n : n); + while (expect != string_t::npos && v.size () < expect) + v.push_back (0.0); + if constexpr (!std::is_const::value) + str = str.substr (i); + return v; + } + + static std::string _conv_to_multi (std::wstring_view _old, UINT ToType) { + int lenOld = (int) _old.length (); + int lenNew = ::WideCharToMultiByte (ToType, 0, _old.data (), lenOld, nullptr, 0, nullptr, nullptr); + std::string s ((size_t) (lenNew / 2 + 1) * 2, '\0'); + if (!::WideCharToMultiByte (ToType, 0, _old.data (), lenOld, &s[0], lenNew, nullptr, nullptr)) + return ""; + return s.c_str (); + } + + static std::wstring _conv_to_wide (std::string_view _old, UINT ToType) { + int lenOld = (int) _old.length (); + int lenNew = ::MultiByteToWideChar (ToType, 0, _old.data (), lenOld, nullptr, 0); + std::wstring s ((size_t) (lenNew / 2 + 1) * 2, L'\0'); + if (!::MultiByteToWideChar (ToType, 0, _old.data (), lenOld, &s[0], lenNew)) + return L""; + return s.c_str (); + } + + //static std::vector split (string_view_t str, TCHAR sp) { + // std::vector v; + // return v; + //} + }; +} + +#endif //__FAW_TOOLS_HPP__ diff --git a/DuiLib/Utils/Flash11.tlb b/DuiLib/Utils/Flash11.tlb new file mode 100644 index 0000000000000000000000000000000000000000..813228bf9571c96aa64726c2063a22828a33d65d GIT binary patch literal 14600 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 + +namespace DuiLib { + class CFlashEventHandler { + public: + CFlashEventHandler () {} + virtual ~CFlashEventHandler () {} + + virtual ULONG STDMETHODCALLTYPE Release (void) { + return S_OK; + } + virtual ULONG STDMETHODCALLTYPE AddRef (void) { + return S_OK; + } + + virtual HRESULT OnReadyStateChange (long newState) { + return S_OK; + } + + virtual HRESULT OnProgress (long percentDone) { + return S_OK; + } + + virtual HRESULT FSCommand (LPCTSTR command, LPCTSTR args) { + return S_OK; + } + + virtual HRESULT FlashCall (LPCTSTR request) { + return S_OK; + } + }; +} \ No newline at end of file diff --git a/DuiLib/Utils/StdAfx.h b/DuiLib/Utils/StdAfx.h new file mode 100644 index 0000000..c07aaba --- /dev/null +++ b/DuiLib/Utils/StdAfx.h @@ -0,0 +1 @@ +#include "../StdAfx.h" \ No newline at end of file diff --git a/DuiLib/Utils/TrayIcon.cpp b/DuiLib/Utils/TrayIcon.cpp new file mode 100644 index 0000000..d4bbcbf --- /dev/null +++ b/DuiLib/Utils/TrayIcon.cpp @@ -0,0 +1,99 @@ +#include "StdAfx.h" +#include "TrayIcon.h" + +namespace DuiLib { + CTrayIcon::CTrayIcon (void): m_uMessage (UIMSG_TRAYICON) {} + + CTrayIcon::~CTrayIcon (void) { + DeleteTrayIcon (); + } + + void CTrayIcon::CreateTrayIcon (HWND _RecvHwnd, UINT _IconIDResource, string_view_t _ToolTipText, UINT _Message) { + if (!_RecvHwnd || _IconIDResource <= 0) { + return; + } + if (_Message != 0) m_uMessage = _Message; + m_hIcon = LoadIcon (CPaintManagerUI::GetInstance (), MAKEINTRESOURCE (_IconIDResource)); + m_trayData.cbSize = sizeof (NOTIFYICONDATA); + m_trayData.hWnd = _RecvHwnd; + m_trayData.uID = _IconIDResource; + m_trayData.hIcon = m_hIcon; + m_trayData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; + m_trayData.uCallbackMessage = m_uMessage; + if (!_ToolTipText.empty ()) _tcscpy (m_trayData.szTip, _ToolTipText.data ()); + Shell_NotifyIcon (NIM_ADD, &m_trayData); + m_bEnabled = true; + } + + void CTrayIcon::DeleteTrayIcon () { + Shell_NotifyIcon (NIM_DELETE, &m_trayData); + m_bEnabled = false; + m_bVisible = false; + m_hWnd = nullptr; + m_uMessage = UIMSG_TRAYICON; + } + + bool CTrayIcon::SetTooltipText (string_view_t _ToolTipText) { + if (!_ToolTipText.empty ()) _tcscpy (m_trayData.szTip, _ToolTipText.data ()); + if (!m_bEnabled) return FALSE; + m_trayData.uFlags = NIF_TIP; + return Shell_NotifyIcon (NIM_MODIFY, &m_trayData) == TRUE; + } + + bool CTrayIcon::SetTooltipText (UINT _IDResource) { + TCHAR mbuf[256] = { 0 }; + LoadString (CPaintManagerUI::GetInstance (), _IDResource, mbuf, 256); + return SetTooltipText (mbuf); + } + + CDuiString CTrayIcon::GetTooltipText () const { + return m_trayData.szTip; + } + + bool CTrayIcon::SetIcon (HICON _Hicon) { + if (_Hicon) m_hIcon = _Hicon; + m_trayData.uFlags = NIF_ICON; + m_trayData.hIcon = _Hicon; + + if (!m_bEnabled) return FALSE; + return Shell_NotifyIcon (NIM_MODIFY, &m_trayData) == TRUE; + + return false; + } + + bool CTrayIcon::SetIcon (string_view_t _IconFile) { + HICON hIcon = LoadIcon (CPaintManagerUI::GetInstance (), _IconFile.data ()); + return SetIcon (hIcon); + } + + bool CTrayIcon::SetIcon (UINT _IDResource) { + HICON hIcon = LoadIcon (CPaintManagerUI::GetInstance (), MAKEINTRESOURCE (_IDResource)); + return SetIcon (hIcon); + } + + HICON CTrayIcon::GetIcon () const { + HICON hIcon = nullptr; + hIcon = m_trayData.hIcon; + return hIcon; + } + + void CTrayIcon::SetHideIcon () { + if (IsVisible ()) { + SetIcon ((HICON)nullptr); + m_bVisible = TRUE; + } + } + + void CTrayIcon::SetShowIcon () { + if (!IsVisible ()) { + SetIcon (m_hIcon); + m_bVisible = FALSE; + } + } + + void CTrayIcon::RemoveIcon () { + m_trayData.uFlags = 0; + Shell_NotifyIcon (NIM_DELETE, &m_trayData); + m_bEnabled = FALSE; + } +} diff --git a/DuiLib/Utils/TrayIcon.h b/DuiLib/Utils/TrayIcon.h new file mode 100644 index 0000000..58c2edc --- /dev/null +++ b/DuiLib/Utils/TrayIcon.h @@ -0,0 +1,44 @@ +#ifndef __UITRAICON_H__ +#define __UITRAICON_H__ + +#pragma once +#include + +namespace DuiLib { + class UILIB_API CTrayIcon { + public: + CTrayIcon (void); + virtual ~CTrayIcon (void); + + public: + void CreateTrayIcon (HWND _RecvHwnd, UINT _IconIDResource, string_view_t _ToolTipText = _T (""), UINT _Message = 0); + void DeleteTrayIcon (); + bool SetTooltipText (string_view_t _ToolTipText); + bool SetTooltipText (UINT _IDResource); + CDuiString GetTooltipText () const; + + bool SetIcon (HICON _Hicon); + bool SetIcon (string_view_t _IconFile); + bool SetIcon (UINT _IDResource); + HICON GetIcon () const; + void SetHideIcon (); + void SetShowIcon (); + void RemoveIcon (); + bool Enabled () { + return m_bEnabled; + }; + bool IsVisible () { + return !m_bVisible; + }; + + private: + bool m_bEnabled = false; + bool m_bVisible = false; + HWND m_hWnd = NULL; + UINT m_uMessage; + HICON m_hIcon = NULL; + NOTIFYICONDATA m_trayData = { 0 }; + }; +} +#endif // + diff --git a/DuiLib/Utils/UIDelegate.cpp b/DuiLib/Utils/UIDelegate.cpp new file mode 100644 index 0000000..79f002e --- /dev/null +++ b/DuiLib/Utils/UIDelegate.cpp @@ -0,0 +1,87 @@ +#include "StdAfx.h" + +namespace DuiLib { + + CDelegateBase::CDelegateBase (void* pObject, void* pFn) { + m_pObject = pObject; + m_pFn = pFn; + } + + CDelegateBase::CDelegateBase (const CDelegateBase& rhs) { + m_pObject = rhs.m_pObject; + m_pFn = rhs.m_pFn; + } + + CDelegateBase::~CDelegateBase () { + + } + + bool CDelegateBase::Equals (const CDelegateBase& rhs) const { + return m_pObject == rhs.m_pObject && m_pFn == rhs.m_pFn; + } + + bool CDelegateBase::operator() (void* param) { + return Invoke (param); + } + + void* CDelegateBase::GetFn () { + return m_pFn; + } + + void* CDelegateBase::GetObject () { + return m_pObject; + } + + CEventSource::~CEventSource () { + for (int i = 0; i < m_aDelegates.GetSize (); i++) { + CDelegateBase* pObject = static_cast(m_aDelegates[i]); + if (pObject) delete pObject; + } + } + + CEventSource::operator bool () { + return m_aDelegates.GetSize () > 0; + } + + void CEventSource::operator+= (const CDelegateBase& d) { + for (int i = 0; i < m_aDelegates.GetSize (); i++) { + CDelegateBase* pObject = static_cast(m_aDelegates[i]); + if (pObject && pObject->Equals (d)) return; + } + + m_aDelegates.Add (d.Copy ()); + } + + void CEventSource::operator+= (FnType pFn) { + (*this) += MakeDelegate (pFn); + } + + void CEventSource::operator-= (const CDelegateBase& d) { + for (int i = 0; i < m_aDelegates.GetSize (); i++) { + CDelegateBase* pObject = static_cast(m_aDelegates[i]); + if (pObject && pObject->Equals (d)) { + delete pObject; + m_aDelegates.Remove (i); + return; + } + } + } + void CEventSource::operator-= (FnType pFn) { + (*this) -= MakeDelegate (pFn); + } + + bool CEventSource::operator() (void* param) { + for (int i = 0; i < m_aDelegates.GetSize (); i++) { + CDelegateBase* pObject = static_cast(m_aDelegates[i]); + if (pObject && !(*pObject)(param)) return false; + } + return true; + } + void CEventSource::Clear () { + for (int i = 0; i < m_aDelegates.GetSize (); i++) { + CDelegateBase* pObject = static_cast(m_aDelegates[i]); + if (pObject) delete pObject; + } + m_aDelegates.Empty (); + } +} // namespace DuiLib diff --git a/DuiLib/Utils/UIDelegate.h b/DuiLib/Utils/UIDelegate.h new file mode 100644 index 0000000..f2f3319 --- /dev/null +++ b/DuiLib/Utils/UIDelegate.h @@ -0,0 +1,92 @@ +#ifndef __UIDELEGATE_H__ +#define __UIDELEGATE_H__ + +#pragma once + +namespace DuiLib { + + class UILIB_API CDelegateBase { + public: + CDelegateBase (void* pObject, void* pFn); + CDelegateBase (const CDelegateBase& rhs); + virtual ~CDelegateBase (); + bool Equals (const CDelegateBase& rhs) const; + bool operator() (void* param); + virtual CDelegateBase* Copy () const = 0; // add const for gcc + + protected: + void* GetFn (); + void* GetObject (); + virtual bool Invoke (void* param) = 0; + + private: + void* m_pObject; + void* m_pFn; + }; + + class CDelegateStatic: public CDelegateBase { + typedef bool (*Fn)(void*); + public: + CDelegateStatic (Fn pFn): CDelegateBase (nullptr, pFn) {} + CDelegateStatic (const CDelegateStatic& rhs): CDelegateBase (rhs) {} + virtual CDelegateBase* Copy () const { + return new CDelegateStatic (*this); + } + + protected: + virtual bool Invoke (void* param) { + Fn pFn = (Fn) GetFn (); + return (*pFn)(param); + } + }; + + template + class CDelegate: public CDelegateBase { + typedef bool (T::* Fn)(void*); + public: + CDelegate (O* pObj, Fn pFn): CDelegateBase (pObj, &pFn), m_pFn (pFn) {} + CDelegate (const CDelegate& rhs): CDelegateBase (rhs) { + m_pFn = rhs.m_pFn; + } + virtual CDelegateBase* Copy () const { + return new CDelegate (*this); + } + + protected: + virtual bool Invoke (void* param) { + O* pObject = (O*) GetObject (); + return (pObject->*m_pFn)(param); + } + + private: + Fn m_pFn; + }; + + template + CDelegate MakeDelegate (O* pObject, bool (T::* pFn)(void*)) { + return CDelegate (pObject, pFn); + } + + inline CDelegateStatic MakeDelegate (bool (*pFn)(void*)) { + return CDelegateStatic (pFn); + } + + class UILIB_API CEventSource { + typedef bool (*FnType)(void*); + public: + ~CEventSource (); + operator bool (); + void operator+= (const CDelegateBase& d); // add const for gcc + void operator+= (FnType pFn); + void operator-= (const CDelegateBase& d); + void operator-= (FnType pFn); + bool operator() (void* param); + void Clear (); + + protected: + CStdPtrArray m_aDelegates; + }; + +} // namespace DuiLib + +#endif // __UIDELEGATE_H__ \ No newline at end of file diff --git a/DuiLib/Utils/UIShadow.cpp b/DuiLib/Utils/UIShadow.cpp new file mode 100644 index 0000000..fb5bca3 --- /dev/null +++ b/DuiLib/Utils/UIShadow.cpp @@ -0,0 +1,621 @@ +#include "StdAfx.h" +#include "UIShadow.h" +#include "math.h" +#include "crtdbg.h" + +namespace DuiLib { + + const TCHAR *strWndClassName = _T ("PerryShadowWnd"); + bool CShadowUI::s_bHasInit = FALSE; + + CShadowUI::CShadowUI (void) {} + + CShadowUI::~CShadowUI (void) {} + + bool CShadowUI::Initialize (HINSTANCE hInstance) { + if (s_bHasInit) + return false; + + // Register window class for shadow window + WNDCLASSEX wcex; + + memset (&wcex, 0, sizeof (wcex)); + + wcex.cbSize = sizeof (WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = DefWindowProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = nullptr; + wcex.hCursor = LoadCursor (nullptr, IDC_ARROW); + wcex.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); + wcex.lpszMenuName = nullptr; + wcex.lpszClassName = strWndClassName; + wcex.hIconSm = nullptr; + + RegisterClassEx (&wcex); + + s_bHasInit = true; + return true; + } + + void CShadowUI::Create (CPaintManagerUI* pPaintManager) { + if (!m_bIsShowShadow) + return; + + // Already initialized + _ASSERT (CPaintManagerUI::GetInstance () != INVALID_HANDLE_VALUE); + _ASSERT (pPaintManager); + m_pManager = pPaintManager; + HWND hParentWnd = m_pManager->GetPaintWindow (); + // Add parent window - shadow pair to the map + _ASSERT (GetShadowMap ().find (hParentWnd) == GetShadowMap ().end ()); // Only one shadow for each window + GetShadowMap ()[hParentWnd] = this; + + // Determine the initial show state of shadow according to parent window's state + LONG lParentStyle = GetWindowLongPtr (hParentWnd, GWL_STYLE); + + // Create the shadow window + LONG styleValue = lParentStyle & WS_CAPTION; + m_hWnd = CreateWindowEx (WS_EX_LAYERED | WS_EX_TRANSPARENT, strWndClassName, nullptr, + /*WS_VISIBLE | */styleValue | WS_POPUPWINDOW, + CW_USEDEFAULT, 0, 0, 0, hParentWnd, nullptr, CPaintManagerUI::GetInstance (), nullptr); + + if (!(WS_VISIBLE & lParentStyle)) // Parent invisible + m_Status = SS_ENABLED; + else if ((WS_MAXIMIZE | WS_MINIMIZE) & lParentStyle) // Parent visible but does not need shadow + m_Status = SS_ENABLED | SS_PARENTVISIBLE; + else // Show the shadow + { + m_Status = SS_ENABLED | SS_VISABLE | SS_PARENTVISIBLE; + ::ShowWindow (m_hWnd, SW_SHOWNOACTIVATE); + Update (hParentWnd); + } + + // Replace the original WndProc of parent window to steal messages + m_OriParentProc = GetWindowLongPtr (hParentWnd, GWLP_WNDPROC); + +#pragma warning(disable: 4311) // temporrarily disable the type_cast warning in Win32 + SetWindowLongPtr (hParentWnd, GWLP_WNDPROC, (LONG_PTR) ParentProc); +#pragma warning(default: 4311) + + } + + std::map& CShadowUI::GetShadowMap () { + static std::map s_Shadowmap; + return s_Shadowmap; + } + + LRESULT CALLBACK CShadowUI::ParentProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + _ASSERT (GetShadowMap ().find (hwnd) != GetShadowMap ().end ()); // Shadow must have been attached + + CShadowUI *pThis = GetShadowMap ()[hwnd]; + if (pThis->m_bIsDisableShadow) { + +#pragma warning(disable: 4312) // temporrarily disable the type_cast warning in Win32 + // Call the default(original) window procedure for other messages or messages processed but not returned + return ((WNDPROC) pThis->m_OriParentProc)(hwnd, uMsg, wParam, lParam); +#pragma warning(default: 4312) + } + RECT WndRect = { 0 }; + switch (uMsg) { + case WM_ACTIVATEAPP: + case WM_NCACTIVATE: + { + ::SetWindowPos (pThis->m_hWnd, hwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOREDRAW); + break; + } + case WM_WINDOWPOSCHANGED: + GetWindowRect (hwnd, &WndRect); + if (pThis->m_bIsImageMode) { + SetWindowPos (pThis->m_hWnd, hwnd, WndRect.left - pThis->m_nSize, WndRect.top - pThis->m_nSize, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE); + } else { + SetWindowPos (pThis->m_hWnd, hwnd, WndRect.left + pThis->m_nxOffset - pThis->m_nSize, WndRect.top + pThis->m_nyOffset - pThis->m_nSize, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE); + } + break; + case WM_MOVE: + if (pThis->m_Status & SS_VISABLE) { + GetWindowRect (hwnd, &WndRect); + if (pThis->m_bIsImageMode) { + SetWindowPos (pThis->m_hWnd, hwnd, WndRect.left - pThis->m_nSize, WndRect.top - pThis->m_nSize, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE); + } else { + SetWindowPos (pThis->m_hWnd, hwnd, WndRect.left + pThis->m_nxOffset - pThis->m_nSize, WndRect.top + pThis->m_nyOffset - pThis->m_nSize, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE); + } + } + break; + + case WM_SIZE: + if (pThis->m_Status & SS_ENABLED) { + if (SIZE_MAXIMIZED == wParam || SIZE_MINIMIZED == wParam) { + ::ShowWindow (pThis->m_hWnd, SW_HIDE); + pThis->m_Status &= ~SS_VISABLE; + } else if (pThis->m_Status & SS_PARENTVISIBLE) // Parent maybe resized even if invisible + { + // Awful! It seems that if the window size was not decreased + // the window region would never be updated until WM_PAINT was sent. + // So do not Update() until next WM_PAINT is received in this case + if (LOWORD (lParam) > LOWORD (pThis->m_WndSize) || HIWORD (lParam) > HIWORD (pThis->m_WndSize)) + pThis->m_bUpdate = true; + else + pThis->Update (hwnd); + if (!(pThis->m_Status & SS_VISABLE)) { + ::ShowWindow (pThis->m_hWnd, SW_SHOWNOACTIVATE); + pThis->m_Status |= SS_VISABLE; + } + } + pThis->m_WndSize = lParam; + } + break; + + case WM_PAINT: + { + if (pThis->m_bUpdate) { + pThis->Update (hwnd); + pThis->m_bUpdate = false; + } + //return hr; + break; + } + + // In some cases of sizing, the up-right corner of the parent window region would not be properly updated + // Update() again when sizing is finished + case WM_EXITSIZEMOVE: + if (pThis->m_Status & SS_VISABLE) { + pThis->Update (hwnd); + } + break; + + case WM_SHOWWINDOW: + if (pThis->m_Status & SS_ENABLED) { + if (!wParam) // the window is being hidden + { + ::ShowWindow (pThis->m_hWnd, SW_HIDE); + pThis->m_Status &= ~(SS_VISABLE | SS_PARENTVISIBLE); + } else if (!(pThis->m_Status & SS_PARENTVISIBLE)) { + //pThis->Update(hwnd); + pThis->m_bUpdate = true; + ::ShowWindow (pThis->m_hWnd, SW_SHOWNOACTIVATE); + pThis->m_Status |= SS_VISABLE | SS_PARENTVISIBLE; + } + } + break; + + case WM_DESTROY: + DestroyWindow (pThis->m_hWnd); // Destroy the shadow + break; + + case WM_NCDESTROY: + GetShadowMap ().erase (hwnd); // Remove this window and shadow from the map + break; + + } + + +#pragma warning(disable: 4312) // temporrarily disable the type_cast warning in Win32 + // Call the default(original) window procedure for other messages or messages processed but not returned + return ((WNDPROC) pThis->m_OriParentProc)(hwnd, uMsg, wParam, lParam); +#pragma warning(default: 4312) + + } + void GetLastErrorMessage () { //Formats GetLastError() value. + LPVOID lpMsgBuf; + + FormatMessage ( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, GetLastError (), + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, 0, nullptr + ); + + // Display the string. + //MessageBox(nullptr, (const wchar_t*)lpMsgBuf, L"GetLastError", MB_OK | MB_ICONINFORMATION); + + // Free the buffer. + LocalFree (lpMsgBuf); + + } + void CShadowUI::Update (HWND hParent) { + if (!m_bIsShowShadow || !(m_Status & SS_VISABLE)) return; + RECT WndRect = { 0 }; + GetWindowRect (hParent, &WndRect); + int nShadWndWid; + int nShadWndHei; + if (m_bIsImageMode) { + if (m_sShadowImage.empty ()) return; + nShadWndWid = WndRect.right - WndRect.left + m_nSize * 2; + nShadWndHei = WndRect.bottom - WndRect.top + m_nSize * 2; + } else { + if (m_nSize == 0) return; + nShadWndWid = WndRect.right - WndRect.left + m_nSize * 2; + nShadWndHei = WndRect.bottom - WndRect.top + m_nSize * 2; + } + + // Create the alpha blending bitmap + BITMAPINFO bmi; // bitmap header + ZeroMemory (&bmi, sizeof (BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = nShadWndWid; + bmi.bmiHeader.biHeight = nShadWndHei; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; // four 8-bit components + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = nShadWndWid * nShadWndHei * 4; + BYTE *pvBits; // pointer to DIB section + HBITMAP hbitmap = CreateDIBSection (nullptr, &bmi, DIB_RGB_COLORS, (void **) &pvBits, nullptr, 0); + if (!hbitmap) { + GetLastErrorMessage (); + } + + HDC hMemDC = CreateCompatibleDC (nullptr); + if (!hMemDC) { + GetLastErrorMessage (); + } + HBITMAP hOriBmp = (HBITMAP) SelectObject (hMemDC, hbitmap); + if (GetLastError () != 0) { + GetLastErrorMessage (); + } + if (m_bIsImageMode) { + RECT rcPaint = { 0, 0, nShadWndWid, nShadWndHei }; + const TImageInfo* data = m_pManager->GetImageEx (m_sShadowImage, nullptr, 0); + if (!data) return; + RECT rcBmpPart = { 0 }; + rcBmpPart.right = data->nX; + rcBmpPart.bottom = data->nY; + RECT corner = m_rcShadowCorner; + HBITMAP hBmp = (data->hBitmap ? data->hBitmap : *(data->phBitmap)); + CRenderEngine::DrawImage (hMemDC, hBmp, rcPaint, rcPaint, rcBmpPart, corner, data->bAlpha, 0xFF, true, false, false); + } else { + ZeroMemory (pvBits, bmi.bmiHeader.biSizeImage); + MakeShadow ((UINT32 *) pvBits, hParent, &WndRect); + } + + POINT ptDst = { 0 }; + if (m_bIsImageMode) { + ptDst.x = WndRect.left - m_nSize; + ptDst.y = WndRect.top - m_nSize; + } else { + ptDst.x = WndRect.left + m_nxOffset - m_nSize; + ptDst.y = WndRect.top + m_nyOffset - m_nSize; + } + + POINT ptSrc = { 0, 0 }; + SIZE WndSize = { nShadWndWid, nShadWndHei }; + BLENDFUNCTION blendPixelFunction = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + MoveWindow (m_hWnd, ptDst.x, ptDst.y, nShadWndWid, nShadWndHei, FALSE); + BOOL bRet = ::UpdateLayeredWindow (m_hWnd, nullptr, &ptDst, &WndSize, hMemDC, &ptSrc, 0, &blendPixelFunction, ULW_ALPHA); + _ASSERT (bRet); // something was wrong.... + // Delete used resources + SelectObject (hMemDC, hOriBmp); + DeleteObject (hbitmap); + DeleteDC (hMemDC); + } + + void CShadowUI::MakeShadow (UINT32 *pShadBits, HWND hParent, RECT *rcParent) { + // The shadow algorithm: + // Get the region of parent window, + // Apply morphologic erosion to shrink it into the size (ShadowWndSize - Sharpness) + // Apply modified (with blur effect) morphologic dilation to make the blurred border + // The algorithm is optimized by assuming parent window is just "one piece" and without "wholes" on it + + // Get the region of parent window, + HRGN hParentRgn = CreateRectRgn (0, 0, rcParent->right - rcParent->left, rcParent->bottom - rcParent->top); + GetWindowRgn (hParent, hParentRgn); + + // Determine the Start and end point of each horizontal scan line + SIZE szParent = { rcParent->right - rcParent->left, rcParent->bottom - rcParent->top }; + SIZE szShadow = { szParent.cx + 2 * m_nSize, szParent.cy + 2 * m_nSize }; + // Extra 2 lines (set to be empty) in ptAnchors are used in dilation + int nAnchors = max (szParent.cy, szShadow.cy); // # of anchor points pares + int (*ptAnchors)[2] = new int[nAnchors + 2][2]; + int (*ptAnchorsOri)[2] = new int[szParent.cy][2]; // anchor points, will not modify during erosion + ptAnchors[0][0] = szParent.cx; + ptAnchors[0][1] = 0; + ptAnchors[nAnchors + 1][0] = szParent.cx; + ptAnchors[nAnchors + 1][1] = 0; + if (m_nSize > 0) { + // Put the parent window anchors at the center + for (int i = 0; i < m_nSize; i++) { + ptAnchors[i + 1][0] = szParent.cx; + ptAnchors[i + 1][1] = 0; + ptAnchors[szShadow.cy - i][0] = szParent.cx; + ptAnchors[szShadow.cy - i][1] = 0; + } + ptAnchors += m_nSize; + } + for (int i = 0; i < szParent.cy; i++) { + // find start point + int j; + for (j = 0; j < szParent.cx; j++) { + if (PtInRegion (hParentRgn, j, i)) { + ptAnchors[i + 1][0] = j + m_nSize; + ptAnchorsOri[i][0] = j; + break; + } + } + + if (j >= szParent.cx) // Start point not found + { + ptAnchors[i + 1][0] = szParent.cx; + ptAnchorsOri[i][1] = 0; + ptAnchors[i + 1][0] = szParent.cx; + ptAnchorsOri[i][1] = 0; + } else { + // find end point + for (j = szParent.cx - 1; j >= ptAnchors[i + 1][0]; j--) { + if (PtInRegion (hParentRgn, j, i)) { + ptAnchors[i + 1][1] = j + 1 + m_nSize; + ptAnchorsOri[i][1] = j + 1; + break; + } + } + } + } + + if (m_nSize > 0) + ptAnchors -= m_nSize; // Restore pos of ptAnchors for erosion + int (*ptAnchorsTmp)[2] = new int[nAnchors + 2][2]; // Store the result of erosion + // First and last line should be empty + ptAnchorsTmp[0][0] = szParent.cx; + ptAnchorsTmp[0][1] = 0; + ptAnchorsTmp[nAnchors + 1][0] = szParent.cx; + ptAnchorsTmp[nAnchors + 1][1] = 0; + int nEroTimes = 0; + // morphologic erosion + for (int i = 0; i < m_nSharpness - m_nSize; i++) { + nEroTimes++; + //ptAnchorsTmp[1][0] = szParent.cx; + //ptAnchorsTmp[1][1] = 0; + //ptAnchorsTmp[szParent.cy + 1][0] = szParent.cx; + //ptAnchorsTmp[szParent.cy + 1][1] = 0; + for (int j = 1; j < nAnchors + 1; j++) { + ptAnchorsTmp[j][0] = max (ptAnchors[j - 1][0], max (ptAnchors[j][0], ptAnchors[j + 1][0])) + 1; + ptAnchorsTmp[j][1] = min (ptAnchors[j - 1][1], min (ptAnchors[j][1], ptAnchors[j + 1][1])) - 1; + } + // Exchange ptAnchors and ptAnchorsTmp; + int (*ptAnchorsXange)[2] = ptAnchorsTmp; + ptAnchorsTmp = ptAnchors; + ptAnchors = ptAnchorsXange; + } + + // morphologic dilation + ptAnchors += (m_nSize < 0 ? -m_nSize : 0) + 1; // now coordinates in ptAnchors are same as in shadow window + // Generate the kernel + int nKernelSize = m_nSize > m_nSharpness ? m_nSize : m_nSharpness; + int nCenterSize = m_nSize > m_nSharpness ? (m_nSize - m_nSharpness) : 0; + UINT32 *pKernel = new UINT32[(2 * nKernelSize + 1) * (2 * nKernelSize + 1)]; + UINT32 *pKernelIter = pKernel; + for (int i = 0; i <= 2 * nKernelSize; i++) { + for (int j = 0; j <= 2 * nKernelSize; j++) { + double dLength = sqrt ((i - nKernelSize) * (i - nKernelSize) + (j - nKernelSize) * (double) (j - nKernelSize)); + if (dLength < nCenterSize) + *pKernelIter = m_nDarkness << 24 | PreMultiply (m_Color, m_nDarkness); + else if (dLength <= nKernelSize) { + UINT32 nFactor = ((UINT32) ((1 - (dLength - nCenterSize) / (m_nSharpness + 1)) * m_nDarkness)); + *pKernelIter = nFactor << 24 | PreMultiply (m_Color, (unsigned char) nFactor); + } else + *pKernelIter = 0; + //TRACE("%d ", *pKernelIter >> 24); + pKernelIter++; + } + //TRACE("\n"); + } + // Generate blurred border + for (int i = nKernelSize; i < szShadow.cy - nKernelSize; i++) { + int j; + if (ptAnchors[i][0] < ptAnchors[i][1]) { + + // Start of line + for (j = ptAnchors[i][0]; + j < min (max (ptAnchors[i - 1][0], ptAnchors[i + 1][0]) + 1, ptAnchors[i][1]); + j++) { + for (int k = 0; k <= 2 * nKernelSize; k++) { + UINT32 *pPixel = pShadBits + + (szShadow.cy - i - 1 + nKernelSize - k) * szShadow.cx + j - nKernelSize; + UINT32 *pKernelPixel = pKernel + k * (2 * nKernelSize + 1); + for (int l = 0; l <= 2 * nKernelSize; l++) { + if (*pPixel < *pKernelPixel) + *pPixel = *pKernelPixel; + pPixel++; + pKernelPixel++; + } + } + } // for() start of line + + // End of line + for (j = max (j, min (ptAnchors[i - 1][1], ptAnchors[i + 1][1]) - 1); + j < ptAnchors[i][1]; + j++) { + for (int k = 0; k <= 2 * nKernelSize; k++) { + UINT32 *pPixel = pShadBits + + (szShadow.cy - i - 1 + nKernelSize - k) * szShadow.cx + j - nKernelSize; + UINT32 *pKernelPixel = pKernel + k * (2 * nKernelSize + 1); + for (int l = 0; l <= 2 * nKernelSize; l++) { + if (*pPixel < *pKernelPixel) + *pPixel = *pKernelPixel; + pPixel++; + pKernelPixel++; + } + } + } // for() end of line + + } + } // for() Generate blurred border + + // Erase unwanted parts and complement missing + UINT32 clCenter = m_nDarkness << 24 | PreMultiply (m_Color, m_nDarkness); + for (int i = min (nKernelSize, max (m_nSize - m_nyOffset, 0)); + i < max (szShadow.cy - nKernelSize, min (szParent.cy + m_nSize - m_nyOffset, szParent.cy + 2 * m_nSize)); + i++) { + UINT32 *pLine = pShadBits + (szShadow.cy - i - 1) * szShadow.cx; + if (i - m_nSize + m_nyOffset < 0 || i - m_nSize + m_nyOffset >= szParent.cy) // Line is not covered by parent window + { + for (int j = ptAnchors[i][0]; j < ptAnchors[i][1]; j++) { + *(pLine + j) = clCenter; + } + } else { + for (int j = ptAnchors[i][0]; + j < min (ptAnchorsOri[i - m_nSize + m_nyOffset][0] + m_nSize - m_nxOffset, ptAnchors[i][1]); + j++) + *(pLine + j) = clCenter; + for (int j = max (ptAnchorsOri[i - m_nSize + m_nyOffset][0] + m_nSize - m_nxOffset, 0); + j < min (ptAnchorsOri[i - m_nSize + m_nyOffset][1] + m_nSize - m_nxOffset, szShadow.cx); + j++) + *(pLine + j) = 0; + for (int j = max (ptAnchorsOri[i - m_nSize + m_nyOffset][1] + m_nSize - m_nxOffset, ptAnchors[i][0]); + j < ptAnchors[i][1]; + j++) + *(pLine + j) = clCenter; + } + } + + // Delete used resources + delete[] (ptAnchors - (m_nSize < 0 ? -m_nSize : 0) - 1); + delete[] ptAnchorsTmp; + delete[] ptAnchorsOri; + delete[] pKernel; + DeleteObject (hParentRgn); + } + + void CShadowUI::ShowShadow (bool bShow) { + m_bIsShowShadow = bShow; + } + + bool CShadowUI::IsShowShadow () const { + return m_bIsShowShadow; + } + + + void CShadowUI::DisableShadow (bool bDisable) { + + + m_bIsDisableShadow = bDisable; + if (m_hWnd) { + + if (m_bIsDisableShadow) { + ::ShowWindow (m_hWnd, SW_HIDE); + } else { + // Determine the initial show state of shadow according to parent window's state + LONG lParentStyle = GetWindowLongPtr (GetParent (m_hWnd), GWL_STYLE); + + + if (!(WS_VISIBLE & lParentStyle)) // Parent invisible + m_Status = SS_ENABLED; + else if ((WS_MAXIMIZE | WS_MINIMIZE) & lParentStyle) // Parent visible but does not need shadow + m_Status = SS_ENABLED | SS_PARENTVISIBLE; + else // Show the shadow + { + m_Status = SS_ENABLED | SS_VISABLE | SS_PARENTVISIBLE; + + } + + + if ((WS_VISIBLE & lParentStyle) && !((WS_MAXIMIZE | WS_MINIMIZE) & lParentStyle))// Parent visible && no maxsize or min size + { + ::ShowWindow (m_hWnd, SW_SHOWNOACTIVATE); + Update (GetParent (m_hWnd)); + } + + + + } + + + } + + } + ////TODO shadow disnable fix//// + bool CShadowUI::IsDisableShadow () const { + + return m_bIsDisableShadow; + } + + bool CShadowUI::SetSize (int NewSize) { + if (NewSize > 35 || NewSize < -35) + return false; + + m_nSize = (signed char) NewSize; + if (m_hWnd && (SS_VISABLE & m_Status)) + Update (GetParent (m_hWnd)); + return true; + } + + bool CShadowUI::SetSharpness (unsigned int NewSharpness) { + if (NewSharpness > 35) + return false; + + m_nSharpness = (unsigned char) NewSharpness; + if (m_hWnd && (SS_VISABLE & m_Status)) + Update (GetParent (m_hWnd)); + return true; + } + + bool CShadowUI::SetDarkness (unsigned int NewDarkness) { + if (NewDarkness > 255) + return false; + + m_nDarkness = (unsigned char) NewDarkness; + if (m_hWnd && (SS_VISABLE & m_Status)) + Update (GetParent (m_hWnd)); + return true; + } + + bool CShadowUI::SetPosition (int NewXOffset, int NewYOffset) { + if (NewXOffset > 35 || NewXOffset < -35 || + NewYOffset > 35 || NewYOffset < -35) + return false; + + m_nxOffset = (signed char) NewXOffset; + m_nyOffset = (signed char) NewYOffset; + if (m_hWnd && (SS_VISABLE & m_Status)) + Update (GetParent (m_hWnd)); + return true; + } + + bool CShadowUI::SetColor (COLORREF NewColor) { + m_Color = NewColor; + if (m_hWnd && (SS_VISABLE & m_Status)) + Update (GetParent (m_hWnd)); + return true; + } + + bool CShadowUI::SetImage (string_view_t szImage) { + if (!szImage.empty ()) + return false; + + m_bIsImageMode = true; + m_sShadowImage = szImage; + if (m_hWnd && (SS_VISABLE & m_Status)) + Update (GetParent (m_hWnd)); + + return true; + } + + bool CShadowUI::SetShadowCorner (RECT rcCorner) { + if (rcCorner.left < 0 || rcCorner.top < 0 || rcCorner.right < 0 || rcCorner.bottom < 0) return false; + + m_rcShadowCorner = rcCorner; + if (m_hWnd && (SS_VISABLE & m_Status)) { + Update (GetParent (m_hWnd)); + } + + return true; + } + + bool CShadowUI::CopyShadow (CShadowUI* pShadow) { + if (m_bIsImageMode) { + pShadow->SetImage (m_sShadowImage); + pShadow->SetShadowCorner (m_rcShadowCorner); + pShadow->SetSize ((int) m_nSize); + } else { + pShadow->SetSize ((int) m_nSize); + pShadow->SetSharpness ((unsigned int) m_nSharpness); + pShadow->SetDarkness ((unsigned int) m_nDarkness); + pShadow->SetColor (m_Color); + pShadow->SetPosition ((int) m_nxOffset, (int) m_nyOffset); + } + + pShadow->ShowShadow (m_bIsShowShadow); + return true; + } +} //namespace DuiLib \ No newline at end of file diff --git a/DuiLib/Utils/UIShadow.h b/DuiLib/Utils/UIShadow.h new file mode 100644 index 0000000..bf31404 --- /dev/null +++ b/DuiLib/Utils/UIShadow.h @@ -0,0 +1,106 @@ +#ifndef __UISHADOW_H__ +#define __UISHADOW_H__ + +#pragma once +#include + +namespace DuiLib { + + class UILIB_API CShadowUI { + public: + friend class CPaintManagerUI; + + CShadowUI (void); + virtual ~CShadowUI (void); + + public: + // bShowΪʱŻᴴӰ + void ShowShadow (bool bShow); + bool IsShowShadow () const; + + void DisableShadow (bool bDisable); + bool IsDisableShadow () const; + + // 㷨Ӱĺ + bool SetSize (int NewSize = 0); + bool SetSharpness (unsigned int NewSharpness = 5); + bool SetDarkness (unsigned int NewDarkness = 200); + bool SetPosition (int NewXOffset = 5, int NewYOffset = 5); + bool SetColor (COLORREF NewColor = 0); + + // ͼƬӰĺ + bool SetImage (string_view_t szImage); + bool SetShadowCorner (RECT rcCorner); // ŹʽӰ + + // ԼӰʽƵ + bool CopyShadow (CShadowUI* pShadow); + + // Ӱ壬CPaintManagerUIԶ,ԼҪӰ + void Create (CPaintManagerUI* pPaintManager); + protected: + + // ʼעӰ + static bool Initialize (HINSTANCE hInstance); + + // ѾӵĴӰ,ParentProc()ͨõӰ + static std::map& GetShadowMap (); + + // ໯ + static LRESULT CALLBACK ParentProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + // ıСƶػӰʱ + void Update (HWND hParent); + + // ͨ㷨Ӱ + void MakeShadow (UINT32 *pShadBits, HWND hParent, RECT *rcParent); + + // alphaԤֵ + inline DWORD PreMultiply (COLORREF cl, unsigned char nAlpha) { + return (GetRValue (cl) * (DWORD) nAlpha / 255) | + (GetGValue (cl) * (DWORD) nAlpha / 255) << 8 | + (GetBValue (cl) * (DWORD) nAlpha / 255) << 16; + } + + protected: + enum ShadowStatus { + SS_ENABLED = 1, // Shadow is enabled, if not, the following one is always false + SS_VISABLE = 1 << 1, // Shadow window is visible + SS_PARENTVISIBLE = 1 << 2 // Parent window is visible, if not, the above one is always false + }; + + + static bool s_bHasInit; + + CPaintManagerUI *m_pManager; // CPaintManagerUIȡزԴ͸ + HWND m_hWnd = NULL; // Ӱľ + LONG_PTR m_OriParentProc = NULL; // ໯ + BYTE m_Status = 0; + bool m_bIsImageMode = false; // ǷΪͼƬӰģʽ + bool m_bIsShowShadow = false; // ǷҪʾӰ + bool m_bIsDisableShadow = false; + // 㷨ӰԱ + unsigned char m_nDarkness = 150; // Darkness, transparency of blurred area + unsigned char m_nSharpness = 5; // Sharpness, width of blurred border of shadow window + signed char m_nSize = 0; // Shadow window size, relative to parent window size + + // The X and Y offsets of shadow window, + // relative to the parent window, at center of both windows (not top-left corner), signed + signed char m_nxOffset = 0; + signed char m_nyOffset = 0; + + // Restore last parent window size, used to determine the update strategy when parent window is resized + LPARAM m_WndSize = 0; + + // Set this to true if the shadow should not be update until next WM_PAINT is received + bool m_bUpdate = false; + + COLORREF m_Color = RGB (0, 0, 0); // Color of shadow + + // ͼƬӰԱ + CDuiString m_sShadowImage; + RECT m_rcShadowCorner = { 0 }; + }; + +} + +#endif //__UISHADOW_H__ \ No newline at end of file diff --git a/DuiLib/Utils/Utils.cpp b/DuiLib/Utils/Utils.cpp new file mode 100644 index 0000000..20cc23a --- /dev/null +++ b/DuiLib/Utils/Utils.cpp @@ -0,0 +1,703 @@ +#include "StdAfx.h" +#include "Utils.h" + +namespace DuiLib { + /*void RECT::Join (const RECT& rc) { + left = min (left, rc.left); + top = min (top, rc.top); + right = max (right, rc.right); + bottom = max (bottom, rc.bottom); + }*/ + + //void RECT::ResetOffset () { + // ::OffsetRect (this, -left, -top); + //} + + //void RECT::Normalize () { + // if (left > right) { + // int iTemp = left; left = right; right = iTemp; + // } + // if (top > bottom) { + // int iTemp = top; top = bottom; bottom = iTemp; + // } + //} + + //void RECT::Offset (int cx, int cy) { + // ::OffsetRect (this, cx, cy); + //} + + //void RECT::Inflate (int cx, int cy) { + // ::InflateRect (this, cx, cy); + //} + + //void RECT::Deflate (int cx, int cy) { + // ::InflateRect (this, -cx, -cy); + //} + + //void RECT::Union (RECT& rc) { + // ::UnionRect (this, this, &rc); + //} + + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + CStdPtrArray::CStdPtrArray (int iPreallocSize): m_ppVoid (nullptr), m_nCount (0), m_nAllocated (iPreallocSize) { + ASSERT (iPreallocSize >= 0); + if (iPreallocSize > 0) m_ppVoid = static_cast(malloc (iPreallocSize * sizeof (LPVOID))); + } + + CStdPtrArray::CStdPtrArray (const CStdPtrArray& src) : m_ppVoid (nullptr), m_nCount (0), m_nAllocated (0) { + for (int i = 0; i < src.GetSize (); i++) + Add (src.GetAt (i)); + } + + CStdPtrArray::~CStdPtrArray () { + if (m_ppVoid) free (m_ppVoid); + } + + void CStdPtrArray::Empty () { + if (m_ppVoid) free (m_ppVoid); + m_ppVoid = nullptr; + m_nCount = m_nAllocated = 0; + } + + void CStdPtrArray::Resize (int iSize) { + Empty (); + m_ppVoid = static_cast(malloc (iSize * sizeof (LPVOID))); + ::ZeroMemory (m_ppVoid, iSize * sizeof (LPVOID)); + m_nAllocated = iSize; + m_nCount = iSize; + } + + bool CStdPtrArray::empty () const { + return m_nCount == 0; + } + + bool CStdPtrArray::Add (LPVOID pData) { + if (++m_nCount >= m_nAllocated) { + int nAllocated = m_nAllocated * 2; + if (nAllocated == 0) nAllocated = 11; + LPVOID* ppVoid = static_cast(realloc (m_ppVoid, nAllocated * sizeof (LPVOID))); + if (ppVoid) { + m_nAllocated = nAllocated; + m_ppVoid = ppVoid; + } else { + --m_nCount; + return false; + } + } + m_ppVoid[m_nCount - 1] = pData; + return true; + } + + bool CStdPtrArray::InsertAt (int iIndex, LPVOID pData) { + if (iIndex == m_nCount) return Add (pData); + if (iIndex < 0 || iIndex > m_nCount) return false; + if (++m_nCount >= m_nAllocated) { + int nAllocated = m_nAllocated * 2; + if (nAllocated == 0) nAllocated = 11; + LPVOID* ppVoid = static_cast(realloc (m_ppVoid, nAllocated * sizeof (LPVOID))); + if (ppVoid) { + m_nAllocated = nAllocated; + m_ppVoid = ppVoid; + } else { + --m_nCount; + return false; + } + } + memmove (&m_ppVoid[iIndex + 1], &m_ppVoid[iIndex], (m_nCount - iIndex - 1) * sizeof (LPVOID)); + m_ppVoid[iIndex] = pData; + return true; + } + + bool CStdPtrArray::SetAt (int iIndex, LPVOID pData) { + if (iIndex < 0 || iIndex >= m_nCount) return false; + m_ppVoid[iIndex] = pData; + return true; + } + + bool CStdPtrArray::Remove (int iIndex) { + if (iIndex < 0 || iIndex >= m_nCount) return false; + if (iIndex < --m_nCount) ::CopyMemory (m_ppVoid + iIndex, m_ppVoid + iIndex + 1, (m_nCount - iIndex) * sizeof (LPVOID)); + return true; + } + + int CStdPtrArray::Find (LPVOID pData) const { + for (int i = 0; i < m_nCount; i++) if (m_ppVoid[i] == pData) return i; + return -1; + } + + int CStdPtrArray::GetSize () const { + return m_nCount; + } + + LPVOID* CStdPtrArray::GetData () { + return m_ppVoid; + } + + LPVOID CStdPtrArray::GetAt (int iIndex) const { + if (iIndex < 0 || iIndex >= m_nCount) return nullptr; + return m_ppVoid[iIndex]; + } + + LPVOID CStdPtrArray::operator[] (int iIndex) const { + ASSERT (iIndex >= 0 && iIndex < m_nCount); + return m_ppVoid[iIndex]; + } + + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + CStdValArray::CStdValArray (int iElementSize, int iPreallocSize /*= 0*/): + m_pVoid (nullptr), + m_nCount (0), + m_iElementSize (iElementSize), + m_nAllocated (iPreallocSize) { + ASSERT (iElementSize > 0); + ASSERT (iPreallocSize >= 0); + if (iPreallocSize > 0) m_pVoid = static_cast(malloc (iPreallocSize * m_iElementSize)); + } + + CStdValArray::~CStdValArray () { + if (m_pVoid) free (m_pVoid); + } + + void CStdValArray::Empty () { + m_nCount = 0; // NOTE: We keep the memory in place + } + + bool CStdValArray::empty () const { + return m_nCount == 0; + } + + bool CStdValArray::Add (LPCVOID pData) { + if (++m_nCount >= m_nAllocated) { + int nAllocated = m_nAllocated * 2; + if (nAllocated == 0) nAllocated = 11; + LPBYTE pVoid = static_cast(realloc (m_pVoid, nAllocated * m_iElementSize)); + if (pVoid) { + m_nAllocated = nAllocated; + m_pVoid = pVoid; + } else { + --m_nCount; + return false; + } + } + ::CopyMemory (m_pVoid + ((m_nCount - 1) * m_iElementSize), pData, m_iElementSize); + return true; + } + + bool CStdValArray::Remove (int iIndex) { + if (iIndex < 0 || iIndex >= m_nCount) return false; + if (iIndex < --m_nCount) ::CopyMemory (m_pVoid + (iIndex * m_iElementSize), m_pVoid + ((iIndex + 1) * m_iElementSize), (m_nCount - iIndex) * m_iElementSize); + return true; + } + + int CStdValArray::GetSize () const { + return m_nCount; + } + + LPVOID CStdValArray::GetData () { + return static_cast(m_pVoid); + } + + LPVOID CStdValArray::GetAt (int iIndex) const { + if (iIndex < 0 || iIndex >= m_nCount) return nullptr; + return m_pVoid + (iIndex * m_iElementSize); + } + + LPVOID CStdValArray::operator[] (int iIndex) const { + ASSERT (iIndex >= 0 && iIndex < m_nCount); + return m_pVoid + (iIndex * m_iElementSize); + } + + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + CDuiString::CDuiString () { + reserve (64); + } + + CDuiString::CDuiString (const TCHAR ch) { + reserve (64); + assign (1, ch); + } + + CDuiString::CDuiString (LPCTSTR str) { + reserve (64); + assign (str); + } + + CDuiString::CDuiString (const string_t& src) : string_t (src) { + if (src.length () < 64) + reserve (64); + } + + CDuiString::CDuiString (string_view_t lpsz, int nLen) { + reserve (64); + if (!lpsz.empty ()) { + if (nLen == -1) + assign (lpsz); + else + assign (lpsz, nLen); + } + } + + int CDuiString::Replace (string_view_t pstrFrom, string_view_t pstrTo) { + //CDuiString sTemp; + //int nCount = 0; + //size_t iPos = find (pstrFrom); + //if (iPos == string_t::npos) return 0; + //int cchFrom = (int) _tcslen (pstrFrom); + //int cchTo = (int) _tcslen (pstrTo); + //while (iPos >= 0) { + // sTemp = Left (iPos); + // sTemp += pstrTo; + // sTemp += Mid (iPos + cchFrom); + // assign (sTemp); + // iPos = find (pstrFrom, iPos + cchTo); + // nCount++; + //} + //return nCount; + size_t ret = 0, pos = find (pstrFrom); + while (pos != string_t::npos) { + replace (pos, pstrFrom.length (), pstrTo); + ++ret; + pos = find (pstrFrom, pos + 2); + } + return (int) ret; + } + + int CDuiString::Format (string_view_t pstrFormat, ...) { + if (pstrFormat.empty ()) + return 0; + try { + va_list ap; +#ifndef __GNUC__ + //Դhttp://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf + ptrdiff_t final_n, n = (pstrFormat.length ()) * 2; + std::unique_ptr formatted; + while (true) { + formatted.reset (new TCHAR[n]); + //strcpy_s (&formatted [0], fmt_str.size (), fmt_str); + va_start (ap, pstrFormat); + // _vsntprintf_s + final_n = _vsntprintf_s (formatted.get (), n, _TRUNCATE, pstrFormat.data (), ap); + va_end (ap); + if (final_n < 0 || final_n >= n) + n += abs (final_n - n + 1); + else + break; + } + assign (formatted.get ()); +#else //__GNUC__ + char *buf = nullptr; + va_start (ap, fmt_str); + int iresult = vasprintf (&buf, fmt_str, ap); + va_end (ap); + if (buf) { + if (iresult >= 0) { + iresult = strlen (buf); + str_result.append (buf, iresult); + } + free (buf); + } +#endif //__GNUC__ + } catch (...) { + } + return (int) length (); + } + + ///////////////////////////////////////////////////////////////////////////// + // + // + + static UINT HashKey (string_view_t Key) { + UINT i = 0; + SIZE_T len = Key.length (); + while (len-- > 0) i = (i << 5) + i + Key[len]; + return i; + } + + static UINT HashKey (const CDuiString& Key) { + return HashKey (Key); + }; + + CStdStringPtrMap::CStdStringPtrMap (int nSize): m_nCount (0) { + if (nSize < 16) nSize = 16; + m_nBuckets = nSize; + m_aT = new TITEM*[nSize]; + memset (m_aT, 0, nSize * sizeof (TITEM*)); + } + + CStdStringPtrMap::~CStdStringPtrMap () { + if (m_aT) { + int len = m_nBuckets; + while (len--) { + TITEM* pItem = m_aT[len]; + while (pItem) { + TITEM* pKill = pItem; + pItem = pItem->pNext; + delete pKill; + } + } + delete[] m_aT; + m_aT = nullptr; + } + } + + void CStdStringPtrMap::RemoveAll () { + this->Resize (m_nBuckets); + } + + void CStdStringPtrMap::Resize (int nSize) { + if (m_aT) { + int len = m_nBuckets; + while (len--) { + TITEM* pItem = m_aT[len]; + while (pItem) { + TITEM* pKill = pItem; + pItem = pItem->pNext; + delete pKill; + } + } + delete[] m_aT; + m_aT = nullptr; + } + + if (nSize < 0) nSize = 0; + if (nSize > 0) { + m_aT = new TITEM*[nSize]; + memset (m_aT, 0, nSize * sizeof (TITEM*)); + } + m_nBuckets = nSize; + m_nCount = 0; + } + + LPVOID CStdStringPtrMap::Find (string_view_t key, bool optimize) const { + if (m_nBuckets == 0 || GetSize () == 0) return nullptr; + + UINT slot = HashKey (key) % m_nBuckets; + for (TITEM* pItem = m_aT[slot]; pItem; pItem = pItem->pNext) { + if (pItem->Key == key) { + if (optimize && pItem != m_aT[slot]) { + if (pItem->pNext) { + pItem->pNext->pPrev = pItem->pPrev; + } + pItem->pPrev->pNext = pItem->pNext; + pItem->pPrev = nullptr; + pItem->pNext = m_aT[slot]; + pItem->pNext->pPrev = pItem; + //itemƶͷ + m_aT[slot] = pItem; + } + return pItem->Data; + } + } + + return nullptr; + } + + bool CStdStringPtrMap::Insert (string_view_t key, LPVOID pData) { + if (m_nBuckets == 0) return false; + if (Find (key)) return false; + + // Add first in bucket + UINT slot = HashKey (key) % m_nBuckets; + TITEM* pItem = new TITEM; + pItem->Key = key; + pItem->Data = pData; + pItem->pPrev = nullptr; + pItem->pNext = m_aT[slot]; + if (pItem->pNext) + pItem->pNext->pPrev = pItem; + m_aT[slot] = pItem; + m_nCount++; + return true; + } + + LPVOID CStdStringPtrMap::Set (string_view_t key, LPVOID pData) { + if (m_nBuckets == 0) return pData; + + if (GetSize () > 0) { + UINT slot = HashKey (key) % m_nBuckets; + // Modify existing item + for (TITEM* pItem = m_aT[slot]; pItem; pItem = pItem->pNext) { + if (pItem->Key == key) { + LPVOID pOldData = pItem->Data; + pItem->Data = pData; + return pOldData; + } + } + } + + Insert (key, pData); + return nullptr; + } + + bool CStdStringPtrMap::Remove (string_view_t key) { + if (m_nBuckets == 0 || GetSize () == 0) return false; + + UINT slot = HashKey (key) % m_nBuckets; + TITEM** ppItem = &m_aT[slot]; + while (*ppItem) { + if ((*ppItem)->Key == key) { + TITEM* pKill = *ppItem; + *ppItem = (*ppItem)->pNext; + if (*ppItem) + (*ppItem)->pPrev = pKill->pPrev; + delete pKill; + m_nCount--; + return true; + } + ppItem = &((*ppItem)->pNext); + } + + return false; + } + + int CStdStringPtrMap::GetSize () const { +#if 0//def _DEBUG + int nCount = 0; + int len = m_nBuckets; + while (len--) { + for (const TITEM* pItem = m_aT[len]; pItem; pItem = pItem->pNext) nCount++; + } + ASSERT (m_nCount == nCount); +#endif + return m_nCount; + } + + TITEM *CStdStringPtrMap::GetAt (int iIndex) const { + if (m_nBuckets == 0 || GetSize () == 0) return false; + + int pos = 0; + int len = m_nBuckets; + while (len--) { + for (TITEM* pItem = m_aT[len]; pItem; pItem = pItem->pNext) { + if (pos++ == iIndex) { + return pItem; + } + } + } + + return nullptr; + } + + TITEM * CStdStringPtrMap::operator[] (int nIndex) const { + return GetAt (nIndex); + } + + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + + CWaitCursor::CWaitCursor () { + m_hOrigCursor = ::SetCursor (::LoadCursor (nullptr, IDC_WAIT)); + } + + CWaitCursor::~CWaitCursor () { + ::SetCursor (m_hOrigCursor); + } + + + ///////////////////////////////////////////////////////////////////////////////////// + // + // + //CImageString::CImageString () { + // Clear (); + //} + + //CImageString::CImageString (const CImageString& image) { + // Clone (image); + //} + + //const CImageString& CImageString::operator= (const CImageString& image) { + // Clone (image); + // return *this; + //} + + //void CImageString::Clone (const CImageString& image) { + // m_sImageAttribute = image.m_sImageAttribute; + + // m_sImage = image.m_sImage; + // m_sResType = image.m_sResType; + // m_imageInfo = image.m_imageInfo; + // m_bLoadSuccess = image.m_bLoadSuccess; + + // m_rcDest = image.m_rcDest; + // m_rcSource = image.m_rcSource; + // m_rcCorner = image.m_rcCorner; + // m_bFade = image.m_bFade; + // m_dwMask = image.m_dwMask; + // m_bHole = image.m_bHole; + // m_bTiledX = image.m_bTiledX; + // m_bTiledY = image.m_bTiledY; + //} + + //CImageString::~CImageString () { + + //} + + //const CDuiString& CImageString::GetAttributeString() const { + // return m_sImageAttribute; + //} + + //void CImageString::SetAttributeString(string_view_t pStrImageAttri) { + // if (m_sImageAttribute == pStrImageAttri) return; + // Clear (); + // m_sImageAttribute = pStrImageAttri; + // m_sImage = m_sImageAttribute; + //} + + //bool CImageString::LoadImage(CPaintManagerUI* pManager) { + // m_imageInfo = nullptr; + // m_bLoadSuccess = true; + // ZeroMemory(&m_rcDest, sizeof(RECT)); + // ZeroMemory(&m_rcSource, sizeof(RECT)); + // ZeroMemory(&m_rcCorner, sizeof(RECT)); + // m_bFade = 0xFF; + // m_dwMask = 0; + // m_bHole = false; + // m_bTiledX = false; + // m_bTiledY = false; + // ParseAttribute(m_sImageAttribute,*pManager->GetDPIObj()); + // if (!m_bLoadSuccess) return false; + + // const TImageInfo* data = nullptr; + // if (m_sResType.empty()) { + // data = pManager->GetImageEx(m_sImage.c_str (), nullptr, m_dwMask); + // } else { + // data = pManager->GetImageEx(m_sImage.c_str (), m_sResType.c_str (), m_dwMask); + // } + // if (!data) { + // m_bLoadSuccess = false; + // return false; + // } else { + // m_bLoadSuccess = true; + // } + + // if (m_rcSource.left == 0 && m_rcSource.right == 0 && m_rcSource.top == 0 && m_rcSource.bottom == 0) { + // m_rcSource.right = data->nX; + // m_rcSource.bottom = data->nY; + // } + // if (m_rcSource.right > data->nX) m_rcSource.right = data->nX; + // if (m_rcSource.bottom > data->nY) m_rcSource.bottom = data->nY; + // m_imageInfo = const_cast(data); + + // return true; + //} + + //bool CImageString::IsLoadSuccess () { + // return !m_sImageAttribute.empty () && m_bLoadSuccess; + //} + + //void CImageString::ModifyAttribute (string_view_t pStrModify) { + // //ParseAttribute (pStrModify); + //} + + //void CImageString::Clear () { + // m_sImageAttribute.Empty (); + // m_sImage.Empty(); + // m_sResType.Empty(); + // m_imageInfo = nullptr; + // m_bLoadSuccess = true; + // ZeroMemory (&m_rcDest, sizeof (RECT)); + // ZeroMemory (&m_rcSource, sizeof (RECT)); + // ZeroMemory (&m_rcCorner, sizeof (RECT)); + // m_bFade = 0xFF; + // m_dwMask = 0; + // m_bHole = false; + // m_bTiledX = false; + // m_bTiledY = false; + //} + + //void CImageString::ParseAttribute(string_view_t pStrImage) { + // if (!pStrImage.empty ()) + // return; + + // // 1aaa.jpg + // // 2file='aaa.jpg' res='' restype='0' dest='0,0,0,0' source='0,0,0,0' corner='0,0,0,0' + // // mask='#FF0000' fade='255' hole='false' xtiled='false' ytiled='false' + // CDuiString sItem; + // CDuiString sValue; + + // while (*pStrImage != _T('\0')) { + // sItem.Empty(); + // sValue.Empty(); + // while (*pStrImage > _T('\0') && *pStrImage <= _T(' ')) pStrImage = ::CharNext(pStrImage); + // while (*pStrImage != _T('\0') && *pStrImage != _T('=') && *pStrImage > _T(' ')) { + // LPTSTR pstrTemp = ::CharNext(pStrImage); + // while (pStrImage < pstrTemp) { + // sItem += *pStrImage++; + // } + // } + // while (*pStrImage > _T('\0') && *pStrImage <= _T(' ')) pStrImage = ::CharNext(pStrImage); + // if (*pStrImage++ != _T('=')) break; + // while (*pStrImage > _T('\0') && *pStrImage <= _T(' ')) pStrImage = ::CharNext(pStrImage); + // if (*pStrImage++ != _T('\'')) break; + // while (*pStrImage != _T('\0') && *pStrImage != _T('\'')) { + // LPTSTR pstrTemp = ::CharNext(pStrImage); + // while (pStrImage < pstrTemp) { + // sValue += *pStrImage++; + // } + // } + // if (*pStrImage++ != _T('\'')) break; + // if (!sValue.empty()) { + // if (sItem == _T("file") || sItem == _T("res")) { + // m_sImage = sValue; + // //if (g_Dpi.GetScale() != 100) { + // // std::wstringstream wss; + // // wss << L"@" << g_Dpi.GetScale() << L"."; + // // std::wstring suffix = wss.str(); + // // m_sImage.Replace(L".", suffix.c_str()); + // //} + // } else if (sItem == _T("restype")) { + // m_sResType = sValue; + // } else if (sItem == _T("dest")) { + // m_rcDest = FawTools::parse_rect (sValue); + // //g_Dpi.ScaleRect(&m_rcDest); + // } else if (sItem == _T("source")) { + // m_rcSource = FawTools::parse_rect (sValue); + // //g_Dpi.ScaleRect(&m_rcSource); + // } else if (sItem == _T("corner")) { + // m_rcCorner = FawTools::parse_rect (sValue); + // //g_Dpi.ScaleRect(&m_rcCorner); + // } else if (sItem == _T("mask")) { + // m_dwMask = (DWORD) FawTools::parse_hex (sValue); + // } else if (sItem == _T("fade")) { + // m_bFade = (BYTE) FawTools::parse_dec (sValue); + // } else if (sItem == _T("hole")) { + // m_bHole = FawTools::parse_bool (sValue); + // } else if (sItem == _T("xtiled")) { + // m_bTiledX = FawTools::parse_bool (sValue); + // } else if (sItem == _T("ytiled")) { + // m_bTiledY = FawTools::parse_bool (sValue); + // } + // } + // if (*pStrImage++ != _T(' ')) break; + // } + //} + + //void CImageString::SetDest(const RECT &rcDest) + //{ + // m_rcDest = rcDest; + //} + + //RECT CImageString::GetDest() const + //{ + // return m_rcDest; + //} + + //const TImageInfo* CImageString::GetImageInfo() const + //{ + // return m_imageInfo; + //} +} // namespace DuiLib \ No newline at end of file diff --git a/DuiLib/Utils/Utils.h b/DuiLib/Utils/Utils.h new file mode 100644 index 0000000..f18d4a1 --- /dev/null +++ b/DuiLib/Utils/Utils.h @@ -0,0 +1,223 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#pragma once +#include "OAIdl.h" +#include + +namespace DuiLib { + class UILIB_API CStdPtrArray { + public: + CStdPtrArray (int iPreallocSize = 0); + CStdPtrArray (const CStdPtrArray& src); + virtual ~CStdPtrArray (); + + void Empty (); + void Resize (int iSize); + bool empty () const; + int Find (LPVOID iIndex) const; + bool Add (LPVOID pData); + bool SetAt (int iIndex, LPVOID pData); + bool InsertAt (int iIndex, LPVOID pData); + bool Remove (int iIndex); + int GetSize () const; + LPVOID* GetData (); + + LPVOID GetAt (int iIndex) const; + LPVOID operator[] (int nIndex) const; + + protected: + LPVOID* m_ppVoid; + int m_nCount; + int m_nAllocated; + }; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CStdValArray { + public: + CStdValArray (int iElementSize, int iPreallocSize = 0); + virtual ~CStdValArray (); + + void Empty (); + bool empty () const; + bool Add (LPCVOID pData); + bool Remove (int iIndex); + int GetSize () const; + LPVOID GetData (); + + LPVOID GetAt (int iIndex) const; + LPVOID operator[] (int nIndex) const; + + protected: + LPBYTE m_pVoid; + int m_iElementSize; + int m_nCount; + int m_nAllocated; + }; + + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CDuiString : public string_t { + public: + CDuiString (); + CDuiString (const TCHAR ch); + CDuiString (LPCTSTR str); + CDuiString (const string_t& src); + CDuiString (string_view_t lpsz, int nLen = -1); + + inline int Compare (string_view_t pstr) const { return _tcscmp (c_str (), pstr.data ()); } + inline int CompareNoCase (string_view_t pstr) const { return _tcsicmp (c_str (), pstr.data ()); } + + inline void MakeUpper () { _tcsupr (&this->operator[] (0)); } + inline void MakeLower () { _tcslwr (&this->operator[] (0)); } + + inline CDuiString Left (size_t nLength) const { return substr (0, nLength); } + inline CDuiString Mid (size_t iPos, size_t nLength = string_t::npos) const { return substr (iPos, nLength); } + inline CDuiString Right (size_t nLength) const { return substr (length () - nLength); } + + int Replace (string_view_t pstrFrom, string_view_t pstrTo); + int Format (string_view_t pstrFormat, ...); + }; + + static std::vector StrSplit (CDuiString text, CDuiString sp) { + std::vector vResults; + size_t pos = text.find (sp); + while (pos != string_t::npos) { + CDuiString t = text.Left (pos); + vResults.push_back (t); + text = text.Right (text.length () - pos - sp.length ()); + pos = text.find (sp); + } + vResults.push_back (text); + return vResults; + } + ///////////////////////////////////////////////////////////////////////////////////// + // + + struct TITEM { + CDuiString Key; + LPVOID Data; + struct TITEM* pPrev; + struct TITEM* pNext; + }; + + class UILIB_API CStdStringPtrMap { + public: + CStdStringPtrMap (int nSize = 83); + virtual ~CStdStringPtrMap (); + + void Resize (int nSize = 83); + LPVOID Find (string_view_t key, bool optimize = true) const; + bool Insert (string_view_t key, LPVOID pData); + LPVOID Set (string_view_t key, LPVOID pData); + bool Remove (string_view_t key); + void RemoveAll (); + int GetSize () const; + TITEM *GetAt (int iIndex) const; + TITEM *operator[] (int nIndex) const; + + protected: + TITEM** m_aT; + int m_nBuckets; + int m_nCount; + }; + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class UILIB_API CWaitCursor { + public: + CWaitCursor (); + virtual ~CWaitCursor (); + + protected: + HCURSOR m_hOrigCursor; + }; + + ///////////////////////////////////////////////////////////////////////////////////// + // + + class CDuiVariant: public VARIANT { + public: + CDuiVariant () { + VariantInit (this); + } + CDuiVariant (int i) { + VariantInit (this); + this->vt = VT_I4; + this->intVal = i; + } + CDuiVariant (float f) { + VariantInit (this); + this->vt = VT_R4; + this->fltVal = f; + } + CDuiVariant (LPOLESTR s) { + VariantInit (this); + this->vt = VT_BSTR; + this->bstrVal = s; + } + CDuiVariant (IDispatch *disp) { + VariantInit (this); + this->vt = VT_DISPATCH; + this->pdispVal = disp; + } + + virtual ~CDuiVariant () { + VariantClear (this); + } + }; + + /////////////////////////////////////////////////////////////////////////////////////// + //// + //struct TImageInfo; + //class CPaintManagerUI; + //class UILIB_API CImageString + //{ + //public: + // CImageString(); + // CImageString(const CImageString&); + // const CImageString& operator=(const CImageString&); + // virtual ~CImageString(); + + // const CDuiString& GetAttributeString() const; + // void SetAttributeString(string_view_t pStrImageAttri); + // void ModifyAttribute(string_view_t pStrModify); + // bool LoadImage(CPaintManagerUI* pManager); + // bool IsLoadSuccess(); + + // RECT GetDest() const; + // void SetDest(const RECT &rcDest); + // const TImageInfo* GetImageInfo() const; + + //private: + // void Clone(const CImageString&); + // void Clear(); + // void ParseAttribute(string_view_t pStrImageAttri); + + //protected: + // friend class CRenderEngine; + // CDuiString m_sImageAttribute; + + // CDuiString m_sImage; + // CDuiString m_sResType; + // TImageInfo *m_imageInfo; + // bool m_bLoadSuccess; + + // RECT m_rcDest; + // RECT m_rcSource; + // RECT m_rcCorner; + // BYTE m_bFade; + // DWORD m_dwMask; + // bool m_bHole; + // bool m_bTiledX; + // bool m_bTiledY; + //}; +}// namespace DuiLib + +#endif // __UTILS_H__ \ No newline at end of file diff --git a/DuiLib/Utils/VersionHelpers.h b/DuiLib/Utils/VersionHelpers.h new file mode 100644 index 0000000..d0e7387 --- /dev/null +++ b/DuiLib/Utils/VersionHelpers.h @@ -0,0 +1,89 @@ +#ifndef _VERSIONHELPERS_H_INCLUDED_ +#define _VERSIONHELPERS_H_INCLUDED_ +#include + +namespace DuiLib { +#define _WIN32_WINNT_NT4 0x0400 +#define _WIN32_WINNT_WIN2K 0x0500 +#define _WIN32_WINNT_WINXP 0x0501 +#define _WIN32_WINNT_WS03 0x0502 +#define _WIN32_WINNT_WIN6 0x0600 +#define _WIN32_WINNT_VISTA 0x0600 +#define _WIN32_WINNT_WS08 0x0600 +#define _WIN32_WINNT_LONGHORN 0x0600 +#define _WIN32_WINNT_WIN7 0x0601 +#define _WIN32_WINNT_WIN8 0x0602 +#define _WIN32_WINNT_WINBLUE 0x0603 +#define _WIN32_WINNT_WINTHRESHOLD 0x0A00 /* ABRACADABRA_THRESHOLD*/ +#define _WIN32_WINNT_WIN10 0x0A00 /* ABRACADABRA_THRESHOLD*/ + +#define WM_DPICHANGED 0x02E0 + + static BOOL IsWindowsVersionOrGreater (WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) { + OSVERSIONINFOEXW osvi = { sizeof (osvi), 0, 0, 0, 0, { 0 }, 0, 0 }; + DWORDLONG const dwlConditionMask = VerSetConditionMask (VerSetConditionMask (VerSetConditionMask (0, VER_MAJORVERSION, VER_GREATER_EQUAL), VER_MINORVERSION, VER_GREATER_EQUAL), VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + osvi.dwMajorVersion = wMajorVersion; + osvi.dwMinorVersion = wMinorVersion; + osvi.wServicePackMajor = wServicePackMajor; + return VerifyVersionInfoW (&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; + } + + static BOOL IsWindowsXPOrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_WINXP), LOBYTE (_WIN32_WINNT_WINXP), 0); + } + + static BOOL IsWindowsXPSP1OrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_WINXP), LOBYTE (_WIN32_WINNT_WINXP), 1); + } + + static BOOL IsWindowsXPSP2OrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_WINXP), LOBYTE (_WIN32_WINNT_WINXP), 2); + } + + static BOOL IsWindowsXPSP3OrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_WINXP), LOBYTE (_WIN32_WINNT_WINXP), 3); + } + + static BOOL IsWindowsVistaOrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_VISTA), LOBYTE (_WIN32_WINNT_VISTA), 0); + } + + static BOOL IsWindowsVistaSP1OrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_VISTA), LOBYTE (_WIN32_WINNT_VISTA), 1); + } + + static BOOL IsWindowsVistaSP2OrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_VISTA), LOBYTE (_WIN32_WINNT_VISTA), 2); + } + + static BOOL IsWindows7OrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_WIN7), LOBYTE (_WIN32_WINNT_WIN7), 0); + } + + static BOOL IsWindows7SP1OrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_WIN7), LOBYTE (_WIN32_WINNT_WIN7), 1); + } + + static BOOL IsWindows8OrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_WIN8), LOBYTE (_WIN32_WINNT_WIN8), 0); + } + + static BOOL IsWindows8Point1OrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_WINBLUE), LOBYTE (_WIN32_WINNT_WINBLUE), 0); + } + + static BOOL IsWindowsThresholdOrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_WINTHRESHOLD), LOBYTE (_WIN32_WINNT_WINTHRESHOLD), 0); + } + + static BOOL IsWindows10OrGreater () { + return IsWindowsVersionOrGreater (HIBYTE (_WIN32_WINNT_WINTHRESHOLD), LOBYTE (_WIN32_WINNT_WINTHRESHOLD), 0); + } + + static BOOL IsWindowsServer () { + OSVERSIONINFOEXW osvi = { sizeof (osvi), 0, 0, 0, 0, { 0 }, 0, 0, 0, VER_NT_WORKSTATION }; + DWORDLONG const dwlConditionMask = VerSetConditionMask (0, VER_PRODUCT_TYPE, VER_EQUAL); + return !VerifyVersionInfoW (&osvi, VER_PRODUCT_TYPE, dwlConditionMask); + } +} +#endif // _VERSIONHELPERS_H_INCLUDED_ diff --git a/DuiLib/Utils/WebBrowserEventHandler.h b/DuiLib/Utils/WebBrowserEventHandler.h new file mode 100644 index 0000000..437debf --- /dev/null +++ b/DuiLib/Utils/WebBrowserEventHandler.h @@ -0,0 +1,136 @@ +#pragma once +#include +#include +#include + +namespace DuiLib { + class CWebBrowserUI; + class CWebBrowserEventHandler { + public: + CWebBrowserEventHandler () {} + virtual ~CWebBrowserEventHandler () {} + + virtual void BeforeNavigate2 (CWebBrowserUI* pWeb, IDispatch *pDisp, VARIANT *&url, VARIANT *&Flags, VARIANT *&TargetFrameName, VARIANT *&PostData, VARIANT *&Headers, VARIANT_BOOL *&Cancel) {} + virtual void NavigateError (CWebBrowserUI* pWeb, IDispatch *pDisp, VARIANT * &url, VARIANT *&TargetFrameName, VARIANT *&StatusCode, VARIANT_BOOL *&Cancel) {} + virtual void NavigateComplete2 (CWebBrowserUI* pWeb, IDispatch *pDisp, VARIANT *&url) {} + virtual void ProgressChange (CWebBrowserUI* pWeb, LONG nProgress, LONG nProgressMax) {} + virtual void NewWindow3 (CWebBrowserUI* pWeb, IDispatch **pDisp, VARIANT_BOOL *&Cancel, DWORD dwFlags, BSTR bstrUrlContext, BSTR bstrUrl) {} + virtual void CommandStateChange (CWebBrowserUI* pWeb, long Command, VARIANT_BOOL Enable) {}; + virtual void TitleChange (CWebBrowserUI* pWeb, BSTR bstrTitle) {}; + virtual void DocumentComplete (CWebBrowserUI* pWeb, IDispatch *pDisp, VARIANT *&url) {} + + // interface IDocHostUIHandler + virtual HRESULT STDMETHODCALLTYPE ShowContextMenu (CWebBrowserUI* pWeb, + /* [in] */ DWORD dwID, + /* [in] */ POINT __RPC_FAR *ppt, + /* [in] */ IUnknown __RPC_FAR *pcmdtReserved, + /* [in] */ IDispatch __RPC_FAR *pdispReserved) { + //return E_NOTIMPL; + // E_NOTIMPL ϵͳҼ˵ + return S_OK; + //S_OK ϵͳҼ˵ + } + + virtual HRESULT STDMETHODCALLTYPE GetHostInfo (CWebBrowserUI* pWeb, + /* [out][in] */ DOCHOSTUIINFO __RPC_FAR *pInfo) { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE ShowUI (CWebBrowserUI* pWeb, + /* [in] */ DWORD dwID, + /* [in] */ IOleInPlaceActiveObject __RPC_FAR *pActiveObject, + /* [in] */ IOleCommandTarget __RPC_FAR *pCommandTarget, + /* [in] */ IOleInPlaceFrame __RPC_FAR *pFrame, + /* [in] */ IOleInPlaceUIWindow __RPC_FAR *pDoc) { + return S_FALSE; + } + + virtual HRESULT STDMETHODCALLTYPE HideUI (CWebBrowserUI* pWeb) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE UpdateUI (CWebBrowserUI* pWeb) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE EnableModeless (CWebBrowserUI* pWeb, + /* [in] */ BOOL fEnable) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE OnDocWindowActivate (CWebBrowserUI* pWeb, + /* [in] */ BOOL fActivate) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE OnFrameWindowActivate (CWebBrowserUI* pWeb, + /* [in] */ BOOL fActivate) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE ResizeBorder (CWebBrowserUI* pWeb, + /* [in] */ LPCRECT prcBorder, + /* [in] */ IOleInPlaceUIWindow __RPC_FAR *pUIWindow, + /* [in] */ BOOL fRameWindow) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE TranslateAccelerator (CWebBrowserUI* pWeb, + /* [in] */ LPMSG lpMsg, + /* [in] */ const GUID __RPC_FAR *pguidCmdGroup, + /* [in] */ DWORD nCmdID) { + return S_FALSE; + } + + virtual HRESULT STDMETHODCALLTYPE GetOptionKeyPath (CWebBrowserUI* pWeb, + /* [out] */ LPOLESTR __RPC_FAR *pchKey, + /* [in] */ DWORD dw) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE GetDropTarget (CWebBrowserUI* pWeb, + /* [in] */ IDropTarget __RPC_FAR *pDropTarget, + /* [out] */ IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget) { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE GetExternal (CWebBrowserUI* pWeb, + /* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch) { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE TranslateUrl (CWebBrowserUI* pWeb, + /* [in] */ DWORD dwTranslate, + /* [in] */ OLECHAR __RPC_FAR *pchURLIn, + /* [out] */ OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut) { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE FilterDataObject (CWebBrowserUI* pWeb, + /* [in] */ IDataObject __RPC_FAR *pDO, + /* [out] */ IDataObject __RPC_FAR *__RPC_FAR *ppDORet) { + return S_OK; + } + + // virtual HRESULT STDMETHODCALLTYPE GetOverrideKeyPath( + // /* [annotation][out] */ + // __deref_out LPOLESTR *pchKey, + // /* [in] */ DWORD dw) + // { + // return E_NOTIMPL; + // } + + // IDownloadManager + virtual HRESULT STDMETHODCALLTYPE Download (CWebBrowserUI* pWeb, + /* [in] */ IMoniker *pmk, + /* [in] */ IBindCtx *pbc, + /* [in] */ DWORD dwBindVerb, + /* [in] */ LONG grfBINDF, + /* [in] */ BINDINFO *pBindInfo, + /* [in] */ LPCOLESTR pszHeaders, + /* [in] */ LPCOLESTR pszRedir, + /* [in] */ UINT uiCP) { + return S_OK; + } + }; +} diff --git a/DuiLib/Utils/WinImplBase.cpp b/DuiLib/Utils/WinImplBase.cpp new file mode 100644 index 0000000..1c06557 --- /dev/null +++ b/DuiLib/Utils/WinImplBase.cpp @@ -0,0 +1,385 @@ +#include "StdAfx.h" +#include +namespace DuiLib { + ////////////////////////////////////////////////////////////////////////// + // + DUI_BEGIN_MESSAGE_MAP (WindowImplBase, CNotifyPump) + DUI_ON_MSGTYPE (DUI_MSGTYPE_CLICK, OnClick) + DUI_ON_MSGTYPE (DUI_MSGTYPE_HEADERCLICK, OnHeaderClick) + DUI_ON_MSGTYPE (DUI_MSGTYPE_SELECTCHANGED, OnSelectChanged) + DUI_ON_MSGTYPE (DUI_MSGTYPE_TEXTCHANGED, OnTextChanged) + DUI_ON_MSGTYPE (DUI_MSGTYPE_ITEMSELECT, OnItemSelect) + DUI_ON_MSGTYPE (DUI_MSGTYPE_TIMER, OnTimer) + DUI_END_MESSAGE_MAP () + + void WindowImplBase::OnFinalMessage (HWND hWnd) { + m_pm.RemovePreMessageFilter (this); + m_pm.RemoveNotifier (this); + m_pm.ReapObjects (m_pm.GetRoot ()); + } + + LRESULT WindowImplBase::ResponseDefaultKeyEvent (WPARAM wParam) { + if (wParam == VK_RETURN) { + return FALSE; + } else if (wParam == VK_ESCAPE) { + return TRUE; + } + + return FALSE; + } + + UINT WindowImplBase::GetClassStyle () const { + return CS_DBLCLKS; + } + + CControlUI* WindowImplBase::CreateControl (string_view_t pstrClass) { + return nullptr; + } + + string_view_t WindowImplBase::QueryControlText (string_view_t lpstrId, string_view_t lpstrType) { + return nullptr; + } + + LRESULT WindowImplBase::MessageHandler (UINT uMsg, WPARAM wParam, LPARAM /*lParam*/, bool& /*bHandled*/) { + if (uMsg == WM_KEYDOWN) { + switch (wParam) { + case VK_RETURN: + case VK_ESCAPE: + return ResponseDefaultKeyEvent (wParam); + default: + break; + } + } + return FALSE; + } + + LRESULT WindowImplBase::OnClose (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + + LRESULT WindowImplBase::OnDestroy (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + +#if defined(WIN32) && !defined(UNDER_CE) + LRESULT WindowImplBase::OnNcActivate (UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled) { + if (::IsIconic (GetHWND ())) bHandled = FALSE; + return (wParam == 0) ? TRUE : FALSE; + } + + LRESULT WindowImplBase::OnNcCalcSize (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + return 0; + } + + LRESULT WindowImplBase::OnNcPaint (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { + return 0; + } + + + BOOL WindowImplBase::IsInStaticControl (CControlUI *pControl, POINT &pt) { + if (!pControl || pControl->IsDynamic (pt)) + return FALSE; + + CDuiString strClassName; + static std::vector vctStaticName { _T ("controlui"), _T ("textui"), _T ("labelui"), _T ("containerui"), _T ("horizontallayoutui"), _T ("verticallayoutui"), _T("tablayoutui"), _T ("childlayoutui"), _T ("dialoglayoutui"), _T ("progresscontainerui") }; + + strClassName = pControl->GetClass (); + strClassName.MakeLower (); + if (vctStaticName.end () != std::find (vctStaticName.begin (), vctStaticName.end (), strClassName)) { + CControlUI* pParent = pControl->GetParent (); + while (pParent) { + if (pParent->IsDynamic (pt)) + return FALSE; + strClassName = pParent->GetClass (); + strClassName.MakeLower (); + if (vctStaticName.end () == std::find (vctStaticName.begin (), vctStaticName.end (), strClassName)) + return FALSE; + + pParent = pParent->GetParent (); + } + + return TRUE; + } + + return FALSE; + } + + LRESULT WindowImplBase::OnNcHitTest (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + POINT pt { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + ::ScreenToClient (GetHWND (), &pt); + + RECT rcClient = { 0 }; + ::GetClientRect (GetHWND (), &rcClient); + + if (!::IsZoomed (GetHWND ())) { + RECT rcSizeBox = m_pm.GetSizeBox (); + if (pt.y < rcClient.top + rcSizeBox.top) { + if (pt.x < rcClient.left + rcSizeBox.left) return HTTOPLEFT; + if (pt.x > rcClient.right - rcSizeBox.right) return HTTOPRIGHT; + return HTTOP; + } else if (pt.y > rcClient.bottom - rcSizeBox.bottom) { + if (pt.x < rcClient.left + rcSizeBox.left) return HTBOTTOMLEFT; + if (pt.x > rcClient.right - rcSizeBox.right) return HTBOTTOMRIGHT; + return HTBOTTOM; + } + + if (pt.x < rcClient.left + rcSizeBox.left) return HTLEFT; + if (pt.x > rcClient.right - rcSizeBox.right) return HTRIGHT; + } + + RECT rcCaption = m_pm.GetCaptionRect (); + if (-1 == rcCaption.bottom) { + rcCaption.bottom = rcClient.bottom; + } + + if (pt.x >= rcClient.left + rcCaption.left && pt.x < rcClient.right - rcCaption.right + && pt.y >= rcCaption.top && pt.y < rcCaption.bottom) { + CControlUI* pControl = m_pm.FindControl (pt); + if (IsInStaticControl (pControl, pt)) { + return HTCAPTION; + } + } + + return HTCLIENT; + } + + LRESULT WindowImplBase::OnGetMinMaxInfo (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + MONITORINFO Monitor = {}; + Monitor.cbSize = sizeof (Monitor); + ::GetMonitorInfo (::MonitorFromWindow (m_hWnd, MONITOR_DEFAULTTOPRIMARY), &Monitor); + RECT rcWork = Monitor.rcWork; + if (Monitor.dwFlags != MONITORINFOF_PRIMARY) { + ::OffsetRect (&rcWork, -rcWork.left, -rcWork.top); + } + + LPMINMAXINFO lpMMI = (LPMINMAXINFO) lParam; + lpMMI->ptMaxPosition.x = rcWork.left; + lpMMI->ptMaxPosition.y = rcWork.top; + lpMMI->ptMaxSize.x = rcWork.right - rcWork.left; + lpMMI->ptMaxSize.y = rcWork.bottom - rcWork.top; + lpMMI->ptMaxTrackSize.x = m_pm.GetMaxInfo ().cx == 0 ? rcWork.right - rcWork.left : m_pm.GetMaxInfo ().cx; + lpMMI->ptMaxTrackSize.y = m_pm.GetMaxInfo ().cy == 0 ? rcWork.bottom - rcWork.top : m_pm.GetMaxInfo ().cy; + lpMMI->ptMinTrackSize.x = m_pm.GetMinInfo ().cx; + lpMMI->ptMinTrackSize.y = m_pm.GetMinInfo ().cy; + + bHandled = TRUE; + return 0; + } + + LRESULT WindowImplBase::OnMouseWheel (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + + LRESULT WindowImplBase::OnMouseHover (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } +#endif + + LRESULT WindowImplBase::OnSize (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + SIZE szRoundCorner = m_pm.GetRoundCorner (); +#if defined(WIN32) && !defined(UNDER_CE) + if (!::IsIconic (GetHWND ())) { + RECT rcWnd = { 0 }; + ::GetWindowRect (GetHWND (), &rcWnd); + ::OffsetRect (&rcWnd, -rcWnd.left, -rcWnd.top); + rcWnd.right++; rcWnd.bottom++; + HRGN hRgn = ::CreateRoundRectRgn (rcWnd.left, rcWnd.top, rcWnd.right, rcWnd.bottom, szRoundCorner.cx, szRoundCorner.cy); + ::SetWindowRgn (GetHWND (), hRgn, TRUE); + ::DeleteObject (hRgn); + } +#endif + bHandled = FALSE; + return 0; + } + + LRESULT WindowImplBase::OnChar (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + + LRESULT WindowImplBase::OnSysCommand (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + if (wParam == SC_CLOSE) { + bHandled = TRUE; + SendMessage (WM_CLOSE); + return 0; + } +#if defined(WIN32) && !defined(UNDER_CE) + BOOL bZoomed = ::IsZoomed (GetHWND ()); + LRESULT lRes = CWindowWnd::HandleMessage (uMsg, wParam, lParam); + if (::IsZoomed (GetHWND ()) != bZoomed) { + if (!bZoomed) { + CControlUI* pControl = static_cast(m_pm.FindControl (_T ("maxbtn"))); + if (pControl) pControl->SetVisible (false); + pControl = static_cast(m_pm.FindControl (_T ("restorebtn"))); + if (pControl) pControl->SetVisible (true); + } else { + CControlUI* pControl = static_cast(m_pm.FindControl (_T ("maxbtn"))); + if (pControl) pControl->SetVisible (true); + pControl = static_cast(m_pm.FindControl (_T ("restorebtn"))); + if (pControl) pControl->SetVisible (false); + } + } +#else + LRESULT lRes = CWindowWnd::HandleMessage (uMsg, wParam, lParam); +#endif + return lRes; + } + + LRESULT WindowImplBase::OnCreate (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + // ʽ + LONG styleValue = ::GetWindowLong (GetHWND (), GWL_STYLE); + styleValue &= ~WS_CAPTION; + ::SetWindowLong (GetHWND (), GWL_STYLE, styleValue | WS_CLIPSIBLINGS | WS_CLIPCHILDREN); + + // UI + m_pm.Init (m_hWnd, GetManagerName ()); + // עPreMessageص + m_pm.AddPreMessageFilter (this); + + // + CControlUI* pRoot = nullptr; + CDialogBuilder builder; + CDuiString sSkinType = GetSkinType (); + std::variant xml; + if (!sSkinType.empty ()) { + std::variant xml = FawTools::parse_dec (GetSkinFile ()); + pRoot = builder.Create (xml, sSkinType, this, &m_pm); + } else { + std::variant xml = string_t (GetSkinFile ()); + pRoot = builder.Create (xml, _T (""), this, &m_pm); + } + + if (!pRoot) { + CDuiString sError = _T ("Դļʧܣ"); + sError += GetSkinFile (); + MessageBox (nullptr, sError.c_str (), _T ("Duilib"), MB_OK | MB_ICONERROR); + ExitProcess (1); + return 0; + } + m_pm.AttachDialog (pRoot); + // Notify¼ӿ + m_pm.AddNotifier (this); + // ʼ󶨿ؼ + //BindCtrlBase::init_binding (&m_pm); + // ڳʼ + InitWindow (); + return 0; + } + + LRESULT WindowImplBase::OnKeyDown (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + + LRESULT WindowImplBase::OnKillFocus (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + + LRESULT WindowImplBase::OnSetFocus (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + + LRESULT WindowImplBase::OnLButtonDown (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + + LRESULT WindowImplBase::OnLButtonUp (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + + LRESULT WindowImplBase::OnRButtonDown (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + + LRESULT WindowImplBase::OnRButtonUp (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + + LRESULT WindowImplBase::OnMouseMove (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + + LRESULT WindowImplBase::HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam) { + LRESULT lRes = 0; + BOOL bHandled = TRUE; + switch (uMsg) { + case WM_CREATE: lRes = OnCreate (uMsg, wParam, lParam, bHandled); break; + case WM_CLOSE: lRes = OnClose (uMsg, wParam, lParam, bHandled); break; + case WM_DESTROY: lRes = OnDestroy (uMsg, wParam, lParam, bHandled); break; +#if defined(WIN32) && !defined(UNDER_CE) + case WM_NCACTIVATE: lRes = OnNcActivate (uMsg, wParam, lParam, bHandled); break; + case WM_NCCALCSIZE: lRes = OnNcCalcSize (uMsg, wParam, lParam, bHandled); break; + case WM_NCPAINT: lRes = OnNcPaint (uMsg, wParam, lParam, bHandled); break; + case WM_NCHITTEST: lRes = OnNcHitTest (uMsg, wParam, lParam, bHandled); break; + case WM_GETMINMAXINFO: lRes = OnGetMinMaxInfo (uMsg, wParam, lParam, bHandled); break; + case WM_MOUSEWHEEL: lRes = OnMouseWheel (uMsg, wParam, lParam, bHandled); break; +#endif + case WM_SIZE: lRes = OnSize (uMsg, wParam, lParam, bHandled); break; + case WM_CHAR: lRes = OnChar (uMsg, wParam, lParam, bHandled); break; + case WM_SYSCOMMAND: lRes = OnSysCommand (uMsg, wParam, lParam, bHandled); break; + case WM_KEYDOWN: lRes = OnKeyDown (uMsg, wParam, lParam, bHandled); break; + case WM_KILLFOCUS: lRes = OnKillFocus (uMsg, wParam, lParam, bHandled); break; + case WM_SETFOCUS: lRes = OnSetFocus (uMsg, wParam, lParam, bHandled); break; + case WM_LBUTTONUP: lRes = OnLButtonUp (uMsg, wParam, lParam, bHandled); break; + case WM_LBUTTONDOWN: lRes = OnLButtonDown (uMsg, wParam, lParam, bHandled); break; + case WM_RBUTTONUP: lRes = OnRButtonUp (uMsg, wParam, lParam, bHandled); break; + case WM_RBUTTONDOWN: lRes = OnRButtonDown (uMsg, wParam, lParam, bHandled); break; + case WM_MOUSEMOVE: lRes = OnMouseMove (uMsg, wParam, lParam, bHandled); break; + case WM_MOUSEHOVER: lRes = OnMouseHover (uMsg, wParam, lParam, bHandled); break; + default: bHandled = FALSE; break; + } + if (bHandled) return lRes; + + lRes = HandleCustomMessage (uMsg, wParam, lParam, bHandled); + if (bHandled) return lRes; + + if (m_pm.MessageHandler (uMsg, wParam, lParam, lRes)) + return lRes; + return CWindowWnd::HandleMessage (uMsg, wParam, lParam); + } + + LRESULT WindowImplBase::HandleCustomMessage (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + bHandled = FALSE; + return 0; + } + + LONG WindowImplBase::GetStyle () { + LONG styleValue = ::GetWindowLong (GetHWND (), GWL_STYLE); + styleValue &= ~WS_CAPTION; + + return styleValue; + } + + void WindowImplBase::OnClick (TNotifyUI& msg) { + CDuiString sCtrlName = msg.pSender->GetName (); + if (sCtrlName == _T ("closebtn")) { + Close (); + return; + } else if (sCtrlName == _T ("minbtn")) { + SendMessage (WM_SYSCOMMAND, SC_MINIMIZE, 0); + return; + } else if (sCtrlName == _T ("maxbtn")) { + SendMessage (WM_SYSCOMMAND, SC_MAXIMIZE, 0); + return; + } else if (sCtrlName == _T ("restorebtn")) { + SendMessage (WM_SYSCOMMAND, SC_RESTORE, 0); + return; + } + return; + } + + void WindowImplBase::Notify (TNotifyUI& msg) { + return CNotifyPump::NotifyPump (msg); + } +} \ No newline at end of file diff --git a/DuiLib/Utils/WinImplBase.h b/DuiLib/Utils/WinImplBase.h new file mode 100644 index 0000000..01ee2b9 --- /dev/null +++ b/DuiLib/Utils/WinImplBase.h @@ -0,0 +1,79 @@ +#include "StdAfx.h" + +#ifndef WIN_IMPL_BASE_HPP +#define WIN_IMPL_BASE_HPP + +namespace DuiLib { + class UILIB_API WindowImplBase + : public CWindowWnd + , public CNotifyPump + , public INotifyUI + , public IMessageFilterUI + , public IDialogBuilderCallback + , public IQueryControlText { + public: + WindowImplBase () {}; + virtual ~WindowImplBase () {}; + // ֻдʼԴԽӿڣ + virtual void InitResource () {}; + // ÿڶд + virtual void InitWindow () {}; + virtual void OnFinalMessage (HWND hWnd); + virtual void Notify (TNotifyUI& msg); + + DUI_DECLARE_MESSAGE_MAP () + virtual void OnClick (TNotifyUI& msg); + virtual void OnHeaderClick (TNotifyUI& msg) {} + virtual void OnSelectChanged (TNotifyUI& msg) {} + virtual void OnTextChanged (TNotifyUI& msg) {} + virtual void OnItemSelect (TNotifyUI& msg) {} + virtual void OnTimer (TNotifyUI& msg) {} + virtual BOOL IsInStaticControl (CControlUI *pControl, POINT &pt); + + protected: + virtual string_view_t GetSkinType () { + return _T (""); + } + virtual string_view_t GetSkinFile () = 0; + virtual string_view_t GetWindowClassName (void) const = 0; + virtual string_view_t GetManagerName () { return _T (""); } + virtual LRESULT ResponseDefaultKeyEvent (WPARAM wParam); + CPaintManagerUI m_pm; + + public: + virtual UINT GetClassStyle () const; + virtual CControlUI* CreateControl (string_view_t pstrClass); + virtual string_view_t QueryControlText (string_view_t lpstrId, string_view_t lpstrType); + + virtual LRESULT MessageHandler (UINT uMsg, WPARAM wParam, LPARAM /*lParam*/, bool& /*bHandled*/); + virtual LRESULT OnClose (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); + virtual LRESULT OnDestroy (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); + +#if defined(WIN32) && !defined(UNDER_CE) + virtual LRESULT OnNcActivate (UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled); + virtual LRESULT OnNcCalcSize (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + virtual LRESULT OnNcPaint (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); + virtual LRESULT OnNcHitTest (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + virtual LRESULT OnGetMinMaxInfo (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + virtual LRESULT OnMouseWheel (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); + virtual LRESULT OnMouseHover (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); +#endif + virtual LRESULT OnSize (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + virtual LRESULT OnChar (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + virtual LRESULT OnSysCommand (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + virtual LRESULT OnCreate (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + virtual LRESULT OnKeyDown (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); + virtual LRESULT OnKillFocus (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); + virtual LRESULT OnSetFocus (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); + virtual LRESULT OnLButtonDown (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); + virtual LRESULT OnLButtonUp (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); + virtual LRESULT OnRButtonDown (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); + virtual LRESULT OnRButtonUp (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); + virtual LRESULT OnMouseMove (UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); + virtual LRESULT HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam); + virtual LRESULT HandleCustomMessage (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + virtual LONG GetStyle (); + }; +} + +#endif // WIN_IMPL_BASE_HPP diff --git a/DuiLib/Utils/downloadmgr.h b/DuiLib/Utils/downloadmgr.h new file mode 100644 index 0000000..0f02654 --- /dev/null +++ b/DuiLib/Utils/downloadmgr.h @@ -0,0 +1,209 @@ + +#pragma warning( disable: 4049 ) /* more than 64k source lines */ + +/* this ALWAYS GENERATED file contains the definitions for the interfaces */ + + + /* File created by MIDL compiler version 5.03.0279 */ +/* at Mon Jul 23 17:42:46 2001 + */ + /* Compiler settings for downloadmgr.idl: + Oicf (OptLev=i2), W1, Zp8, env=Win32 (32b run), ms_ext, c_ext + error checks: allocation ref bounds_check enum stub_data + VC __declspec() decoration level: + __declspec(uuid()), __declspec(selectany), __declspec(novtable) + DECLSPEC_UUID(), MIDL_INTERFACE() + */ + //@@MIDL_FILE_HEADING( ) + + + /* verify that the version is high enough to compile this file*/ +#ifndef __REQUIRED_RPCNDR_H_VERSION__ +#define __REQUIRED_RPCNDR_H_VERSION__ 440 +#endif + +#include "rpc.h" +#include "rpcndr.h" + +#ifndef __RPCNDR_H_VERSION__ +#error this stub requires an updated version of +#endif // __RPCNDR_H_VERSION__ + +#ifndef COM_NO_WINDOWS_H +#include "windows.h" +#include "ole2.h" +#endif /*COM_NO_WINDOWS_H*/ + +#ifndef __downloadmgr_h__ +#define __downloadmgr_h__ + +/* Forward Declarations */ + +#ifndef __IDownloadManager_FWD_DEFINED__ +#define __IDownloadManager_FWD_DEFINED__ +typedef interface IDownloadManager IDownloadManager; +#endif /* __IDownloadManager_FWD_DEFINED__ */ + + +/* header files for imported files */ +#include "unknwn.h" +#include "ocidl.h" + +#ifdef __cplusplus +extern "C" { +#endif + + void __RPC_FAR * __RPC_USER MIDL_user_allocate (size_t); + void __RPC_USER MIDL_user_free (void __RPC_FAR *); + + /* interface __MIDL_itf_downloadmgr_0000 */ + /* [local] */ + + //=--------------------------------------------------------------------------= + // downloadmgr.h + //=--------------------------------------------------------------------------= + // (C) Copyright 2000 Microsoft Corporation. All Rights Reserved. + // + // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF + // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO + // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A + // PARTICULAR PURPOSE. + //=--------------------------------------------------------------------------= + +#pragma comment(lib,"uuid.lib") + +//---------------------------------------------------------------------------= +// Internet Explorer Download Manager Interfaces + +// -------------------------------------------------------------------------------- +// GUIDS +// -------------------------------------------------------------------------------- +// {988934A4-064B-11D3-BB80-00104B35E7F9} + DEFINE_GUID (IID_IDownloadManager, 0x988934a4, 0x064b, 0x11d3, 0xbb, 0x80, 0x0, 0x10, 0x4b, 0x35, 0xe7, 0xf9); +#define SID_SDownloadManager IID_IDownloadManager + + + + extern RPC_IF_HANDLE __MIDL_itf_downloadmgr_0000_v0_0_c_ifspec; + extern RPC_IF_HANDLE __MIDL_itf_downloadmgr_0000_v0_0_s_ifspec; + +#ifndef __IDownloadManager_INTERFACE_DEFINED__ +#define __IDownloadManager_INTERFACE_DEFINED__ + + /* interface IDownloadManager */ + /* [local][unique][uuid][object][helpstring] */ + + + EXTERN_C const IID IID_IDownloadManager; + +#if defined(__cplusplus) && !defined(CINTERFACE) + + MIDL_INTERFACE ("988934A4-064B-11D3-BB80-00104B35E7F9") + IDownloadManager : public IUnknown + { + public: + virtual HRESULT STDMETHODCALLTYPE Download ( + /* [in] */ IMoniker __RPC_FAR *pmk, + /* [in] */ IBindCtx __RPC_FAR *pbc, + /* [in] */ DWORD dwBindVerb, + /* [in] */ LONG grfBINDF, + /* [in] */ BINDINFO __RPC_FAR *pBindInfo, + /* [in] */ LPCOLESTR pszHeaders, + /* [in] */ LPCOLESTR pszRedir, + /* [in] */ UINT uiCP) = 0; + + }; + +#else /* C style interface */ + + typedef struct IDownloadManagerVtbl { + BEGIN_INTERFACE + + HRESULT (STDMETHODCALLTYPE __RPC_FAR *QueryInterface)( + IDownloadManager __RPC_FAR * This, + /* [in] */ REFIID riid, + /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject); + + ULONG (STDMETHODCALLTYPE __RPC_FAR *AddRef)( + IDownloadManager __RPC_FAR * This); + + ULONG (STDMETHODCALLTYPE __RPC_FAR *Release)( + IDownloadManager __RPC_FAR * This); + + HRESULT (STDMETHODCALLTYPE __RPC_FAR *Download)( + IDownloadManager __RPC_FAR * This, + /* [in] */ IMoniker __RPC_FAR *pmk, + /* [in] */ IBindCtx __RPC_FAR *pbc, + /* [in] */ DWORD dwBindVerb, + /* [in] */ LONG grfBINDF, + /* [in] */ BINDINFO __RPC_FAR *pBindInfo, + /* [in] */ LPCOLESTR pszHeaders, + /* [in] */ LPCOLESTR pszRedir, + /* [in] */ UINT uiCP); + + END_INTERFACE + } IDownloadManagerVtbl; + + interface IDownloadManager { + CONST_VTBL struct IDownloadManagerVtbl __RPC_FAR *lpVtbl; + }; + + + +#ifdef COBJMACROS + + +#define IDownloadManager_QueryInterface(This,riid,ppvObject) \ + (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) + +#define IDownloadManager_AddRef(This) \ + (This)->lpVtbl -> AddRef(This) + +#define IDownloadManager_Release(This) \ + (This)->lpVtbl -> Release(This) + + +#define IDownloadManager_Download(This,pmk,pbc,dwBindVerb,grfBINDF,pBindInfo,pszHeaders,pszRedir,uiCP) \ + (This)->lpVtbl -> Download(This,pmk,pbc,dwBindVerb,grfBINDF,pBindInfo,pszHeaders,pszRedir,uiCP) + +#endif /* COBJMACROS */ + + +#endif /* C style interface */ + + + + HRESULT STDMETHODCALLTYPE IDownloadManager_Download_Proxy ( + IDownloadManager __RPC_FAR * This, + /* [in] */ IMoniker __RPC_FAR *pmk, + /* [in] */ IBindCtx __RPC_FAR *pbc, + /* [in] */ DWORD dwBindVerb, + /* [in] */ LONG grfBINDF, + /* [in] */ BINDINFO __RPC_FAR *pBindInfo, + /* [in] */ LPCOLESTR pszHeaders, + /* [in] */ LPCOLESTR pszRedir, + /* [in] */ UINT uiCP); + + + void __RPC_STUB IDownloadManager_Download_Stub ( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + + + +#endif /* __IDownloadManager_INTERFACE_DEFINED__ */ + + + /* Additional Prototypes for ALL interfaces */ + + /* end of Additional Prototypes */ + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/DuiLib/Utils/flash11.tlh b/DuiLib/Utils/flash11.tlh new file mode 100644 index 0000000..4dee038 --- /dev/null +++ b/DuiLib/Utils/flash11.tlh @@ -0,0 +1,384 @@ +// Created by Microsoft (R) C/C++ Compiler Version 10.00.30319.01 (9b1e6a3c). +// +// f:\duilib\duilib\duilib\build\debug\flash11.tlh +// +// C++ source equivalent of Win32 type library C:\Windows\System32\Macromed\Flash\flash11.ocx +// compiler-generated file created 12/07/12 at 22:56:25 - DO NOT EDIT! + +#pragma once +#pragma pack(push, 8) + +#include + +// +// Forward references and typedefs +// + +struct __declspec(uuid("d27cdb6b-ae6d-11cf-96b8-444553540000")) +/* LIBID */ __ShockwaveFlashObjects; +struct __declspec(uuid("d27cdb6c-ae6d-11cf-96b8-444553540000")) +/* dual interface */ IShockwaveFlash; +struct __declspec(uuid("c5598e60-b307-11d1-b27d-006008c3fbfb")) +/* interface */ ICanHandleException; +struct __declspec(uuid("d27cdb6d-ae6d-11cf-96b8-444553540000")) +/* dispinterface */ _IShockwaveFlashEvents; +struct /* coclass */ ShockwaveFlash; +struct __declspec(uuid("d27cdb70-ae6d-11cf-96b8-444553540000")) +/* interface */ IFlashFactory; +struct __declspec(uuid("d27cdb72-ae6d-11cf-96b8-444553540000")) +/* interface */ IFlashObjectInterface; +struct __declspec(uuid("a6ef9860-c720-11d0-9337-00a0c90dcaa9")) +/* interface */ IMyDispatchEx; +struct /* coclass */ FlashObjectInterface; +struct __declspec(uuid("86230738-d762-4c50-a2de-a753e5b1686f")) +/* dual interface */ IFlashObject; +struct /* coclass */ FlashObject; + +// +// Smart pointer typedef declarations +// + +_COM_SMARTPTR_TYPEDEF(IShockwaveFlash, __uuidof(IShockwaveFlash)); +_COM_SMARTPTR_TYPEDEF(ICanHandleException, __uuidof(ICanHandleException)); +_COM_SMARTPTR_TYPEDEF(_IShockwaveFlashEvents, __uuidof(_IShockwaveFlashEvents)); +_COM_SMARTPTR_TYPEDEF(IFlashFactory, __uuidof(IFlashFactory)); +_COM_SMARTPTR_TYPEDEF(IMyDispatchEx, __uuidof(IMyDispatchEx)); +_COM_SMARTPTR_TYPEDEF(IFlashObjectInterface, __uuidof(IFlashObjectInterface)); +_COM_SMARTPTR_TYPEDEF(IFlashObject, __uuidof(IFlashObject)); + +// +// Type library items +// + +struct __declspec(uuid("d27cdb6c-ae6d-11cf-96b8-444553540000")) +IShockwaveFlash : IDispatch +{ + // + // Raw methods provided by interface + // + + virtual HRESULT __stdcall get_ReadyState ( + /*[out,retval]*/ long * pVal ) = 0; + virtual HRESULT __stdcall get_TotalFrames ( + /*[out,retval]*/ long * pVal ) = 0; + virtual HRESULT __stdcall get_Playing ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_Playing ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall get_Quality ( + /*[out,retval]*/ int * pVal ) = 0; + virtual HRESULT __stdcall put_Quality ( + /*[in]*/ int pVal ) = 0; + virtual HRESULT __stdcall get_ScaleMode ( + /*[out,retval]*/ int * pVal ) = 0; + virtual HRESULT __stdcall put_ScaleMode ( + /*[in]*/ int pVal ) = 0; + virtual HRESULT __stdcall get_AlignMode ( + /*[out,retval]*/ int * pVal ) = 0; + virtual HRESULT __stdcall put_AlignMode ( + /*[in]*/ int pVal ) = 0; + virtual HRESULT __stdcall get_BackgroundColor ( + /*[out,retval]*/ long * pVal ) = 0; + virtual HRESULT __stdcall put_BackgroundColor ( + /*[in]*/ long pVal ) = 0; + virtual HRESULT __stdcall get_Loop ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_Loop ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall get_Movie ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_Movie ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_FrameNum ( + /*[out,retval]*/ long * pVal ) = 0; + virtual HRESULT __stdcall put_FrameNum ( + /*[in]*/ long pVal ) = 0; + virtual HRESULT __stdcall SetZoomRect ( + /*[in]*/ long left, + /*[in]*/ long top, + /*[in]*/ long right, + /*[in]*/ long bottom ) = 0; + virtual HRESULT __stdcall Zoom ( + /*[in]*/ int factor ) = 0; + virtual HRESULT __stdcall Pan ( + /*[in]*/ long x, + /*[in]*/ long y, + /*[in]*/ int mode ) = 0; + virtual HRESULT __stdcall Play ( ) = 0; + virtual HRESULT __stdcall Stop ( ) = 0; + virtual HRESULT __stdcall Back ( ) = 0; + virtual HRESULT __stdcall Forward ( ) = 0; + virtual HRESULT __stdcall Rewind ( ) = 0; + virtual HRESULT __stdcall StopPlay ( ) = 0; + virtual HRESULT __stdcall GotoFrame ( + /*[in]*/ long FrameNum ) = 0; + virtual HRESULT __stdcall CurrentFrame ( + /*[out,retval]*/ long * FrameNum ) = 0; + virtual HRESULT __stdcall IsPlaying ( + /*[out,retval]*/ VARIANT_BOOL * Playing ) = 0; + virtual HRESULT __stdcall PercentLoaded ( + /*[out,retval]*/ long * percent ) = 0; + virtual HRESULT __stdcall FrameLoaded ( + /*[in]*/ long FrameNum, + /*[out,retval]*/ VARIANT_BOOL * loaded ) = 0; + virtual HRESULT __stdcall FlashVersion ( + /*[out,retval]*/ long * version ) = 0; + virtual HRESULT __stdcall get_WMode ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_WMode ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_SAlign ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_SAlign ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_Menu ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_Menu ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall get_Base ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_Base ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_Scale ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_Scale ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_DeviceFont ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_DeviceFont ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall get_EmbedMovie ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_EmbedMovie ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall get_BGColor ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_BGColor ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_Quality2 ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_Quality2 ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall LoadMovie ( + /*[in]*/ int layer, + /*[in]*/ BSTR url ) = 0; + virtual HRESULT __stdcall TGotoFrame ( + /*[in]*/ BSTR target, + /*[in]*/ long FrameNum ) = 0; + virtual HRESULT __stdcall TGotoLabel ( + /*[in]*/ BSTR target, + /*[in]*/ BSTR label ) = 0; + virtual HRESULT __stdcall TCurrentFrame ( + /*[in]*/ BSTR target, + /*[out,retval]*/ long * FrameNum ) = 0; + virtual HRESULT __stdcall TCurrentLabel ( + /*[in]*/ BSTR target, + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall TPlay ( + /*[in]*/ BSTR target ) = 0; + virtual HRESULT __stdcall TStopPlay ( + /*[in]*/ BSTR target ) = 0; + virtual HRESULT __stdcall SetVariable ( + /*[in]*/ BSTR name, + /*[in]*/ BSTR value ) = 0; + virtual HRESULT __stdcall GetVariable ( + /*[in]*/ BSTR name, + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall TSetProperty ( + /*[in]*/ BSTR target, + /*[in]*/ int property, + /*[in]*/ BSTR value ) = 0; + virtual HRESULT __stdcall TGetProperty ( + /*[in]*/ BSTR target, + /*[in]*/ int property, + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall TCallFrame ( + /*[in]*/ BSTR target, + /*[in]*/ int FrameNum ) = 0; + virtual HRESULT __stdcall TCallLabel ( + /*[in]*/ BSTR target, + /*[in]*/ BSTR label ) = 0; + virtual HRESULT __stdcall TSetPropertyNum ( + /*[in]*/ BSTR target, + /*[in]*/ int property, + /*[in]*/ double value ) = 0; + virtual HRESULT __stdcall TGetPropertyNum ( + /*[in]*/ BSTR target, + /*[in]*/ int property, + /*[out,retval]*/ double * pVal ) = 0; + virtual HRESULT __stdcall TGetPropertyAsNumber ( + /*[in]*/ BSTR target, + /*[in]*/ int property, + /*[out,retval]*/ double * pVal ) = 0; + virtual HRESULT __stdcall get_SWRemote ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_SWRemote ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_FlashVars ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_FlashVars ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_AllowScriptAccess ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_AllowScriptAccess ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_MovieData ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_MovieData ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_InlineData ( + /*[out,retval]*/ IUnknown * * ppIUnknown ) = 0; + virtual HRESULT __stdcall put_InlineData ( + /*[in]*/ IUnknown * ppIUnknown ) = 0; + virtual HRESULT __stdcall get_SeamlessTabbing ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_SeamlessTabbing ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall EnforceLocalSecurity ( ) = 0; + virtual HRESULT __stdcall get_Profile ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_Profile ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall get_ProfileAddress ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_ProfileAddress ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_ProfilePort ( + /*[out,retval]*/ long * pVal ) = 0; + virtual HRESULT __stdcall put_ProfilePort ( + /*[in]*/ long pVal ) = 0; + virtual HRESULT __stdcall CallFunction ( + /*[in]*/ BSTR request, + /*[out,retval]*/ BSTR * response ) = 0; + virtual HRESULT __stdcall SetReturnValue ( + /*[in]*/ BSTR returnValue ) = 0; + virtual HRESULT __stdcall DisableLocalSecurity ( ) = 0; + virtual HRESULT __stdcall get_AllowNetworking ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_AllowNetworking ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_AllowFullScreen ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_AllowFullScreen ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_AllowFullScreenInteractive ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_AllowFullScreenInteractive ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_IsDependent ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_IsDependent ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; +}; + +struct __declspec(uuid("c5598e60-b307-11d1-b27d-006008c3fbfb")) +IMyCanHandleException : IUnknown +{ + // + // Raw methods provided by interface + // + + virtual HRESULT __stdcall CanHandleException ( + /*[in]*/ EXCEPINFO * pExcepInfo, + /*[in]*/ VARIANT * pvar ) = 0; +}; + +struct __declspec(uuid("d27cdb6d-ae6d-11cf-96b8-444553540000")) +_IShockwaveFlashEvents : IDispatch +{}; + +struct __declspec(uuid("d27cdb6e-ae6d-11cf-96b8-444553540000")) +ShockwaveFlash; + // [ default ] interface IShockwaveFlash + // [ default, source ] dispinterface _IShockwaveFlashEvents + +struct __declspec(uuid("d27cdb70-ae6d-11cf-96b8-444553540000")) +IFlashFactory : IUnknown +{}; + +struct __declspec(uuid("a6ef9860-c720-11d0-9337-00a0c90dcaa9")) +IMyDispatchEx : IDispatch +{ + // + // Raw methods provided by interface + // + + virtual HRESULT __stdcall GetDispID ( + /*[in]*/ BSTR bstrName, + /*[in]*/ unsigned long grfdex, + /*[out]*/ long * pid ) = 0; + virtual HRESULT __stdcall RemoteInvokeEx ( + /*[in]*/ long id, + /*[in]*/ unsigned long lcid, + /*[in]*/ unsigned long dwFlags, + /*[in]*/ DISPPARAMS * pdp, + /*[out]*/ VARIANT * pvarRes, + /*[out]*/ EXCEPINFO * pei, + /*[in]*/ struct IServiceProvider * pspCaller, + /*[in]*/ unsigned int cvarRefArg, + /*[in]*/ unsigned int * rgiRefArg, + /*[in,out]*/ VARIANT * rgvarRefArg ) = 0; + virtual HRESULT __stdcall DeleteMemberByName ( + /*[in]*/ BSTR bstrName, + /*[in]*/ unsigned long grfdex ) = 0; + virtual HRESULT __stdcall DeleteMemberByDispID ( + /*[in]*/ long id ) = 0; + virtual HRESULT __stdcall GetMemberProperties ( + /*[in]*/ long id, + /*[in]*/ unsigned long grfdexFetch, + /*[out]*/ unsigned long * pgrfdex ) = 0; + virtual HRESULT __stdcall GetMemberName ( + /*[in]*/ long id, + /*[out]*/ BSTR * pbstrName ) = 0; + virtual HRESULT __stdcall GetNextDispID ( + /*[in]*/ unsigned long grfdex, + /*[in]*/ long id, + /*[out]*/ long * pid ) = 0; + virtual HRESULT __stdcall GetNameSpaceParent ( + /*[out]*/ IUnknown * * ppunk ) = 0; +}; + +struct __declspec(uuid("d27cdb72-ae6d-11cf-96b8-444553540000")) +IFlashObjectInterface : IMyDispatchEx +{}; + +struct __declspec(uuid("d27cdb71-ae6d-11cf-96b8-444553540000")) +FlashObjectInterface; + // [ default ] interface IFlashObjectInterface + +struct __declspec(uuid("86230738-d762-4c50-a2de-a753e5b1686f")) +IFlashObject : IMyDispatchEx +{}; + +struct __declspec(uuid("e0920e11-6b65-4d5d-9c58-b1fc5c07dc43")) +FlashObject; + // [ default ] interface IFlashObject + +// +// Named GUID constants initializations +// + +extern "C" const GUID __declspec(selectany) LIBID_ShockwaveFlashObjects = + {0xd27cdb6b,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) IID_IShockwaveFlash = + {0xd27cdb6c,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) IID_IMyCanHandleException = + {0xc5598e60,0xb307,0x11d1,{0xb2,0x7d,0x00,0x60,0x08,0xc3,0xfb,0xfb}}; +extern "C" const GUID __declspec(selectany) DIID__IShockwaveFlashEvents = + {0xd27cdb6d,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) CLSID_ShockwaveFlash = + {0xd27cdb6e,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) IID_IFlashFactory = + {0xd27cdb70,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) IID_IMyDispatchEx = + {0xa6ef9860,0xc720,0x11d0,{0x93,0x37,0x00,0xa0,0xc9,0x0d,0xca,0xa9}}; +extern "C" const GUID __declspec(selectany) IID_IFlashObjectInterface = + {0xd27cdb72,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) CLSID_FlashObjectInterface = + {0xd27cdb71,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) IID_IFlashObject = + {0x86230738,0xd762,0x4c50,{0xa2,0xde,0xa7,0x53,0xe5,0xb1,0x68,0x6f}}; +extern "C" const GUID __declspec(selectany) CLSID_FlashObject = + {0xe0920e11,0x6b65,0x4d5d,{0x9c,0x58,0xb1,0xfc,0x5c,0x07,0xdc,0x43}}; + +#pragma pack(pop) diff --git a/DuiLib/Utils/observer_impl_base.h b/DuiLib/Utils/observer_impl_base.h new file mode 100644 index 0000000..de0b47a --- /dev/null +++ b/DuiLib/Utils/observer_impl_base.h @@ -0,0 +1,116 @@ +#ifndef OBSERVER_IMPL_BASE_HPP +#define OBSERVER_IMPL_BASE_HPP + +#include +#include + +template +class ReceiverImplBase; + +template +class ObserverImplBase { +public: + virtual void AddReceiver (ReceiverImplBase* receiver) = 0; + virtual void RemoveReceiver (ReceiverImplBase* receiver) = 0; + virtual ReturnT Broadcast (ParamT param) = 0; + virtual ReturnT Notify (ParamT param) = 0; +}; + +template +class ReceiverImplBase { +public: + virtual void AddObserver (ObserverImplBase* observer) = 0; + virtual void RemoveObserver () = 0; + virtual ReturnT Receive (ParamT param) = 0; + virtual ReturnT Respond (ParamT param, ObserverImplBase* observer) = 0; +}; + +template +class ReceiverImpl; + +template +class ObserverImpl: public ObserverImplBase { +public: + ObserverImpl () + : count_ (0) {} + + virtual ~ObserverImpl () {} + + virtual void AddReceiver (ReceiverImplBase* receiver) { + if (!receiver) + return; + + receivers_[count_] = receiver; + receiver->AddObserver (this); + count_++; + } + + virtual void RemoveReceiver (ReceiverImplBase* receiver) { + if (!receiver) + return; + + for (auto it = receivers_.begin (); it != receivers_.end (); ++it) { + if (it->second == receiver) { + receivers_.erase (it); + break; + } + } + } + + virtual ReturnT Broadcast (ParamT param) { + for (auto it = receivers_.begin (); it != receivers_.end (); ++it) { + it->second->Receive (param); + } + + return ReturnT (); + } + + virtual ReturnT Notify (ParamT param) { + for (auto it = receivers_.begin (); it != receivers_.end (); ++it) { + it->second->Respond (param, this); + } + + return ReturnT (); + } + +protected: + typedef std::map*> ReceiversMap; + ReceiversMap receivers_; + int count_; +}; + + +template +class ReceiverImpl: public ReceiverImplBase { +public: + ReceiverImpl () + : count_ (0) {} + + virtual ~ReceiverImpl () {} + + virtual void AddObserver (ObserverImplBase* observer) { + observers_[count_] = observer; + count_++; + } + + virtual void RemoveObserver () { + for (auto it = observers_.begin (); it != observers_.end (); ++it) { + it->second->RemoveReceiver (this); + } + } + + virtual ReturnT Receive (ParamT param) { + return ReturnT (); + } + + virtual ReturnT Respond (ParamT param, ObserverImplBase* observer) { + return ReturnT (); + } + +protected: + typedef std::map*> ObserversMap; + ObserversMap observers_; + int count_; +}; + +#endif // OBSERVER_IMPL_BASE_HPP \ No newline at end of file diff --git a/DuiLib/Utils/stb_image.h b/DuiLib/Utils/stb_image.h new file mode 100644 index 0000000..4ec52be --- /dev/null +++ b/DuiLib/Utils/stb_image.h @@ -0,0 +1,6360 @@ +/* stb_image - v2.08 - public domain image loader - http://nothings.org/stb_image.h + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8-bit-per-channel (16 bpc not supported) + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + + Revision 2.00 release notes: + + - Progressive JPEG is now supported. + + - PPM and PGM binary formats are now supported, thanks to Ken Miller. + + - x86 platforms now make use of SSE2 SIMD instructions for + JPEG decoding, and ARM platforms can use NEON SIMD if requested. + This work was done by Fabian "ryg" Giesen. SSE2 is used by + default, but NEON must be enabled explicitly; see docs. + + With other JPEG optimizations included in this version, we see + 2x speedup on a JPEG on an x86 machine, and a 1.5x speedup + on a JPEG on an ARM machine, relative to previous versions of this + library. The same results will not obtain for all JPGs and for all + x86/ARM machines. (Note that progressive JPEGs are significantly + slower to decode than regular JPEGs.) This doesn't mean that this + is the fastest JPEG decoder in the land; rather, it brings it + closer to parity with standard libraries. If you want the fastest + decode, look elsewhere. (See "Philosophy" section of docs below.) + + See final bullet items below for more info on SIMD. + + - Added STBI_MALLOC, STBI_REALLOC, and STBI_FREE macros for replacing + the memory allocator. Unlike other STBI libraries, these macros don't + support a context parameter, so if you need to pass a context in to + the allocator, you'll have to store it in a global or a thread-local + variable. + + - Split existing STBI_NO_HDR flag into two flags, STBI_NO_HDR and + STBI_NO_LINEAR. + STBI_NO_HDR: suppress implementation of .hdr reader format + STBI_NO_LINEAR: suppress high-dynamic-range light-linear float API + + - You can suppress implementation of any of the decoders to reduce + your code footprint by #defining one or more of the following + symbols before creating the implementation. + + STBI_NO_JPEG + STBI_NO_PNG + STBI_NO_BMP + STBI_NO_PSD + STBI_NO_TGA + STBI_NO_GIF + STBI_NO_HDR + STBI_NO_PIC + STBI_NO_PNM (.ppm and .pgm) + + - You can request *only* certain decoders and suppress all other ones + (this will be more forward-compatible, as addition of new decoders + doesn't require you to disable them explicitly): + + STBI_ONLY_JPEG + STBI_ONLY_PNG + STBI_ONLY_BMP + STBI_ONLY_PSD + STBI_ONLY_TGA + STBI_ONLY_GIF + STBI_ONLY_HDR + STBI_ONLY_PIC + STBI_ONLY_PNM (.ppm and .pgm) + + Note that you can define multiples of these, and you will get all + of them ("only x" and "only y" is interpreted to mean "only x&y"). + + - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still + want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB + + - Compilation of all SIMD code can be suppressed with + #define STBI_NO_SIMD + It should not be necessary to disable SIMD unless you have issues + compiling (e.g. using an x86 compiler which doesn't support SSE + intrinsics or that doesn't support the method used to detect + SSE2 support at run-time), and even those can be reported as + bugs so I can refine the built-in compile-time checking to be + smarter. + + - The old STBI_SIMD system which allowed installing a user-defined + IDCT etc. has been removed. If you need this, don't upgrade. My + assumption is that almost nobody was doing this, and those who + were will find the built-in SIMD more satisfactory anyway. + + - RGB values computed for JPEG images are slightly different from + previous versions of stb_image. (This is due to using less + integer precision in SIMD.) The C code has been adjusted so + that the same RGB values will be computed regardless of whether + SIMD support is available, so your app should always produce + consistent results. But these results are slightly different from + previous versions. (Specifically, about 3% of available YCbCr values + will compute different RGB results from pre-1.49 versions by +-1; + most of the deviating values are one smaller in the G channel.) + + - If you must produce consistent results with previous versions of + stb_image, #define STBI_JPEG_OLD and you will get the same results + you used to; however, you will not get the SIMD speedups for + the YCbCr-to-RGB conversion step (although you should still see + significant JPEG speedup from the other changes). + + Please note that STBI_JPEG_OLD is a temporary feature; it will be + removed in future versions of the library. It is only intended for + near-term back-compatibility use. + + + Latest revision history: + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) partial animated GIF support + limited 16-bit PSD support + minor bugs, code cleanup, and compiler warnings + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) additional corruption checking + stbi_set_flip_vertically_on_load + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPEG, including x86 SSE2 & ARM NEON SIMD + progressive JPEG + PGM/PPM support + STBI_MALLOC,STBI_REALLOC,STBI_FREE + STBI_NO_*, STBI_ONLY_* + GIF bugfix + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support (both grayscale and paletted) + optimize PNG + fix bug in interlaced PNG with user-specified channel count + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Bug fixes & warning fixes + Sean Barrett (jpeg, png, bmp) Marc LeBlanc + Nicolas Schulz (hdr, psd) Christpher Lloyd + Jonathan Dummer (tga) Dave Moore + Jean-Marc Lienher (gif) Won Chun + Tom Seddon (pic) the Horde3D community + Thatcher Ulrich (psd) Janez Zemva + Ken Miller (pgm, ppm) Jonathan Blow + urraka@github (animated gif) Laurent Gomila + Aruelien Pocheville + Ryamond Barbiero + David Woo + Extensions, features Martin Golini + Jetro Lauha (stbi_info) Roy Eltham + Martin "SpartanJ" Golini (stbi_info) Luke Graham + James "moose2000" Brown (iPhone PNG) Thomas Ruf + Ben "Disch" Wenger (io callbacks) John Bartholomew + Omar Cornut (1/2/4-bit PNG) Ken Hamada + Nicolas Guillemot (vertical flip) Cort Stratton + Richard Mitton (16-bit PSD) Blazej Dariusz Roszkowski + Thibault Reuille + Paul Du Bois + Guillaume George + Jerry Jansson + Hayaki Saito + Johan Duparc + Ronny Chevalier + Optimizations & bugfixes Michal Cichon + Fabian "ryg" Giesen Tero Hanninen + Arseny Kapoulkine Sergio Gonzalez + Cass Everitt + Engin Manap + If your name should be here but Martins Mozeiko + isn't, let Sean know. Joseph Thomson + Phil Jordan + Nathan Reed + Michaelangel007@github + Nick Verigakis + +LICENSE + +This software is in the public domain. Where that dedication is not +recognized, you are granted a perpetual, irrevocable license to copy, +distribute, and modify this file as you see fit. + +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 16-bit-per-channel PNG +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - no 1-bit BMP +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not nullptr ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *comp -- outputs # of image components in image file +// int req_comp -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or nullptr on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to see if it's trivially opaque +// because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be nullptr, +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy to use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries do not emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// make more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// The output of the JPEG decoder is slightly different from versions where +// SIMD support was introduced (that is, for versions before 1.49). The +// difference is only +-1 in the 8-bit RGB channels, and only on a small +// fraction of pixels. You can force the pre-1.49 behavior by defining +// STBI_JPEG_OLD, but this will disable some of the SIMD decoding path +// and hence cost some performance. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// + + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum { + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif + + ////////////////////////////////////////////////////////////////////////////// + // + // PRIMARY API - works on images of any type + // + + // + // load image by filename, open file, or memory buffer + // + + typedef struct { + int (*read) (void *user, char *data, int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user, int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data + } stbi_io_callbacks; + + STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); + STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + STBIDEF stbi_uc *stbi_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO + STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + // for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma (float gamma); + STBIDEF void stbi_hdr_to_ldr_scale (float scale); +#endif + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma (float gamma); + STBIDEF void stbi_ldr_to_hdr_scale (float scale); +#endif // STBI_NO_HDR + + // stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR + STBIDEF int stbi_is_hdr_from_callbacks (stbi_io_callbacks const *clbk, void *user); + STBIDEF int stbi_is_hdr_from_memory (stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO + STBIDEF int stbi_is_hdr (char const *filename); + STBIDEF int stbi_is_hdr_from_file (FILE *f); +#endif // STBI_NO_STDIO + + + // get a VERY brief reason for failure + // NOT THREADSAFE + STBIDEF const char *stbi_failure_reason (void); + + // free the loaded image -- this is just free() + STBIDEF void stbi_image_free (void *retval_from_stbi_load); + + // get image dimensions & components without fully decoding + STBIDEF int stbi_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp); + STBIDEF int stbi_info_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO + STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); + STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + +#endif + + + + // for image formats that explicitly notate that they have premultiplied alpha, + // we just return the colors as stored in the file. set this flag to force + // unpremultiplication. results are undefined if the unpremultiply overflow. + STBIDEF void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply); + + // indicate whether we should process iphone images back to canonical format, + // or just pass them through "as-is" + STBIDEF void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert); + + // flip the image vertically, so the first pixel in the output array is the bottom left + STBIDEF void stbi_set_flip_vertically_on_load (int flag_true_if_should_flip); + + // ZLIB client - used by PNG, available for other purposes + + STBIDEF char *stbi_zlib_decode_malloc_guesssize (const char *buffer, int len, int initial_size, int *outlen); + STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag (const char *buffer, int len, int initial_size, int *outlen, int parse_header); + STBIDEF char *stbi_zlib_decode_malloc (const char *buffer, int len, int *outlen); + STBIDEF int stbi_zlib_decode_buffer (char *obuffer, int olen, const char *ibuffer, int ilen); + + STBIDEF char *stbi_zlib_decode_noheader_malloc (const char *buffer, int len, int *outlen); + STBIDEF int stbi_zlib_decode_noheader_buffer (char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) +#ifndef STBI_ONLY_JPEG +#define STBI_NO_JPEG +#endif +#ifndef STBI_ONLY_PNG +#define STBI_NO_PNG +#endif +#ifndef STBI_ONLY_BMP +#define STBI_NO_BMP +#endif +#ifndef STBI_ONLY_PSD +#define STBI_NO_PSD +#endif +#ifndef STBI_ONLY_TGA +#define STBI_NO_TGA +#endif +#ifndef STBI_ONLY_GIF +#define STBI_NO_GIF +#endif +#ifndef STBI_ONLY_HDR +#define STBI_NO_HDR +#endif +#ifndef STBI_ONLY_PIC +#define STBI_NO_PIC +#endif +#ifndef STBI_ONLY_PNM +#define STBI_NO_PNM +#endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + + +#ifndef _MSC_VER +#ifdef __cplusplus +#define stbi_inline inline +#else +#define stbi_inline +#endif +#else +#define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof (stbi__uint32) == 4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL +#define stbi_lrot(x,y) _lrotl(x,y) +#else +#define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && defined(STBI_REALLOC) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,sz) realloc(p,sz) +#define STBI_FREE(p) free(p) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// NOTE: not clear do we actually need this for the 64-bit path? +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; +// this is just broken and gcc are jerks for not fixing it properly +// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && defined(STBI__X86_TARGET) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3 (void) { + int info[4]; + __cpuid (info, 1); + return info[3]; +} +#else +static int stbi__cpuid3 (void) { + int res; + __asm { + mov eax, 1 + cpuid + mov res, edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +static int stbi__sse2_available () { + int info3 = stbi__cpuid3 (); + return ((info3 >> 26) & 1) != 0; +} +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +static int stbi__sse2_available () { +#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later + // GCC 4.8+ has a nice way to do this + return __builtin_cpu_supports ("sse2"); +#else + // portable way to do this, preferably without using GCC inline ASM? + // just bail for now. + return 0; +#endif +} +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct { + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer (stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem (stbi__context *s, stbi_uc const *buffer, int len) { + s->io.read = nullptr; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer + len; +} + +// initialize a callback-based context +static void stbi__start_callbacks (stbi__context *s, stbi_io_callbacks *c, void *user) { + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof (s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer (s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read (void *user, char *data, int size) { + return (int) fread (data, 1, size, (FILE*) user); +} + +static void stbi__stdio_skip (void *user, int n) { + fseek ((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof (void *user) { + return feof ((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file (stbi__context *s, FILE *f) { + stbi__start_callbacks (s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind (stbi__context *s) { + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test (stbi__context *s); +static stbi_uc *stbi__jpeg_load (stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__jpeg_info (stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test (stbi__context *s); +static stbi_uc *stbi__png_load (stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__png_info (stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test (stbi__context *s); +static stbi_uc *stbi__bmp_load (stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__bmp_info (stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test (stbi__context *s); +static stbi_uc *stbi__tga_load (stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__tga_info (stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test (stbi__context *s); +static stbi_uc *stbi__psd_load (stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__psd_info (stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test (stbi__context *s); +static float *stbi__hdr_load (stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__hdr_info (stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test (stbi__context *s); +static stbi_uc *stbi__pic_load (stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pic_info (stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test (stbi__context *s); +static stbi_uc *stbi__gif_load (stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__gif_info (stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test (stbi__context *s); +static stbi_uc *stbi__pnm_load (stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pnm_info (stbi__context *s, int *x, int *y, int *comp); +#endif + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason (void) { + return stbi__g_failure_reason; +} + +static int stbi__err (const char *str) { + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc (size_t size) { + return STBI_MALLOC (size); +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS +#define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) +#define stbi__err(x,y) stbi__err(y) +#else +#define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?nullptr:nullptr)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?nullptr:nullptr)) + +STBIDEF void stbi_image_free (void *retval_from_stbi_load) { + STBI_FREE (retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr (stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr (float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load (int flag_true_if_should_flip) { + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static unsigned char *stbi__load_main (stbi__context *s, int *x, int *y, int *comp, int req_comp) { +#ifndef STBI_NO_JPEG + if (stbi__jpeg_test (s)) return stbi__jpeg_load (s, x, y, comp, req_comp); +#endif +#ifndef STBI_NO_PNG + if (stbi__png_test (s)) return stbi__png_load (s, x, y, comp, req_comp); +#endif +#ifndef STBI_NO_BMP + if (stbi__bmp_test (s)) return stbi__bmp_load (s, x, y, comp, req_comp); +#endif +#ifndef STBI_NO_GIF + if (stbi__gif_test (s)) return stbi__gif_load (s, x, y, comp, req_comp); +#endif +#ifndef STBI_NO_PSD + if (stbi__psd_test (s)) return stbi__psd_load (s, x, y, comp, req_comp); +#endif +#ifndef STBI_NO_PIC + if (stbi__pic_test (s)) return stbi__pic_load (s, x, y, comp, req_comp); +#endif +#ifndef STBI_NO_PNM + if (stbi__pnm_test (s)) return stbi__pnm_load (s, x, y, comp, req_comp); +#endif + +#ifndef STBI_NO_HDR + if (stbi__hdr_test (s)) { + float *hdr = stbi__hdr_load (s, x, y, comp, req_comp); + return stbi__hdr_to_ldr (hdr, *x, *y, req_comp ? req_comp : *comp); + } +#endif + +#ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test (s)) + return stbi__tga_load (s, x, y, comp, req_comp); +#endif + + return stbi__errpuc ("unknown image type", "Image not of any known type, or corrupt"); +} + +static unsigned char *stbi__load_flip (stbi__context *s, int *x, int *y, int *comp, int req_comp) { + unsigned char *result = stbi__load_main (s, x, y, comp, req_comp); + + if (stbi__vertically_flip_on_load && result) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row, col, z; + stbi_uc temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h >> 1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } + + return result; +} + +#ifndef STBI_NO_HDR +static void stbi__float_postprocess (float *result, int *x, int *y, int *comp, int req_comp) { + if (stbi__vertically_flip_on_load && result) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row, col, z; + float temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h >> 1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } +} +#endif + +#ifndef STBI_NO_STDIO + +static FILE *stbi__fopen (char const *filename, char const *mode) { + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s (&f, filename, mode)) + f = 0; +#else + f = fopen (filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp) { + FILE *f = stbi__fopen (filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc ("can't fopen", "Unable to open file"); + result = stbi_load_from_file (f, x, y, comp, req_comp); + fclose (f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) { + unsigned char *result; + stbi__context s; + stbi__start_file (&s, f); + result = stbi__load_flip (&s, x, y, comp, req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek (f, -(int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} +#endif //!STBI_NO_STDIO + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) { + stbi__context s; + stbi__start_mem (&s, buffer, len); + return stbi__load_flip (&s, x, y, comp, req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { + stbi__context s; + stbi__start_callbacks (&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_flip (&s, x, y, comp, req_comp); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main (stbi__context *s, int *x, int *y, int *comp, int req_comp) { + unsigned char *data; +#ifndef STBI_NO_HDR + if (stbi__hdr_test (s)) { + float *hdr_data = stbi__hdr_load (s, x, y, comp, req_comp); + if (hdr_data) + stbi__float_postprocess (hdr_data, x, y, comp, req_comp); + return hdr_data; + } +#endif + data = stbi__load_flip (s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr (data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf ("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) { + stbi__context s; + stbi__start_mem (&s, buffer, len); + return stbi__loadf_main (&s, x, y, comp, req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { + stbi__context s; + stbi__start_callbacks (&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main (&s, x, y, comp, req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp) { + float *result; + FILE *f = stbi__fopen (filename, "rb"); + if (!f) return stbi__errpf ("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file (f, x, y, comp, req_comp); + fclose (f); + return result; +} + +STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) { + stbi__context s; + stbi__start_file (&s, f); + return stbi__loadf_main (&s, x, y, comp, req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory (stbi_uc const *buffer, int len) { +#ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem (&s, buffer, len); + return stbi__hdr_test (&s); +#else + STBI_NOTUSED (buffer); + STBI_NOTUSED (len); + return 0; +#endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) { + FILE *f = stbi__fopen (filename, "rb"); + int result = 0; + if (f) { + result = stbi_is_hdr_from_file (f); + fclose (f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file (FILE *f) { +#ifndef STBI_NO_HDR + stbi__context s; + stbi__start_file (&s, f); + return stbi__hdr_test (&s); +#else + STBI_NOTUSED (f); + return 0; +#endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks (stbi_io_callbacks const *clbk, void *user) { +#ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks (&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test (&s); +#else + STBI_NOTUSED (clbk); + STBI_NOTUSED (user); + return 0; +#endif +} + +static float stbi__h2l_gamma_i = 1.0f / 2.2f, stbi__h2l_scale_i = 1.0f; +static float stbi__l2h_gamma = 2.2f, stbi__l2h_scale = 1.0f; + +#ifndef STBI_NO_LINEAR +STBIDEF void stbi_ldr_to_hdr_gamma (float gamma) { + stbi__l2h_gamma = gamma; +} +STBIDEF void stbi_ldr_to_hdr_scale (float scale) { + stbi__l2h_scale = scale; +} +#endif + +STBIDEF void stbi_hdr_to_ldr_gamma (float gamma) { + stbi__h2l_gamma_i = 1 / gamma; +} +STBIDEF void stbi_hdr_to_ldr_scale (float scale) { + stbi__h2l_scale_i = 1 / scale; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum { + STBI__SCAN_load = 0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer (stbi__context *s) { + int n = (s->io.read)(s->io_user_data, (char*) s->buffer_start, s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + 1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8 (stbi__context *s) { + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer (s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof (stbi__context *s) { + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip (stbi__context *s, int n) { + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn (stbi__context *s, stbi_uc *buffer, int n) { + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy (buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n - blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer + n <= s->img_buffer_end) { + memcpy (buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be (stbi__context *s) { + int z = stbi__get8 (s); + return (z << 8) + stbi__get8 (s); +} + +static stbi__uint32 stbi__get32be (stbi__context *s) { + stbi__uint32 z = stbi__get16be (s); + return (z << 16) + stbi__get16be (s); +} + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le (stbi__context *s) { + int z = stbi__get8 (s); + return z + (stbi__get8 (s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le (stbi__context *s) { + stbi__uint32 z = stbi__get16le (s); + return z + (stbi__get16le (s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y (int r, int g, int b) { + return (stbi_uc) (((r * 77) + (g * 150) + (29 * b)) >> 8); +} + +static unsigned char *stbi__convert_format (unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) { + int i, j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT (req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc (req_comp * x * y); + if (!good) { + STBI_FREE (data); + return stbi__errpuc ("outofmem", "Out of memory"); + } + + for (j = 0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n; + unsigned char *dest = good + j * x * req_comp; + +#define COMBO(a,b) ((a)*8+(b)) +#define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (COMBO (img_n, req_comp)) { + CASE (1, 2) dest[0] = src[0], dest[1] = 255; break; + CASE (1, 3) dest[0] = dest[1] = dest[2] = src[0]; break; + CASE (1, 4) dest[0] = dest[1] = dest[2] = src[0], dest[3] = 255; break; + CASE (2, 1) dest[0] = src[0]; break; + CASE (2, 3) dest[0] = dest[1] = dest[2] = src[0]; break; + CASE (2, 4) dest[0] = dest[1] = dest[2] = src[0], dest[3] = src[1]; break; + CASE (3, 4) dest[0] = src[0], dest[1] = src[1], dest[2] = src[2], dest[3] = 255; break; + CASE (3, 1) dest[0] = stbi__compute_y (src[0], src[1], src[2]); break; + CASE (3, 2) dest[0] = stbi__compute_y (src[0], src[1], src[2]), dest[1] = 255; break; + CASE (4, 1) dest[0] = stbi__compute_y (src[0], src[1], src[2]); break; + CASE (4, 2) dest[0] = stbi__compute_y (src[0], src[1], src[2]), dest[1] = src[3]; break; + CASE (4, 3) dest[0] = src[0], dest[1] = src[1], dest[2] = src[2]; break; + default: STBI_ASSERT (0); + } +#undef CASE + } + + STBI_FREE (data); + return good; +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr (stbi_uc *data, int x, int y, int comp) { + int i, k, n; + float *output = (float *) stbi__malloc (x * y * comp * sizeof (float)); + if (!output) { + STBI_FREE (data); return stbi__errpf ("outofmem", "Out of memory"); + } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp - 1; + for (i = 0; i < x*y; ++i) { + for (k = 0; k < n; ++k) { + output[i*comp + k] = (float) (pow (data[i*comp + k] / 255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + if (k < comp) output[i*comp + k] = data[i*comp + k] / 255.0f; + } + STBI_FREE (data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr (float *data, int x, int y, int comp) { + int i, k, n; + stbi_uc *output = (stbi_uc *) stbi__malloc (x * y * comp); + if (!output) { + STBI_FREE (data); return stbi__errpuc ("outofmem", "Out of memory"); + } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp - 1; + for (i = 0; i < x*y; ++i) { + for (k = 0; k < n; ++k) { + float z = (float) pow (data[i*comp + k] * stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int (z); + } + if (k < comp) { + float z = data[i*comp + k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int (z); + } + } + STBI_FREE (data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct { + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct { + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi_uc dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + + // sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + + // definition of jpeg image component + struct { + int id; + int h, v; + int tq; + int hd, ha; + int dc_pred; + + int x, y, w2, h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + + int scan_n, order[4]; + int restart_interval, todo; + + // kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman (stbi__huffman *h, int *count) { + int i, j, k = 0, code; + // build size list for each symbol (from JPEG spec) + for (i = 0; i < 16; ++i) + for (j = 0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i + 1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for (j = 1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code - 1 >= (1 << j)) return stbi__err ("bad code lengths", "Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16 - j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset (h->fast, 255, 1 << FAST_BITS); + for (i = 0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS - s); + int m = 1 << (FAST_BITS - s); + for (j = 0; j < m; ++j) { + h->fast[c + j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac (stbi__int16 *fast_ac, stbi__huffman *h) { + int i; + for (i = 0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (-1 << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe (stbi__jpeg *j) { + do { + int b = j->nomore ? 0 : stbi__get8 (j->s); + if (b == 0xff) { + int c = stbi__get8 (j->s); + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static stbi__uint32 stbi__bmask[17] = { 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535 }; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode (stbi__jpeg *j, stbi__huffman *h) { + unsigned int temp; + int c, k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe (j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS) - 1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k = FAST_BITS + 1; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT ((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe (j); + + sgn = (stbi__int32) j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot (j->code_buffer, n); + STBI_ASSERT (n >= 0 && n < (int) (sizeof (stbi__bmask) / sizeof (*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits (stbi__jpeg *j, int n) { + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe (j); + k = stbi_lrot (j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit (stbi__jpeg *j) { + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe (j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static stbi_uc stbi__jpeg_dezigzag[64 + 15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block (stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi_uc *dequant) { + int diff, dc, k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe (j); + t = stbi__jpeg_huff_decode (j, hdc); + if (t < 0) return stbi__err ("bad huffman code", "Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset (data, 0, 64 * sizeof (data[0])); + + diff = t ? stbi__extend_receive (j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c, r, s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe (j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS) - 1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode (j, hac); + if (rs < 0) return stbi__err ("bad huffman code", "Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive (j, s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc (stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) { + int diff, dc; + int t; + if (j->spec_end != 0) return stbi__err ("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe (j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset (data, 0, 64 * sizeof (data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode (j, hdc); + diff = t ? stbi__extend_receive (j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit (j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac (stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) { + int k; + if (j->spec_start == 0) return stbi__err ("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c, r, s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe (j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS) - 1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode (j, hac); + if (rs < 0) return stbi__err ("bad huffman code", "Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits (j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive (j, s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit (j)) + if ((*p & bit) == 0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r, s; + int rs = stbi__jpeg_huff_decode (j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err ("bad huffman code", "Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits (j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err ("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit (j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit (j)) + if ((*p & bit) == 0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp (int x) { + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block (stbi_uc *out, int out_stride, short data[64]) { + int i, val[64], *v = val; + stbi_uc *o; + short *d = data; + + // columns + for (i = 0; i < 8; ++i, ++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[8] == 0 && d[16] == 0 && d[24] == 0 && d[32] == 0 + && d[40] == 0 && d[48] == 0 && d[56] == 0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D (d[0], d[8], d[16], d[24], d[32], d[40], d[48], d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[0] = (x0 + t3) >> 10; + v[56] = (x0 - t3) >> 10; + v[8] = (x1 + t2) >> 10; + v[48] = (x1 - t2) >> 10; + v[16] = (x2 + t1) >> 10; + v[40] = (x2 - t1) >> 10; + v[24] = (x3 + t0) >> 10; + v[32] = (x3 - t0) >> 10; + } + } + + for (i = 0, v = val, o = out; i < 8; ++i, v += 8, o += out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D (v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128 << 17); + x1 += 65536 + (128 << 17); + x2 += 65536 + (128 << 17); + x3 += 65536 + (128 << 17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp ((x0 + t3) >> 17); + o[7] = stbi__clamp ((x0 - t3) >> 17); + o[1] = stbi__clamp ((x1 + t2) >> 17); + o[6] = stbi__clamp ((x1 - t2) >> 17); + o[2] = stbi__clamp ((x2 + t1) >> 17); + o[5] = stbi__clamp ((x2 - t1) >> 17); + o[3] = stbi__clamp ((x3 + t0) >> 17); + o[4] = stbi__clamp ((x3 - t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd (stbi_uc *out, int out_stride, short data[64]) { + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y +#define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + +// out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) +// out(1) = c1[even]*x + c1[odd]*y +#define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) +#define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add +#define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub +#define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack +#define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) +#define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) +#define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + +#define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const (stbi__f2f (0.5411961f), stbi__f2f (0.5411961f) + stbi__f2f (-1.847759065f)); + __m128i rot0_1 = dct_const (stbi__f2f (0.5411961f) + stbi__f2f (0.765366865f), stbi__f2f (0.5411961f)); + __m128i rot1_0 = dct_const (stbi__f2f (1.175875602f) + stbi__f2f (-0.899976223f), stbi__f2f (1.175875602f)); + __m128i rot1_1 = dct_const (stbi__f2f (1.175875602f), stbi__f2f (1.175875602f) + stbi__f2f (-2.562915447f)); + __m128i rot2_0 = dct_const (stbi__f2f (-1.961570560f) + stbi__f2f (0.298631336f), stbi__f2f (-1.961570560f)); + __m128i rot2_1 = dct_const (stbi__f2f (-1.961570560f), stbi__f2f (-1.961570560f) + stbi__f2f (3.072711026f)); + __m128i rot3_0 = dct_const (stbi__f2f (-0.390180644f) + stbi__f2f (2.053119869f), stbi__f2f (-0.390180644f)); + __m128i rot3_1 = dct_const (stbi__f2f (-0.390180644f), stbi__f2f (-0.390180644f) + stbi__f2f (1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32 (512); + __m128i bias_1 = _mm_set1_epi32 (65536 + (128 << 17)); + + // load + row0 = _mm_load_si128 ((const __m128i *) (data + 0 * 8)); + row1 = _mm_load_si128 ((const __m128i *) (data + 1 * 8)); + row2 = _mm_load_si128 ((const __m128i *) (data + 2 * 8)); + row3 = _mm_load_si128 ((const __m128i *) (data + 3 * 8)); + row4 = _mm_load_si128 ((const __m128i *) (data + 4 * 8)); + row5 = _mm_load_si128 ((const __m128i *) (data + 5 * 8)); + row6 = _mm_load_si128 ((const __m128i *) (data + 6 * 8)); + row7 = _mm_load_si128 ((const __m128i *) (data + 7 * 8)); + + // column pass + dct_pass (bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16 (row0, row4); + dct_interleave16 (row1, row5); + dct_interleave16 (row2, row6); + dct_interleave16 (row3, row7); + + // transpose pass 2 + dct_interleave16 (row0, row2); + dct_interleave16 (row1, row3); + dct_interleave16 (row4, row6); + dct_interleave16 (row5, row7); + + // transpose pass 3 + dct_interleave16 (row0, row1); + dct_interleave16 (row2, row3); + dct_interleave16 (row4, row5); + dct_interleave16 (row6, row7); + } + + // row pass + dct_pass (bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16 (row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16 (row2, row3); + __m128i p2 = _mm_packus_epi16 (row4, row5); + __m128i p3 = _mm_packus_epi16 (row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8 (p0, p2); // a0e0a1e1... + dct_interleave8 (p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8 (p0, p1); // a0c0e0g0... + dct_interleave8 (p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8 (p0, p2); // a0b0c0d0... + dct_interleave8 (p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64 ((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64 ((__m128i *) out, _mm_shuffle_epi32 (p0, 0x4e)); out += out_stride; + _mm_storel_epi64 ((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64 ((__m128i *) out, _mm_shuffle_epi32 (p2, 0x4e)); out += out_stride; + _mm_storel_epi64 ((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64 ((__m128i *) out, _mm_shuffle_epi32 (p1, 0x4e)); out += out_stride; + _mm_storel_epi64 ((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64 ((__m128i *) out, _mm_shuffle_epi32 (p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd (stbi_uc *out, int out_stride, short data[64]) { + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16 (stbi__f2f (0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16 (stbi__f2f (-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16 (stbi__f2f (0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16 (stbi__f2f (1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16 (stbi__f2f (-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16 (stbi__f2f (-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16 (stbi__f2f (-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16 (stbi__f2f (-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16 (stbi__f2f (0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16 (stbi__f2f (2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16 (stbi__f2f (3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16 (stbi__f2f (1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + + // wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16 (data + 0 * 8); + row1 = vld1q_s16 (data + 1 * 8); + row2 = vld1q_s16 (data + 2 * 8); + row3 = vld1q_s16 (data + 3 * 8); + row4 = vld1q_s16 (data + 4 * 8); + row5 = vld1q_s16 (data + 5 * 8); + row6 = vld1q_s16 (data + 6 * 8); + row7 = vld1q_s16 (data + 7 * 8); + + // add DC bias + row0 = vaddq_s16 (row0, vsetq_lane_s16 (1024, vdupq_n_s16 (0), 0)); + + // column pass + dct_pass (vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { + // these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. + // whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16 (row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16 (row2, row3); + dct_trn16 (row4, row5); + dct_trn16 (row6, row7); + + // pass 2 + dct_trn32 (row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32 (row1, row3); + dct_trn32 (row4, row6); + dct_trn32 (row5, row7); + + // pass 3 + dct_trn64 (row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64 (row1, row5); + dct_trn64 (row2, row6); + dct_trn64 (row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass (vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16 (row0, 1); + uint8x8_t p1 = vqrshrun_n_s16 (row1, 1); + uint8x8_t p2 = vqrshrun_n_s16 (row2, 1); + uint8x8_t p3 = vqrshrun_n_s16 (row3, 1); + uint8x8_t p4 = vqrshrun_n_s16 (row4, 1); + uint8x8_t p5 = vqrshrun_n_s16 (row5, 1); + uint8x8_t p6 = vqrshrun_n_s16 (row6, 1); + uint8x8_t p7 = vqrshrun_n_s16 (row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8 (p0, p1); + dct_trn8_8 (p2, p3); + dct_trn8_8 (p4, p5); + dct_trn8_8 (p6, p7); + + // pass 2 + dct_trn8_16 (p0, p2); + dct_trn8_16 (p1, p3); + dct_trn8_16 (p4, p6); + dct_trn8_16 (p5, p7); + + // pass 3 + dct_trn8_32 (p0, p4); + dct_trn8_32 (p1, p5); + dct_trn8_32 (p2, p6); + dct_trn8_32 (p3, p7); + + // store + vst1_u8 (out, p0); out += out_stride; + vst1_u8 (out, p1); out += out_stride; + vst1_u8 (out, p2); out += out_stride; + vst1_u8 (out, p3); out += out_stride; + vst1_u8 (out, p4); out += out_stride; + vst1_u8 (out, p5); out += out_stride; + vst1_u8 (out, p6); out += out_stride; + vst1_u8 (out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker (stbi__jpeg *j) { + stbi_uc x; + if (j->marker != STBI__MARKER_none) { + x = j->marker; j->marker = STBI__MARKER_none; return x; + } + x = stbi__get8 (j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8 (j->s); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset (stbi__jpeg *j) { + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data (stbi__jpeg *z) { + stbi__jpeg_reset (z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i, j; + STBI_SIMD_ALIGN (short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x + 7) >> 3; + int h = (z->img_comp[n].y + 7) >> 3; + for (j = 0; j < h; ++j) { + for (i = 0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block (z, data, z->huff_dc + z->img_comp[n].hd, z->huff_ac + ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel (z->img_comp[n].data + z->img_comp[n].w2*j * 8 + i * 8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe (z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART (z->marker)) return 1; + stbi__jpeg_reset (z); + } + } + } + return 1; + } else { // interleaved + int i, j, k, x, y; + STBI_SIMD_ALIGN (short, data[64]); + for (j = 0; j < z->img_mcu_y; ++j) { + for (i = 0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k = 0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y = 0; y < z->img_comp[n].v; ++y) { + for (x = 0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x) * 8; + int y2 = (j*z->img_comp[n].v + y) * 8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block (z, data, z->huff_dc + z->img_comp[n].hd, z->huff_ac + ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel (z->img_comp[n].data + z->img_comp[n].w2*y2 + x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe (z); + if (!STBI__RESTART (z->marker)) return 1; + stbi__jpeg_reset (z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i, j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x + 7) >> 3; + int h = (z->img_comp[n].y + 7) >> 3; + for (j = 0; j < h; ++j) { + for (i = 0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc (z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac (z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe (z); + if (!STBI__RESTART (z->marker)) return 1; + stbi__jpeg_reset (z); + } + } + } + return 1; + } else { // interleaved + int i, j, k, x, y; + for (j = 0; j < z->img_mcu_y; ++j) { + for (i = 0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k = 0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y = 0; y < z->img_comp[n].v; ++y) { + for (x = 0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc (z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe (z); + if (!STBI__RESTART (z->marker)) return 1; + stbi__jpeg_reset (z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize (short *data, stbi_uc *dequant) { + int i; + for (i = 0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish (stbi__jpeg *z) { + if (z->progressive) { + // dequantize and idct the data + int i, j, n; + for (n = 0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x + 7) >> 3; + int h = (z->img_comp[n].y + 7) >> 3; + for (j = 0; j < h; ++j) { + for (i = 0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize (data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel (z->img_comp[n].data + z->img_comp[n].w2*j * 8 + i * 8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker (stbi__jpeg *z, int m) { + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err ("expected marker", "Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be (z->s) != 4) return stbi__err ("bad DRI len", "Corrupt JPEG"); + z->restart_interval = stbi__get16be (z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be (z->s) - 2; + while (L > 0) { + int q = stbi__get8 (z->s); + int p = q >> 4; + int t = q & 15, i; + if (p != 0) return stbi__err ("bad DQT type", "Corrupt JPEG"); + if (t > 3) return stbi__err ("bad DQT table", "Corrupt JPEG"); + for (i = 0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8 (z->s); + L -= 65; + } + return L == 0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be (z->s) - 2; + while (L > 0) { + stbi_uc *v; + int sizes[16], i, n = 0; + int q = stbi__get8 (z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err ("bad DHT header", "Corrupt JPEG"); + for (i = 0; i < 16; ++i) { + sizes[i] = stbi__get8 (z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman (z->huff_dc + th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman (z->huff_ac + th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i = 0; i < n; ++i) + v[i] = stbi__get8 (z->s); + if (tc != 0) + stbi__build_fast_ac (z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L == 0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + stbi__skip (z->s, stbi__get16be (z->s) - 2); + return 1; + } + return 0; +} + +// after we see SOS +static int stbi__process_scan_header (stbi__jpeg *z) { + int i; + int Ls = stbi__get16be (z->s); + z->scan_n = stbi__get8 (z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err ("bad SOS component count", "Corrupt JPEG"); + if (Ls != 6 + 2 * z->scan_n) return stbi__err ("bad SOS len", "Corrupt JPEG"); + for (i = 0; i < z->scan_n; ++i) { + int id = stbi__get8 (z->s), which; + int q = stbi__get8 (z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err ("bad DC huff", "Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err ("bad AC huff", "Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8 (z->s); + z->spec_end = stbi__get8 (z->s); // should be 63, but might be 0 + aa = stbi__get8 (z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err ("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err ("bad SOS", "Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err ("bad SOS", "Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__process_frame_header (stbi__jpeg *z, int scan) { + stbi__context *s = z->s; + int Lf, p, i, q, h_max = 1, v_max = 1, c; + Lf = stbi__get16be (s); if (Lf < 11) return stbi__err ("bad SOF len", "Corrupt JPEG"); // JPEG + p = stbi__get8 (s); if (p != 8) return stbi__err ("only 8-bit", "JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be (s); if (s->img_y == 0) return stbi__err ("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be (s); if (s->img_x == 0) return stbi__err ("0 width", "Corrupt JPEG"); // JPEG requires + c = stbi__get8 (s); + if (c != 3 && c != 1) return stbi__err ("bad component count", "Corrupt JPEG"); // JFIF requires + s->img_n = c; + for (i = 0; i < c; ++i) { + z->img_comp[i].data = nullptr; + z->img_comp[i].linebuf = nullptr; + } + + if (Lf != 8 + 3 * s->img_n) return stbi__err ("bad SOF len", "Corrupt JPEG"); + + for (i = 0; i < s->img_n; ++i) { + z->img_comp[i].id = stbi__get8 (s); + if (z->img_comp[i].id != i + 1) // JFIF requires + if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files! + return stbi__err ("bad component ID", "Corrupt JPEG"); + q = stbi__get8 (s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err ("bad H", "Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err ("bad V", "Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8 (s); if (z->img_comp[i].tq > 3) return stbi__err ("bad TQ", "Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err ("too large", "Image too large to decode"); + + for (i = 0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + z->img_mcu_x = (s->img_x + z->img_mcu_w - 1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h - 1) / z->img_mcu_h; + + for (i = 0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max - 1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max - 1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].raw_data = stbi__malloc (z->img_comp[i].w2 * z->img_comp[i].h2 + 15); + + if (!z->img_comp[i].raw_data) { + for (--i; i >= 0; --i) { + STBI_FREE (z->img_comp[i].raw_data); + z->img_comp[i].raw_data = nullptr; + } + return stbi__err ("outofmem", "Out of memory"); + } + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + z->img_comp[i].linebuf = nullptr; + if (z->progressive) { + z->img_comp[i].coeff_w = (z->img_comp[i].w2 + 7) >> 3; + z->img_comp[i].coeff_h = (z->img_comp[i].h2 + 7) >> 3; + z->img_comp[i].raw_coeff = STBI_MALLOC (z->img_comp[i].coeff_w * z->img_comp[i].coeff_h * 64 * sizeof (short) + 15); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } else { + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header (stbi__jpeg *z, int scan) { + int m; + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker (z); + if (!stbi__SOI (m)) return stbi__err ("no SOI", "Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker (z); + while (!stbi__SOF (m)) { + if (!stbi__process_marker (z, m)) return 0; + m = stbi__get_marker (z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof (z->s)) return stbi__err ("no SOF", "Corrupt JPEG"); + m = stbi__get_marker (z); + } + } + z->progressive = stbi__SOF_progressive (m); + if (!stbi__process_frame_header (z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image (stbi__jpeg *j) { + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = nullptr; + j->img_comp[m].raw_coeff = nullptr; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header (j, STBI__SCAN_load)) return 0; + m = stbi__get_marker (j); + while (!stbi__EOI (m)) { + if (stbi__SOS (m)) { + if (!stbi__process_scan_header (j)) return 0; + if (!stbi__parse_entropy_coded_data (j)) return 0; + if (j->marker == STBI__MARKER_none) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof (j->s)) { + int x = stbi__get8 (j->s); + if (x == 255) { + j->marker = stbi__get8 (j->s); + break; + } else if (x != 0) { + return stbi__err ("junk before marker", "Corrupt JPEG"); + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else { + if (!stbi__process_marker (j, m)) return 0; + } + m = stbi__get_marker (j); + } + if (j->progressive) + stbi__jpeg_finish (j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1 (stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { + STBI_NOTUSED (out); + STBI_NOTUSED (in_far); + STBI_NOTUSED (w); + STBI_NOTUSED (hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2 (stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED (hs); + for (i = 0; i < w; ++i) + out[i] = stbi__div4 (3 * in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2 (stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4 (input[0] * 3 + input[1] + 2); + for (i = 1; i < w - 1; ++i) { + int n = 3 * input[i] + 2; + out[i * 2 + 0] = stbi__div4 (n + input[i - 1]); + out[i * 2 + 1] = stbi__div4 (n + input[i + 1]); + } + out[i * 2 + 0] = stbi__div4 (input[w - 2] * 3 + input[w - 1] + 2); + out[i * 2 + 1] = input[w - 1]; + + STBI_NOTUSED (in_far); + STBI_NOTUSED (hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2 (stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { + // need to generate 2x2 samples for every one in input + int i, t0, t1; + if (w == 1) { + out[0] = out[1] = stbi__div4 (3 * in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3 * in_near[0] + in_far[0]; + out[0] = stbi__div4 (t1 + 2); + for (i = 1; i < w; ++i) { + t0 = t1; + t1 = 3 * in_near[i] + in_far[i]; + out[i * 2 - 1] = stbi__div16 (3 * t0 + t1 + 8); + out[i * 2] = stbi__div16 (3 * t1 + t0 + 8); + } + out[w * 2 - 1] = stbi__div4 (t1 + 2); + + STBI_NOTUSED (hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd (stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { + // need to generate 2x2 samples for every one in input + int i = 0, t0, t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4 (3 * in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3 * in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w - 1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128 (); + __m128i farb = _mm_loadl_epi64 ((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64 ((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8 (farb, zero); + __m128i nearw = _mm_unpacklo_epi8 (nearb, zero); + __m128i diff = _mm_sub_epi16 (farw, nearw); + __m128i nears = _mm_slli_epi16 (nearw, 2); + __m128i curr = _mm_add_epi16 (nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128 (curr, 2); + __m128i nxt0 = _mm_srli_si128 (curr, 2); + __m128i prev = _mm_insert_epi16 (prv0, t1, 0); + __m128i next = _mm_insert_epi16 (nxt0, 3 * in_near[i + 8] + in_far[i + 8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16 (8); + __m128i curs = _mm_slli_epi16 (curr, 2); + __m128i prvd = _mm_sub_epi16 (prev, curr); + __m128i nxtd = _mm_sub_epi16 (next, curr); + __m128i curb = _mm_add_epi16 (curs, bias); + __m128i even = _mm_add_epi16 (prvd, curb); + __m128i odd = _mm_add_epi16 (nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16 (even, odd); + __m128i int1 = _mm_unpackhi_epi16 (even, odd); + __m128i de0 = _mm_srli_epi16 (int0, 4); + __m128i de1 = _mm_srli_epi16 (int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16 (de0, de1); + _mm_storeu_si128 ((__m128i *) (out + i * 2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8 (in_far + i); + uint8x8_t nearb = vld1_u8 (in_near + i); + int16x8_t diff = vreinterpretq_s16_u16 (vsubl_u8 (farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16 (vshll_n_u8 (nearb, 2)); + int16x8_t curr = vaddq_s16 (nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16 (curr, curr, 7); + int16x8_t nxt0 = vextq_s16 (curr, curr, 1); + int16x8_t prev = vsetq_lane_s16 (t1, prv0, 0); + int16x8_t next = vsetq_lane_s16 (3 * in_near[i + 8] + in_far[i + 8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16 (curr, 2); + int16x8_t prvd = vsubq_s16 (prev, curr); + int16x8_t nxtd = vsubq_s16 (next, curr); + int16x8_t even = vaddq_s16 (curs, prvd); + int16x8_t odd = vaddq_s16 (curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16 (even, 4); + o.val[1] = vqrshrun_n_s16 (odd, 4); + vst2_u8 (out + i * 2, o); +#endif + + // "previous" value for next iter + t1 = 3 * in_near[i + 7] + in_far[i + 7]; + } + + t0 = t1; + t1 = 3 * in_near[i] + in_far[i]; + out[i * 2] = stbi__div16 (3 * t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3 * in_near[i] + in_far[i]; + out[i * 2 - 1] = stbi__div16 (3 * t0 + t1 + 8); + out[i * 2] = stbi__div16 (3 * t1 + t0 + 8); + } + out[w * 2 - 1] = stbi__div4 (t1 + 2); + + STBI_NOTUSED (hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic (stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { + // resample with nearest-neighbor + int i, j; + STBI_NOTUSED (in_far); + for (i = 0; i < w; ++i) + for (j = 0; j < hs; ++j) + out[i*hs + j] = in_near[i]; + return out; +} + +#ifdef STBI_JPEG_OLD +// this is the same YCbCr-to-RGB calculation that stb_image has used +// historically before the algorithm changes in 1.49 +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) +static void stbi__YCbCr_to_RGB_row (stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) { + int i; + for (i = 0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r, g, b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr * float2fixed (1.40200f); + g = y_fixed - cr * float2fixed (0.71414f) - cb * float2fixed (0.34414f); + b = y_fixed + cb * float2fixed (1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { + if (r < 0) r = 0; else r = 255; + } + if ((unsigned) g > 255) { + if (g < 0) g = 0; else g = 255; + } + if ((unsigned) b > 255) { + if (b < 0) b = 0; else b = 255; + } + out[0] = (stbi_uc) r; + out[1] = (stbi_uc) g; + out[2] = (stbi_uc) b; + out[3] = 255; + out += step; + } +} +#else +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row (stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) { + int i; + for (i = 0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1 << 19); // rounding + int r, g, b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr * float2fixed (1.40200f); + g = y_fixed + (cr*-float2fixed (0.71414f)) + ((cb*-float2fixed (0.34414f)) & 0xffff0000); + b = y_fixed + cb * float2fixed (1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { + if (r < 0) r = 0; else r = 255; + } + if ((unsigned) g > 255) { + if (g < 0) g = 0; else g = 255; + } + if ((unsigned) b > 255) { + if (b < 0) b = 0; else b = 255; + } + out[0] = (stbi_uc) r; + out[1] = (stbi_uc) g; + out[2] = (stbi_uc) b; + out[3] = 255; + out += step; + } +} +#endif + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd (stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) { + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8 (-0x80); + __m128i cr_const0 = _mm_set1_epi16 ((short) (1.40200f*4096.0f + 0.5f)); + __m128i cr_const1 = _mm_set1_epi16 (-(short) (0.71414f*4096.0f + 0.5f)); + __m128i cb_const0 = _mm_set1_epi16 (-(short) (0.34414f*4096.0f + 0.5f)); + __m128i cb_const1 = _mm_set1_epi16 ((short) (1.77200f*4096.0f + 0.5f)); + __m128i y_bias = _mm_set1_epi8 ((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16 (255); // alpha channel + + for (; i + 7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64 ((__m128i *) (y + i)); + __m128i cr_bytes = _mm_loadl_epi64 ((__m128i *) (pcr + i)); + __m128i cb_bytes = _mm_loadl_epi64 ((__m128i *) (pcb + i)); + __m128i cr_biased = _mm_xor_si128 (cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128 (cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8 (y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8 (_mm_setzero_si128 (), cr_biased); + __m128i cbw = _mm_unpacklo_epi8 (_mm_setzero_si128 (), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16 (yw, 4); + __m128i cr0 = _mm_mulhi_epi16 (cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16 (cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16 (cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16 (crw, cr_const1); + __m128i rws = _mm_add_epi16 (cr0, yws); + __m128i gwt = _mm_add_epi16 (cb0, yws); + __m128i bws = _mm_add_epi16 (yws, cb1); + __m128i gws = _mm_add_epi16 (gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16 (rws, 4); + __m128i bw = _mm_srai_epi16 (bws, 4); + __m128i gw = _mm_srai_epi16 (gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16 (rw, bw); + __m128i gxb = _mm_packus_epi16 (gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8 (brb, gxb); + __m128i t1 = _mm_unpackhi_epi8 (brb, gxb); + __m128i o0 = _mm_unpacklo_epi16 (t0, t1); + __m128i o1 = _mm_unpackhi_epi16 (t0, t1); + + // store + _mm_storeu_si128 ((__m128i *) (out + 0), o0); + _mm_storeu_si128 ((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8 (0x80); + int16x8_t cr_const0 = vdupq_n_s16 ((short) (1.40200f*4096.0f + 0.5f)); + int16x8_t cr_const1 = vdupq_n_s16 (-(short) (0.71414f*4096.0f + 0.5f)); + int16x8_t cb_const0 = vdupq_n_s16 (-(short) (0.34414f*4096.0f + 0.5f)); + int16x8_t cb_const1 = vdupq_n_s16 ((short) (1.77200f*4096.0f + 0.5f)); + + for (; i + 7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8 (y + i); + uint8x8_t cr_bytes = vld1_u8 (pcr + i); + uint8x8_t cb_bytes = vld1_u8 (pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8 (vsub_u8 (cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8 (vsub_u8 (cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16 (vshll_n_u8 (y_bytes, 4)); + int16x8_t crw = vshll_n_s8 (cr_biased, 7); + int16x8_t cbw = vshll_n_s8 (cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16 (crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16 (cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16 (crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16 (cbw, cb_const1); + int16x8_t rws = vaddq_s16 (yws, cr0); + int16x8_t gws = vaddq_s16 (vaddq_s16 (yws, cb0), cr1); + int16x8_t bws = vaddq_s16 (yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16 (rws, 4); + o.val[1] = vqrshrun_n_s16 (gws, 4); + o.val[2] = vqrshrun_n_s16 (bws, 4); + o.val[3] = vdup_n_u8 (255); + + // store, interleaving r/g/b/a + vst4_u8 (out, o); + out += 8 * 4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1 << 19); // rounding + int r, g, b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr * float2fixed (1.40200f); + g = y_fixed + cr * -float2fixed (0.71414f) + ((cb*-float2fixed (0.34414f)) & 0xffff0000); + b = y_fixed + cb * float2fixed (1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { + if (r < 0) r = 0; else r = 255; + } + if ((unsigned) g > 255) { + if (g < 0) g = 0; else g = 255; + } + if ((unsigned) b > 255) { + if (b < 0) b = 0; else b = 255; + } + out[0] = (stbi_uc) r; + out[1] = (stbi_uc) g; + out[2] = (stbi_uc) b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg (stbi__jpeg *j) { + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available ()) { + j->idct_block_kernel = stbi__idct_simd; +#ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; +#endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; +#ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; +#endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg (stbi__jpeg *j) { + int i; + for (i = 0; i < j->s->img_n; ++i) { + if (j->img_comp[i].raw_data) { + STBI_FREE (j->img_comp[i].raw_data); + j->img_comp[i].raw_data = nullptr; + j->img_comp[i].data = nullptr; + } + if (j->img_comp[i].raw_coeff) { + STBI_FREE (j->img_comp[i].raw_coeff); + j->img_comp[i].raw_coeff = 0; + j->img_comp[i].coeff = 0; + } + if (j->img_comp[i].linebuf) { + STBI_FREE (j->img_comp[i].linebuf); + j->img_comp[i].linebuf = nullptr; + } + } +} + +typedef struct { + resample_row_func resample; + stbi_uc *line0, *line1; + int hs, vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +static stbi_uc *load_jpeg_image (stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) { + int n, decode_n; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc ("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image (z)) { + stbi__cleanup_jpeg (z); return nullptr; + } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n; + + if (z->s->img_n == 3 && n < 3) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i, j; + stbi_uc *output; + stbi_uc *coutput[4]; + + stbi__resample res_comp[4]; + + for (k = 0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc (z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { + stbi__cleanup_jpeg (z); return stbi__errpuc ("outofmem", "Out of memory"); + } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs - 1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc (n * z->s->img_x * z->s->img_y + 1); + if (!output) { + stbi__cleanup_jpeg (z); return stbi__errpuc ("outofmem", "Out of memory"); + } + + // now go ahead and resample + for (j = 0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k = 0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample (z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + z->YCbCr_to_RGB_kernel (out, y, coutput[1], coutput[2], z->s->img_x, n); + } else + for (i = 0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i = 0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i = 0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + stbi__cleanup_jpeg (z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n; // report original components, not output + return output; + } +} + +static unsigned char *stbi__jpeg_load (stbi__context *s, int *x, int *y, int *comp, int req_comp) { + stbi__jpeg j; + j.s = s; + stbi__setup_jpeg (&j); + return load_jpeg_image (&j, x, y, comp, req_comp); +} + +static int stbi__jpeg_test (stbi__context *s) { + int r; + stbi__jpeg j; + j.s = s; + stbi__setup_jpeg (&j); + r = stbi__decode_jpeg_header (&j, STBI__SCAN_type); + stbi__rewind (s); + return r; +} + +static int stbi__jpeg_info_raw (stbi__jpeg *j, int *x, int *y, int *comp) { + if (!stbi__decode_jpeg_header (j, STBI__SCAN_header)) { + stbi__rewind (j->s); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n; + return 1; +} + +static int stbi__jpeg_info (stbi__context *s, int *x, int *y, int *comp) { + stbi__jpeg j; + j.s = s; + return stbi__jpeg_info_raw (&j, x, y, comp); +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct { + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16 (int n) { + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse (int v, int bits) { + STBI_ASSERT (bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16 (v) >> (16 - bits); +} + +static int stbi__zbuild_huffman (stbi__zhuffman *z, stbi_uc *sizelist, int num) { + int i, k = 0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset (sizes, 0, sizeof (sizes)); + memset (z->fast, 0, sizeof (z->fast)); + for (i = 0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i = 1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err ("bad sizes", "Corrupt PNG"); + code = 0; + for (i = 1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code - 1 >= (1 << i)) return stbi__err ("bad codelengths", "Corrupt PNG"); + z->maxcode[i] = code << (16 - i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i = 0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size[c] = (stbi_uc) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse (next_code[s], s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct { + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8 (stbi__zbuf *z) { + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits (stbi__zbuf *z) { + do { + STBI_ASSERT (z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8 (z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive (stbi__zbuf *z, int n) { + unsigned int k; + if (z->num_bits < n) stbi__fill_bits (z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath (stbi__zbuf *a, stbi__zhuffman *z) { + int b, s, k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse (a->code_buffer, 16); + for (s = STBI__ZFAST_BITS + 1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16 - s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT (z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode (stbi__zbuf *a, stbi__zhuffman *z) { + int b, s; + if (a->num_bits < 16) stbi__fill_bits (a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath (a, z); +} + +static int stbi__zexpand (stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err ("output buffer limit", "Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC (z->zout_start, limit); + if (!q) return stbi__err ("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int stbi__zlength_base[31] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, + 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; + +static int stbi__zlength_extra[31] = +{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; + +static int stbi__zdist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, +257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; + +static int stbi__zdist_extra[32] = +{ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; + +static int stbi__parse_huffman_block (stbi__zbuf *a) { + char *zout = a->zout; + for (;;) { + int z = stbi__zhuffman_decode (a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err ("bad huffman code", "Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand (a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len, dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive (a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode (a, &a->z_distance); + if (z < 0) return stbi__err ("bad huffman code", "Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive (a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err ("bad dist", "Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand (a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { + do *zout++ = v; while (--len); + } + } else { + if (len) { + do *zout++ = *p++; while (--len); + } + } + } + } +} + +static int stbi__compute_huffman_codes (stbi__zbuf *a) { + static stbi_uc length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286 + 32 + 137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i, n; + + int hlit = stbi__zreceive (a, 5) + 257; + int hdist = stbi__zreceive (a, 5) + 1; + int hclen = stbi__zreceive (a, 4) + 4; + + memset (codelength_sizes, 0, sizeof (codelength_sizes)); + for (i = 0; i < hclen; ++i) { + int s = stbi__zreceive (a, 3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman (&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < hlit + hdist) { + int c = stbi__zhuffman_decode (a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err ("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else if (c == 16) { + c = stbi__zreceive (a, 2) + 3; + memset (lencodes + n, lencodes[n - 1], c); + n += c; + } else if (c == 17) { + c = stbi__zreceive (a, 3) + 3; + memset (lencodes + n, 0, c); + n += c; + } else { + STBI_ASSERT (c == 18); + c = stbi__zreceive (a, 7) + 11; + memset (lencodes + n, 0, c); + n += c; + } + } + if (n != hlit + hdist) return stbi__err ("bad codelengths", "Corrupt PNG"); + if (!stbi__zbuild_huffman (&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman (&a->z_distance, lencodes + hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncomperssed_block (stbi__zbuf *a) { + stbi_uc header[4]; + int len, nlen, k; + if (a->num_bits & 7) + stbi__zreceive (a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT (a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8 (a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err ("zlib corrupt", "Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err ("read past buffer", "Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand (a, a->zout, len)) return 0; + memcpy (a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header (stbi__zbuf *a) { + int cmf = stbi__zget8 (a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8 (a); + if ((cmf * 256 + flg) % 31 != 0) return stbi__err ("bad zlib header", "Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err ("no preset dict", "Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err ("bad compression", "Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32]; +static void stbi__init_zdefaults (void) { + int i; // use <= to match clearly with spec + for (i = 0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for (; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for (; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for (; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i = 0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} + +static int stbi__parse_zlib (stbi__zbuf *a, int parse_header) { + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header (a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive (a, 1); + type = stbi__zreceive (a, 2); + if (type == 0) { + if (!stbi__parse_uncomperssed_block (a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zdefault_distance[31]) stbi__init_zdefaults (); + if (!stbi__zbuild_huffman (&a->z_length, stbi__zdefault_length, 288)) return 0; + if (!stbi__zbuild_huffman (&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes (a)) return 0; + } + if (!stbi__parse_huffman_block (a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib (stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) { + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib (a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize (const char *buffer, int len, int initial_size, int *outlen) { + stbi__zbuf a; + char *p = (char *) stbi__malloc (initial_size); + if (!p) return nullptr; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib (&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE (a.zout_start); + return nullptr; + } +} + +STBIDEF char *stbi_zlib_decode_malloc (char const *buffer, int len, int *outlen) { + return stbi_zlib_decode_malloc_guesssize (buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag (const char *buffer, int len, int initial_size, int *outlen, int parse_header) { + stbi__zbuf a; + char *p = (char *) stbi__malloc (initial_size); + if (!p) return nullptr; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib (&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE (a.zout_start); + return nullptr; + } +} + +STBIDEF int stbi_zlib_decode_buffer (char *obuffer, int olen, char const *ibuffer, int ilen) { + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib (&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc (char const *buffer, int len, int *outlen) { + stbi__zbuf a; + char *p = (char *) stbi__malloc (16384); + if (!p) return nullptr; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib (&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE (a.zout_start); + return nullptr; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer (char *obuffer, int olen, const char *ibuffer, int ilen) { + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib (&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct { + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header (stbi__context *s) { + stbi__pngchunk c; + c.length = stbi__get32be (s); + c.type = stbi__get32be (s); + return c; +} + +static int stbi__check_png_header (stbi__context *s) { + static stbi_uc png_sig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + int i; + for (i = 0; i < 8; ++i) + if (stbi__get8 (s) != png_sig[i]) return stbi__err ("bad png sig", "Not a PNG"); + return 1; +} + +typedef struct { + stbi__context *s; + stbi_uc *idata, *expanded, *out; +} stbi__png; + + +enum { + STBI__F_none = 0, + STBI__F_sub = 1, + STBI__F_up = 2, + STBI__F_avg = 3, + STBI__F_paeth = 4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth (int a, int b, int c) { + int p = a + b - c; + int pa = abs (p - a); + int pb = abs (p - b); + int pc = abs (p - c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0, 0, 0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw (stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) { + stbi__context *s = a->s; + stbi__uint32 i, j, stride = x * out_n; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + STBI_ASSERT (out_n == s->img_n || out_n == s->img_n + 1); + a->out = (stbi_uc *) stbi__malloc (x * y * out_n); // extra bytes to write off the end into + if (!a->out) return stbi__err ("outofmem", "Out of memory"); + + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + if (s->img_x == x && s->img_y == y) { + if (raw_len != img_len) return stbi__err ("not enough pixels", "Corrupt PNG"); + } else { // interlaced: + if (raw_len < img_len) return stbi__err ("not enough pixels", "Corrupt PNG"); + } + + for (j = 0; j < y; ++j) { + stbi_uc *cur = a->out + stride * j; + stbi_uc *prior = cur - stride; + int filter = *raw++; + int filter_bytes = img_n; + int width = x; + if (filter > 4) + return stbi__err ("invalid filter", "Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT (img_width_bytes <= x); + cur += x * out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k = 0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none: cur[k] = raw[k]; break; + case STBI__F_sub: cur[k] = raw[k]; break; + case STBI__F_up: cur[k] = STBI__BYTECAST (raw[k] + prior[k]); break; + case STBI__F_avg: cur[k] = STBI__BYTECAST (raw[k] + (prior[k] >> 1)); break; + case STBI__F_paeth: cur[k] = STBI__BYTECAST (raw[k] + stbi__paeth (0, prior[k], 0)); break; + case STBI__F_avg_first: cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*img_n; +#define CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy (cur, raw, nk); break; + CASE (STBI__F_sub) cur[k] = STBI__BYTECAST (raw[k] + cur[k - filter_bytes]); break; + CASE (STBI__F_up) cur[k] = STBI__BYTECAST (raw[k] + prior[k]); break; + CASE (STBI__F_avg) cur[k] = STBI__BYTECAST (raw[k] + ((prior[k] + cur[k - filter_bytes]) >> 1)); break; + CASE (STBI__F_paeth) cur[k] = STBI__BYTECAST (raw[k] + stbi__paeth (cur[k - filter_bytes], prior[k], prior[k - filter_bytes])); break; + CASE (STBI__F_avg_first) cur[k] = STBI__BYTECAST (raw[k] + (cur[k - filter_bytes] >> 1)); break; + CASE (STBI__F_paeth_first) cur[k] = STBI__BYTECAST (raw[k] + stbi__paeth (cur[k - filter_bytes], 0, 0)); break; + } +#undef CASE + raw += nk; + } else { + STBI_ASSERT (img_n + 1 == out_n); +#define CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ + for (k=0; k < img_n; ++k) + switch (filter) { + CASE (STBI__F_none) cur[k] = raw[k]; break; + CASE (STBI__F_sub) cur[k] = STBI__BYTECAST (raw[k] + cur[k - out_n]); break; + CASE (STBI__F_up) cur[k] = STBI__BYTECAST (raw[k] + prior[k]); break; + CASE (STBI__F_avg) cur[k] = STBI__BYTECAST (raw[k] + ((prior[k] + cur[k - out_n]) >> 1)); break; + CASE (STBI__F_paeth) cur[k] = STBI__BYTECAST (raw[k] + stbi__paeth (cur[k - out_n], prior[k], prior[k - out_n])); break; + CASE (STBI__F_avg_first) cur[k] = STBI__BYTECAST (raw[k] + (cur[k - out_n] >> 1)); break; + CASE (STBI__F_paeth_first) cur[k] = STBI__BYTECAST (raw[k] + stbi__paeth (cur[k - out_n], 0, 0)); break; + } +#undef CASE + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j = 0; j < y; ++j) { + stbi_uc *cur = a->out + stride * j; + stbi_uc *in = a->out + stride * j + x * out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k = x * img_n; k >= 2; k -= 2, ++in) { + *cur++ = scale * ((*in >> 4)); + *cur++ = scale * ((*in) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4)); + } else if (depth == 2) { + for (k = x * img_n; k >= 4; k -= 4, ++in) { + *cur++ = scale * ((*in >> 6)); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6)); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k = x * img_n; k >= 8; k -= 8, ++in) { + *cur++ = scale * ((*in >> 7)); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7)); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride * j; + if (img_n == 1) { + for (q = x - 1; q >= 0; --q) { + cur[q * 2 + 1] = 255; + cur[q * 2 + 0] = cur[q]; + } + } else { + STBI_ASSERT (img_n == 3); + for (q = x - 1; q >= 0; --q) { + cur[q * 4 + 3] = 255; + cur[q * 4 + 2] = cur[q * 3 + 2]; + cur[q * 4 + 1] = cur[q * 3 + 1]; + cur[q * 4 + 0] = cur[q * 3 + 0]; + } + } + } + } + } + + return 1; +} + +static int stbi__create_png_image (stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) { + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw (a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc (a->s->img_x * a->s->img_y * out_n); + for (p = 0; p < 7; ++p) { + int xorig[] = { 0, 4, 0, 2, 0, 1, 0 }; + int yorig[] = { 0, 0, 4, 0, 2, 0, 1 }; + int xspc[] = { 8, 8, 4, 4, 2, 2, 1 }; + int yspc[] = { 8, 8, 8, 4, 4, 2, 2 }; + int i, j, x, y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p] - 1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p] - 1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw (a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE (final); + return 0; + } + for (j = 0; j < y; ++j) { + for (i = 0; i < x; ++i) { + int out_y = j * yspc[p] + yorig[p]; + int out_x = i * xspc[p] + xorig[p]; + memcpy (final + out_y * a->s->img_x*out_n + out_x * out_n, + a->out + (j*x + i)*out_n, out_n); + } + } + STBI_FREE (a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency (stbi__png *z, stbi_uc tc[3], int out_n) { + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT (out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette (stbi__png *a, stbi_uc *palette, int len, int pal_img_n) { + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc (pixel_count * pal_img_n); + if (!p) return stbi__err ("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i = 0; i < pixel_count; ++i) { + int n = orig[i] * 4; + p[0] = palette[n]; + p[1] = palette[n + 1]; + p[2] = palette[n + 2]; + p += 3; + } + } else { + for (i = 0; i < pixel_count; ++i) { + int n = orig[i] * 4; + p[0] = palette[n]; + p[1] = palette[n + 1]; + p[2] = palette[n + 2]; + p[3] = palette[n + 3]; + p += 4; + } + } + STBI_FREE (a->out); + a->out = temp_out; + + STBI_NOTUSED (len); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) { + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) { + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone (stbi__png *z) { + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i = 0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT (s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i = 0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i = 0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static int stbi__parse_png_file (stbi__png *z, int scan, int req_comp) { + stbi_uc palette[1024], pal_img_n = 0; + stbi_uc has_trans = 0, tc[3]; + stbi__uint32 ioff = 0, idata_limit = 0, i, pal_len = 0; + int first = 1, k, interlace = 0, color = 0, depth = 0, is_iphone = 0; + stbi__context *s = z->s; + + z->expanded = nullptr; + z->idata = nullptr; + z->out = nullptr; + + if (!stbi__check_png_header (s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header (s); + switch (c.type) { + case STBI__PNG_TYPE ('C', 'g', 'B', 'I'): + is_iphone = 1; + stbi__skip (s, c.length); + break; + case STBI__PNG_TYPE ('I', 'H', 'D', 'R'): + { + int comp, filter; + if (!first) return stbi__err ("multiple IHDR", "Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err ("bad IHDR len", "Corrupt PNG"); + s->img_x = stbi__get32be (s); if (s->img_x > (1 << 24)) return stbi__err ("too large", "Very large image (corrupt?)"); + s->img_y = stbi__get32be (s); if (s->img_y > (1 << 24)) return stbi__err ("too large", "Very large image (corrupt?)"); + depth = stbi__get8 (s); if (depth != 1 && depth != 2 && depth != 4 && depth != 8) return stbi__err ("1/2/4/8-bit only", "PNG not supported: 1/2/4/8-bit only"); + color = stbi__get8 (s); if (color > 6) return stbi__err ("bad ctype", "Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err ("bad ctype", "Corrupt PNG"); + comp = stbi__get8 (s); if (comp) return stbi__err ("bad comp method", "Corrupt PNG"); + filter = stbi__get8 (s); if (filter) return stbi__err ("bad filter method", "Corrupt PNG"); + interlace = stbi__get8 (s); if (interlace > 1) return stbi__err ("bad interlace method", "Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err ("0-pixel image", "Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err ("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err ("too large", "Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE ('P', 'L', 'T', 'E'): + { + if (first) return stbi__err ("first not IHDR", "Corrupt PNG"); + if (c.length > 256 * 3) return stbi__err ("invalid PLTE", "Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err ("invalid PLTE", "Corrupt PNG"); + for (i = 0; i < pal_len; ++i) { + palette[i * 4 + 0] = stbi__get8 (s); + palette[i * 4 + 1] = stbi__get8 (s); + palette[i * 4 + 2] = stbi__get8 (s); + palette[i * 4 + 3] = 255; + } + break; + } + + case STBI__PNG_TYPE ('t', 'R', 'N', 'S'): + { + if (first) return stbi__err ("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err ("tRNS after IDAT", "Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { + s->img_n = 4; return 1; + } + if (pal_len == 0) return stbi__err ("tRNS before PLTE", "Corrupt PNG"); + if (c.length > pal_len) return stbi__err ("bad tRNS len", "Corrupt PNG"); + pal_img_n = 4; + for (i = 0; i < c.length; ++i) + palette[i * 4 + 3] = stbi__get8 (s); + } else { + if (!(s->img_n & 1)) return stbi__err ("tRNS with alpha", "Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n * 2) return stbi__err ("bad tRNS len", "Corrupt PNG"); + has_trans = 1; + for (k = 0; k < s->img_n; ++k) + tc[k] = (stbi_uc) (stbi__get16be (s) & 255) * stbi__depth_scale_table[depth]; // non 8-bit images will be larger + } + break; + } + + case STBI__PNG_TYPE ('I', 'D', 'A', 'T'): + { + if (first) return stbi__err ("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err ("no PLTE", "Corrupt PNG"); + if (scan == STBI__SCAN_header) { + s->img_n = pal_img_n; return 1; + } + if ((int) (ioff + c.length) < (int) ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + p = (stbi_uc *) STBI_REALLOC (z->idata, idata_limit); if (!p) return stbi__err ("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn (s, z->idata + ioff, c.length)) return stbi__err ("outofdata", "Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE ('I', 'E', 'N', 'D'): + { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err ("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (!z->idata) return stbi__err ("no IDAT", "Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag ((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (!z->expanded) return 0; // zlib should set error + STBI_FREE (z->idata); z->idata = nullptr; + if ((req_comp == s->img_n + 1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n + 1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image (z, z->expanded, raw_len, s->img_out_n, depth, color, interlace)) return 0; + if (has_trans) + if (!stbi__compute_transparency (z, tc, s->img_out_n)) return 0; + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone (z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette (z, palette, pal_len, s->img_out_n)) + return 0; + } + STBI_FREE (z->expanded); z->expanded = nullptr; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err ("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { +#ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST (c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST (c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST (c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST (c.type >> 0); +#endif + return stbi__err (invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip (s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be (s); + } +} + +static unsigned char *stbi__do_png (stbi__png *p, int *x, int *y, int *n, int req_comp) { + unsigned char *result = nullptr; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc ("bad req_comp", "Internal error"); + if (stbi__parse_png_file (p, STBI__SCAN_load, req_comp)) { + result = p->out; + p->out = nullptr; + if (req_comp && req_comp != p->s->img_out_n) { + result = stbi__convert_format (result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (!result) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_out_n; + } + STBI_FREE (p->out); p->out = nullptr; + STBI_FREE (p->expanded); p->expanded = nullptr; + STBI_FREE (p->idata); p->idata = nullptr; + + return result; +} + +static unsigned char *stbi__png_load (stbi__context *s, int *x, int *y, int *comp, int req_comp) { + stbi__png p; + p.s = s; + return stbi__do_png (&p, x, y, comp, req_comp); +} + +static int stbi__png_test (stbi__context *s) { + int r; + r = stbi__check_png_header (s); + stbi__rewind (s); + return r; +} + +static int stbi__png_info_raw (stbi__png *p, int *x, int *y, int *comp) { + if (!stbi__parse_png_file (p, STBI__SCAN_header, 0)) { + stbi__rewind (p->s); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info (stbi__context *s, int *x, int *y, int *comp) { + stbi__png p; + p.s = s; + return stbi__png_info_raw (&p, x, y, comp); +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw (stbi__context *s) { + int r; + int sz; + if (stbi__get8 (s) != 'B') return 0; + if (stbi__get8 (s) != 'M') return 0; + stbi__get32le (s); // discard filesize + stbi__get16le (s); // discard reserved + stbi__get16le (s); // discard reserved + stbi__get32le (s); // discard data offset + sz = stbi__get32le (s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test (stbi__context *s) { + int r = stbi__bmp_test_raw (s); + stbi__rewind (s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit (unsigned int z) { + int n = 0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int stbi__bitcount (unsigned int a) { + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int stbi__shiftsigned (int v, int shift, int bits) { + int result; + int z = 0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +static stbi_uc *stbi__bmp_load (stbi__context *s, int *x, int *y, int *comp, int req_comp) { + stbi_uc *out; + unsigned int mr = 0, mg = 0, mb = 0, ma = 0, all_a = 255; + stbi_uc pal[256][4]; + int psize = 0, i, j, compress = 0, width; + int bpp, flip_vertically, pad, target, offset, hsz; + if (stbi__get8 (s) != 'B' || stbi__get8 (s) != 'M') return stbi__errpuc ("not BMP", "Corrupt BMP"); + stbi__get32le (s); // discard filesize + stbi__get16le (s); // discard reserved + stbi__get16le (s); // discard reserved + offset = stbi__get32le (s); + hsz = stbi__get32le (s); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc ("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le (s); + s->img_y = stbi__get16le (s); + } else { + s->img_x = stbi__get32le (s); + s->img_y = stbi__get32le (s); + } + if (stbi__get16le (s) != 1) return stbi__errpuc ("bad BMP", "bad BMP"); + bpp = stbi__get16le (s); + if (bpp == 1) return stbi__errpuc ("monochrome", "BMP type not supported: 1-bit"); + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs ((int) s->img_y); + if (hsz == 12) { + if (bpp < 24) + psize = (offset - 14 - 24) / 3; + } else { + compress = stbi__get32le (s); + if (compress == 1 || compress == 2) return stbi__errpuc ("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le (s); // discard sizeof + stbi__get32le (s); // discard hres + stbi__get32le (s); // discard vres + stbi__get32le (s); // discard colorsused + stbi__get32le (s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le (s); + stbi__get32le (s); + stbi__get32le (s); + stbi__get32le (s); + } + if (bpp == 16 || bpp == 32) { + mr = mg = mb = 0; + if (compress == 0) { + if (bpp == 32) { + mr = 0xffu << 16; + mg = 0xffu << 8; + mb = 0xffu << 0; + ma = 0xffu << 24; + all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + mr = 31u << 10; + mg = 31u << 5; + mb = 31u << 0; + } + } else if (compress == 3) { + mr = stbi__get32le (s); + mg = stbi__get32le (s); + mb = stbi__get32le (s); + // not documented, but generated by photoshop and handled by mspaint + if (mr == mg && mg == mb) { + // ?!?!? + return stbi__errpuc ("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc ("bad BMP", "bad BMP"); + } + } else { + STBI_ASSERT (hsz == 108 || hsz == 124); + mr = stbi__get32le (s); + mg = stbi__get32le (s); + mb = stbi__get32le (s); + ma = stbi__get32le (s); + stbi__get32le (s); // discard color space + for (i = 0; i < 12; ++i) + stbi__get32le (s); // discard color space parameters + if (hsz == 124) { + stbi__get32le (s); // discard rendering intent + stbi__get32le (s); // discard offset of profile data + stbi__get32le (s); // discard size of profile data + stbi__get32le (s); // discard reserved + } + } + if (bpp < 16) + psize = (offset - 14 - hsz) >> 2; + } + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + out = (stbi_uc *) stbi__malloc (target * s->img_x * s->img_y); + if (!out) return stbi__errpuc ("outofmem", "Out of memory"); + if (bpp < 16) { + int z = 0; + if (psize == 0 || psize > 256) { + STBI_FREE (out); return stbi__errpuc ("invalid", "Corrupt BMP"); + } + for (i = 0; i < psize; ++i) { + pal[i][2] = stbi__get8 (s); + pal[i][1] = stbi__get8 (s); + pal[i][0] = stbi__get8 (s); + if (hsz != 12) stbi__get8 (s); + pal[i][3] = 255; + } + stbi__skip (s, offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4)); + if (bpp == 4) width = (s->img_x + 1) >> 1; + else if (bpp == 8) width = s->img_x; + else { + STBI_FREE (out); return stbi__errpuc ("bad bpp", "Corrupt BMP"); + } + pad = (-width) & 3; + for (j = 0; j < (int) s->img_y; ++j) { + for (i = 0; i < (int) s->img_x; i += 2) { + int v = stbi__get8 (s), v2 = 0; + if (bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i + 1 == (int) s->img_x) break; + v = (bpp == 8) ? stbi__get8 (s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip (s, pad); + } + } else { + int rshift = 0, gshift = 0, bshift = 0, ashift = 0, rcount = 0, gcount = 0, bcount = 0, acount = 0; + int z = 0; + int easy = 0; + stbi__skip (s, offset - 14 - hsz); + if (bpp == 24) width = 3 * s->img_x; + else if (bpp == 16) width = 2 * s->img_x; + else /* bpp = 32 and pad = 0 */ width = 0; + pad = (-width) & 3; + if (bpp == 24) { + easy = 1; + } else if (bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { + STBI_FREE (out); return stbi__errpuc ("bad masks", "Corrupt BMP"); + } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit (mr) - 7; rcount = stbi__bitcount (mr); + gshift = stbi__high_bit (mg) - 7; gcount = stbi__bitcount (mg); + bshift = stbi__high_bit (mb) - 7; bcount = stbi__bitcount (mb); + ashift = stbi__high_bit (ma) - 7; acount = stbi__bitcount (ma); + } + for (j = 0; j < (int) s->img_y; ++j) { + if (easy) { + for (i = 0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z + 2] = stbi__get8 (s); + out[z + 1] = stbi__get8 (s); + out[z + 0] = stbi__get8 (s); + z += 3; + a = (easy == 2 ? stbi__get8 (s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + for (i = 0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le (s) : stbi__get32le (s)); + int a; + out[z++] = STBI__BYTECAST (stbi__shiftsigned (v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST (stbi__shiftsigned (v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST (stbi__shiftsigned (v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned (v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST (a); + } + } + stbi__skip (s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i = 4 * s->img_x*s->img_y - 1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j = 0; j < (int) s->img_y >> 1; ++j) { + stbi_uc *p1 = out + j * s->img_x*target; + stbi_uc *p2 = out + (s->img_y - 1 - j)*s->img_x*target; + for (i = 0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format (out, target, req_comp, s->img_x, s->img_y); + if (!out) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +static int stbi__tga_info (stbi__context *s, int *x, int *y, int *comp) { + int tga_w, tga_h, tga_comp; + int sz; + stbi__get8 (s); // discard Offset + sz = stbi__get8 (s); // color type + if (sz > 1) { + stbi__rewind (s); + return 0; // only RGB or indexed allowed + } + sz = stbi__get8 (s); // image type + // only RGB or grey allowed, +/- RLE + if ((sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11)) return 0; + stbi__skip (s, 9); + tga_w = stbi__get16le (s); + if (tga_w < 1) { + stbi__rewind (s); + return 0; // test width + } + tga_h = stbi__get16le (s); + if (tga_h < 1) { + stbi__rewind (s); + return 0; // test height + } + sz = stbi__get8 (s); // bits per pixel + // only RGB or RGBA or grey allowed + if ((sz != 8) && (sz != 16) && (sz != 24) && (sz != 32)) { + stbi__rewind (s); + return 0; + } + tga_comp = sz; + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp / 8; + return 1; // seems to have passed everything +} + +static int stbi__tga_test (stbi__context *s) { + int res; + int sz; + stbi__get8 (s); // discard Offset + sz = stbi__get8 (s); // color type + if (sz > 1) return 0; // only RGB or indexed allowed + sz = stbi__get8 (s); // image type + if ((sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11)) return 0; // only RGB or grey allowed, +/- RLE + stbi__get16be (s); // discard palette start + stbi__get16be (s); // discard palette length + stbi__get8 (s); // discard bits per palette color entry + stbi__get16be (s); // discard x origin + stbi__get16be (s); // discard y origin + if (stbi__get16be (s) < 1) return 0; // test width + if (stbi__get16be (s) < 1) return 0; // test height + sz = stbi__get8 (s); // bits per pixel + if ((sz != 8) && (sz != 16) && (sz != 24) && (sz != 32)) + res = 0; + else + res = 1; + stbi__rewind (s); + return res; +} + +static stbi_uc *stbi__tga_load (stbi__context *s, int *x, int *y, int *comp, int req_comp) { + // read in the TGA header stuff + int tga_offset = stbi__get8 (s); + int tga_indexed = stbi__get8 (s); + int tga_image_type = stbi__get8 (s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le (s); + int tga_palette_len = stbi__get16le (s); + int tga_palette_bits = stbi__get8 (s); + int tga_x_origin = stbi__get16le (s); + int tga_y_origin = stbi__get16le (s); + int tga_width = stbi__get16le (s); + int tga_height = stbi__get16le (s); + int tga_bits_per_pixel = stbi__get8 (s); + int tga_comp = tga_bits_per_pixel / 8; + int tga_inverted = stbi__get8 (s); + // image data + unsigned char *tga_data; + unsigned char *tga_palette = nullptr; + int i, j; + unsigned char raw_data[4]; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + + // do a tiny bit of precessing + if (tga_image_type >= 8) { + tga_image_type -= 8; + tga_is_RLE = 1; + } + /* int tga_alpha_bits = tga_inverted & 15; */ + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // error check + if ( //(tga_indexed) || + (tga_width < 1) || (tga_height < 1) || + (tga_image_type < 1) || (tga_image_type > 3) || + ((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16) && + (tga_bits_per_pixel != 24) && (tga_bits_per_pixel != 32)) + ) { + return nullptr; // we don't report this as a bad TGA because we don't even know if it's TGA + } + + // If I'm paletted, then I'll use the number of bits from the palette + if (tga_indexed) { + tga_comp = tga_palette_bits / 8; + } + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + tga_data = (unsigned char*) stbi__malloc ((size_t) tga_width * tga_height * tga_comp); + if (!tga_data) return stbi__errpuc ("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip (s, tga_offset); + + if (!tga_indexed && !tga_is_RLE) { + for (i = 0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height - i - 1 : i; + stbi_uc *tga_row = tga_data + row * tga_width*tga_comp; + stbi__getn (s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if (tga_indexed) { + // any data to skip? (offset usually = 0) + stbi__skip (s, tga_palette_start); + // load the palette + tga_palette = (unsigned char*) stbi__malloc (tga_palette_len * tga_palette_bits / 8); + if (!tga_palette) { + STBI_FREE (tga_data); + return stbi__errpuc ("outofmem", "Out of memory"); + } + if (!stbi__getn (s, tga_palette, tga_palette_len * tga_palette_bits / 8)) { + STBI_FREE (tga_data); + STBI_FREE (tga_palette); + return stbi__errpuc ("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i = 0; i < tga_width * tga_height; ++i) { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if (tga_is_RLE) { + if (RLE_count == 0) { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8 (s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if (!RLE_repeating) { + read_next_pixel = 1; + } + } else { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if (read_next_pixel) { + // load however much data we did have + if (tga_indexed) { + // read in 1 byte, then perform the lookup + int pal_idx = stbi__get8 (s); + if (pal_idx >= tga_palette_len) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_bits_per_pixel / 8; + for (j = 0; j * 8 < tga_bits_per_pixel; ++j) { + raw_data[j] = tga_palette[pal_idx + j]; + } + } else { + // read in the data raw + for (j = 0; j * 8 < tga_bits_per_pixel; ++j) { + raw_data[j] = stbi__get8 (s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp + j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if (tga_inverted) { + for (j = 0; j * 2 < tga_height; ++j) { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if (tga_palette) { + STBI_FREE (tga_palette); + } + } + + // swap RGB + if (tga_comp >= 3) { + unsigned char* tga_pixel = tga_data; + for (i = 0; i < tga_width * tga_height; ++i) { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format (tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test (stbi__context *s) { + int r = (stbi__get32be (s) == 0x38425053); + stbi__rewind (s); + return r; +} + +static stbi_uc *stbi__psd_load (stbi__context *s, int *x, int *y, int *comp, int req_comp) { + int pixelCount; + int channelCount, compression; + int channel, i, count, len; + int bitdepth; + int w, h; + stbi_uc *out; + + // Check identifier + if (stbi__get32be (s) != 0x38425053) // "8BPS" + return stbi__errpuc ("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be (s) != 1) + return stbi__errpuc ("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip (s, 6); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be (s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc ("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be (s); + w = stbi__get32be (s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be (s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc ("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be (s) != 3) + return stbi__errpuc ("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip (s, stbi__get32be (s)); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip (s, stbi__get32be (s)); + + // Skip the reserved data. + stbi__skip (s, stbi__get32be (s)); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be (s); + if (compression > 1) + return stbi__errpuc ("bad compression", "PSD has an unknown compression format"); + + // Create the destination image. + out = (stbi_uc *) stbi__malloc (4 * w*h); + if (!out) return stbi__errpuc ("outofmem", "Out of memory"); + pixelCount = w * h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip (s, h * channelCount * 2); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out + channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + count = 0; + while (count < pixelCount) { + len = stbi__get8 (s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + count += len; + while (len) { + *p = stbi__get8 (s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + val = stbi__get8 (s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out + channel; + if (channel >= channelCount) { + // Fill this channel with default data. + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } else { + // Read the data. + if (bitdepth == 16) { + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be (s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8 (s); + } + } + } + } + + if (req_comp && req_comp != 4) { + out = stbi__convert_format (out, 4, req_comp, w, h); + if (!out) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4 (stbi__context *s, const char *str) { + int i; + for (i = 0; i < 4; ++i) + if (stbi__get8 (s) != (stbi_uc) str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core (stbi__context *s) { + int i; + + if (!stbi__pic_is4 (s, "\x53\x80\xF6\x34")) + return 0; + + for (i = 0; i < 84; ++i) + stbi__get8 (s); + + if (!stbi__pic_is4 (s, "PICT")) + return 0; + + return 1; +} + +typedef struct { + stbi_uc size, type, channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval (stbi__context *s, int channel, stbi_uc *dest) { + int mask = 0x80, i; + + for (i = 0; i < 4; ++i, mask >>= 1) { + if (channel & mask) { + if (stbi__at_eof (s)) return stbi__errpuc ("bad file", "PIC file too short"); + dest[i] = stbi__get8 (s); + } + } + + return dest; +} + +static void stbi__copyval (int channel, stbi_uc *dest, const stbi_uc *src) { + int mask = 0x80, i; + + for (i = 0; i < 4; ++i, mask >>= 1) + if (channel&mask) + dest[i] = src[i]; +} + +static stbi_uc *stbi__pic_load_core (stbi__context *s, int width, int height, int *comp, stbi_uc *result) { + int act_comp = 0, num_packets = 0, y, chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets == sizeof (packets) / sizeof (packets[0])) + return stbi__errpuc ("bad format", "too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8 (s); + packet->size = stbi__get8 (s); + packet->type = stbi__get8 (s); + packet->channel = stbi__get8 (s); + + act_comp |= packet->channel; + + if (stbi__at_eof (s)) return stbi__errpuc ("bad file", "file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc ("bad format", "packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for (y = 0; y < height; ++y) { + int packet_idx; + + for (packet_idx = 0; packet_idx < num_packets; ++packet_idx) { + stbi__pic_packet *packet = &packets[packet_idx]; + stbi_uc *dest = result + y * width * 4; + + switch (packet->type) { + default: + return stbi__errpuc ("bad format", "packet has bad compression type"); + + case 0: + {//uncompressed + int x; + + for (x = 0; x < width; ++x, dest += 4) + if (!stbi__readval (s, packet->channel, dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left = width, i; + + while (left > 0) { + stbi_uc count, value[4]; + + count = stbi__get8 (s); + if (stbi__at_eof (s)) return stbi__errpuc ("bad file", "file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval (s, packet->channel, value)) return 0; + + for (i = 0; i < count; ++i, dest += 4) + stbi__copyval (packet->channel, dest, value); + left -= count; + } + } + break; + + case 2: + {//Mixed RLE + int left = width; + while (left > 0) { + int count = stbi__get8 (s), i; + if (stbi__at_eof (s)) return stbi__errpuc ("bad file", "file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count == 128) + count = stbi__get16be (s); + else + count -= 127; + if (count > left) + return stbi__errpuc ("bad file", "scanline overrun"); + + if (!stbi__readval (s, packet->channel, value)) + return 0; + + for (i = 0; i < count; ++i, dest += 4) + stbi__copyval (packet->channel, dest, value); + } else { // Raw + ++count; + if (count > left) return stbi__errpuc ("bad file", "scanline overrun"); + + for (i = 0; i < count; ++i, dest += 4) + if (!stbi__readval (s, packet->channel, dest)) + return 0; + } + left -= count; + } + break; + } + } + } + } + + return result; +} + +static stbi_uc *stbi__pic_load (stbi__context *s, int *px, int *py, int *comp, int req_comp) { + stbi_uc *result; + int i, x, y; + + for (i = 0; i < 92; ++i) + stbi__get8 (s); + + x = stbi__get16be (s); + y = stbi__get16be (s); + if (stbi__at_eof (s)) return stbi__errpuc ("bad file", "file too short (pic header)"); + if ((1 << 28) / x < y) return stbi__errpuc ("too large", "Image too large to decode"); + + stbi__get32be (s); //skip `ratio' + stbi__get16be (s); //skip `fields' + stbi__get16be (s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc (x*y * 4); + memset (result, 0xff, x*y * 4); + + if (!stbi__pic_load_core (s, x, y, comp, result)) { + STBI_FREE (result); + result = 0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result = stbi__convert_format (result, 4, req_comp, x, y); + + return result; +} + +static int stbi__pic_test (stbi__context *s) { + int r = stbi__pic_test_core (s); + stbi__rewind (s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct { + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct { + int w, h; + stbi_uc *out, *old_out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags, delay; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[4096]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; +} stbi__gif; + +static int stbi__gif_test_raw (stbi__context *s) { + int sz; + if (stbi__get8 (s) != 'G' || stbi__get8 (s) != 'I' || stbi__get8 (s) != 'F' || stbi__get8 (s) != '8') return 0; + sz = stbi__get8 (s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8 (s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test (stbi__context *s) { + int r = stbi__gif_test_raw (s); + stbi__rewind (s); + return r; +} + +static void stbi__gif_parse_colortable (stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) { + int i; + for (i = 0; i < num_entries; ++i) { + pal[i][2] = stbi__get8 (s); + pal[i][1] = stbi__get8 (s); + pal[i][0] = stbi__get8 (s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header (stbi__context *s, stbi__gif *g, int *comp, int is_info) { + stbi_uc version; + if (stbi__get8 (s) != 'G' || stbi__get8 (s) != 'I' || stbi__get8 (s) != 'F' || stbi__get8 (s) != '8') + return stbi__err ("not GIF", "Corrupt GIF"); + + version = stbi__get8 (s); + if (version != '7' && version != '9') return stbi__err ("not GIF", "Corrupt GIF"); + if (stbi__get8 (s) != 'a') return stbi__err ("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le (s); + g->h = stbi__get16le (s); + g->flags = stbi__get8 (s); + g->bgindex = stbi__get8 (s); + g->ratio = stbi__get8 (s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable (s, g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw (stbi__context *s, int *x, int *y, int *comp) { + stbi__gif g; + if (!stbi__gif_header (s, &g, comp, 1)) { + stbi__rewind (s); + return 0; + } + if (x) *x = g.w; + if (y) *y = g.h; + return 1; +} + +static void stbi__out_gif_code (stbi__gif *g, stbi__uint16 code) { + stbi_uc *p, *c; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code (g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + p = &g->out[g->cur_x + g->cur_y]; + c = &g->color_table[g->codes[code].suffix * 4]; + + if (c[3] >= 128) { + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster (stbi__context *s, stbi__gif *g) { + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8 (s); + if (lzw_cs > 12) return nullptr; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear + 2; + oldcode = -1; + + len = 0; + for (;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8 (s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8 (s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip (s, len); + while ((len = stbi__get8 (s)) > 0) + stbi__skip (s, len); + return g->out; + } else if (code <= avail) { + if (first) return stbi__errpuc ("no clear code", "Corrupt GIF"); + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 4096) return stbi__errpuc ("too many codes", "Corrupt GIF"); + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc ("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code (g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc ("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +static void stbi__fill_gif_background (stbi__gif *g, int x0, int y0, int x1, int y1) { + int x, y; + stbi_uc *c = g->pal[g->bgindex]; + for (y = y0; y < y1; y += 4 * g->w) { + for (x = x0; x < x1; x += 4) { + stbi_uc *p = &g->out[y + x]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = 0; + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +static stbi_uc *stbi__gif_load_next (stbi__context *s, stbi__gif *g, int *comp, int req_comp) { + int i; + stbi_uc *prev_out = 0; + + if (g->out == 0 && !stbi__gif_header (s, g, comp, 0)) + return 0; // stbi__g_failure_reason set by stbi__gif_header + + prev_out = g->out; + g->out = (stbi_uc *) stbi__malloc (4 * g->w * g->h); + if (g->out == 0) return stbi__errpuc ("outofmem", "Out of memory"); + + switch ((g->eflags & 0x1C) >> 2) { + case 0: // unspecified (also always used on 1st frame) + stbi__fill_gif_background (g, 0, 0, 4 * g->w, 4 * g->w * g->h); + break; + case 1: // do not dispose + if (prev_out) memcpy (g->out, prev_out, 4 * g->w * g->h); + g->old_out = prev_out; + break; + case 2: // dispose to background + if (prev_out) memcpy (g->out, prev_out, 4 * g->w * g->h); + stbi__fill_gif_background (g, g->start_x, g->start_y, g->max_x, g->max_y); + break; + case 3: // dispose to previous + if (g->old_out) { + for (i = g->start_y; i < g->max_y; i += 4 * g->w) + memcpy (&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); + } + break; + } + + for (;;) { + switch (stbi__get8 (s)) { + case 0x2C: /* Image Descriptor */ + { + int prev_trans = -1; + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le (s); + y = stbi__get16le (s); + w = stbi__get16le (s); + h = stbi__get16le (s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc ("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = stbi__get8 (s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable (s, g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + if (g->transparent >= 0 && (g->eflags & 0x01)) { + prev_trans = g->pal[g->transparent][3]; + g->pal[g->transparent][3] = 0; + } + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc ("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster (s, g); + if (!o) return nullptr; + + if (prev_trans != -1) + g->pal[g->transparent][3] = (stbi_uc) prev_trans; + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + if (stbi__get8 (s) == 0xF9) { // Graphic Control Extension. + len = stbi__get8 (s); + if (len == 4) { + g->eflags = stbi__get8 (s); + g->delay = stbi__get16le (s); + g->transparent = stbi__get8 (s); + } else { + stbi__skip (s, len); + break; + } + } + while ((len = stbi__get8 (s)) != 0) + stbi__skip (s, len); + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc ("unknown code", "Corrupt GIF"); + } + } + + STBI_NOTUSED (req_comp); +} + +static stbi_uc *stbi__gif_load (stbi__context *s, int *x, int *y, int *comp, int req_comp) { + stbi_uc *u = 0; + stbi__gif g; + memset (&g, 0, sizeof (g)); + + u = stbi__gif_load_next (s, &g, comp, req_comp); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + if (req_comp && req_comp != 4) + u = stbi__convert_format (u, 4, req_comp, g.w, g.h); + } else if (g.out) + STBI_FREE (g.out); + + return u; +} + +static int stbi__gif_info (stbi__context *s, int *x, int *y, int *comp) { + return stbi__gif_info_raw (s, x, y, comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core (stbi__context *s) { + const char *signature = "#?RADIANCE\n"; + int i; + for (i = 0; signature[i]; ++i) + if (stbi__get8 (s) != signature[i]) + return 0; + return 1; +} + +static int stbi__hdr_test (stbi__context* s) { + int r = stbi__hdr_test_core (s); + stbi__rewind (s); + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken (stbi__context *z, char *buffer) { + int len = 0; + char c = '\0'; + + c = (char) stbi__get8 (z); + + while (!stbi__at_eof (z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN - 1) { + // flush to end of line + while (!stbi__at_eof (z) && stbi__get8 (z) != '\n') + ; + break; + } + c = (char) stbi__get8 (z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert (float *output, stbi_uc *input, int req_comp) { + if (input[3] != 0) { + float f1; + // Exponent + f1 = (float) ldexp (1.0f, input[3] - (int) (128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load (stbi__context *s, int *x, int *y, int *comp, int req_comp) { + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1, c2, z; + + + // Check identifier + if (strcmp (stbi__hdr_gettoken (s, buffer), "#?RADIANCE") != 0) + return stbi__errpf ("not HDR", "Corrupt HDR image"); + + // Parse header + for (;;) { + token = stbi__hdr_gettoken (s, buffer); + if (token[0] == 0) break; + if (strcmp (token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf ("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken (s, buffer); + if (strncmp (token, "-Y ", 3)) return stbi__errpf ("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol (token, &token, 10); + while (*token == ' ') ++token; + if (strncmp (token, "+X ", 3)) return stbi__errpf ("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol (token, nullptr, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + // Read data + hdr_data = (float *) stbi__malloc (height * width * req_comp * sizeof (float)); + + // Load image data + // image data is stored as some number of sca + if (width < 8 || width >= 32768) { + // Read flat data + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn (s, rgbe, 4); + stbi__hdr_convert (hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = nullptr; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8 (s); + c2 = stbi__get8 (s); + len = stbi__get8 (s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8 (s); + stbi__hdr_convert (hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE (scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8 (s); + if (len != width) { + STBI_FREE (hdr_data); STBI_FREE (scanline); return stbi__errpf ("invalid decoded scanline length", "corrupt HDR"); + } + if (!scanline) scanline = (stbi_uc *) stbi__malloc (width * 4); + + for (k = 0; k < 4; ++k) { + i = 0; + while (i < width) { + count = stbi__get8 (s); + if (count > 128) { + // Run + value = stbi__get8 (s); + count -= 128; + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8 (s); + } + } + } + for (i = 0; i < width; ++i) + stbi__hdr_convert (hdr_data + (j*width + i)*req_comp, scanline + i * 4, req_comp); + } + STBI_FREE (scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info (stbi__context *s, int *x, int *y, int *comp) { + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + + if (strcmp (stbi__hdr_gettoken (s, buffer), "#?RADIANCE") != 0) { + stbi__rewind (s); + return 0; + } + + for (;;) { + token = stbi__hdr_gettoken (s, buffer); + if (token[0] == 0) break; + if (strcmp (token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind (s); + return 0; + } + token = stbi__hdr_gettoken (s, buffer); + if (strncmp (token, "-Y ", 3)) { + stbi__rewind (s); + return 0; + } + token += 3; + *y = (int) strtol (token, &token, 10); + while (*token == ' ') ++token; + if (strncmp (token, "+X ", 3)) { + stbi__rewind (s); + return 0; + } + token += 3; + *x = (int) strtol (token, nullptr, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info (stbi__context *s, int *x, int *y, int *comp) { + int hsz; + if (stbi__get8 (s) != 'B' || stbi__get8 (s) != 'M') { + stbi__rewind (s); + return 0; + } + stbi__skip (s, 12); + hsz = stbi__get32le (s); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) { + stbi__rewind (s); + return 0; + } + if (hsz == 12) { + *x = stbi__get16le (s); + *y = stbi__get16le (s); + } else { + *x = stbi__get32le (s); + *y = stbi__get32le (s); + } + if (stbi__get16le (s) != 1) { + stbi__rewind (s); + return 0; + } + *comp = stbi__get16le (s) / 8; + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info (stbi__context *s, int *x, int *y, int *comp) { + int channelCount; + if (stbi__get32be (s) != 0x38425053) { + stbi__rewind (s); + return 0; + } + if (stbi__get16be (s) != 1) { + stbi__rewind (s); + return 0; + } + stbi__skip (s, 6); + channelCount = stbi__get16be (s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind (s); + return 0; + } + *y = stbi__get32be (s); + *x = stbi__get32be (s); + if (stbi__get16be (s) != 8) { + stbi__rewind (s); + return 0; + } + if (stbi__get16be (s) != 3) { + stbi__rewind (s); + return 0; + } + *comp = 4; + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info (stbi__context *s, int *x, int *y, int *comp) { + int act_comp = 0, num_packets = 0, chained; + stbi__pic_packet packets[10]; + + if (!stbi__pic_is4 (s, "\x53\x80\xF6\x34")) { + stbi__rewind (s); + return 0; + } + + stbi__skip (s, 88); + + *x = stbi__get16be (s); + *y = stbi__get16be (s); + if (stbi__at_eof (s)) { + stbi__rewind (s); + return 0; + } + if ((*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind (s); + return 0; + } + + stbi__skip (s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets == sizeof (packets) / sizeof (packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8 (s); + packet->size = stbi__get8 (s); + packet->type = stbi__get8 (s); + packet->channel = stbi__get8 (s); + act_comp |= packet->channel; + + if (stbi__at_eof (s)) { + stbi__rewind (s); + return 0; + } + if (packet->size != 8) { + stbi__rewind (s); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test (stbi__context *s) { + char p, t; + p = (char) stbi__get8 (s); + t = (char) stbi__get8 (s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind (s); + return 0; + } + return 1; +} + +static stbi_uc *stbi__pnm_load (stbi__context *s, int *x, int *y, int *comp, int req_comp) { + stbi_uc *out; + if (!stbi__pnm_info (s, (int *) &s->img_x, (int *) &s->img_y, (int *) &s->img_n)) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = s->img_n; + + out = (stbi_uc *) stbi__malloc (s->img_n * s->img_x * s->img_y); + if (!out) return stbi__errpuc ("outofmem", "Out of memory"); + stbi__getn (s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format (out, s->img_n, req_comp, s->img_x, s->img_y); + if (!out) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace (char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace (stbi__context *s, char *c) { + while (!stbi__at_eof (s) && stbi__pnm_isspace (*c)) + *c = (char) stbi__get8 (s); +} + +static int stbi__pnm_isdigit (char c) { + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger (stbi__context *s, char *c) { + int value = 0; + + while (!stbi__at_eof (s) && stbi__pnm_isdigit (*c)) { + value = value * 10 + (*c - '0'); + *c = (char) stbi__get8 (s); + } + + return value; +} + +static int stbi__pnm_info (stbi__context *s, int *x, int *y, int *comp) { + int maxv; + char c, p, t; + + stbi__rewind (s); + + // Get identifier + p = (char) stbi__get8 (s); + t = (char) stbi__get8 (s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind (s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8 (s); + stbi__pnm_skip_whitespace (s, &c); + + *x = stbi__pnm_getinteger (s, &c); // read width + stbi__pnm_skip_whitespace (s, &c); + + *y = stbi__pnm_getinteger (s, &c); // read height + stbi__pnm_skip_whitespace (s, &c); + + maxv = stbi__pnm_getinteger (s, &c); // read max value + + if (maxv > 255) + return stbi__err ("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main (stbi__context *s, int *x, int *y, int *comp) { +#ifndef STBI_NO_JPEG + if (stbi__jpeg_info (s, x, y, comp)) return 1; +#endif + +#ifndef STBI_NO_PNG + if (stbi__png_info (s, x, y, comp)) return 1; +#endif + +#ifndef STBI_NO_GIF + if (stbi__gif_info (s, x, y, comp)) return 1; +#endif + +#ifndef STBI_NO_BMP + if (stbi__bmp_info (s, x, y, comp)) return 1; +#endif + +#ifndef STBI_NO_PSD + if (stbi__psd_info (s, x, y, comp)) return 1; +#endif + +#ifndef STBI_NO_PIC + if (stbi__pic_info (s, x, y, comp)) return 1; +#endif + +#ifndef STBI_NO_PNM + if (stbi__pnm_info (s, x, y, comp)) return 1; +#endif + +#ifndef STBI_NO_HDR + if (stbi__hdr_info (s, x, y, comp)) return 1; +#endif + + // test tga last because it's a crappy test! +#ifndef STBI_NO_TGA + if (stbi__tga_info (s, x, y, comp)) + return 1; +#endif + return stbi__err ("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp) { + FILE *f = stbi__fopen (filename, "rb"); + int result; + if (!f) return stbi__err ("can't fopen", "Unable to open file"); + result = stbi_info_from_file (f, x, y, comp); + fclose (f); + return result; +} + +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp) { + int r; + stbi__context s; + long pos = ftell (f); + stbi__start_file (&s, f); + r = stbi__info_main (&s, x, y, comp); + fseek (f, pos, SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp) { + stbi__context s; + stbi__start_mem (&s, buffer, len); + return stbi__info_main (&s, x, y, comp); +} + +STBIDEF int stbi_info_from_callbacks (stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) { + stbi__context s; + stbi__start_callbacks (&s, (stbi_io_callbacks *) c, user); + return stbi__info_main (&s, x, y, comp); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bit PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning nullptr + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow nullptr for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ diff --git a/DuiLib/Utils/unzip.cpp b/DuiLib/Utils/unzip.cpp new file mode 100644 index 0000000..3573124 --- /dev/null +++ b/DuiLib/Utils/unzip.cpp @@ -0,0 +1,4142 @@ +#include +#include +#include +#include +#include +#include "unzip.h" + +#pragma warning(disable : 4996) // disable bogus deprecation warning + +// THIS FILE is almost entirely based upon code by Jean-loup Gailly +// and Mark Adler. It has been modified by Lucian Wischik. +// The modifications were: incorporate the bugfixes of 1.1.4, allow +// unzipping to/from handles/pipes/files/memory, encryption, unicode, +// a windowsish api, and putting everything into a single .cpp file. +// The original code may be found at http://www.gzip.org/zlib/ +// The original copyright text follows. +// +// +// +// zlib.h -- interface of the 'zlib' general purpose compression library +// version 1.1.3, July 9th, 1998 +// +// Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler +// +// 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. +// +// Jean-loup Gailly Mark Adler +// jloup@gzip.org madler@alumni.caltech.edu +// +// +// The data format used by the zlib library is described by RFCs (Request for +// Comments) 1950 to 1952 in the files ftp://ds.internic.net/rfc/rfc1950.txt +// (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +// +// +// The 'zlib' compression library provides in-memory compression and +// decompression functions, including integrity checks of the uncompressed +// data. This version of the library supports only one compression method +// (deflation) but other algorithms will be added later and will have the same +// stream interface. +// +// Compression can be done in a single step if the buffers are large +// enough (for example if an input file is mmap'ed), or can be done by +// repeated calls of the compression function. In the latter case, the +// application must provide more input and/or consume the output +// (providing more output space) before each call. +// +// The library also supports reading and writing files in gzip (.gz) format +// with an interface similar to that of stdio. +// +// The library does not install any signal handler. The decoder checks +// the consistency of the compressed data, so the library should never +// crash even in case of corrupted input. +// +// for more info about .ZIP format, see ftp://ftp.cdrom.com/pub/infozip/doc/appnote-970311-iz.zip +// PkWare has also a specification at ftp://ftp.pkware.com/probdesc.zip + +#define ZIP_HANDLE 1 +#define ZIP_FILENAME 2 +#define ZIP_MEMORY 3 + + +#define zmalloc(len) malloc(len) + +#define zfree(p) free(p) + +/* +void *zmalloc(unsigned int len) +{ char *buf = new char[len+32]; + for (int i=0; i<16; i++) + { buf[i]=i; + buf[len+31-i]=i; + } + *((unsigned int*)buf) = len; + char c[1000]; wsprintf(c,"malloc 0x%lx - %lu",buf+16,len); + OutputDebugString(c); + return buf+16; +} + +void zfree(void *buf) +{ char c[1000]; wsprintf(c,"free 0x%lx",buf); + OutputDebugString(c); + char *p = ((char*)buf)-16; + unsigned int len = *((unsigned int*)p); + bool blown=false; + for (int i=0; i<16; i++) + { char lo = p[i]; + char hi = p[len+31-i]; + if (hi!=i || (lo!=i && i>4)) blown=true; + } + if (blown) + { OutputDebugString("BLOWN!!!"); + } + delete[] p; +} +*/ + + +typedef struct tm_unz_s { + unsigned int tm_sec; // seconds after the minute - [0,59] + unsigned int tm_min; // minutes after the hour - [0,59] + unsigned int tm_hour; // hours since midnight - [0,23] + unsigned int tm_mday; // day of the month - [1,31] + unsigned int tm_mon; // months since January - [0,11] + unsigned int tm_year; // years - [1980..2044] +} tm_unz; + + +// unz_global_info structure contain global data about the ZIPfile +typedef struct unz_global_info_s { + unsigned long number_entry; // total number of entries in the central dir on this disk + unsigned long size_comment; // size of the global comment of the zipfile +} unz_global_info; + +// unz_file_info contain information about a file in the zipfile +typedef struct unz_file_info_s { + unsigned long version; // version made by 2 bytes + unsigned long version_needed; // version needed to extract 2 bytes + unsigned long flag; // general purpose bit flag 2 bytes + unsigned long compression_method; // compression method 2 bytes + unsigned long dosDate; // last mod file date in Dos fmt 4 bytes + unsigned long crc; // crc-32 4 bytes + unsigned long compressed_size; // compressed size 4 bytes + unsigned long uncompressed_size; // uncompressed size 4 bytes + unsigned long size_filename; // filename length 2 bytes + unsigned long size_file_extra; // extra field length 2 bytes + unsigned long size_file_comment; // file comment length 2 bytes + unsigned long disk_num_start; // disk number start 2 bytes + unsigned long internal_fa; // internal file attributes 2 bytes + unsigned long external_fa; // external file attributes 4 bytes + tm_unz tmu_date; +} unz_file_info; + + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) +#define UNZ_PASSWORD (-106) + + + + + + + +#define ZLIB_VERSION "1.1.3" + + +// Allowed flush values; see deflate() for details +#define Z_NO_FLUSH 0 +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 + + +// compression levels +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) + +// compression strategy; see deflateInit2() for details +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_DEFAULT_STRATEGY 0 + +// Possible values of the data_type field +#define Z_BINARY 0 +#define Z_ASCII 1 +#define Z_UNKNOWN 2 + +// The deflate compression method (the only one supported in this version) +#define Z_DEFLATED 8 + +// for initializing zalloc, zfree, opaque +#define Z_nullptr 0 + +// case sensitivity when searching for filenames +#define CASE_SENSITIVE 1 +#define CASE_INSENSITIVE 2 + + +// Return codes for the compression/decompression functions. Negative +// values are errors, positive values are used for special but normal events. +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) + + + +// Basic data types +typedef unsigned char Byte; // 8 bits +typedef unsigned int uInt; // 16 bits or more +typedef unsigned long uLong; // 32 bits or more +typedef void *voidpf; +typedef void *voidp; +typedef long z_off_t; + + + + + + + + + + + + +typedef voidpf (*alloc_func) (voidpf opaque, uInt items, uInt size); +typedef void (*free_func) (voidpf opaque, voidpf address); + +struct internal_state; + +typedef struct z_stream_s { + Byte *next_in; // next input byte + uInt avail_in; // number of bytes available at next_in + uLong total_in; // total nb of input bytes read so far + + Byte *next_out; // next output byte should be put there + uInt avail_out; // remaining free space at next_out + uLong total_out; // total nb of bytes output so far + + char *msg; // last error message, nullptr if no error + struct internal_state *state; // not visible by applications + + alloc_func zalloc; // used to allocate the internal state + free_func zfree; // used to free the internal state + voidpf opaque; // private data object passed to zalloc and zfree + + int data_type; // best guess about the data type: ascii or binary + uLong adler; // adler32 value of the uncompressed data + uLong reserved; // reserved for future use +} z_stream; + +typedef z_stream *z_streamp; + + +// The application must update next_in and avail_in when avail_in has +// dropped to zero. It must update next_out and avail_out when avail_out +// has dropped to zero. The application must initialize zalloc, zfree and +// opaque before calling the init function. All other fields are set by the +// compression library and must not be updated by the application. +// +// The opaque value provided by the application will be passed as the first +// parameter for calls of zalloc and zfree. This can be useful for custom +// memory management. The compression library attaches no meaning to the +// opaque value. +// +// zalloc must return Z_nullptr if there is not enough memory for the object. +// If zlib is used in a multi-threaded application, zalloc and zfree must be +// thread safe. +// +// The fields total_in and total_out can be used for statistics or +// progress reports. After compression, total_in holds the total size of +// the uncompressed data and may be saved for use in the decompressor +// (particularly if the decompressor wants to decompress everything in +// a single step). +// + + +// basic functions + +const char *zlibVersion (); +// The application can compare zlibVersion and ZLIB_VERSION for consistency. +// If the first character differs, the library code actually used is +// not compatible with the zlib.h header file used by the application. +// This check is automatically made by inflateInit. + + + + + + +int inflate (z_streamp strm, int flush); +// +// inflate decompresses as much data as possible, and stops when the input +// buffer becomes empty or the output buffer becomes full. It may some +// introduce some output latency (reading input without producing any output) +// except when forced to flush. +// +// The detailed semantics are as follows. inflate performs one or both of the +// following actions: +// +// - Decompress more input starting at next_in and update next_in and avail_in +// accordingly. If not all input can be processed (because there is not +// enough room in the output buffer), next_in is updated and processing +// will resume at this point for the next call of inflate(). +// +// - Provide more output starting at next_out and update next_out and avail_out +// accordingly. inflate() provides as much output as possible, until there +// is no more input data or no more space in the output buffer (see below +// about the flush parameter). +// +// Before the call of inflate(), the application should ensure that at least +// one of the actions is possible, by providing more input and/or consuming +// more output, and updating the next_* and avail_* values accordingly. +// The application can consume the uncompressed output when it wants, for +// example when the output buffer is full (avail_out == 0), or after each +// call of inflate(). If inflate returns Z_OK and with zero avail_out, it +// must be called again after making room in the output buffer because there +// might be more output pending. +// +// If the parameter flush is set to Z_SYNC_FLUSH, inflate flushes as much +// output as possible to the output buffer. The flushing behavior of inflate is +// not specified for values of the flush parameter other than Z_SYNC_FLUSH +// and Z_FINISH, but the current implementation actually flushes as much output +// as possible anyway. +// +// inflate() should normally be called until it returns Z_STREAM_END or an +// error. However if all decompression is to be performed in a single step +// (a single call of inflate), the parameter flush should be set to +// Z_FINISH. In this case all pending input is processed and all pending +// output is flushed; avail_out must be large enough to hold all the +// uncompressed data. (The size of the uncompressed data may have been saved +// by the compressor for this purpose.) The next operation on this stream must +// be inflateEnd to deallocate the decompression state. The use of Z_FINISH +// is never required, but can be used to inform inflate that a faster routine +// may be used for the single inflate() call. +// +// If a preset dictionary is needed at this point (see inflateSetDictionary +// below), inflate sets strm-adler to the adler32 checksum of the +// dictionary chosen by the compressor and returns Z_NEED_DICT; otherwise +// it sets strm->adler to the adler32 checksum of all output produced +// so far (that is, total_out bytes) and returns Z_OK, Z_STREAM_END or +// an error code as described below. At the end of the stream, inflate() +// checks that its computed adler32 checksum is equal to that saved by the +// compressor and returns Z_STREAM_END only if the checksum is correct. +// +// inflate() returns Z_OK if some progress has been made (more input processed +// or more output produced), Z_STREAM_END if the end of the compressed data has +// been reached and all uncompressed output has been produced, Z_NEED_DICT if a +// preset dictionary is needed at this point, Z_DATA_ERROR if the input data was +// corrupted (input stream not conforming to the zlib format or incorrect +// adler32 checksum), Z_STREAM_ERROR if the stream structure was inconsistent +// (for example if next_in or next_out was nullptr), Z_MEM_ERROR if there was not +// enough memory, Z_BUF_ERROR if no progress is possible or if there was not +// enough room in the output buffer when Z_FINISH is used. In the Z_DATA_ERROR +// case, the application may then call inflateSync to look for a good +// compression block. +// + + +int inflateEnd (z_streamp strm); +// +// All dynamically allocated data structures for this stream are freed. +// This function discards any unprocessed input and does not flush any +// pending output. +// +// inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state +// was inconsistent. In the error case, msg may be set but then points to a +// static string (which must not be deallocated). + + // Advanced functions + +// The following functions are needed only in some special applications. + + + + + +int inflateSetDictionary (z_streamp strm, + const Byte *dictionary, + uInt dictLength); +// +// Initializes the decompression dictionary from the given uncompressed byte +// sequence. This function must be called immediately after a call of inflate +// if this call returned Z_NEED_DICT. The dictionary chosen by the compressor +// can be determined from the Adler32 value returned by this call of +// inflate. The compressor and decompressor must use exactly the same +// dictionary. +// +// inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a +// parameter is invalid (such as nullptr dictionary) or the stream state is +// inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the +// expected one (incorrect Adler32 value). inflateSetDictionary does not +// perform any decompression: this will be done by subsequent calls of +// inflate(). + + +int inflateSync (z_streamp strm); +// +// Skips invalid compressed data until a full flush point can be found, or until all +// available input is skipped. No output is provided. +// +// inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR +// if no more input was provided, Z_DATA_ERROR if no flush point has been found, +// or Z_STREAM_ERROR if the stream structure was inconsistent. In the success +// case, the application may save the current current value of total_in which +// indicates where valid compressed data was found. In the error case, the +// application may repeatedly call inflateSync, providing more input each time, +// until success or end of the input data. + + +int inflateReset (z_streamp strm); +// This function is equivalent to inflateEnd followed by inflateInit, +// but does not free and reallocate all the internal decompression state. +// The stream will keep attributes that may have been set by inflateInit2. +// +// inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source +// stream state was inconsistent (such as zalloc or state being nullptr). +// + + + +// checksum functions +// These functions are not related to compression but are exported +// anyway because they might be useful in applications using the +// compression library. + +uLong adler32 (uLong adler, const Byte *buf, uInt len); +// Update a running Adler-32 checksum with the bytes buf[0..len-1] and +// return the updated checksum. If buf is nullptr, this function returns +// the required initial value for the checksum. +// An Adler-32 checksum is almost as reliable as a CRC32 but can be computed +// much faster. Usage example: +// +// uLong adler = adler32(0L, Z_nullptr, 0); +// +// while (read_buffer(buffer, length) != EOF) { +// adler = adler32(adler, buffer, length); +// } +// if (adler != original_adler) error(); + +uLong ucrc32 (uLong crc, const Byte *buf, uInt len); +// Update a running crc with the bytes buf[0..len-1] and return the updated +// crc. If buf is nullptr, this function returns the required initial value +// for the crc. Pre- and post-conditioning (one's complement) is performed +// within this function so it shouldn't be done by the application. +// Usage example: +// +// uLong crc = crc32(0L, Z_nullptr, 0); +// +// while (read_buffer(buffer, length) != EOF) { +// crc = crc32(crc, buffer, length); +// } +// if (crc != original_crc) error(); + + + + +const char *zError (int err); +int inflateSyncPoint (z_streamp z); +const uLong *get_crc_table (void); + + + +typedef unsigned char uch; +typedef uch uchf; +typedef unsigned short ush; +typedef ush ushf; +typedef unsigned long ulg; + + + +const char * const z_errmsg[10] = { // indexed by 2-zlib_error + "need dictionary", // Z_NEED_DICT 2 + "stream end", // Z_STREAM_END 1 + "", // Z_OK 0 + "file error", // Z_ERRNO (-1) + "stream error", // Z_STREAM_ERROR (-2) + "data error", // Z_DATA_ERROR (-3) + "insufficient memory", // Z_MEM_ERROR (-4) + "buffer error", // Z_BUF_ERROR (-5) + "incompatible version",// Z_VERSION_ERROR (-6) + "" }; + + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = (char*)ERR_MSG(err), (err)) +// To be used only when the state is known to be valid + + // common constants + + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +// The three kinds of block type + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +// The minimum and maximum match lengths + +#define PRESET_DICT 0x20 // preset dictionary flag in zlib header + + // target dependencies + +#define OS_CODE 0x0b // Window 95 & Windows NT + + + + // functions + +#define zmemzero(dest, len) memset(dest, 0, len) + +// Diagnostic functions +#define LuAssert(cond,msg) +#define LuTrace(x) +#define LuTracev(x) +#define LuTracevv(x) +#define LuTracec(c,x) +#define LuTracecv(c,x) + + +typedef uLong (*check_func) (uLong check, const Byte *buf, uInt len); +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size); +void zcfree (voidpf opaque, voidpf ptr); + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) + +//void ZFREE(z_streamp strm,voidpf addr) +//{ *((strm)->zfree))((strm)->opaque, addr); +//} + +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + + + + +// Huffman code lookup table entry--this entry is four bytes for machines +// that have 16-bit pointers (e.g. PC's in the small or medium model). + + +typedef struct inflate_huft_s inflate_huft; + +struct inflate_huft_s { + union { + struct { + Byte Exop; // number of extra bits or operation + Byte Bits; // number of bits in this code or subcode + } what; + uInt pad; // pad structure to a power of 2 (4 bytes for + } word; // 16-bit, 8 bytes for 32-bit int's) + uInt base; // literal, length base, distance base, or table offset +}; + +// Maximum size of dynamic tree. The maximum found in a long but non- +// exhaustive search was 1004 huft structures (850 for length/literals +// and 154 for distances, the latter actually the result of an +// exhaustive search). The actual maximum is not known, but the +// value below is more than safe. +#define MANY 1440 + +int inflate_trees_bits ( + uInt *, // 19 code lengths + uInt *, // bits tree desired/actual depth + inflate_huft * *, // bits tree result + inflate_huft *, // space for trees + z_streamp); // for messages + +int inflate_trees_dynamic ( + uInt, // number of literal/length codes + uInt, // number of distance codes + uInt *, // that many (total) code lengths + uInt *, // literal desired/actual bit depth + uInt *, // distance desired/actual bit depth + inflate_huft * *, // literal/length tree result + inflate_huft * *, // distance tree result + inflate_huft *, // space for trees + z_streamp); // for messages + +int inflate_trees_fixed ( + uInt *, // literal desired/actual bit depth + uInt *, // distance desired/actual bit depth + const inflate_huft * *, // literal/length tree result + const inflate_huft * *, // distance tree result + z_streamp); // for memory allocation + + + + + +struct inflate_blocks_state; +typedef struct inflate_blocks_state inflate_blocks_statef; + +inflate_blocks_statef * inflate_blocks_new ( + z_streamp z, + check_func c, // check function + uInt w); // window size + +int inflate_blocks ( + inflate_blocks_statef *, + z_streamp, + int); // initial return code + +void inflate_blocks_reset ( + inflate_blocks_statef *, + z_streamp, + uLong *); // check value on output + +int inflate_blocks_free ( + inflate_blocks_statef *, + z_streamp); + +void inflate_set_dictionary ( + inflate_blocks_statef *s, + const Byte *d, // dictionary + uInt n); // dictionary length + +int inflate_blocks_sync_point ( + inflate_blocks_statef *s); + + + + +struct inflate_codes_state; +typedef struct inflate_codes_state inflate_codes_statef; + +inflate_codes_statef *inflate_codes_new ( + uInt, uInt, + const inflate_huft *, const inflate_huft *, + z_streamp); + +int inflate_codes ( + inflate_blocks_statef *, + z_streamp, + int); + +void inflate_codes_free ( + inflate_codes_statef *, + z_streamp); + + + + +typedef enum { + IBM_TYPE, // get type bits (3, including end bit) + IBM_LENS, // get lengths for stored + IBM_STORED, // processing stored block + IBM_TABLE, // get table lengths + IBM_BTREE, // get bit lengths tree for a dynamic block + IBM_DTREE, // get length, distance trees for a dynamic block + IBM_CODES, // processing fixed or dynamic block + IBM_DRY, // output remaining window bytes + IBM_DONE, // finished last block, done + IBM_BAD +} // got a data error--stuck here +inflate_block_mode; + +// inflate blocks semi-private state +struct inflate_blocks_state { + + // mode + inflate_block_mode mode; // current inflate_block mode + + // mode dependent information + union { + uInt left; // if STORED, bytes left to copy + struct { + uInt table; // table lengths (14 bits) + uInt index; // index into blens (or border) + uInt *blens; // bit lengths of codes + uInt bb; // bit length tree depth + inflate_huft *tb; // bit length decoding tree + } trees; // if DTREE, decoding info for trees + struct { + inflate_codes_statef + *codes; + } decode; // if CODES, current state + } sub; // submode + uInt last; // true if this block is the last block + + // mode independent information + uInt bitk; // bits in bit buffer + uLong bitb; // bit buffer + inflate_huft *hufts; // single malloc for tree space + Byte *window; // sliding window + Byte *end; // one byte after sliding window + Byte *read; // window read pointer + Byte *write; // window write pointer + check_func checkfn; // check function + uLong check; // check on output + +}; + + +// defines for inflate input/output +// update pointers and return +#define UPDBITS {s->bitb=b;s->bitk=k;} +#define UPDIN {z->avail_in=n;z->total_in+=(uLong)(p-z->next_in);z->next_in=p;} +#define UPDOUT {s->write=q;} +#define UPDATE {UPDBITS UPDIN UPDOUT} +#define LEAVE {UPDATE return inflate_flush(s,z,r);} +// get bytes and bits +#define LOADIN {p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;} +#define NEEDBYTE {if(n)r=Z_OK;else LEAVE} +#define NEXTBYTE (n--,*p++) +#define NEEDBITS(j) {while(k<(j)){NEEDBYTE;b|=((uLong)NEXTBYTE)<>=(j);k-=(j);} +// output bytes +#define WAVAIL (uInt)(qread?s->read-q-1:s->end-q) +#define LOADOUT {q=s->write;m=(uInt)WAVAIL;m;} +#define WRAP {if(q==s->end&&s->read!=s->window){q=s->window;m=(uInt)WAVAIL;}} +#define FLUSH {UPDOUT r=inflate_flush(s,z,r); LOADOUT} +#define NEEDOUT {if(m==0){WRAP if(m==0){FLUSH WRAP if(m==0) LEAVE}}r=Z_OK;} +#define OUTBYTE(a) {*q++=(Byte)(a);m--;} +// load local pointers +#define LOAD {LOADIN LOADOUT} + +// masks for lower bits (size given to avoid silly warnings with Visual C++) +// And'ing with mask[n] masks the lower n bits +const uInt inflate_mask[17] = { + 0x0000, + 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff +}; + +// copy as much as possible from the sliding window to the output area +int inflate_flush (inflate_blocks_statef *, z_streamp, int); + +int inflate_fast (uInt, uInt, const inflate_huft *, const inflate_huft *, inflate_blocks_statef *, z_streamp); + + + +const uInt fixed_bl = 9; +const uInt fixed_bd = 5; +const inflate_huft fixed_tl[] = { + { { { 96, 7 } }, 256 }, { { { 0, 8 } }, 80 }, { { { 0, 8 } }, 16 }, { { { 84, 8 } }, 115 }, +{ { { 82, 7 } }, 31 }, { { { 0, 8 } }, 112 }, { { { 0, 8 } }, 48 }, { { { 0, 9 } }, 192 }, +{ { { 80, 7 } }, 10 }, { { { 0, 8 } }, 96 }, { { { 0, 8 } }, 32 }, { { { 0, 9 } }, 160 }, +{ { { 0, 8 } }, 0 }, { { { 0, 8 } }, 128 }, { { { 0, 8 } }, 64 }, { { { 0, 9 } }, 224 }, +{ { { 80, 7 } }, 6 }, { { { 0, 8 } }, 88 }, { { { 0, 8 } }, 24 }, { { { 0, 9 } }, 144 }, +{ { { 83, 7 } }, 59 }, { { { 0, 8 } }, 120 }, { { { 0, 8 } }, 56 }, { { { 0, 9 } }, 208 }, +{ { { 81, 7 } }, 17 }, { { { 0, 8 } }, 104 }, { { { 0, 8 } }, 40 }, { { { 0, 9 } }, 176 }, +{ { { 0, 8 } }, 8 }, { { { 0, 8 } }, 136 }, { { { 0, 8 } }, 72 }, { { { 0, 9 } }, 240 }, +{ { { 80, 7 } }, 4 }, { { { 0, 8 } }, 84 }, { { { 0, 8 } }, 20 }, { { { 85, 8 } }, 227 }, +{ { { 83, 7 } }, 43 }, { { { 0, 8 } }, 116 }, { { { 0, 8 } }, 52 }, { { { 0, 9 } }, 200 }, +{ { { 81, 7 } }, 13 }, { { { 0, 8 } }, 100 }, { { { 0, 8 } }, 36 }, { { { 0, 9 } }, 168 }, +{ { { 0, 8 } }, 4 }, { { { 0, 8 } }, 132 }, { { { 0, 8 } }, 68 }, { { { 0, 9 } }, 232 }, +{ { { 80, 7 } }, 8 }, { { { 0, 8 } }, 92 }, { { { 0, 8 } }, 28 }, { { { 0, 9 } }, 152 }, +{ { { 84, 7 } }, 83 }, { { { 0, 8 } }, 124 }, { { { 0, 8 } }, 60 }, { { { 0, 9 } }, 216 }, +{ { { 82, 7 } }, 23 }, { { { 0, 8 } }, 108 }, { { { 0, 8 } }, 44 }, { { { 0, 9 } }, 184 }, +{ { { 0, 8 } }, 12 }, { { { 0, 8 } }, 140 }, { { { 0, 8 } }, 76 }, { { { 0, 9 } }, 248 }, +{ { { 80, 7 } }, 3 }, { { { 0, 8 } }, 82 }, { { { 0, 8 } }, 18 }, { { { 85, 8 } }, 163 }, +{ { { 83, 7 } }, 35 }, { { { 0, 8 } }, 114 }, { { { 0, 8 } }, 50 }, { { { 0, 9 } }, 196 }, +{ { { 81, 7 } }, 11 }, { { { 0, 8 } }, 98 }, { { { 0, 8 } }, 34 }, { { { 0, 9 } }, 164 }, +{ { { 0, 8 } }, 2 }, { { { 0, 8 } }, 130 }, { { { 0, 8 } }, 66 }, { { { 0, 9 } }, 228 }, +{ { { 80, 7 } }, 7 }, { { { 0, 8 } }, 90 }, { { { 0, 8 } }, 26 }, { { { 0, 9 } }, 148 }, +{ { { 84, 7 } }, 67 }, { { { 0, 8 } }, 122 }, { { { 0, 8 } }, 58 }, { { { 0, 9 } }, 212 }, +{ { { 82, 7 } }, 19 }, { { { 0, 8 } }, 106 }, { { { 0, 8 } }, 42 }, { { { 0, 9 } }, 180 }, +{ { { 0, 8 } }, 10 }, { { { 0, 8 } }, 138 }, { { { 0, 8 } }, 74 }, { { { 0, 9 } }, 244 }, +{ { { 80, 7 } }, 5 }, { { { 0, 8 } }, 86 }, { { { 0, 8 } }, 22 }, { { { 192, 8 } }, 0 }, +{ { { 83, 7 } }, 51 }, { { { 0, 8 } }, 118 }, { { { 0, 8 } }, 54 }, { { { 0, 9 } }, 204 }, +{ { { 81, 7 } }, 15 }, { { { 0, 8 } }, 102 }, { { { 0, 8 } }, 38 }, { { { 0, 9 } }, 172 }, +{ { { 0, 8 } }, 6 }, { { { 0, 8 } }, 134 }, { { { 0, 8 } }, 70 }, { { { 0, 9 } }, 236 }, +{ { { 80, 7 } }, 9 }, { { { 0, 8 } }, 94 }, { { { 0, 8 } }, 30 }, { { { 0, 9 } }, 156 }, +{ { { 84, 7 } }, 99 }, { { { 0, 8 } }, 126 }, { { { 0, 8 } }, 62 }, { { { 0, 9 } }, 220 }, +{ { { 82, 7 } }, 27 }, { { { 0, 8 } }, 110 }, { { { 0, 8 } }, 46 }, { { { 0, 9 } }, 188 }, +{ { { 0, 8 } }, 14 }, { { { 0, 8 } }, 142 }, { { { 0, 8 } }, 78 }, { { { 0, 9 } }, 252 }, +{ { { 96, 7 } }, 256 }, { { { 0, 8 } }, 81 }, { { { 0, 8 } }, 17 }, { { { 85, 8 } }, 131 }, +{ { { 82, 7 } }, 31 }, { { { 0, 8 } }, 113 }, { { { 0, 8 } }, 49 }, { { { 0, 9 } }, 194 }, +{ { { 80, 7 } }, 10 }, { { { 0, 8 } }, 97 }, { { { 0, 8 } }, 33 }, { { { 0, 9 } }, 162 }, +{ { { 0, 8 } }, 1 }, { { { 0, 8 } }, 129 }, { { { 0, 8 } }, 65 }, { { { 0, 9 } }, 226 }, +{ { { 80, 7 } }, 6 }, { { { 0, 8 } }, 89 }, { { { 0, 8 } }, 25 }, { { { 0, 9 } }, 146 }, +{ { { 83, 7 } }, 59 }, { { { 0, 8 } }, 121 }, { { { 0, 8 } }, 57 }, { { { 0, 9 } }, 210 }, +{ { { 81, 7 } }, 17 }, { { { 0, 8 } }, 105 }, { { { 0, 8 } }, 41 }, { { { 0, 9 } }, 178 }, +{ { { 0, 8 } }, 9 }, { { { 0, 8 } }, 137 }, { { { 0, 8 } }, 73 }, { { { 0, 9 } }, 242 }, +{ { { 80, 7 } }, 4 }, { { { 0, 8 } }, 85 }, { { { 0, 8 } }, 21 }, { { { 80, 8 } }, 258 }, +{ { { 83, 7 } }, 43 }, { { { 0, 8 } }, 117 }, { { { 0, 8 } }, 53 }, { { { 0, 9 } }, 202 }, +{ { { 81, 7 } }, 13 }, { { { 0, 8 } }, 101 }, { { { 0, 8 } }, 37 }, { { { 0, 9 } }, 170 }, +{ { { 0, 8 } }, 5 }, { { { 0, 8 } }, 133 }, { { { 0, 8 } }, 69 }, { { { 0, 9 } }, 234 }, +{ { { 80, 7 } }, 8 }, { { { 0, 8 } }, 93 }, { { { 0, 8 } }, 29 }, { { { 0, 9 } }, 154 }, +{ { { 84, 7 } }, 83 }, { { { 0, 8 } }, 125 }, { { { 0, 8 } }, 61 }, { { { 0, 9 } }, 218 }, +{ { { 82, 7 } }, 23 }, { { { 0, 8 } }, 109 }, { { { 0, 8 } }, 45 }, { { { 0, 9 } }, 186 }, +{ { { 0, 8 } }, 13 }, { { { 0, 8 } }, 141 }, { { { 0, 8 } }, 77 }, { { { 0, 9 } }, 250 }, +{ { { 80, 7 } }, 3 }, { { { 0, 8 } }, 83 }, { { { 0, 8 } }, 19 }, { { { 85, 8 } }, 195 }, +{ { { 83, 7 } }, 35 }, { { { 0, 8 } }, 115 }, { { { 0, 8 } }, 51 }, { { { 0, 9 } }, 198 }, +{ { { 81, 7 } }, 11 }, { { { 0, 8 } }, 99 }, { { { 0, 8 } }, 35 }, { { { 0, 9 } }, 166 }, +{ { { 0, 8 } }, 3 }, { { { 0, 8 } }, 131 }, { { { 0, 8 } }, 67 }, { { { 0, 9 } }, 230 }, +{ { { 80, 7 } }, 7 }, { { { 0, 8 } }, 91 }, { { { 0, 8 } }, 27 }, { { { 0, 9 } }, 150 }, +{ { { 84, 7 } }, 67 }, { { { 0, 8 } }, 123 }, { { { 0, 8 } }, 59 }, { { { 0, 9 } }, 214 }, +{ { { 82, 7 } }, 19 }, { { { 0, 8 } }, 107 }, { { { 0, 8 } }, 43 }, { { { 0, 9 } }, 182 }, +{ { { 0, 8 } }, 11 }, { { { 0, 8 } }, 139 }, { { { 0, 8 } }, 75 }, { { { 0, 9 } }, 246 }, +{ { { 80, 7 } }, 5 }, { { { 0, 8 } }, 87 }, { { { 0, 8 } }, 23 }, { { { 192, 8 } }, 0 }, +{ { { 83, 7 } }, 51 }, { { { 0, 8 } }, 119 }, { { { 0, 8 } }, 55 }, { { { 0, 9 } }, 206 }, +{ { { 81, 7 } }, 15 }, { { { 0, 8 } }, 103 }, { { { 0, 8 } }, 39 }, { { { 0, 9 } }, 174 }, +{ { { 0, 8 } }, 7 }, { { { 0, 8 } }, 135 }, { { { 0, 8 } }, 71 }, { { { 0, 9 } }, 238 }, +{ { { 80, 7 } }, 9 }, { { { 0, 8 } }, 95 }, { { { 0, 8 } }, 31 }, { { { 0, 9 } }, 158 }, +{ { { 84, 7 } }, 99 }, { { { 0, 8 } }, 127 }, { { { 0, 8 } }, 63 }, { { { 0, 9 } }, 222 }, +{ { { 82, 7 } }, 27 }, { { { 0, 8 } }, 111 }, { { { 0, 8 } }, 47 }, { { { 0, 9 } }, 190 }, +{ { { 0, 8 } }, 15 }, { { { 0, 8 } }, 143 }, { { { 0, 8 } }, 79 }, { { { 0, 9 } }, 254 }, +{ { { 96, 7 } }, 256 }, { { { 0, 8 } }, 80 }, { { { 0, 8 } }, 16 }, { { { 84, 8 } }, 115 }, +{ { { 82, 7 } }, 31 }, { { { 0, 8 } }, 112 }, { { { 0, 8 } }, 48 }, { { { 0, 9 } }, 193 }, +{ { { 80, 7 } }, 10 }, { { { 0, 8 } }, 96 }, { { { 0, 8 } }, 32 }, { { { 0, 9 } }, 161 }, +{ { { 0, 8 } }, 0 }, { { { 0, 8 } }, 128 }, { { { 0, 8 } }, 64 }, { { { 0, 9 } }, 225 }, +{ { { 80, 7 } }, 6 }, { { { 0, 8 } }, 88 }, { { { 0, 8 } }, 24 }, { { { 0, 9 } }, 145 }, +{ { { 83, 7 } }, 59 }, { { { 0, 8 } }, 120 }, { { { 0, 8 } }, 56 }, { { { 0, 9 } }, 209 }, +{ { { 81, 7 } }, 17 }, { { { 0, 8 } }, 104 }, { { { 0, 8 } }, 40 }, { { { 0, 9 } }, 177 }, +{ { { 0, 8 } }, 8 }, { { { 0, 8 } }, 136 }, { { { 0, 8 } }, 72 }, { { { 0, 9 } }, 241 }, +{ { { 80, 7 } }, 4 }, { { { 0, 8 } }, 84 }, { { { 0, 8 } }, 20 }, { { { 85, 8 } }, 227 }, +{ { { 83, 7 } }, 43 }, { { { 0, 8 } }, 116 }, { { { 0, 8 } }, 52 }, { { { 0, 9 } }, 201 }, +{ { { 81, 7 } }, 13 }, { { { 0, 8 } }, 100 }, { { { 0, 8 } }, 36 }, { { { 0, 9 } }, 169 }, +{ { { 0, 8 } }, 4 }, { { { 0, 8 } }, 132 }, { { { 0, 8 } }, 68 }, { { { 0, 9 } }, 233 }, +{ { { 80, 7 } }, 8 }, { { { 0, 8 } }, 92 }, { { { 0, 8 } }, 28 }, { { { 0, 9 } }, 153 }, +{ { { 84, 7 } }, 83 }, { { { 0, 8 } }, 124 }, { { { 0, 8 } }, 60 }, { { { 0, 9 } }, 217 }, +{ { { 82, 7 } }, 23 }, { { { 0, 8 } }, 108 }, { { { 0, 8 } }, 44 }, { { { 0, 9 } }, 185 }, +{ { { 0, 8 } }, 12 }, { { { 0, 8 } }, 140 }, { { { 0, 8 } }, 76 }, { { { 0, 9 } }, 249 }, +{ { { 80, 7 } }, 3 }, { { { 0, 8 } }, 82 }, { { { 0, 8 } }, 18 }, { { { 85, 8 } }, 163 }, +{ { { 83, 7 } }, 35 }, { { { 0, 8 } }, 114 }, { { { 0, 8 } }, 50 }, { { { 0, 9 } }, 197 }, +{ { { 81, 7 } }, 11 }, { { { 0, 8 } }, 98 }, { { { 0, 8 } }, 34 }, { { { 0, 9 } }, 165 }, +{ { { 0, 8 } }, 2 }, { { { 0, 8 } }, 130 }, { { { 0, 8 } }, 66 }, { { { 0, 9 } }, 229 }, +{ { { 80, 7 } }, 7 }, { { { 0, 8 } }, 90 }, { { { 0, 8 } }, 26 }, { { { 0, 9 } }, 149 }, +{ { { 84, 7 } }, 67 }, { { { 0, 8 } }, 122 }, { { { 0, 8 } }, 58 }, { { { 0, 9 } }, 213 }, +{ { { 82, 7 } }, 19 }, { { { 0, 8 } }, 106 }, { { { 0, 8 } }, 42 }, { { { 0, 9 } }, 181 }, +{ { { 0, 8 } }, 10 }, { { { 0, 8 } }, 138 }, { { { 0, 8 } }, 74 }, { { { 0, 9 } }, 245 }, +{ { { 80, 7 } }, 5 }, { { { 0, 8 } }, 86 }, { { { 0, 8 } }, 22 }, { { { 192, 8 } }, 0 }, +{ { { 83, 7 } }, 51 }, { { { 0, 8 } }, 118 }, { { { 0, 8 } }, 54 }, { { { 0, 9 } }, 205 }, +{ { { 81, 7 } }, 15 }, { { { 0, 8 } }, 102 }, { { { 0, 8 } }, 38 }, { { { 0, 9 } }, 173 }, +{ { { 0, 8 } }, 6 }, { { { 0, 8 } }, 134 }, { { { 0, 8 } }, 70 }, { { { 0, 9 } }, 237 }, +{ { { 80, 7 } }, 9 }, { { { 0, 8 } }, 94 }, { { { 0, 8 } }, 30 }, { { { 0, 9 } }, 157 }, +{ { { 84, 7 } }, 99 }, { { { 0, 8 } }, 126 }, { { { 0, 8 } }, 62 }, { { { 0, 9 } }, 221 }, +{ { { 82, 7 } }, 27 }, { { { 0, 8 } }, 110 }, { { { 0, 8 } }, 46 }, { { { 0, 9 } }, 189 }, +{ { { 0, 8 } }, 14 }, { { { 0, 8 } }, 142 }, { { { 0, 8 } }, 78 }, { { { 0, 9 } }, 253 }, +{ { { 96, 7 } }, 256 }, { { { 0, 8 } }, 81 }, { { { 0, 8 } }, 17 }, { { { 85, 8 } }, 131 }, +{ { { 82, 7 } }, 31 }, { { { 0, 8 } }, 113 }, { { { 0, 8 } }, 49 }, { { { 0, 9 } }, 195 }, +{ { { 80, 7 } }, 10 }, { { { 0, 8 } }, 97 }, { { { 0, 8 } }, 33 }, { { { 0, 9 } }, 163 }, +{ { { 0, 8 } }, 1 }, { { { 0, 8 } }, 129 }, { { { 0, 8 } }, 65 }, { { { 0, 9 } }, 227 }, +{ { { 80, 7 } }, 6 }, { { { 0, 8 } }, 89 }, { { { 0, 8 } }, 25 }, { { { 0, 9 } }, 147 }, +{ { { 83, 7 } }, 59 }, { { { 0, 8 } }, 121 }, { { { 0, 8 } }, 57 }, { { { 0, 9 } }, 211 }, +{ { { 81, 7 } }, 17 }, { { { 0, 8 } }, 105 }, { { { 0, 8 } }, 41 }, { { { 0, 9 } }, 179 }, +{ { { 0, 8 } }, 9 }, { { { 0, 8 } }, 137 }, { { { 0, 8 } }, 73 }, { { { 0, 9 } }, 243 }, +{ { { 80, 7 } }, 4 }, { { { 0, 8 } }, 85 }, { { { 0, 8 } }, 21 }, { { { 80, 8 } }, 258 }, +{ { { 83, 7 } }, 43 }, { { { 0, 8 } }, 117 }, { { { 0, 8 } }, 53 }, { { { 0, 9 } }, 203 }, +{ { { 81, 7 } }, 13 }, { { { 0, 8 } }, 101 }, { { { 0, 8 } }, 37 }, { { { 0, 9 } }, 171 }, +{ { { 0, 8 } }, 5 }, { { { 0, 8 } }, 133 }, { { { 0, 8 } }, 69 }, { { { 0, 9 } }, 235 }, +{ { { 80, 7 } }, 8 }, { { { 0, 8 } }, 93 }, { { { 0, 8 } }, 29 }, { { { 0, 9 } }, 155 }, +{ { { 84, 7 } }, 83 }, { { { 0, 8 } }, 125 }, { { { 0, 8 } }, 61 }, { { { 0, 9 } }, 219 }, +{ { { 82, 7 } }, 23 }, { { { 0, 8 } }, 109 }, { { { 0, 8 } }, 45 }, { { { 0, 9 } }, 187 }, +{ { { 0, 8 } }, 13 }, { { { 0, 8 } }, 141 }, { { { 0, 8 } }, 77 }, { { { 0, 9 } }, 251 }, +{ { { 80, 7 } }, 3 }, { { { 0, 8 } }, 83 }, { { { 0, 8 } }, 19 }, { { { 85, 8 } }, 195 }, +{ { { 83, 7 } }, 35 }, { { { 0, 8 } }, 115 }, { { { 0, 8 } }, 51 }, { { { 0, 9 } }, 199 }, +{ { { 81, 7 } }, 11 }, { { { 0, 8 } }, 99 }, { { { 0, 8 } }, 35 }, { { { 0, 9 } }, 167 }, +{ { { 0, 8 } }, 3 }, { { { 0, 8 } }, 131 }, { { { 0, 8 } }, 67 }, { { { 0, 9 } }, 231 }, +{ { { 80, 7 } }, 7 }, { { { 0, 8 } }, 91 }, { { { 0, 8 } }, 27 }, { { { 0, 9 } }, 151 }, +{ { { 84, 7 } }, 67 }, { { { 0, 8 } }, 123 }, { { { 0, 8 } }, 59 }, { { { 0, 9 } }, 215 }, +{ { { 82, 7 } }, 19 }, { { { 0, 8 } }, 107 }, { { { 0, 8 } }, 43 }, { { { 0, 9 } }, 183 }, +{ { { 0, 8 } }, 11 }, { { { 0, 8 } }, 139 }, { { { 0, 8 } }, 75 }, { { { 0, 9 } }, 247 }, +{ { { 80, 7 } }, 5 }, { { { 0, 8 } }, 87 }, { { { 0, 8 } }, 23 }, { { { 192, 8 } }, 0 }, +{ { { 83, 7 } }, 51 }, { { { 0, 8 } }, 119 }, { { { 0, 8 } }, 55 }, { { { 0, 9 } }, 207 }, +{ { { 81, 7 } }, 15 }, { { { 0, 8 } }, 103 }, { { { 0, 8 } }, 39 }, { { { 0, 9 } }, 175 }, +{ { { 0, 8 } }, 7 }, { { { 0, 8 } }, 135 }, { { { 0, 8 } }, 71 }, { { { 0, 9 } }, 239 }, +{ { { 80, 7 } }, 9 }, { { { 0, 8 } }, 95 }, { { { 0, 8 } }, 31 }, { { { 0, 9 } }, 159 }, +{ { { 84, 7 } }, 99 }, { { { 0, 8 } }, 127 }, { { { 0, 8 } }, 63 }, { { { 0, 9 } }, 223 }, +{ { { 82, 7 } }, 27 }, { { { 0, 8 } }, 111 }, { { { 0, 8 } }, 47 }, { { { 0, 9 } }, 191 }, +{ { { 0, 8 } }, 15 }, { { { 0, 8 } }, 143 }, { { { 0, 8 } }, 79 }, { { { 0, 9 } }, 255 } +}; +const inflate_huft fixed_td[] = { + { { { 80, 5 } }, 1 }, { { { 87, 5 } }, 257 }, { { { 83, 5 } }, 17 }, { { { 91, 5 } }, 4097 }, +{ { { 81, 5 } }, 5 }, { { { 89, 5 } }, 1025 }, { { { 85, 5 } }, 65 }, { { { 93, 5 } }, 16385 }, +{ { { 80, 5 } }, 3 }, { { { 88, 5 } }, 513 }, { { { 84, 5 } }, 33 }, { { { 92, 5 } }, 8193 }, +{ { { 82, 5 } }, 9 }, { { { 90, 5 } }, 2049 }, { { { 86, 5 } }, 129 }, { { { 192, 5 } }, 24577 }, +{ { { 80, 5 } }, 2 }, { { { 87, 5 } }, 385 }, { { { 83, 5 } }, 25 }, { { { 91, 5 } }, 6145 }, +{ { { 81, 5 } }, 7 }, { { { 89, 5 } }, 1537 }, { { { 85, 5 } }, 97 }, { { { 93, 5 } }, 24577 }, +{ { { 80, 5 } }, 4 }, { { { 88, 5 } }, 769 }, { { { 84, 5 } }, 49 }, { { { 92, 5 } }, 12289 }, +{ { { 82, 5 } }, 13 }, { { { 90, 5 } }, 3073 }, { { { 86, 5 } }, 193 }, { { { 192, 5 } }, 24577 } +}; + + + + + + + +// copy as much as possible from the sliding window to the output area +int inflate_flush (inflate_blocks_statef *s, z_streamp z, int r) { + uInt n; + Byte *p; + Byte *q; + + // local copies of source and destination pointers + p = z->next_out; + q = s->read; + + // compute number of bytes to copy as far as end of window + n = (uInt) ((q <= s->write ? s->write : s->end) - q); + if (n > z->avail_out) n = z->avail_out; + if (n && r == Z_BUF_ERROR) r = Z_OK; + + // update counters + z->avail_out -= n; + z->total_out += n; + + // update check information + if (s->checkfn != Z_nullptr) + z->adler = s->check = (*s->checkfn)(s->check, q, n); + + // copy as far as end of window + if (n != 0) // check for n!=0 to avoid waking up CodeGuard + { + memcpy (p, q, n); + p += n; + q += n; + } + + // see if more to copy at beginning of window + if (q == s->end) { + // wrap pointers + q = s->window; + if (s->write == s->end) + s->write = s->window; + + // compute bytes to copy + n = (uInt) (s->write - q); + if (n > z->avail_out) n = z->avail_out; + if (n && r == Z_BUF_ERROR) r = Z_OK; + + // update counters + z->avail_out -= n; + z->total_out += n; + + // update check information + if (s->checkfn != Z_nullptr) + z->adler = s->check = (*s->checkfn)(s->check, q, n); + + // copy + if (n != 0) { + memcpy (p, q, n); p += n; q += n; + } + } + + // update pointers + z->next_out = p; + s->read = q; + + // done + return r; +} + + + + + + +// simplify the use of the inflate_huft type with some defines +#define exop word.what.Exop +#define bits word.what.Bits + +typedef enum { // waiting for "i:"=input, "o:"=output, "x:"=nothing + START, // x: set up for LEN + LEN, // i: get length/literal/eob next + LENEXT, // i: getting length extra (have base) + DIST, // i: get distance next + DISTEXT, // i: getting distance extra + COPY, // o: copying bytes in window, waiting for space + LIT, // o: got literal, waiting for output space + WASH, // o: got eob, possibly still output waiting + END, // x: got eob and all data flushed + BADCODE +} // x: got error +inflate_codes_mode; + +// inflate codes private state +struct inflate_codes_state { + + // mode + inflate_codes_mode mode; // current inflate_codes mode + + // mode dependent information + uInt len; + union { + struct { + const inflate_huft *tree; // pointer into tree + uInt need; // bits needed + } code; // if LEN or DIST, where in tree + uInt lit; // if LIT, literal + struct { + uInt get; // bits to get for extra + uInt dist; // distance back to copy from + } copy; // if EXT or COPY, where and how much + } sub; // submode + + // mode independent information + Byte lbits; // ltree bits decoded per branch + Byte dbits; // dtree bits decoder per branch + const inflate_huft *ltree; // literal/length/eob tree + const inflate_huft *dtree; // distance tree + +}; + + +inflate_codes_statef *inflate_codes_new ( + uInt bl, uInt bd, + const inflate_huft *tl, + const inflate_huft *td, // need separate declaration for Borland C++ + z_streamp z) { + inflate_codes_statef *c; + + if ((c = (inflate_codes_statef *) + ZALLOC (z, 1, sizeof (struct inflate_codes_state))) != Z_nullptr) { + c->mode = START; + c->lbits = (Byte) bl; + c->dbits = (Byte) bd; + c->ltree = tl; + c->dtree = td; + LuTracev ((stderr, "inflate: codes new\n")); + } + return c; +} + + +int inflate_codes (inflate_blocks_statef *s, z_streamp z, int r) { + uInt j; // temporary storage + const inflate_huft *t; // temporary pointer + uInt e; // extra bits or operation + uLong b; // bit buffer + uInt k; // bits in bit buffer + Byte *p; // input data pointer + uInt n; // bytes available there + Byte *q; // output window write pointer + uInt m; // bytes to end of window or read pointer + Byte *f; // pointer to copy strings from + inflate_codes_statef *c = s->sub.decode.codes; // codes state + + // copy input/output information to locals (UPDATE macro restores) + LOAD + + // process input and output based on current state + for (;;) switch (c->mode) { // waiting for "i:"=input, "o:"=output, "x:"=nothing + case START: // x: set up for LEN +#ifndef SLOW + if (m >= 258 && n >= 10) { + UPDATE + r = inflate_fast (c->lbits, c->dbits, c->ltree, c->dtree, s, z); + LOAD + if (r != Z_OK) { + c->mode = r == Z_STREAM_END ? WASH : BADCODE; + break; + } + } +#endif // !SLOW + c->sub.code.need = c->lbits; + c->sub.code.tree = c->ltree; + c->mode = LEN; + case LEN: // i: get length/literal/eob next + j = c->sub.code.need; + NEEDBITS (j) + t = c->sub.code.tree + ((uInt) b & inflate_mask[j]); + DUMPBITS (t->bits) + e = (uInt) (t->exop); + if (e == 0) // literal + { + c->sub.lit = t->base; + LuTracevv ((stderr, t->base >= 0x20 && t->base < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", t->base)); + c->mode = LIT; + break; + } + if (e & 16) // length + { + c->sub.copy.get = e & 15; + c->len = t->base; + c->mode = LENEXT; + break; + } + if ((e & 64) == 0) // next table + { + c->sub.code.need = e; + c->sub.code.tree = t + t->base; + break; + } + if (e & 32) // end of block + { + LuTracevv ((stderr, "inflate: end of block\n")); + c->mode = WASH; + break; + } + c->mode = BADCODE; // invalid code + z->msg = (char*)"invalid literal/length code"; + r = Z_DATA_ERROR; + LEAVE + case LENEXT: // i: getting length extra (have base) + j = c->sub.copy.get; + NEEDBITS (j) + c->len += (uInt) b & inflate_mask[j]; + DUMPBITS (j) + c->sub.code.need = c->dbits; + c->sub.code.tree = c->dtree; + LuTracevv ((stderr, "inflate: length %u\n", c->len)); + c->mode = DIST; + case DIST: // i: get distance next + j = c->sub.code.need; + NEEDBITS (j) + t = c->sub.code.tree + ((uInt) b & inflate_mask[j]); + DUMPBITS (t->bits) + e = (uInt) (t->exop); + if (e & 16) // distance + { + c->sub.copy.get = e & 15; + c->sub.copy.dist = t->base; + c->mode = DISTEXT; + break; + } + if ((e & 64) == 0) // next table + { + c->sub.code.need = e; + c->sub.code.tree = t + t->base; + break; + } + c->mode = BADCODE; // invalid code + z->msg = (char*)"invalid distance code"; + r = Z_DATA_ERROR; + LEAVE + case DISTEXT: // i: getting distance extra + j = c->sub.copy.get; + NEEDBITS (j) + c->sub.copy.dist += (uInt) b & inflate_mask[j]; + DUMPBITS (j) + LuTracevv ((stderr, "inflate: distance %u\n", c->sub.copy.dist)); + c->mode = COPY; + case COPY: // o: copying bytes in window, waiting for space + f = q - c->sub.copy.dist; + while (f < s->window) // modulo window size-"while" instead + f += s->end - s->window; // of "if" handles invalid distances + while (c->len) { + NEEDOUT + OUTBYTE (*f++) + if (f == s->end) + f = s->window; + c->len--; + } + c->mode = START; + break; + case LIT: // o: got literal, waiting for output space + NEEDOUT + OUTBYTE (c->sub.lit) + c->mode = START; + break; + case WASH: // o: got eob, possibly more output + if (k > 7) // return unused byte, if any + { + //Assert(k < 16, "inflate_codes grabbed too many bytes") + k -= 8; + n++; + p--; // can always return one + } + FLUSH + if (s->read != s->write) + LEAVE + c->mode = END; + case END: + r = Z_STREAM_END; + LEAVE + case BADCODE: // x: got error + r = Z_DATA_ERROR; + LEAVE + default: + r = Z_STREAM_ERROR; + LEAVE + } +} + + +void inflate_codes_free (inflate_codes_statef *c, z_streamp z) { + ZFREE (z, c); + LuTracev ((stderr, "inflate: codes free\n")); +} + + + +// infblock.c -- interpret and process block types to last block +// Copyright (C) 1995-1998 Mark Adler +// For conditions of distribution and use, see copyright notice in zlib.h + +//struct inflate_codes_state {int dummy;}; // for buggy compilers + + + +// Table for deflate from PKZIP's appnote.txt. +const uInt border[] = { // Order of the bit length code lengths + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +// +// Notes beyond the 1.93a appnote.txt: +// +// 1. Distance pointers never point before the beginning of the output stream. +// 2. Distance pointers can point back across blocks, up to 32k away. +// 3. There is an implied maximum of 7 bits for the bit length table and +// 15 bits for the actual data. +// 4. If only one code exists, then it is encoded using one bit. (Zero +// would be more efficient, but perhaps a little confusing.) If two +// codes exist, they are coded using one bit each (0 and 1). +// 5. There is no way of sending zero distance codes--a dummy must be +// sent if there are none. (History: a pre 2.0 version of PKZIP would +// store blocks with no distance codes, but this was discovered to be +// too harsh a criterion.) Valid only for 1.93a. 2.04c does allow +// zero distance codes, which is sent as one code of zero bits in +// length. +// 6. There are up to 286 literal/length codes. Code 256 represents the +// end-of-block. Note however that the static length tree defines +// 288 codes just to fill out the Huffman codes. Codes 286 and 287 +// cannot be used though, since there is no length base or extra bits +// defined for them. Similarily, there are up to 30 distance codes. +// However, static trees define 32 codes (all 5 bits) to fill out the +// Huffman codes, but the last two had better not show up in the data. +// 7. Unzip can check dynamic Huffman blocks for complete code sets. +// The exception is that a single code would not be complete (see #4). +// 8. The five bits following the block type is really the number of +// literal codes sent minus 257. +// 9. Length codes 8,16,16 are interpreted as 13 length codes of 8 bits +// (1+6+6). Therefore, to output three times the length, you output +// three codes (1+1+1), whereas to output four times the same length, +// you only need two codes (1+3). Hmm. +//10. In the tree reconstruction algorithm, Code = Code + Increment +// only if BitLength(i) is not zero. (Pretty obvious.) +//11. Correction: 4 Bits: # of Bit Length codes - 4 (4 - 19) +//12. Note: length code 284 can represent 227-258, but length code 285 +// really is 258. The last length deserves its own, short code +// since it gets used a lot in very redundant files. The length +// 258 is special since 258 - 3 (the min match length) is 255. +//13. The literal/length and distance code bit lengths are read as a +// single stream of lengths. It is possible (and advantageous) for +// a repeat code (16, 17, or 18) to go across the boundary between +// the two sets of lengths. + + +void inflate_blocks_reset (inflate_blocks_statef *s, z_streamp z, uLong *c) { + if (c != Z_nullptr) + *c = s->check; + if (s->mode == IBM_BTREE || s->mode == IBM_DTREE) + ZFREE (z, s->sub.trees.blens); + if (s->mode == IBM_CODES) + inflate_codes_free (s->sub.decode.codes, z); + s->mode = IBM_TYPE; + s->bitk = 0; + s->bitb = 0; + s->read = s->write = s->window; + if (s->checkfn != Z_nullptr) + z->adler = s->check = (*s->checkfn)(0L, (const Byte *) Z_nullptr, 0); + LuTracev ((stderr, "inflate: blocks reset\n")); +} + + +inflate_blocks_statef *inflate_blocks_new (z_streamp z, check_func c, uInt w) { + inflate_blocks_statef *s; + + if ((s = (inflate_blocks_statef *) ZALLOC + (z, 1, sizeof (struct inflate_blocks_state))) == Z_nullptr) + return s; + if ((s->hufts = + (inflate_huft *) ZALLOC (z, sizeof (inflate_huft), MANY)) == Z_nullptr) { + ZFREE (z, s); + return Z_nullptr; + } + if ((s->window = (Byte *) ZALLOC (z, 1, w)) == Z_nullptr) { + ZFREE (z, s->hufts); + ZFREE (z, s); + return Z_nullptr; + } + s->end = s->window + w; + s->checkfn = c; + s->mode = IBM_TYPE; + LuTracev ((stderr, "inflate: blocks allocated\n")); + inflate_blocks_reset (s, z, Z_nullptr); + return s; +} + + +int inflate_blocks (inflate_blocks_statef *s, z_streamp z, int r) { + uInt t; // temporary storage + uLong b; // bit buffer + uInt k; // bits in bit buffer + Byte *p; // input data pointer + uInt n; // bytes available there + Byte *q; // output window write pointer + uInt m; // bytes to end of window or read pointer + + // copy input/output information to locals (UPDATE macro restores) + LOAD + + // process input based on current state + for (;;) switch (s->mode) { + case IBM_TYPE: + NEEDBITS (3) + t = (uInt) b & 7; + s->last = t & 1; + switch (t >> 1) { + case 0: // stored + LuTracev ((stderr, "inflate: stored block%s\n", + s->last ? " (last)" : "")); + DUMPBITS (3) + t = k & 7; // go to byte boundary + DUMPBITS (t) + s->mode = IBM_LENS; // get length of stored block + break; + case 1: // fixed + LuTracev ((stderr, "inflate: fixed codes block%s\n", + s->last ? " (last)" : "")); + { + uInt bl, bd; + const inflate_huft *tl, *td; + + inflate_trees_fixed (&bl, &bd, &tl, &td, z); + s->sub.decode.codes = inflate_codes_new (bl, bd, tl, td, z); + if (s->sub.decode.codes == Z_nullptr) { + r = Z_MEM_ERROR; + LEAVE + } + } + DUMPBITS (3) + s->mode = IBM_CODES; + break; + case 2: // dynamic + LuTracev ((stderr, "inflate: dynamic codes block%s\n", + s->last ? " (last)" : "")); + DUMPBITS (3) + s->mode = IBM_TABLE; + break; + case 3: // illegal + DUMPBITS (3) + s->mode = IBM_BAD; + z->msg = (char*)"invalid block type"; + r = Z_DATA_ERROR; + LEAVE + } + break; + case IBM_LENS: + NEEDBITS (32) + if ((((~b) >> 16) & 0xffff) != (b & 0xffff)) { + s->mode = IBM_BAD; + z->msg = (char*)"invalid stored block lengths"; + r = Z_DATA_ERROR; + LEAVE + } + s->sub.left = (uInt) b & 0xffff; + b = k = 0; // dump bits + LuTracev ((stderr, "inflate: stored length %u\n", s->sub.left)); + s->mode = s->sub.left ? IBM_STORED : (s->last ? IBM_DRY : IBM_TYPE); + break; + case IBM_STORED: + if (n == 0) + LEAVE + NEEDOUT + t = s->sub.left; + if (t > n) t = n; + if (t > m) t = m; + memcpy (q, p, t); + p += t; n -= t; + q += t; m -= t; + if ((s->sub.left -= t) != 0) + break; + LuTracev ((stderr, "inflate: stored end, %lu total out\n", + z->total_out + (q >= s->read ? q - s->read : + (s->end - s->read) + (q - s->window)))); + s->mode = s->last ? IBM_DRY : IBM_TYPE; + break; + case IBM_TABLE: + NEEDBITS (14) + s->sub.trees.table = t = (uInt) b & 0x3fff; + // remove this section to workaround bug in pkzip + if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) { + s->mode = IBM_BAD; + z->msg = (char*)"too many length or distance symbols"; + r = Z_DATA_ERROR; + LEAVE + } + // end remove + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if ((s->sub.trees.blens = (uInt*) ZALLOC (z, t, sizeof (uInt))) == Z_nullptr) { + r = Z_MEM_ERROR; + LEAVE + } + DUMPBITS (14) + s->sub.trees.index = 0; + LuTracev ((stderr, "inflate: table sizes ok\n")); + s->mode = IBM_BTREE; + case IBM_BTREE: + while (s->sub.trees.index < 4 + (s->sub.trees.table >> 10)) { + NEEDBITS (3) + s->sub.trees.blens[border[s->sub.trees.index++]] = (uInt) b & 7; + DUMPBITS (3) + } + while (s->sub.trees.index < 19) + s->sub.trees.blens[border[s->sub.trees.index++]] = 0; + s->sub.trees.bb = 7; + t = inflate_trees_bits (s->sub.trees.blens, &s->sub.trees.bb, + &s->sub.trees.tb, s->hufts, z); + if (t != Z_OK) { + r = t; + if (r == Z_DATA_ERROR) { + ZFREE (z, s->sub.trees.blens); + s->mode = IBM_BAD; + } + LEAVE + } + s->sub.trees.index = 0; + LuTracev ((stderr, "inflate: bits tree ok\n")); + s->mode = IBM_DTREE; + case IBM_DTREE: + while (t = s->sub.trees.table, + s->sub.trees.index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f)) { + inflate_huft *h; + uInt i, j, c; + + t = s->sub.trees.bb; + NEEDBITS (t) + h = s->sub.trees.tb + ((uInt) b & inflate_mask[t]); + t = h->bits; + c = h->base; + if (c < 16) { + DUMPBITS (t) + s->sub.trees.blens[s->sub.trees.index++] = c; + } else // c == 16..18 + { + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; + NEEDBITS (t + i) + DUMPBITS (t) + j += (uInt) b & inflate_mask[i]; + DUMPBITS (i) + i = s->sub.trees.index; + t = s->sub.trees.table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || + (c == 16 && i < 1)) { + ZFREE (z, s->sub.trees.blens); + s->mode = IBM_BAD; + z->msg = (char*)"invalid bit length repeat"; + r = Z_DATA_ERROR; + LEAVE + } + c = c == 16 ? s->sub.trees.blens[i - 1] : 0; + do { + s->sub.trees.blens[i++] = c; + } while (--j); + s->sub.trees.index = i; + } + } + s->sub.trees.tb = Z_nullptr; + { + uInt bl, bd; + inflate_huft *tl, *td; + inflate_codes_statef *c; + + bl = 9; // must be <= 9 for lookahead assumptions + bd = 6; // must be <= 9 for lookahead assumptions + t = s->sub.trees.table; + t = inflate_trees_dynamic (257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), + s->sub.trees.blens, &bl, &bd, &tl, &td, + s->hufts, z); + if (t != Z_OK) { + if (t == (uInt) Z_DATA_ERROR) { + ZFREE (z, s->sub.trees.blens); + s->mode = IBM_BAD; + } + r = t; + LEAVE + } + LuTracev ((stderr, "inflate: trees ok\n")); + if ((c = inflate_codes_new (bl, bd, tl, td, z)) == Z_nullptr) { + r = Z_MEM_ERROR; + LEAVE + } + s->sub.decode.codes = c; + } + ZFREE (z, s->sub.trees.blens); + s->mode = IBM_CODES; + case IBM_CODES: + UPDATE + if ((r = inflate_codes (s, z, r)) != Z_STREAM_END) + return inflate_flush (s, z, r); + r = Z_OK; + inflate_codes_free (s->sub.decode.codes, z); + LOAD + LuTracev ((stderr, "inflate: codes end, %lu total out\n", + z->total_out + (q >= s->read ? q - s->read : + (s->end - s->read) + (q - s->window)))); + if (!s->last) { + s->mode = IBM_TYPE; + break; + } + s->mode = IBM_DRY; + case IBM_DRY: + FLUSH + if (s->read != s->write) + LEAVE + s->mode = IBM_DONE; + case IBM_DONE: + r = Z_STREAM_END; + LEAVE + case IBM_BAD: + r = Z_DATA_ERROR; + LEAVE + default: + r = Z_STREAM_ERROR; + LEAVE + } +} + + +int inflate_blocks_free (inflate_blocks_statef *s, z_streamp z) { + inflate_blocks_reset (s, z, Z_nullptr); + ZFREE (z, s->window); + ZFREE (z, s->hufts); + ZFREE (z, s); + LuTracev ((stderr, "inflate: blocks freed\n")); + return Z_OK; +} + + + +// inftrees.c -- generate Huffman trees for efficient decoding +// Copyright (C) 1995-1998 Mark Adler +// For conditions of distribution and use, see copyright notice in zlib.h +// + + + +extern const char inflate_copyright[] = +" inflate 1.1.3 Copyright 1995-1998 Mark Adler "; +// If you use the zlib library in a product, an acknowledgment is welcome +// in the documentation of your product. If for some reason you cannot +// include such an acknowledgment, I would appreciate that you keep this +// copyright string in the executable of your product. + + + +int huft_build ( + uInt *, // code lengths in bits + uInt, // number of codes + uInt, // number of "simple" codes + const uInt *, // list of base values for non-simple codes + const uInt *, // list of extra bits for non-simple codes + inflate_huft **,// result: starting table + uInt *, // maximum lookup bits (returns actual) + inflate_huft *, // space for trees + uInt *, // hufts used in space + uInt *); // space for values + +// Tables for deflate from PKZIP's appnote.txt. +const uInt cplens[31] = { // Copy lengths for literal codes 257..285 + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; +// see note #13 above about 258 +const uInt cplext[31] = { // Extra bits for literal codes 257..285 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112 }; // 112==invalid +const uInt cpdist[30] = { // Copy offsets for distance codes 0..29 + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 }; +const uInt cpdext[30] = { // Extra bits for distance codes + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13 }; + +// +// Huffman code decoding is performed using a multi-level table lookup. +// The fastest way to decode is to simply build a lookup table whose +// size is determined by the longest code. However, the time it takes +// to build this table can also be a factor if the data being decoded +// is not very long. The most common codes are necessarily the +// shortest codes, so those codes dominate the decoding time, and hence +// the speed. The idea is you can have a shorter table that decodes the +// shorter, more probable codes, and then point to subsidiary tables for +// the longer codes. The time it costs to decode the longer codes is +// then traded against the time it takes to make longer tables. +// +// This results of this trade are in the variables lbits and dbits +// below. lbits is the number of bits the first level table for literal/ +// length codes can decode in one step, and dbits is the same thing for +// the distance codes. Subsequent tables are also less than or equal to +// those sizes. These values may be adjusted either when all of the +// codes are shorter than that, in which case the longest code length in +// bits is used, or when the shortest code is *longer* than the requested +// table size, in which case the length of the shortest code in bits is +// used. +// +// There are two different values for the two tables, since they code a +// different number of possibilities each. The literal/length table +// codes 286 possible values, or in a flat code, a little over eight +// bits. The distance table codes 30 possible values, or a little less +// than five bits, flat. The optimum values for speed end up being +// about one bit more than those, so lbits is 8+1 and dbits is 5+1. +// The optimum values may differ though from machine to machine, and +// possibly even between compilers. Your mileage may vary. +// + + +// If BMAX needs to be larger than 16, then h and x[] should be uLong. +#define BMAX 15 // maximum bit length of any code + +int huft_build ( + uInt *b, // code lengths in bits (all assumed <= BMAX) + uInt n, // number of codes (assumed <= 288) + uInt s, // number of simple-valued codes (0..s-1) + const uInt *d, // list of base values for non-simple codes + const uInt *e, // list of extra bits for non-simple codes + inflate_huft * *t, // result: starting table + uInt *m, // maximum lookup bits, returns actual + inflate_huft *hp, // space for trees + uInt *hn, // hufts used in space + uInt *v) // working area: values in order of bit length + // Given a list of code lengths and a maximum table size, make a set of + // tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR + // if the given code set is incomplete (the tables are still built in this + // case), or Z_DATA_ERROR if the input is invalid. +{ + + uInt a; // counter for codes of length k + uInt c[BMAX + 1]; // bit length count table + uInt f; // i repeats in table every f entries + int g; // maximum code length + int h; // table level + uInt i; // counter, current code + uInt j; // counter + int k; // number of bits in current code + int l; // bits per table (returned in m) + uInt mask; // (1 << w) - 1, to avoid cc -O bug on HP + uInt *p; // pointer into c[], b[], or v[] + inflate_huft *q; // points to current table + struct inflate_huft_s r; // table entry for structure assignment + inflate_huft *u[BMAX]; // table stack + int w; // bits before this table == (l * h) + uInt x[BMAX + 1]; // bit offsets, then code stack + uInt *xp; // pointer into x + int y; // number of dummy codes added + uInt z; // number of entries in current table + + + // Generate counts for each bit length + p = c; +#define C0 *p++ = 0; +#define C2 C0 C0 C0 C0 +#define C4 C2 C2 C2 C2 + C4; p; // clear c[]--assume BMAX+1 is 16 + p = b; i = n; + do { + c[*p++]++; // assume all entries <= BMAX + } while (--i); + if (c[0] == n) // nullptr input--all zero length codes + { + *t = (inflate_huft *) Z_nullptr; + *m = 0; + return Z_OK; + } + + + // Find minimum and maximum length, bound *m by those + l = *m; + for (j = 1; j <= BMAX; j++) + if (c[j]) + break; + k = j; // minimum code length + if ((uInt) l < j) + l = j; + for (i = BMAX; i; i--) + if (c[i]) + break; + g = i; // maximum code length + if ((uInt) l > i) + l = i; + *m = l; + + + // Adjust last length count to fill out codes, if needed + for (y = 1 << j; j < i; j++, y <<= 1) + if ((y -= c[j]) < 0) + return Z_DATA_ERROR; + if ((y -= c[i]) < 0) + return Z_DATA_ERROR; + c[i] += y; + + + // Generate starting offsets into the value table for each length + x[1] = j = 0; + p = c + 1; xp = x + 2; + while (--i) { // note that i == g from above + *xp++ = (j += *p++); + } + + + // Make a table of values in order of bit lengths + p = b; i = 0; + do { + if ((j = *p++) != 0) + v[x[j]++] = i; + } while (++i < n); + n = x[g]; // set n to length of v + + + // Generate the Huffman codes and for each, make the table entries + x[0] = i = 0; // first Huffman code is zero + p = v; // grab values in bit order + h = -1; // no tables yet--level -1 + w = -l; // bits decoded == (l * h) + u[0] = (inflate_huft *) Z_nullptr; // just to keep compilers happy + q = (inflate_huft *) Z_nullptr; // ditto + z = 0; // ditto + + // go through the bit lengths (k already is bits in shortest code) + for (; k <= g; k++) { + a = c[k]; + while (a--) { + // here i is the Huffman code of length k bits for value *p + // make tables up to required level + while (k > w + l) { + h++; + w += l; // previous table always l bits + + // compute minimum size table less than or equal to l bits + z = g - w; + z = z > (uInt) l ? l : z; // table size upper limit + if ((f = 1 << (j = k - w)) > a + 1) // try a k-w bit table + { // too few codes for k-w bit table + f -= a + 1; // deduct codes from patterns left + xp = c + k; + if (j < z) + while (++j < z) // try smaller tables up to z bits + { + if ((f <<= 1) <= *++xp) + break; // enough codes to use up j bits + f -= *xp; // else deduct codes from patterns + } + } + z = 1 << j; // table entries for j-bit table + + // allocate new table + if (*hn + z > MANY) // (note: doesn't matter for fixed) + return Z_DATA_ERROR; // overflow of MANY + u[h] = q = hp + *hn; + *hn += z; + + // connect to last table, if there is one + if (h) { + x[h] = i; // save pattern for backing up + r.bits = (Byte) l; // bits to dump before this table + r.exop = (Byte) j; // bits in this table + j = i >> (w - l); + r.base = (uInt) (q - u[h - 1] - j); // offset to this table + u[h - 1][j] = r; // connect to last table + } else + *t = q; // first table is returned result + } + + // set up table entry in r + r.bits = (Byte) (k - w); + if (p >= v + n) + r.exop = 128 + 64; // out of values--invalid code + else if (*p < s) { + r.exop = (Byte) (*p < 256 ? 0 : 32 + 64); // 256 is end-of-block + r.base = *p++; // simple code is just the value + } else { + r.exop = (Byte) (e[*p - s] + 16 + 64);// non-simple--look up in lists + r.base = d[*p++ - s]; + } + + // fill code-like entries with r + f = 1 << (k - w); + for (j = i >> w; j < z; j += f) + q[j] = r; + + // backwards increment the k-bit code i + for (j = 1 << (k - 1); i & j; j >>= 1) + i ^= j; + i ^= j; + + // backup over finished tables + mask = (1 << w) - 1; // needed on HP, cc -O bug + while ((i & mask) != x[h]) { + h--; // don't need to update q + w -= l; + mask = (1 << w) - 1; + } + } + } + + + // Return Z_BUF_ERROR if we were given an incomplete table + return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; +} + + +int inflate_trees_bits ( + uInt *c, // 19 code lengths + uInt *bb, // bits tree desired/actual depth + inflate_huft * *tb, // bits tree result + inflate_huft *hp, // space for trees + z_streamp z) // for messages +{ + int r; + uInt hn = 0; // hufts used in space + uInt *v; // work area for huft_build + + if ((v = (uInt*) ZALLOC (z, 19, sizeof (uInt))) == Z_nullptr) + return Z_MEM_ERROR; + r = huft_build (c, 19, 19, (uInt*) Z_nullptr, (uInt*) Z_nullptr, + tb, bb, hp, &hn, v); + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed dynamic bit lengths tree"; + else if (r == Z_BUF_ERROR || *bb == 0) { + z->msg = (char*)"incomplete dynamic bit lengths tree"; + r = Z_DATA_ERROR; + } + ZFREE (z, v); + return r; +} + + +int inflate_trees_dynamic ( + uInt nl, // number of literal/length codes + uInt nd, // number of distance codes + uInt *c, // that many (total) code lengths + uInt *bl, // literal desired/actual bit depth + uInt *bd, // distance desired/actual bit depth + inflate_huft * *tl, // literal/length tree result + inflate_huft * *td, // distance tree result + inflate_huft *hp, // space for trees + z_streamp z) // for messages +{ + int r; + uInt hn = 0; // hufts used in space + uInt *v; // work area for huft_build + + // allocate work area + if ((v = (uInt*) ZALLOC (z, 288, sizeof (uInt))) == Z_nullptr) + return Z_MEM_ERROR; + + // build literal/length tree + r = huft_build (c, nl, 257, cplens, cplext, tl, bl, hp, &hn, v); + if (r != Z_OK || *bl == 0) { + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed literal/length tree"; + else if (r != Z_MEM_ERROR) { + z->msg = (char*)"incomplete literal/length tree"; + r = Z_DATA_ERROR; + } + ZFREE (z, v); + return r; + } + + // build distance tree + r = huft_build (c + nl, nd, 0, cpdist, cpdext, td, bd, hp, &hn, v); + if (r != Z_OK || (*bd == 0 && nl > 257)) { + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed distance tree"; + else if (r == Z_BUF_ERROR) { + z->msg = (char*)"incomplete distance tree"; + r = Z_DATA_ERROR; + } else if (r != Z_MEM_ERROR) { + z->msg = (char*)"empty distance tree with lengths"; + r = Z_DATA_ERROR; + } + ZFREE (z, v); + return r; + } + + // done + ZFREE (z, v); + return Z_OK; +} + + + + + +int inflate_trees_fixed ( + uInt *bl, // literal desired/actual bit depth + uInt *bd, // distance desired/actual bit depth + const inflate_huft * * tl, // literal/length tree result + const inflate_huft * *td, // distance tree result + z_streamp) // for memory allocation +{ + *bl = fixed_bl; + *bd = fixed_bd; + *tl = fixed_tl; + *td = fixed_td; + return Z_OK; +} + + +// inffast.c -- process literals and length/distance pairs fast +// Copyright (C) 1995-1998 Mark Adler +// For conditions of distribution and use, see copyright notice in zlib.h +// + + +//struct inflate_codes_state {int dummy;}; // for buggy compilers + + +// macros for bit input with no checking and for returning unused bytes +#define GRABBITS(j) {while(k<(j)){b|=((uLong)NEXTBYTE)<avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3;} + +// Called with number of bytes left to write in window at least 258 +// (the maximum string length) and number of input bytes available +// at least ten. The ten bytes are six bytes for the longest length/ +// distance pair plus four bytes for overloading the bit buffer. + +int inflate_fast ( + uInt bl, uInt bd, + const inflate_huft *tl, + const inflate_huft *td, // need separate declaration for Borland C++ + inflate_blocks_statef *s, + z_streamp z) { + const inflate_huft *t; // temporary pointer + uInt e; // extra bits or operation + uLong b; // bit buffer + uInt k; // bits in bit buffer + Byte *p; // input data pointer + uInt n; // bytes available there + Byte *q; // output window write pointer + uInt m; // bytes to end of window or read pointer + uInt ml; // mask for literal/length tree + uInt md; // mask for distance tree + uInt c; // bytes to copy + uInt d; // distance back to copy from + Byte *r; // copy source pointer + + // load input, output, bit values + LOAD + + // initialize masks + ml = inflate_mask[bl]; + md = inflate_mask[bd]; + + // do until not enough input or output space for fast loop + do { // assume called with m >= 258 && n >= 10 + // get literal/length code + GRABBITS (20) // max bits for literal/length code + if ((e = (t = tl + ((uInt) b & ml))->exop) == 0) { + DUMPBITS (t->bits) + LuTracevv ((stderr, t->base >= 0x20 && t->base < 0x7f ? + "inflate: * literal '%c'\n" : + "inflate: * literal 0x%02x\n", t->base)); + *q++ = (Byte) t->base; + m--; + continue; + } + for (;;) { + DUMPBITS (t->bits) + if (e & 16) { + // get extra bits for length + e &= 15; + c = t->base + ((uInt) b & inflate_mask[e]); + DUMPBITS (e) + LuTracevv ((stderr, "inflate: * length %u\n", c)); + + // decode distance base of block to copy + GRABBITS (15); // max bits for distance code + e = (t = td + ((uInt) b & md))->exop; + for (;;) { + DUMPBITS (t->bits) + if (e & 16) { + // get extra bits to add to distance base + e &= 15; + GRABBITS (e) // get extra bits (up to 13) + d = t->base + ((uInt) b & inflate_mask[e]); + DUMPBITS (e) + LuTracevv ((stderr, "inflate: * distance %u\n", d)); + + // do the copy + m -= c; + r = q - d; + if (r < s->window) // wrap if needed + { + do { + r += s->end - s->window; // force pointer in window + } while (r < s->window); // covers invalid distances + e = (uInt) (s->end - r); + if (c > e) { + c -= e; // wrapped copy + do { + *q++ = *r++; + } while (--e); + r = s->window; + do { + *q++ = *r++; + } while (--c); + } else // normal copy + { + *q++ = *r++; c--; + *q++ = *r++; c--; + do { + *q++ = *r++; + } while (--c); + } + } else /* normal copy */ + { + *q++ = *r++; c--; + *q++ = *r++; c--; + do { + *q++ = *r++; + } while (--c); + } + break; + } else if ((e & 64) == 0) { + t += t->base; + e = (t += ((uInt) b & inflate_mask[e]))->exop; + } else { + z->msg = (char*)"invalid distance code"; + UNGRAB + UPDATE + return Z_DATA_ERROR; + } + }; + break; + } + if ((e & 64) == 0) { + t += t->base; + if ((e = (t += ((uInt) b & inflate_mask[e]))->exop) == 0) { + DUMPBITS (t->bits) + LuTracevv ((stderr, t->base >= 0x20 && t->base < 0x7f ? + "inflate: * literal '%c'\n" : + "inflate: * literal 0x%02x\n", t->base)); + *q++ = (Byte) t->base; + m--; + break; + } + } else if (e & 32) { + LuTracevv ((stderr, "inflate: * end of block\n")); + UNGRAB + UPDATE + return Z_STREAM_END; + } else { + z->msg = (char*)"invalid literal/length code"; + UNGRAB + UPDATE + return Z_DATA_ERROR; + } + }; + } while (m >= 258 && n >= 10); + + // not enough input or output--restore pointers and return + UNGRAB + UPDATE + return Z_OK; +} + + + + + + +// crc32.c -- compute the CRC-32 of a data stream +// Copyright (C) 1995-1998 Mark Adler +// For conditions of distribution and use, see copyright notice in zlib.h + +// @(#) $Id$ + + + + + + +// Table of CRC-32's of all single-byte values (made by make_crc_table) +const uLong crc_table[256] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL +}; + +const uLong * get_crc_table () { + return (const uLong *) crc_table; +} + +#define CRC_DO1(buf) crc = crc_table[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8); +#define CRC_DO2(buf) CRC_DO1(buf); CRC_DO1(buf); +#define CRC_DO4(buf) CRC_DO2(buf); CRC_DO2(buf); +#define CRC_DO8(buf) CRC_DO4(buf); CRC_DO4(buf); + +uLong ucrc32 (uLong crc, const Byte *buf, uInt len) { + if (buf == Z_nullptr) return 0L; + crc = crc ^ 0xffffffffL; + while (len >= 8) { + CRC_DO8 (buf); len -= 8; + } + if (len) do { + CRC_DO1 (buf); + } while (--len); + return crc ^ 0xffffffffL; +} + + + +// ============================================================= +// some decryption routines +#define CRC32(c, b) (crc_table[((int)(c)^(b))&0xff]^((c)>>8)) +void Uupdate_keys (unsigned long *keys, char c) { + keys[0] = CRC32 (keys[0], c); + keys[1] += keys[0] & 0xFF; + keys[1] = keys[1] * 134775813L + 1; + keys[2] = CRC32 (keys[2], keys[1] >> 24); +} +char Udecrypt_byte (unsigned long *keys) { + unsigned temp = ((unsigned) keys[2] & 0xffff) | 2; + return (char) (((temp * (temp ^ 1)) >> 8) & 0xff); +} +char zdecode (unsigned long *keys, char c) { + c ^= Udecrypt_byte (keys); + Uupdate_keys (keys, c); + return c; +} + + + +// adler32.c -- compute the Adler-32 checksum of a data stream +// Copyright (C) 1995-1998 Mark Adler +// For conditions of distribution and use, see copyright notice in zlib.h + +// @(#) $Id$ + + +#define BASE 65521L // largest prime smaller than 65536 +#define NMAX 5552 +// NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 + +#define AD_DO1(buf,i) {s1 += buf[i]; s2 += s1;} +#define AD_DO2(buf,i) AD_DO1(buf,i); AD_DO1(buf,i+1); +#define AD_DO4(buf,i) AD_DO2(buf,i); AD_DO2(buf,i+2); +#define AD_DO8(buf,i) AD_DO4(buf,i); AD_DO4(buf,i+4); +#define AD_DO16(buf) AD_DO8(buf,0); AD_DO8(buf,8); + +// ========================================================================= +uLong adler32 (uLong adler, const Byte *buf, uInt len) { + unsigned long s1 = adler & 0xffff; + unsigned long s2 = (adler >> 16) & 0xffff; + int k; + + if (buf == Z_nullptr) return 1L; + + while (len > 0) { + k = len < NMAX ? len : NMAX; + len -= k; + while (k >= 16) { + AD_DO16 (buf); + buf += 16; + k -= 16; + } + if (k != 0) do { + s1 += *buf++; + s2 += s1; + } while (--k); + s1 %= BASE; + s2 %= BASE; + } + return (s2 << 16) | s1; +} + + + +// zutil.c -- target dependent utility functions for the compression library +// Copyright (C) 1995-1998 Jean-loup Gailly. +// For conditions of distribution and use, see copyright notice in zlib.h +// @(#) $Id$ + + + + + + +const char * zlibVersion () { + return ZLIB_VERSION; +} + +// exported to allow conversion of error code to string for compress() and +// uncompress() +const char * zError (int err) { + return ERR_MSG (err); +} + + + + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) { + if (opaque) items += size - size; // make compiler happy + return (voidpf) calloc (items, size); +} + +void zcfree (voidpf opaque, voidpf ptr) { + zfree (ptr); + if (opaque) return; // make compiler happy +} + + + +// inflate.c -- zlib interface to inflate modules +// Copyright (C) 1995-1998 Mark Adler +// For conditions of distribution and use, see copyright notice in zlib.h + +//struct inflate_blocks_state {int dummy;}; // for buggy compilers + +typedef enum { + IM_METHOD, // waiting for method byte + IM_FLAG, // waiting for flag byte + IM_DICT4, // four dictionary check bytes to go + IM_DICT3, // three dictionary check bytes to go + IM_DICT2, // two dictionary check bytes to go + IM_DICT1, // one dictionary check byte to go + IM_DICT0, // waiting for inflateSetDictionary + IM_BLOCKS, // decompressing blocks + IM_CHECK4, // four check bytes to go + IM_CHECK3, // three check bytes to go + IM_CHECK2, // two check bytes to go + IM_CHECK1, // one check byte to go + IM_DONE, // finished check, done + IM_BAD +} // got an error--stay here +inflate_mode; + +// inflate private state +struct internal_state { + + // mode + inflate_mode mode; // current inflate mode + + // mode dependent information + union { + uInt method; // if IM_FLAGS, method byte + struct { + uLong was; // computed check value + uLong need; // stream check value + } check; // if CHECK, check values to compare + uInt marker; // if IM_BAD, inflateSync's marker bytes count + } sub; // submode + + // mode independent information + int nowrap; // flag for no wrapper + uInt wbits; // log2(window size) (8..15, defaults to 15) + inflate_blocks_statef + *blocks; // current inflate_blocks state + +}; + +int inflateReset (z_streamp z) { + if (z == Z_nullptr || z->state == Z_nullptr) + return Z_STREAM_ERROR; + z->total_in = z->total_out = 0; + z->msg = Z_nullptr; + z->state->mode = z->state->nowrap ? IM_BLOCKS : IM_METHOD; + inflate_blocks_reset (z->state->blocks, z, Z_nullptr); + LuTracev ((stderr, "inflate: reset\n")); + return Z_OK; +} + +int inflateEnd (z_streamp z) { + if (z == Z_nullptr || z->state == Z_nullptr || z->zfree == Z_nullptr) + return Z_STREAM_ERROR; + if (z->state->blocks != Z_nullptr) + inflate_blocks_free (z->state->blocks, z); + ZFREE (z, z->state); + z->state = Z_nullptr; + LuTracev ((stderr, "inflate: end\n")); + return Z_OK; +} + + +int inflateInit2 (z_streamp z) { + const char *version = ZLIB_VERSION; int stream_size = sizeof (z_stream); + if (version == Z_nullptr || version[0] != ZLIB_VERSION[0] || stream_size != sizeof (z_stream)) return Z_VERSION_ERROR; + + int w = -15; // MAX_WBITS: 32K LZ77 window. + // Warning: reducing MAX_WBITS makes minigzip unable to extract .gz files created by gzip. + // The memory requirements for deflate are (in bytes): + // (1 << (windowBits+2)) + (1 << (memLevel+9)) + // that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + // plus a few kilobytes for small objects. For example, if you want to reduce + // the default memory requirements from 256K to 128K, compile with + // make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + // Of course this will generally degrade compression (there's no free lunch). + // + // The memory requirements for inflate are (in bytes) 1 << windowBits + // that is, 32K for windowBits=15 (default value) plus a few kilobytes + // for small objects. + + // initialize state + if (z == Z_nullptr) return Z_STREAM_ERROR; + z->msg = Z_nullptr; + if (z->zalloc == Z_nullptr) { + z->zalloc = zcalloc; + z->opaque = (voidpf) 0; + } + if (z->zfree == Z_nullptr) z->zfree = zcfree; + if ((z->state = (struct internal_state *) + ZALLOC (z, 1, sizeof (struct internal_state))) == Z_nullptr) + return Z_MEM_ERROR; + z->state->blocks = Z_nullptr; + + // handle undocumented nowrap option (no zlib header or check) + z->state->nowrap = 0; + if (w < 0) { + w = -w; + z->state->nowrap = 1; + } + + // set window size + if (w < 8 || w > 15) { + inflateEnd (z); + return Z_STREAM_ERROR; + } + z->state->wbits = (uInt) w; + + // create inflate_blocks state + if ((z->state->blocks = + inflate_blocks_new (z, z->state->nowrap ? Z_nullptr : adler32, (uInt) 1 << w)) + == Z_nullptr) { + inflateEnd (z); + return Z_MEM_ERROR; + } + LuTracev ((stderr, "inflate: allocated\n")); + + // reset state + inflateReset (z); + return Z_OK; +} + + + +#define IM_NEEDBYTE {if(z->avail_in==0)return r;r=f;} +#define IM_NEXTBYTE (z->avail_in--,z->total_in++,*z->next_in++) + +int inflate (z_streamp z, int f) { + int r; + uInt b; + + if (z == Z_nullptr || z->state == Z_nullptr || z->next_in == Z_nullptr) + return Z_STREAM_ERROR; + f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK; + r = Z_BUF_ERROR; + for (;;) switch (z->state->mode) { + case IM_METHOD: + IM_NEEDBYTE + if (((z->state->sub.method = IM_NEXTBYTE) & 0xf) != Z_DEFLATED) { + z->state->mode = IM_BAD; + z->msg = (char*)"unknown compression method"; + z->state->sub.marker = 5; // can't try inflateSync + break; + } + if ((z->state->sub.method >> 4) + 8 > z->state->wbits) { + z->state->mode = IM_BAD; + z->msg = (char*)"invalid window size"; + z->state->sub.marker = 5; // can't try inflateSync + break; + } + z->state->mode = IM_FLAG; + case IM_FLAG: + IM_NEEDBYTE + b = IM_NEXTBYTE; + if (((z->state->sub.method << 8) + b) % 31) { + z->state->mode = IM_BAD; + z->msg = (char*)"incorrect header check"; + z->state->sub.marker = 5; // can't try inflateSync + break; + } + LuTracev ((stderr, "inflate: zlib header ok\n")); + if (!(b & PRESET_DICT)) { + z->state->mode = IM_BLOCKS; + break; + } + z->state->mode = IM_DICT4; + case IM_DICT4: + IM_NEEDBYTE + z->state->sub.check.need = (uLong) IM_NEXTBYTE << 24; + z->state->mode = IM_DICT3; + case IM_DICT3: + IM_NEEDBYTE + z->state->sub.check.need += (uLong) IM_NEXTBYTE << 16; + z->state->mode = IM_DICT2; + case IM_DICT2: + IM_NEEDBYTE + z->state->sub.check.need += (uLong) IM_NEXTBYTE << 8; + z->state->mode = IM_DICT1; + case IM_DICT1: + IM_NEEDBYTE; r; + z->state->sub.check.need += (uLong) IM_NEXTBYTE; + z->adler = z->state->sub.check.need; + z->state->mode = IM_DICT0; + return Z_NEED_DICT; + case IM_DICT0: + z->state->mode = IM_BAD; + z->msg = (char*)"need dictionary"; + z->state->sub.marker = 0; // can try inflateSync + return Z_STREAM_ERROR; + case IM_BLOCKS: + r = inflate_blocks (z->state->blocks, z, r); + if (r == Z_DATA_ERROR) { + z->state->mode = IM_BAD; + z->state->sub.marker = 0; // can try inflateSync + break; + } + if (r == Z_OK) + r = f; + if (r != Z_STREAM_END) + return r; + r = f; + inflate_blocks_reset (z->state->blocks, z, &z->state->sub.check.was); + if (z->state->nowrap) { + z->state->mode = IM_DONE; + break; + } + z->state->mode = IM_CHECK4; + case IM_CHECK4: + IM_NEEDBYTE + z->state->sub.check.need = (uLong) IM_NEXTBYTE << 24; + z->state->mode = IM_CHECK3; + case IM_CHECK3: + IM_NEEDBYTE + z->state->sub.check.need += (uLong) IM_NEXTBYTE << 16; + z->state->mode = IM_CHECK2; + case IM_CHECK2: + IM_NEEDBYTE + z->state->sub.check.need += (uLong) IM_NEXTBYTE << 8; + z->state->mode = IM_CHECK1; + case IM_CHECK1: + IM_NEEDBYTE + z->state->sub.check.need += (uLong) IM_NEXTBYTE; + + if (z->state->sub.check.was != z->state->sub.check.need) { + z->state->mode = IM_BAD; + z->msg = (char*)"incorrect data check"; + z->state->sub.marker = 5; // can't try inflateSync + break; + } + LuTracev ((stderr, "inflate: zlib check ok\n")); + z->state->mode = IM_DONE; + case IM_DONE: + return Z_STREAM_END; + case IM_BAD: + return Z_DATA_ERROR; + default: + return Z_STREAM_ERROR; + } +} + + + + + +// unzip.c -- IO on .zip files using zlib +// Version 0.15 beta, Mar 19th, 1998, +// Read unzip.h for more info + + + + +#define UNZ_BUFSIZE (16384) +#define UNZ_MAXFILENAMEINZIP (256) +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + + + +const char unz_copyright[] = " unzip 0.15 Copyright 1998 Gilles Vollant "; + +// unz_file_info_interntal contain internal info about a file in zipfile +typedef struct unz_file_info_internal_s { + uLong offset_curfile;// relative offset of local header 4 bytes +} unz_file_info_internal; + + +typedef struct { + bool is_handle; // either a handle or memory + bool canseek; + // for handles: + HANDLE h; bool herr; unsigned long initial_offset; bool mustclosehandle; + // for memory: + void *buf; unsigned int len, pos; // if it's a memory block +} LUFILE; + + +LUFILE *lufopen (void *z, unsigned int len, DWORD flags, ZRESULT *err) { + if (flags != ZIP_HANDLE && flags != ZIP_FILENAME && flags != ZIP_MEMORY) { + *err = ZR_ARGS; return nullptr; + } + // + HANDLE h = 0; bool canseek = false; *err = ZR_OK; + bool mustclosehandle = false; + if (flags == ZIP_HANDLE || flags == ZIP_FILENAME) { + if (flags == ZIP_HANDLE) { + HANDLE hf = z; + h = hf; mustclosehandle = false; +#ifdef DuplicateHandle + BOOL res = DuplicateHandle (GetCurrentProcess (), hf, GetCurrentProcess (), &h, 0, FALSE, DUPLICATE_SAME_ACCESS); + if (!res) mustclosehandle = true; +#endif + } else { + h = CreateFile ((const TCHAR*) z, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (h == INVALID_HANDLE_VALUE) { + *err = ZR_NOFILE; return nullptr; + } + mustclosehandle = true; + } + // test if we can seek on it. We can't use GetFileType(h)==FILE_TYPE_DISK since it's not on CE. + DWORD res = SetFilePointer (h, 0, 0, FILE_CURRENT); + canseek = (res != 0xFFFFFFFF); + } + LUFILE *lf = new LUFILE; + if (flags == ZIP_HANDLE || flags == ZIP_FILENAME) { + lf->is_handle = true; lf->mustclosehandle = mustclosehandle; + lf->canseek = canseek; + lf->h = h; lf->herr = false; + lf->initial_offset = 0; + if (canseek) lf->initial_offset = SetFilePointer (h, 0, nullptr, FILE_CURRENT); + } else { + lf->is_handle = false; + lf->canseek = true; + lf->mustclosehandle = false; + lf->buf = z; lf->len = len; lf->pos = 0; lf->initial_offset = 0; + } + *err = ZR_OK; + return lf; +} + + +int lufclose (LUFILE *stream) { + if (stream == nullptr) return EOF; + if (stream->mustclosehandle) CloseHandle (stream->h); + delete stream; + return 0; +} + +int luferror (LUFILE *stream) { + if (stream->is_handle && stream->herr) return 1; + else return 0; +} + +long int luftell (LUFILE *stream) { + if (stream->is_handle && stream->canseek) return SetFilePointer (stream->h, 0, nullptr, FILE_CURRENT) - stream->initial_offset; + else if (stream->is_handle) return 0; + else return stream->pos; +} + +int lufseek (LUFILE *stream, long offset, int whence) { + if (stream->is_handle && stream->canseek) { + if (whence == SEEK_SET) SetFilePointer (stream->h, stream->initial_offset + offset, 0, FILE_BEGIN); + else if (whence == SEEK_CUR) SetFilePointer (stream->h, offset, nullptr, FILE_CURRENT); + else if (whence == SEEK_END) SetFilePointer (stream->h, offset, nullptr, FILE_END); + else return 19; // EINVAL + return 0; + } else if (stream->is_handle) return 29; // ESPIPE + else { + if (whence == SEEK_SET) stream->pos = offset; + else if (whence == SEEK_CUR) stream->pos += offset; + else if (whence == SEEK_END) stream->pos = stream->len + offset; + return 0; + } +} + + +size_t lufread (void *ptr, size_t size, size_t n, LUFILE *stream) { + unsigned int toread = (unsigned int) (size*n); + if (stream->is_handle) { + DWORD red; BOOL res = ReadFile (stream->h, ptr, toread, &red, nullptr); + if (!res) stream->herr = true; + return red / size; + } + if (stream->pos + toread > stream->len) toread = stream->len - stream->pos; + memcpy (ptr, (char*) stream->buf + stream->pos, toread); DWORD red = toread; + stream->pos += red; + return red / size; +} + + + + +// file_in_zip_read_info_s contain internal information about a file in zipfile, +// when reading and decompress it +typedef struct { + char *read_buffer; // internal buffer for compressed data + z_stream stream; // zLib stream structure for inflate + + uLong pos_in_zipfile; // position in byte on the zipfile, for fseek + uLong stream_initialised; // flag set if stream structure is initialised + + uLong offset_local_extrafield;// offset of the local extra field + uInt size_local_extrafield;// size of the local extra field + uLong pos_local_extrafield; // position in the local extra field in read + + uLong crc32; // crc32 of all data uncompressed + uLong crc32_wait; // crc32 we must obtain after decompress all + uLong rest_read_compressed; // number of byte to be decompressed + uLong rest_read_uncompressed;//number of byte to be obtained after decomp + LUFILE* file; // io structore of the zipfile + uLong compression_method; // compression method (0==store) + uLong byte_before_the_zipfile;// byte before the zipfile, (>0 for sfx) + bool encrypted; // is it encrypted? + unsigned long keys[3]; // decryption keys, initialized by unzOpenCurrentFile + int encheadleft; // the first call(s) to unzReadCurrentFile will read this many encryption-header bytes first + char crcenctest; // if encrypted, we'll check the encryption buffer against this +} file_in_zip_read_info_s; + + +// unz_s contain internal information about the zipfile +typedef struct { + LUFILE* file; // io structore of the zipfile + unz_global_info gi; // public global information + uLong byte_before_the_zipfile;// byte before the zipfile, (>0 for sfx) + uLong num_file; // number of the current file in the zipfile + uLong pos_in_central_dir; // pos of the current file in the central dir + uLong current_file_ok; // flag about the usability of the current file + uLong central_pos; // position of the beginning of the central dir + + uLong size_central_dir; // size of the central directory + uLong offset_central_dir; // offset of start of central directory with respect to the starting disk number + + unz_file_info cur_file_info; // public info about the current file in zip + unz_file_info_internal cur_file_info_internal; // private info about it + file_in_zip_read_info_s* pfile_in_zip_read; // structure about the current file if we are decompressing it +} unz_s, *unzFile; + + +int unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity); +// Compare two filename (fileName1,fileName2). + +z_off_t unztell (unzFile file); +// Give the current position in uncompressed data + +int unzeof (unzFile file); +// return 1 if the end of file was reached, 0 elsewhere + +int unzGetLocalExtrafield (unzFile file, voidp buf, unsigned len); +// Read extra field from the current file (opened by unzOpenCurrentFile) +// This is the local-header version of the extra field (sometimes, there is +// more info in the local-header version than in the central-header) +// +// if buf==nullptr, it return the size of the local extra field +// +// if buf!=nullptr, len is the size of the buffer, the extra header is copied in +// buf. +// the return value is the number of bytes copied in buf, or (if <0) +// the error code + + + +// =========================================================================== +// Read a byte from a gz_stream; update next_in and avail_in. Return EOF +// for end of file. +// IN assertion: the stream s has been sucessfully opened for reading. + +int unzlocal_getByte (LUFILE *fin, int *pi) { + unsigned char c; + int err = (int) lufread (&c, 1, 1, fin); + if (err == 1) { + *pi = (int) c; + return UNZ_OK; + } else { + if (luferror (fin)) return UNZ_ERRNO; + else return UNZ_EOF; + } +} + + +// =========================================================================== +// Reads a long in LSB order from the given gz_stream. Sets +int unzlocal_getShort (LUFILE *fin, uLong *pX) { + uLong x; + int i; + int err; + + err = unzlocal_getByte (fin, &i); + x = (uLong) i; + + if (err == UNZ_OK) + err = unzlocal_getByte (fin, &i); + x += ((uLong) i) << 8; + + if (err == UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +int unzlocal_getLong (LUFILE *fin, uLong *pX) { + uLong x; + int i; + int err; + + err = unzlocal_getByte (fin, &i); + x = (uLong) i; + + if (err == UNZ_OK) + err = unzlocal_getByte (fin, &i); + x += ((uLong) i) << 8; + + if (err == UNZ_OK) + err = unzlocal_getByte (fin, &i); + x += ((uLong) i) << 16; + + if (err == UNZ_OK) + err = unzlocal_getByte (fin, &i); + x += ((uLong) i) << 24; + + if (err == UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + + +// My own strcmpi / strcasecmp +int strcmpcasenosensitive_internal (const char* fileName1, const char *fileName2) { + for (;;) { + char c1 = *(fileName1++); + char c2 = *(fileName2++); + if ((c1 >= 'a') && (c1 <= 'z')) + c1 -= (char) 0x20; + if ((c2 >= 'a') && (c2 <= 'z')) + c2 -= (char) 0x20; + if (c1 == '\0') + return ((c2 == '\0') ? 0 : -1); + if (c2 == '\0') + return 1; + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + } +} + + + + +// +// Compare two filename (fileName1,fileName2). +// If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) +// If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi or strcasecmp) +// +int unzStringFileNameCompare (const char*fileName1, const char*fileName2, int iCaseSensitivity) { + if (iCaseSensitivity == 1) return strcmp (fileName1, fileName2); + else return strcmpcasenosensitive_internal (fileName1, fileName2); +} + +#define BUFREADCOMMENT (0x400) + + +// Locate the Central directory of a zipfile (at the end, just before +// the global comment). Lu bugfix 2005.07.26 - returns 0xFFFFFFFF if not found, +// rather than 0, since 0 is a valid central-dir-location for an empty zipfile. +uLong unzlocal_SearchCentralDir (LUFILE *fin) { + if (lufseek (fin, 0, SEEK_END) != 0) return 0xFFFFFFFF; + uLong uSizeFile = luftell (fin); + + uLong uMaxBack = 0xffff; // maximum size of global comment + if (uMaxBack > uSizeFile) uMaxBack = uSizeFile; + + unsigned char *buf = (unsigned char*) zmalloc (BUFREADCOMMENT + 4); + if (buf == nullptr) return 0xFFFFFFFF; + uLong uPosFound = 0xFFFFFFFF; + + uLong uBackRead = 4; + while (uBackRead < uMaxBack) { + uLong uReadSize, uReadPos; + int i; + if (uBackRead + BUFREADCOMMENT > uMaxBack) uBackRead = uMaxBack; + else uBackRead += BUFREADCOMMENT; + uReadPos = uSizeFile - uBackRead; + uReadSize = ((BUFREADCOMMENT + 4) < (uSizeFile - uReadPos)) ? (BUFREADCOMMENT + 4) : (uSizeFile - uReadPos); + if (lufseek (fin, uReadPos, SEEK_SET) != 0) break; + if (lufread (buf, (uInt) uReadSize, 1, fin) != 1) break; + for (i = (int) uReadSize - 3; (i--) >= 0;) { + if (((*(buf + i)) == 0x50) && ((*(buf + i + 1)) == 0x4b) && ((*(buf + i + 2)) == 0x05) && ((*(buf + i + 3)) == 0x06)) { + uPosFound = uReadPos + i; break; + } + } + if (uPosFound != 0) break; + } + if (buf) zfree (buf); + return uPosFound; +} + + +int unzGoToFirstFile (unzFile file); +int unzCloseCurrentFile (unzFile file); + +// Open a Zip file. +// If the zipfile cannot be opened (file don't exist or in not valid), return nullptr. +// Otherwise, the return value is a unzFile Handle, usable with other unzip functions +unzFile unzOpenInternal (LUFILE *fin) { + if (fin == nullptr) return nullptr; + if (unz_copyright[0] != ' ') { + lufclose (fin); return nullptr; + } + + int err = UNZ_OK; + unz_s us; + uLong central_pos, uL; + central_pos = unzlocal_SearchCentralDir (fin); + if (central_pos == 0xFFFFFFFF) err = UNZ_ERRNO; + if (lufseek (fin, central_pos, SEEK_SET) != 0) err = UNZ_ERRNO; + // the signature, already checked + if (unzlocal_getLong (fin, &uL) != UNZ_OK) err = UNZ_ERRNO; + // number of this disk + uLong number_disk; // number of the current dist, used for spanning ZIP, unsupported, always 0 + if (unzlocal_getShort (fin, &number_disk) != UNZ_OK) err = UNZ_ERRNO; + // number of the disk with the start of the central directory + uLong number_disk_with_CD; // number the the disk with central dir, used for spaning ZIP, unsupported, always 0 + if (unzlocal_getShort (fin, &number_disk_with_CD) != UNZ_OK) err = UNZ_ERRNO; + // total number of entries in the central dir on this disk + if (unzlocal_getShort (fin, &us.gi.number_entry) != UNZ_OK) err = UNZ_ERRNO; + // total number of entries in the central dir + uLong number_entry_CD; // total number of entries in the central dir (same than number_entry on nospan) + if (unzlocal_getShort (fin, &number_entry_CD) != UNZ_OK) err = UNZ_ERRNO; + if ((number_entry_CD != us.gi.number_entry) || (number_disk_with_CD != 0) || (number_disk != 0)) err = UNZ_BADZIPFILE; + // size of the central directory + if (unzlocal_getLong (fin, &us.size_central_dir) != UNZ_OK) err = UNZ_ERRNO; + // offset of start of central directory with respect to the starting disk number + if (unzlocal_getLong (fin, &us.offset_central_dir) != UNZ_OK) err = UNZ_ERRNO; + // zipfile comment length + if (unzlocal_getShort (fin, &us.gi.size_comment) != UNZ_OK) err = UNZ_ERRNO; + if ((central_pos + fin->initial_offset < us.offset_central_dir + us.size_central_dir) && (err == UNZ_OK)) err = UNZ_BADZIPFILE; + if (err != UNZ_OK) { + lufclose (fin); return nullptr; + } + + us.file = fin; + us.byte_before_the_zipfile = central_pos + fin->initial_offset - (us.offset_central_dir + us.size_central_dir); + us.central_pos = central_pos; + us.pfile_in_zip_read = nullptr; + fin->initial_offset = 0; // since the zipfile itself is expected to handle this + + unz_s *s = (unz_s*) zmalloc (sizeof (unz_s)); + *s = us; + unzGoToFirstFile ((unzFile) s); + return (unzFile) s; +} + + + +// Close a ZipFile opened with unzipOpen. +// If there is files inside the .Zip opened with unzipOpenCurrentFile (see later), +// these files MUST be closed with unzipCloseCurrentFile before call unzipClose. +// return UNZ_OK if there is no problem. +int unzClose (unzFile file) { + unz_s* s; + if (file == nullptr) + return UNZ_PARAMERROR; + s = (unz_s*) file; + + if (s->pfile_in_zip_read) + unzCloseCurrentFile (file); + + lufclose (s->file); + if (s) zfree (s); // unused s=0; + return UNZ_OK; +} + + +// Write info about the ZipFile in the *pglobal_info structure. +// No preparation of the structure is needed +// return UNZ_OK if there is no problem. +int unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info) { + unz_s* s; + if (file == nullptr) + return UNZ_PARAMERROR; + s = (unz_s*) file; + *pglobal_info = s->gi; + return UNZ_OK; +} + + +// Translate date/time from Dos format to tm_unz (readable more easilty) +void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm) { + uLong uDate; + uDate = (uLong) (ulDosDate >> 16); + ptm->tm_mday = (uInt) (uDate & 0x1f); + ptm->tm_mon = (uInt) ((((uDate) & 0x1E0) / 0x20) - 1); + ptm->tm_year = (uInt) (((uDate & 0x0FE00) / 0x0200) + 1980); + + ptm->tm_hour = (uInt) ((ulDosDate & 0xF800) / 0x800); + ptm->tm_min = (uInt) ((ulDosDate & 0x7E0) / 0x20); + ptm->tm_sec = (uInt) (2 * (ulDosDate & 0x1f)); +} + +// Get Info about the current file in the zipfile, with internal only info +int unzlocal_GetCurrentFileInfoInternal (unzFile file, + unz_file_info *pfile_info, + unz_file_info_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize); + +int unzlocal_GetCurrentFileInfoInternal (unzFile file, unz_file_info *pfile_info, + unz_file_info_internal *pfile_info_internal, char *szFileName, + uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, + char *szComment, uLong commentBufferSize) { + unz_s* s; + unz_file_info file_info; + unz_file_info_internal file_info_internal; + int err = UNZ_OK; + uLong uMagic; + long lSeek = 0; + + if (file == nullptr) + return UNZ_PARAMERROR; + s = (unz_s*) file; + if (lufseek (s->file, s->pos_in_central_dir + s->byte_before_the_zipfile, SEEK_SET) != 0) + err = UNZ_ERRNO; + + + // we check the magic + if (err == UNZ_OK) + if (unzlocal_getLong (s->file, &uMagic) != UNZ_OK) + err = UNZ_ERRNO; + else if (uMagic != 0x02014b50) + err = UNZ_BADZIPFILE; + + if (unzlocal_getShort (s->file, &file_info.version) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getShort (s->file, &file_info.version_needed) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getShort (s->file, &file_info.flag) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getShort (s->file, &file_info.compression_method) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getLong (s->file, &file_info.dosDate) != UNZ_OK) + err = UNZ_ERRNO; + + unzlocal_DosDateToTmuDate (file_info.dosDate, &file_info.tmu_date); + + if (unzlocal_getLong (s->file, &file_info.crc) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getLong (s->file, &file_info.compressed_size) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getLong (s->file, &file_info.uncompressed_size) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getShort (s->file, &file_info.size_filename) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getShort (s->file, &file_info.size_file_extra) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getShort (s->file, &file_info.size_file_comment) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getShort (s->file, &file_info.disk_num_start) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getShort (s->file, &file_info.internal_fa) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getLong (s->file, &file_info.external_fa) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getLong (s->file, &file_info_internal.offset_curfile) != UNZ_OK) + err = UNZ_ERRNO; + + lSeek += file_info.size_filename; + if ((err == UNZ_OK) && (szFileName)) { + uLong uSizeRead; + if (file_info.size_filename < fileNameBufferSize) { + *(szFileName + file_info.size_filename) = '\0'; + uSizeRead = file_info.size_filename; + } else + uSizeRead = fileNameBufferSize; + + if ((file_info.size_filename > 0) && (fileNameBufferSize > 0)) + if (lufread (szFileName, (uInt) uSizeRead, 1, s->file) != 1) + err = UNZ_ERRNO; + lSeek -= uSizeRead; + } + + + if ((err == UNZ_OK) && (extraField)) { + uLong uSizeRead; + if (file_info.size_file_extra < extraFieldBufferSize) + uSizeRead = file_info.size_file_extra; + else + uSizeRead = extraFieldBufferSize; + + if (lSeek != 0) + if (lufseek (s->file, lSeek, SEEK_CUR) == 0) + lSeek = 0; + else + err = UNZ_ERRNO; + if ((file_info.size_file_extra > 0) && (extraFieldBufferSize > 0)) + if (lufread (extraField, (uInt) uSizeRead, 1, s->file) != 1) + err = UNZ_ERRNO; + lSeek += file_info.size_file_extra - uSizeRead; + } else + lSeek += file_info.size_file_extra; + + + if ((err == UNZ_OK) && (szComment)) { + uLong uSizeRead; + if (file_info.size_file_comment < commentBufferSize) { + *(szComment + file_info.size_file_comment) = '\0'; + uSizeRead = file_info.size_file_comment; + } else + uSizeRead = commentBufferSize; + + if (lSeek != 0) + if (lufseek (s->file, lSeek, SEEK_CUR) == 0) { + } // unused lSeek=0; + else + err = UNZ_ERRNO; + if ((file_info.size_file_comment > 0) && (commentBufferSize > 0)) + if (lufread (szComment, (uInt) uSizeRead, 1, s->file) != 1) + err = UNZ_ERRNO; + //unused lSeek+=file_info.size_file_comment - uSizeRead; + } else { + } //unused lSeek+=file_info.size_file_comment; + + if ((err == UNZ_OK) && (pfile_info)) + *pfile_info = file_info; + + if ((err == UNZ_OK) && (pfile_info_internal)) + *pfile_info_internal = file_info_internal; + + return err; +} + + + +// Write info about the ZipFile in the *pglobal_info structure. +// No preparation of the structure is needed +// return UNZ_OK if there is no problem. +int unzGetCurrentFileInfo (unzFile file, unz_file_info *pfile_info, + char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, + char *szComment, uLong commentBufferSize) { + return unzlocal_GetCurrentFileInfoInternal (file, pfile_info, nullptr, szFileName, fileNameBufferSize, + extraField, extraFieldBufferSize, szComment, commentBufferSize); +} + + +// Set the current file of the zipfile to the first file. +// return UNZ_OK if there is no problem +int unzGoToFirstFile (unzFile file) { + int err; + unz_s* s; + if (file == nullptr) return UNZ_PARAMERROR; + s = (unz_s*) file; + s->pos_in_central_dir = s->offset_central_dir; + s->num_file = 0; + err = unzlocal_GetCurrentFileInfoInternal (file, &s->cur_file_info, + &s->cur_file_info_internal, + nullptr, 0, nullptr, 0, nullptr, 0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +// Set the current file of the zipfile to the next file. +// return UNZ_OK if there is no problem +// return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +int unzGoToNextFile (unzFile file) { + unz_s* s; + int err; + + if (file == nullptr) + return UNZ_PARAMERROR; + s = (unz_s*) file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->num_file + 1 == s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment; + s->num_file++; + err = unzlocal_GetCurrentFileInfoInternal (file, &s->cur_file_info, + &s->cur_file_info_internal, + nullptr, 0, nullptr, 0, nullptr, 0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +// Try locate the file szFileName in the zipfile. +// For the iCaseSensitivity signification, see unzStringFileNameCompare +// return value : +// UNZ_OK if the file is found. It becomes the current file. +// UNZ_END_OF_LIST_OF_FILE if the file is not found +int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) { + unz_s* s; + int err; + + + uLong num_fileSaved; + uLong pos_in_central_dirSaved; + + + if (file == nullptr) + return UNZ_PARAMERROR; + + if (strlen (szFileName) >= UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s = (unz_s*) file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + + err = unzGoToFirstFile (file); + + while (err == UNZ_OK) { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP + 1]; + unzGetCurrentFileInfo (file, nullptr, + szCurrentFileName, sizeof (szCurrentFileName) - 1, + nullptr, 0, nullptr, 0); + if (unzStringFileNameCompare (szCurrentFileName, szFileName, iCaseSensitivity) == 0) + return UNZ_OK; + err = unzGoToNextFile (file); + } + + s->num_file = num_fileSaved; + s->pos_in_central_dir = pos_in_central_dirSaved; + return err; +} + + +// Read the local header of the current zipfile +// Check the coherency of the local header and info in the end of central +// directory about this file +// store in *piSizeVar the size of extra info in local header +// (filename and size of extra field data) +int unzlocal_CheckCurrentFileCoherencyHeader (unz_s *s, uInt *piSizeVar, + uLong *poffset_local_extrafield, uInt *psize_local_extrafield) { + uLong uMagic, uData, uFlags; + uLong size_filename; + uLong size_extra_field; + int err = UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (lufseek (s->file, s->cur_file_info_internal.offset_curfile + s->byte_before_the_zipfile, SEEK_SET) != 0) + return UNZ_ERRNO; + + + if (err == UNZ_OK) + if (unzlocal_getLong (s->file, &uMagic) != UNZ_OK) + err = UNZ_ERRNO; + else if (uMagic != 0x04034b50) + err = UNZ_BADZIPFILE; + + if (unzlocal_getShort (s->file, &uData) != UNZ_OK) + err = UNZ_ERRNO; + // else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + // err=UNZ_BADZIPFILE; + if (unzlocal_getShort (s->file, &uFlags) != UNZ_OK) + err = UNZ_ERRNO; + + if (unzlocal_getShort (s->file, &uData) != UNZ_OK) + err = UNZ_ERRNO; + else if ((err == UNZ_OK) && (uData != s->cur_file_info.compression_method)) + err = UNZ_BADZIPFILE; + + if ((err == UNZ_OK) && (s->cur_file_info.compression_method != 0) && + (s->cur_file_info.compression_method != Z_DEFLATED)) + err = UNZ_BADZIPFILE; + + if (unzlocal_getLong (s->file, &uData) != UNZ_OK) // date/time + err = UNZ_ERRNO; + + if (unzlocal_getLong (s->file, &uData) != UNZ_OK) // crc + err = UNZ_ERRNO; + else if ((err == UNZ_OK) && (uData != s->cur_file_info.crc) && + ((uFlags & 8) == 0)) + err = UNZ_BADZIPFILE; + + if (unzlocal_getLong (s->file, &uData) != UNZ_OK) // size compr + err = UNZ_ERRNO; + else if ((err == UNZ_OK) && (uData != s->cur_file_info.compressed_size) && + ((uFlags & 8) == 0)) + err = UNZ_BADZIPFILE; + + if (unzlocal_getLong (s->file, &uData) != UNZ_OK) // size uncompr + err = UNZ_ERRNO; + else if ((err == UNZ_OK) && (uData != s->cur_file_info.uncompressed_size) && + ((uFlags & 8) == 0)) + err = UNZ_BADZIPFILE; + + + if (unzlocal_getShort (s->file, &size_filename) != UNZ_OK) + err = UNZ_ERRNO; + else if ((err == UNZ_OK) && (size_filename != s->cur_file_info.size_filename)) + err = UNZ_BADZIPFILE; + + *piSizeVar += (uInt) size_filename; + + if (unzlocal_getShort (s->file, &size_extra_field) != UNZ_OK) + err = UNZ_ERRNO; + *poffset_local_extrafield = s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt) size_extra_field; + + *piSizeVar += (uInt) size_extra_field; + + return err; +} + + + + + +// Open for reading data the current file in the zipfile. +// If there is no error and the file is opened, the return value is UNZ_OK. +int unzOpenCurrentFile (unzFile file, const char *password) { + int err; + int Store; + uInt iSizeVar; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uLong offset_local_extrafield; // offset of the local extra field + uInt size_local_extrafield; // size of the local extra field + + if (file == nullptr) + return UNZ_PARAMERROR; + s = (unz_s*) file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read) + unzCloseCurrentFile (file); + + if (unzlocal_CheckCurrentFileCoherencyHeader (s, &iSizeVar, + &offset_local_extrafield, &size_local_extrafield) != UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip_read_info_s*) zmalloc (sizeof (file_in_zip_read_info_s)); + if (pfile_in_zip_read_info == nullptr) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer = (char*) zmalloc (UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield = 0; + + if (pfile_in_zip_read_info->read_buffer == nullptr) { + if (pfile_in_zip_read_info != 0) zfree (pfile_in_zip_read_info); //unused pfile_in_zip_read_info=0; + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised = 0; + + if ((s->cur_file_info.compression_method != 0) && (s->cur_file_info.compression_method != Z_DEFLATED)) { // unused err=UNZ_BADZIPFILE; + } + Store = s->cur_file_info.compression_method == 0; + + pfile_in_zip_read_info->crc32_wait = s->cur_file_info.crc; + pfile_in_zip_read_info->crc32 = 0; + pfile_in_zip_read_info->compression_method = s->cur_file_info.compression_method; + pfile_in_zip_read_info->file = s->file; + pfile_in_zip_read_info->byte_before_the_zipfile = s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if (!Store) { + pfile_in_zip_read_info->stream.zalloc = (alloc_func) 0; + pfile_in_zip_read_info->stream.zfree = (free_func) 0; + pfile_in_zip_read_info->stream.opaque = (voidpf) 0; + + err = inflateInit2 (&pfile_in_zip_read_info->stream); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised = 1; + // windowBits is passed < 0 to tell that there is no zlib header. + // Note that in this case inflate *requires* an extra "dummy" byte + // after the compressed stream in order to complete decompression and + // return Z_STREAM_END. + // In unzip, i don't wait absolutely Z_STREAM_END because I known the + // size of both compressed and uncompressed data + } + pfile_in_zip_read_info->rest_read_compressed = s->cur_file_info.compressed_size; + pfile_in_zip_read_info->rest_read_uncompressed = s->cur_file_info.uncompressed_size; + pfile_in_zip_read_info->encrypted = (s->cur_file_info.flag & 1) != 0; + bool extlochead = (s->cur_file_info.flag & 8) != 0; + if (extlochead) pfile_in_zip_read_info->crcenctest = (char) ((s->cur_file_info.dosDate >> 8) & 0xff); + else pfile_in_zip_read_info->crcenctest = (char) (s->cur_file_info.crc >> 24); + pfile_in_zip_read_info->encheadleft = (pfile_in_zip_read_info->encrypted ? 12 : 0); + pfile_in_zip_read_info->keys[0] = 305419896L; + pfile_in_zip_read_info->keys[1] = 591751049L; + pfile_in_zip_read_info->keys[2] = 878082192L; + for (const char *cp = password; cp != 0 && *cp != 0; cp++) Uupdate_keys (pfile_in_zip_read_info->keys, *cp); + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt) 0; + + s->pfile_in_zip_read = pfile_in_zip_read_info; + + return UNZ_OK; +} + + +// Read bytes from the current file. +// buf contain buffer where data must be copied +// len the size of buf. +// return the number of byte copied if somes bytes are copied (and also sets *reached_eof) +// return 0 if the end of file was reached. (and also sets *reached_eof). +// return <0 with error code if there is an error. (in which case *reached_eof is meaningless) +// (UNZ_ERRNO for IO error, or zLib error for uncompress error) +int unzReadCurrentFile (unzFile file, voidp buf, unsigned len, bool *reached_eof) { + int err = UNZ_OK; + uInt iRead = 0; + if (reached_eof != 0) *reached_eof = false; + + unz_s *s = (unz_s*) file; + if (s == nullptr) return UNZ_PARAMERROR; + + file_in_zip_read_info_s* pfile_in_zip_read_info = s->pfile_in_zip_read; + if (pfile_in_zip_read_info == nullptr) return UNZ_PARAMERROR; + if ((pfile_in_zip_read_info->read_buffer == nullptr)) return UNZ_END_OF_LIST_OF_FILE; + if (len == 0) return 0; + + pfile_in_zip_read_info->stream.next_out = (Byte*) buf; + pfile_in_zip_read_info->stream.avail_out = (uInt) len; + + if (len > pfile_in_zip_read_info->rest_read_uncompressed) { + pfile_in_zip_read_info->stream.avail_out = (uInt) pfile_in_zip_read_info->rest_read_uncompressed; + } + + while (pfile_in_zip_read_info->stream.avail_out > 0) { + if ((pfile_in_zip_read_info->stream.avail_in == 0) && (pfile_in_zip_read_info->rest_read_compressed > 0)) { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressed < uReadThis) uReadThis = (uInt) pfile_in_zip_read_info->rest_read_compressed; + if (uReadThis == 0) { + if (reached_eof != 0) *reached_eof = true; return UNZ_EOF; + } + if (lufseek (pfile_in_zip_read_info->file, pfile_in_zip_read_info->pos_in_zipfile + pfile_in_zip_read_info->byte_before_the_zipfile, SEEK_SET) != 0) return UNZ_ERRNO; + if (lufread (pfile_in_zip_read_info->read_buffer, uReadThis, 1, pfile_in_zip_read_info->file) != 1) return UNZ_ERRNO; + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + pfile_in_zip_read_info->rest_read_compressed -= uReadThis; + pfile_in_zip_read_info->stream.next_in = (Byte*) pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt) uReadThis; + // + if (pfile_in_zip_read_info->encrypted) { + char *_buf = (char*) pfile_in_zip_read_info->stream.next_in; + for (unsigned int i = 0; i < uReadThis; i++) _buf[i] = zdecode (pfile_in_zip_read_info->keys, _buf[i]); + } + } + + unsigned int uDoEncHead = pfile_in_zip_read_info->encheadleft; + if (uDoEncHead > pfile_in_zip_read_info->stream.avail_in) uDoEncHead = pfile_in_zip_read_info->stream.avail_in; + if (uDoEncHead > 0) { + char bufcrc = pfile_in_zip_read_info->stream.next_in[uDoEncHead - 1]; + pfile_in_zip_read_info->rest_read_uncompressed -= uDoEncHead; + pfile_in_zip_read_info->stream.avail_in -= uDoEncHead; + pfile_in_zip_read_info->stream.next_in += uDoEncHead; + pfile_in_zip_read_info->encheadleft -= uDoEncHead; + if (pfile_in_zip_read_info->encheadleft == 0) { + if (bufcrc != pfile_in_zip_read_info->crcenctest) return UNZ_PASSWORD; + } + } + + if (pfile_in_zip_read_info->compression_method == 0) { + uInt uDoCopy, i; + if (pfile_in_zip_read_info->stream.avail_out < pfile_in_zip_read_info->stream.avail_in) { + uDoCopy = pfile_in_zip_read_info->stream.avail_out; + } else { + uDoCopy = pfile_in_zip_read_info->stream.avail_in; + } + for (i = 0; i < uDoCopy; i++) *(pfile_in_zip_read_info->stream.next_out + i) = *(pfile_in_zip_read_info->stream.next_in + i); + pfile_in_zip_read_info->crc32 = ucrc32 (pfile_in_zip_read_info->crc32, pfile_in_zip_read_info->stream.next_out, uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed -= uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) { + if (reached_eof != 0) *reached_eof = true; + } + } else { + uLong uTotalOutBefore, uTotalOutAfter; + const Byte *bufBefore; + uLong uOutThis; + int flush = Z_SYNC_FLUSH; + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + // + err = inflate (&pfile_in_zip_read_info->stream, flush); + // + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter - uTotalOutBefore; + pfile_in_zip_read_info->crc32 = ucrc32 (pfile_in_zip_read_info->crc32, bufBefore, (uInt) (uOutThis)); + pfile_in_zip_read_info->rest_read_uncompressed -= uOutThis; + iRead += (uInt) (uTotalOutAfter - uTotalOutBefore); + if (err == Z_STREAM_END || pfile_in_zip_read_info->rest_read_uncompressed == 0) { + if (reached_eof != 0) *reached_eof = true; + return iRead; + } + if (err != Z_OK) break; + } + } + + if (err == Z_OK) return iRead; + return err; +} + + +// Give the current position in uncompressed data +z_off_t unztell (unzFile file) { + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file == nullptr) + return UNZ_PARAMERROR; + s = (unz_s*) file; + pfile_in_zip_read_info = s->pfile_in_zip_read; + + if (pfile_in_zip_read_info == nullptr) + return UNZ_PARAMERROR; + + return (z_off_t) pfile_in_zip_read_info->stream.total_out; +} + + +// return 1 if the end of file was reached, 0 elsewhere +int unzeof (unzFile file) { + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file == nullptr) + return UNZ_PARAMERROR; + s = (unz_s*) file; + pfile_in_zip_read_info = s->pfile_in_zip_read; + + if (pfile_in_zip_read_info == nullptr) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +// Read extra field from the current file (opened by unzOpenCurrentFile) +// This is the local-header version of the extra field (sometimes, there is +// more info in the local-header version than in the central-header) +// if buf==nullptr, it return the size of the local extra field that can be read +// if buf!=nullptr, len is the size of the buffer, the extra header is copied in buf. +// the return value is the number of bytes copied in buf, or (if <0) the error code +int unzGetLocalExtrafield (unzFile file, voidp buf, unsigned len) { + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uInt read_now; + uLong size_to_read; + + if (file == nullptr) + return UNZ_PARAMERROR; + s = (unz_s*) file; + pfile_in_zip_read_info = s->pfile_in_zip_read; + + if (pfile_in_zip_read_info == nullptr) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf == nullptr) + return (int) size_to_read; + + if (len > size_to_read) + read_now = (uInt) size_to_read; + else + read_now = (uInt) len; + + if (read_now == 0) + return 0; + + if (lufseek (pfile_in_zip_read_info->file, pfile_in_zip_read_info->offset_local_extrafield + pfile_in_zip_read_info->pos_local_extrafield, SEEK_SET) != 0) + return UNZ_ERRNO; + + if (lufread (buf, (uInt) size_to_read, 1, pfile_in_zip_read_info->file) != 1) + return UNZ_ERRNO; + + return (int) read_now; +} + +// Close the file in zip opened with unzipOpenCurrentFile +// Return UNZ_CRCERROR if all the file was read but the CRC is not good +int unzCloseCurrentFile (unzFile file) { + int err = UNZ_OK; + + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file == nullptr) + return UNZ_PARAMERROR; + s = (unz_s*) file; + pfile_in_zip_read_info = s->pfile_in_zip_read; + + if (pfile_in_zip_read_info == nullptr) + return UNZ_PARAMERROR; + + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err = UNZ_CRCERROR; + } + + + if (pfile_in_zip_read_info->read_buffer != 0) { + void *buf = pfile_in_zip_read_info->read_buffer; + zfree (buf); + pfile_in_zip_read_info->read_buffer = 0; + } + pfile_in_zip_read_info->read_buffer = nullptr; + if (pfile_in_zip_read_info->stream_initialised) + inflateEnd (&pfile_in_zip_read_info->stream); + + pfile_in_zip_read_info->stream_initialised = 0; + if (pfile_in_zip_read_info != 0) zfree (pfile_in_zip_read_info); // unused pfile_in_zip_read_info=0; + + s->pfile_in_zip_read = nullptr; + + return err; +} + + +// Get the global comment string of the ZipFile, in the szComment buffer. +// uSizeBuf is the size of the szComment buffer. +// return the number of byte copied or an error code <0 +int unzGetGlobalComment (unzFile file, char *szComment, uLong uSizeBuf) { //int err=UNZ_OK; + unz_s* s; + uLong uReadThis; + if (file == nullptr) return UNZ_PARAMERROR; + s = (unz_s*) file; + uReadThis = uSizeBuf; + if (uReadThis > s->gi.size_comment) uReadThis = s->gi.size_comment; + if (lufseek (s->file, s->central_pos + 22, SEEK_SET) != 0) return UNZ_ERRNO; + if (uReadThis > 0) { + *szComment = '\0'; + if (lufread (szComment, (uInt) uReadThis, 1, s->file) != 1) return UNZ_ERRNO; + } + if ((szComment) && (uSizeBuf > s->gi.size_comment)) *(szComment + s->gi.size_comment) = '\0'; + return (int) uReadThis; +} + + + + + +int unzOpenCurrentFile (unzFile file, const char *password); +int unzReadCurrentFile (unzFile file, void *buf, unsigned len); +int unzCloseCurrentFile (unzFile file); + + +typedef unsigned __int32 lutime_t; // define it ourselves since we don't include time.h + +FILETIME timet2filetime (const lutime_t t) { + LONGLONG i = Int32x32To64 (t, 10000000) + 116444736000000000; + FILETIME ft; + ft.dwLowDateTime = (DWORD) i; + ft.dwHighDateTime = (DWORD) (i >> 32); + return ft; +} + +FILETIME dosdatetime2filetime (WORD dosdate, WORD dostime) { // date: bits 0-4 are day of month 1-31. Bits 5-8 are month 1..12. Bits 9-15 are year-1980 + // time: bits 0-4 are seconds/2, bits 5-10 are minute 0..59. Bits 11-15 are hour 0..23 + SYSTEMTIME st; + st.wYear = (WORD) (((dosdate >> 9) & 0x7f) + 1980); + st.wMonth = (WORD) ((dosdate >> 5) & 0xf); + st.wDay = (WORD) (dosdate & 0x1f); + st.wHour = (WORD) ((dostime >> 11) & 0x1f); + st.wMinute = (WORD) ((dostime >> 5) & 0x3f); + st.wSecond = (WORD) ((dostime & 0x1f) * 2); + st.wMilliseconds = 0; + FILETIME ft; SystemTimeToFileTime (&st, &ft); + return ft; +} + + + +class TUnzip { +public: + TUnzip (const char *pwd): uf (0), unzbuf (0), currentfile (-1), czei (-1), password (0) { + if (pwd != 0) { + password = new char[strlen (pwd) + 1]; strcpy (password, pwd); + } + } + ~TUnzip () { + if (password != 0) delete[] password; password = 0; if (unzbuf != 0) delete[] unzbuf; unzbuf = 0; + } + + unzFile uf; int currentfile; ZIPENTRY cze; int czei; + char *password; + char *unzbuf; // lazily created and destroyed, used by Unzip + TCHAR rootdir[MAX_PATH]; // includes a trailing slash + + ZRESULT Open (void *z, unsigned int len, DWORD flags); + ZRESULT Get (int index, ZIPENTRY *ze); + ZRESULT Find (const TCHAR *name, bool ic, int *index, ZIPENTRY *ze); + ZRESULT Unzip (int index, void *dst, unsigned int len, DWORD flags); + ZRESULT SetUnzipBaseDir (const TCHAR *dir); + ZRESULT Close (); +}; + + +ZRESULT TUnzip::Open (void *z, unsigned int len, DWORD flags) { + if (uf != 0 || currentfile != -1) return ZR_NOTINITED; + // +#ifdef GetCurrentDirectory + GetCurrentDirectory (MAX_PATH, rootdir); +#else + _tcscpy (rootdir, _T ("\\")); +#endif + TCHAR lastchar = rootdir[_tcslen (rootdir) - 1]; + if (lastchar != '\\' && lastchar != '/') _tcscat (rootdir, _T ("\\")); + // + if (flags == ZIP_HANDLE) { // test if we can seek on it. We can't use GetFileType(h)==FILE_TYPE_DISK since it's not on CE. + DWORD res = SetFilePointer (z, 0, 0, FILE_CURRENT); + bool canseek = (res != 0xFFFFFFFF); + if (!canseek) return ZR_SEEK; + } + ZRESULT e; LUFILE *f = lufopen (z, len, flags, &e); + if (f == nullptr) return e; + uf = unzOpenInternal (f); + if (uf == 0) return ZR_NOFILE; + return ZR_OK; +} + +ZRESULT TUnzip::SetUnzipBaseDir (const TCHAR *dir) { + _tcscpy (rootdir, dir); + TCHAR lastchar = rootdir[_tcslen (rootdir) - 1]; + if (lastchar != '\\' && lastchar != '/') _tcscat (rootdir, _T ("\\")); + return ZR_OK; +} + +ZRESULT TUnzip::Get (int index, ZIPENTRY *ze) { + if (index < -1 || index >= (int) uf->gi.number_entry) return ZR_ARGS; + if (currentfile != -1) unzCloseCurrentFile (uf); currentfile = -1; + if (index == czei && index != -1) { + memcpy (ze, &cze, sizeof (ZIPENTRY)); return ZR_OK; + } + if (index == -1) { + ze->index = uf->gi.number_entry; + ze->name[0] = 0; + ze->attr = 0; + ze->atime.dwLowDateTime = 0; ze->atime.dwHighDateTime = 0; + ze->ctime.dwLowDateTime = 0; ze->ctime.dwHighDateTime = 0; + ze->mtime.dwLowDateTime = 0; ze->mtime.dwHighDateTime = 0; + ze->comp_size = 0; + ze->unc_size = 0; + return ZR_OK; + } + if (index < (int) uf->num_file) unzGoToFirstFile (uf); + while ((int) uf->num_file < index) unzGoToNextFile (uf); + unz_file_info ufi; char fn[MAX_PATH]; + unzGetCurrentFileInfo (uf, &ufi, fn, MAX_PATH, nullptr, 0, nullptr, 0); + // now get the extra header. We do this ourselves, instead of + // calling unzOpenCurrentFile &c., to avoid allocating more than necessary. + unsigned int extralen, iSizeVar; unsigned long offset; + int res = unzlocal_CheckCurrentFileCoherencyHeader (uf, &iSizeVar, &offset, &extralen); + if (res != UNZ_OK) return ZR_CORRUPT; + if (lufseek (uf->file, offset, SEEK_SET) != 0) return ZR_READ; + unsigned char *extra = new unsigned char[extralen]; + if (lufread (extra, 1, (uInt) extralen, uf->file) != extralen) { + delete[] extra; return ZR_READ; + } + // + ze->index = uf->num_file; + TCHAR tfn[MAX_PATH]; +#ifdef UNICODE + MultiByteToWideChar (CP_UTF8, 0, fn, -1, tfn, MAX_PATH); +#else + strcpy (tfn, fn); +#endif + // As a safety feature: if the zip filename had sneaky stuff + // like "c:\windows\file.txt" or "\windows\file.txt" or "fred\..\..\..\windows\file.txt" + // then we get rid of them all. That way, when the programmer does UnzipItem(hz,i,ze.name), + // it won't be a problem. (If the programmer really did want to get the full evil information, + // then they can edit out this security feature from here). + // In particular, we chop off any prefixes that are "c:\" or "\" or "/" or "[stuff]\.." or "[stuff]/.." + const TCHAR *sfn = tfn; + for (;;) { + if (sfn[0] != 0 && sfn[1] == ':') { + sfn += 2; continue; + } + if (sfn[0] == '\\') { + sfn++; continue; + } + if (sfn[0] == '/') { + sfn++; continue; + } + const TCHAR *c; + c = _tcsstr (sfn, _T ("\\..\\")); if (c != 0) { + sfn = c + 4; continue; + } + c = _tcsstr (sfn, _T ("\\../")); if (c != 0) { + sfn = c + 4; continue; + } + c = _tcsstr (sfn, _T ("/../")); if (c != 0) { + sfn = c + 4; continue; + } + c = _tcsstr (sfn, _T ("/..\\")); if (c != 0) { + sfn = c + 4; continue; + } + break; + } + _tcscpy (ze->name, sfn); + + + // zip has an 'attribute' 32bit value. Its lower half is windows stuff + // its upper half is standard unix stat.st_mode. We'll start trying + // to read it in unix mode + unsigned long a = ufi.external_fa; + bool isdir = (a & 0x40000000) != 0; + bool readonly = (a & 0x00800000) == 0; + //bool readable= (a&0x01000000)!=0; // unused + //bool executable=(a&0x00400000)!=0; // unused + bool hidden = false, system = false, archive = true; + // but in normal hostmodes these are overridden by the lower half... + int host = ufi.version >> 8; + if (host == 0 || host == 7 || host == 11 || host == 14) { + readonly = (a & 0x00000001) != 0; + hidden = (a & 0x00000002) != 0; + system = (a & 0x00000004) != 0; + isdir = (a & 0x00000010) != 0; + archive = (a & 0x00000020) != 0; + } + ze->attr = 0; + if (isdir) ze->attr |= FILE_ATTRIBUTE_DIRECTORY; + if (archive) ze->attr |= FILE_ATTRIBUTE_ARCHIVE; + if (hidden) ze->attr |= FILE_ATTRIBUTE_HIDDEN; + if (readonly) ze->attr |= FILE_ATTRIBUTE_READONLY; + if (system) ze->attr |= FILE_ATTRIBUTE_SYSTEM; + ze->comp_size = ufi.compressed_size; + ze->unc_size = ufi.uncompressed_size; + // + WORD dostime = (WORD) (ufi.dosDate & 0xFFFF); + WORD dosdate = (WORD) ((ufi.dosDate >> 16) & 0xFFFF); + FILETIME ftd = dosdatetime2filetime (dosdate, dostime); + FILETIME ft; LocalFileTimeToFileTime (&ftd, &ft); + ze->atime = ft; ze->ctime = ft; ze->mtime = ft; + // the zip will always have at least that dostime. But if it also has + // an extra header, then we'll instead get the info from that. + unsigned int epos = 0; + while (epos + 4 < extralen) { + char etype[3]; etype[0] = extra[epos + 0]; etype[1] = extra[epos + 1]; etype[2] = 0; + int size = extra[epos + 2]; + if (strcmp (etype, "UT") != 0) { + epos += 4 + size; continue; + } + int flags = extra[epos + 4]; + bool hasmtime = (flags & 1) != 0; + bool hasatime = (flags & 2) != 0; + bool hasctime = (flags & 4) != 0; + epos += 5; + if (hasmtime) { + lutime_t mtime = ((extra[epos + 0]) << 0) | ((extra[epos + 1]) << 8) | ((extra[epos + 2]) << 16) | ((extra[epos + 3]) << 24); + epos += 4; + ze->mtime = timet2filetime (mtime); + } + if (hasatime) { + lutime_t atime = ((extra[epos + 0]) << 0) | ((extra[epos + 1]) << 8) | ((extra[epos + 2]) << 16) | ((extra[epos + 3]) << 24); + epos += 4; + ze->atime = timet2filetime (atime); + } + if (hasctime) { + lutime_t ctime = ((extra[epos + 0]) << 0) | ((extra[epos + 1]) << 8) | ((extra[epos + 2]) << 16) | ((extra[epos + 3]) << 24); + epos += 4; + ze->ctime = timet2filetime (ctime); + } + break; + } + // + if (extra != 0) delete[] extra; + memcpy (&cze, ze, sizeof (ZIPENTRY)); czei = index; + return ZR_OK; +} + +ZRESULT TUnzip::Find (const TCHAR *tname, bool ic, int *index, ZIPENTRY *ze) { + char name[MAX_PATH]; +#ifdef UNICODE + WideCharToMultiByte (CP_UTF8, 0, tname, -1, name, MAX_PATH, 0, 0); +#else + strcpy (name, tname); +#endif + int res = unzLocateFile (uf, name, ic ? CASE_INSENSITIVE : CASE_SENSITIVE); + if (res != UNZ_OK) { + if (index != 0) *index = -1; + if (ze) { + ZeroMemory (ze, sizeof (ZIPENTRY)); ze->index = -1; + } + return ZR_NOTFOUND; + } + if (currentfile != -1) unzCloseCurrentFile (uf); currentfile = -1; + int i = (int) uf->num_file; + if (index) *index = i; + if (ze) { + ZRESULT zres = Get (i, ze); + if (zres != ZR_OK) return zres; + } + return ZR_OK; +} + +void EnsureDirectory (const TCHAR *rootdir, const TCHAR *dir) { + if (rootdir != 0 && GetFileAttributes (rootdir) == 0xFFFFFFFF) CreateDirectory (rootdir, 0); + if (*dir == 0) return; + const TCHAR *lastslash = dir, *c = lastslash; + while (*c != 0) { + if (*c == '/' || *c == '\\') lastslash = c; c++; + } + const TCHAR *name = lastslash; + if (lastslash != dir) { + TCHAR tmp[MAX_PATH]; memcpy (tmp, dir, sizeof (TCHAR)*(lastslash - dir)); + tmp[lastslash - dir] = 0; + EnsureDirectory (rootdir, tmp); + name++; + } + TCHAR cd[MAX_PATH]; *cd = 0; if (rootdir != 0) _tcscpy (cd, rootdir); _tcscat (cd, dir); + if (GetFileAttributes (cd) == 0xFFFFFFFF) CreateDirectory (cd, nullptr); +} + + + +ZRESULT TUnzip::Unzip (int index, void *dst, unsigned int len, DWORD flags) { + if (flags != ZIP_MEMORY && flags != ZIP_FILENAME && flags != ZIP_HANDLE) return ZR_ARGS; + if (flags == ZIP_MEMORY) { + if (index != currentfile) { + if (currentfile != -1) unzCloseCurrentFile (uf); currentfile = -1; + if (index >= (int) uf->gi.number_entry) return ZR_ARGS; + if (index < (int) uf->num_file) unzGoToFirstFile (uf); + while ((int) uf->num_file < index) unzGoToNextFile (uf); + unzOpenCurrentFile (uf, password); currentfile = index; + } + bool reached_eof; + int res = unzReadCurrentFile (uf, dst, len, &reached_eof); + if (res <= 0) { + unzCloseCurrentFile (uf); currentfile = -1; + } + if (reached_eof) return ZR_OK; + if (res > 0) return ZR_MORE; + if (res == UNZ_PASSWORD) return ZR_PASSWORD; + return ZR_FLATE; + } + // otherwise we're writing to a handle or a file + if (currentfile != -1) unzCloseCurrentFile (uf); currentfile = -1; + if (index >= (int) uf->gi.number_entry) return ZR_ARGS; + if (index < (int) uf->num_file) unzGoToFirstFile (uf); + while ((int) uf->num_file < index) unzGoToNextFile (uf); + ZIPENTRY ze; Get (index, &ze); + // zipentry=directory is handled specially + if ((ze.attr&FILE_ATTRIBUTE_DIRECTORY) != 0) { + if (flags == ZIP_HANDLE) return ZR_OK; // don't do anything + const TCHAR *dir = (const TCHAR*) dst; + bool isabsolute = (dir[0] == '/' || dir[0] == '\\' || (dir[0] != 0 && dir[1] == ':')); + if (isabsolute) EnsureDirectory (0, dir); else EnsureDirectory (rootdir, dir); + return ZR_OK; + } + // otherwise, we write the zipentry to a file/handle + HANDLE h; + if (flags == ZIP_HANDLE) h = dst; + else { + const TCHAR *ufn = (const TCHAR*) dst; + // We'll qualify all relative names to our root dir, and leave absolute names as they are + // ufn="zipfile.txt" dir="" name="zipfile.txt" fn="c:\\currentdir\\zipfile.txt" + // ufn="dir1/dir2/subfile.txt" dir="dir1/dir2/" name="subfile.txt" fn="c:\\currentdir\\dir1/dir2/subfiles.txt" + // ufn="\z\file.txt" dir="\z\" name="file.txt" fn="\z\file.txt" + // This might be a security risk, in the case where we just use the zipentry's name as "ufn", where + // a malicious zip could unzip itself into c:\windows. Our solution is that GetZipItem (which + // is how the user retrieve's the file's name within the zip) never returns absolute paths. + const TCHAR *name = ufn; const TCHAR *c = name; while (*c != 0) { + if (*c == '/' || *c == '\\') name = c + 1; c++; + } + TCHAR dir[MAX_PATH]; _tcscpy (dir, ufn); if (name == ufn) *dir = 0; else dir[name - ufn] = 0; + TCHAR fn[MAX_PATH]; + bool isabsolute = (dir[0] == '/' || dir[0] == '\\' || (dir[0] != 0 && dir[1] == ':')); + if (isabsolute) { + wsprintf (fn, _T ("%s%s"), dir, name); EnsureDirectory (0, dir); + } else { + wsprintf (fn, _T ("%s%s%s"), rootdir, dir, name); EnsureDirectory (rootdir, dir); + } + // + h = CreateFile (fn, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, ze.attr, nullptr); + } + if (h == INVALID_HANDLE_VALUE) return ZR_NOFILE; + unzOpenCurrentFile (uf, password); + if (unzbuf == 0) unzbuf = new char[16384]; DWORD haderr = 0; + // + + for (; haderr == 0;) { + bool reached_eof; + int res = unzReadCurrentFile (uf, unzbuf, 16384, &reached_eof); + if (res == UNZ_PASSWORD) { + haderr = ZR_PASSWORD; break; + } + if (res < 0) { + haderr = ZR_FLATE; break; + } + if (res > 0) { + DWORD writ; BOOL bres = WriteFile (h, unzbuf, res, &writ, nullptr); if (!bres) { + haderr = ZR_WRITE; break; + } + } + if (reached_eof) break; + if (res == 0) { + haderr = ZR_FLATE; break; + } + } + if (!haderr) SetFileTime (h, &ze.ctime, &ze.atime, &ze.mtime); // may fail if it was a pipe + if (flags != ZIP_HANDLE) CloseHandle (h); + unzCloseCurrentFile (uf); + if (haderr != 0) return haderr; + return ZR_OK; +} + +ZRESULT TUnzip::Close () { + if (currentfile != -1) unzCloseCurrentFile (uf); currentfile = -1; + if (uf != 0) unzClose (uf); uf = 0; + return ZR_OK; +} + + + + + +ZRESULT lasterrorU = ZR_OK; + +unsigned int FormatZipMessageU (ZRESULT code, TCHAR *buf, unsigned int len) { + if (code == ZR_RECENT) code = lasterrorU; + const TCHAR *msg = _T ("unknown zip result code"); + switch (code) { + case ZR_OK: msg = _T ("Success"); break; + case ZR_NODUPH: msg = _T ("Culdn't duplicate handle"); break; + case ZR_NOFILE: msg = _T ("Couldn't create/open file"); break; + case ZR_NOALLOC: msg = _T ("Failed to allocate memory"); break; + case ZR_WRITE: msg = _T ("Error writing to file"); break; + case ZR_NOTFOUND: msg = _T ("File not found in the zipfile"); break; + case ZR_MORE: msg = _T ("Still more data to unzip"); break; + case ZR_CORRUPT: msg = _T ("Zipfile is corrupt or not a zipfile"); break; + case ZR_READ: msg = _T ("Error reading file"); break; + case ZR_PASSWORD: msg = _T ("Correct password required"); break; + case ZR_ARGS: msg = _T ("Caller: faulty arguments"); break; + case ZR_PARTIALUNZ: msg = _T ("Caller: the file had already been partially unzipped"); break; + case ZR_NOTMMAP: msg = _T ("Caller: can only get memory of a memory zipfile"); break; + case ZR_MEMSIZE: msg = _T ("Caller: not enough space allocated for memory zipfile"); break; + case ZR_FAILED: msg = _T ("Caller: there was a previous error"); break; + case ZR_ENDED: msg = _T ("Caller: additions to the zip have already been ended"); break; + case ZR_ZMODE: msg = _T ("Caller: mixing creation and opening of zip"); break; + case ZR_NOTINITED: msg = _T ("Zip-bug: internal initialisation not completed"); break; + case ZR_SEEK: msg = _T ("Zip-bug: trying to seek the unseekable"); break; + case ZR_MISSIZE: msg = _T ("Zip-bug: the anticipated size turned out wrong"); break; + case ZR_NOCHANGE: msg = _T ("Zip-bug: tried to change mind, but not allowed"); break; + case ZR_FLATE: msg = _T ("Zip-bug: an internal error during flation"); break; + } + unsigned int mlen = (unsigned int) _tcslen (msg); + if (buf == 0 || len == 0) return mlen; + unsigned int n = mlen; if (n + 1 > len) n = len - 1; + _tcsncpy (buf, msg, n); buf[n] = 0; + return mlen; +} + + +typedef struct { + DWORD flag; + TUnzip *unz; +} TUnzipHandleData; + +HZIP OpenZipInternal (void *z, unsigned int len, DWORD flags, const char *password) { + TUnzip *unz = new TUnzip (password); + lasterrorU = unz->Open (z, len, flags); + if (lasterrorU != ZR_OK) { + delete unz; return 0; + } + TUnzipHandleData *han = new TUnzipHandleData; + han->flag = 1; han->unz = unz; return (HZIP) han; +} +HZIP OpenZipHandle (HANDLE h, const char *password) { + return OpenZipInternal ((void*) h, 0, ZIP_HANDLE, password); +} +HZIP OpenZip (const TCHAR *fn, const char *password) { + return OpenZipInternal ((void*) fn, 0, ZIP_FILENAME, password); +} +HZIP OpenZip (void *z, unsigned int len, const char *password) { + return OpenZipInternal (z, len, ZIP_MEMORY, password); +} + + +ZRESULT GetZipItem (HZIP hz, int index, ZIPENTRY *ze) { + ze->index = 0; *ze->name = 0; ze->unc_size = 0; + if (hz == 0) { + lasterrorU = ZR_ARGS; return ZR_ARGS; + } + TUnzipHandleData *han = (TUnzipHandleData*) hz; + if (han->flag != 1) { + lasterrorU = ZR_ZMODE; return ZR_ZMODE; + } + TUnzip *unz = han->unz; + lasterrorU = unz->Get (index, ze); + return lasterrorU; +} + +ZRESULT FindZipItem (HZIP hz, const TCHAR *name, bool ic, int *index, ZIPENTRY *ze) { + if (hz == 0) { + lasterrorU = ZR_ARGS; return ZR_ARGS; + } + TUnzipHandleData *han = (TUnzipHandleData*) hz; + if (han->flag != 1) { + lasterrorU = ZR_ZMODE; return ZR_ZMODE; + } + TUnzip *unz = han->unz; + lasterrorU = unz->Find (name, ic, index, ze); + return lasterrorU; +} + +ZRESULT UnzipItemInternal (HZIP hz, int index, void *dst, unsigned int len, DWORD flags) { + if (hz == 0) { + lasterrorU = ZR_ARGS; return ZR_ARGS; + } + TUnzipHandleData *han = (TUnzipHandleData*) hz; + if (han->flag != 1) { + lasterrorU = ZR_ZMODE; return ZR_ZMODE; + } + TUnzip *unz = han->unz; + lasterrorU = unz->Unzip (index, dst, len, flags); + return lasterrorU; +} +ZRESULT UnzipItemHandle (HZIP hz, int index, HANDLE h) { + return UnzipItemInternal (hz, index, (void*) h, 0, ZIP_HANDLE); +} +ZRESULT UnzipItem (HZIP hz, int index, const TCHAR *fn) { + return UnzipItemInternal (hz, index, (void*) fn, 0, ZIP_FILENAME); +} +ZRESULT UnzipItem (HZIP hz, int index, void *z, unsigned int len) { + return UnzipItemInternal (hz, index, z, len, ZIP_MEMORY); +} + +ZRESULT SetUnzipBaseDir (HZIP hz, const TCHAR *dir) { + if (hz == 0) { + lasterrorU = ZR_ARGS; return ZR_ARGS; + } + TUnzipHandleData *han = (TUnzipHandleData*) hz; + if (han->flag != 1) { + lasterrorU = ZR_ZMODE; return ZR_ZMODE; + } + TUnzip *unz = han->unz; + lasterrorU = unz->SetUnzipBaseDir (dir); + return lasterrorU; +} + + +ZRESULT CloseZipU (HZIP hz) { + if (hz == 0) { + lasterrorU = ZR_ARGS; return ZR_ARGS; + } + TUnzipHandleData *han = (TUnzipHandleData*) hz; + if (han->flag != 1) { + lasterrorU = ZR_ZMODE; return ZR_ZMODE; + } + TUnzip *unz = han->unz; + lasterrorU = unz->Close (); + delete unz; + delete han; + return lasterrorU; +} + +bool IsZipHandleU (HZIP hz) { + if (hz == 0) return false; + TUnzipHandleData *han = (TUnzipHandleData*) hz; + return (han->flag == 1); +} + + diff --git a/DuiLib/Utils/unzip.h b/DuiLib/Utils/unzip.h new file mode 100644 index 0000000..88532b9 --- /dev/null +++ b/DuiLib/Utils/unzip.h @@ -0,0 +1,214 @@ +#ifndef _unzip_H +#define _unzip_H + +// UNZIPPING functions -- for unzipping. +// This file is a repackaged form of extracts from the zlib code available +// at www.gzip.org/zlib, by Jean-Loup Gailly and Mark Adler. The original +// copyright notice may be found in unzip.cpp. The repackaging was done +// by Lucian Wischik to simplify and extend its use in Windows/C++. Also +// encryption and unicode filenames have been added. + + +#ifndef _zip_H +DECLARE_HANDLE (HZIP); +#endif +// An HZIP identifies a zip file that has been opened + +typedef DWORD ZRESULT; +// return codes from any of the zip functions. Listed later. + +typedef struct { + int index; // index of this file within the zip + TCHAR name[MAX_PATH]; // filename within the zip + DWORD attr; // attributes, as in GetFileAttributes. + FILETIME atime, ctime, mtime;// access, create, modify filetimes + long comp_size; // sizes of item, compressed and uncompressed. These + long unc_size; // may be -1 if not yet known (e.g. being streamed in) +} ZIPENTRY; + + +HZIP OpenZip (const TCHAR *fn, const char *password = nullptr); +HZIP OpenZip (void *z, unsigned int len, const char *password = nullptr); +HZIP OpenZipHandle (HANDLE h, const char *password); +// OpenZip - opens a zip file and returns a handle with which you can +// subsequently examine its contents. You can open a zip file from: +// from a pipe: OpenZipHandle(hpipe_read,0); +// from a file (by handle): OpenZipHandle(hfile,0); +// from a file (by name): OpenZip("c:\\test.zip","password"); +// from a memory block: OpenZip(bufstart, buflen,0); +// If the file is opened through a pipe, then items may only be +// accessed in increasing order, and an item may only be unzipped once, +// although GetZipItem can be called immediately before and after unzipping +// it. If it's opened in any other way, then full random access is possible. +// Note: pipe input is not yet implemented. +// Note: zip passwords are ascii, not unicode. +// Note: for windows-ce, you cannot close the handle until after CloseZip. +// but for real windows, the zip makes its own copy of your handle, so you +// can close yours anytime. + +ZRESULT GetZipItem (HZIP hz, int index, ZIPENTRY *ze); +// GetZipItem - call this to get information about an item in the zip. +// If index is -1 and the file wasn't opened through a pipe, +// then it returns information about the whole zipfile +// (and in particular ze.index returns the number of index items). +// Note: the item might be a directory (ze.attr & FILE_ATTRIBUTE_DIRECTORY) +// See below for notes on what happens when you unzip such an item. +// Note: if you are opening the zip through a pipe, then random access +// is not possible and GetZipItem(-1) fails and you can't discover the number +// of items except by calling GetZipItem on each one of them in turn, +// starting at 0, until eventually the call fails. Also, in the event that +// you are opening through a pipe and the zip was itself created into a pipe, +// then then comp_size and sometimes unc_size as well may not be known until +// after the item has been unzipped. + +ZRESULT FindZipItem (HZIP hz, const TCHAR *name, bool ic, int *index, ZIPENTRY *ze); +// FindZipItem - finds an item by name. ic means 'insensitive to case'. +// It returns the index of the item, and returns information about it. +// If nothing was found, then index is set to -1 and the function returns +// an error code. + +ZRESULT UnzipItem (HZIP hz, int index, const TCHAR *fn); +ZRESULT UnzipItem (HZIP hz, int index, void *z, unsigned int len); +ZRESULT UnzipItemHandle (HZIP hz, int index, HANDLE h); +// UnzipItem - given an index to an item, unzips it. You can unzip to: +// to a pipe: UnzipItemHandle(hz,i, hpipe_write); +// to a file (by handle): UnzipItemHandle(hz,i, hfile); +// to a file (by name): UnzipItem(hz,i, ze.name); +// to a memory block: UnzipItem(hz,i, buf,buflen); +// In the final case, if the buffer isn't large enough to hold it all, +// then the return code indicates that more is yet to come. If it was +// large enough, and you want to know precisely how big, GetZipItem. +// Note: zip files are normally stored with relative pathnames. If you +// unzip with ZIP_FILENAME a relative pathname then the item gets created +// relative to the current directory - it first ensures that all necessary +// subdirectories have been created. Also, the item may itself be a directory. +// If you unzip a directory with ZIP_FILENAME, then the directory gets created. +// If you unzip it to a handle or a memory block, then nothing gets created +// and it emits 0 bytes. +ZRESULT SetUnzipBaseDir (HZIP hz, const TCHAR *dir); +// if unzipping to a filename, and it's a relative filename, then it will be relative to here. +// (defaults to current-directory). + + +ZRESULT CloseZip (HZIP hz); +// CloseZip - the zip handle must be closed with this function. + +unsigned int FormatZipMessage (ZRESULT code, TCHAR *buf, unsigned int len); +// FormatZipMessage - given an error code, formats it as a string. +// It returns the length of the error message. If buf/len points +// to a real buffer, then it also writes as much as possible into there. + + +// These are the result codes: +#define ZR_OK 0x00000000 // nb. the pseudo-code zr-recent is never returned, +#define ZR_RECENT 0x00000001 // but can be passed to FormatZipMessage. +// The following come from general system stuff (e.g. files not openable) +#define ZR_GENMASK 0x0000FF00 +#define ZR_NODUPH 0x00000100 // couldn't duplicate the handle +#define ZR_NOFILE 0x00000200 // couldn't create/open the file +#define ZR_NOALLOC 0x00000300 // failed to allocate some resource +#define ZR_WRITE 0x00000400 // a general error writing to the file +#define ZR_NOTFOUND 0x00000500 // couldn't find that file in the zip +#define ZR_MORE 0x00000600 // there's still more data to be unzipped +#define ZR_CORRUPT 0x00000700 // the zipfile is corrupt or not a zipfile +#define ZR_READ 0x00000800 // a general error reading the file +#define ZR_PASSWORD 0x00001000 // we didn't get the right password to unzip the file +// The following come from mistakes on the part of the caller +#define ZR_CALLERMASK 0x00FF0000 +#define ZR_ARGS 0x00010000 // general mistake with the arguments +#define ZR_NOTMMAP 0x00020000 // tried to ZipGetMemory, but that only works on mmap zipfiles, which yours wasn't +#define ZR_MEMSIZE 0x00030000 // the memory size is too small +#define ZR_FAILED 0x00040000 // the thing was already failed when you called this function +#define ZR_ENDED 0x00050000 // the zip creation has already been closed +#define ZR_MISSIZE 0x00060000 // the indicated input file size turned out mistaken +#define ZR_PARTIALUNZ 0x00070000 // the file had already been partially unzipped +#define ZR_ZMODE 0x00080000 // tried to mix creating/opening a zip +// The following come from bugs within the zip library itself +#define ZR_BUGMASK 0xFF000000 +#define ZR_NOTINITED 0x01000000 // initialisation didn't work +#define ZR_SEEK 0x02000000 // trying to seek in an unseekable file +#define ZR_NOCHANGE 0x04000000 // changed its mind on storage, but not allowed +#define ZR_FLATE 0x05000000 // an internal error in the de/inflation code + + + + + +// e.g. +// +// SetCurrentDirectory("c:\\docs\\stuff"); +// HZIP hz = OpenZip("c:\\stuff.zip",0); +// ZIPENTRY ze; GetZipItem(hz,-1,&ze); int numitems=ze.index; +// for (int i=0; i + + + + + + + + + + + + + + + + + + + + + $(MSBuildThisFileDirectory) + + + $(ProjectDir)VC-LTL + + + $(SolutionDir)VC-LTL + + + $(ProjectDir)..\VC-LTL + + + $(SolutionDir)..\VC-LTL + + + $(Registry:HKEY_CURRENT_USER\Code\VC-LTL@Root) + + + + + + + + + \ No newline at end of file diff --git a/NetToolbox.sln b/NetToolbox.sln new file mode 100644 index 0000000..27bcd0b --- /dev/null +++ b/NetToolbox.sln @@ -0,0 +1,65 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2016 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NetToolbox", "NetToolbox\NetToolbox.vcxproj", "{3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}" + ProjectSection(ProjectDependencies) = postProject + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E} = {E106ACD7-4E53-4AEE-942B-D0DD426DB34E} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DuiLib", "DuiLib\DuiLib.vcxproj", "{E106ACD7-4E53-4AEE-942B-D0DD426DB34E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93B1A62E-549E-4D52-86AB-3767BDD2F101}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + LTL|Any CPU = LTL|Any CPU + LTL|x64 = LTL|x64 + LTL|x86 = LTL|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.Debug|x64.ActiveCfg = Debug|x64 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.Debug|x64.Build.0 = Debug|x64 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.Debug|x86.ActiveCfg = Debug|Win32 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.Debug|x86.Build.0 = Debug|Win32 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.LTL|Any CPU.ActiveCfg = LTL|Win32 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.LTL|x64.ActiveCfg = LTL|x64 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.LTL|x64.Build.0 = LTL|x64 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.LTL|x86.ActiveCfg = LTL|Win32 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.LTL|x86.Build.0 = LTL|Win32 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.Release|Any CPU.ActiveCfg = Release|Win32 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.Release|x64.ActiveCfg = Release|x64 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.Release|x64.Build.0 = Release|x64 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.Release|x86.ActiveCfg = Release|Win32 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26}.Release|x86.Build.0 = Release|Win32 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.Debug|x64.ActiveCfg = SDebug|x64 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.Debug|x64.Build.0 = SDebug|x64 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.Debug|x86.ActiveCfg = SDebug|Win32 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.Debug|x86.Build.0 = SDebug|Win32 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.LTL|Any CPU.ActiveCfg = LTL|Win32 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.LTL|x64.ActiveCfg = LTL|x64 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.LTL|x64.Build.0 = LTL|x64 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.LTL|x86.ActiveCfg = LTL|Win32 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.LTL|x86.Build.0 = LTL|Win32 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.Release|Any CPU.ActiveCfg = Release|Win32 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.Release|x64.ActiveCfg = SRelease|x64 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.Release|x64.Build.0 = SRelease|x64 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.Release|x86.ActiveCfg = SRelease|Win32 + {E106ACD7-4E53-4AEE-942B-D0DD426DB34E}.Release|x86.Build.0 = SRelease|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {00015C7D-7C41-4CD2-B0D2-7694D150C2AC} + EndGlobalSection +EndGlobal diff --git a/NetToolbox/3rdparty/Simple-Web-Server b/NetToolbox/3rdparty/Simple-Web-Server new file mode 160000 index 0000000..0dccc11 --- /dev/null +++ b/NetToolbox/3rdparty/Simple-Web-Server @@ -0,0 +1 @@ +Subproject commit 0dccc1154485eb8f08c10f0daa1c5a4681451db6 diff --git a/NetToolbox/3rdparty/serial/list_ports_win.cc b/NetToolbox/3rdparty/serial/list_ports_win.cc new file mode 100644 index 0000000..abfddae --- /dev/null +++ b/NetToolbox/3rdparty/serial/list_ports_win.cc @@ -0,0 +1,152 @@ +#if defined(_WIN32) + +/* + * Copyright (c) 2014 Craig Lilley + * This software is made available under the terms of the MIT licence. + * A copy of the licence can be obtained from: + * http://opensource.org/licenses/MIT + */ + +#include "serial.h" +#include +#include +#include +#include +#include +#include + +using serial::PortInfo; +using std::vector; +using std::string; + +static const DWORD port_name_max_length = 256; +static const DWORD friendly_name_max_length = 256; +static const DWORD hardware_id_max_length = 256; + +// Convert a wide Unicode string to an UTF8 string +std::string utf8_encode(const std::wstring &wstr) +{ + int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); + std::string strTo( size_needed, 0 ); + WideCharToMultiByte (CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); + return strTo; +} + +vector +serial::list_ports() +{ + vector devices_found; + + HDEVINFO device_info_set = SetupDiGetClassDevs( + (const GUID *) &GUID_DEVCLASS_PORTS, + NULL, + NULL, + DIGCF_PRESENT); + + unsigned int device_info_set_index = 0; + SP_DEVINFO_DATA device_info_data; + + device_info_data.cbSize = sizeof(SP_DEVINFO_DATA); + + while(SetupDiEnumDeviceInfo(device_info_set, device_info_set_index, &device_info_data)) + { + device_info_set_index++; + + // Get port name + + HKEY hkey = SetupDiOpenDevRegKey( + device_info_set, + &device_info_data, + DICS_FLAG_GLOBAL, + 0, + DIREG_DEV, + KEY_READ); + + TCHAR port_name[port_name_max_length]; + DWORD port_name_length = port_name_max_length; + + LONG return_code = RegQueryValueEx( + hkey, + _T("PortName"), + NULL, + NULL, + (LPBYTE)port_name, + &port_name_length); + + RegCloseKey(hkey); + + if(return_code != EXIT_SUCCESS) + continue; + + if(port_name_length > 0 && port_name_length <= port_name_max_length) + port_name[port_name_length-1] = '\0'; + else + port_name[0] = '\0'; + + // Ignore parallel ports + + if(_tcsstr(port_name, _T("LPT")) != NULL) + continue; + + // Get port friendly name + + TCHAR friendly_name[friendly_name_max_length]; + DWORD friendly_name_actual_length = 0; + + BOOL got_friendly_name = SetupDiGetDeviceRegistryProperty( + device_info_set, + &device_info_data, + SPDRP_FRIENDLYNAME, + NULL, + (PBYTE)friendly_name, + friendly_name_max_length, + &friendly_name_actual_length); + + if(got_friendly_name == TRUE && friendly_name_actual_length > 0) + friendly_name[friendly_name_actual_length-1] = '\0'; + else + friendly_name[0] = '\0'; + + // Get hardware ID + + TCHAR hardware_id[hardware_id_max_length]; + DWORD hardware_id_actual_length = 0; + + BOOL got_hardware_id = SetupDiGetDeviceRegistryProperty( + device_info_set, + &device_info_data, + SPDRP_HARDWAREID, + NULL, + (PBYTE)hardware_id, + hardware_id_max_length, + &hardware_id_actual_length); + + if(got_hardware_id == TRUE && hardware_id_actual_length > 0) + hardware_id[hardware_id_actual_length-1] = '\0'; + else + hardware_id[0] = '\0'; + + #ifdef UNICODE + std::string portName = utf8_encode(port_name); + std::string friendlyName = utf8_encode(friendly_name); + std::string hardwareId = utf8_encode(hardware_id); + #else + std::string portName = port_name; + std::string friendlyName = friendly_name; + std::string hardwareId = hardware_id; + #endif + + PortInfo port_entry; + port_entry.port = portName; + port_entry.description = friendlyName; + port_entry.hardware_id = hardwareId; + + devices_found.push_back(port_entry); + } + + SetupDiDestroyDeviceInfoList(device_info_set); + + return devices_found; +} + +#endif // #if defined(_WIN32) diff --git a/NetToolbox/3rdparty/serial/serial.cc b/NetToolbox/3rdparty/serial/serial.cc new file mode 100644 index 0000000..7cad1dc --- /dev/null +++ b/NetToolbox/3rdparty/serial/serial.cc @@ -0,0 +1,414 @@ +/* Copyright 2012 William Woodall and John Harrison */ +#include + +#if !defined(_WIN32) && !defined(__OpenBSD__) && !defined(__FreeBSD__) +# include +#endif + +#if defined (__MINGW32__) +# define alloca __builtin_alloca +#endif + +#include "serial.h" + +#ifdef _WIN32 +#include "win.h" +#else +#include "unix.h" +#endif + +using std::invalid_argument; +using std::min; +using std::numeric_limits; +using std::vector; +using std::size_t; +using std::string; + +using serial::Serial; +using serial::SerialException; +using serial::IOException; +using serial::bytesize_t; +using serial::parity_t; +using serial::stopbits_t; +using serial::flowcontrol_t; + +class Serial::ScopedReadLock { +public: + ScopedReadLock(SerialImpl *pimpl) : pimpl_(pimpl) { + this->pimpl_->readLock(); + } + ~ScopedReadLock() { + this->pimpl_->readUnlock(); + } +private: + // Disable copy constructors + ScopedReadLock(const ScopedReadLock&); + const ScopedReadLock& operator=(ScopedReadLock); + + SerialImpl *pimpl_; +}; + +class Serial::ScopedWriteLock { +public: + ScopedWriteLock(SerialImpl *pimpl) : pimpl_(pimpl) { + this->pimpl_->writeLock(); + } + ~ScopedWriteLock() { + this->pimpl_->writeUnlock(); + } +private: + // Disable copy constructors + ScopedWriteLock(const ScopedWriteLock&); + const ScopedWriteLock& operator=(ScopedWriteLock); + SerialImpl *pimpl_; +}; + +Serial::Serial (const string &port, uint32_t baudrate, serial::Timeout timeout, + bytesize_t bytesize, parity_t parity, stopbits_t stopbits, + flowcontrol_t flowcontrol) + : pimpl_(new SerialImpl (port, baudrate, bytesize, parity, + stopbits, flowcontrol)) +{ + pimpl_->setTimeout(timeout); +} + +Serial::~Serial () +{ + delete pimpl_; +} + +void +Serial::open () +{ + pimpl_->open (); +} + +void +Serial::close () +{ + pimpl_->close (); +} + +bool +Serial::isOpen () const +{ + return pimpl_->isOpen (); +} + +size_t +Serial::available () +{ + return pimpl_->available (); +} + +bool +Serial::waitReadable () +{ + serial::Timeout timeout(pimpl_->getTimeout ()); + return pimpl_->waitReadable(timeout.read_timeout_constant); +} + +void +Serial::waitByteTimes (size_t count) +{ + pimpl_->waitByteTimes(count); +} + +size_t +Serial::read_ (uint8_t *buffer, size_t size) +{ + return this->pimpl_->read (buffer, size); +} + +size_t +Serial::read (uint8_t *buffer, size_t size) +{ + ScopedReadLock lock(this->pimpl_); + return this->pimpl_->read (buffer, size); +} + +size_t +Serial::read (std::vector &buffer, size_t size) +{ + ScopedReadLock lock(this->pimpl_); + uint8_t *buffer_ = new uint8_t[size]; + size_t bytes_read = this->pimpl_->read (buffer_, size); + buffer.insert (buffer.end (), buffer_, buffer_+bytes_read); + delete[] buffer_; + return bytes_read; +} + +size_t +Serial::read (std::string &buffer, size_t size) +{ + ScopedReadLock lock(this->pimpl_); + uint8_t *buffer_ = new uint8_t[size]; + size_t bytes_read = this->pimpl_->read (buffer_, size); + buffer.append (reinterpret_cast(buffer_), bytes_read); + delete[] buffer_; + return bytes_read; +} + +string +Serial::read (size_t size) +{ + std::string buffer; + this->read (buffer, size); + return buffer; +} + +size_t +Serial::readline (string &buffer, size_t size, string eol) +{ + ScopedReadLock lock(this->pimpl_); + size_t eol_len = eol.length (); + uint8_t *buffer_ = static_cast + (alloca (size * sizeof (uint8_t))); + size_t read_so_far = 0; + while (true) + { + size_t bytes_read = this->read_ (buffer_ + read_so_far, 1); + read_so_far += bytes_read; + if (bytes_read == 0) { + break; // Timeout occured on reading 1 byte + } + if (string (reinterpret_cast + (buffer_ + read_so_far - eol_len), eol_len) == eol) { + break; // EOL found + } + if (read_so_far == size) { + break; // Reached the maximum read length + } + } + buffer.append(reinterpret_cast (buffer_), read_so_far); + return read_so_far; +} + +string +Serial::readline (size_t size, string eol) +{ + std::string buffer; + this->readline (buffer, size, eol); + return buffer; +} + +vector +Serial::readlines (size_t size, string eol) +{ + ScopedReadLock lock(this->pimpl_); + std::vector lines; + size_t eol_len = eol.length (); + uint8_t *buffer_ = static_cast + (alloca (size * sizeof (uint8_t))); + size_t read_so_far = 0; + size_t start_of_line = 0; + while (read_so_far < size) { + size_t bytes_read = this->read_ (buffer_+read_so_far, 1); + read_so_far += bytes_read; + if (bytes_read == 0) { + if (start_of_line != read_so_far) { + lines.push_back ( + string (reinterpret_cast (buffer_ + start_of_line), + read_so_far - start_of_line)); + } + break; // Timeout occured on reading 1 byte + } + if (string (reinterpret_cast + (buffer_ + read_so_far - eol_len), eol_len) == eol) { + // EOL found + lines.push_back( + string(reinterpret_cast (buffer_ + start_of_line), + read_so_far - start_of_line)); + start_of_line = read_so_far; + } + if (read_so_far == size) { + if (start_of_line != read_so_far) { + lines.push_back( + string(reinterpret_cast (buffer_ + start_of_line), + read_so_far - start_of_line)); + } + break; // Reached the maximum read length + } + } + return lines; +} + +size_t +Serial::write (const string &data) +{ + ScopedWriteLock lock(this->pimpl_); + return this->write_ (reinterpret_cast(data.c_str()), + data.length()); +} + +size_t +Serial::write (const std::vector &data) +{ + ScopedWriteLock lock(this->pimpl_); + return this->write_ (&data[0], data.size()); +} + +size_t +Serial::write (const uint8_t *data, size_t size) +{ + ScopedWriteLock lock(this->pimpl_); + return this->write_(data, size); +} + +size_t +Serial::write_ (const uint8_t *data, size_t length) +{ + return pimpl_->write (data, length); +} + +void +Serial::setPort (const string &port) +{ + ScopedReadLock rlock(this->pimpl_); + ScopedWriteLock wlock(this->pimpl_); + bool was_open = pimpl_->isOpen (); + if (was_open) close(); + pimpl_->setPort (port); + if (was_open) open (); +} + +string +Serial::getPort () const +{ + return pimpl_->getPort (); +} + +void +Serial::setTimeout (serial::Timeout &timeout) +{ + pimpl_->setTimeout (timeout); +} + +serial::Timeout +Serial::getTimeout () const { + return pimpl_->getTimeout (); +} + +void +Serial::setBaudrate (uint32_t baudrate) +{ + pimpl_->setBaudrate (baudrate); +} + +uint32_t +Serial::getBaudrate () const +{ + return uint32_t(pimpl_->getBaudrate ()); +} + +void +Serial::setBytesize (bytesize_t bytesize) +{ + pimpl_->setBytesize (bytesize); +} + +bytesize_t +Serial::getBytesize () const +{ + return pimpl_->getBytesize (); +} + +void +Serial::setParity (parity_t parity) +{ + pimpl_->setParity (parity); +} + +parity_t +Serial::getParity () const +{ + return pimpl_->getParity (); +} + +void +Serial::setStopbits (stopbits_t stopbits) +{ + pimpl_->setStopbits (stopbits); +} + +stopbits_t +Serial::getStopbits () const +{ + return pimpl_->getStopbits (); +} + +void +Serial::setFlowcontrol (flowcontrol_t flowcontrol) +{ + pimpl_->setFlowcontrol (flowcontrol); +} + +flowcontrol_t +Serial::getFlowcontrol () const +{ + return pimpl_->getFlowcontrol (); +} + +void Serial::flush () +{ + ScopedReadLock rlock(this->pimpl_); + ScopedWriteLock wlock(this->pimpl_); + pimpl_->flush (); +} + +void Serial::flushInput () +{ + ScopedReadLock lock(this->pimpl_); + pimpl_->flushInput (); +} + +void Serial::flushOutput () +{ + ScopedWriteLock lock(this->pimpl_); + pimpl_->flushOutput (); +} + +void Serial::sendBreak (int duration) +{ + pimpl_->sendBreak (duration); +} + +void Serial::setBreak (bool level) +{ + pimpl_->setBreak (level); +} + +void Serial::setRTS (bool level) +{ + pimpl_->setRTS (level); +} + +void Serial::setDTR (bool level) +{ + pimpl_->setDTR (level); +} + +bool Serial::waitForChange() +{ + return pimpl_->waitForChange(); +} + +bool Serial::getCTS () +{ + return pimpl_->getCTS (); +} + +bool Serial::getDSR () +{ + return pimpl_->getDSR (); +} + +bool Serial::getRI () +{ + return pimpl_->getRI (); +} + +bool Serial::getCD () +{ + return pimpl_->getCD (); +} diff --git a/NetToolbox/3rdparty/serial/serial.h b/NetToolbox/3rdparty/serial/serial.h new file mode 100644 index 0000000..46aa055 --- /dev/null +++ b/NetToolbox/3rdparty/serial/serial.h @@ -0,0 +1,775 @@ +/*! + * \file serial/serial.h + * \author William Woodall + * \author John Harrison + * \version 0.1 + * + * \section LICENSE + * + * The MIT License + * + * Copyright (c) 2012 William Woodall + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * \section DESCRIPTION + * + * This provides a cross platform interface for interacting with Serial Ports. + */ + +#ifndef SERIAL_H +#define SERIAL_H + +#include +#include +#include +#include +#include +#include +#include +#include "v8stdint.h" + +#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, \ +__LINE__, (message) ) + +namespace serial { + +/*! + * Enumeration defines the possible bytesizes for the serial port. + */ +typedef enum { + fivebits = 5, + sixbits = 6, + sevenbits = 7, + eightbits = 8 +} bytesize_t; + +/*! + * Enumeration defines the possible parity types for the serial port. + */ +typedef enum { + parity_none = 0, + parity_odd = 1, + parity_even = 2, + parity_mark = 3, + parity_space = 4 +} parity_t; + +/*! + * Enumeration defines the possible stopbit types for the serial port. + */ +typedef enum { + stopbits_one = 1, + stopbits_two = 2, + stopbits_one_point_five +} stopbits_t; + +/*! + * Enumeration defines the possible flowcontrol types for the serial port. + */ +typedef enum { + flowcontrol_none = 0, + flowcontrol_software, + flowcontrol_hardware +} flowcontrol_t; + +/*! + * Structure for setting the timeout of the serial port, times are + * in milliseconds. + * + * In order to disable the interbyte timeout, set it to Timeout::max(). + */ +struct Timeout { +#ifdef max +# undef max +#endif + static uint32_t max() {return std::numeric_limits::max();} + /*! + * Convenience function to generate Timeout structs using a + * single absolute timeout. + * + * \param timeout A long that defines the time in milliseconds until a + * timeout occurs after a call to read or write is made. + * + * \return Timeout struct that represents this simple timeout provided. + */ + static Timeout simpleTimeout(uint32_t timeout) { + return Timeout(max(), timeout, 0, timeout, 0); + } + + /*! Number of milliseconds between bytes received to timeout on. */ + uint32_t inter_byte_timeout; + /*! A constant number of milliseconds to wait after calling read. */ + uint32_t read_timeout_constant; + /*! A multiplier against the number of requested bytes to wait after + * calling read. + */ + uint32_t read_timeout_multiplier; + /*! A constant number of milliseconds to wait after calling write. */ + uint32_t write_timeout_constant; + /*! A multiplier against the number of requested bytes to wait after + * calling write. + */ + uint32_t write_timeout_multiplier; + + explicit Timeout (uint32_t inter_byte_timeout_=0, + uint32_t read_timeout_constant_=0, + uint32_t read_timeout_multiplier_=0, + uint32_t write_timeout_constant_=0, + uint32_t write_timeout_multiplier_=0) + : inter_byte_timeout(inter_byte_timeout_), + read_timeout_constant(read_timeout_constant_), + read_timeout_multiplier(read_timeout_multiplier_), + write_timeout_constant(write_timeout_constant_), + write_timeout_multiplier(write_timeout_multiplier_) + {} +}; + +/*! + * Class that provides a portable serial port interface. + */ +class Serial { +public: + /*! + * Creates a Serial object and opens the port if a port is specified, + * otherwise it remains closed until serial::Serial::open is called. + * + * \param port A std::string containing the address of the serial port, + * which would be something like 'COM1' on Windows and '/dev/ttyS0' + * on Linux. + * + * \param baudrate An unsigned 32-bit integer that represents the baudrate + * + * \param timeout A serial::Timeout struct that defines the timeout + * conditions for the serial port. \see serial::Timeout + * + * \param bytesize Size of each byte in the serial transmission of data, + * default is eightbits, possible values are: fivebits, sixbits, sevenbits, + * eightbits + * + * \param parity Method of parity, default is parity_none, possible values + * are: parity_none, parity_odd, parity_even + * + * \param stopbits Number of stop bits used, default is stopbits_one, + * possible values are: stopbits_one, stopbits_one_point_five, stopbits_two + * + * \param flowcontrol Type of flowcontrol used, default is + * flowcontrol_none, possible values are: flowcontrol_none, + * flowcontrol_software, flowcontrol_hardware + * + * \throw serial::PortNotOpenedException + * \throw serial::IOException + * \throw std::invalid_argument + */ + Serial (const std::string &port = "", + uint32_t baudrate = 9600, + Timeout timeout = Timeout(), + bytesize_t bytesize = eightbits, + parity_t parity = parity_none, + stopbits_t stopbits = stopbits_one, + flowcontrol_t flowcontrol = flowcontrol_none); + + /*! Destructor */ + virtual ~Serial (); + + /*! + * Opens the serial port as long as the port is set and the port isn't + * already open. + * + * If the port is provided to the constructor then an explicit call to open + * is not needed. + * + * \see Serial::Serial + * + * \throw std::invalid_argument + * \throw serial::SerialException + * \throw serial::IOException + */ + void + open (); + + /*! Gets the open status of the serial port. + * + * \return Returns true if the port is open, false otherwise. + */ + bool + isOpen () const; + + /*! Closes the serial port. */ + void + close (); + + /*! Return the number of characters in the buffer. */ + size_t + available (); + + /*! Block until there is serial data to read or read_timeout_constant + * number of milliseconds have elapsed. The return value is true when + * the function exits with the port in a readable state, false otherwise + * (due to timeout or select interruption). */ + bool + waitReadable (); + + /*! Block for a period of time corresponding to the transmission time of + * count characters at present serial settings. This may be used in con- + * junction with waitReadable to read larger blocks of data from the + * port. */ + void + waitByteTimes (size_t count); + + /*! Read a given amount of bytes from the serial port into a given buffer. + * + * The read function will return in one of three cases: + * * The number of requested bytes was read. + * * In this case the number of bytes requested will match the size_t + * returned by read. + * * A timeout occurred, in this case the number of bytes read will not + * match the amount requested, but no exception will be thrown. One of + * two possible timeouts occurred: + * * The inter byte timeout expired, this means that number of + * milliseconds elapsed between receiving bytes from the serial port + * exceeded the inter byte timeout. + * * The total timeout expired, which is calculated by multiplying the + * read timeout multiplier by the number of requested bytes and then + * added to the read timeout constant. If that total number of + * milliseconds elapses after the initial call to read a timeout will + * occur. + * * An exception occurred, in this case an actual exception will be thrown. + * + * \param buffer An uint8_t array of at least the requested size. + * \param size A size_t defining how many bytes to be read. + * + * \return A size_t representing the number of bytes read as a result of the + * call to read. + * + * \throw serial::PortNotOpenedException + * \throw serial::SerialException + */ + size_t + read (uint8_t *buffer, size_t size); + + /*! Read a given amount of bytes from the serial port into a give buffer. + * + * \param buffer A reference to a std::vector of uint8_t. + * \param size A size_t defining how many bytes to be read. + * + * \return A size_t representing the number of bytes read as a result of the + * call to read. + * + * \throw serial::PortNotOpenedException + * \throw serial::SerialException + */ + size_t + read (std::vector &buffer, size_t size = 1); + + /*! Read a given amount of bytes from the serial port into a give buffer. + * + * \param buffer A reference to a std::string. + * \param size A size_t defining how many bytes to be read. + * + * \return A size_t representing the number of bytes read as a result of the + * call to read. + * + * \throw serial::PortNotOpenedException + * \throw serial::SerialException + */ + size_t + read (std::string &buffer, size_t size = 1); + + /*! Read a given amount of bytes from the serial port and return a string + * containing the data. + * + * \param size A size_t defining how many bytes to be read. + * + * \return A std::string containing the data read from the port. + * + * \throw serial::PortNotOpenedException + * \throw serial::SerialException + */ + std::string + read (size_t size = 1); + + /*! Reads in a line or until a given delimiter has been processed. + * + * Reads from the serial port until a single line has been read. + * + * \param buffer A std::string reference used to store the data. + * \param size A maximum length of a line, defaults to 65536 (2^16) + * \param eol A string to match against for the EOL. + * + * \return A size_t representing the number of bytes read. + * + * \throw serial::PortNotOpenedException + * \throw serial::SerialException + */ + size_t + readline (std::string &buffer, size_t size = 65536, std::string eol = "\n"); + + /*! Reads in a line or until a given delimiter has been processed. + * + * Reads from the serial port until a single line has been read. + * + * \param size A maximum length of a line, defaults to 65536 (2^16) + * \param eol A string to match against for the EOL. + * + * \return A std::string containing the line. + * + * \throw serial::PortNotOpenedException + * \throw serial::SerialException + */ + std::string + readline (size_t size = 65536, std::string eol = "\n"); + + /*! Reads in multiple lines until the serial port times out. + * + * This requires a timeout > 0 before it can be run. It will read until a + * timeout occurs and return a list of strings. + * + * \param size A maximum length of combined lines, defaults to 65536 (2^16) + * + * \param eol A string to match against for the EOL. + * + * \return A vector containing the lines. + * + * \throw serial::PortNotOpenedException + * \throw serial::SerialException + */ + std::vector + readlines (size_t size = 65536, std::string eol = "\n"); + + /*! Write a string to the serial port. + * + * \param data A const reference containing the data to be written + * to the serial port. + * + * \param size A size_t that indicates how many bytes should be written from + * the given data buffer. + * + * \return A size_t representing the number of bytes actually written to + * the serial port. + * + * \throw serial::PortNotOpenedException + * \throw serial::SerialException + * \throw serial::IOException + */ + size_t + write (const uint8_t *data, size_t size); + + /*! Write a string to the serial port. + * + * \param data A const reference containing the data to be written + * to the serial port. + * + * \return A size_t representing the number of bytes actually written to + * the serial port. + * + * \throw serial::PortNotOpenedException + * \throw serial::SerialException + * \throw serial::IOException + */ + size_t + write (const std::vector &data); + + /*! Write a string to the serial port. + * + * \param data A const reference containing the data to be written + * to the serial port. + * + * \return A size_t representing the number of bytes actually written to + * the serial port. + * + * \throw serial::PortNotOpenedException + * \throw serial::SerialException + * \throw serial::IOException + */ + size_t + write (const std::string &data); + + /*! Sets the serial port identifier. + * + * \param port A const std::string reference containing the address of the + * serial port, which would be something like 'COM1' on Windows and + * '/dev/ttyS0' on Linux. + * + * \throw std::invalid_argument + */ + void + setPort (const std::string &port); + + /*! Gets the serial port identifier. + * + * \see Serial::setPort + * + * \throw std::invalid_argument + */ + std::string + getPort () const; + + /*! Sets the timeout for reads and writes using the Timeout struct. + * + * There are two timeout conditions described here: + * * The inter byte timeout: + * * The inter_byte_timeout component of serial::Timeout defines the + * maximum amount of time, in milliseconds, between receiving bytes on + * the serial port that can pass before a timeout occurs. Setting this + * to zero will prevent inter byte timeouts from occurring. + * * Total time timeout: + * * The constant and multiplier component of this timeout condition, + * for both read and write, are defined in serial::Timeout. This + * timeout occurs if the total time since the read or write call was + * made exceeds the specified time in milliseconds. + * * The limit is defined by multiplying the multiplier component by the + * number of requested bytes and adding that product to the constant + * component. In this way if you want a read call, for example, to + * timeout after exactly one second regardless of the number of bytes + * you asked for then set the read_timeout_constant component of + * serial::Timeout to 1000 and the read_timeout_multiplier to zero. + * This timeout condition can be used in conjunction with the inter + * byte timeout condition with out any problems, timeout will simply + * occur when one of the two timeout conditions is met. This allows + * users to have maximum control over the trade-off between + * responsiveness and efficiency. + * + * Read and write functions will return in one of three cases. When the + * reading or writing is complete, when a timeout occurs, or when an + * exception occurs. + * + * A timeout of 0 enables non-blocking mode. + * + * \param timeout A serial::Timeout struct containing the inter byte + * timeout, and the read and write timeout constants and multipliers. + * + * \see serial::Timeout + */ + void + setTimeout (Timeout &timeout); + + /*! Sets the timeout for reads and writes. */ + void + setTimeout (uint32_t inter_byte_timeout, uint32_t read_timeout_constant, + uint32_t read_timeout_multiplier, uint32_t write_timeout_constant, + uint32_t write_timeout_multiplier) + { + Timeout timeout(inter_byte_timeout, read_timeout_constant, + read_timeout_multiplier, write_timeout_constant, + write_timeout_multiplier); + return setTimeout(timeout); + } + + /*! Gets the timeout for reads in seconds. + * + * \return A Timeout struct containing the inter_byte_timeout, and read + * and write timeout constants and multipliers. + * + * \see Serial::setTimeout + */ + Timeout + getTimeout () const; + + /*! Sets the baudrate for the serial port. + * + * Possible baudrates depends on the system but some safe baudrates include: + * 110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 56000, + * 57600, 115200 + * Some other baudrates that are supported by some comports: + * 128000, 153600, 230400, 256000, 460800, 500000, 921600 + * + * \param baudrate An integer that sets the baud rate for the serial port. + * + * \throw std::invalid_argument + */ + void + setBaudrate (uint32_t baudrate); + + /*! Gets the baudrate for the serial port. + * + * \return An integer that sets the baud rate for the serial port. + * + * \see Serial::setBaudrate + * + * \throw std::invalid_argument + */ + uint32_t + getBaudrate () const; + + /*! Sets the bytesize for the serial port. + * + * \param bytesize Size of each byte in the serial transmission of data, + * default is eightbits, possible values are: fivebits, sixbits, sevenbits, + * eightbits + * + * \throw std::invalid_argument + */ + void + setBytesize (bytesize_t bytesize); + + /*! Gets the bytesize for the serial port. + * + * \see Serial::setBytesize + * + * \throw std::invalid_argument + */ + bytesize_t + getBytesize () const; + + /*! Sets the parity for the serial port. + * + * \param parity Method of parity, default is parity_none, possible values + * are: parity_none, parity_odd, parity_even + * + * \throw std::invalid_argument + */ + void + setParity (parity_t parity); + + /*! Gets the parity for the serial port. + * + * \see Serial::setParity + * + * \throw std::invalid_argument + */ + parity_t + getParity () const; + + /*! Sets the stopbits for the serial port. + * + * \param stopbits Number of stop bits used, default is stopbits_one, + * possible values are: stopbits_one, stopbits_one_point_five, stopbits_two + * + * \throw std::invalid_argument + */ + void + setStopbits (stopbits_t stopbits); + + /*! Gets the stopbits for the serial port. + * + * \see Serial::setStopbits + * + * \throw std::invalid_argument + */ + stopbits_t + getStopbits () const; + + /*! Sets the flow control for the serial port. + * + * \param flowcontrol Type of flowcontrol used, default is flowcontrol_none, + * possible values are: flowcontrol_none, flowcontrol_software, + * flowcontrol_hardware + * + * \throw std::invalid_argument + */ + void + setFlowcontrol (flowcontrol_t flowcontrol); + + /*! Gets the flow control for the serial port. + * + * \see Serial::setFlowcontrol + * + * \throw std::invalid_argument + */ + flowcontrol_t + getFlowcontrol () const; + + /*! Flush the input and output buffers */ + void + flush (); + + /*! Flush only the input buffer */ + void + flushInput (); + + /*! Flush only the output buffer */ + void + flushOutput (); + + /*! Sends the RS-232 break signal. See tcsendbreak(3). */ + void + sendBreak (int duration); + + /*! Set the break condition to a given level. Defaults to true. */ + void + setBreak (bool level = true); + + /*! Set the RTS handshaking line to the given level. Defaults to true. */ + void + setRTS (bool level = true); + + /*! Set the DTR handshaking line to the given level. Defaults to true. */ + void + setDTR (bool level = true); + + /*! + * Blocks until CTS, DSR, RI, CD changes or something interrupts it. + * + * Can throw an exception if an error occurs while waiting. + * You can check the status of CTS, DSR, RI, and CD once this returns. + * Uses TIOCMIWAIT via ioctl if available (mostly only on Linux) with a + * resolution of less than +-1ms and as good as +-0.2ms. Otherwise a + * polling method is used which can give +-2ms. + * + * \return Returns true if one of the lines changed, false if something else + * occurred. + * + * \throw SerialException + */ + bool + waitForChange (); + + /*! Returns the current status of the CTS line. */ + bool + getCTS (); + + /*! Returns the current status of the DSR line. */ + bool + getDSR (); + + /*! Returns the current status of the RI line. */ + bool + getRI (); + + /*! Returns the current status of the CD line. */ + bool + getCD (); + +private: + // Disable copy constructors + Serial(const Serial&); + Serial& operator=(const Serial&); + + // Pimpl idiom, d_pointer + class SerialImpl; + SerialImpl *pimpl_; + + // Scoped Lock Classes + class ScopedReadLock; + class ScopedWriteLock; + + // Read common function + size_t + read_ (uint8_t *buffer, size_t size); + // Write common function + size_t + write_ (const uint8_t *data, size_t length); + +}; + +class SerialException : public std::exception +{ + // Disable copy constructors + SerialException& operator=(const SerialException&); + std::string e_what_; +public: + SerialException (const char *description) { + std::stringstream ss; + ss << "SerialException " << description << " failed."; + e_what_ = ss.str(); + } + SerialException (const SerialException& other) : e_what_(other.e_what_) {} + virtual ~SerialException() throw() {} + virtual const char* what () const throw () { + return e_what_.c_str(); + } +}; + +class IOException : public std::exception +{ + // Disable copy constructors + IOException& operator=(const IOException&); + std::string file_; + int line_; + std::string e_what_; + int errno_; +public: + explicit IOException (std::string file, int line, int errnum) + : file_(file), line_(line), errno_(errnum) { + std::stringstream ss; +#if defined(_WIN32) && !defined(__MINGW32__) + char error_str [1024]; + strerror_s(error_str, 1024, errnum); +#else + char * error_str = strerror(errnum); +#endif + ss << "IO Exception (" << errno_ << "): " << error_str; + ss << ", file " << file_ << ", line " << line_ << "."; + e_what_ = ss.str(); + } + explicit IOException (std::string file, int line, const char * description) + : file_(file), line_(line), errno_(0) { + std::stringstream ss; + ss << "IO Exception: " << description; + ss << ", file " << file_ << ", line " << line_ << "."; + e_what_ = ss.str(); + } + virtual ~IOException() throw() {} + IOException (const IOException& other) : line_(other.line_), e_what_(other.e_what_), errno_(other.errno_) {} + + int getErrorNumber () const { return errno_; } + + virtual const char* what () const throw () { + return e_what_.c_str(); + } +}; + +class PortNotOpenedException : public std::exception +{ + // Disable copy constructors + const PortNotOpenedException& operator=(PortNotOpenedException); + std::string e_what_; +public: + PortNotOpenedException (const char * description) { + std::stringstream ss; + ss << "PortNotOpenedException " << description << " failed."; + e_what_ = ss.str(); + } + PortNotOpenedException (const PortNotOpenedException& other) : e_what_(other.e_what_) {} + virtual ~PortNotOpenedException() throw() {} + virtual const char* what () const throw () { + return e_what_.c_str(); + } +}; + +/*! + * Structure that describes a serial device. + */ +struct PortInfo { + + /*! Address of the serial port (this can be passed to the constructor of Serial). */ + std::string port; + + /*! Human readable description of serial device if available. */ + std::string description; + + /*! Hardware ID (e.g. VID:PID of USB serial devices) or "n/a" if not available. */ + std::string hardware_id; + +}; + +/* Lists the serial ports available on the system + * + * Returns a vector of available serial ports, each represented + * by a serial::PortInfo data structure: + * + * \return vector of serial::PortInfo. + */ +std::vector +list_ports(); + +} // namespace serial + +#endif diff --git a/NetToolbox/3rdparty/serial/v8stdint.h b/NetToolbox/3rdparty/serial/v8stdint.h new file mode 100644 index 0000000..f3be96b --- /dev/null +++ b/NetToolbox/3rdparty/serial/v8stdint.h @@ -0,0 +1,57 @@ +// This header is from the v8 google project: +// http://code.google.com/p/v8/source/browse/trunk/include/v8stdint.h + +// Copyright 2012 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Load definitions of standard types. + +#ifndef V8STDINT_H_ +#define V8STDINT_H_ + +#include +#include + +#if defined(_WIN32) && !defined(__MINGW32__) + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; // NOLINT +typedef unsigned short uint16_t; // NOLINT +typedef int int32_t; +typedef unsigned int uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +// intptr_t and friends are defined in crtdefs.h through stdio.h. + +#else + +#include + +#endif + +#endif // V8STDINT_H_ diff --git a/NetToolbox/3rdparty/serial/win.cc b/NetToolbox/3rdparty/serial/win.cc new file mode 100644 index 0000000..9147f5a --- /dev/null +++ b/NetToolbox/3rdparty/serial/win.cc @@ -0,0 +1,646 @@ +#if defined(_WIN32) + +/* Copyright 2012 William Woodall and John Harrison */ + +#include + +#include "win.h" + +using std::string; +using std::wstring; +using std::stringstream; +using std::invalid_argument; +using serial::Serial; +using serial::Timeout; +using serial::bytesize_t; +using serial::parity_t; +using serial::stopbits_t; +using serial::flowcontrol_t; +using serial::SerialException; +using serial::PortNotOpenedException; +using serial::IOException; + +inline wstring +_prefix_port_if_needed(const wstring &input) +{ + static wstring windows_com_port_prefix = L"\\\\.\\"; + if (input.compare(windows_com_port_prefix) != 0) + { + return windows_com_port_prefix + input; + } + return input; +} + +Serial::SerialImpl::SerialImpl (const string &port, unsigned long baudrate, + bytesize_t bytesize, + parity_t parity, stopbits_t stopbits, + flowcontrol_t flowcontrol) + : port_ (port.begin(), port.end()), fd_ (INVALID_HANDLE_VALUE), is_open_ (false), + baudrate_ (baudrate), parity_ (parity), + bytesize_ (bytesize), stopbits_ (stopbits), flowcontrol_ (flowcontrol) +{ + if (port_.empty () == false) + open (); + read_mutex = CreateMutex(NULL, false, NULL); + write_mutex = CreateMutex(NULL, false, NULL); +} + +Serial::SerialImpl::~SerialImpl () +{ + this->close(); + CloseHandle(read_mutex); + CloseHandle(write_mutex); +} + +void +Serial::SerialImpl::open () +{ + if (port_.empty ()) { + throw invalid_argument ("Empty port is invalid."); + } + if (is_open_ == true) { + throw SerialException ("Serial port already open."); + } + + // See: https://github.com/wjwwood/serial/issues/84 + wstring port_with_prefix = _prefix_port_if_needed(port_); + LPCWSTR lp_port = port_with_prefix.c_str(); + fd_ = CreateFileW(lp_port, + GENERIC_READ | GENERIC_WRITE, + 0, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + 0); + + if (fd_ == INVALID_HANDLE_VALUE) { + DWORD create_file_err = GetLastError(); + stringstream ss; + switch (create_file_err) { + case ERROR_FILE_NOT_FOUND: + // Use this->getPort to convert to a std::string + ss << "Specified port, " << this->getPort() << ", does not exist."; + THROW (IOException, ss.str().c_str()); + default: + ss << "Unknown error opening the serial port: " << create_file_err; + THROW (IOException, ss.str().c_str()); + } + } + + reconfigurePort(); + is_open_ = true; +} + +void +Serial::SerialImpl::reconfigurePort () +{ + if (fd_ == INVALID_HANDLE_VALUE) { + // Can only operate on a valid file descriptor + THROW (IOException, "Invalid file descriptor, is the serial port open?"); + } + + DCB dcbSerialParams = {0}; + + dcbSerialParams.DCBlength=sizeof(dcbSerialParams); + + if (!GetCommState(fd_, &dcbSerialParams)) { + //error getting state + THROW (IOException, "Error getting the serial port state."); + } + + // setup baud rate + switch (baudrate_) { +#ifdef CBR_0 + case 0: dcbSerialParams.BaudRate = CBR_0; break; +#endif +#ifdef CBR_50 + case 50: dcbSerialParams.BaudRate = CBR_50; break; +#endif +#ifdef CBR_75 + case 75: dcbSerialParams.BaudRate = CBR_75; break; +#endif +#ifdef CBR_110 + case 110: dcbSerialParams.BaudRate = CBR_110; break; +#endif +#ifdef CBR_134 + case 134: dcbSerialParams.BaudRate = CBR_134; break; +#endif +#ifdef CBR_150 + case 150: dcbSerialParams.BaudRate = CBR_150; break; +#endif +#ifdef CBR_200 + case 200: dcbSerialParams.BaudRate = CBR_200; break; +#endif +#ifdef CBR_300 + case 300: dcbSerialParams.BaudRate = CBR_300; break; +#endif +#ifdef CBR_600 + case 600: dcbSerialParams.BaudRate = CBR_600; break; +#endif +#ifdef CBR_1200 + case 1200: dcbSerialParams.BaudRate = CBR_1200; break; +#endif +#ifdef CBR_1800 + case 1800: dcbSerialParams.BaudRate = CBR_1800; break; +#endif +#ifdef CBR_2400 + case 2400: dcbSerialParams.BaudRate = CBR_2400; break; +#endif +#ifdef CBR_4800 + case 4800: dcbSerialParams.BaudRate = CBR_4800; break; +#endif +#ifdef CBR_7200 + case 7200: dcbSerialParams.BaudRate = CBR_7200; break; +#endif +#ifdef CBR_9600 + case 9600: dcbSerialParams.BaudRate = CBR_9600; break; +#endif +#ifdef CBR_14400 + case 14400: dcbSerialParams.BaudRate = CBR_14400; break; +#endif +#ifdef CBR_19200 + case 19200: dcbSerialParams.BaudRate = CBR_19200; break; +#endif +#ifdef CBR_28800 + case 28800: dcbSerialParams.BaudRate = CBR_28800; break; +#endif +#ifdef CBR_57600 + case 57600: dcbSerialParams.BaudRate = CBR_57600; break; +#endif +#ifdef CBR_76800 + case 76800: dcbSerialParams.BaudRate = CBR_76800; break; +#endif +#ifdef CBR_38400 + case 38400: dcbSerialParams.BaudRate = CBR_38400; break; +#endif +#ifdef CBR_115200 + case 115200: dcbSerialParams.BaudRate = CBR_115200; break; +#endif +#ifdef CBR_128000 + case 128000: dcbSerialParams.BaudRate = CBR_128000; break; +#endif +#ifdef CBR_153600 + case 153600: dcbSerialParams.BaudRate = CBR_153600; break; +#endif +#ifdef CBR_230400 + case 230400: dcbSerialParams.BaudRate = CBR_230400; break; +#endif +#ifdef CBR_256000 + case 256000: dcbSerialParams.BaudRate = CBR_256000; break; +#endif +#ifdef CBR_460800 + case 460800: dcbSerialParams.BaudRate = CBR_460800; break; +#endif +#ifdef CBR_921600 + case 921600: dcbSerialParams.BaudRate = CBR_921600; break; +#endif + default: + // Try to blindly assign it + dcbSerialParams.BaudRate = baudrate_; + } + + // setup char len + if (bytesize_ == eightbits) + dcbSerialParams.ByteSize = 8; + else if (bytesize_ == sevenbits) + dcbSerialParams.ByteSize = 7; + else if (bytesize_ == sixbits) + dcbSerialParams.ByteSize = 6; + else if (bytesize_ == fivebits) + dcbSerialParams.ByteSize = 5; + else + throw invalid_argument ("invalid char len"); + + // setup stopbits + if (stopbits_ == stopbits_one) + dcbSerialParams.StopBits = ONESTOPBIT; + else if (stopbits_ == stopbits_one_point_five) + dcbSerialParams.StopBits = ONE5STOPBITS; + else if (stopbits_ == stopbits_two) + dcbSerialParams.StopBits = TWOSTOPBITS; + else + throw invalid_argument ("invalid stop bit"); + + // setup parity + if (parity_ == parity_none) { + dcbSerialParams.Parity = NOPARITY; + } else if (parity_ == parity_even) { + dcbSerialParams.Parity = EVENPARITY; + } else if (parity_ == parity_odd) { + dcbSerialParams.Parity = ODDPARITY; + } else if (parity_ == parity_mark) { + dcbSerialParams.Parity = MARKPARITY; + } else if (parity_ == parity_space) { + dcbSerialParams.Parity = SPACEPARITY; + } else { + throw invalid_argument ("invalid parity"); + } + + // setup flowcontrol + if (flowcontrol_ == flowcontrol_none) { + dcbSerialParams.fOutxCtsFlow = false; + dcbSerialParams.fRtsControl = RTS_CONTROL_DISABLE; + dcbSerialParams.fOutX = false; + dcbSerialParams.fInX = false; + } + if (flowcontrol_ == flowcontrol_software) { + dcbSerialParams.fOutxCtsFlow = false; + dcbSerialParams.fRtsControl = RTS_CONTROL_DISABLE; + dcbSerialParams.fOutX = true; + dcbSerialParams.fInX = true; + } + if (flowcontrol_ == flowcontrol_hardware) { + dcbSerialParams.fOutxCtsFlow = true; + dcbSerialParams.fRtsControl = RTS_CONTROL_HANDSHAKE; + dcbSerialParams.fOutX = false; + dcbSerialParams.fInX = false; + } + + // activate settings + if (!SetCommState(fd_, &dcbSerialParams)){ + CloseHandle(fd_); + THROW (IOException, "Error setting serial port settings."); + } + + // Setup timeouts + COMMTIMEOUTS timeouts = {0}; + timeouts.ReadIntervalTimeout = timeout_.inter_byte_timeout; + timeouts.ReadTotalTimeoutConstant = timeout_.read_timeout_constant; + timeouts.ReadTotalTimeoutMultiplier = timeout_.read_timeout_multiplier; + timeouts.WriteTotalTimeoutConstant = timeout_.write_timeout_constant; + timeouts.WriteTotalTimeoutMultiplier = timeout_.write_timeout_multiplier; + if (!SetCommTimeouts(fd_, &timeouts)) { + THROW (IOException, "Error setting timeouts."); + } +} + +void +Serial::SerialImpl::close () +{ + if (is_open_ == true) { + if (fd_ != INVALID_HANDLE_VALUE) { + int ret; + ret = CloseHandle(fd_); + if (ret == 0) { + stringstream ss; + ss << "Error while closing serial port: " << GetLastError(); + THROW (IOException, ss.str().c_str()); + } else { + fd_ = INVALID_HANDLE_VALUE; + } + } + is_open_ = false; + } +} + +bool +Serial::SerialImpl::isOpen () const +{ + return is_open_; +} + +size_t +Serial::SerialImpl::available () +{ + if (!is_open_) { + return 0; + } + COMSTAT cs; + if (!ClearCommError(fd_, NULL, &cs)) { + stringstream ss; + ss << "Error while checking status of the serial port: " << GetLastError(); + THROW (IOException, ss.str().c_str()); + } + return static_cast(cs.cbInQue); +} + +bool +Serial::SerialImpl::waitReadable (uint32_t /*timeout*/) +{ + THROW (IOException, "waitReadable is not implemented on Windows."); + return false; +} + +void +Serial::SerialImpl::waitByteTimes (size_t /*count*/) +{ + THROW (IOException, "waitByteTimes is not implemented on Windows."); +} + +size_t +Serial::SerialImpl::read (uint8_t *buf, size_t size) +{ + if (!is_open_) { + throw PortNotOpenedException ("Serial::read"); + } + DWORD bytes_read; + if (!ReadFile(fd_, buf, static_cast(size), &bytes_read, NULL)) { + stringstream ss; + ss << "Error while reading from the serial port: " << GetLastError(); + THROW (IOException, ss.str().c_str()); + } + return (size_t) (bytes_read); +} + +size_t +Serial::SerialImpl::write (const uint8_t *data, size_t length) +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::write"); + } + DWORD bytes_written; + if (!WriteFile(fd_, data, static_cast(length), &bytes_written, NULL)) { + stringstream ss; + ss << "Error while writing to the serial port: " << GetLastError(); + THROW (IOException, ss.str().c_str()); + } + return (size_t) (bytes_written); +} + +void +Serial::SerialImpl::setPort (const string &port) +{ + port_ = wstring(port.begin(), port.end()); +} + +string +Serial::SerialImpl::getPort () const +{ + return string(port_.begin(), port_.end()); +} + +void +Serial::SerialImpl::setTimeout (serial::Timeout &timeout) +{ + timeout_ = timeout; + if (is_open_) { + reconfigurePort (); + } +} + +serial::Timeout +Serial::SerialImpl::getTimeout () const +{ + return timeout_; +} + +void +Serial::SerialImpl::setBaudrate (unsigned long baudrate) +{ + baudrate_ = baudrate; + if (is_open_) { + reconfigurePort (); + } +} + +unsigned long +Serial::SerialImpl::getBaudrate () const +{ + return baudrate_; +} + +void +Serial::SerialImpl::setBytesize (serial::bytesize_t bytesize) +{ + bytesize_ = bytesize; + if (is_open_) { + reconfigurePort (); + } +} + +serial::bytesize_t +Serial::SerialImpl::getBytesize () const +{ + return bytesize_; +} + +void +Serial::SerialImpl::setParity (serial::parity_t parity) +{ + parity_ = parity; + if (is_open_) { + reconfigurePort (); + } +} + +serial::parity_t +Serial::SerialImpl::getParity () const +{ + return parity_; +} + +void +Serial::SerialImpl::setStopbits (serial::stopbits_t stopbits) +{ + stopbits_ = stopbits; + if (is_open_) { + reconfigurePort (); + } +} + +serial::stopbits_t +Serial::SerialImpl::getStopbits () const +{ + return stopbits_; +} + +void +Serial::SerialImpl::setFlowcontrol (serial::flowcontrol_t flowcontrol) +{ + flowcontrol_ = flowcontrol; + if (is_open_) { + reconfigurePort (); + } +} + +serial::flowcontrol_t +Serial::SerialImpl::getFlowcontrol () const +{ + return flowcontrol_; +} + +void +Serial::SerialImpl::flush () +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::flush"); + } + FlushFileBuffers (fd_); +} + +void +Serial::SerialImpl::flushInput () +{ + if (is_open_ == false) { + throw PortNotOpenedException("Serial::flushInput"); + } + PurgeComm(fd_, PURGE_RXCLEAR); +} + +void +Serial::SerialImpl::flushOutput () +{ + if (is_open_ == false) { + throw PortNotOpenedException("Serial::flushOutput"); + } + PurgeComm(fd_, PURGE_TXCLEAR); +} + +void +Serial::SerialImpl::sendBreak (int /*duration*/) +{ + THROW (IOException, "sendBreak is not supported on Windows."); +} + +void +Serial::SerialImpl::setBreak (bool level) +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::setBreak"); + } + if (level) { + EscapeCommFunction (fd_, SETBREAK); + } else { + EscapeCommFunction (fd_, CLRBREAK); + } +} + +void +Serial::SerialImpl::setRTS (bool level) +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::setRTS"); + } + if (level) { + EscapeCommFunction (fd_, SETRTS); + } else { + EscapeCommFunction (fd_, CLRRTS); + } +} + +void +Serial::SerialImpl::setDTR (bool level) +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::setDTR"); + } + if (level) { + EscapeCommFunction (fd_, SETDTR); + } else { + EscapeCommFunction (fd_, CLRDTR); + } +} + +bool +Serial::SerialImpl::waitForChange () +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::waitForChange"); + } + DWORD dwCommEvent; + + if (!SetCommMask(fd_, EV_CTS | EV_DSR | EV_RING | EV_RLSD)) { + // Error setting communications mask + return false; + } + + if (!WaitCommEvent(fd_, &dwCommEvent, NULL)) { + // An error occurred waiting for the event. + return false; + } else { + // Event has occurred. + return true; + } +} + +bool +Serial::SerialImpl::getCTS () +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::getCTS"); + } + DWORD dwModemStatus; + if (!GetCommModemStatus(fd_, &dwModemStatus)) { + THROW (IOException, "Error getting the status of the CTS line."); + } + + return (MS_CTS_ON & dwModemStatus) != 0; +} + +bool +Serial::SerialImpl::getDSR () +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::getDSR"); + } + DWORD dwModemStatus; + if (!GetCommModemStatus(fd_, &dwModemStatus)) { + THROW (IOException, "Error getting the status of the DSR line."); + } + + return (MS_DSR_ON & dwModemStatus) != 0; +} + +bool +Serial::SerialImpl::getRI() +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::getRI"); + } + DWORD dwModemStatus; + if (!GetCommModemStatus(fd_, &dwModemStatus)) { + THROW (IOException, "Error getting the status of the RI line."); + } + + return (MS_RING_ON & dwModemStatus) != 0; +} + +bool +Serial::SerialImpl::getCD() +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::getCD"); + } + DWORD dwModemStatus; + if (!GetCommModemStatus(fd_, &dwModemStatus)) { + // Error in GetCommModemStatus; + THROW (IOException, "Error getting the status of the CD line."); + } + + return (MS_RLSD_ON & dwModemStatus) != 0; +} + +void +Serial::SerialImpl::readLock() +{ + if (WaitForSingleObject(read_mutex, INFINITE) != WAIT_OBJECT_0) { + THROW (IOException, "Error claiming read mutex."); + } +} + +void +Serial::SerialImpl::readUnlock() +{ + if (!ReleaseMutex(read_mutex)) { + THROW (IOException, "Error releasing read mutex."); + } +} + +void +Serial::SerialImpl::writeLock() +{ + if (WaitForSingleObject(write_mutex, INFINITE) != WAIT_OBJECT_0) { + THROW (IOException, "Error claiming write mutex."); + } +} + +void +Serial::SerialImpl::writeUnlock() +{ + if (!ReleaseMutex(write_mutex)) { + THROW (IOException, "Error releasing write mutex."); + } +} + +#endif // #if defined(_WIN32) + diff --git a/NetToolbox/3rdparty/serial/win.h b/NetToolbox/3rdparty/serial/win.h new file mode 100644 index 0000000..06286fc --- /dev/null +++ b/NetToolbox/3rdparty/serial/win.h @@ -0,0 +1,207 @@ +/*! + * \file serial/impl/windows.h + * \author William Woodall + * \author John Harrison + * \version 0.1 + * + * \section LICENSE + * + * The MIT License + * + * Copyright (c) 2012 William Woodall, John Harrison + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * \section DESCRIPTION + * + * This provides a windows implementation of the Serial class interface. + * + */ + +#if defined(_WIN32) + +#ifndef SERIAL_IMPL_WINDOWS_H +#define SERIAL_IMPL_WINDOWS_H + +#include "serial.h" + +#include "windows.h" + +namespace serial { + +using std::string; +using std::wstring; +using std::invalid_argument; + +using serial::SerialException; +using serial::IOException; + +class serial::Serial::SerialImpl { +public: + SerialImpl (const string &port, + unsigned long baudrate, + bytesize_t bytesize, + parity_t parity, + stopbits_t stopbits, + flowcontrol_t flowcontrol); + + virtual ~SerialImpl (); + + void + open (); + + void + close (); + + bool + isOpen () const; + + size_t + available (); + + bool + waitReadable (uint32_t timeout); + + void + waitByteTimes (size_t count); + + size_t + read (uint8_t *buf, size_t size = 1); + + size_t + write (const uint8_t *data, size_t length); + + void + flush (); + + void + flushInput (); + + void + flushOutput (); + + void + sendBreak (int duration); + + void + setBreak (bool level); + + void + setRTS (bool level); + + void + setDTR (bool level); + + bool + waitForChange (); + + bool + getCTS (); + + bool + getDSR (); + + bool + getRI (); + + bool + getCD (); + + void + setPort (const string &port); + + string + getPort () const; + + void + setTimeout (Timeout &timeout); + + Timeout + getTimeout () const; + + void + setBaudrate (unsigned long baudrate); + + unsigned long + getBaudrate () const; + + void + setBytesize (bytesize_t bytesize); + + bytesize_t + getBytesize () const; + + void + setParity (parity_t parity); + + parity_t + getParity () const; + + void + setStopbits (stopbits_t stopbits); + + stopbits_t + getStopbits () const; + + void + setFlowcontrol (flowcontrol_t flowcontrol); + + flowcontrol_t + getFlowcontrol () const; + + void + readLock (); + + void + readUnlock (); + + void + writeLock (); + + void + writeUnlock (); + +protected: + void reconfigurePort (); + +private: + wstring port_; // Path to the file descriptor + HANDLE fd_; + + bool is_open_; + + Timeout timeout_; // Timeout for read operations + unsigned long baudrate_; // Baudrate + + parity_t parity_; // Parity + bytesize_t bytesize_; // Size of the bytes + stopbits_t stopbits_; // Stop Bits + flowcontrol_t flowcontrol_; // Flow Control + + // Mutex used to lock the read functions + HANDLE read_mutex; + // Mutex used to lock the write functions + HANDLE write_mutex; +}; + +} + +#endif // SERIAL_IMPL_WINDOWS_H + +#endif // if defined(_WIN32) diff --git a/NetToolbox/NetToolbox.vcxproj b/NetToolbox/NetToolbox.vcxproj new file mode 100644 index 0000000..aa17548 --- /dev/null +++ b/NetToolbox/NetToolbox.vcxproj @@ -0,0 +1,415 @@ + + + + + Debug + Win32 + + + LTL + Win32 + + + LTL + x64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {3327CB5C-CFAF-4DE9-8BAD-C6F15E09EE26} + NetToolbox + 10.0.17134.0 + true + + + + Application + true + v141 + Unicode + x86-windows-static + + + Application + false + v141 + true + Unicode + x86-windows-static + + + Application + false + v141 + true + Unicode + x86-windows-static + + + Application + true + v141 + Unicode + x64-windows-static + + + Application + false + v141 + true + Unicode + x64-windows-static + + + Application + false + v141 + true + Unicode + x64-windows-static + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)$(Configuration)_x64\ + $(Configuration)_x64\ + + + $(SolutionDir)$(Configuration)_x64\ + $(Configuration)_x64\ + + + $(SolutionDir)$(Configuration)_x64\ + $(Configuration)_x64\ + D:\GitHub\json\single_include;D:\Software\Program\lib\boost_1_68_0;$(IncludePath) + D:\Software\Program\lib\boost_1_68_0\stage\lib;$(LibraryPath) + + + $(IncludePath) + $(LibraryPath) + + + D:\GitHub\json\single_include;D:\Software\Program\lib\boost_1_68_0;$(IncludePath) + D:\Software\Program\lib\boost_1_68_0\stage\lib;$(LibraryPath) + + + + Level3 + Disabled + true + true + UILIB_STATIC;%(PreprocessorDefinitions) + Use + MultiThreadedDebug + stdcpp17 + /bigobj %(AdditionalOptions) + + + ".\res\xml.manifest" %(AdditionalManifestFiles) + + + + + Level3 + Disabled + true + true + UILIB_STATIC;%(PreprocessorDefinitions) + Use + MultiThreadedDebug + stdcpp17 + /bigobj %(AdditionalOptions) + + + ".\res\xml.manifest" %(AdditionalManifestFiles) + + + AsInvoker + + + + + Level3 + MaxSpeed + true + true + true + true + UILIB_STATIC;%(PreprocessorDefinitions) + Use + MultiThreaded + stdcpp17 + /bigobj %(AdditionalOptions) + + + true + true + + + ".\res\xml.manifest" %(AdditionalManifestFiles) + + + del "$(SolutionDir)$(ProjectName)\res\res.zip" /q +7z u -tzip "$(SolutionDir)$(ProjectName)\res\res.zip" "$(SolutionDir)res\*" + + + upx -9 $(SolutionDir)$(IntDir)$(TargetFileName) +$(SolutionDir)NetToolbox_publish32.exe +del $(SolutionDir)$(IntDir)NetToolbox.7z /q +7z u -t7z $(SolutionDir)$(IntDir)NetToolbox.7z $(SolutionDir)$(IntDir)LICENSE_* $(SolutionDir)$(IntDir)*.exe + + + + + Level3 + MaxSpeed + true + true + true + true + UILIB_STATIC;%(PreprocessorDefinitions) + Use + MultiThreaded + stdcpp17 + /bigobj %(AdditionalOptions) + + + true + true + + + ".\res\xml.manifest" %(AdditionalManifestFiles) + + + del "$(SolutionDir)$(ProjectName)\res\res.zip" /q +7z u -tzip "$(SolutionDir)$(ProjectName)\res\res.zip" "$(SolutionDir)res\*" + + + upx -9 $(SolutionDir)$(IntDir)$(TargetFileName) +$(SolutionDir)NetToolbox_publish32.exe +del $(SolutionDir)$(IntDir)NetToolbox.7z /q +7z u -t7z $(SolutionDir)$(IntDir)NetToolbox.7z $(SolutionDir)$(IntDir)LICENSE_* $(SolutionDir)$(IntDir)*.exe + + + + + Level3 + MaxSpeed + true + true + true + true + UILIB_STATIC;%(PreprocessorDefinitions) + Use + MultiThreaded + stdcpp17 + /bigobj %(AdditionalOptions) + + + true + true + AsInvoker + + + ".\res\xml.manifest" %(AdditionalManifestFiles) + + + del "$(SolutionDir)$(ProjectName)\res\res.zip" /q +7z u -tzip "$(SolutionDir)$(ProjectName)\res\res.zip" "$(SolutionDir)res\*" + + + upx -9 $(SolutionDir)$(IntDir)$(TargetFileName) +$(SolutionDir)NetToolbox_publish.exe +del $(SolutionDir)$(IntDir)NetToolbox.7z /q +7z u -t7z $(SolutionDir)$(IntDir)NetToolbox.7z $(SolutionDir)$(IntDir)LICENSE_* $(SolutionDir)$(IntDir)*.exe + + + + + Level3 + MaxSpeed + true + true + true + true + UILIB_STATIC;%(PreprocessorDefinitions) + Use + MultiThreaded + stdcpp17 + /bigobj %(AdditionalOptions) + + + true + true + AsInvoker + + + ".\res\xml.manifest" %(AdditionalManifestFiles) + + + del "$(SolutionDir)$(ProjectName)\res\res.zip" /q +7z u -tzip "$(SolutionDir)$(ProjectName)\res\res.zip" "$(SolutionDir)res\*" + + + upx -9 $(SolutionDir)$(IntDir)$(TargetFileName) +$(SolutionDir)NetToolbox_publish.exe +del $(SolutionDir)$(IntDir)NetToolbox.7z /q +7z u -t7z $(SolutionDir)$(IntDir)NetToolbox.7z $(SolutionDir)$(IntDir)LICENSE_* $(SolutionDir)$(IntDir)*.exe + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + Create + Create + Create + Create + Create + Create + + + + + + + + + + + + + + + + Designer + + + + + + + + + + + + + \ No newline at end of file diff --git a/NetToolbox/NetToolbox.vcxproj.filters b/NetToolbox/NetToolbox.vcxproj.filters new file mode 100644 index 0000000..fa0d608 --- /dev/null +++ b/NetToolbox/NetToolbox.vcxproj.filters @@ -0,0 +1,263 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {ec64c10e-a558-46d9-918d-6a682351c6c0} + + + {2007662a-86a0-4f84-87c3-98372bc5c222} + + + {37d6437e-f29e-4879-84ac-91d262a28943} + + + {b8661dc5-170b-432e-9de8-b079bb7d1f8d} + + + {06808546-92f6-42dc-a6ce-f9ad269ae1bb} + + + {c04a2ce7-6e0c-4ac7-9df0-8a12cd8f3201} + + + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件\pages + + + 头文件\tools + + + 头文件\pages + + + 头文件\tools + + + 头文件\tools + + + 头文件\tools + + + 3rdparty\serial + + + 3rdparty\serial + + + 3rdparty\serial + + + 头文件\tools + + + 头文件\tools + + + 头文件\pages + + + 头文件\tools + + + 头文件\tools + + + 头文件\tools + + + 头文件\pages + + + 头文件\tools + + + 头文件\pages + + + 头文件\tools + + + 头文件\tools + + + 头文件\tools + + + 头文件\pages + + + 头文件\tools + + + 3rdparty\Simple-Web-Server + + + 3rdparty\Simple-Web-Server + + + 3rdparty\Simple-Web-Server + + + 3rdparty\Simple-Web-Server + + + 3rdparty\Simple-Web-Server + + + 3rdparty\Simple-Web-Server + + + 3rdparty\Simple-Web-Server + + + 头文件\tools + + + 头文件\pages + + + 头文件\pages + + + 头文件\tools + + + 头文件\tools + + + 头文件\tools + + + 头文件\tools + + + 头文件\pages + + + 头文件\tools + + + 头文件 + + + 头文件\pages + + + 头文件\db + + + 头文件\pages + + + 头文件\pages + + + 头文件\pages + + + 头文件\tools + + + 头文件\pages + + + 头文件\tools + + + 头文件\pages + + + 头文件\tools + + + 头文件\pages + + + 头文件\tools + + + 头文件 + + + 头文件\pages + + + + + 源文件 + + + 源文件 + + + 源文件 + + + 3rdparty\serial + + + 3rdparty\serial + + + 3rdparty\serial + + + + + 资源文件 + + + + + 资源文件 + + + + + 资源文件 + + + + + 资源文件 + + + + + 资源文件 + + + 资源文件 + + + 资源文件 + + + 资源文件 + + + + \ No newline at end of file diff --git a/NetToolbox/NetToolbox.vcxproj.user b/NetToolbox/NetToolbox.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/NetToolbox/NetToolbox.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/NetToolbox/NetToolboxWnd.cpp b/NetToolbox/NetToolboxWnd.cpp new file mode 100644 index 0000000..b7b3f4d --- /dev/null +++ b/NetToolbox/NetToolboxWnd.cpp @@ -0,0 +1,298 @@ +#include "StdAfx.h" +#include "NetToolboxWnd.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "tools/tool_String.hpp" +#include "tools/tool_Utils.hpp" +#include "tools/tool_Path.hpp" +#include "tools/tool_Process.hpp" + +#include "pages/page_base.hpp" +// +#include "pages/page_SysInfo.hpp" +#include "pages/page_LocalNet.hpp" +#include "pages/page_LocalConn.hpp" +// +// +// +#include "pages/page_Tracert.hpp" +#include "pages/page_Http.hpp" +#include "pages/page_Qps.hpp" +#include "pages/page_IPScan.hpp" +// +#include "pages/page_Regex.hpp" +#include "pages/page_Rsa.hpp" +#include "pages/page_EncDec.hpp" +// +#include "pages/page_Gif.hpp" +#include "pages/page_SerialPort.hpp" +#include "pages/page_Window.hpp" +#include "pages/page_File.hpp" +#include "pages/page_Record.hpp" +// + + + +void NetToolboxWnd::InitWindow () { + //ڳʼ¼ + ::SendMessage (m_hWnd, WM_SETICON, ICON_SMALL, (LPARAM) m_icon); + + // ñ + m_text_caption->SetText (m_caption.c_str ()); + + m_pages = { + { new page_SysInfo (this), new page_LocalNet (this), new page_LocalConn (this) }, + { nullptr }, + { nullptr }, + { new page_Tracert (this), new page_Http (this), new page_Qps (this), new page_IPScan (this) }, + { new page_Regex (this), new page_Rsa (this), new page_EncDec (this) }, + { new page_Gif (this), new page_SerialPort (this), new page_Window (this), new page_File (this), new page_Record (this) }, + { nullptr } + }; + + auto &args = tool_Path::get_args (); +#ifdef _DEBUG + if (args.size () < 2) { + args.push_back (_T ("-jump")); + args.push_back (_T ("6,0")); + } +#endif + for (size_t i = 1; i < args.size (); ++i) { + if (args[i] == _T ("-jump") && i < args.size () - 1) { + size_t p = args[i + 1].find (_T (',')); + size_t sel1 = _ttoi (&args[i + 1][0]), sel2 = _ttoi (&args[i + 1][p + 1]); + direct_page (sel1, sel2); + } + } + ::SetTimer (m_hWnd, (UINT_PTR) this, 1000, nullptr); + + //if (!m_notifyicon) + // m_notifyicon = new hNotifyIcon (m_hWnd, m_icon, NetToolboxWnd::lp_window_name, WM_NOTIFY_ICON); + + //exec_settings (); +} + +void NetToolboxWnd::OnClick (TNotifyUI& msg) { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("btn_set")) { + //static int t = 0; + //show_error_code (++t); + } else if (name == _T ("step_recorder")) { + tool_Process::shell_exec (_T ("psr.exe")); + //} else if (name == _T ("btn_blog")) { + // tool_Process::shell_exec (_T ("https://www.fawdlstty.com")); + //} else if (name == _T ("btn_join")) { + // tool_Process::shell_exec (_T ("https://jq.qq.com/?_wv=1027&k=5TMvF3B")); + } else if (name.Left (3) == _T ("tab") || name.Left (5) == _T ("group")) { + WindowImplBase::OnClick (msg); + } else if (name == _T ("about_home")) { + tool_Process::shell_exec (_T ("https://nettoolbox.fawdlstty.com")); + } else if (name == _T ("about_source")) { + tool_Process::shell_exec (_T ("https://github.com/fawdlstty/NetToolbox")); + } else { + page_base *ppage = get_moment_page (); + if (!ppage || !ppage->OnClick (msg)) + WindowImplBase::OnClick (msg); + } +} + +void NetToolboxWnd::OnHeaderClick (TNotifyUI& msg) { + page_base *ppage = get_moment_page (); + if (!ppage || !ppage->OnHeaderClick (msg)) + WindowImplBase::OnHeaderClick (msg); +} + +void NetToolboxWnd::OnSelectChanged (TNotifyUI& msg) { + CDuiString name = msg.pSender->GetName (); + //ǩҳл¼ + if (name.Left (3) == _T ("tab")) { + //TCHAR s[16] = _T ("tab?"); + //s[3] = name[3]; + //int idx = (int) (name[5] - _T ('0')); + //BindTabLayoutUI ctrl { s }; + //ctrl->SelectItem (idx); + //m_sel1 = m_tabM->GetCurSel (); + //m_sel2 = m_tab[m_sel1]->GetCurSel (); + //ui_update_data (); + m_tabM->SelectItem ((int) (m_sel1 = _ttoi (&name[3]))); + m_tab[m_sel1]->SelectItem ((int) (m_sel2 = _ttoi (&name[5]))); + ui_update_data (); + } else if (name.Left (5) == _T ("group")) { + size_t sel1 = _ttoi (&name[5]); + for (size_t i = 0; i < m_tab.size (); ++i) { + string_t name1 = tool_StringT::format (_T ("group%d"), i); + BindOptionUI option { name1 }; + string_t name2 = tool_StringT::format (_T ("group_item%d"), i); + BindVerticalLayoutUI vertical { name2 }; + if (*vertical) { + option->SetTextColor (i == sel1 ? 0xFFFFFFB0 : 0xFFFFFFFF); + vertical->SetVisible (i == sel1); + } + } + } else { + page_base *ppage = get_moment_page (); + if (ppage && ppage->OnSelectChanged (msg)) + return; + WindowImplBase::OnSelectChanged (msg); + } +} + +void NetToolboxWnd::OnTextChanged (TNotifyUI& msg) { + page_base *ppage = get_moment_page (); + if (ppage && ppage->OnTextChanged (msg)) + return; + WindowImplBase::OnTextChanged (msg); +} + +void NetToolboxWnd::OnItemSelect (TNotifyUI& msg) { + page_base *ppage = get_moment_page (); + if (ppage && ppage->OnItemSelect (msg)) + return; + WindowImplBase::OnItemSelect (msg); +} + +void NetToolboxWnd::OnTimer (TNotifyUI& msg) { + if (!enum_pages ([&msg] (page_base *page) { return page->OnTimer (msg); })) { + WindowImplBase::OnTimer (msg); + } +} + +void NetToolboxWnd::Notify (TNotifyUI& msg) { + if (msg.sType == _T ("menu")) { + + } + WindowImplBase::Notify (msg); +} + +void NetToolboxWnd::OnDropFiles (HDROP hDrop) { + TCHAR tBuf[MAX_PATH] = { 0 }; + ::DragQueryFile (hDrop, 0, tBuf, MAX_PATH); + ::DragFinish (hDrop); + page_base *ppage = get_moment_page (); + if (!ppage || !ppage->OnDropFiles (tBuf)) { + //WindowImplBase::OnDropFiles (msg); + } +} + +LRESULT NetToolboxWnd::OnLButtonDown (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + page_base *ppage = get_moment_page (); + bHandled = ppage ? !!ppage->OnLButtonDown (pt) : FALSE; + return (bHandled ? 0 : WindowImplBase::OnLButtonDown (uMsg, wParam, lParam, bHandled)); +} + +LRESULT NetToolboxWnd::OnLButtonUp (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + page_base *ppage = get_moment_page (); + bHandled = ppage ? !!ppage->OnLButtonUp (pt) : FALSE; + return (bHandled ? 0 : WindowImplBase::OnLButtonUp (uMsg, wParam, lParam, bHandled)); +} + +LRESULT NetToolboxWnd::OnRButtonDown (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + page_base *ppage = get_moment_page (); + bHandled = ppage ? !!ppage->OnRButtonDown (pt) : FALSE; + return (bHandled ? 0 : WindowImplBase::OnRButtonDown (uMsg, wParam, lParam, bHandled)); +} + +LRESULT NetToolboxWnd::OnRButtonUp (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + page_base *ppage = get_moment_page (); + bHandled = ppage ? !!ppage->OnRButtonUp (pt) : FALSE; + return (bHandled ? 0 : WindowImplBase::OnRButtonUp (uMsg, wParam, lParam, bHandled)); +} + +LRESULT NetToolboxWnd::OnMouseMove (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + page_base *ppage = get_moment_page (); + bHandled = ppage ? !!ppage->OnMouseMove (pt) : FALSE; + return (bHandled ? 0 : WindowImplBase::OnMouseMove (uMsg, wParam, lParam, bHandled)); +} + +LRESULT NetToolboxWnd::HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (WM_USER + 0x101 == uMsg) { + auto f = reinterpret_cast*>(lParam); + LRESULT r = (*f) (); + if (!wParam) + delete f; + return r; + } else if (WM_TIMER == uMsg) { + if (wParam == (WPARAM) this) { + ui_update_data (); + return 0; + } + } else if (WM_DROPFILES == uMsg) { + OnDropFiles ((HDROP) wParam); + return 0; + } else if (WM_MENUCLICK == uMsg) { + MenuCmd *mc = reinterpret_cast (wParam); + if (mc) { + page_base *ppage = get_moment_page (); + if (ppage->OnMenuClick (mc)) + return 0; + } + } + return WindowImplBase::HandleMessage (uMsg, wParam, lParam); +} + +CControlUI* NetToolboxWnd::CreateControl (string_view_t pstrClass) { + if (pstrClass == _T ("HanAnim")) return CHanAnimUI::CreateControl (); + return WindowImplBase::CreateControl (pstrClass); +} + + + +void NetToolboxWnd::show_status (StatusIcon _icon, string_view_t _info) { + static std::map _status_images { + { StatusIcon::Ok, _T ("file='status_small.png' source='0,0,16,16'") }, + { StatusIcon::Info, _T ("file='status_small.png' source='16,0,32,16'") }, + { StatusIcon::Warning, _T ("file='status_small.png' source='0,16,16,32'") }, + { StatusIcon::Error, _T ("file='status_small.png' source='16,16,32,32'") }, + { StatusIcon::Loading, _T ("wait.png") } + }; + if (StatusIcon::Loading == _icon) { + m_img_status->SetAnimateBkImage (_status_images[_icon], 112, 14); + } else { + m_img_status->SetBkImage (_status_images[_icon]); + } + m_text_status->SetText (_info); +} + +void NetToolboxWnd::show_error_code (DWORD err_no) { + if (err_no == 0) { + show_status (StatusIcon::Ok, _T ("ɹ")); + } else { + show_status (StatusIcon::Ok, tool_Utils::get_error_info (err_no).c_str ()); + } +} + +void NetToolboxWnd::direct_page (size_t sel1, size_t sel2) { + BindOptionUI { tool_StringT::format (_T ("group%d"), sel1) }->Activate (); + BindOptionUI { tool_StringT::format (_T ("tab%d_%d"), sel1, sel2) }->Activate (); +} + +bool NetToolboxWnd::enum_pages (std::function f) { + for (size_t i = 0; i < m_pages.size (); ++i) { + for (size_t j = 0; j < m_pages[i].size (); ++j) { + if (m_pages[i][j]) + if (f (m_pages[i][j])) + return true; + } + } + return false; +} + + + +void NetToolboxWnd::ui_update_data () { + if (m_pages.size () > m_sel1 && m_pages[m_sel1].size () > m_sel2 && m_pages[m_sel1][m_sel2]) + m_pages[m_sel1][m_sel2]->ui_update_data (); + ::DragAcceptFiles (m_hWnd, m_sel1 == 5 && m_sel2 == 3); +} diff --git a/NetToolbox/NetToolboxWnd.h b/NetToolbox/NetToolboxWnd.h new file mode 100644 index 0000000..5c55493 --- /dev/null +++ b/NetToolbox/NetToolboxWnd.h @@ -0,0 +1,80 @@ +#ifndef __NET_TOOLBOX_WND_HPP__ +#define __NET_TOOLBOX_WND_HPP__ + +#include "resource.h" + +#include "../DuiLib/StdAfx.h" +#include "../DuiLib/UIlib.h" +using namespace DuiLib; + +#include +#include +#include + +#include "HanAnim.hpp" + +#include "pages/page_base.hpp" + + + +class NetToolboxWnd: public WindowImplBase { +public: + enum class StatusIcon { + Ok = 0, + Info = 1, + Warning = 2, + Error = 3, + Loading = 4 + }; + NetToolboxWnd (string_t caption): m_caption (caption) {} + + // duilibҪغ + string_view_t GetWindowClassName () const override { return _T ("NetToolbox"); } + string_view_t GetSkinFile () override { return _T ("main.xml"); } + void InitWindow () override; + void OnClick (TNotifyUI& msg) override; + void OnHeaderClick (TNotifyUI& msg) override; + void OnSelectChanged (TNotifyUI& msg) override; + void OnTextChanged (TNotifyUI& msg) override; + void OnItemSelect (TNotifyUI& msg) override; + void OnTimer (TNotifyUI& msg) override; + void Notify (TNotifyUI& msg) override; + virtual void OnDropFiles (HDROP hDrop); + LRESULT OnLButtonDown (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) override; + LRESULT OnLButtonUp (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) override; + LRESULT OnRButtonDown (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) override; + LRESULT OnRButtonUp (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) override; + LRESULT OnMouseMove (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) override; + LRESULT HandleMessage (UINT uMsg, WPARAM wParam, LPARAM lParam) override; + CControlUI* CreateControl (string_view_t pstrClass) override; + + // ṩĹܺ + void show_status (StatusIcon _icon = StatusIcon::Info, string_view_t _info = _T ("")); + void show_error_code (DWORD last_error); + LRESULT invoke (std::function f) { return ::SendMessage (m_hWnd, WM_USER + 0x101, 1, (LPARAM) &f); } + void async_invoke (std::function f) { ::PostMessage (m_hWnd, WM_USER + 0x101, 0, (LPARAM) new decltype (f) (f)); } + void direct_page (size_t sel1, size_t sel2 = string_t::npos); + CControlUI *find_control (POINT pt) { return m_pm.FindControl (pt); } + HINSTANCE get_instance () { return CPaintManagerUI::GetInstance (); } + CPaintManagerUI *get_pm () { return &m_pm; } + page_base *get_moment_page () { return (m_pages.size () > m_sel1 && m_pages[m_sel1].size () > m_sel2 ? m_pages[m_sel1][m_sel2] : nullptr); } + + bool enum_pages (std::function f); + + size_t m_sel1 = 0, m_sel2 = 0; +private: + // ܴ + void ui_update_data (); + + string_t m_caption; + HICON m_icon = ::LoadIcon (CPaintManagerUI::GetInstance (), MAKEINTRESOURCE (IDI_ICON1)); + DWORD m_last_img_status = 1; + BindTextUI m_text_caption { _T ("text_caption2") }; + BindHanAnimUI m_img_status { _T ("img_status") }; + BindTextUI m_text_status { _T ("text_status") }; + BindTabLayoutUI m_tabM { _T ("tabM") }; + std::vector m_tab { { _T ("tab0") }, { _T ("tab1") }, { _T ("tab2") }, { _T ("tab3") }, { _T ("tab4") }, { _T ("tab5") }, { _T ("tab6") } }; + std::vector> m_pages; +}; + +#endif //__NET_TOOLBOX_WND_HPP__ diff --git a/NetToolbox/Resource.aps b/NetToolbox/Resource.aps new file mode 100644 index 0000000000000000000000000000000000000000..0df270f44a35571d57212260932f8444eeb44d08 GIT binary patch literal 358316 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
=O9k&sN!glU51<7aZo1gwN95A*#)>?83 zdiql{pRYUcIyTdfn}Rm2$aRx8?1Fxaa&7PD+Xw&5`|MBrcb~9eHbGt-Gs9}a-doxs zyzL0KlH4#I+AsXdFWBGxM}OBYF23prVT*t@&PLAhoiiipc7#wjZOlQ{9CeT$E+9A+ zLMkBF^+g;s7iX9|HfIN)?AmB<$(HgPv_K=+o1gv{iD>s`?C=Bot@Xgzu63?L*nCSg zT17(4bj5lE&CwKu)}0c_w+{M=_A9`sDs$uK5O=NN@qAtAq@Y9a!727f$L(}?+NOg( zba90^gzN-ZtrgoW5(u;|rp%CO)oyWiB>_dJ0GSA+(jl!8sljFVz18It=#k@U>dGY> zp1Wh~H)qn493^{A4Ng~wEi4mTDlgcIf1M`P7fSjNIT5crQBzNjFBvR#%WZnzNjvP{ z=l1zbZJGB8mh`0pr}3HxsgD$3kXLlyRsT-bt$DiGACPPfR2?M(Y8 zAo{1Rk8|oXj*Pp$SeH$9d<~GeYIN<(3W=}{oS@q`!R14vNsV>*Z~BQ9ENK1DzDARR6IZu5ni)FL|&xzy@M z5o|4WbL!`P$+RwJ{rgT4w+V_RbxYPyKpWgn2-s^Xn;KTOyjBrTWzX1Y{i#hsBiAk3 z-^Kh}8TKfr;{aNttP7Bw&vPzyX)W#c33+=0bqla)iwZZ9irR+oH$JmA2y9^~AYVbu zT<71`7q>xg3G0V^{n$P_pQTvnQZ$rn9GCv{6@SSIlcHIqPj!p>*7^5VAbX&{bbei& zsfN`)FL{tvIZ}j^*L)cFjTO(X-^eMLh~0{v3LuhCAATDs=GRl|r6oyVvvFxNka}-b zC*b=c_&S>649C-UeeR+h?#bf?)r zZNuyzN$plAv2!;=>r>3!enuA$-d03DOVN;fElb*c3$qvO`nk{9txK2P%1|wd(UyU_ zw#warOt}h~^pM|7_~Uj!*LP_-od*08>{GJ>g%A11c8@%A(Eg*p^dbB3A9;@jUHk=o zZ@dN@3)9`Fv~7-TEyy4aIMoXa&)Hx5oBxG<>MNf{5D{G&hRj1ZZtH+ahLf*M`a=eIjK6977#dy!?BY` zZT?+t>(Ab_aWJq5vR6PbUGiT)=L9kjndO$D#=FDHk%jfM5G-l}v;H>Fp5eOK^2yG- z?c~0|ZrIm&HEST86kXkHVb^VyBnlQ_1R2>~QXDBySqMQ}3AB}t(vXf^H=tgB zr?ViF0+sutKL2)MBF7s?)iX!u-Fx->x$UbSu(k50oe#dsYgGFDXr#?eiPAigJWm&) zfRx#*wGaKd_{X8t0t26WYZHl-e||`ujt9-Fjmc^xti-f4g}7c(jk_63@j#cuS~@C~0>Z zrRE<>>=kr)@jU#iB!~BCmEr|o&UB4ZI6#l&!aTMZ9hLdeM%J}s`xfoW%m0(T_QLb< zxUQw?(49o5b=Pz7r2gyPVIpEp+UxUaU^aqu>q7CQ8`%RN+;9KupZ(J|eX5IHYT#6( z*}9I0yr-GZyc|>X(;*pMg(2QtyKcY!>3?T`{ipth-Rdux-y!i+H8`hz-ND~kNIkl^ ztQ`b49P|5d*G|9xfDPtevqh3ahe})_GbN+V4+C3sx1~CKe9$;&okHJnc+{Z;iarjvFF0!Nn|j3J@DgplVM&1kaVSk^>Bb@vjIHoH zHuT5Vsiq-yCv7dzxc355E9~q}gb>g*Z_(!Mv`uGocB%ZD-HLBo%Z)fwEMTx&eiT)@ zSZXNxX`N!;?(^PfZDe2Zbh8&o!EVvnZIDw61B$#u)lr)*AG1Do*7vE(MVwVCR5`(k z1PBSxD*=Hr@*$o<8)}2EQTS@Dx2Fqcb^+>9J3S$cyK3DrFA_muZ3-UWnh17&4|S zn(9)?T;U$mDRYn+s$2_@Ezk@mA9du!WuaQ$cN)6rFmS~=u>m5zxaQgQTSF3f%k4O% zo7U*^FwH!<)|ZZu*EHKqB(Wr_>_k`lc9*uaG`4QIvv(J~JBTup1G*G}{ehi(sf|A9 zd;Ym=hBPpNt?~;CUFC0N(asOO$_`$SDIPG#?uu2}^!o932V_3=9U*ywO@(u%tls)D%CKA6(H1s&Ewug(UW2^K0Rs(d?*`BoZ4;;^X(_Ev z{nTcAk=;D#+c$3_?*iOgYtPl@C?v(fy~+YYN*>e|jY(!R92eW9hTXLe~>|zl$ zwW=E}g=KDYb|2OF6Sk}+KZcW9hu2jcV007;U7L0{?eTO4yw=H+Io$eD4Z-XJObCWh z+g081noib2uE;w`Qn(-NoK;gB3>JnzXp!Pjq_`Iilu{_}PH`&)EAA{#fuO~U1nq}A z#T^R43&mZM5S-uyD;jKVc6Rp{?9JYtdEPm5&gFCUjw*TT`43W7Tr!KZd^gJu8y`Qd z#q)90ggD`f9WQ1cyHL%)Z7F^IK&iAF!VX#H4#|){CV-+ ze8yujsbUo-8S!R7`=S6slC|leBB8jNom{&B{LUKp;qa?st-*l(4MHFPPN5su)9iZ; z$ssmGFlu&-hM9+R(9v8`f=*6`a%RohXga$!G1*NgPK#i{H$+)rCC|>$h$lWCwvx%@ zBdB>IAMq#$H*1B{uWH1&~;#oe<6U= zU$z-H@Z>9Ab@a|HQ)eo-I{3xYE1lPD!Xh7+5(e6;GRLiSNJWj3mmJoCRfxvp?&Y#f zFItP8IoA86YawgNvLqG@LCO<}c^ist@1Cu+xS}^7aNpjdE!S$Bg95kZ#@-IwQ}JsE zXiumeX6$rLDh6II*TE0k@iP*+c8Z@LQ>Q-D^TsVMyb0M2>jPKKIV}*9Vcmc;81_vP zuJ3z)Ug117aiETHP9jo&4KR`(eZuH6Zpptbm$sI^P73~o&AM_%u)SFzfYG8N=vi%f-%V7Q0-s@N(< z;G~p>%fRhl7WadCB=KF@KB9 z9SvDly625-TPEUcT5YBWBo&=dkmd1VEADzH{yGtGf3Mad{^l$mfnYtEQL&6~!-3`T z{Q@N;LF1&~JcL@^7FtE?)JZ5bwjp%C5{Cbv2s6P$F1u0nx&oD!U!puiAQ{|xyh88h%OleLcN75A#qMr}V|1lG4<&Q3o23Vb7> zfGP;qp|Csam}bjSOl|&FxsbSj1{)#$4RVrO5UW$!7zi;|LST@@X>nZeb7hyQUvPMZS~k4ag~1$U9)Dq z)6*(~7JlwQS6I|MK0fW9z&U!(Pn_+4UVGDybk0Y9Es^)hnk^z?EAO08S>V=G(5HC0HbCjVLmlfZ$FwRnJT^3Z}nUBf8qp2J` zbTj*XsJfdQ>2^{Jh0_}bE$`eR#yQuVl;WzkoSMTB#WFvp2(twzg> z-15EkSG+RQP@y=c1jC^P4CgzXmG4&{NV(Ugw^sq@IdsYcdLbyvaI++#!xyL-d75dxC3B}^ z{JXL9-bIxi5U#Pf(4kC+&|8C3*_qtfzZ^r4Ne+qTZ=F0zyCEY4vjzjIsba5_xgh$;7B z^eW+WMDgOoCvbEeo0RBFp;Qa+QUzIp6~Z66;$7xHKDpC4Dmx=RL}KIl6-FMwX56f_ zJKYnja_&YAs(4rc!t`1pJu`pM5zRBs7R&Pl>z-n!RtU-PT!6op)W;w3e+YUP(@rUj zI!5b3NFrN3A36b@SB?!Oi&IQg16!sVuHwt$28lCYFszWgl?2T)Jcd}v@x{>LspryN;49@i zAE!de^}V{w=Vvv2)E!8#{mj$Z2j#x98ob*2$a>ferZiE{o-0Op9wGFrT!~SLr15S- zOZTiwXpLKw46AF=vppBTZ`)DR!XKAb-WD^Z2(rKWZsE;aig`6kaoCOkpIAVn@nyd- z=A`zrR2mxU_rkVnU+~e&7!WKv$c<*s99SZMttS;!4cLU0Ihik7pe^AFyOoSBpY|0@f z*ejt|eOj?|$rmc!sJub;LORxm^QoB*yUzQIfo*uU-*@RByE0eucxK**Zpe2;E|)B^ z641w)%fLYD*<^>-mp>ztDPwC)OOk2SSJ8rjjL$WWbgn5+xq_(pEYt57s|QY~R6r&h z6}bwA-IcQJF>T1zrr>KsPWl*aetsw7zdTN#zeCBL-3@4(fk_Ro>Uz%GT&5Y%)s%E_ zMYSNwo`g0>e^b*eiQuP?HWB~spE@DYm)P4lMm0W~{0Aj$4Sg}J#TU>mKKSxL%TN9~Lm;^q#CIkYx49Xa1t z%nS=8za)Py1r;&-$HvVQm5Ap9Hu%r443EL#zE}pH&<81+@UFYUnS&Rv$K=2K_t^x8 z|NbiG|MCAkJ{{8e^^|w(XlcG-e7tMO)^1>6+kMjWi1;mW9c0wYGx2*NqXUEn_Ef$t z%`HqgpRQcYqVAcqaVa%A1AimdH)M)8wxp1TP@9t&_i0gPrGIj?)iM4WEM^cR5os`d z0<^2z;~P%qhzBrc1~t|HP*~-z@04qkY1Bks-oODddfu|)3(efh?`<7)O0RAyS)4b0 zW_DrsR;%Hd52X#I!+#oF;7FUtPAo<*wG1z4Lhr(wV-wD;W*RFCP_|Ye$OuR1oWUns zp%hV|inRX%$@ZKyL}4@*HN5+pn3Pq8X|z%mENK(Rbd-1W2{~vQJhF@Wwp! z$It>vr9%0S?8uwjQi78ish^dzj(n)TU^9wYoo76tx=&RG>~+*ba23WbIhtDvKu&%Y zYjFtyoR2(8=ejWrRRUAC`9Uv@z3^|-j+&&Rg0=EaX#7W=!@vwKnPHxpRC5A;VC!QbqB!H2L4;7sXjUw{s9c0dn1LXw6fVNxT0ncx#m?)muIz zM)&q?~ z4Unfzb6yN@R*LM-J~v|rrG5VyCKh^3dHaX#+cUBh5o~N(E=M3ujBXn*s{{7;)7X3Q zqsN=?3E_vYcBb)=O^p$hFJxYQR%l@E?#NcTL=Af|twjwhY~ov~r*$;6Fo(&jZupNN>R_D!>3UoiJ^W1O;0?><$I@7x!`P?Wsi)+HCNUYT}>3lOy+vGKVb6N`h zAeS-huZ%R4>xczJur=PCzDmgj)|1OGpQ`U?@e^)`z&yt7y>O@8W^%I%wMB1>cjyM> zXD@N_>06j`_eyf9D81Fy6H|Wzy9wQ%CwjM$+xt(&8@$V)w7XCApEABX4m~`)e9V)e zdQWPBKVog020jDF_kaI=JQ}qj1o^yW`#b7Qc7dYJ29Kpiuo)U7RwvG3jZ9 z*hK8QXytyK;%>cxcxe`_2r=cAT@Q+v6k{^mR(B`LfpEoSczkGh)5sKGXd~|&Njfjq z8tK0;(2=%4 z7rkUcjo`|&;mVjB84L#s4{K9+#hy6VVkUU&vrV`SPm1*7J znhh+rCV^mupNT}Y&g*Tn4kKBnQU+%}N%XHEdBuA1{*I3<3jJ03iwjZz|K{q?`d(Es<&gwp_|%}YB2cOv;gFeK2E>dKL4@R1P(o26$X8exVw4B7UiwFGxBsUZt1;+fBxpOnY}J;LR`MS z^;Iq62Wt!D`c{_uR596f=6z+Figy+^XA46bA{d3~F0OP;*@Ry~ktXb4?kI=gt_^zA zxNdSz@l5<#r0QJW*27J|HX>*vBd4~JC**L>occAS4FY>0Q%d1=k+SZ&>iXcMCI?L8 zIWTMNXIM)VJr7gfCs_=}4d>kDvRY&YsC^r|D2g=l`2;GL5T%<2U!v(2A>fV2xA_3z zTRK!;265JFCE)tD%)g|+ZE#D8g8WY{Hd36oo$u~J~VaIiJgq)5nbD2 zH<~_qEpll5o_Sw~^+d@-!J%Q#-%+qm4i>D}j);!Na;xXPd7PzOl84-?O^I2j)3(<* z<(lwX)v`Nhf!FB2ksQV54+~3D)Yi<~G4u}ggS&1-*8+-hYvt}$xy5g$i@Lneps1>~ zPR9*Pj7Rxce4Lt?8gr4>53GYXTjE3tmFxO6TGzuk)B(TcF38uNH?lx$jE|uYdlq-0 z^q)`vkVw6*Y@AME_G7EVkwtQ24Foj zYK?`2MOCu- zG&g=Rmo>)wP=h8YO72uhMWZ~!b#nqju$8i|Dh9l8hmi&*NoI8oP2xN)W+)bnef_}6 zooXyA?eN^2FTct9@frqrid#9IG%nwhCD$bg_8aB!ZmI=*fudrrV{(TXU&Eyj7tK;1 z!KT7E@g5Z7Jqp?nNd?p2)NUN{xVbmK%Be$DkK{5jRbFgeX-ym~ZBIaNRvffL~X(c!8ebKXWCpYzN*yO@Ubn~@ zeo>ZDv_D7Ux~XodcMl?ga|H^KkKbJ$$cH`ae7vWR8vd4%y%Be(d?uV`pEP9~m>k0Wl(O>LV7k{yRaCygZ=4`0My1^HG9`rYN8;?zJ#S@=kvTd#_ zXJ>vE2gy`k>{@7jNAnSmw-UooFxcF|p_RO=KV=w2vWTUUODj4j-`X9#ZuUh@$#}Y3 zAcT&e*ZjQtvUIj_u(Df9rX(u(^w|XWCGU#G(r7X0oSorwp)HqxMaOC%@D zrjt_qZyem_xcx&$+JXRnSaSNWNkrpT!&Rs^nz~?dC-jKTn&Db0b`VEncI9|v*sxC6uItWYUGMd4stb0HRt1TyF@a^@Ngk;gA+{zbhBl#r;h3 z6mIU!64eUTQDCu%E_htXM~_{(2XC#3YyvP@;hmR6Rm*9clOJoywK*yR*bD*lF&|@R z!$W;3!n$@Va8O|!l04QY*$=cIhO&Fc#v@aO9 z#Xhyk|AGciNWPp-5${Pg2+?|Ey^T~HW8mo?8l7$`kFaQ@W>=^*tQotkumYUSjtji4 zrl|lIq%Jw$eOJ5<4ivhHvMn?$*a#IXnT)Sp)aor>+(3&XV$cpI*A~{E_wOgG_BAfg z@Yv3~7L;8mpG=y|U3qaz^PgSxmOF?d|75GUEMmaQCocRGKQEk4!fFgIib?$ThNa-r zm<=o*pXwD@8o=Z9d43Z8`62TW%1k4B8gOzi=Ym$ zV%n&AN!I|A?h7Ps75D9FF+~zq=Wj7sdA5!iAM{X|Eu!=v@X4tZWQezMv{P$+RBo## z?k=-DNnHTcU_&*tm_g^Tkd&zlkrcdT)lDx+9qE-%A8aUcI$3^^IDq*{U1)8EJKC*K zhvecOdwk)kHvk4|UM0aMuw8#=e^e*wwVevXT!vk;n{s6fdBSfJ=|it&;7S*@yMN?S zv($T6w$*ZTYypAuKahtDVWqn554C70KzaVh~i|mcA6UJ-X z#yMgu9kkWHYlQzkU5@1UtG$%!ik@y?jzpt>=9<=hsu~FPAP&h>cmgnJ;Rw%)<@@Gp z6{VQQT$aHsM|V4nw==*53`~6VBd*>rXg{|3LA{O@j*~`0#G^W-Zzmi}Rdd&dq=xz` zeY6yWpB4tt`7|G=6S&YUv@Zp>3hLh|M%R4%p_bU^_S2yIX(b(?ndExW?0zQFKuV_+ zU7ch$w6dgmtN>aC$VVqPR3BDOzB~sB$#qm9^wgqj`gcy0jds<5Xj>L_vZXh}xer93 zIA$}WNYF2C@2G}ouG?3Yd@0Ae>RJ8(0TaF_>~ZhJ~-T)@}r{u<_&sA)ZC-~9vo zqu==tX^Q(8ZM>W^(9sGpuZQfWYQO!iaF>$SK{VUu8xzg)3D%VQXB@T5v&$z>4a6|U zhl(TwZ;rb2AijS^2P>Q1W_f^#ycS1DZ36mgtmi@f%IgVfkGl1mf(&pnu+A3fychjK zREGIs0>=HCVhVYW29lf@rJGs2kYp(s(np4+bMDVKL?6WxYaoiQE6o_q*cEK$WWxuW8!QPnj zeG>L_j`;8GN|~MR2Lr=}T=)sxnqX7xoXpX0C!7E^U8-lTxN)vVK4W1DH1d;&%ymZt z>{eK`am9Ebh8B(*)YQkG0Jve5*Sm(Tlx`e&_siOon+ehtEG)hIaev~O;59cTs^v2u z=0e@5L%kZExenJQIl?YJ@je6kPDO_mI$Od%1$xJmQ80=VbOIAl5R7?h}a774)LYQ|8UBe zorFSMt6E3TmIXwKCiOm5xtmhgGDWZr!ge0Egts+-@x2zw<3ia5ulNBA79q|S&VG2P zPSVdW-d!@|SEXKzabrn?qKICq*BWT~c@~7GPWXOzQIJ}729j260YBCUMj(KS| ztjex@VNj{H(LX9=?Q3eAF z0|J)~eAw}xMaJW6-&=j+DNSFQbhA7SberA>3Laq4F7fNi09!D{k6&jTN925({yCW9 zuSz6WZ@M;AYkeetN8j56=u^I6ZD=E)x9O@n;FDKw=2H4fdc|c-@l*cA`)tIKoaIB^ zOMF8nz5$}kc7(tok&MBaHM0CxYK@;*moAsUg+j8_XomWM9YVFA%A3g4UIC&hd{qE# zQ;ilINBXDAs@~>tdu$7M5c2oh5q+(R54swhKR@K24eV4TFOf`@O-F<&?5XZYj)QRB zJX2t*A56sSFq2;vzPI0k#Vy*Va@_z0a=gw>R2gpvN^6R~u%Ve7M4^LIHjL-Vn_WxM zrDt?&#q=Yfc6RNIv&`v3_1OJ+=b1nFitx+dn-djof1sg99Stq6rNS|zfgtmyoF!@; zdZnd8?!niweaT)_Rx%Uft%Gv(@7|>8j(>)!e3`I4&_nCo8d6>HO!{fD9#q2hCastdDw zV@t5(roBMx@X+N#=@|PDt*)oEhv}y)uE4b<3CmBwu{7td)x>pOcZO}QFv7WH4rxPEK?X@-mSr^*A6WSSut(a50C z{L1DElz)?Sw5JC}-A2~M%*aY*I_<*$2rw9$7qiO`Wp>LZ$~>BluUk4BBZlw4JZW$r zm#V>_A?S8S@#Rk>3-veHPT{$@TNC&_<0@!9yR?k-a$e>i=2pipOt8mA>I@ZmSW3gt zdaY?)HWp*juG3V(+HXpLkGF;e(UsendNd@VV(hUS8C~zXUEbq>0xPc{TJiKAOFE6*VR) z4GMSaa*o8U;jGc^p-UMO-V^YP$nG%DRs8mo!7 zkju?|GZ8CbipQGAfe`)>MCW>-cquY!%T;Yn zuZ924B}@!#o4%6AOU-K)d%3|cXtKJ|z7&%{GM^a9*h+1q5$BvtFnNNuv66O*STcC> z5pNEbdUNaf>CY$LkZWdq`#pK~P;j>Jg)0q^oRsb_xchE*8!FFHR2#GH2b=swmV!s` z8}pI|nky8=ekz@%*iml)pBZgfJ=*6K3a9NiU}Nd z{;usxPVr2nb@TB{PDF@(_aJx?l0q2M(x_jfocRN><+rCwK>EY^edrQdRA~*eVuC8x zvm4k*okx@P6xmRkpX7@Dw$lUv%nl#YV)mtdSIYXQwo)wy8{r=pXJ^jrb-R?()W9dT zHGpzjkFD7I{3L7SmgB2M(K*1xGK&p%ifU5KR+)(Dmw0{-{6z-MVxhID-1)2gL>F`u zvP-Mh#HhGwps&<(VhLSeJHOnUC+8yb%z^dXp*i{E1x@5YH$^rG*S&K+^<`|AJf7YC zT)e(QfxA@=y1CbF^Lf9a-(3a?Yh*1y_lSG9Z%_YIEbO8tqr~ZD-)Pz2^yW^Fp7v2J z$C|XOU-C}iN|mwgEjUOlDB_qXCJC@4Qa$XWrR@8ctR|4lgq!IL7cmDQE$t;&aP%o# z>F*cr@JUS7k3XY!#)f~9lYW+K9ai3pVv+L*!#tV3ru224X`*@pIaNJ#;Z@^Bf>0_Y zcJmtWACk;io+61Z`%A%r>kZe4iXrIHpZ4ZuS+26PlCm6!--@E=E2lj81L_VnIrQF2 z8yJZ=Q=oeN7hgXx-UF{V**0hY_|9mm?`&Y3S(TJ~`~smm<~~6=^I!Z)hQD1=Js)k8 zX??xP!J}9E#^(xWZr8_MDT|2I@w=A$oVWo z)CTf%lka@@OA8f|V|J+s@(Ufwa6ajZk+5b!NcGba=DZRb1^~)(!$~WqbK8tiP4cUs zVjeM7twSZ9jHq0q?Y`S1fALLE%hOPFzYMStRo!Q+#-T7C)Z&>Oqbe508|H-Qtp1ID<1^sRp@*-Rvs^HK!aW-ulp_oAG$5! z-&fh){#Gk}u_PIK6fLsFLZqdE2swk8XadYkghGiBp&(L)9}q_BmE;_evj#3a!Y9A^cT+ed^CZf9R(V_zrDZ744B8UnFfBe4>+qfauTtO&j6x8%mw* zZf->dGe$3_M^VOtx@`zBQptp3YuV6Ej3t2(@BA(HOZv)~=g88Kx?HW(r#H$tZ&{jr z@xWV6tmbp*Tf#e&rbrVhQNHS2h1Rzve~5;6b@Ml>lWS~^!EidUR^!b$rRUFMhBWLZ z+MbR2(9A7-sEK3T&iU~QctYMGnS;2kYpD*Q*d)9q;@-CK*1Dg0In2?Tpbj{%H3w!PUy`a}Ir{g%yQ9c|nOe*rMNAc5(w~m%nhJ3J|`^%6|A=kSh zztC_R&2K<*WkVg5JB8CPpg3K$^5;(t4qY~r-G{_MnKj-$mhY9vYC+}|W|U0n`mbcO zE$cRR5(=!<1L!&!sC#YqbH05Eg`ZukY*}i%g#o^sjbO7zwbT1>t z>;a%j6ahne>$#DignJU3v{n4z39RN6nBlRyObcY0t(fsn5P9h@XRyQfZS4h}+VgBH zR$P^!w0pH@p7Ra9#p@3btJILXdxk*vyGGl>tCF+`m1{s^&6hBMR?2=GAvzP8_1QVu zelp9s#?`K?z}mht2?m2`$^|8|m0ighPsfLANupVu<%OY93E{bMKeG1HhmDu;!fZ_5 zjsIH6qObo6Lo(JME@&Jzp5TJr9L{&lcrIXBU)3Bh6XKemI{pd@EcMei&Cje|&D){s zEH-?Jqid-att1KP>TU%dp_Y^HV|@Bixo{_>r+O3zyP(}(3Gqa3k&7glrNDZU&vbcz zl|Fz9h!NfcOxosBs$}7J3m@o_=tVcxdf9M}{ZK}^B6qW>O$_}ywWvdgOB~Cq=6LMm zooRIhwo#LCZS!|eAYbHX%(*mg5<$q)tBVj%iX{;Xt;2S)QTYMVf-P+pm8aPo(x8)# z{zT^`iN9!1+rX{OmVJ{55g)JRM`zi7Xym;ZX7FfAn4ELG*tcQ)ie0)Q~ z_idu~X%(0pmwch+C}T)|(i>U$1xVNxWFs~d9x#I(_grU9Iab z56*L%Ptu-OwG5EgFdnTnkNJG2Szh8y&i8b|ZLmQ_qZv01X2dDQUVyEW1ENApO3gY2 z1qDuX zd!FOoz~3ppNRJM(X?dZ0zL5*uC3J(Ih=a>7(nU!9;mD@GBex4rQ^-88%lkSIWG&em zxk0vis!206T6!cKkZc>&*o}7E)bM_y8^*5|onW8hu8(NFH zMZ66K#xJJvy#@R!F`Mt%pZGt2#NPj7hi&HIAqu88_CjtD7a3R9YT{w=gvK2!!aZ`-o6AeA+lmk73x*BRR$xTEm^h1;k5sd-Cvxt-(LN78xkbF zXym3fy)=XP(+@3z(MSOf5jOG;1wIh~KC2O<(%n#j^cFynXm^$r*5h1bijv5ivyV;x z$5wVeYXkqLTSF=8&?Z<9{jJ~i%7+wb1f_tOX@qh*+h_azvo;l;uvhb6vs@P?l8vOd z7z55A!6I9_d4MCN6uWP};H^fzLxJ9-AjtW=4BOghRs>$7$rKW3w1B8TWMjfy>2vbttu>E$%4tbB;74OT)vP7%2Jf&s1)@>mRpX6%_ zp#(<-7UUF(eJkpus*2d7|2!`tnJgoBmtrupKE+21XFuxy0b8ZI);3NB{@`Tixq40J zz3xKaKHU0nC7eZMY`GbbeURU%yh5vy%NH@!^lsnUk#vnbZ#k*tQxLrag+?

Om@$ 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 z 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

<~Y&{SB+KLwhE&c7Lxvl&n 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%0D^L2T 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

_|i0%zlI)h+jSw1%q8EFMj6i+X>lKx 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 literal 0 HcmV?d00001 diff --git a/NetToolbox/Resource.rc b/NetToolbox/Resource.rc new file mode 100644 index 0000000000000000000000000000000000000000..5dadbe7711d2642b221715c0cc5aa289eb116c70 GIT binary patch literal 6428 zcmdUz&2k$>6ov1SqF}`f4Dy1cNMxIM0ZVMljtW_FEL%l_gDTjTQI#yIB$*h80(LBU z30{ErVVhTA2jM$Cx|$hjWMY|xsHsuUbl>hieeSR3&EKoGYLV?(+fHm?JsWbjY(!s! zzM);(3+wQpj@7ufVvKrbIdb^Bv zkUkBvg_-$9`_>9US~FLITr14e+MnCL)$P!ZY}?9Kv!)$biQa9Vt5&h6^wjy?vrT#` zJp2B_uzZ)J^ER?wc$wz71xr1<;QS-l5f6u)`b&PxoO+9MijNZgpKwoW*ykTSpDVfp8fo)UVHD&!|Gp+`#G8(ru?QI zIQcN+Kc{vNmUgh5*l+6i$Ppg2h3z!4pbD4P{edf*JM)k=4zSZE^B&`2_rtCgdaLvv z(e4IY^qyEwmRdr(WDi)oj@%}@#dD@5X+` zF|q0x502w~f^r_BM++jl?LI3Z2A>iY6<6X*TFZ+uv)1N1O6z&Ko`q0|yp6Dw6EaM9 z(vv5 ztV|SB?I4f3>2kT7WYS~qYsXl)xH{)9j?a_SH?D!@vJXe&=)6^3sOnE_r?ukN(jaa( zktvZM6v4%-7_Jh-Yp|Og@qW#YLX@vCGb^KtW)0nZO4eBC`GI}Ly%Fp*B0n6k?l!q( zi%4~wnaU~JZ71ZQUC{24i|XWzi2bhY5f)nsyC|BqCN+k#WQpi{M0CyKPF9LnSf9*4 zL;pi~yIFoMC9j%ByJlz9z!$H*0Y+UL<2ea?ht$6NAk1P&#^b!3mGYhQbB3~X4SvN; z0R$uTFW)L}z?5b<%SEtea4ZqoZkj%-;X+G+013ju976%1z zt8VTS3A)69D(wY`3!w%n*axkTp1$*S@7F)8ub&^+e)}qa-~5&R^xLeL{>Kx0Ms|lM zKmGCJ>*p_@{Qhc@9kM>>E4yD5gI^T!iz4jZW_{^R5pzJRNIA_;m9Y_i)#K`*mmYpm zr0 zu{Vwd`j?=HuQ(US!5D9XHhI#xZqHlG*E%|H^-V>!kG`A?CfNNPs!g`S|114F)4#L> z-jsP%k1N-y$MvVR61k*z{!_X(QDvrl)5J6N1*nJx3t8-KLX?l@85nA0P38LRPIp0b z4F*N*CP>iNV99o_$h<7e=v z`c&LH_GxC*GP8UpT&3lb-uX{yX8o()HLUhie9r%m`}SX&mQxpQ^Uk)~xQf5&TjA{g o_~kyGpHl5zzZ&bi%FSPmoy+CsWWG&ziQM`wF#C;oR_{9NKWR$(Jpcdz literal 0 HcmV?d00001 diff --git a/NetToolbox/StdAfx.cpp b/NetToolbox/StdAfx.cpp new file mode 100644 index 0000000..09f15b6 --- /dev/null +++ b/NetToolbox/StdAfx.cpp @@ -0,0 +1 @@ +#include "StdAfx.h" \ No newline at end of file diff --git a/NetToolbox/StdAfx.h b/NetToolbox/StdAfx.h new file mode 100644 index 0000000..c51e21e --- /dev/null +++ b/NetToolbox/StdAfx.h @@ -0,0 +1,14 @@ +#define _CRT_SECURE_NO_WARNINGS +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#define _SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING +#define _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS +#include +#include +#include +#include "../DuiLib/StdAfx.h" +#ifdef min +#undef min +#endif +#ifdef max +#undef max +#endif diff --git a/NetToolbox/VC-LTL helper for Visual Studio.props b/NetToolbox/VC-LTL helper for Visual Studio.props new file mode 100644 index 0000000..af7df07 --- /dev/null +++ b/NetToolbox/VC-LTL helper for Visual Studio.props @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + $(MSBuildThisFileDirectory) + + + $(ProjectDir)VC-LTL + + + $(SolutionDir)VC-LTL + + + $(ProjectDir)..\VC-LTL + + + $(SolutionDir)..\VC-LTL + + + $(Registry:HKEY_CURRENT_USER\Code\VC-LTL@Root) + + + + + + + + + \ No newline at end of file diff --git a/NetToolbox/db/db_Sqlite.hpp b/NetToolbox/db/db_Sqlite.hpp new file mode 100644 index 0000000..26dabf8 --- /dev/null +++ b/NetToolbox/db/db_Sqlite.hpp @@ -0,0 +1,119 @@ +#ifndef __DB_SQLITE_HPP__ +#define __DB_SQLITE_HPP__ + +#include +#include +#include +#include +#include + +#include + + + +class SqliteValue: public std::string { +public: + SqliteValue (const char *val = nullptr): std::string (val ? val : ""), m_is_null (!val) {} + SqliteValue (SqliteValue &o): std::string (o), m_is_null (o.m_is_null) {} + void operator= (SqliteValue &o) { __super::assign (o.cbegin (), o.cend ()); m_is_null = o.m_is_null; } + void assign (const char *val) { __super::assign (val ? val : ""); m_is_null = !val; } + bool empty () { return m_is_null || empty (); } + bool is_null () const { return m_is_null; } + +private: + bool m_is_null = false; +}; + +using SqliteRow = std::vector; +using SqliteColumn = SqliteRow; +using SqliteTable = std::vector; + +class db_Sqlite { +public: + db_Sqlite (std::string_view path) { + if (sqlite3_open (path.data (), &m_db)) { + if (m_db) { sqlite3_close (m_db); m_db = nullptr; } + return; + } + } + ~db_Sqlite () { if (m_db) sqlite3_close (m_db); } + + std::tuple execute (std::string_view sql) { + std::string info = "ִϡ"; + char *err_msg = nullptr; + SqliteTable data; + std::function _proc_data = [&data] (int argc, char **argv, char **col_name) { + if (data.size () == 0) { + SqliteRow v_hdr; + for (size_t i = 0; i < argc; ++i) + v_hdr.push_back (col_name[i]); + data.push_back (v_hdr); + } + SqliteRow v_data; + for (size_t i = 0; i < argc; ++i) + v_data.push_back (argv[i]); + data.push_back (v_data); + }; + if (SQLITE_OK != sqlite3_exec (m_db, sql.data (), exec_callback, (void*) &_proc_data, &err_msg)) { + info = err_msg; + sqlite3_free (err_msg); + } + return { info, data }; + } + + SqliteColumn execute_list (std::string_view sql) { + char *err_msg = nullptr; + SqliteColumn data; + std::function _proc_data = [&data] (int argc, char **argv, char **col_name) { + data.push_back (argv[0]); + }; + if (SQLITE_OK != sqlite3_exec (m_db, sql.data (), exec_callback, (void*) &_proc_data, &err_msg)) { + sqlite3_free (err_msg); + } + return data; + } + + SqliteRow execute_row (std::string_view sql) { + char *err_msg = nullptr; + SqliteRow data; + std::function _proc_data = [&data] (int argc, char **argv, char **col_name) { + if (data.empty ()) { + for (size_t i = 0; i < argc; ++i) { + data.push_back (argv[i]); + } + } + }; + if (SQLITE_OK != sqlite3_exec (m_db, sql.data (), exec_callback, (void*) &_proc_data, &err_msg)) { + sqlite3_free (err_msg); + } + return data; + } + + SqliteValue execute_scalar (std::string_view sql) { + char *err_msg = nullptr; + SqliteValue data; + std::function _proc_data = [&data] (int argc, char **argv, char **col_name) { + if (data.empty ()) + data.assign (argv[0]); + }; + if (SQLITE_OK != sqlite3_exec (m_db, sql.data (), exec_callback, (void*) &_proc_data, &err_msg)) { + sqlite3_free (err_msg); + } + return data; + } + + SqliteColumn get_table_list () { return execute_list ("select tbl_name from sqlite_master where type = 'table';"); } + +private: + static int exec_callback (void *p_arg, int argc, char **argv, char **col_name) { + auto data = (std::function*) p_arg; + (*data) (argc, argv, col_name); + return 0; + } + + sqlite3 *m_db = nullptr; +}; + + + +#endif //__DB_SQLITE_HPP__ diff --git a/NetToolbox/hanAnim.hpp b/NetToolbox/hanAnim.hpp new file mode 100644 index 0000000..f8deb12 --- /dev/null +++ b/NetToolbox/hanAnim.hpp @@ -0,0 +1,108 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// CHanAnimUI +// Զؼʵֶ̬Ч +// +// ÷ʾģContainer +// +// +// ڼ̳IDialogBuilderCallbackȻʵֺ +// CControlUI* CreateControl (LPCTSTR pstrClass) override { +// if (_tcsicmp (pstrClass, _T ("hanAnim")) == 0) return CHanAnimUI::CreateControl (); +// return WindowImplBase::CreateControl (pstrClass); +// } +// +//////////////////////////////////////////////////////////////////////////////// + + + +#ifndef __HAN_ANIM_HPP__ +#define __HAN_ANIM_HPP__ + +namespace DuiLib { + class CHanAnimUI: public CControlUI { + public: + CHanAnimUI (): CControlUI (), m_imglist () {} + virtual ~CHanAnimUI () = default; + + static CControlUI* CreateControl () { + return new CHanAnimUI (); + } + string_view_t GetClass () const { + return _T ("CHanAnimUI"); + } + LPVOID GetInterface (string_view_t pstrName) override { + if (pstrName == _T ("HanAnim")) return static_cast(this); + return CControlUI::GetInterface (pstrName); + } + + virtual void SetBkImage (string_view_t pStrImage) { + if (m_is_animate) { + this->KillTimer (m_timer_id); + m_is_animate = FALSE; + } + CControlUI::SetBkImage (pStrImage); + } + + void SetAnimateBkImage (string_view_t pStrImage, int width, int height, int framecount = 0, int speed = 100) { + if (m_is_animate) this->KillTimer (m_timer_id); + BOOL hori = width > height; + int _width, _height; _width = _height = (width > height ? height : width); + if (!framecount) framecount = hori ? (width / height) : (height / width); + if (m_imglist.size ()) m_imglist.clear (); + TCHAR buf[MAX_PATH]; + TCHAR fmt[32]; + size_t szFmt = sizeof (fmt) / sizeof (fmt[0]); + for (int i = 0; i < framecount; ++i) { + lstrcpy (buf, _T ("file='")); + lstrcat (buf, pStrImage.data ()); + lstrcat (buf, _T ("' source='")); + if (hori) { + _stprintf_s (fmt, szFmt, _T ("%d,%d,%d,%d"), _width*i, 0, _width*(i + 1), _height); + } else { + _stprintf_s (fmt, szFmt, _T ("%d,%d,%d,%d"), 0, _height*i, _width, _height*(i + 1)); + } + lstrcat (buf, fmt); + lstrcat (buf, _T ("'")); + if (this->GetWidth () != _width || this->GetHeight () != _height) { + lstrcat (buf, _T ("dest='")); + _stprintf_s ( + fmt, szFmt, _T ("%d,%d,%d,%d'"), + this->GetWidth () > _width ? this->GetWidth () - _width : 0, + 0, + this->GetWidth (), + _height + ); + lstrcat (buf, fmt); + } + m_imglist.push_back (buf); + } + m_moment_frame = -1; + m_is_animate = true; + this->SetTimer (m_timer_id, speed); + } + + void DoEvent (TEventUI& event) override { + if (event.Type == UIEVENT_TIMER && event.wParam == m_timer_id) { + CControlUI::SetBkImage (m_imglist[m_moment_frame = ++m_moment_frame % m_imglist.size ()]); + } else { + return CControlUI::DoEvent (event); + } + } + + private: + int m_moment_frame = -1; + bool m_is_animate = false; + std::vector m_imglist; + UINT m_timer_id = (UINT) this; + + public: + static LPCTSTR s_interface; + }; +} + +#ifdef DEF_BINDCTRL +DEF_BINDCTRL (HanAnim); +#endif //DEF_BINDCTRL + +#endif //__HAN_ANIM_HPP__ diff --git a/NetToolbox/main.cpp b/NetToolbox/main.cpp new file mode 100644 index 0000000..c17ae59 --- /dev/null +++ b/NetToolbox/main.cpp @@ -0,0 +1,249 @@ +#include "StdAfx.h" +#include "NetToolboxWnd.h" + +#include +#include +#include +#include + +#include +using Json = nlohmann::json; + +#include "tools/tool_Encoding.hpp" +#include "tools/tool_Process.hpp" +#include "tools/tool_Path.hpp" +#include "tools/tool_PE.hpp" +#include "tools/tool_Mutex.hpp" +#include "tools/tool_Priv.hpp" +#include "tools/tool_WMI.hpp" + +#include "Settings.hpp" + +// ref: NetToolboxWnd +#if (defined _UNICODE) && (defined _DEBUG) && (defined _WIN64) +# pragma comment (lib, "../lib/DuiLib_64sd.lib") +#elif (defined _UNICODE) && (defined _DEBUG) && (!defined _WIN64) +# pragma comment (lib, "../lib/DuiLib_sd.lib") +#elif (defined _UNICODE) && (!defined _DEBUG) && (defined _WIN64) +# pragma comment (lib, "../lib/DuiLib_64s.lib") +#elif (defined _UNICODE) && (!defined _DEBUG) && (!defined _WIN64) +# pragma comment (lib, "../lib/DuiLib_s.lib") +#elif (!defined _UNICODE) && (defined _DEBUG) && (defined _WIN64) +# pragma comment (lib, "../lib/DuiLibA_64sd.lib") +#elif (!defined _UNICODE) && (defined _DEBUG) && (!defined _WIN64) +# pragma comment (lib, "../lib/DuiLibA_sd.lib") +#elif (!defined _UNICODE) && (!defined _DEBUG) && (defined _WIN64) +# pragma comment (lib, "../lib/DuiLibA_64s.lib") +#elif (!defined _UNICODE) && (!defined _DEBUG) && (!defined _WIN64) +# pragma comment (lib, "../lib/DuiLibA_s.lib") +#endif + +// ref: tools/tool_SerialPort +#pragma comment (lib, "SetupAPI.lib") +// ref: tools/tool_NetInfotools/tool_Tracert +#pragma comment(lib, "iphlpapi.lib") +// ref: tools/tool_Tracert +#pragma comment (lib, "ws2_32.lib") +// ref: tools/tool_PE +#pragma comment (lib, "Dbghelp.lib") +// ref: tools/tool_PE +#pragma comment (lib, "Version.lib") +// ref: tools/tool_MakeAvi +#pragma comment (lib, "Vfw32.lib") +// ref: tools/tool_Process +#pragma comment (lib, "psapi.lib") +// ref: tools/tool_WMI +#pragma comment (lib, "wbemuuid.lib") + + + +class ProgramGuard { +public: + ProgramGuard () { + WSAData wd; + if (::WSAStartup (MAKEWORD (2, 2), &wd)) { + ::MessageBox (NULL, _T ("WSAStartupʧܣ˳"), _T ("ʾ"), MB_ICONHAND); + return; + } + if (FAILED (::CoInitializeEx (NULL, COINIT_APARTMENTTHREADED))) { + ::MessageBox (NULL, _T ("COM+ʼʧܣ˳"), _T ("ʾ"), MB_ICONHAND); + ::WSACleanup (); + return; + } + if (FAILED (::CoInitializeSecurity (NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL))) { + ::MessageBox (NULL, _T ("COM+ȫϢʧܣ˳"), _T ("ʾ"), MB_ICONHAND); + ::CoUninitialize (); + ::WSACleanup (); + return; + } + is_succeed = true; + } + ~ProgramGuard () { + if (is_succeed) { + ::CoUninitialize (); + ::WSACleanup (); + } + } + bool is_succeed = false; +}; + +int WINAPI _tWinMain (HINSTANCE hInstance, HINSTANCE, LPTSTR, int nCmdShow) { + // ʼ + ProgramGuard pg; + if (!pg.is_succeed) + return 0; + string_t path = tool_Path::get_exe_path (); + string_t _src = path + _T ("NetToolbox.exe"), _srcd = path + _T ("res.dll"); + auto ver_src = tool_PE::get_version (tool_Encoding::get_gb18030 (_src).c_str ()); + + // жϰ汾 & ¿ +#ifndef _DEBUG + // Ƚϰ汾 + auto _cmp_ver = [] (std::tuple a, std::tuple b) -> int { + auto[a1, a2, a3, a4] = a; + auto[b1, b2, b3, b4] = b; + if (a1 > b1) { return 1; } else if (b1 > a1) { return -1; } + if (a2 > b2) { return 1; } else if (b2 > a2) { return -1; } + if (a3 > b3) { return 1; } else if (b3 > a3) { return -1; } + if (a4 > b4) { return 1; } else if (b4 > a4) { return -1; } + return 0; + }; + + // 汾 + auto _parse_ver = [] (std::string s) -> std::tuple { + std::vector v; + tool_StringA::split (s, v, '.'); + while (v.size () < 4) + v.push_back ("0"); + return { stoi (v[0]), stoi (v[1]), stoi (v[2]), stoi (v[3]) }; + }; + + // ¿ + string_t _new = path + _T ("NetToolbox.new.exe"), _newd = path + _T ("res.new.dll"), _oldd = path + _T ("res.dll"); + decltype (ver_src) ver_new = { 0, 0, 0, 0 }; + bool exist_new = tool_Path::file_exist (_new.c_str ()); + if (!exist_new) { + // ° + std::thread ([=] () { + try { + std::string url_base = "https://nettoolbox.fawdlstty.com/"; + std::string data = tool_WebRequest::get (url_base + "info.json"); + Json o = Json::parse (data); + std::string ver_srv = o["version"]; + if (_parse_ver (ver_srv) != ver_src) { + tool_Mutex m (_T ("nettoolbox_checknew")); + if (!m.try_lock ()) + return; + for (auto &item : o["files"]) { + std::string _file = item["name"]; + std::string _md5 = item["md5"]; + std::string _local_file = tool_Encoding::get_gb18030 (path) + (_file == "NetToolbox.exe" ? "NetToolbox.new.exe" : _file); + if (tool_Path::get_file_md5 (tool_Encoding::get_T (_local_file).c_str ()) != _md5) { + std::string _data = tool_WebRequest::get (url_base + _file); + if (!_data.empty ()) { + MD5_CTX ctx_md5; + ::MD5_Init (&ctx_md5); + ::MD5_Update (&ctx_md5, &_data[0], _data.size ()); + unsigned char buf_md5[16] = { 0 }; + ::MD5_Final (buf_md5, &ctx_md5); + std::string str_md5 = ""; + for (size_t i = 0; i < sizeof (buf_md5); ++i) + str_md5 += tool_StringA::byte_to_str (buf_md5[i]); + if (tool_StringA::is_equal_nocase (str_md5, _md5)) { + // ޸release¿ֱӴļ + [_local_file, _data] () { + std::ofstream ofs (_local_file, std::ios::trunc | std::ios::binary); + ofs.write (_data.c_str (), _data.size ()); + ofs.close (); + } (); + } else { + if (tool_Path::file_exist (_new)) + ::DeleteFile (_new.c_str ()); + if (tool_Path::file_exist (_newd)) + ::DeleteFile (_newd.c_str ()); + if (IDOK == ::MessageBox (NULL, _T ("ʧܣǷֶ£"), _T ("״ʦ繤"), MB_ICONQUESTION | MB_OKCANCEL)) + ::ShellExecute (NULL, _T ("open"), _T ("https://nettoolbox.fawdlstty.com/NetToolbox.7z"), NULL, NULL, SW_SHOW); + return; + } + } + } + } + ::MessageBox (NULL, _T ("ɣ´ʱɸ¡"), _T ("״ʦ繤"), MB_ICONINFORMATION); + } + } catch (...) { + } + }).detach (); + } else { + ver_new = tool_PE::get_version (tool_Encoding::get_gb18030 (_new).c_str ()); + if (tool_Path::get_exe_name () == _T ("NetToolbox.exe")) { + // 汾Ƿļȣȴ½˳ɾļ򴴽½̲ + if (_cmp_ver (ver_src, ver_new) == 0) { + while (tool_Process::process_exist (_T ("NetToolbox.new.exe"))) + std::this_thread::sleep_for (std::chrono::milliseconds (100)); + if (tool_Path::file_exist (_new)) + ::DeleteFile (_new.c_str ()); + if (tool_Path::file_exist (_newd)) + ::DeleteFile (_newd.c_str ()); + if (tool_Path::file_exist (_oldd)) + ::DeleteFile (_oldd.c_str ()); + } else { + tool_Process::create_process (_new); + return 0; + } + } else { + // 汾Ƿȣ˳˳Ƹ + if (_cmp_ver (ver_src, ver_new) != 0) { + while (tool_Process::process_exist (_T ("NetToolbox.exe"))) + std::this_thread::sleep_for (std::chrono::milliseconds (100)); + ::CopyFile (_new.c_str (), _src.c_str (), FALSE); + if (tool_Path::file_exist (_newd.c_str ())) + ::CopyFile (_newd.c_str (), _srcd.c_str (), FALSE); + } + tool_Process::create_process (_src); + return 0; + } + } +#endif + + // ļ + Settings::init (); + + // ʼ· + CPaintManagerUI::SetInstance (hInstance); +#ifdef _DEBUG + path.erase (path.begin () + path.rfind (_T ('\\'), path.size () - 2) + 1, path.end ()); + CPaintManagerUI::SetResourcePath ((path + _T ("res")).c_str ()); +#else + CPaintManagerUI::SetResourceType (UILIB_ZIPRESOURCE); + HRSRC hResource = ::FindResource (hInstance, MAKEINTRESOURCE (IDR_ZIPRES1), _T ("ZIPRES")); + if (hResource != NULL) { + DWORD dwSize = 0; + HGLOBAL hGlobal = ::LoadResource (hInstance, hResource); + if (hGlobal) { + DWORD dwSize = ::SizeofResource (hInstance, hResource); + if (dwSize) { + CPaintManagerUI::SetResourceZip ((LPBYTE)::LockResource (hGlobal), dwSize); + CResourceManager::GetInstance ()->LoadResource (_T ("res.xml"), _T ("")); + } + } + ::FreeResource (hResource); + } +#endif + + // ʼȨ + tool_Priv::adjust_debug (); + + // 汾 + auto[v1, v2, v3, v4] = ver_src; + std::vector publish { _T ("Alpha"), _T ("Beta"), _T ("Gamma"), _T ("RC"), _T ("GA") }; + string_t str_publish = _T ("Community"); + //_T ("Community"), _T ("Personal"), _T ("Professional"), _T ("Enterprise"), _T ("Ultimate") + string_t _caption2 = tool_StringT::format (_T ("%04d.%02d.%02d%s"), v1, v2, v3, str_publish.c_str ()); + + // + NetToolboxWnd wnd (_caption2); + wnd.Create (NULL, _T ("״ʦ繤"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE); + wnd.CenterWindow (); + wnd.ShowModal (); + return 0; +} diff --git a/NetToolbox/pages/page_EncDec.hpp b/NetToolbox/pages/page_EncDec.hpp new file mode 100644 index 0000000..9bc8c48 --- /dev/null +++ b/NetToolbox/pages/page_EncDec.hpp @@ -0,0 +1,72 @@ +#ifndef __PAGE_ENCDEC_HPP__ +#define __PAGE_ENCDEC_HPP__ + +#include "../tools/tool_Encoding.hpp" +#include "../tools/tool_Base64.hpp" + +#include "page_base.hpp" + + + +class page_EncDec: public page_base { +public: + page_EncDec (NetToolboxWnd *parent): page_base (parent) {} + virtual ~page_EncDec () = default; + + bool OnClick (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("encdec_enc")) { + string_t _src = m_encdec_data->GetText (), t_data; + std::string data_gb18030 = tool_Encoding::get_gb18030 (_src); + std::string data_utf8 = tool_Encoding::get_utf8 (_src); + t_data = tool_Encoding::get_T (tool_Encoding::percent_str_encode (data_utf8)); + m_encdec_percent->SetText (t_data); + t_data = tool_Encoding::get_T (tool_Encoding::escape_x_str_encode (data_utf8)); + m_encdec_escape_x->SetText (t_data); + t_data = tool_Encoding::get_T (tool_Encoding::escape_u_str_encode (data_gb18030)); + m_encdec_escape_u->SetText (t_data); + t_data = tool_Encoding::get_T (tool_Base64::base64_encode ((const unsigned char*) data_utf8.c_str (), data_utf8.size ())); + m_encdec_base64->SetText (t_data); + return true; + } else if (name == _T ("encdec_dec")) { + std::string data = tool_Encoding::get_gb18030 (m_encdec_data->GetText ()); + string_t t_data; + if (tool_Encoding::is_percent_str (data)) { + t_data = tool_Encoding::get_T_from_utf8 (tool_Encoding::percent_str_decode (data)); + } else { + t_data = _T ("(ʧ)"); + } + m_encdec_percent->SetText (t_data); + if (tool_Encoding::is_escape_x_str (data)) { + t_data = tool_Encoding::get_T_from_utf8 (tool_Encoding::escape_x_str_decode (data)); + } else { + t_data = _T ("(ʧ)"); + } + m_encdec_escape_x->SetText (t_data); + if (tool_Encoding::is_escape_u_str (data)) { + t_data = tool_Encoding::get_T (tool_Encoding::escape_u_str_decode (data)); + } else { + t_data = _T ("(ʧ)"); + } + m_encdec_escape_u->SetText (t_data); + if (tool_Base64::is_base64 ((const unsigned char*) data.c_str (), data.size ())) { + t_data = tool_Encoding::get_T (tool_Base64::base64_decode (data)); + } else { + t_data = _T ("(ʧ)"); + } + m_encdec_base64->SetText (t_data); + return true; + } + return false; + } + +private: + BindRichEditUI m_encdec_data { _T ("encdec_data") }; + BindRichEditUI m_encdec_percent { _T ("encdec_percent") }; + BindRichEditUI m_encdec_escape_x { _T ("encdec_escape_x") }; + BindRichEditUI m_encdec_escape_u { _T ("encdec_escape_u") }; + BindRichEditUI m_encdec_base64 { _T ("encdec_base64") }; + size_t m_val_index = 0; +}; + +#endif //__PAGE_ENCDEC_HPP__ diff --git a/NetToolbox/pages/page_Example.hpp b/NetToolbox/pages/page_Example.hpp new file mode 100644 index 0000000..b384838 --- /dev/null +++ b/NetToolbox/pages/page_Example.hpp @@ -0,0 +1,14 @@ +#ifndef __PAGE_EXAMPLE_HPP__ +#define __PAGE_EXAMPLE_HPP__ + +#include "page_base.hpp" + + + +class page_Example: public page_base { +public: + page_Example (NetToolboxWnd *parent): page_base (parent) {} + virtual ~page_Example () = default; +}; + +#endif //__PAGE_EXAMPLE_HPP__ diff --git a/NetToolbox/pages/page_File.hpp b/NetToolbox/pages/page_File.hpp new file mode 100644 index 0000000..965d7b8 --- /dev/null +++ b/NetToolbox/pages/page_File.hpp @@ -0,0 +1,183 @@ +#ifndef __PAGE_FILE_HPP__ +#define __PAGE_FILE_HPP__ + +#include +#include + +#include +#include +#include +#include + +#include "page_base.hpp" +#include "../tools/tool_String.hpp" +#include "../tools/tool_Encoding.hpp" +#include "../tools/tool_Utils.hpp" +#include "../tools/tool_PE.hpp" + + + +class page_File: public page_base { +public: + page_File (NetToolboxWnd *parent): page_base (parent) {} + virtual ~page_File () = default; + + bool OnClick (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("file_analysis")) { + CDuiString file = m_file_path->GetText (); + HANDLE hFile = ::CreateFile (file.c_str (), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + m_parent->show_status (NetToolboxWnd::StatusIcon::Error, _T ("δҵļ޷")); + return true; + } + m_parent->show_status (NetToolboxWnd::StatusIcon::Loading, _T ("ڷ")); + DWORD dwsz_high = 0; + DWORD dwsz = ::GetFileSize (hFile, &dwsz_high); + ::SetFilePointer (hFile, 0, nullptr, FILE_BEGIN); + int64_t file_length = dwsz | (((int64_t) dwsz_high) * 0x100000000); + constexpr size_t sz_1M = 1024 * 1024; + int8_t *buf = new int8_t[sz_1M]; + size_t i, j, block_count = (size_t) (file_length / sz_1M); + size_t last_size = (size_t) (file_length - (sz_1M * (int64_t) block_count)); + string_t content = tool_StringT::format (_T ("ļ· %s\nļС %ld ֽڣ%s\nļHash\n"), file.c_str (), file_length, tool_Utils::format_unit (file_length).c_str ()); + // + // ļHash + // + boost::crc_32_type crc32; + MD4_CTX _md4; + ::MD4_Init (&_md4); + MD5_CTX _md5; + ::MD5_Init (&_md5); + SHA_CTX _sha; + ::SHA_Init (&_sha); + SHA_CTX _sha1; + ::SHA1_Init (&_sha1); + SHA256_CTX _sha224; + ::SHA224_Init (&_sha224); + SHA256_CTX _sha256; + ::SHA256_Init (&_sha256); + SHA512_CTX _sha384; + ::SHA384_Init (&_sha384); + SHA512_CTX _sha512; + ::SHA512_Init (&_sha512); + // + for (i = 0; i < block_count; ++i) { + ::ReadFile (hFile, buf, sz_1M, &dwsz, nullptr); + crc32.process_bytes (buf, sz_1M); + ::MD4_Update (&_md4, buf, sz_1M); + ::MD5_Update (&_md5, buf, sz_1M); + ::SHA_Update (&_sha, buf, sz_1M); + ::SHA1_Update (&_sha1, buf, sz_1M); + ::SHA224_Update (&_sha224, buf, sz_1M); + ::SHA256_Update (&_sha256, buf, sz_1M); + ::SHA384_Update (&_sha384, buf, sz_1M); + ::SHA512_Update (&_sha512, buf, sz_1M); + } + if (last_size > 0) { + ::ReadFile (hFile, buf, (DWORD) last_size, &dwsz, nullptr); + crc32.process_bytes (buf, last_size); + ::MD4_Update (&_md4, buf, last_size); + ::MD5_Update (&_md5, buf, last_size); + ::SHA_Update (&_sha, buf, last_size); + ::SHA1_Update (&_sha1, buf, last_size); + ::SHA224_Update (&_sha224, buf, last_size); + ::SHA256_Update (&_sha256, buf, last_size); + ::SHA384_Update (&_sha384, buf, last_size); + ::SHA512_Update (&_sha512, buf, last_size); + } + // + unsigned char buf_md4[16] = { 0 }, buf_md5[16] = { 0 }, buf_sha[20] = { 0 }, buf_sha1[20] = { 0 }, buf_sha224[28] = { 0 }, buf_sha256[32] = { 0 }, buf_sha384[48] = { 0 }, buf_sha512[64] = { 0 }; + ::MD4_Final (buf_md4, &_md4); + ::MD5_Final (buf_md5, &_md5); + ::SHA_Final (buf_sha, &_sha); + ::SHA1_Final (buf_sha1, &_sha1); + ::SHA224_Final (buf_sha224, &_sha224); + ::SHA256_Final (buf_sha256, &_sha256); + ::SHA384_Final (buf_sha384, &_sha384); + ::SHA512_Final (buf_sha512, &_sha512); + // + string_t str_crc32 = tool_StringT::format (_T ("%08X"), crc32.checksum ()); + string_t str_md4 = _T (""), str_md5 = _T (""), str_sha = _T (""), str_sha1 = _T (""), str_sha224 = _T (""), str_sha256 = _T (""), str_sha384 = _T (""), str_sha512 = _T (""); + for (i = 0; i < 64; ++i) { + if (i < sizeof (buf_md4)) str_md4 += tool_StringT::byte_to_str (buf_md4[i]); + if (i < sizeof (buf_md5)) str_md5 += tool_StringT::byte_to_str (buf_md5[i]); + if (i < sizeof (buf_sha)) str_sha += tool_StringT::byte_to_str (buf_sha[i]); + if (i < sizeof (buf_sha1)) str_sha1 += tool_StringT::byte_to_str (buf_sha1[i]); + if (i < sizeof (buf_sha224)) str_sha224 += tool_StringT::byte_to_str (buf_sha224[i]); + if (i < sizeof (buf_sha256)) str_sha256 += tool_StringT::byte_to_str (buf_sha256[i]); + if (i < sizeof (buf_sha384)) str_sha384 += tool_StringT::byte_to_str (buf_sha384[i]); + if (i < sizeof (buf_sha512)) str_sha512 += tool_StringT::byte_to_str (buf_sha512[i]); + } + // + delete[] buf; + ::CloseHandle (hFile); + // + content += tool_StringT::format (_T ("CRC32: %s\n"), str_crc32.c_str ()); + content += tool_StringT::format (_T ("MD4: %s\n"), str_md4.c_str ()); + content += tool_StringT::format (_T ("MD5: %s\n"), str_md5.c_str ()); + content += tool_StringT::format (_T ("SHA: %s\n"), str_sha.c_str ()); + content += tool_StringT::format (_T ("SHA1: %s\n"), str_sha1.c_str ()); + content += tool_StringT::format (_T ("SHA224: %s\n"), str_sha224.c_str ()); + content += tool_StringT::format (_T ("SHA256: %s\n"), str_sha256.c_str ()); + content += tool_StringT::format (_T ("SHA384: %s\n"), str_sha384.c_str ()); + content += tool_StringT::format (_T ("SHA512: %s\n"), str_sha512.c_str ()); + // + // PE + // + std::vector vexport; + std::vector>>> vimport; + if (tool_PE::read_import_export (file.c_str (), vexport, vimport)) { + content += _T ("\n\n\nPEϢ\n\n"); + for (size_t i = 0; i < vexport.size (); ++i) { + content += tool_Encoding::get_T (vexport[i]); + content += _T ('\n'); + } + content += _T ("\n뺯\n"); + for (i = 0; i < vimport.size (); ++i) { + auto[dll_name, dll_funcs] = vimport[i]; + content += tool_Encoding::get_T (dll_name); + content += _T ('\n'); + for (j = 0; j < dll_funcs.size (); ++j) { + auto[func_id, func_name] = dll_funcs[j]; + content += tool_StringT::format (_T ("\t%d\t%s\n"), func_id, tool_Encoding::get_T (func_name).c_str ()); + } + } + } else { + content += _T ("\n\n\nļPEļPEݡ\n"); + } + // + m_file_result->SetText (content.c_str ()); + m_parent->show_status (NetToolboxWnd::StatusIcon::Ok, _T ("ļϡ")); + return true; + } + return false; + } + + bool is_accept_drag () override { + return true; + } + + bool OnDropFiles (LPCTSTR path) override { + WIN32_FIND_DATA wfd = { 0 }; + HANDLE hFind = ::FindFirstFile (path, &wfd); + bool bExist = (hFind != INVALID_HANDLE_VALUE); + if (bExist) { + ::FindClose (hFind); + if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + m_parent->show_status (NetToolboxWnd::StatusIcon::Error, _T ("ļ")); + return true; + } + m_file_path->SetText (path); + } else { + m_parent->show_status (NetToolboxWnd::StatusIcon::Error, _T ("δҵļ")); + } + return true; + } + +protected: + BindEditUI m_file_path { _T ("file_path") }; + BindRichEditUI m_file_result { _T ("file_result") }; +}; + +#endif //__PAGE_FILE_HPP__ diff --git a/NetToolbox/pages/page_Gif.hpp b/NetToolbox/pages/page_Gif.hpp new file mode 100644 index 0000000..ea8eb4d --- /dev/null +++ b/NetToolbox/pages/page_Gif.hpp @@ -0,0 +1,262 @@ +#ifndef __PAGE_GIF_HPP__ +#define __PAGE_GIF_HPP__ + +#include +#include +#include + +#include "page_base.hpp" +#include "../tools/tool_Path.hpp" +#include "../tools/tool_String.hpp" +#include "../tools/tool_Gdip.hpp" +#include "../tools/tool_Zoomer.hpp" + + + +class page_GifWnd: public WindowImplBase { +public: + page_GifWnd (std::vector *ppvgif, volatile size_t *_pdelay): m_pvgif (ppvgif), m_pdelay (_pdelay) {} + virtual ~page_GifWnd () { + while (m_vgif.size ()) { + delete m_vgif[0]; + m_vgif.erase (m_vgif.begin ()); + } + } + + string_view_t GetWindowClassName () const override { return _T ("NetToolbox"); } + string_view_t GetSkinFile () override { return _T ("scr2gif.xml"); } + void InitWindow () override { + m_init = true; + RECT rc { 0 }; + ::GetWindowRect (GetHWND (), &rc); + ::SetWindowPos (GetHWND (), HWND_TOPMOST, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_NOREDRAW); + } + + LRESULT OnSize (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) override { + if (m_init) { + POINT pt = { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; + m_gifw_width->SetText (tool_StringT::format (_T ("%d"), pt.x - 24)); + m_gifw_height->SetText (tool_StringT::format (_T ("%d"), pt.y - 64)); + } + return WindowImplBase::OnSize (uMsg, wParam, lParam, bHandled); + } + + void OnClick (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("gifw_ok")) { + if (m_run) { + m_run = false; + m_thread.join (); + } + while (m_pvgif->size () > 0) { + delete (*m_pvgif)[0]; + m_pvgif->erase (m_pvgif->begin ()); + } + *m_pdelay = m_delay; + m_pvgif->assign (m_vgif.cbegin (), m_vgif.cend ()); + m_vgif.clear (); + Close (); + } else if (name == _T ("gifw_cancel")) { + if (m_run) { + m_run = false; + m_thread.join (); + } + Close (); + } else if (name == _T ("gifw_start")) { + m_gifw_ok->SetEnabled (false); + m_gifw_start->SetVisible (false); + m_gifw_fps->SetReadOnly (true); + m_gifw_width->SetReadOnly (true); + m_gifw_height->SetReadOnly (true); + RECT sz_box { 0, 0, 0, 0 }; + m_pm.SetSizeBox (sz_box); + m_run = true; + m_delay = (size_t) (100.0 / _ttoi (m_gifw_fps->GetText ().c_str ()) + 0.5) * 10; + size_t _width = _ttoi (m_gifw_width->GetText ().c_str ()); + _width = tool_Zoomer::zoom_x (_width); + size_t _height = _ttoi (m_gifw_height->GetText ().c_str ()); + _height = tool_Zoomer::zoom_y (_height); + m_thread = std::thread (&page_GifWnd::_record_thread, this, m_delay, _width, _height); + m_gifw_stop->SetVisible (true); + } else if (name == _T ("gifw_stop")) { + m_gifw_stop->SetVisible (false); + m_run = false; + m_thread.join (); + RECT sz_box { 5, 5, 5, 5 }; + m_pm.SetSizeBox (sz_box); + m_gifw_fps->SetReadOnly (false); + m_gifw_width->SetReadOnly (false); + m_gifw_height->SetReadOnly (false); + m_gifw_start->SetVisible (true); + m_gifw_ok->SetEnabled (m_vgif.size () > 0); + } + } + + void OnTextChanged (TNotifyUI &msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("gifw_width")) { + SIZE sz = m_pm.GetInitSize (); + sz.cx = _ttoi (m_gifw_width->GetText ().c_str ()) + 24; + //m_pm.SetInitSize (sz.cx, sz.cy); + ::SetWindowPos (GetHWND (), nullptr, 0, 0, sz.cx, sz.cy, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); + } else if (name == _T ("gifw_height")) { + SIZE sz = m_pm.GetInitSize (); + sz.cy = _ttoi (m_gifw_height->GetText ().c_str ()) + 64; + //m_pm.SetInitSize (sz.cx, sz.cy); + ::SetWindowPos (GetHWND (), nullptr, 0, 0, sz.cx, sz.cy, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); + } else { + WindowImplBase::OnTextChanged (msg); + } + } + +private: + void _record_thread (size_t m_delay, size_t _width, size_t _height) { + while (m_vgif.size () > 0) { + delete m_vgif[0]; + m_vgif.erase (m_vgif.begin ()); + } + auto tp = std::chrono::system_clock::now (); + while (m_run) { + // Ļͼ + RECT rect = { 0 }; + GetWindowRect (GetHWND (), &rect); + rect.left += 12; + rect.top += 32; + tool_Zoomer::zoom (rect); + Gdiplus::Bitmap *bmp = new Gdiplus::Bitmap ((INT) _width, (INT) _height, PixelFormat24bppRGB); + { + Gdiplus::Graphics g (bmp); + HDC hDeskDC = ::GetDC (NULL); + HDC hGDC = g.GetHDC (); + ::BitBlt (hGDC, 0, 0, (int) _width, (int) _height, hDeskDC, rect.left, rect.top, SRCCOPY); + g.ReleaseHDC (hGDC); + ::ReleaseDC (NULL, hDeskDC); + } + tool_Zoomer::unzoom (&bmp); + Gdiplus::Graphics g (bmp); + + // ָ + CURSORINFO ci { sizeof (CURSORINFO) }; + ::GetCursorInfo (&ci); + tool_Zoomer::zoom (ci.ptScreenPos); + Gdiplus::Bitmap *bmp_cur = tool_Gdip::cursor_to_bmp (ci.hCursor); + SIZE sz_cur { (LONG) bmp_cur->GetWidth (), (LONG) bmp_cur->GetHeight () }; + tool_Zoomer::unzoom (sz_cur); + RECT _rect = { ci.ptScreenPos.x - rect.left, ci.ptScreenPos.y - rect.top, sz_cur.cx, sz_cur.cy }; + tool_Zoomer::unzoom (_rect); + g.DrawImage (bmp_cur, Gdiplus::Rect (_rect.left, _rect.top, _rect.right, _rect.bottom), 0, 0, sz_cur.cx, sz_cur.cy, Gdiplus::UnitPixel); + delete bmp_cur; + + // һѭ + m_vgif.push_back (bmp); + tp += std::chrono::milliseconds (m_delay); + std::this_thread::sleep_until (tp); + } + } + + BindEditUI m_gifw_fps { _T ("gifw_fps"), &m_pm }; + BindEditUI m_gifw_width { _T ("gifw_width"), &m_pm }; + BindEditUI m_gifw_height { _T ("gifw_height"), &m_pm }; + BindButtonUI m_gifw_start { _T ("gifw_start"), &m_pm }; + BindButtonUI m_gifw_stop { _T ("gifw_stop"), &m_pm }; + BindButtonUI m_gifw_ok { _T ("gifw_ok"), &m_pm }; + bool m_init = false; + // + std::thread m_thread; + volatile bool m_run = false; + std::vector m_vgif; + std::vector *m_pvgif = nullptr; + volatile size_t m_delay; + volatile size_t *m_pdelay = nullptr; +}; + + + +class page_Gif: public page_base { +public: + page_Gif (NetToolboxWnd *parent): page_base (parent) { + string_t path = tool_Path::get_exe_path (); + m_gif_path->SetText (tool_StringT::format (_T ("%c:\\record.gif"), path[0])); + } + virtual ~page_Gif () = default; + + bool OnClick (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("gif_showwnd")) { + m_run = false; + m_parent->ShowWindow (false); + page_GifWnd wnd_child { &m_vgif, &m_delay }; + wnd_child.Create (m_parent->GetHWND (), _T ("GIF¼"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE); + wnd_child.CenterWindow (); + wnd_child.ShowModal (); + m_parent->ShowWindow (true); + bool _recorded = m_vgif.size () > 0; + m_gif_save->SetEnabled (_recorded); + if (_recorded) { + if (m_thread.joinable ()) + m_thread.join (); + m_gif_preview->SetText (_T ("")); + m_run = true; + m_thread = std::thread (&page_Gif::_preview_thread, this); + } + return true; + } else if (name == _T ("gif_save")) { + string_t file = m_gif_path->GetText (); + if (tool_Path::file_exist (file)) { + string_t info = tool_StringT::format (_T ("ļѴڣǷ񸲸ǣ\n%s"), file.c_str ()); + if (IDOK != ::MessageBox (NULL, info.c_str (), _T ("ʾ"), MB_ICONQUESTION | MB_OKCANCEL)) + return true; + } + if (tool_Gdip::gdip_save_animation (m_vgif, file.c_str (), m_delay)) + m_parent->show_status (NetToolboxWnd::StatusIcon::Ok, _T ("GIFͼɹ")); + else + m_parent->show_status (NetToolboxWnd::StatusIcon::Error, _T ("GIFͼʧܣ")); + return true; + } + return false; + } + +protected: + void _preview_thread () { + auto tp = std::chrono::system_clock::now (); + size_t i = 0; + while (m_run) { + Gdiplus::Image *img = m_vgif[i++ % m_vgif.size ()]; + m_parent->invoke ([this, img] () -> LRESULT { + if (m_gif_preview->IsVisible ()) { + HDC hDC = ::GetDC (m_parent->GetHWND ()); + Gdiplus::Graphics g (hDC); + RECT rc = m_gif_preview->GetPos (); + int wnd_width = rc.right - rc.left; + int wnd_height = rc.bottom - rc.top; + int img_width = (int) img->GetWidth (); + int img_height = (int) img->GetHeight (); + if (img_width > wnd_width) { + img_height = img_height * wnd_width / img_width; + img_width = wnd_width; + } + if (img_height > wnd_height) { + img_width = img_width * wnd_height / img_height; + img_height = wnd_height; + } + g.DrawImage (img, Gdiplus::Rect (rc.left, rc.top, img_width, img_height), 0, 0, img->GetWidth (), img->GetHeight (), Gdiplus::UnitPixel); + ::ReleaseDC (m_parent->GetHWND (), hDC); + } + return 0; + }); + tp += std::chrono::milliseconds (m_delay); + std::this_thread::sleep_until (tp); + } + } + + BindEditUI m_gif_path { _T ("gif_path") }; + BindButtonUI m_gif_save { _T ("gif_save") }; + BindTextUI m_gif_preview { _T ("gif_preview") }; + + std::vector m_vgif; + volatile size_t m_delay = 50; + volatile bool m_run = false; + std::thread m_thread; +}; + +#endif //__PAGE_GIF_HPP__ diff --git a/NetToolbox/pages/page_Http.hpp b/NetToolbox/pages/page_Http.hpp new file mode 100644 index 0000000..e6f9ca6 --- /dev/null +++ b/NetToolbox/pages/page_Http.hpp @@ -0,0 +1,246 @@ +#ifndef __PAGE_HTTP_HPP__ +#define __PAGE_HTTP_HPP__ + +#include +#include + +#include "page_base.hpp" +#include "../tools/tool_Encoding.hpp" +#include "../tools/tool_WebRequest.hpp" +#include "../tools/tool_String.hpp" + + + +class page_Http: public page_base { +public: + page_Http (NetToolboxWnd *parent): page_base (parent) { + _convert_data (false); + } + virtual ~page_Http () = default; + + bool OnClick (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("http_begin")) { + std::string type = tool_Encoding::get_utf8 (m_http_type->GetText ()); + std::string url = tool_Encoding::get_utf8 (m_http_url->GetText ()); + if (url.empty ()) + url = tool_Encoding::get_utf8 (m_http_url->GetTipValue ()); + if (m_http_postdata_tab->GetCurSel () == 0) + _convert_data (true); + std::string post_data = (type == "POST" ? tool_Encoding::get_utf8 (m_http_postdata_src->GetText ()) : ""); + try { + std::string ret; + if (type == "POST") { + ret = tool_WebRequest::post (url, post_data); + } else { + ret = tool_WebRequest::get (url); + } + m_http_content->SetText (tool_Encoding::get_T_from_utf8 (ret)); + } catch (std::exception &e) { + m_http_content->SetText (tool_Encoding::get_T (e.what ())); + } catch (...) { + m_http_content->SetText (_T ("ʧܣδ֪")); + } + return true; + } else if (name == _T ("http_change")) { + bool is_show_list = (m_http_postdata_tab->GetCurSel () == 0); + _convert_data (is_show_list); + m_http_postdata_tab->SelectItem (is_show_list ? 1 : 0); + return true; + } else if (name == _T ("http_postdata_ctrl_up")) { + CControlUI *container = msg.pSender->GetParent ()->GetParent (); + int n = m_http_postdata_list->GetItemIndex (container); + if (n == 0) + return true; + string_t name1 { container->GetName () }; + string_t name2 { m_http_postdata_list->GetItemAt (n - 1)->GetName () }; + _swap_item (name1, name2); + return true; + } else if (name == _T ("http_postdata_ctrl_down")) { + CControlUI *container = msg.pSender->GetParent ()->GetParent (); + int n = m_http_postdata_list->GetItemIndex (container); + if (n >= m_http_postdata_list->GetCount () - 2) + return true; + string_t name1 { container->GetName () }; + string_t name2 { m_http_postdata_list->GetItemAt (n + 1)->GetName () }; + _swap_item (name1, name2); + return true; + } else if (name == _T ("http_postdata_ctrl_new")) { + CControlUI *container = msg.pSender->GetParent ()->GetParent (); + m_http_postdata_list->Remove (container); + _add_item (_T (""), _T (""), _T ("ɾ")); + _add_item (_T (""), _T (""), _T ("½")); + return true; + } else if (name == _T ("http_postdata_ctrl_del")) { + CControlUI *container = msg.pSender->GetParent ()->GetParent (); + int n = m_http_postdata_list->GetItemIndex (container); + m_http_postdata_list->Remove (container); + for (; n < m_http_postdata_list->GetCount (); ++n) { + container = m_http_postdata_list->GetItemAt (n); + string_t _name { container->GetName () }; + string_t color = (n % 2) ? _T ("#FFDDDDDD") : _T ("#FFFFFFFF"); + string_t _name1 = _name + _T ("_key"); + BindEditUI edit_key { _name1 }; + edit_key->SetAttribute (_T ("bkcolor"), color); + edit_key->SetAttribute (_T ("nativebkcolor"), color); + _name1 = _name + _T ("_value"); + BindEditUI edit_value { _name1 }; + edit_value->SetAttribute (_T ("bkcolor"), color); + edit_value->SetAttribute (_T ("nativebkcolor"), color); + } + return true; + } + return false; + } + + bool OnItemSelect (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("http_type")) { + bool is_post = m_http_type->GetText () == _T ("POST"); + m_http_change->SetEnabled (is_post); + m_http_postdata_src->SetEnabled (is_post); + m_http_postdata_src->SetTextColor (is_post ? 0xFF000000 : 0xFF777777); + if (!is_post) { + if (m_http_postdata_tab->GetCurSel () == 0) { + _convert_data (true); + m_http_postdata_tab->SelectItem (1); + } + } + return true; + } + return false; + } + +protected: + // һ + void _add_item (string_view_t key, string_view_t value, string_view_t btntext) { + static size_t n_sign = 0; + CListContainerElementUI *item = new CListContainerElementUI (); + item->SetFixedHeight (20); + string_t color = (m_http_postdata_list->GetCount () % 2) ? _T ("#FFEEEEEE") : _T ("#FFFFFFFF"); + bool is_new = (btntext == _T ("½")); + if (is_new) + color = _T ("#FFDDDDDD"); + item->SetAttribute (_T ("name"), tool_StringT::format (_T ("http_postdata_item_%d"), ++n_sign)); + // + CContainerUI *ctnr = new CContainerUI (); + CControlUI *ctrl = new CEditUI (); + ctrl->SetManager (m_parent->get_pm (), item); + ctrl->SetText (key); + ctrl->SetAttribute (_T ("name"), tool_StringT::format (_T ("http_postdata_item_%d_key"), n_sign)); + ctrl->SetAttribute (_T ("bkcolor"), color); + ctrl->SetAttribute (_T ("nativebkcolor"), color); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("10,4,0,4")); + if (is_new) + ctrl->SetAttribute (_T ("enabled"), _T ("false")); + ctnr->Add (ctrl); + item->Add (ctnr); + // + ctnr = new CContainerUI (); + ctrl = new CEditUI (); + ctrl->SetManager (m_parent->get_pm (), item); + ctrl->SetText (value); + ctrl->SetAttribute (_T ("name"), tool_StringT::format (_T ("http_postdata_item_%d_value"), n_sign)); + ctrl->SetAttribute (_T ("bkcolor"), color); + ctrl->SetAttribute (_T ("nativebkcolor"), color); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("0,4,0,4")); + if (is_new) + ctrl->SetAttribute (_T ("enabled"), _T ("false")); + ctnr->Add (ctrl); + item->Add (ctnr); + // + ctnr = new CHorizontalLayoutUI (); + ctrl = new CButtonUI (); + ctrl->SetManager (m_parent->get_pm (), ctnr); + ctrl->SetText (_T ("")); + ctrl->SetAttribute (_T ("style"), _T ("x_button")); + ctrl->SetAttribute (_T ("name"), _T ("http_postdata_ctrl_up")); + ctrl->SetAttribute (_T ("width"), _T ("20")); + if (is_new) + ctrl->SetAttribute (_T ("visible"), _T ("false")); + ctnr->Add (ctrl); + ctrl = new CButtonUI (); + ctrl->SetManager (m_parent->get_pm (), ctnr); + ctrl->SetText (_T ("")); + ctrl->SetAttribute (_T ("style"), _T ("x_button")); + ctrl->SetAttribute (_T ("name"), _T ("http_postdata_ctrl_down")); + ctrl->SetAttribute (_T ("width"), _T ("20")); + if (is_new) + ctrl->SetAttribute (_T ("visible"), _T ("false")); + ctnr->Add (ctrl); + ctrl = new CButtonUI (); + ctrl->SetManager (m_parent->get_pm (), ctnr); + ctrl->SetText (btntext); + ctrl->SetAttribute (_T ("style"), _T ("x_button")); + ctrl->SetAttribute (_T ("name"), (is_new ? _T ("http_postdata_ctrl_new") : _T ("http_postdata_ctrl_del"))); + ctrl->SetAttribute (_T ("width"), (is_new ? _T ("80") : _T ("40"))); + ctnr->Add (ctrl); + item->Add (ctnr); + m_http_postdata_list->Add (item); + } + + // + void _swap_item (string_t name1, string_t name2) { + string_t name_key1 = name1 + _T ("_key"); + BindEditUI edit_key1 { name_key1 }; + string_t name_key2 = name2 + _T ("_key"); + BindEditUI edit_key2 { name_key2 }; + string_t name_tmp = edit_key1->GetText (); + edit_key1->SetText (edit_key2->GetText ()); + edit_key2->SetText (name_tmp); + // + string_t name_value1 = name1 + _T ("_value"); + BindEditUI edit_value1 { name_value1 }; + string_t name_value2 = name2 + _T ("_value"); + BindEditUI edit_value2 { name_value2 }; + name_tmp = edit_value1->GetText (); + edit_value1->SetText (edit_value2->GetText ()); + edit_value2->SetText (name_tmp); + } + + // ԭʼݻת + void _convert_data (bool list2src) { + if (list2src) { + string_t post_data = _T (""); + for (int i = 0; i < m_http_postdata_list->GetCount () - 1; ++i) { + CControlUI *ctrl = m_http_postdata_list->GetItemAt (i); + string_t name { ctrl->GetName () }; + string_t name_key = name + _T ("_key"); + BindEditUI edit_key { name_key }; + string_t name_value = name + _T ("_value"); + BindEditUI edit_value { name_value }; + if (!post_data.empty ()) + post_data += _T ("&"); + post_data += tool_Encoding::get_T_from_utf8 (tool_Encoding::percent_str_encode (tool_Encoding::get_utf8 (edit_key->GetText ()))); + post_data += _T ("="); + post_data += tool_Encoding::get_T_from_utf8 (tool_Encoding::percent_str_encode (tool_Encoding::get_utf8 (edit_value->GetText ()))); + } + m_http_postdata_src->SetText (post_data); + } else { + string_t post_data = m_http_postdata_src->GetText (); + std::vector vitems = tool_StringT::split (post_data, _T ('&'), _T ("&"), true); + m_http_postdata_list->RemoveAll (); + for (size_t i = 0; i < vitems.size (); ++i) { + std::vector vkey_val = tool_StringT::split (vitems[i], _T ('='), _T (""), false); + while (vkey_val.size () < 2) + vkey_val.push_back (_T ("")); + vkey_val[0] = tool_Encoding::get_T_from_utf8 (tool_Encoding::percent_str_decode (tool_Encoding::get_utf8 (vkey_val[0]))); + vkey_val[1] = tool_Encoding::get_T_from_utf8 (tool_Encoding::percent_str_decode (tool_Encoding::get_utf8 (vkey_val[1]))); + _add_item (vkey_val[0], vkey_val[1], _T ("ɾ")); + } + _add_item (_T (""), _T (""), _T ("½")); + } + } + + BindComboUI m_http_type { _T ("http_type") }; + BindEditUI m_http_url { _T ("http_url") }; + BindButtonUI m_http_change { _T ("http_change") }; + BindTabLayoutUI m_http_postdata_tab { _T ("http_postdata_tab") }; + BindListUI m_http_postdata_list { _T ("http_postdata_list") }; + BindRichEditUI m_http_postdata_src { _T ("http_postdata_src") }; + BindRichEditUI m_http_content { _T ("http_content") }; +}; + +#endif //__PAGE_HTTP_HPP__ diff --git a/NetToolbox/pages/page_IPScan.hpp b/NetToolbox/pages/page_IPScan.hpp new file mode 100644 index 0000000..bc89a5a --- /dev/null +++ b/NetToolbox/pages/page_IPScan.hpp @@ -0,0 +1,106 @@ +#ifndef __PAGE_IPSCAN_HPP__ +#define __PAGE_IPSCAN_HPP__ + +#include +#include +#include +#include + +#include "../tools/tool_NetInfo.hpp" +#include "../tools/tool_Utils.hpp" +#include "../tools/tool_Encoding.hpp" + +#include "page_base.hpp" + + + +class page_IPScan: public page_base { +public: + page_IPScan (NetToolboxWnd *parent): page_base (parent) { + auto[ip1, ip2] = tool_NetInfo::get_ip_segment (); + m_ipscan_ip1->SetText (ip1); + m_ipscan_ip2->SetText (ip2); + } + virtual ~page_IPScan () = default; + + bool OnClick (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("ipscan_start")) { + string_t sip1 = m_ipscan_ip1->GetText (), sip2 = m_ipscan_ip2->GetText (); + uint32_t ip1 = tool_Utils::from_ipv4_my (sip1), ip2 = tool_Utils::from_ipv4_my (sip2); + if (ip1 > ip2) { + ::MessageBox (m_parent->GetHWND (), _T ("ʼIPֹܴIP"), _T (""), MB_ICONHAND); + } else if (ip2 - ip1 > 65536) { + ::MessageBox (m_parent->GetHWND (), _T ("˹ֳ֧ܲΧIPɨ裡"), _T (""), MB_ICONHAND); + } else { + m_ipscan_start->SetEnabled (false); + m_ipscan_list->RemoveAll (); + std::thread (&page_IPScan::scan_thread, this, ip1, ip2).detach (); + } + return true; + } + return false; + } + +private: + void scan_thread (uint32_t ip1, uint32_t ip2) { + static uint8_t send_data[50] = { + 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21, 0x00, 0x01 + }; + static uint8_t recv_data[512]; + boost::asio::const_buffer send_buf { send_data, sizeof (send_data) }; + boost::asio::io_service service; + boost::asio::ip::udp::socket sock (service); + sock.open (boost::asio::ip::udp::endpoint (boost::asio::ip::udp::v4 (), 25252).protocol ()); + static boost::asio::ip::udp::endpoint sender_ep; + // + std::function on_receive; + on_receive = [this, &sock, &on_receive] (boost::system::error_code ec, std::size_t length) { + if (!ec && length > 0) { + m_parent->async_invoke ([this] () -> LRESULT { + CListContainerElementUI *item = new CListContainerElementUI (); + item->SetFixedHeight (20); + // + CControlUI *ctrl = new CControlUI (); + ctrl->SetBkImage (_T ("file='list_ico_1.png' source='0,0,16,16' dest='0,0,16,16'")); + //ctrl->SetText (is_ipv4 ? _T ("TCP") : _T ("UDP")); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("7,2,7,2")); + item->Add (ctrl); + m_ipscan_list->Add (item); + return 0; + }); + } + sock.async_receive_from (boost::asio::buffer (recv_data, sizeof (recv_data)), sender_ep, [on_receive] (boost::system::error_code ec, std::size_t length) { on_receive (ec, length); }); + }; + //on_receive (boost::system::error_code (), 0); + // + while (ip1 <= ip2) { + std::string sip = tool_Encoding::get_gb18030 (tool_Utils::format_ipv4_my (ip1)); + boost::asio::ip::udp::endpoint ep { boost::asio::ip::address::from_string (sip), 137 }; + sock.send_to (send_buf, ep); + ++ip1; + service.run_one (); + } + std::chrono::system_clock::time_point tp = std::chrono::system_clock::now (); + while (std::chrono::system_clock::now () - tp < std::chrono::seconds (1)) { + service.run_one (); + std::this_thread::sleep_for (std::chrono::milliseconds (1)); + } + m_parent->invoke ([this] () -> LRESULT { + m_ipscan_start->SetEnabled (true); + return 0; + }); + } + + BindEditUI m_ipscan_ip1 { _T ("ipscan_ip1") }; + BindEditUI m_ipscan_ip2 { _T ("ipscan_ip2") }; + BindButtonUI m_ipscan_start { _T ("ipscan_start") }; + BindListUI m_ipscan_list { _T ("ipscan_list") }; +}; + +#endif //__PAGE_IPSCAN_HPP__ diff --git a/NetToolbox/pages/page_LocalConn.hpp b/NetToolbox/pages/page_LocalConn.hpp new file mode 100644 index 0000000..0275ecc --- /dev/null +++ b/NetToolbox/pages/page_LocalConn.hpp @@ -0,0 +1,284 @@ +#ifndef __PAGE_LOCAL_CONNECTION_HPP__ +#define __PAGE_LOCAL_CONNECTION_HPP__ + +#include + +#include "page_base.hpp" +#include "../tools/tool_NetInfo.hpp" +#include "../tools/tool_Encoding.hpp" +#include "../tools/tool_Process.hpp" +#include "../tools/tool_Path.hpp" +#include "../tools/tool_Utils.hpp" +#include "../tools/tool_Priv.hpp" + + +class page_LocalConn: public page_base { + using conn_item_t = std::tuple; +public: + page_LocalConn (NetToolboxWnd *parent): page_base (parent) {} + virtual ~page_LocalConn () { + CMenuWnd::DestroyMenu (); + if (m_menu) { + delete m_menu; + m_menu = nullptr; + } + } + + bool OnClick (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("localnet_tcplisten") || name == _T ("localnet_tcpconn") || name == _T ("localnet_udp")) { + m_show_type = msg.pSender->GetText (); + _update_data (false); + return true; + } + return false; + } + + bool OnRButtonDown (POINT &pt) override { + CControlUI *ctrl = m_parent->find_control (pt); + while (ctrl->GetClass () != _T ("ListContainerElementUI")) { + if ((ctrl = ctrl->GetParent ()) == nullptr) + break; + } + if (ctrl) { + //CMenuUI DUI_MSGTYPE_MENU + m_sel_pid = _ttoi (ctrl->GetCustomAttribute (_T ("pid")).data ()); + m_sel_pname = ctrl->GetCustomAttribute (_T ("pname")); + CMenuWnd::DestroyMenu (); + ::ClientToScreen (m_parent->GetHWND (), &pt); + m_parent->async_invoke ([this, pt] () -> LRESULT { + if (m_menu) { + delete m_menu; + m_menu = nullptr; + } + CMenuWnd::GetGlobalContextMenuObserver ().SetMenuCheckInfo (&m_MenuInfos); + m_menu = new CMenuWnd (); + m_menu->Init (nullptr, _T ("menu_localnet.xml"), pt, m_parent->get_pm ()); + return 0; + }); + return true; + } + return false; + } + + void ui_update_data () override { _update_data (true); } + + bool OnHeaderClick (TNotifyUI &msg) { + if (msg.pSender->GetParent ()->GetParent ()->GetName () == _T ("localnet_connection")) { + CDuiString name = msg.pSender->GetText (); + if (name == m_sort_str) { + m_is_increment = !m_is_increment; + } else { + m_sort_str = name; + m_is_increment = true; + } + _update_data (false); + return true; + } + return false; + } + + bool OnMenuClick (MenuCmd *mc) override { + if (mc->szName == _T ("menu_localnet_kill")) { + string_t tip_info = tool_StringT::format (_T ("ȷҪ %s (pid:%d) "), m_sel_pname.c_str (), m_sel_pid); + if (IDOK == ::MessageBox (m_parent->GetHWND (), tip_info.c_str (), _T ("ʾ"), MB_ICONQUESTION | MB_OKCANCEL)) { + if (tool_Process::kill ((DWORD) m_sel_pid)) { + ui_update_data (); + } else { + // ޷ + if (tool_Priv::is_admin ()) { + // AdminȨޣʾʧ + ::MessageBox (m_parent->GetHWND (), _T ("޷"), _T ("ʾ"), MB_ICONWARNING); + } else if (tool_Priv::adjust_restart (m_parent->m_sel1, m_parent->m_sel2)) { + // AdminȨޣȨ˳ + m_parent->Close (); + } + } + } + return true; + } else if (mc->szName == _T ("menu_localnet_show")) { + auto[err, path] = tool_Process::get_path ((DWORD) m_sel_pid); + if (path.empty ()) { + // ·޷ʾ + if (tool_Priv::is_admin ()) { + // AdminȨޣʾʧ + string_t err_str = tool_Utils::get_error_info (err); + ::MessageBox (m_parent->GetHWND (), err_str.c_str (), _T ("ʾ"), MB_ICONWARNING); + } else if (tool_Priv::adjust_restart (m_parent->m_sel1, m_parent->m_sel2)) { + // AdminȨޣȨ˳ + m_parent->Close (); + } + return true; + } + tool_Path::show_path (path); + return true; + } + return false; + } + +protected: + void _update_data (bool bUpdateData) { + static size_t i, x = 0; + if (bUpdateData && x++ % 5 > 0) + return; + bool bIsEqual = true; + static std::vector vconns, type_vconns; + std::vector _vconns, _type_vconns; + if (bUpdateData) { + _vconns = tool_NetInfo::get_connections (); + bIsEqual = (vconns.size () == _vconns.size ()); + if (bIsEqual) { + for (i = 0; i < vconns.size (); ++i) { + if (vconns[i] != _vconns[i]) { + bIsEqual = false; + break; + } + } + } + } else { + type_vconns.clear (); + } + + if (!bUpdateData || !bIsEqual) { + if (bUpdateData) { + vconns.swap (_vconns); + std::sort (vconns.begin (), vconns.end (), [this] (const conn_item_t &_v1, const conn_item_t &_v2) -> bool { + if (m_sort_str == _T ("صַ")) { + string_t _val1 = std::get<1> (_v1); + string_t _val2 = std::get<1> (_v2); + if (_val1 == _val2) + return false; + return (_val1 < _val2) == m_is_increment; + } else if (m_sort_str == _T ("ض˿")) { + uint16_t _val1 = std::get<2> (_v1); + uint16_t _val2 = std::get<2> (_v2); + if (_val1 == _val2) + return false; + return (_val1 < _val2) == m_is_increment; + } else if (m_sort_str == _T ("Զ̵ַ")) { + string_t _val1 = std::get<3> (_v1); + string_t _val2 = std::get<3> (_v2); + if (_val1 == _val2) + return false; + return (_val1 < _val2) == m_is_increment; + } else if (m_sort_str == _T ("Զ̶˿")) { + uint16_t _val1 = std::get<4> (_v1); + uint16_t _val2 = std::get<4> (_v2); + if (_val1 == _val2) + return false; + return (_val1 < _val2) == m_is_increment; + } else if (m_sort_str == _T ("״̬")) { + string_t _val1 = std::get<5> (_v1); + string_t _val2 = std::get<5> (_v2); + if (_val1 == _val2) + return false; + return (_val1 < _val2) == m_is_increment; + } else if (m_sort_str == _T ("PID")) { + DWORD _val1 = std::get<6> (_v1); + DWORD _val2 = std::get<6> (_v2); + if (_val1 == _val2) + return false; + return (_val1 < _val2) == m_is_increment; + } else if (m_sort_str == _T ("")) { + string_t _val1 = std::get<7> (_v1); + string_t _val2 = std::get<7> (_v2); + if (_val1 == _val2) + return false; + return (_val1 < _val2) == m_is_increment; + } + return _v1 < _v2; + }); + } + for (i = 0; i < vconns.size (); ++i) { + auto[is_ipv4/**/, local_addr, local_port, remote_addr, remote_port, conn_state/**/, pid, pname] = vconns[i]; + if (conn_state == m_show_type) + _type_vconns.push_back (vconns[i]); + } + bIsEqual = (type_vconns.size () == _type_vconns.size ()); + if (bIsEqual) { + for (i = 0; i < _type_vconns.size (); ++i) { + if (_type_vconns[i] != type_vconns[i]) { + bIsEqual = false; + break; + } + } + } + if (bUpdateData && bIsEqual) + return; + type_vconns.swap (_type_vconns); + + m_localnet_connection->RemoveAll (); + for (i = 0; i < type_vconns.size (); ++i) { + //Ƿipv4ʾصַض˿ڣԶ̵ַԶ̶˿ڣǰ״̬PIDƣ·ʾ + auto[is_ipv4/**/, local_addr, local_port, remote_addr, remote_port, conn_state/**/, pid, pname] = type_vconns[i]; + CListContainerElementUI *item = new CListContainerElementUI (); + item->AddCustomAttribute (_T ("pid"), tool_StringT::format (_T ("%d"), pid)); + item->AddCustomAttribute (_T ("pname"), pname); + item->SetFixedHeight (20); + // + CControlUI *ctrl = new CControlUI (); + ctrl->SetBkImage (_T ("file='list_ico_1.png' source='0,0,16,16' dest='0,0,16,16'")); + //ctrl->SetText (is_ipv4 ? _T ("TCP") : _T ("UDP")); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("7,2,7,2")); + item->Add (ctrl); + // + ctrl = new CTextUI (); + ctrl->SetText (local_addr.c_str ()); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("10,4,0,4")); + item->Add (ctrl); + // + ctrl = new CTextUI (); + ctrl->SetText (tool_StringT::format (_T ("%d"), local_port).c_str ()); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("0,4,0,4")); + item->Add (ctrl); + // + ctrl = new CTextUI (); + ctrl->SetText (remote_addr.c_str ()); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("0,4,0,4")); + item->Add (ctrl); + // + ctrl = new CTextUI (); + ctrl->SetText (remote_addr == _T ("*") ? _T ("*") : tool_StringT::format (_T ("%d"), remote_port).c_str ()); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("0,4,0,4")); + item->Add (ctrl); + // + //ctrl = new CTextUI (); + //ctrl->SetText (conn_state.c_str ()); + //ctrl->SetAttribute (_T ("align"), _T ("center")); + //ctrl->SetAttribute (_T ("padding"), _T ("0,4,0,4")); + //item->Add (ctrl); + // + ctrl = new CTextUI (); + ctrl->SetText (tool_StringT::format (_T ("%d"), pid).c_str ()); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("0,4,0,4")); + item->Add (ctrl); + // + ctrl = new CTextUI (); + ctrl->SetText (pname.c_str ()); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("0,4,0,4")); + item->Add (ctrl); + m_localnet_connection->Add (item); + } + } + } + +protected: + BindListUI m_localnet_connection { _T ("localnet_connection") }; + CDuiString m_sort_str = _T ("ض˿"); + string_t m_show_type = _T ("TCPڼ"); + bool m_is_increment = true; + + CStdStringPtrMap m_MenuInfos; + CMenuWnd *m_menu = nullptr; + size_t m_sel_pid = 0; + string_t m_sel_pname = _T (""); +}; + +#endif //__PAGE_LOCAL_CONNECTION_HPP__ diff --git a/NetToolbox/pages/page_LocalNet.hpp b/NetToolbox/pages/page_LocalNet.hpp new file mode 100644 index 0000000..cb59356 --- /dev/null +++ b/NetToolbox/pages/page_LocalNet.hpp @@ -0,0 +1,76 @@ +#ifndef __PAGE_LOCAL_NET_HPP__ +#define __PAGE_LOCAL_NET_HPP__ + +#include "page_base.hpp" +#include "../tools/tool_NetInfo.hpp" +#include "../tools/tool_Encoding.hpp" + + + +class page_LocalNet: public page_base { +public: + page_LocalNet (NetToolboxWnd *parent): page_base (parent) { ui_update_data (); } + + virtual ~page_LocalNet () = default; + + void ui_update_data () override { + static size_t x = 0; + if (x++ % 5 > 0) + return; + static std::vector> vlocal_net; + auto _vlocal_net = tool_NetInfo::get_local_net (); + bool bIsEqual = (vlocal_net.size () == _vlocal_net.size ()); + if (bIsEqual) { + for (size_t i = 0; i < vlocal_net.size (); ++i) { + if (vlocal_net[i] != _vlocal_net[i]) { + bIsEqual = false; + break; + } + } + } + if (!bIsEqual) { + m_localnet_list->RemoveAll (); + vlocal_net = _vlocal_net; + for (size_t i = 0; i < vlocal_net.size (); ++i) { + auto[desc, local, gateway, is_dhcp] = vlocal_net[i]; + CListContainerElementUI *item = new CListContainerElementUI (); + item->SetFixedHeight (20); + // + CControlUI *ctrl = new CContainerUI (); + ctrl->SetBkImage (_T ("file='list_ico_0.png' source='0,0,16,16' dest='0,0,16,16'")); + ctrl->SetAttribute (_T ("padding"), _T ("7,2,7,2")); + item->Add (ctrl); + // + ctrl = new CTextUI (); + ctrl->SetText (tool_Encoding::gb18030_to_utf16 (desc).c_str ()); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("10,4,0,4")); + item->Add (ctrl); + // + ctrl = new CTextUI (); + ctrl->SetText (tool_Encoding::gb18030_to_utf16 (local).c_str ()); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("0,4,0,4")); + item->Add (ctrl); + // + ctrl = new CTextUI (); + ctrl->SetText (tool_Encoding::gb18030_to_utf16 (gateway).c_str ()); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("0,4,0,4")); + item->Add (ctrl); + // + ctrl = new CTextUI (); + ctrl->SetText (is_dhcp ? _T ("") : _T ("ѽ")); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("0,4,0,4")); + item->Add (ctrl); + m_localnet_list->Add (item); + } + } + } + +protected: + BindListUI m_localnet_list { _T ("localnet_list") }; +}; + +#endif //__PAGE_LOCAL_NET_HPP__ diff --git a/NetToolbox/pages/page_Qps.hpp b/NetToolbox/pages/page_Qps.hpp new file mode 100644 index 0000000..a318b1c --- /dev/null +++ b/NetToolbox/pages/page_Qps.hpp @@ -0,0 +1,305 @@ +#ifndef __PAGE_QPS_HPP__ +#define __PAGE_QPS_HPP__ + +#include +#include +#include +#include +#include +#include + +#include "page_base.hpp" +#include "../tools/tool_String.hpp" +#include "../tools/tool_Encoding.hpp" +#include "../tools/tool_WebRequest.hpp" + + + +class page_Qps: public page_base { +public: + page_Qps (NetToolboxWnd *parent): page_base (parent) { + _convert_data (false); + } + virtual ~page_Qps () = default; + + void ui_update_data () override { + if (m_is_run) { + size_t succ_count = 0, fail_count = 0; + for (size_t i = 0; i < m_succ_counts.size (); ++i) { + succ_count += m_succ_counts[i]; + fail_count += m_fail_counts[i]; + } + size_t inc_succ_count = succ_count - m_last_succ_count, inc_fail_count = fail_count - m_last_fail_count; + m_desp += tool_StringT::format (_T (" %-6dɹ %-6dʧ %d\n"), inc_succ_count + inc_fail_count, inc_succ_count, inc_fail_count); + m_qps_content->SetText (m_desp.c_str ()); + m_last_succ_count += inc_succ_count; + m_last_fail_count += inc_fail_count; + } + } + + bool OnClick (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("qps_begin") || name == _T ("qps_stop")) { + if ((name == _T ("qps_begin")) == m_is_run) + return true; + if (!m_is_run) { + // start + size_t threnad_num = _ttoi (m_qps_thread_num->GetText ().c_str ()); + if (threnad_num < 1) { + m_parent->show_status (NetToolboxWnd::StatusIcon::Error, _T ("߳Ϊ1")); + return true; + } else if (threnad_num > 100) { + m_parent->show_status (NetToolboxWnd::StatusIcon::Error, _T ("߳Ϊ100")); + return true; + } + m_desp = _T (""); + m_succ_counts.resize (threnad_num); + m_fail_counts.resize (threnad_num); + m_last_succ_count = m_last_fail_count = 0; + std::string type = tool_Encoding::get_gb18030 (m_qps_type->GetText ()); + std::string url = tool_Encoding::get_gb18030 (m_qps_url->GetText ()); + if (url.empty ()) + url = tool_Encoding::get_utf8 (m_qps_url->GetTipValue ()); + if (m_qps_postdata_tab->GetCurSel () == 0) + _convert_data (true); + std::string post_data = (type == "POST" ? tool_Encoding::get_utf8 (m_qps_postdata_src->GetText ()) : ""); + std::tuple params = { type, url, post_data }; + m_is_run = !m_is_run; + for (size_t i = 0; i < threnad_num; ++i) { + m_succ_counts[i] = m_fail_counts[i] = 0; + std::thread (std::bind (&page_Qps::thread_func, this, i, params)).detach (); + } + std::this_thread::sleep_for (std::chrono::milliseconds (100)); + } else { + m_is_run = !m_is_run; + } + m_qps_begin->SetVisible (!m_is_run); + m_qps_stop->SetVisible (m_is_run); + return true; + } else if (name == _T ("qps_change")) { + bool is_show_list = (m_qps_postdata_tab->GetCurSel () == 0); + _convert_data (is_show_list); + m_qps_postdata_tab->SelectItem (is_show_list ? 1 : 0); + return true; + } else if (name == _T ("qps_postdata_ctrl_up")) { + CControlUI *container = msg.pSender->GetParent ()->GetParent (); + int n = m_qps_postdata_list->GetItemIndex (container); + if (n == 0) + return true; + string_t name1 { container->GetName () }; + string_t name2 { m_qps_postdata_list->GetItemAt (n - 1)->GetName () }; + _swap_item (name1, name2); + return true; + } else if (name == _T ("qps_postdata_ctrl_down")) { + CControlUI *container = msg.pSender->GetParent ()->GetParent (); + int n = m_qps_postdata_list->GetItemIndex (container); + if (n >= m_qps_postdata_list->GetCount () - 2) + return true; + string_t name1 { container->GetName () }; + string_t name2 { m_qps_postdata_list->GetItemAt (n + 1)->GetName () }; + _swap_item (name1, name2); + return true; + } else if (name == _T ("qps_postdata_ctrl_new")) { + CControlUI *container = msg.pSender->GetParent ()->GetParent (); + m_qps_postdata_list->Remove (container); + _add_item (_T (""), _T (""), _T ("ɾ")); + _add_item (_T (""), _T (""), _T ("½")); + return true; + } else if (name == _T ("qps_postdata_ctrl_del")) { + CControlUI *container = msg.pSender->GetParent ()->GetParent (); + int n = m_qps_postdata_list->GetItemIndex (container); + m_qps_postdata_list->Remove (container); + for (; n < m_qps_postdata_list->GetCount (); ++n) { + container = m_qps_postdata_list->GetItemAt (n); + string_t _name { container->GetName () }; + string_t color = (n % 2) ? _T ("#FFDDDDDD") : _T ("#FFFFFFFF"); + string_t _name1 = _name + _T ("_key"); + BindEditUI edit_key { _name1 }; + edit_key->SetAttribute (_T ("bkcolor"), color); + edit_key->SetAttribute (_T ("nativebkcolor"), color); + _name1 = _name + _T ("_value"); + BindEditUI edit_value { _name1 }; + edit_value->SetAttribute (_T ("bkcolor"), color); + edit_value->SetAttribute (_T ("nativebkcolor"), color); + } + return true; + } + return false; + } + + bool OnItemSelect (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("qps_type")) { + bool is_post = m_qps_type->GetText () == _T ("POST"); + m_qps_change->SetEnabled (is_post); + m_qps_postdata_src->SetEnabled (is_post); + m_qps_postdata_src->SetTextColor (is_post ? 0xFF000000 : 0xFF777777); + if (!is_post) { + if (m_qps_postdata_tab->GetCurSel () == 0) { + _convert_data (true); + m_qps_postdata_tab->SelectItem (1); + } + } + return true; + } + return false; + } + +protected: + void thread_func (size_t n, std::tuple params) { + auto[type, url, post_data] = params; + while (m_is_run) { + try { + if (type == "POST") { + tool_WebRequest::post (url, post_data); + } else { + tool_WebRequest::get (url); + } + m_succ_counts[n]++; + } catch (...) { + m_fail_counts[n]++; + } + } + } + + // һ + void _add_item (string_view_t key, string_view_t value, string_view_t btntext) { + static size_t n_sign = 0; + CListContainerElementUI *item = new CListContainerElementUI (); + item->SetFixedHeight (20); + string_t color = (m_qps_postdata_list->GetCount () % 2) ? _T ("#FFEEEEEE") : _T ("#FFFFFFFF"); + bool is_new = (btntext == _T ("½")); + if (is_new) + color = _T ("#FFDDDDDD"); + item->SetAttribute (_T ("name"), tool_StringT::format (_T ("qps_postdata_item_%d"), ++n_sign)); + // + CContainerUI *ctnr = new CContainerUI (); + CControlUI *ctrl = new CEditUI (); + ctrl->SetManager (m_parent->get_pm (), item); + ctrl->SetText (key); + ctrl->SetAttribute (_T ("name"), tool_StringT::format (_T ("qps_postdata_item_%d_key"), n_sign)); + ctrl->SetAttribute (_T ("bkcolor"), color); + ctrl->SetAttribute (_T ("nativebkcolor"), color); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("10,4,0,4")); + if (is_new) + ctrl->SetAttribute (_T ("enabled"), _T ("false")); + ctnr->Add (ctrl); + item->Add (ctnr); + // + ctnr = new CContainerUI (); + ctrl = new CEditUI (); + ctrl->SetManager (m_parent->get_pm (), item); + ctrl->SetText (value); + ctrl->SetAttribute (_T ("name"), tool_StringT::format (_T ("qps_postdata_item_%d_value"), n_sign)); + ctrl->SetAttribute (_T ("bkcolor"), color); + ctrl->SetAttribute (_T ("nativebkcolor"), color); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("0,4,0,4")); + if (is_new) + ctrl->SetAttribute (_T ("enabled"), _T ("false")); + ctnr->Add (ctrl); + item->Add (ctnr); + // + ctnr = new CHorizontalLayoutUI (); + ctrl = new CButtonUI (); + ctrl->SetManager (m_parent->get_pm (), ctnr); + ctrl->SetText (_T ("")); + ctrl->SetAttribute (_T ("style"), _T ("x_button")); + ctrl->SetAttribute (_T ("name"), _T ("qps_postdata_ctrl_up")); + ctrl->SetAttribute (_T ("width"), _T ("20")); + if (is_new) + ctrl->SetAttribute (_T ("visible"), _T ("false")); + ctnr->Add (ctrl); + ctrl = new CButtonUI (); + ctrl->SetManager (m_parent->get_pm (), ctnr); + ctrl->SetText (_T ("")); + ctrl->SetAttribute (_T ("style"), _T ("x_button")); + ctrl->SetAttribute (_T ("name"), _T ("qps_postdata_ctrl_down")); + ctrl->SetAttribute (_T ("width"), _T ("20")); + if (is_new) + ctrl->SetAttribute (_T ("visible"), _T ("false")); + ctnr->Add (ctrl); + ctrl = new CButtonUI (); + ctrl->SetManager (m_parent->get_pm (), ctnr); + ctrl->SetText (btntext); + ctrl->SetAttribute (_T ("style"), _T ("x_button")); + ctrl->SetAttribute (_T ("name"), (is_new ? _T ("qps_postdata_ctrl_new") : _T ("qps_postdata_ctrl_del"))); + ctrl->SetAttribute (_T ("width"), (is_new ? _T ("80") : _T ("40"))); + ctnr->Add (ctrl); + item->Add (ctnr); + m_qps_postdata_list->Add (item); + } + + // + void _swap_item (string_t name1, string_t name2) { + string_t name_key1 = name1 + _T ("_key"); + BindEditUI edit_key1 { name_key1 }; + string_t name_key2 = name2 + _T ("_key"); + BindEditUI edit_key2 { name_key2 }; + string_t name_tmp = edit_key1->GetText (); + edit_key1->SetText (edit_key2->GetText ()); + edit_key2->SetText (name_tmp); + // + string_t name_value1 = name1 + _T ("_value"); + BindEditUI edit_value1 { name_value1 }; + string_t name_value2 = name2 + _T ("_value"); + BindEditUI edit_value2 { name_value2 }; + name_tmp = edit_value1->GetText (); + edit_value1->SetText (edit_value2->GetText ()); + edit_value2->SetText (name_tmp); + } + + // ԭʼݻת + void _convert_data (bool list2src) { + if (list2src) { + string_t post_data = _T (""); + for (int i = 0; i < m_qps_postdata_list->GetCount () - 1; ++i) { + CControlUI *ctrl = m_qps_postdata_list->GetItemAt (i); + string_t name { ctrl->GetName () }; + string_t name_key = name + _T ("_key"); + BindEditUI edit_key { name_key }; + string_t name_value = name + _T ("_value"); + BindEditUI edit_value { name_value }; + if (!post_data.empty ()) + post_data += _T ("&"); + post_data += tool_Encoding::get_T_from_utf8 (tool_Encoding::percent_str_encode (tool_Encoding::get_utf8 (edit_key->GetText ()))); + post_data += _T ("="); + post_data += tool_Encoding::get_T_from_utf8 (tool_Encoding::percent_str_encode (tool_Encoding::get_utf8 (edit_value->GetText ()))); + } + m_qps_postdata_src->SetText (post_data); + } else { + string_t post_data = m_qps_postdata_src->GetText (); + std::vector vitems = tool_StringT::split (post_data, _T ('&'), _T ("&"), true); + m_qps_postdata_list->RemoveAll (); + for (size_t i = 0; i < vitems.size (); ++i) { + std::vector vkey_val = tool_StringT::split (vitems[i], _T ('='), _T (""), false); + while (vkey_val.size () < 2) + vkey_val.push_back (_T ("")); + vkey_val[0] = tool_Encoding::get_T_from_utf8 (tool_Encoding::percent_str_decode (tool_Encoding::get_utf8 (vkey_val[0]))); + vkey_val[1] = tool_Encoding::get_T_from_utf8 (tool_Encoding::percent_str_decode (tool_Encoding::get_utf8 (vkey_val[1]))); + _add_item (vkey_val[0], vkey_val[1], _T ("ɾ")); + } + _add_item (_T (""), _T (""), _T ("½")); + } + } + + BindComboUI m_qps_type { _T ("qps_type") }; + BindEditUI m_qps_thread_num { _T ("qps_thread_num") }; + BindEditUI m_qps_url { _T ("qps_url") }; + BindButtonUI m_qps_begin { _T ("qps_begin") }; + BindButtonUI m_qps_stop { _T ("qps_stop") }; + BindButtonUI m_qps_change { _T ("qps_change") }; + BindTabLayoutUI m_qps_postdata_tab { _T ("qps_postdata_tab") }; + BindListUI m_qps_postdata_list { _T ("qps_postdata_list") }; + BindRichEditUI m_qps_postdata_src { _T ("qps_postdata_src") }; + BindRichEditUI m_qps_content { _T ("qps_content") }; + volatile bool m_is_run = false; + string_t m_desp = _T (""); + std::vector m_succ_counts; + std::vector m_fail_counts; + size_t m_last_succ_count = 0; + size_t m_last_fail_count = 0; +}; + +#endif //__PAGE_QPS_HPP__ diff --git a/NetToolbox/pages/page_Record.hpp b/NetToolbox/pages/page_Record.hpp new file mode 100644 index 0000000..6333cc7 --- /dev/null +++ b/NetToolbox/pages/page_Record.hpp @@ -0,0 +1,64 @@ +#ifndef __page_Record_HPP__ +#define __page_Record_HPP__ + +#include +#include + +#include +#include + +//#include + +//#include "../tools/tool_MakeAvi.hpp" +//#include "../tools/tool_VP9.hpp" +#include "../tools/tool_Gdip.hpp" + +#include "page_base.hpp" + + + +class page_Record: public page_base { +public: + page_Record (NetToolboxWnd *parent): page_base (parent) { + string_t path = tool_Path::get_exe_path (); + m_record_path->SetText (tool_StringT::format (_T ("%c:\\record.gif"), path[0])); + } + virtual ~page_Record () = default; + + bool OnClick (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("record_begin")) { + //tool_VP9 (_T ("D:/a.ivf")); + // + //tool_MakeAvi avi (_T ("D:/a.avi")); + //HBITMAP hbmp = tool_Gdip::capture_screen_gdi (); + //bool bRet = avi.init (hbmp); + //::DeleteObject (hbmp); + //if (bRet) { + // auto tp = std::chrono::system_clock::now (); + // for (size_t i = 0; i < 100; ++i) { + // hbmp = tool_Gdip::capture_screen_gdi (); + // bRet = avi.add_frame (hbmp); + // ::DeleteObject (hbmp); + // if (!bRet) { + // DWORD d = ::GetLastError (); + // d = d; + // break; + // } + // tp += std::chrono::milliseconds (1000 / 30); + // std::this_thread::sleep_until (tp); + // } + //} else { + // DWORD d = ::GetLastError (); + // d = d; + //} + return true; + } + return false; + } + +protected: + BindEditUI m_record_path { _T ("record_path") }; +}; + +#endif //__page_Record_HPP__ diff --git a/NetToolbox/pages/page_Regex.hpp b/NetToolbox/pages/page_Regex.hpp new file mode 100644 index 0000000..ddc2c89 --- /dev/null +++ b/NetToolbox/pages/page_Regex.hpp @@ -0,0 +1,69 @@ +#ifndef __PAGE_REGEX_HPP__ +#define __PAGE_REGEX_HPP__ + +#include +#include +#include +#include + +#include "page_base.hpp" +#include "../tools/tool_String.hpp" +#include "../tools/tool_Encoding.hpp" +#include "../tools/tool_Formatting.hpp" + + + +class page_Regex: public page_base { +public: + page_Regex (NetToolboxWnd *parent): page_base (parent) {} + virtual ~page_Regex () = default; + + bool OnClick (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("regex_match")) { + std::string _statement = tool_Encoding::get_gb18030 (m_regex_statement->GetText ()); + tool_StringA::replace (_statement, "\r\n", "\n"); + tool_StringA::replace (_statement, "\r", "\n"); + std::string _content = tool_Encoding::get_gb18030 (m_regex_content->GetText ()); + tool_StringA::replace (_content, "\r\n", "\n"); + tool_StringA::replace (_content, "\r", "\n"); + auto[_err, _vresult] = tool_StringT::match_regex (_statement, _content); + if (!_err.empty ()) + m_parent->show_status (NetToolboxWnd::StatusIcon::Warning, tool_Encoding::get_T (_err)); + std::string _result = ""; + for (auto &_item : _vresult) { + _result += _item; + _result += '\n'; + } + string_t t_result = tool_Encoding::get_T (_result); + m_regex_result->SetText (t_result.c_str ()); + return true; + } + return false; + } + + bool OnItemSelect (TNotifyUI& msg) override { + static std::function _query_regex = [] (string_t desp) { + return tool_Encoding::get_T (tool_Formatting::query_regex (tool_Encoding::get_gb18030 (desp))); + }; + CDuiString name = msg.pSender->GetName (); + if (name == _T ("regex_combo_net")) { + m_regex_statement->SetText (_query_regex (m_regex_combo_net->GetText ())); + m_regex_combo_net->SetText (_T ("")); + return true; + } else if (name == _T ("regex_combo_format")) { + m_regex_statement->SetText (_query_regex (m_regex_combo_format->GetText ())); + m_regex_combo_format->SetText (_T ("ʽ")); + } + return false; + } + +protected: + BindComboUI m_regex_combo_net { _T ("regex_combo_net") }; + BindComboUI m_regex_combo_format { _T ("regex_combo_format") }; + BindRichEditUI m_regex_statement { _T ("regex_statement") }; + BindRichEditUI m_regex_content { _T ("regex_content") }; + BindRichEditUI m_regex_result { _T ("regex_result") }; +}; + +#endif //__PAGE_REGEX_HPP__ diff --git a/NetToolbox/pages/page_Rsa.hpp b/NetToolbox/pages/page_Rsa.hpp new file mode 100644 index 0000000..2182d4d --- /dev/null +++ b/NetToolbox/pages/page_Rsa.hpp @@ -0,0 +1,61 @@ +#ifndef __PAGE_RSA_HPP__ +#define __PAGE_RSA_HPP__ + +#include "page_base.hpp" +#include "../tools/tool_SysInfo.hpp" +#include "../tools/tool_Path.hpp" +#include "../tools/tool_String.hpp" +#include "../tools/tool_Rsa.hpp" +#include "../tools/tool_Encoding.hpp" + + + +class page_Rsa: public page_base { +public: + page_Rsa (NetToolboxWnd *parent): page_base (parent) { + string_t path = tool_Path::get_exe_path (); + m_rsa_evp_pubkey->SetText (tool_StringT::format (_T ("%c:\\evp_pubkey.key"), path[0])); + m_rsa_rsa_pubkey->SetText (tool_StringT::format (_T ("%c:\\rsa_pubkey.key"), path[0])); + m_rsa_rsa_prvkey->SetText (tool_StringT::format (_T ("%c:\\rsa_prvkey.key"), path[0])); + } + virtual ~page_Rsa () = default; + + bool OnClick (TNotifyUI &msg) override { + if (msg.pSender->GetName () == _T ("rsa_generate")) { + bool succ = tool_Rsa::generate ( + _ttoi (m_rsa_gensize->GetText ().c_str ()), + (m_rsa_selevp_pubkey->IsSelected () ? tool_Encoding::get_gb18030 (m_rsa_evp_pubkey->GetText ()) : ""), + (m_rsa_selrsa_pubkey->IsSelected () ? tool_Encoding::get_gb18030 (m_rsa_rsa_pubkey->GetText ()) : ""), + tool_Encoding::get_gb18030 (m_rsa_rsa_prvkey->GetText ())); + if (succ) + m_parent->show_status (NetToolboxWnd::StatusIcon::Ok, _T ("RSA˽Կɹ")); + else + m_parent->show_status (NetToolboxWnd::StatusIcon::Error, _T ("RSA˽Կʧܣ")); + return true; + } + return false; + } + + bool OnSelectChanged (TNotifyUI &msg) override { + if (msg.pSender->GetName () == _T ("rsa_selevp_pubkey")) { + m_rsa_evp_pubkey->SetEnabled (true); + m_rsa_rsa_pubkey->SetEnabled (false); + return true; + } else if (msg.pSender->GetName () == _T ("rsa_selrsa_pubkey")) { + m_rsa_evp_pubkey->SetEnabled (false); + m_rsa_rsa_pubkey->SetEnabled (true); + return true; + } + return false; + } + +protected: + BindComboUI m_rsa_gensize { _T ("rsa_gensize") }; + BindOptionUI m_rsa_selevp_pubkey { _T ("rsa_selevp_pubkey") }; + BindEditUI m_rsa_evp_pubkey { _T ("rsa_evp_pubkey") }; + BindOptionUI m_rsa_selrsa_pubkey { _T ("rsa_selrsa_pubkey") }; + BindEditUI m_rsa_rsa_pubkey { _T ("rsa_rsa_pubkey") }; + BindEditUI m_rsa_rsa_prvkey { _T ("rsa_rsa_prvkey") }; +}; + +#endif //__PAGE_RSA_HPP__ diff --git a/NetToolbox/pages/page_SerialPort.hpp b/NetToolbox/pages/page_SerialPort.hpp new file mode 100644 index 0000000..069d7ca --- /dev/null +++ b/NetToolbox/pages/page_SerialPort.hpp @@ -0,0 +1,290 @@ +#ifndef __PAGE_SERIAL_PORT_HPP__ +#define __PAGE_SERIAL_PORT_HPP__ + +#include +#include +#include +#include +#include +#include +#include + +#include "page_base.hpp" +#include "../tools/tool_String.hpp" +#include "../tools/tool_SerialPort.hpp" +#include "../tools/tool_Encoding.hpp" + + + +class page_SerialPort: public page_base { + enum SerialDataType { + SerialDataTypeInfo = DT_CENTER, + SerialDataTypeSend = DT_RIGHT, + SerialDataTypeRecv = DT_LEFT + }; + +public: + page_SerialPort (NetToolboxWnd *parent): page_base (parent) { + m_serial.set_on_event (std::bind (&page_SerialPort::on_receive, this, std::placeholders::_1), std::bind (&page_SerialPort::on_close, this)); + // + static std::vector vboudrate { _T ("110"), _T ("300"), _T ("600"), _T ("1200"), _T ("2400"), _T ("4800"), _T ("9600"), _T ("14400"), _T ("19200"), _T ("38400"), _T ("56000"), _T ("57600"), _T ("115200"), _T ("128000"), _T ("256000") }; + for (auto bd : vboudrate) + m_serial_boudrate->Add (new CListLabelElementUI (bd)); + m_serial_boudrate->SetText (_T ("9600")); + m_serial_boudrate->SetDropBoxSize ({ 0, (LONG) vboudrate.size () * 20 + 2 }); + // + static std::vector vbytesize { _T ("1"), _T ("2"), _T ("3"), _T ("4"), _T ("5"), _T ("6"), _T ("7"), _T ("8") }; + for (auto bs : vbytesize) + m_serial_bytesize->Add (new CListLabelElementUI (bs)); + m_serial_bytesize->SetText (_T ("8")); + m_serial_bytesize->SetDropBoxSize ({ 0, (LONG) vbytesize.size () * 20 + 2 }); + // + static std::vector vparity { _T ("none"), _T ("odd"), _T ("even"), _T ("mark"), _T ("space") }; + for (auto p : vparity) + m_serial_parity->Add (new CListLabelElementUI (p)); + m_serial_parity->SetText (_T ("none")); + m_serial_parity->SetDropBoxSize ({ 0, (LONG) vparity.size () * 20 + 2 }); + // + static std::vector vstopbits { _T ("1"), _T ("1.5"), _T ("2") }; + for (auto sb : vstopbits) + m_serial_stopbits->Add (new CListLabelElementUI (sb)); + m_serial_stopbits->SetText (_T ("1")); + m_serial_stopbits->SetDropBoxSize ({ 0, (LONG) vstopbits.size () * 20 + 2 }); + } + virtual ~page_SerialPort () = default; + + void ui_update_data () override { + static auto _is_equal = [] (std::vector& v1, std::vector& v2) { + if (v1.size () != v2.size ()) + return false; + for (size_t i = 0; i < v1.size (); ++i) { + if (v1[i] != v2[i]) + return false; + } + return true; + }; + static auto _index_arr = [] (std::vector& v, std::string str) { + for (size_t i = 0; i < v.size (); ++i) { + if (str == _serial_name (v[i])) + return i; + } + return std::string::npos; + }; + + static std::vector s_vSp; + auto _vSp = tool_SerialPort::get_list (); + if (_is_equal (s_vSp, _vSp)) + return; + if (m_serial.is_open () && !_index_arr (_vSp, m_serial.get_name ())) { + m_serial.close (); + } + s_vSp = _vSp; + m_serial_name->RemoveAll (); + for (auto name : s_vSp) { + m_serial_name->Add (new CListLabelElementUI (tool_Encoding::get_T (name).c_str (), 20)); + } + if (s_vSp.size () > 0) + m_serial_name->SelectItem (0); + m_serial_name->SetDropBoxSize ({ 0, (LONG) s_vSp.size () * 20 + 2 }); + } + + bool on_send () { + if (m_tmp_port_name == "") + return false; + CDuiString data = m_serial_senddata->GetText (); + std::string _data = tool_Encoding::get_gb18030 (data); + if (!append_data (SerialDataTypeSend, _data, !m_serial_hex->IsSelected ())) + return false; + if (m_serial_newline->IsSelected ()) + _data += "\r\n"; + m_serial.write (_data); + return true; + } + + void on_receive (std::string data) { + append_data (SerialDataTypeRecv, data); + } + + void on_close () { + enable_combos (true); + append_data (SerialDataTypeInfo, tool_StringA::format ("Ͽ%s", m_tmp_port_name.c_str ())); + m_tmp_port_name = ""; + } + + bool OnClick (TNotifyUI& msg) override { + try { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("serial_btnopen")) { + if (!m_serial.is_open () && m_tmp_port_name == "") { + m_tmp_port_name = tool_Encoding::get_gb18030 (m_serial_name->GetText ()); + std::string _parity = tool_Encoding::get_gb18030 (m_serial_parity->GetText ()); + std::string _stopbits = tool_Encoding::get_gb18030 (m_serial_stopbits->GetText ()); + if (m_tmp_port_name == "") { + m_parent->invoke ([this] () -> LRESULT { + m_parent->show_status (NetToolboxWnd::StatusIcon::Error, _T ("򿪴ʧܣδѡõĴ")); + append_data (SerialDataTypeInfo, "򿪴ʧܣδѡõĴ"); + return 0; + }); + return true; + } + uint32_t _baud_rate = _ttoi (m_serial_boudrate->GetText ().c_str ()); + uint8_t _byte_size = _ttoi (m_serial_bytesize->GetText ().c_str ()); + m_serial.open (_serial_name (m_tmp_port_name), _baud_rate, _byte_size, _parity, _stopbits); + if (m_serial.is_open ()) { + enable_combos (false); + append_data (SerialDataTypeInfo, tool_StringA::format ("Ѵ򿪴ڣ%s", m_tmp_port_name.c_str ())); + } + } + return true; + } else if (name == _T ("serial_btnclose")) { + if (m_serial.is_open ()) { + m_serial.close (); + enable_combos (true); + append_data (SerialDataTypeInfo, tool_StringA::format ("ѹرմڣ%s", m_tmp_port_name.c_str ())); + m_tmp_port_name = ""; + } + return true; + } else if (name == _T ("serial_btnsend")) { + if (on_send () && m_serial_repeat->IsSelected () && !m_repeat_timer) { + UINT nElapse = _ttoi (m_serial_repeat_mill->GetText ().c_str ()); + m_serial_repeat->SetTimer ((UINT) *m_serial_repeat, (nElapse > 0 ? nElapse : 1)); + m_repeat_timer = true; + } + return true; + } else if (name == _T ("serial_newline")) { + return true; + } + return false; + } catch (std::exception &e) { + m_parent->show_status (NetToolboxWnd::StatusIcon::Error, tool_Encoding::get_T (e.what ()).c_str ()); + return true; + } + } + + bool OnSelectChanged (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("serial_hex")) { + bool is_source = !m_serial_hex->IsSelected (); + for (size_t i = 0; i < m_data.size (); ++i) { + auto[type, source, hex] = m_data[i]; + if (type != SerialDataTypeInfo) { + BindTextUI ctrl { tool_StringT::format (_T ("serial_cnt_%d"), i) }; + ctrl->SetText ((is_source ? source : hex).c_str ()); + } + } + return true; + } else if (name == _T ("serial_repeat")) { + bool is_repeat = m_serial_repeat->IsSelected (); + if (!m_serial_repeat->IsSelected () && m_repeat_timer) { + m_serial_repeat->KillTimer ((UINT) *m_serial_repeat); + m_repeat_timer = false; + } + return true; + } + return false; + } + + bool OnTimer (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("serial_repeat")) { + on_send (); + return true; + } + return false; + } + +protected: + bool append_data (SerialDataType type, std::string data, bool is_source = true) { + std::string hex_data; + static auto _get_hex = [] (unsigned char c) -> char { c &= 0xf; if (c < 10) return c+'0'; if (c < 16) return c-10+'a'; return '?'; }; + static auto _is_hex = [] (char c) -> bool { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); }; + static auto _from_hex = [] (char c) -> unsigned char { if (c >= '0' && c <= '9') return c-'0'; if (c >= 'A' && c <= 'F') return c+10-'A'; if (c >= 'a' && c <= 'f') return c+10-'a'; return '\0'; }; + if (is_source) { + for (auto c : data) { + hex_data += _get_hex (c >> 4); + hex_data += _get_hex (c & 0xf); + } + } else { + hex_data = data; + data.clear (); + for (size_t i = 0; i < hex_data.size () - 1; i += 2) { + unsigned char c1 = hex_data[i], c2 = hex_data[i]; + while ((!_is_hex (c1) || !_is_hex (c2)) && i < hex_data.size () - 1) + ++i; + if (i == hex_data.size ()) { + break; + } else if (i == hex_data.size () - 1) { + return false; + } + char c = (_from_hex (c1) << 4) | _from_hex (c2); + data += c; + } + } + string_t _data = tool_Encoding::get_T (data).c_str (); + string_t _hex_data = tool_Encoding::get_T (hex_data).c_str (); + m_data.push_back ({ type, _data, _hex_data }); + auto ctrl = new CTextUI (); + ctrl->SetName (tool_StringT::format (_T ("serial_cnt_%d"), m_data.size () - 1).c_str ()); + ctrl->SetText ((m_serial_hex->IsSelected () && type != SerialDataTypeInfo ? _hex_data : _data).c_str ()); + ctrl->SetBkColor (0xFFCCCCCC); + ctrl->SetAutoCalcWidth (true); + ctrl->SetAutoCalcHeight (true); + RECT rcPadding = { (type == SerialDataTypeSend ? 100 : 0), 0, (type == SerialDataTypeRecv ? 100 : 0), 10 }; + ctrl->SetPadding (rcPadding); + ctrl->SetFloatAlign (type); + if (type != SerialDataTypeInfo) { + ctrl->SetTextColor (0xFF333333); + ctrl->SetTextPadding ({ 10, 10, 10, 10 }); + ctrl->SetBorderRound ({ 20, 20 }); + } else { + ctrl->SetTextColor (0xFF777777); + ctrl->SetTextPadding ({ 5, 5, 5, 5 }); + ctrl->SetBorderRound ({ 10, 10 }); + ctrl->SetShowHtml (true); + } + SIZE range = m_serial_cnt->GetScrollRange (); + SIZE pos = m_serial_cnt->GetScrollPos (); + m_serial_cnt->Add (ctrl); + if (range.cy < pos.cy + 10) { + std::thread ([this] () { + ::Sleep (10); + m_parent->invoke ([this] () -> LRESULT { + m_serial_cnt->SetScrollPos (m_serial_cnt->GetScrollRange ()); + return 0; + }); + }).detach (); + } + return true; + } + + void enable_combos (bool enable) { + m_serial_name->SetEnabled (enable); + m_serial_boudrate->SetEnabled (enable); + m_serial_bytesize->SetEnabled (enable); + m_serial_parity->SetEnabled (enable); + m_serial_stopbits->SetEnabled (enable); + } + + static std::string _serial_name (std::string s) { + size_t p1 = s.find ('(') + 1, p2 = s.find (')'); + return s.substr (p1, p2 - p1); + } + + BindComboUI m_serial_name { _T ("serial_name") }; + BindComboUI m_serial_boudrate { _T ("serial_boudrate") }; + BindComboUI m_serial_bytesize { _T ("serial_bytesize") }; + BindComboUI m_serial_parity { _T ("serial_parity") }; + BindComboUI m_serial_stopbits { _T ("serial_stopbits") }; + BindCheckBoxUI m_serial_hex { _T ("serial_hex") }; + BindEditUI m_serial_repeat_mill { _T ("serial_repeat_mill") }; + BindCheckBoxUI m_serial_repeat { _T ("serial_repeat") }; + BindCheckBoxUI m_serial_newline { _T ("serial_newline") }; + BindVerticalLayoutUI m_serial_cnt { _T ("serial_cnt") }; + BindRichEditUI m_serial_senddata { _T ("serial_senddata") }; + tool_SerialPort m_serial; + std::string m_tmp_port_name = ""; + bool m_repeat_timer = false; + std::vector> m_data; +}; + +#endif //__PAGE_SERIAL_PORT_HPP__ diff --git a/NetToolbox/pages/page_SysInfo.hpp b/NetToolbox/pages/page_SysInfo.hpp new file mode 100644 index 0000000..bd627cb --- /dev/null +++ b/NetToolbox/pages/page_SysInfo.hpp @@ -0,0 +1,53 @@ +#ifndef __PAGE_SYS_INFO_HPP__ +#define __PAGE_SYS_INFO_HPP__ + +#include + +#include "page_base.hpp" +#include "../tools/tool_SysInfo.hpp" +#include "../tools/tool_Encoding.hpp" +#include "../tools/tool_WMI.hpp" + + + +class page_SysInfo: public page_base { +public: + page_SysInfo (NetToolboxWnd *parent): page_base (parent) { + m_sysinfo_trademark->SetText (tool_Encoding::get_T (tool_SysInfo::get_trademark_name ())); + m_sysinfo_version->SetText (tool_Encoding::get_T (tool_SysInfo::get_system_info ())); + auto[cpu1, cpu2] = tool_SysInfo::get_cpu_info (); + m_sysinfo_cpu1->SetText (tool_Encoding::get_T (cpu1)); + m_sysinfo_cpu2->SetText (tool_Encoding::get_T (cpu2)); + m_sysinfo_mem->SetText (tool_SysInfo::get_memory_info ()); + // + std::thread ([this] () { + m_sysinfo_bios_id->SetText (tool_Encoding::get_T (tool_WMI::get_bios_id ())); + m_sysinfo_cpu_id->SetText (tool_Encoding::get_T (tool_WMI::get_cpu_id ())); + m_sysinfo_disk_id->SetText (tool_Encoding::get_T (tool_WMI::get_disk_id ())); + m_sysinfo_mainboard_id->SetText (tool_Encoding::get_T (tool_WMI::get_mainboard_id ())); + m_sysinfo_netcard_mac->SetText (tool_Encoding::get_T (tool_WMI::get_netcard_mac ())); + m_sysinfo_netcard_id->SetText (tool_Encoding::get_T (tool_WMI::get_netcard_id ())); + }).detach (); + } + virtual ~page_SysInfo () = default; + + void ui_update_data () override { + m_sysinfo_mem->SetText (tool_SysInfo::get_memory_info ()); + } + +protected: + BindEditUI m_sysinfo_trademark { _T ("sysinfo_trademark") }; + BindEditUI m_sysinfo_version { _T ("sysinfo_version") }; + BindEditUI m_sysinfo_cpu1 { _T ("sysinfo_cpu1") }; + BindEditUI m_sysinfo_cpu2 { _T ("sysinfo_cpu2") }; + BindEditUI m_sysinfo_mem { _T ("sysinfo_mem") }; + // + BindEditUI m_sysinfo_bios_id { _T ("sysinfo_bios_id") }; + BindEditUI m_sysinfo_cpu_id { _T ("sysinfo_cpu_id") }; + BindEditUI m_sysinfo_disk_id { _T ("sysinfo_disk_id") }; + BindEditUI m_sysinfo_mainboard_id { _T ("sysinfo_mainboard_id") }; + BindEditUI m_sysinfo_netcard_mac { _T ("sysinfo_netcard_mac") }; + BindEditUI m_sysinfo_netcard_id { _T ("sysinfo_netcard_id") }; +}; + +#endif //__PAGE_SYS_INFO_HPP__ diff --git a/NetToolbox/pages/page_Tracert.hpp b/NetToolbox/pages/page_Tracert.hpp new file mode 100644 index 0000000..ee7c79a --- /dev/null +++ b/NetToolbox/pages/page_Tracert.hpp @@ -0,0 +1,102 @@ +#ifndef __PAGE_TRACERT_HPP__ +#define __PAGE_TRACERT_HPP__ + +#include +#include + +#include "page_base.hpp" +#include "../tools/tool_Tracert.hpp" +#include "../tools/tool_DnsLookup.hpp" +#include "../tools/tool_Formatting.hpp" +#include "../tools/tool_Encoding.hpp" +#include "../tools/tool_String.hpp" + + + +class page_Tracert: public page_base { +public: + page_Tracert (NetToolboxWnd *parent): page_base (parent) {} + virtual ~page_Tracert () = default; + + bool OnClick (TNotifyUI& msg) override { + CDuiString name = msg.pSender->GetName (); + if (name == _T ("tracert_begin")) { + m_tracert_begin->SetEnabled (false); + CDuiString addr = m_tracert_addr->GetText (); + if (addr.empty ()) + addr = m_tracert_addr->GetTipValue (); + size_t p; + if ((p = addr.find (_T ("//"))) != string_t::npos) + addr = addr.substr (p + 2); + if ((p = addr.find (_T ("/"))) != string_t::npos) + addr = addr.substr (0, p); + if ((p = addr.find (_T (":"))) != string_t::npos) + addr = addr.substr (0, p); + std::string s = tool_Encoding::get_gb18030 (addr); + if (tool_Formatting::is_domain (s)) { + s = tool_DnsLookup::query_ip (s.c_str ()); + } + addr = tool_Encoding::get_T (s); + bool is_ipv4 = tool_Formatting::is_ipv4 (s), is_ipv6 = tool_Formatting::is_ipv6 (s); + if (!is_ipv4 && !is_ipv6) { + m_parent->show_status (NetToolboxWnd::StatusIcon::Error, _T ("δַ֪")); + m_tracert_begin->SetEnabled (true); + } else { + m_tracert_list->RemoveAll (); + m_parent->show_status (NetToolboxWnd::StatusIcon::Loading, tool_StringT::format (_T ("·ɸٵ %s"), addr.c_str ())); + std::thread ([this, s, is_ipv4] () { + bool _state; + string_t _info; + auto f = std::bind (&page_Tracert::on_show_info, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); + if (is_ipv4) { + std::tie (_state, _info) = tool_Tracert::start_ipv4 (s, f); + } else { + std::tie (_state, _info) = tool_Tracert::start_ipv6 (s, f); + } + this->m_parent->invoke ([&] () -> LRESULT { + m_parent->show_status (_state ? NetToolboxWnd::StatusIcon::Ok : NetToolboxWnd::StatusIcon::Error, _info.c_str ()); + m_tracert_begin->SetEnabled (true); + return 0; + }); + }).detach (); + } + return true; + } + return false; + } + +protected: + void on_show_info (size_t row, size_t col, std::string data) { + string_t _data = tool_Encoding::get_T (data); + m_parent->invoke ([&] () -> LRESULT { + CListContainerElementUI *item = nullptr; + if ((size_t) m_tracert_list->GetCount () <= row - 1) { + item = new CListContainerElementUI (); + item->SetFixedHeight (20); + // + CControlUI *ctrl = new CContainerUI (); + ctrl->SetBkImage (_T ("file='list_ico_2.png' source='0,0,16,16' dest='0,0,16,16'")); + ctrl->SetAttribute (_T ("padding"), _T ("7,2,7,2")); + item->Add (ctrl); + for (size_t i = 0; i < 5; ++i) { + ctrl = new CTextUI (); + ctrl->SetAttribute (_T ("align"), _T ("center")); + ctrl->SetAttribute (_T ("padding"), _T ("0,4,0,4")); + item->Add (ctrl); + } + m_tracert_list->Add (item); + } else { + item = dynamic_cast (m_tracert_list->GetItemAt ((int) row - 1)); + } + dynamic_cast (item->GetItemAt ((int) col + 1))->SetText (_data.c_str ()); + return 0; + }); + } + +protected: + BindButtonUI m_tracert_begin { _T ("tracert_begin") }; + BindEditUI m_tracert_addr { _T ("tracert_addr") }; + BindListUI m_tracert_list { _T ("tracert_list") }; +}; + +#endif //__PAGE_TRACERT_HPP__ diff --git a/NetToolbox/pages/page_Window.hpp b/NetToolbox/pages/page_Window.hpp new file mode 100644 index 0000000..cd0d96c --- /dev/null +++ b/NetToolbox/pages/page_Window.hpp @@ -0,0 +1,346 @@ +#ifndef __PAGE_WINDOW_HPP__ +#define __PAGE_WINDOW_HPP__ + +#include +#include +#include + +#include "page_base.hpp" + +#include "../tools/tool_Gdip.hpp" +#include "../tools/tool_Zoomer.hpp" + + + +class page_FindWnd: public WindowImplBase { +public: + virtual ~page_FindWnd () { + if (m_bmp_cover) { + ::DeleteObject (m_bmp_cover); + m_bmp_cover = NULL; + } + } + string_view_t GetWindowClassName () const override { return _T ("NetToolbox"); } + string_view_t GetSkinFile () override { return _T ("cur_findcolor.xml"); } + + void parent_notify_onmove (POINT &pt, string_view_t text) { + try { + //POINT pt { 0 }; + ::GetCursorPos (&pt); + m_find_text->SetText (text); + POINT pt_dst { pt.x + 40, pt.y }; + if (pt_dst.x + 180 > tool_Zoomer::get_desp_x ()) + pt_dst.x -= 165; + if (pt_dst.y + 150 > tool_Zoomer::get_desp_y ()) + pt_dst.y -= 110; + ::MoveWindow (GetHWND (), pt_dst.x, pt_dst.y, 120, 102, FALSE); + RECT rc = m_find_image->GetPos (); + int width = rc.right - rc.left; + int height = rc.bottom - rc.top; + if (width == 0 || height == 0) + return; + tool_Zoomer::zoom (pt); + + // жЧʱʼͼƬ + if (!m_bmp_cover) { + BITMAPINFO bi { { sizeof (BITMAPINFOHEADER), (LONG) width, (LONG) height, 1, 24, BI_RGB, (DWORD) width * height * 3, 5000, 5000, 0, 0 }, { 0 } }; + void *ptr = nullptr; + HDC hDeskDC = ::GetDC (NULL); + m_bmp_cover = ::CreateDIBSection (hDeskDC, &bi, DIB_RGB_COLORS, &ptr, NULL, 0); + ::ReleaseDC (NULL, hDeskDC); + m_pm.AddImage (_T ("cur_findcolor"), &m_bmp_cover, width, height, false); + m_find_image->SetBkImage (_T ("cur_findcolor")); + } + + // ͼƬ + int level = 4; + int dest_width = width / level; + int dest_height = height / level; + if (pt.x < dest_width / 2) { + pt.x = dest_width / 2; + } else if (pt.x > tool_Zoomer::get_real_x () - dest_width / 2) { + pt.x = tool_Zoomer::get_real_x () - dest_width / 2; + } + if (pt.y < dest_height / 2) { + pt.y = dest_height / 2; + } else if (pt.y > tool_Zoomer::get_real_y () - dest_height / 2) { + pt.y = tool_Zoomer::get_real_y () - dest_height / 2; + } + pt.x -= dest_width / 2; + pt.y -= dest_height / 2; + HDC hDC = ::GetDC (GetHWND ()); + HDC hMemDC = ::CreateCompatibleDC (hDC); + ::SelectObject (hMemDC, m_bmp_cover); + Gdiplus::Graphics gbmp (hMemDC); + gbmp.SetInterpolationMode (Gdiplus::InterpolationModeNearestNeighbor); + gbmp.DrawImage (m_bmp, Gdiplus::Rect (rc.left, rc.top, width, height), pt.x, pt.y, dest_width, dest_height, Gdiplus::UnitPixel); + RECT rcItem { 0, 0, width, height }; + CRenderEngine::DrawImageString (hMemDC, &m_pm, rcItem, rcItem, _T ("findcolor.png")); + ::DeleteDC (hMemDC); + ::ReleaseDC (GetHWND (), hDC); + m_find_image->Invalidate (); + //m_find_image->SetBkImage (_T ("")); + //m_find_image->SetBkImage (_T ("cur_findcolor")); + } catch (...) { + } + } + + LRESULT invoke (std::function f) { return ::SendMessage (m_hWnd, WM_USER + 0x101, 1, (LPARAM) &f); } + void async_invoke (std::function f) { ::PostMessage (m_hWnd, WM_USER + 0x101, 0, (LPARAM) new decltype (f) (f)); } + + Gdiplus::Bitmap *m_bmp = nullptr; + +private: + + BindControlUI m_find_image { _T ("find_image"), &m_pm }; + BindTextUI m_find_text { _T ("find_text"), &m_pm }; + HBITMAP m_bmp_cover = NULL; +}; + +class page_Window: public page_base { +public: + page_Window (NetToolboxWnd *parent): page_base (parent) { + m_bkimg_findcolor = m_window_findcolor->GetBkImage (); + m_cur_findcolor = ::LoadCursor (m_parent->get_instance (), MAKEINTRESOURCE (IDC_CURSOR_FINDCOLOR)); + m_find_wnd = new page_FindWnd (); + m_find_wnd->Create (m_parent->GetHWND (), _T ("ɫȡ"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE); + m_find_wnd->ShowWindow (false); + + m_bkimg_detector = m_window_detector->GetBkImage (); + m_cur_detector = ::LoadCursor (m_parent->get_instance (), MAKEINTRESOURCE (IDC_CURSOR_DETECTOR)); + + m_bkimg_zoomer = m_window_zoomer->GetBkImage (); + m_cur_zoomer = ::LoadCursor (m_parent->get_instance (), MAKEINTRESOURCE (IDC_CURSOR_ZOOMER)); + } + virtual ~page_Window () { + set_bmp_screen (nullptr); + if (m_find_wnd) { + m_find_wnd->Close (); + delete m_find_wnd; + } + } + + bool OnTimer (TNotifyUI &msg) override { + if (msg.pSender->GetName () == _T ("window_findcolor")) { + return true; + } else if (msg.pSender->GetName () == _T ("window_detector")) { + m_window_detector->KillTimer ((UINT) *m_window_detector); + HWND hDeskWnd = ::GetDesktopWindow (); + HDC hDeskDC = ::GetWindowDC (hDeskWnd); + int old_rop = ::SetROP2 (hDeskDC, R2_NOTXORPEN); + POINT pt = { 0 }; + ::GetCursorPos (&pt); + HWND wnd_sel = ::WindowFromPoint (pt); + RECT rc = { 0 }; + ::GetWindowRect (wnd_sel, &rc); + if (rc.left < 0) rc.left = 0; + if (rc.top < 0) rc.top = 0; + HPEN new_pen = ::CreatePen (0, 5, 0); + HGDIOBJ old_pen = ::SelectObject (hDeskDC, new_pen); + tool_Zoomer::zoom (rc); + ::Rectangle (hDeskDC, rc.left, rc.top, rc.right, rc.bottom); + std::thread ([this, hDeskDC, rc, old_rop, old_pen, new_pen, hDeskWnd] () { + ::Sleep (500); + ::Rectangle (hDeskDC, rc.left, rc.top, rc.right, rc.bottom); + ::SetROP2 (hDeskDC, old_rop); + ::SelectObject (hDeskDC, old_pen); + ::DeletePen (new_pen); + ::ReleaseDC (hDeskWnd, hDeskDC); + if (m_cap_detector) + m_window_detector->SetTimer ((UINT) *m_window_detector, 500); + }).detach (); + return true; + } else if (msg.pSender->GetName () == _T ("window_zoomer")) { + HDC hDC = ::GetDC (m_parent->GetHWND ()); + Gdiplus::Graphics g (hDC); + RECT rc = m_window_zoomhost->GetPos (); + int width = rc.right - rc.left; + int height = rc.bottom - rc.top; + POINT pt { 0 }; + ::GetCursorPos (&pt); + tool_Zoomer::zoom (pt); + int level = 5; + int dest_width = width / level; + int dest_height = height / level; + if (pt.x < dest_width / 2) { pt.x = dest_width / 2; } else if (pt.x > tool_Zoomer::get_real_x () - dest_width / 2) { pt.x = tool_Zoomer::get_real_x () - dest_width / 2; } + if (pt.y < dest_height / 2) { pt.y = dest_height / 2; } else if (pt.y > tool_Zoomer::get_real_y () - dest_height / 2) { pt.y = tool_Zoomer::get_real_y () - dest_height / 2; } + pt.x -= dest_width / 2; + pt.y -= dest_height / 2; + g.SetInterpolationMode (Gdiplus::InterpolationModeNearestNeighbor); + g.DrawImage (m_bmp_screen, Gdiplus::Rect (rc.left, rc.top, width, height), pt.x, pt.y, dest_width, dest_height, Gdiplus::UnitPixel); + ::ReleaseDC (m_parent->GetHWND (), hDC); + return true; + } + return false; + } + + bool OnLButtonDown (POINT &pt) override { + CControlUI *ctrl = m_parent->find_control (pt); + if (m_cap_findcolor || m_cap_detector || m_cap_zoomer) { + return OnLButtonUp (pt); + } else if (ctrl->GetName () == _T ("window_findcolor")) { + m_window_tab->SelectItem (0); + m_cap_findcolor = true; + ::SetCapture (m_parent->GetHWND ()); + m_cur_tmp = ::SetCursor (m_cur_findcolor); + m_window_findcolor->SetBkImage (_T ("")); + set_bmp_screen (tool_Gdip::capture_screen ()); + m_find_wnd->m_bmp = m_bmp_screen; + m_find_wnd->ShowWindow (); + OnMouseMove (pt); + return true; + } else if (ctrl->GetName () == _T ("window_detector")) { + m_window_tab->SelectItem (0); + m_cap_detector = true; + ::SetCapture (m_parent->GetHWND ()); + m_cur_tmp = ::SetCursor (m_cur_detector); + m_window_detector->SetBkImage (_T ("")); + set_bmp_screen (tool_Gdip::capture_screen ()); + ctrl->SetTimer ((UINT) ctrl, 500); + return true; + } else if (ctrl->GetName () == _T ("window_zoomer")) { + m_window_tab->SelectItem (1); + m_cap_zoomer = true; + ::SetCapture (m_parent->GetHWND ()); + m_cur_tmp = ::SetCursor (m_cur_zoomer); + m_window_zoomer->SetBkImage (_T ("")); + set_bmp_screen (tool_Gdip::capture_screen ()); + ctrl->SetTimer ((UINT) ctrl, 70); + return true; + } + return false; + } + bool OnLButtonUp (POINT &pt) override { + if (m_cap_findcolor) { + m_cap_findcolor = false; + ::ReleaseCapture (); + m_window_findcolor->SetBkImage (m_bkimg_findcolor.c_str ()); + m_find_wnd->ShowWindow (false); + } else if (m_cap_detector) { + m_cap_detector = false; + ::ReleaseCapture (); + m_window_detector->KillTimer ((UINT) *m_window_detector); + m_window_detector->SetBkImage (m_bkimg_detector.c_str ()); + + //POINT pt { 0 }; + ::GetCursorPos (&pt); + // ȡɫ + Gdiplus::Color _color; + m_bmp_screen->GetPixel (pt.x, pt.y, &_color); + m_window_color->SetBkColor (_color.GetValue () | 0xFF000000); + string_t str = tool_StringT::format (_T ("#%06X (%d, %d, %d)"), _color.GetValue (), _color.GetR (), _color.GetG (), _color.GetB ()); + m_window_txt_color->SetText (str.c_str ()); + // ȡھ + HWND hDestWnd = ::WindowFromPoint (pt); + str = tool_StringT::format (_T ("%X"), hDestWnd); + m_window_txt_handle->SetText (str.c_str ()); + // ȡλ + RECT rcDest { 0 }; + ::GetWindowRect (hDestWnd, &rcDest); + str = tool_StringT::format (_T ("pos (%d, %d), size (%d, %d)"), rcDest.left, rcDest.top, rcDest.right - rcDest.left, rcDest.bottom - rcDest.top); + m_window_txt_pos->SetText (str.c_str ()); + // ȡڱ + TCHAR wnd_text[MAX_PATH] = { 0 }; + ::GetWindowText (hDestWnd, wnd_text, MAX_PATH); + m_window_txt_title->SetText (wnd_text); + // + //HWND hPrevWnd = ::GetNextWindow (hDestWnd, GW_HWNDPREV); + //HWND hNextWnd = ::GetNextWindow (hDestWnd, GW_HWNDNEXT); + //HWND hChildWnd = ::GetNextWindow (hDestWnd, GW_CHILD); + HWND hParentWnd = ::GetNextWindow (hDestWnd, GW_OWNER); + str = tool_StringT::format (_T ("%X"), hParentWnd); + m_window_txt_parent->SetText (str.c_str ()); + // ʽ + LONG style = ::GetWindowLong (hDestWnd, GWL_STYLE); + str = tool_StringT::format (_T ("0x%08X"), style); + m_window_txt_style->SetText (str.c_str ()); + LONG exstyle = ::GetWindowLong (hDestWnd, GWL_EXSTYLE); + str = tool_StringT::format (_T ("0x%08X"), exstyle); + m_window_txt_exstyle->SetText (str.c_str ()); + // ȡ + TCHAR class_text[MAX_PATH] = { 0 }; + ::GetClassName (hDestWnd, class_text, MAX_PATH); + m_window_txt_wndclass->SetText (class_text); + return true; + } else if (m_cap_zoomer) { + m_cap_zoomer = false; + ::ReleaseCapture (); + m_window_zoomer->KillTimer ((UINT) *m_window_zoomer); + m_window_zoomer->SetBkImage (m_bkimg_zoomer.c_str ()); + return true; + } + return false; + } + + bool OnRButtonDown (POINT &pt) override { + if (m_cap_findcolor || m_cap_detector || m_cap_zoomer) { + return OnLButtonUp (pt); + } + return false; + } + bool OnRButtonUp (POINT &pt) override { + if (m_cap_findcolor || m_cap_detector || m_cap_zoomer) { + return OnLButtonUp (pt); + } + return false; + } + + bool OnMouseMove (POINT &pt) override { + if (m_cap_findcolor) { + //POINT pt { 0 }; + ::GetCursorPos (&pt); + tool_Zoomer::zoom (pt); + // ȡɫ + Gdiplus::Color _color; + m_bmp_screen->GetPixel (pt.x, pt.y, &_color); + m_window_color->SetBkColor (_color.GetValue () | 0xFF000000); + string_t str = tool_StringT::format (_T ("#%06X (%d, %d, %d)"), _color.GetValue () & 0xFFFFFF, _color.GetR (), _color.GetG (), _color.GetB ()); + m_window_txt_color->SetText (str.c_str ()); + // + str = tool_StringT::format (_T ("RGB (%d, %d, %d)"), _color.GetR (), _color.GetG (), _color.GetB ()); + m_find_wnd->parent_notify_onmove (pt, str); + return true; + } + return false; + } + +protected: + void set_bmp_screen (Gdiplus::Bitmap *bmp) { + if (m_bmp_screen) + delete m_bmp_screen; + m_bmp_screen = bmp; + } + + HCURSOR m_cur_tmp = ::LoadCursor (NULL, IDC_ARROW); + Gdiplus::Bitmap *m_bmp_screen = nullptr; + + BindContainerUI m_window_findcolor { _T ("window_findcolor") }; + string_t m_bkimg_findcolor = _T (""); + bool m_cap_findcolor = false; + HCURSOR m_cur_findcolor; + page_FindWnd *m_find_wnd = nullptr; + + BindContainerUI m_window_detector { _T ("window_detector") }; + string_t m_bkimg_detector = _T (""); + bool m_cap_detector = false; + HCURSOR m_cur_detector; + + BindContainerUI m_window_zoomer { _T ("window_zoomer") }; + string_t m_bkimg_zoomer = _T (""); + bool m_cap_zoomer = false; + HCURSOR m_cur_zoomer; + + BindTabLayoutUI m_window_tab { _T ("window_tab") }; + BindContainerUI m_window_color { _T ("window_color") }; + BindEditUI m_window_txt_color { _T ("window_txt_color") }; + BindEditUI m_window_txt_handle { _T ("window_txt_handle") }; + BindEditUI m_window_txt_pos { _T ("window_txt_pos") }; + BindEditUI m_window_txt_title { _T ("window_txt_title") }; + BindEditUI m_window_txt_parent { _T ("window_txt_parent") }; + BindEditUI m_window_txt_style { _T ("window_txt_style") }; + BindEditUI m_window_txt_exstyle { _T ("window_txt_exstyle") }; + BindEditUI m_window_txt_wndclass { _T ("window_txt_wndclass") }; + BindContainerUI m_window_zoomhost { _T ("window_zoomhost") }; +}; + +#endif //__PAGE_WINDOW_HPP__ diff --git a/NetToolbox/pages/page_base.hpp b/NetToolbox/pages/page_base.hpp new file mode 100644 index 0000000..f38aea3 --- /dev/null +++ b/NetToolbox/pages/page_base.hpp @@ -0,0 +1,34 @@ +#ifndef __PAGE_BASE_HPP__ +#define __PAGE_BASE_HPP__ + +#include + + + +class NetToolboxWnd; + +class page_base { +public: + page_base (NetToolboxWnd *parent): m_parent (parent) {} + virtual ~page_base () = default; + + virtual void ui_update_data () {}; + virtual bool OnClick (TNotifyUI &msg) { return false; } + virtual bool OnHeaderClick (TNotifyUI &msg) { return false; } + virtual bool OnSelectChanged (TNotifyUI &msg) { return false; } + virtual bool OnTextChanged (TNotifyUI &msg) { return false; } + virtual bool OnItemSelect (TNotifyUI &msg) { return false; } + virtual bool OnTimer (TNotifyUI &msg) { return false; } + virtual bool is_accept_drag () { return false; } + virtual bool OnDropFiles (LPCTSTR path) { return false; } + virtual bool OnLButtonDown (POINT &pt) { return false; } + virtual bool OnLButtonUp (POINT &pt) { return false; } + virtual bool OnRButtonDown (POINT &pt) { return false; } + virtual bool OnRButtonUp (POINT &pt) { return false; } + virtual bool OnMouseMove (POINT &pt) { return false; } + virtual bool OnMenuClick (MenuCmd *mc) { return false; } +protected: + NetToolboxWnd *m_parent = nullptr; +}; + +#endif //__PAGE_BASE_HPP__ diff --git a/NetToolbox/res/LICENSE_OpenSSL b/NetToolbox/res/LICENSE_OpenSSL new file mode 100644 index 0000000..e953f59 --- /dev/null +++ b/NetToolbox/res/LICENSE_OpenSSL @@ -0,0 +1,125 @@ + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a double license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2018 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + diff --git a/NetToolbox/res/LICENSE_directui b/NetToolbox/res/LICENSE_directui new file mode 100644 index 0000000..ea08b50 --- /dev/null +++ b/NetToolbox/res/LICENSE_directui @@ -0,0 +1,14 @@ +DirectUI - UI Library + +Written by Bjarke Viksoe (bjarke@viksoe.dk) +Copyright (c) 2006-2007 Bjarke Viksoe. + +This code may be used in compiled form in any way you desire. These +source files may be redistributed by any means PROVIDING it is +not sold for profit without the authors written consent, and +providing that this notice and the authors name is included. + +This file is provided "as is" with no expressed or implied warranty. +The author accepts no liability if it causes any damage to you or your +computer whatsoever. It's free, so don't hassle me about it. +Beware of bugs. \ No newline at end of file diff --git a/NetToolbox/res/LICENSE_duilib b/NetToolbox/res/LICENSE_duilib new file mode 100644 index 0000000..9e57e0d --- /dev/null +++ b/NetToolbox/res/LICENSE_duilib @@ -0,0 +1,9 @@ +Copyright (c) 2010-2011, duilib develop team(https://github.com/duilib/duilib).All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met. + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/NetToolbox/res/LICENSE_serial b/NetToolbox/res/LICENSE_serial new file mode 100644 index 0000000..3ac764a --- /dev/null +++ b/NetToolbox/res/LICENSE_serial @@ -0,0 +1,9 @@ +The MIT License + +Copyright (c) 2012 William Woodall, John Harrison + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/NetToolbox/res/find_color.cur b/NetToolbox/res/find_color.cur new file mode 100644 index 0000000000000000000000000000000000000000..4849b35b1fffee5ec774525f9873fc0cf8e0e762 GIT binary patch literal 4286 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 literal 0 HcmV?d00001 diff --git a/NetToolbox/res/res.zip b/NetToolbox/res/res.zip new file mode 100644 index 0000000000000000000000000000000000000000..fc7fc6910e6ab0026478a997f2a3884a94db0c46 GIT binary patch literal 322791 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

<~Y&{SB+KLwhE&c7Lxvl&n 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 + + + + + + + + + + + + + + + + + + + + + diff --git a/NetToolbox/res/zoom_size.cur b/NetToolbox/res/zoom_size.cur new file mode 100644 index 0000000000000000000000000000000000000000..db48f9bca875d2b01ab57b04f4db41478478b8a7 GIT binary patch literal 4286 zcmeH}zl#$=6vyW-NW>o(rF&k)Yg$j%TUVe?K;Tv7K$il$V&md=D7scw(gfSSTN$nQc9!;);vUqK|hv@+o z?^pX1wPieul@K3+doT+v_yTh<1*btiJ8gN?-bBJH@EzQ{OS*Q@+=4BTN9{x8`3&)T z-xzxV&l3D{AEME5S?By1@xNj@p3pxG!=P&4@(eEiHSG!TSrNfi zaB*_|3i2*OZ7o7sTQ0`~+RH z`0Ne>_xz`P;sF0amG4!6{)Ey`2k3ng{a<#;C&nND|A_)(k|bpuZtcsQ?n@HJ#&6IU zy&!8x_W!IV`(p!Kp2ryytVgT&pO^IG=bE&1Qu`XX+Oq%bNek%X8)r=K{qyuE`sm_A zI~bS&S6i-U;-bL*adi3~B-c|o3wvQ5IFofQ`?Rhr?Tb$EkJqI0SG-QVFMFt*pYkyI zCN4wh`ST`PIe+GDBP2Wu_hA-fjmp_-4yK^r`J3S-^UZ+$YYmzgpkQscU~8L;Y#t>R zQ@LFb6}NvK>>fT~oBSR{m4< EH>8U|F8}}l literal 0 HcmV?d00001 diff --git a/NetToolbox/resource.h b/NetToolbox/resource.h new file mode 100644 index 0000000..0f2a20d --- /dev/null +++ b/NetToolbox/resource.h @@ -0,0 +1,23 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ ɵİļ +// Resource.rc ʹ +// +#define IDI_ICON1 101 +#define IDC_CURSOR_DETECTOR 103 +#define IDC_CURSOR_ZOOMER 104 +#define IDR_RT_MANIFEST1 105 +#define IDC_CURSOR1 106 +#define IDC_CURSOR_FINDCOLOR 106 +#define IDR_ZIPRES2 108 +#define IDR_ZIPRES1 108 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 109 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/NetToolbox/settings.hpp b/NetToolbox/settings.hpp new file mode 100644 index 0000000..086e0c6 --- /dev/null +++ b/NetToolbox/settings.hpp @@ -0,0 +1,50 @@ +#ifndef __SETTINGS_HPP__ +#define __SETTINGS_HPP__ + +#include +#include +#include +#include + +#include "tools/tool_Path.hpp" +#include "tools/tool_WebRequest.hpp" +#include "tools/tool_Encoding.hpp" + + + +class Settings { + Settings () = delete; + +public: + // ʼ + static void init () { + m_file = tool_Path::get_exe_path () + _T ("Settings.json"); + m_fileA = tool_Encoding::get_gb18030 (m_file); + // м + bool bInit = !tool_Path::file_exist (m_file); +#ifndef _DEBUG + std::thread ([bInit] () { + if (bInit) { + tool_WebRequest::get ("https://www.fawdlstty.com:3001/start_count"); + } else { + tool_WebRequest::get ("https://www.fawdlstty.com:3001/restart_count"); + } + }).detach (); +#endif + if (bInit) { + tool_Encoding::get_gb18030 (m_file); + std::ofstream ofs (m_fileA, std::ios::binary); + ofs << "{}"; + ofs.close (); + } + } + +private: + static string_t m_file; + static std::string m_fileA; +}; + +inline string_t Settings::m_file; +inline std::string Settings::m_fileA; + +#endif //__SETTINGS_HPP__ diff --git a/NetToolbox/tools/tool_Base64.hpp b/NetToolbox/tools/tool_Base64.hpp new file mode 100644 index 0000000..eca73d4 --- /dev/null +++ b/NetToolbox/tools/tool_Base64.hpp @@ -0,0 +1,99 @@ +#ifndef __TOOL_BASE64_HPP__ +#define __TOOL_BASE64_HPP__ + +#include +#include +#include + + + +class tool_Base64 { +public: + // жǷΪbase64 + static bool is_base64 (const unsigned char *data, int length) { + if (length % 4) { + return false; + } else if (length == 0) { + return true; + } + if (data[length - 1] == '=') + --length; + if (data[length - 1] == '=') + --length; + for (int i = 0; i < length; ++i) { + char ch = data[i]; + if (!(isalnum (ch) || (ch == '+') || (ch == '/'))) + return false; + } + return true; + } + + // base64 + static std::string base64_encode (unsigned char const* bytes_to_encode, unsigned int in_len) { + std::string ret; + int i = 0, j = 0; + unsigned char char_3[3], char_4[4]; + while (in_len--) { + char_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_4[0] = (char_3[0] & 0xfc) >> 2; + char_4[1] = ((char_3[0] & 0x03) << 4) + ((char_3[1] & 0xf0) >> 4); + char_4[2] = ((char_3[1] & 0x0f) << 2) + ((char_3[2] & 0xc0) >> 6); + char_4[3] = char_3[2] & 0x3f; + + for (i = 0; i < 4; i++) + ret += base64_chars[char_4[i]]; + i = 0; + } + } + if (i) { + for (j = i; j < 3; j++) + char_3[j] = '\0'; + char_4[0] = (char_3[0] & 0xfc) >> 2; + char_4[1] = ((char_3[0] & 0x03) << 4) + ((char_3[1] & 0xf0) >> 4); + char_4[2] = ((char_3[1] & 0x0f) << 2) + ((char_3[2] & 0xc0) >> 6); + for (j = 0; j < i + 1; j++) + ret += base64_chars[char_4[j]]; + while ((i++ < 3)) + ret += '='; + } + return ret; + } + + // base64 + static std::string base64_decode (std::string_view encoded_string) { + int in_len = encoded_string.size (), i = 0, j = 0, in_ = 0; + unsigned char char_4[4], char_3[3]; + std::string ret; + auto is_base64 = [] (unsigned char c) { return (isalnum (c) || (c == '+') || (c == '/')); }; + while (in_len-- && (encoded_string[in_] != '=') && is_base64 (encoded_string[in_])) { + char_4[i++] = encoded_string[in_]; in_++; + if (i == 4) { + for (i = 0; i < 4; i++) + char_4[i] = (unsigned char) base64_chars.find (char_4[i]); + char_3[0] = (char_4[0] << 2) + ((char_4[1] & 0x30) >> 4); + char_3[1] = ((char_4[1] & 0xf) << 4) + ((char_4[2] & 0x3c) >> 2); + char_3[2] = ((char_4[2] & 0x3) << 6) + char_4[3]; + + for (i = 0; i < 3; i++) + ret += char_3[i]; + i = 0; + } + } + if (i) { + for (j = 0; j < i; j++) + char_4[j] = (unsigned char) base64_chars.find (char_4[j]); + char_3[0] = (char_4[0] << 2) + ((char_4[1] & 0x30) >> 4); + char_3[1] = ((char_4[1] & 0xf) << 4) + ((char_4[2] & 0x3c) >> 2); + for (j = 0; j < i - 1; j++) ret += char_3[j]; + } + return ret; + } + +private: + static const std::string base64_chars; +}; + +inline const std::string tool_Base64::base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +#endif //__TOOL_BASE64_HPP__ diff --git a/NetToolbox/tools/tool_DnsLookup.hpp b/NetToolbox/tools/tool_DnsLookup.hpp new file mode 100644 index 0000000..c1f7ab9 --- /dev/null +++ b/NetToolbox/tools/tool_DnsLookup.hpp @@ -0,0 +1,136 @@ +#ifndef __TOOL_DNS_LOOKUP_HPP__ +#define __TOOL_DNS_LOOKUP_HPP__ + +#include +#include +#include +#include +#include + +using boost::asio::ip::udp; + +//gethostbynamegetaddrinfo + +#define DNS_TYPE_A 0x0001 //1 a host address +#define DNS_TYPE_CNAME 0x0005 //5 the canonical name for an alias + + + +class tool_DnsLookup { +public: + static std::string query_ip (const char *dest_domain) { + ////getaddrinfo (PCSTR pNodeName, PCSTR pServiceName, const ADDRINFOA *pHints, PADDRINFOA *ppResult); + //addrinfo hints = { 0 }, *result = nullptr; + //hints.ai_flags = AI_NUMERICHOST; + //hints.ai_family = AF_UNSPEC; + //if (::getaddrinfo (dest_domain, nullptr, &hints, &result)) { + // DWORD d = ::GetLastError (); + // return ""; + //} + //result = result; + //::freeaddrinfo (result); + //return ""; + hostent *host = ::gethostbyname (dest_domain); + return (host ? (char*) inet_ntoa (*(in_addr*) host->h_addr) : ""); + } + static std::string query_ipv4_udp (const char *dest_domain, const char *dns_server = "8.8.8.8") { + char send_packet[MAX_PATH]; + memset (send_packet, 0, sizeof (send_packet)); + + //DNSѯͷ + ((uint16_t*) send_packet)[0] = htons (0x1234); // Ựʶ + ((uint16_t*) send_packet)[1] = htons (0x0100); // ʶλ + ((uint16_t*) send_packet)[2] = htons (0x0001); // Question + ((uint16_t*) send_packet)[3] = htons (0x0000); // Answer + ((uint16_t*) send_packet)[4] = htons (0x0000); // Authority + ((uint16_t*) send_packet)[5] = htons (0x0000); // Additional + + // Question + std::string query_url = build_query_url (dest_domain); + memcpy (&send_packet[12], &query_url[0], query_url.size ()); + size_t packet_size = 12 + query_url.size () + 4; + ((uint16_t*) &send_packet[packet_size])[-2] = htons (0x0001); + ((uint16_t*) &send_packet[packet_size])[-1] = htons (0x0001); + + //DNSѯ + boost::asio::io_service io_service; + boost::asio::ip::address addr = boost::asio::ip::address::from_string (dns_server); + udp::endpoint receiver_endpoint (addr, 53); + udp::socket socket (io_service, udp::endpoint (udp::v4 (), 5264)); + socket.send_to (boost::asio::buffer(send_packet, packet_size), receiver_endpoint); + boost::array recv_buf; + boost::system::error_code err_code; + socket.receive_from (boost::asio::buffer (recv_buf), receiver_endpoint, 0, err_code); + + const char *recv_packet = recv_buf.data (); + size_t recv_offset = 12; + uint16_t session_id = ntohs (((uint16_t*) recv_packet)[0]); // 0x1234 + uint16_t flags = ntohs (((uint16_t*) recv_packet)[1]); // (flags & 0xfb7f) == 0x8100 + uint16_t question_count = ntohs (((uint16_t*) recv_packet)[2]); + uint16_t answer_count = ntohs (((uint16_t*) recv_packet)[3]); + + while (question_count-- > 0) + std::string reply_url = parse_query_url (recv_packet, recv_offset); + + if (answer_count == 0) + return ""; + while (answer_count-- > 0) { + std::string reply_url = parse_query_url (recv_packet, recv_offset); + + uint16_t usAnswerType = ntohs (((uint16_t*) (recv_packet + recv_offset))[0]); + uint16_t usAnswerClass = ntohs (((uint16_t*) (recv_packet + recv_offset))[1]); + uint32_t usAnswerTTL = ntohl (((uint32_t*) (recv_packet + recv_offset))[1]); + uint16_t usAnswerDataLen = ntohs (((uint16_t*) (recv_packet + recv_offset))[4]); + recv_offset += 10; + + if (usAnswerType == DNS_TYPE_A) { + uint32_t ulIP = (((uint32_t*) (recv_packet + recv_offset))[0]); + in_addr addr; + addr.S_un.S_addr = ulIP; + return ::inet_ntoa (addr); + //std::cout << "A:" << inet_ntoa (addr) << "\n"; + } else if (usAnswerType == DNS_TYPE_CNAME) { + std::string reply_cname = parse_query_url (recv_packet, recv_offset); + //std::cout << "CNAME:" << reply_cname << "\n"; + } + } + return ""; + } + +protected: + static std::string build_query_url (std::string url) { + std::string query_url = ""; + size_t p1 = 0, p2 = url.find ('.'); + auto _build = [&] () { + query_url += (char) (p2 - p1); + for (; p1 < p2; ++p1) + query_url += url[p1]; + }; + while (p2 != std::string::npos) { + _build (); + p2 = url.find ('.', p2 + 1); + ++p1; + } + p2 = url.length (); + _build (); + query_url += '\0'; + return query_url; + } + + static std::string parse_query_url (const char *p_url, size_t &offset) { + std::string url = ""; + bool compress = ((p_url[offset] & 0xc0) == 0xc0); + size_t _off = (compress ? p_url[offset + 1] : offset); + while (p_url[_off]) { + if (url != "") + url += '.'; + size_t len = (size_t) (p_url[_off++]); + for (size_t i = 0; i < len; ++i) + url += p_url[_off++]; + } + offset = (compress ? offset + 2 : _off + 5); + return url; + } +}; + +#endif //__TOOL_DNS_LOOKUP_HPP__ diff --git a/NetToolbox/tools/tool_Encoding.hpp b/NetToolbox/tools/tool_Encoding.hpp new file mode 100644 index 0000000..5193e02 --- /dev/null +++ b/NetToolbox/tools/tool_Encoding.hpp @@ -0,0 +1,317 @@ +#ifndef __TOOL_ENCODING_HPP__ +#define __TOOL_ENCODING_HPP__ + +#include +#include +#include + + + +class tool_Encoding { +public: + // ² + static std::string guess (unsigned char *data, int length) { + if (is_ascii (data, length)) { + return "ascii"; + } else if (is_utf8 (data, length)) { + return "utf8"; + } else if (is_gb18030 (data, length)) { + return "gb18030"; + } else if (is_utf16 (data, length)) { + return "utf16"; + } else { + return "iso-8859-1"; + } + } + static bool is_ascii (unsigned char *data, int length) { + while (--length >= 0 && !(data[length] & 0x80)); + return length < 0; + } + static bool is_utf8 (unsigned char *data, int length) { + auto high_len = [] (unsigned char ch) { + int _len = 0; + while (ch & 0x80) { + ++_len; + ch <<= 1; + } + return _len; + }; + int state = 0; + for (int i = 0; i < length; ++i) { + int _len = high_len (data[i]); + if (state > 0) { + if (_len != 1) + return false; + --state; + } else if (_len == 1) { + return false; + } else if (_len > 1) { + state = _len - 1; + } + } + return (state == 0); + } + static bool is_gb18030 (unsigned char *data, int length) { + int state = 0; + for (int i = 0; i < length; ++i) { + if (data[i] & 0x80) { + state = 1 - state; + } else if (state == 1) { + return false; + } + } + return true; + } + static bool is_utf16 (unsigned char *data, int length) { + if (length % 2) + return false; + for (int i = 0; i < length; i += 2) { + if ((data[i] & 0x80) != (data[i + 1] & 0x80)) + return false; + } + return true; + } + + // ת + static std::wstring gb18030_to_utf16 (std::string_view _old) { return _conv_to_wide (_old, CP_ACP); } + static std::string utf16_to_gb18030 (std::wstring_view _old) { return _conv_to_multi (_old, CP_ACP); } + static std::wstring utf8_to_utf16 (std::string_view _old) { return _conv_to_wide (_old, CP_UTF8); } + static std::string utf16_to_utf8 (std::wstring_view _old) { return _conv_to_multi (_old, CP_UTF8); } + static std::string gb18030_to_utf8 (std::string_view _old) { return utf16_to_utf8 (gb18030_to_utf16 (_old)); } + static std::string utf8_to_gb18030 (std::string_view _old) { return utf16_to_gb18030 (utf8_to_utf16 (_old)); } +#ifdef UNICODE + static std::string get_gb18030 (string_view_t _old) { return utf16_to_gb18030 (_old); } + static std::string get_utf8 (string_view_t _old) { return utf16_to_utf8 (_old); } + static std::wstring get_utf16 (string_view_t _old) { return std::wstring (_old); } + static string_t get_T (std::string_view _old) { return gb18030_to_utf16 (_old); } + static string_t get_T_from_utf8 (std::string_view _old) { return utf8_to_utf16 (_old); } + static string_t get_T (std::wstring_view _old) { return std::wstring (_old); } +#else + static std::string get_gb18030 (string_view_t _old) { return std::string (_old); } + static std::string get_utf8 (string_view_t _old) { return gb18030_to_utf8 (_old); } + static std::wstring get_utf16 (string_view_t _old) { return gb18030_to_utf16 (_old); } + static string_t get_T (std::string_view _old) { return std::string (_old); } + static string_t get_T_from_utf8 (std::string_view _old) { return utf8_to_gb18030 (_old); } + static string_t get_T (std::wstring_view _old) { return utf16_to_gb18030 (_old); } +#endif +private: + static std::string _conv_to_multi (std::wstring_view _old, UINT ToType) { + int lenOld = lstrlenW (_old.data ()); + int lenNew = ::WideCharToMultiByte (ToType, 0, _old.data (), lenOld, nullptr, 0, nullptr, nullptr); + std::string s; + s.resize (lenNew); + if (!::WideCharToMultiByte (ToType, 0, _old.data (), lenOld, const_cast(s.c_str ()), lenNew, nullptr, nullptr)) + return ""; + return s.c_str (); + } + static std::wstring _conv_to_wide (std::string_view _old, UINT ToType) { + int lenOld = lstrlenA (_old.data ()); + int lenNew = ::MultiByteToWideChar (ToType, 0, _old.data (), lenOld, nullptr, 0); + std::wstring s; + s.resize (lenNew); + if (!::MultiByteToWideChar (ToType, 0, _old.data (), lenOld, const_cast(s.c_str ()), lenNew)) + return L""; + return s.c_str (); + } + +public: + static bool is_hex_char (char ch) { + return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'); + } + static bool is_base64_char (char ch) { + return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '+' || ch == '/'; + } + static char hex_char_to_dec (char ch) { + if (ch >= '0' && ch <= '9') { + return ch - '0'; + } else if (ch >= 'A' && ch <= 'F') { + return ch - 'A' + 10; + } else if (ch >= 'a' && ch <= 'f') { + return ch - 'a' + 10; + } else { + return 0; + } + } + + // ʽ² + static bool is_percent_str (std::string_view data) { + bool ret = false; + for (size_t i = 0; i < data.size (); ++i) { + if (data[i] == '%') { + if (i + 1 >= data.size ()) + return false; + if (data[++i] == '%') + continue; + if (i + 1 >= data.size ()) + return false; + if (!is_hex_char (data[i])) + return false; + if (!is_hex_char (data[++i])) + return false; + ret = true; + } + } + return ret; + } + static bool is_escape_x_str (std::string_view data) { + bool ret = false; + for (size_t i = 0; i < data.size (); ++i) { + if (data[i] == '\\') { + if (i + 1 >= data.size ()) + return false; + if (data[++i] == '\\') + continue; + if (data[i] != 'x') + continue; + if (i + 2 >= data.size ()) + return false; + if (!is_hex_char (data[++i])) + return false; + if (!is_hex_char (data[++i])) + return false; + ret = true; + } + } + return ret; + } + static bool is_escape_u_str (std::string_view data) { + bool ret = false; + for (size_t i = 0; i < data.size (); ++i) { + if (data[i] == '\\') { + if (i + 1 >= data.size ()) + return false; + if (data[++i] == '\\') + continue; + if (data[i] != 'u') + continue; + if (i + 4 >= data.size ()) + return false; + if (!is_hex_char (data[++i])) + return false; + if (!is_hex_char (data[++i])) + return false; + if (!is_hex_char (data[++i])) + return false; + if (!is_hex_char (data[++i])) + return false; + ret = true; + } + } + return ret; + } + static bool is_base64_str (std::string_view data) { + for (size_t i = 0; i < data.size (); ++i) { + if (!is_base64_char (data[i])) { + if (i < data.size () - 2 || data[i] != '=') + return false; + } + } + return true; + } + + // ʽת + static std::string percent_str_encode (std::string_view data) { + const static char *hex_char = "0123456789ABCDEF"; + std::string ret = ""; + for (size_t i = 0; i < data.size (); ++i) { + char ch = data[i]; + if (isalnum ((unsigned char) ch) || ch == '-' || ch == '_' || ch == '.' || ch == '~') { + ret += ch; + } else if (ch == ' ') { + ret += "+"; + } else { + ret += '%'; + ret += hex_char [((unsigned char) ch) >> 4]; + ret += hex_char [((unsigned char) ch) % 16]; + } + } + return ret; + } + static std::string percent_str_decode (std::string_view data) { + std::string ret = ""; + for (size_t i = 0; i < data.size (); ++i) { + char ch = data[i]; + if (ch == '%') { + ret += (char) ((hex_char_to_dec (data[i + 1]) << 4) | hex_char_to_dec (data[i + 2])); + i += 2; + } else { + ret += ch; + } + } + return ret; + } + static std::string escape_x_str_encode (std::string_view data) { + const static char *hex_char = "0123456789ABCDEF"; + std::string ret = ""; + for (size_t i = 0; i < data.size (); ++i) { + char ch = data[i]; + if ((ch >= 0x20 && ch <= 0x7e) || ch == '\x09' || ch == '\x0a' || ch == '\x0b' || ch == '\x0d') { + ret += ch; + } else { + ret += "\\x"; + ret += hex_char[((unsigned char) ch) >> 4]; + ret += hex_char[((unsigned char) ch) % 16]; + } + } + return ret; + } + static std::string escape_x_str_decode (std::string_view data) { + std::string ret = ""; + for (size_t i = 0; i < data.size (); ++i) { + char ch = data[i]; + if (ch == '\\') { + ch = data[++i]; + if (ch == 'x') { + ret += (char) ((hex_char_to_dec (data[i + 1]) << 4) | hex_char_to_dec (data[i + 2])); + i += 2; + } else { + ret += '\\'; + ret += ch; + } + } else { + ret += ch; + } + } + return ret; + } + static std::string escape_u_str_encode (std::string_view data) { + const static char *hex_char = "0123456789ABCDEF"; + std::string ret = ""; + for (size_t i = 0; i < data.size (); ++i) { + char ch = data[i]; + if (ch & 0x80) { + ret += "\\u"; + ret += hex_char[((unsigned char) ch) >> 4]; + ret += hex_char[((unsigned char) ch) % 16]; + ch = data[++i]; + ret += hex_char[((unsigned char) ch) >> 4]; + ret += hex_char[((unsigned char) ch) % 16]; + } else { + ret += ch; + } + } + return ret; + } + static std::string escape_u_str_decode (std::string_view data) { + std::string ret = ""; + for (size_t i = 0; i < data.size (); ++i) { + char ch = data[i]; + if (ch == '\\') { + ch = data[++i]; + if (ch == 'u') { + ret += (char) ((hex_char_to_dec (data[i + 1]) << 4) | hex_char_to_dec (data[i + 2])); + ret += (char) ((hex_char_to_dec (data[i + 3]) << 4) | hex_char_to_dec (data[i + 4])); + i += 4; + } else { + ret += '\\'; + ret += ch; + } + } else { + ret += ch; + } + } + return ret; + } +}; + +#endif //__TOOL_ENCODING_HPP__ diff --git a/NetToolbox/tools/tool_Formatting.hpp b/NetToolbox/tools/tool_Formatting.hpp new file mode 100644 index 0000000..8138a02 --- /dev/null +++ b/NetToolbox/tools/tool_Formatting.hpp @@ -0,0 +1,50 @@ +#ifndef __TOOL_FORMATTING_HPP__ +#define __TOOL_FORMATTING_HPP__ + +#include +#include + + + +class tool_Formatting { +public: + static std::string query_regex (std::string desp) { + static std::map m_regex_items { + { "IPv4", "^(((25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))\\.){3}(25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d))))$" }, + { "IPv6", "^((([0-9|A-F|a-f]{1,4}:){7}([0-9|A-F|a-f]{1,4}|:))|(([0-9|A-F|a-f]{1,4}:){6}(:[0-9|A-F|a-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9|A-F|a-f]{1,4}:){5}(((:[0-9|A-F|a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9|A-F|a-f]{1,4}:){4}(((:[0-9|A-F|a-f]{1,4}){1,3})|((:[0-9|A-F|a-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9|A-F|a-f]{1,4}:){3}(((:[0-9|A-F|a-f]{1,4}){1,4})|((:[0-9|A-F|a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9|A-F|a-f]{1,4}:){2}(((:[0-9|A-F|a-f]{1,4}){1,5})|((:[0-9|A-F|a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9|A-F|a-f]{1,4}:){1}(((:[0-9|A-F|a-f]{1,4}){1,6})|((:[0-9|A-F|a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9|A-F|a-f]{1,4}){1,7})|((:[0-9|A-F|a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?$" }, + { "", "^([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}$" }, + { "URL", "^(http|ftp|https):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&:/~\\+#]*[\\w\\-\\@?^=%&/~\\+#])?$" }, + { "", "^\\w+(\\.\\w+)*@(\\w)+((\\.\\w+)+)$" }, + { "֤", "^(\\d{17}(\\d|x|X|))|(\\d{15})$" }, + { "ֻ", "^\\d{11}$" }, + { "", "^\\d{4}-\\d{1,2}-\\d{1,2}$" }, + { "ʱ", "^\\d{1,2}:\\d{1,2}:\\d{1,2}$" }, + { "", "^([0-9]+|[0-9]{1,3}(,?[0-9]{3})*)(.[0-9]{1,5})?$" }, + { "ʱ", "^\\d{6}$" }, + { "(GB18030)", "^((\\u8140-\\ufefe)|(\\u8130-\\ufe39){2})+$" }, + }; + return m_regex_items[desp]; + } + + static bool is_ipv4 (std::string s) { + std::regex r (query_regex ("IPv4")); + return std::regex_match (s, r); + } + + static bool is_ipv6 (std::string s) { + std::regex r (query_regex ("IPv6")); + return std::regex_match (s, r); + } + + static bool is_domain (std::string s) { + std::regex r (query_regex ("")); + return std::regex_match (s, r); + } + + static bool is_url (std::string s) { + std::regex r (query_regex ("URL")); + return std::regex_match (s, r); + } +}; + +#endif //__TOOL_FORMATTING_HPP__ diff --git a/NetToolbox/tools/tool_Gdip.hpp b/NetToolbox/tools/tool_Gdip.hpp new file mode 100644 index 0000000..442128b --- /dev/null +++ b/NetToolbox/tools/tool_Gdip.hpp @@ -0,0 +1,158 @@ +#ifndef __TOOL_GDIP_HPP__ +#define __TOOL_GDIP_HPP__ + +#include +#include +#include + + + +class tool_Gdip { + static bool gdip_get_encoder_clsid (LPCWSTR format, CLSID* pClsid) { + UINT num = 0, size = 0; + Gdiplus::GetImageEncodersSize (&num, &size); + if (size == 0) + return false; + Gdiplus::ImageCodecInfo* pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc (size)); + Gdiplus::GetImageEncoders (num, size, pImageCodecInfo); + for (UINT i = 0; i < num; ++i) { + if (_wcsicmp (pImageCodecInfo[i].MimeType, format) == 0) { + *pClsid = pImageCodecInfo[i].Clsid; + free (pImageCodecInfo); + return true; + } + } + free (pImageCodecInfo); + return false; + } + +public: + static bool gdip_save (Gdiplus::Image *img, LPCWSTR lpFile) { + std::wstring sFile = lpFile; + size_t p = sFile.rfind (L'.'); + if (-1 == p) + return false; + std::wstring sExt = &sFile[p]; + LPCWSTR lpFormat = nullptr; + if (sExt == L".jpg") lpFormat = L"image/jpeg"; + else if (sExt == L".png") lpFormat = L"image/png"; + else if (sExt == L".bmp") lpFormat = L"image/bmp"; + else if (sExt == L".gif") lpFormat = L"image/gif"; + else return false; + + CLSID clsid; + if (!gdip_get_encoder_clsid (lpFormat, &clsid)) + return false; + return (Gdiplus::Status::Ok == img->Save (lpFile, &clsid)); + } + + static bool gdip_save_animation (std::vector &vimg, LPCWSTR lpFile, size_t delay = 50) { + size_t szImg = vimg.size (), i; + if (0 == szImg) + return false; + else if (1 == szImg) + return gdip_save (vimg[0], lpFile); + + int width = vimg[0]->GetWidth (), height = vimg[0]->GetHeight (); + Gdiplus::Image *img = vimg[0]->Clone (); + + Gdiplus::PropertyItem propertyItem; + short sRepeatNum = 0; + propertyItem.id = PropertyTagLoopCount; + propertyItem.length = sizeof (short); + propertyItem.type = PropertyTagTypeShort; + propertyItem.value = &sRepeatNum; + img->SetPropertyItem (&propertyItem); + ULONG *plValue = new ULONG[szImg]; + size_t delayVal = (size_t) (delay / 10.0 + 0.5); + for (i = 0; i < szImg; ++i) + plValue[i] = (ULONG) delayVal; + propertyItem.id = PropertyTagFrameDelay; + propertyItem.length = sizeof (ULONG) * (ULONG) szImg; + propertyItem.type = PropertyTagTypeLong; + propertyItem.value = plValue; + img->SetPropertyItem (&propertyItem); + delete[] plValue; + + CLSID clsid; + Gdiplus::EncoderParameters encoderParams; + gdip_get_encoder_clsid (L"image/gif", &clsid); + encoderParams.Count = 1; + encoderParams.Parameter[0].Guid = Gdiplus::EncoderSaveFlag; + encoderParams.Parameter[0].NumberOfValues = 1; + encoderParams.Parameter[0].Type = Gdiplus::EncoderParameterValueTypeLong; + long firstValue = Gdiplus::EncoderValueMultiFrame; + encoderParams.Parameter[0].Value = &firstValue; + img->Save (lpFile, &clsid, &encoderParams); + firstValue = Gdiplus::EncoderValueFrameDimensionTime; + encoderParams.Parameter[0].Value = &firstValue; + for (i = 1; i < szImg; ++i) + img->SaveAdd (vimg[i], &encoderParams); + delete img; + return true; + } + + static Gdiplus::Bitmap* capture_screen () { + HDC hDC = ::GetDC (NULL); + int cx = ::GetDeviceCaps (hDC, DESKTOPHORZRES); + int cy = ::GetDeviceCaps (hDC, DESKTOPVERTRES); + ::ReleaseDC (NULL, hDC); + Gdiplus::Bitmap *bmp = new Gdiplus::Bitmap (cx, cy, PixelFormat24bppRGB); + Gdiplus::Graphics g (bmp); + HDC hDeskDC = ::GetDC (NULL); + HDC hGDC = g.GetHDC (); + ::BitBlt (hGDC, 0, 0, cx, cy, hDeskDC, 0, 0, SRCCOPY); + g.ReleaseHDC (hGDC); + ::ReleaseDC (NULL, hDeskDC); + return bmp; + } + + static HBITMAP capture_screen_gdi () { + HDC hDeskDC = ::GetDC (NULL);// ::GetWindowDC (NULL); + HDC hMemDc = ::CreateCompatibleDC (hDeskDC); + int cx = ::GetSystemMetrics (SM_CXSCREEN); + int cy = ::GetSystemMetrics (SM_CYSCREEN); + BITMAPINFO bi { { sizeof (BITMAPINFOHEADER), (LONG) cx, (LONG) cy, 1, 24, BI_RGB, (DWORD) cx * cy * 3, 5000, 5000, 0, 0 }, { 0 } }; + void *ptr = nullptr; + HBITMAP hBmp = ::CreateDIBSection (hMemDc, &bi, DIB_RGB_COLORS, &ptr, NULL, 0); + //HBITMAP hBmp = ::CreateCompatibleBitmap (hMemDc, cx, cy); + ::SelectObject (hMemDc, hBmp); + ::BitBlt (hMemDc, 0, 0, cx, cy, hDeskDC, 0, 0, SRCCOPY); + ::DeleteDC (hMemDc); + ::ReleaseDC (NULL, hDeskDC); + return hBmp; + } + + // ȡָͼƬתλͼ + static Gdiplus::Bitmap *cursor_to_bmp (HCURSOR hcur) { + //CURSORINFO cinfo { sizeof (CURSORINFO) }; + //::GetCursorInfo (&cinfo); + //HICON hIcon = cinfo.hCursor; + ICONINFO iinfo; + if (!::GetIconInfo ((HICON) hcur, &iinfo)) + return nullptr; + Gdiplus::Bitmap *b = Gdiplus::Bitmap::FromHBITMAP (iinfo.hbmColor, NULL); + //Gdiplus::Bitmap *bm = Gdiplus::Bitmap::FromHBITMAP (iinfo.hbmMask, NULL); + Gdiplus::Bitmap *bs = new Gdiplus::Bitmap (b->GetWidth (), b->GetHeight (), PixelFormat32bppARGB); + Gdiplus::Graphics g (bs); + HDC hDC = g.GetHDC (); + ::DrawIcon (hDC, 0, 0, (HICON) hcur); + g.ReleaseHDC (hDC); + //Gdiplus::Color c (0); + //for (UINT i = 0; i < b->GetWidth (); ++i) { + // for (UINT j = 0; j < b->GetHeight (); ++j) { + // bm->GetPixel (i, j, &c); + // Gdiplus::ARGB argb = c.GetValue (); + // if (!(c.GetValue () & 0xffffff)) { + // b->GetPixel (i, j, &c); + // bs->SetPixel (i, j, c); + // } + // } + //} + //delete bm; + delete b; + return bs; + } +}; + +#endif //__TOOL_GDIP_HPP__ diff --git a/NetToolbox/tools/tool_Gzip.hpp b/NetToolbox/tools/tool_Gzip.hpp new file mode 100644 index 0000000..b9ef0fb --- /dev/null +++ b/NetToolbox/tools/tool_Gzip.hpp @@ -0,0 +1,73 @@ +#ifndef __TOOL_GZIP_HPP__ +#define __TOOL_GZIP_HPP__ + +#include +#include +#include + +#define CHUNK 16384 +#define windowBits 15 +#define GZIP_ENCODING 16 + + + +class tool_Gzip { +public: + static std::string compress (std::string_view data, int level = 9) { + std::string ret = ""; + unsigned char out[CHUNK]; + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + if (deflateInit2 (&strm, level, Z_DEFLATED, windowBits | GZIP_ENCODING, 8, Z_DEFAULT_STRATEGY) != Z_OK) + return ret; + strm.next_in = (unsigned char*) data.data (); + strm.avail_in = (uInt) data.length (); + do { + strm.avail_out = CHUNK; + strm.next_out = out; + if (deflate (&strm, Z_FINISH) == Z_STREAM_ERROR) + return ret; + size_t have = CHUNK - strm.avail_out; + ret.append ((char*) out, have); + } while (strm.avail_out == 0); + if (deflateEnd (&strm) != Z_OK) + return ret; + return ret; + } + + static std::string decompress (std::string_view data) { + std::string ret = ""; + z_stream strm; + unsigned char out[CHUNK]; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + if (inflateInit2 (&strm, 16 + MAX_WBITS) != Z_OK) + return ret; + strm.avail_in = (uInt) data.length (); + strm.next_in = (unsigned char*) data.data (); + do { + strm.avail_out = CHUNK; + strm.next_out = out; + int iret = inflate (&strm, Z_NO_FLUSH); + switch (iret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd (&strm); + return ret; + } + size_t have = CHUNK - strm.avail_out; + ret.append ((char*) out, have); + } while (strm.avail_out == 0); + if (inflateEnd (&strm) != Z_OK) + return ret; + return ret; + } +}; + +#endif //__TOOL_GZIP_HPP__ diff --git a/NetToolbox/tools/tool_MakeAvi.hpp b/NetToolbox/tools/tool_MakeAvi.hpp new file mode 100644 index 0000000..85818dd --- /dev/null +++ b/NetToolbox/tools/tool_MakeAvi.hpp @@ -0,0 +1,118 @@ +#ifndef __TOOL_MAKEAVI_HPP__ +#define __TOOL_MAKEAVI_HPP__ + +//http://blog.sina.com.cn/s/blog_a2e5bcda01019bcw.html + +#include +#include +#ifndef UNICODE +using string_t = std::string; +using string_view_t = std::string_view; +#else +using string_t = std::wstring; +using string_view_t = std::wstring_view; +#endif + +#include +#include + +#include + + + +class tool_MakeAvi { +public: + tool_MakeAvi (string_view_t path): m_path (path) {} + + bool init (HBITMAP hBmp) { + ::AVIFileInit (); + if (::AVIFileOpen (&m_pfile, m_path.c_str (), OF_CREATE | OF_WRITE, nullptr) != AVIERR_OK) + return false; + ::GetObject (hBmp, sizeof (BITMAPINFOHEADER), &m_bih); + m_bih.biSize = sizeof (BITMAPINFOHEADER); + m_bih.biPlanes = 1; + m_bih.biCompression = BI_RGB; + m_bih.biBitCount = 24; + m_bih.biXPelsPerMeter = m_bih.biYPelsPerMeter = 3780; + m_bih.biSizeImage = m_bih.biWidth * m_bih.biHeight * m_bih.biBitCount / 8; + m_buf = new unsigned char[m_bih.biSizeImage]; + // + m_asi.fccType = streamtypeVIDEO; + //m_asi.fccType = ICTYPE_VIDEO; + //m_asi.fccHandler = mmioFOURCC ('D', 'I', 'B', ' '); + //m_asi.fccHandler = mmioFOURCC ('x', 'v', 'i', 'd'); + //m_asi.fccHandler = mmioFOURCC ('i', 'v', '4', '1'); + //m_asi.fccHandler = mmioFOURCC ('M', 'P', 'G', '4'); + //m_asi.fccHandler = mmioFOURCC ('X', '2', '6', '4'); + //m_asi.fccHandler = mmioFOURCC ('M', 'P', '4', '2'); + m_asi.fccHandler = mmioFOURCC ('M', 'S', 'V', 'C'); + m_asi.dwRate = 30; + m_asi.dwScale = 1; + m_asi.dwQuality = -1; + m_asi.dwSuggestedBufferSize = m_bih.biSizeImage; + m_asi.rcFrame = { 0, 0, m_bih.biWidth, m_bih.biHeight }; + lstrcpy (m_asi.szName, _T ("NT Video Stream")); + if (::AVIFileCreateStream (m_pfile, &m_pstm, &m_asi) != AVIERR_OK) { + DWORD d = ::GetLastError (); + return false; + } + // + AVICOMPRESSOPTIONS aco { 0 }; + aco.fccType = m_asi.fccType; + aco.fccHandler = m_asi.fccHandler; + aco.dwQuality = 10000; + aco.dwKeyFrameEvery = 30; + aco.dwFlags = AVICOMPRESSF_VALID | AVICOMPRESSF_KEYFRAMES; + aco.dwBytesPerSecond = 1000 / 30; + if (::AVIMakeCompressedStream (&m_pstm_cprs, m_pstm, &aco, nullptr) != AVIERR_OK) { + DWORD d = ::GetLastError (); + return false; + } + if (::AVIStreamSetFormat (m_pstm_cprs, 0, &m_bih, m_bih.biSize) != AVIERR_OK) { + DWORD d = ::GetLastError (); + return false; + } + return true; + } + + bool add_frame (HBITMAP hBmp) { + HDC hTmpDC = ::CreateCompatibleDC (NULL); + BITMAPINFO bi { { sizeof (BITMAPINFOHEADER) }, { 0 } }; + ::GetDIBits (hTmpDC, hBmp, 0, 0, nullptr, &bi, DIB_RGB_COLORS); + bi.bmiHeader = m_bih; + ::GetDIBits (hTmpDC, hBmp, 0, bi.bmiHeader.biHeight, m_buf, &bi, DIB_RGB_COLORS); + ::DeleteDC (hTmpDC); + return ::AVIStreamWrite (m_pstm_cprs, m_next_frame++, 1, m_buf, sizeof (m_buf), AVICOMPRESSF_KEYFRAMES, nullptr, nullptr) == AVIERR_OK; + } + + virtual ~tool_MakeAvi () { + if (m_buf) + delete[] m_buf; + if (m_astm) + ::AVIStreamRelease (m_astm); + if (m_pstm_cprs) + ::AVIStreamRelease (m_pstm_cprs); + if (m_pstm) + ::AVIStreamRelease (m_pstm); + if (m_pfile) + ::AVIFileRelease (m_pfile); + ::AVIFileExit (); + } + +private: + BITMAPINFOHEADER m_bih { sizeof (BITMAPINFOHEADER) }; + AVISTREAMINFO m_asi { 0 }; + string_t m_path = _T (""); + IAVIFile *m_pfile = nullptr; + //WAVEFORMATEX m_wfx { 0 }; + int m_period = 50; // ֡ʱ + IAVIStream *m_astm = nullptr; + IAVIStream *m_pstm = nullptr; + IAVIStream *m_pstm_cprs = nullptr; + unsigned long m_next_frame = 0; + unsigned char *m_buf = nullptr; +}; + + + +#endif //__TOOL_MAKEAVI_HPP__ diff --git a/NetToolbox/tools/tool_Mutex.hpp b/NetToolbox/tools/tool_Mutex.hpp new file mode 100644 index 0000000..d4c6e1c --- /dev/null +++ b/NetToolbox/tools/tool_Mutex.hpp @@ -0,0 +1,46 @@ +#ifndef __TOOL_MUTEX_HPP__ +#define __TOOL_MUTEX_HPP__ + +#include +#include + + + +class tool_Mutex { +public: + tool_Mutex (LPCTSTR lpName) { + m_hMutex = ::CreateMutex (nullptr, FALSE, lpName); + } + void lock () { + if (m_hMutex) + ::WaitForSingleObject (m_hMutex, INFINITE); + m_locked = true; + } + bool try_lock () { + if (!m_hMutex) + return false; + if (WAIT_TIMEOUT == ::WaitForSingleObject (m_hMutex, 0)) + return false; + m_locked = true; + return true; + } + void unlock () { + if (m_hMutex) + ::ReleaseMutex (m_hMutex); + m_locked = false; + } + ~tool_Mutex () { + if (m_hMutex) { + if (m_locked) + unlock (); + ::CloseHandle (m_hMutex); + m_hMutex = NULL; + } + } + +private: + HANDLE m_hMutex = NULL; + bool m_locked = false; +}; + +#endif //__TOOL_MUTEX_HPP__ diff --git a/NetToolbox/tools/tool_NetInfo.hpp b/NetToolbox/tools/tool_NetInfo.hpp new file mode 100644 index 0000000..914c5a5 --- /dev/null +++ b/NetToolbox/tools/tool_NetInfo.hpp @@ -0,0 +1,174 @@ +#ifndef __TOOL_NET_INFO_HPP__ +#define __TOOL_NET_INFO_HPP__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tool_String.hpp" +#include "tool_Process.hpp" +#include "tool_Utils.hpp" + + + +class tool_NetInfo { +public: + // ȡġַصַDHCPб + static std::vector> get_local_net () { + std::vector> ret; + ULONG size = 0; + GetAdaptersInfo (NULL, &size); + if (!size) return ret; + std::unique_ptr buff (new BYTE[size]); + if (!GetAdaptersInfo ((PIP_ADAPTER_INFO) buff.get (), &size)) { + PIP_ADAPTER_INFO pStruc = (PIP_ADAPTER_INFO) buff.get (); + for (; pStruc; pStruc = pStruc->Next) { + ret.push_back ({ pStruc->Description, pStruc->IpAddressList.IpAddress.String, pStruc->GatewayList.IpAddress.String, !!pStruc->DhcpEnabled }); + } + } + return ret; + } + + // ȡ + static std::tuple get_ip_segment () { + ULONG size = 0; + GetAdaptersInfo (NULL, &size); + if (!size) return { _T (""), _T ("") }; + std::unique_ptr buff (new BYTE[size]); + if (!GetAdaptersInfo ((PIP_ADAPTER_INFO) buff.get (), &size)) { + PIP_ADAPTER_INFO pStruc = (PIP_ADAPTER_INFO) buff.get (); + for (; pStruc; pStruc = pStruc->Next) { + IPAddr ipaddr = ::inet_addr (pStruc->IpAddressList.IpAddress.String), ipmask = ::inet_addr (pStruc->IpAddressList.IpMask.String); + size_t mask_bitnum = 0; + for (size_t i = 0; i < 32; ++i) + mask_bitnum += (ipmask & (1 << i)) ? 1 : 0; + if (((ipaddr & ~ipmask) >> mask_bitnum) > 1) + return { tool_Utils::format_ipv4 ((ipaddr & ipmask) | (1 << mask_bitnum)), tool_Utils::format_ipv4 (ipaddr | ~ipmask) }; + } + } + return { _T (""), _T ("") }; + } + + // ȡбǷipv4ʾصַض˿ڣԶ̵ַԶ̶˿ڣǰ״̬PIDƣ·ʾ + static std::vector> get_connections () { + static std::map mtcp_conn_state { + //{ MIB_TCP_STATE_CLOSED, _T ("TCPѹر") }, + { MIB_TCP_STATE_LISTEN, _T ("TCPڼ") }, + //{ MIB_TCP_STATE_SYN_SENT, _T ("TCP") }, + //{ MIB_TCP_STATE_SYN_RCVD, _T ("TCP") }, + { MIB_TCP_STATE_ESTAB, _T ("TCP") }, + //{ MIB_TCP_STATE_FIN_WAIT1, _T ("TCPϿ") }, + //{ MIB_TCP_STATE_FIN_WAIT2, _T ("TCPϿ") }, + //{ MIB_TCP_STATE_CLOSE_WAIT, _T ("TCPϿ") }, + //{ MIB_TCP_STATE_CLOSING, _T ("TCPϿ") }, + //{ MIB_TCP_STATE_LAST_ACK, _T ("TCPѶϿ") }, + //{ MIB_TCP_STATE_TIME_WAIT, _T ("TCPѶϿ") }, + //{ MIB_TCP_STATE_DELETE_TCB, _T ("TCPɾTCB¼") }, + }; + std::map mprocesses = tool_Process::get_processes (); + auto size_max = [](size_t p1, size_t p2) { + if (p1 == std::string::npos) { + return p2; + } else if (p2 == std::string::npos) { + return p1; + } else { + return (p1 >= p2 ? p1 : p2); + } + }; + auto ipv4_to_str = [] (DWORD &addr) -> string_t { + unsigned char *pch = (unsigned char*) &addr; + return tool_StringT::format (_T ("%d.%d.%d.%d"), pch[0], pch[1], pch[2], pch[3]); + }; + auto ipv6_to_str = [] (UCHAR *addr, DWORD scope_id) -> string_t { + TCHAR tBuf[64] = { 0 }; + ::InetNtopW (AF_INET6, &addr, tBuf, sizeof (tBuf) / sizeof (tBuf[0])); + return (scope_id ? tool_StringT::format (_T ("%s%%%d"), tBuf, scope_id) : string_t (tBuf)); + }; + + std::vector> vconn; + // tcp ipv4 + ULONG size = 0; + ::GetTcpTable2 (nullptr, &size, true); + unsigned char *buf = new unsigned char[size]; + PMIB_TCPTABLE2 ptcp4 = (PMIB_TCPTABLE2) buf; + if (NO_ERROR == ::GetTcpTable2 (ptcp4, &size, true)) { + for (size_t i = 0; i < ptcp4->dwNumEntries; ++i) { + string_t local_ip = ipv4_to_str (ptcp4->table[i].dwLocalAddr); + uint16_t local_port = (uint16_t) ptcp4->table[i].dwLocalPort; + string_t remote_ip = ipv4_to_str (ptcp4->table[i].dwRemoteAddr); + uint16_t remote_port = (uint16_t) ptcp4->table[i].dwRemotePort; + string_t tcp_conn_state = mtcp_conn_state[ptcp4->table[i].dwState]; + DWORD process_id = ptcp4->table[i].dwOwningPid; + string_t exe_path = mprocesses[process_id]; + if (tcp_conn_state == _T ("")) + continue; + vconn.push_back ({ true, local_ip, local_port, remote_ip, remote_port, tcp_conn_state, process_id, exe_path }); + } + } + delete[] buf; + + // tcp ipv6 + size = 0; + ::GetTcp6Table2 (nullptr, &size, true); + buf = new unsigned char[size]; + PMIB_TCP6TABLE2 ptcp6 = (PMIB_TCP6TABLE2) buf; + if (NO_ERROR == ::GetTcp6Table2 (ptcp6, &size, true)) { + for (size_t i = 0; i < ptcp6->dwNumEntries; ++i) { + string_t local_ip = ipv6_to_str (ptcp6->table[i].LocalAddr.u.Byte, ptcp6->table[i].dwLocalScopeId); + uint16_t local_port = (uint16_t) ptcp6->table[i].dwLocalPort; + string_t remote_ip = ipv6_to_str (ptcp6->table[i].RemoteAddr.u.Byte, ptcp6->table[i].dwRemoteScopeId); + uint16_t remote_port = (uint16_t) ptcp6->table[i].dwRemotePort; + string_t tcp_conn_state = mtcp_conn_state[ptcp6->table[i].State]; + DWORD process_id = ptcp6->table[i].dwOwningPid; + string_t exe_path = mprocesses[process_id]; + if (tcp_conn_state == _T ("")) + continue; + vconn.push_back ({ false, local_ip, local_port, remote_ip, remote_port, tcp_conn_state, process_id, exe_path }); + } + } + delete[] buf; + + // udp ipv4 + size = 0; + ::GetExtendedUdpTable (nullptr, &size, true, AF_INET, UDP_TABLE_OWNER_PID, 0); + buf = new unsigned char[size]; + PMIB_UDPTABLE_OWNER_PID pudp4 = (PMIB_UDPTABLE_OWNER_PID) buf; + if (NO_ERROR == ::GetExtendedUdpTable (pudp4, &size, true, AF_INET, UDP_TABLE_OWNER_PID, 0)) { + for (size_t i = 0; i < pudp4->dwNumEntries; ++i) { + string_t local_ip = ipv4_to_str (pudp4->table[i].dwLocalAddr); + uint16_t local_port = (uint16_t) pudp4->table[i].dwLocalPort; + DWORD process_id = pudp4->table[i].dwOwningPid; + string_t exe_path = mprocesses[process_id]; + vconn.push_back ({ true, local_ip, local_port, _T ("*"), 0, _T ("UDP"), process_id, exe_path }); + } + } + delete[] buf; + + // udp ipv6 + size = 0; + ::GetExtendedUdpTable (nullptr, &size, true, AF_INET6, UDP_TABLE_OWNER_PID, 0); + buf = new unsigned char[size]; + PMIB_UDP6TABLE_OWNER_PID pudp6 = (PMIB_UDP6TABLE_OWNER_PID) buf; + if (NO_ERROR == ::GetExtendedUdpTable (pudp6, &size, true, AF_INET6, UDP_TABLE_OWNER_PID, 0)) { + for (size_t i = 0; i < pudp6->dwNumEntries; ++i) { + string_t local_ip = ipv6_to_str (pudp6->table[i].ucLocalAddr, pudp6->table[i].dwLocalScopeId); + uint16_t local_port = (uint16_t) pudp6->table[i].dwLocalPort; + DWORD process_id = pudp6->table[i].dwOwningPid; + string_t exe_path = mprocesses[process_id]; + vconn.push_back ({ true, local_ip, local_port, _T ("*"), 0, _T ("UDP"), process_id, exe_path }); + } + } + delete[] buf; + return vconn; + } +}; + +#endif //__TOOL_NET_INFO_HPP__ diff --git a/NetToolbox/tools/tool_PE.hpp b/NetToolbox/tools/tool_PE.hpp new file mode 100644 index 0000000..8065a80 --- /dev/null +++ b/NetToolbox/tools/tool_PE.hpp @@ -0,0 +1,174 @@ +#ifndef __TOOL_PE_HPP__ +#define __TOOL_PE_HPP__ + +#include +#include +#include +#include +#include +#include + +#include "tool_String.hpp" + + + +class tool_PE { +public: + static bool read_import_export (LPCTSTR file_name, std::vector &vexport, std::vector>>> &vimport) { + vexport.clear (); + vimport.clear (); + HANDLE hFile = ::CreateFile (file_name, GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + return false; + } + DWORD dwsz_high = 0; + DWORD dwsz = ::GetFileSize (hFile, &dwsz_high); + int64_t file_length = dwsz | (((int64_t) dwsz_high) * 0x100000000); + HANDLE hFileMap = ::CreateFileMapping (hFile, nullptr, PAGE_READONLY, 0, 0, nullptr); + if (hFileMap == NULL) { + ::CloseHandle (hFile); + return false; + } + LPVOID mod_base = ::MapViewOfFile (hFileMap, FILE_MAP_READ, 0, 0, 0); + if (mod_base == NULL) { + ::CloseHandle (hFileMap); + ::CloseHandle (hFile); + return false; + } + PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER) mod_base; + if (file_length < (int64_t) sizeof (IMAGE_DOS_HEADER)) { + ::CloseHandle (hFileMap); + ::CloseHandle (hFile); + return false; + } + if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { + ::CloseHandle (hFileMap); + ::CloseHandle (hFile); + return false; + } + PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS) ((PBYTE) mod_base + pDosHeader->e_lfanew); + if ((int64_t) pDosHeader->e_lfanew > file_length - (int64_t) sizeof (IMAGE_NT_HEADERS)) { + ::CloseHandle (hFileMap); + ::CloseHandle (hFile); + return false; + } + if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) { + ::CloseHandle (hFileMap); + ::CloseHandle (hFile); + return false; + } + PIMAGE_OPTIONAL_HEADER pOptHeader = (PIMAGE_OPTIONAL_HEADER) ((PBYTE) mod_base + pDosHeader->e_lfanew + 24); + // + DWORD export_virtual_addr = pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (export_virtual_addr) { + PIMAGE_EXPORT_DIRECTORY pExportDesc = (PIMAGE_EXPORT_DIRECTORY) ::ImageRvaToVa (pNtHeader, mod_base, export_virtual_addr, nullptr); + PDWORD nameAddr = (PDWORD) ::ImageRvaToVa (pNtHeader, mod_base, pExportDesc->AddressOfNames, nullptr); + for (DWORD i = 0; i < pExportDesc->NumberOfNames && nameAddr; i++) { + vexport.push_back ((LPCSTR) ::ImageRvaToVa (pNtHeader, mod_base, (DWORD) nameAddr[i], nullptr)); + } + } + // + DWORD import_virtual_addr = pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + if (import_virtual_addr) { + PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR) ::ImageRvaToVa (pNtHeader, mod_base, import_virtual_addr, nullptr); + IMAGE_IMPORT_DESCRIPTOR null_iid = { 0 }; + IMAGE_THUNK_DATA null_thunk = { 0 }; + for (DWORD i = 0; memcmp (pImportTable + i, &null_iid, sizeof (null_iid)) != 0; i++) { + std::string dll_name = (LPCSTR) ::ImageRvaToVa (pNtHeader, mod_base, pImportTable[i].Name, nullptr); + std::vector> dll_vimport; + PIMAGE_THUNK_DATA32 pThunk = (PIMAGE_THUNK_DATA32) ::ImageRvaToVa (pNtHeader, mod_base, pImportTable[i].OriginalFirstThunk, nullptr); + for (DWORD j = 0; memcmp (pThunk + j, &null_thunk, sizeof (null_thunk)) != 0; j++) { + if (pThunk[j].u1.AddressOfData & IMAGE_ORDINAL_FLAG32) { + dll_vimport.push_back ({ (uint16_t) (pThunk[j].u1.AddressOfData & 0xffff), "" }); + } else { + PIMAGE_IMPORT_BY_NAME pFuncName = (PIMAGE_IMPORT_BY_NAME) ::ImageRvaToVa (pNtHeader, mod_base, pThunk[j].u1.AddressOfData, nullptr); + if (pFuncName) { + dll_vimport.push_back ({ (uint16_t) pFuncName->Hint, pFuncName->Name }); + } + } + } + vimport.push_back ({ dll_name, dll_vimport }); + } + } + CloseHandle (hFileMap); + CloseHandle (hFile); + return true; + } + + static string_t extract_all_resource (LPCTSTR file) { + HMODULE hModule = ::LoadLibrary (file); + if (hModule) { + string_t folder = tool_StringT::format (_T ("%s_files"), file); + ::CreateDirectory (folder.c_str (), NULL); + ::EnumResourceTypes (hModule, enum_res_type_proc, (LONG_PTR) folder.c_str ()/*, RESOURCE_ENUM_MUI | RESOURCE_ENUM_LN, NULL*/); + ::FreeLibrary (hModule); + return folder; + } + return _T (""); + } + + static std::tuple get_version (LPCSTR file) { + DWORD dw_handle = 0; + DWORD size = ::GetFileVersionInfoSizeA (file, &dw_handle); + char *buf = new char[size]; + ::GetFileVersionInfoA (file, dw_handle, size, buf); + VS_FIXEDFILEINFO *pinfo = nullptr; + UINT ulen = 0; + ::VerQueryValueA (buf, "\\", (LPVOID*) &pinfo, &ulen); + DWORD verMS = pinfo->dwFileVersionMS; + DWORD verLS = pinfo->dwFileVersionLS; + delete buf; + return { HIWORD (verMS), LOWORD (verMS), HIWORD (verLS), LOWORD (verLS) }; + } + +protected: + static BOOL CALLBACK enum_res_name_proc (HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG_PTR lParam) { + static auto get_file_name = [] (LPCTSTR lpType, LPTSTR lpName) -> string_t { + static std::map mtype = { { RT_CURSOR, _T ("RT_CURSOR") }, { RT_BITMAP, _T ("RT_BITMAP") }, { RT_ICON, _T ("RT_ICON") }, { RT_MENU, _T ("RT_MENU") }, { RT_DIALOG, _T ("RT_DIALOG") }, { RT_STRING, _T ("RT_STRING") }, { RT_FONTDIR, _T ("RT_FONTDIR") }, { RT_FONT, _T ("RT_FONT") }, { RT_ACCELERATOR, _T ("RT_ACCELERATOR") }, { RT_RCDATA, _T ("RT_RCDATA") }, { RT_MESSAGETABLE, _T ("RT_MESSAGETABLE") }, { RT_GROUP_CURSOR, _T ("RT_GROUP_CURSOR") }, { RT_GROUP_ICON, _T ("RT_GROUP_ICON") }, { RT_VERSION, _T ("RT_VERSION") }, { RT_DLGINCLUDE, _T ("RT_DLGINCLUDE") }, { RT_PLUGPLAY, _T ("RT_PLUGPLAY") }, { RT_VXD, _T ("RT_VXD") }, { RT_ANICURSOR, _T ("RT_ANICURSOR") }, { RT_ANIICON, _T ("RT_ANIICON") }, { RT_HTML, _T ("RT_HTML") }, { RT_MANIFEST, _T ("RT_MANIFEST") } }; + static std::map mext = { { RT_CURSOR, _T ("cur") }, { RT_BITMAP, _T ("bmp") }, { RT_ICON, _T ("ico") }, { RT_MENU, _T ("bin") }, { RT_DIALOG, _T ("bin") }, { RT_STRING, _T ("bin") }, { RT_FONTDIR, _T ("bin") }, { RT_FONT, _T ("font") }, { RT_ACCELERATOR, _T ("bin") }, { RT_RCDATA, _T ("bin") }, { RT_MESSAGETABLE, _T ("bin") }, { RT_GROUP_CURSOR, _T ("cur") }, { RT_GROUP_ICON, _T ("ico") }, { RT_VERSION, _T ("bin") }, { RT_DLGINCLUDE, _T ("bin") }, { RT_PLUGPLAY, _T ("bin") }, { RT_VXD, _T ("vxd") }, { RT_ANICURSOR, _T ("cur") }, { RT_ANIICON, _T ("ico") }, { RT_HTML, _T ("html") }, { RT_MANIFEST, _T ("xml") } }; + string_t type, ext = _T ("bin"); + DWORD res_id = (DWORD) lpName; + if (mtype.find (lpType) != mtype.end ()) { + type = mtype[lpType]; + ext = mext[lpType]; + } else if ((DWORD) lpType < 100) { + type = tool_StringT::format (_T ("%d"), (DWORD) lpType); + } else { + type = lpType; + ext = _T (""); + for (TCHAR c : type) + ext += (c >= 'A' && c <= 'Z' ? c - 'A' + 'a' : c); + } + return tool_StringT::format (_T ("%s_%d.%s"), type.c_str (), res_id, ext.c_str ()); + }; + + //std::cout << " " << (size_t) lpName << '\n'; + HRSRC hRsrc = ::FindResource (hModule, lpName, lpType); + if (hRsrc) { + DWORD dwsz = ::SizeofResource (hModule, hRsrc), written = 0; + HGLOBAL hGlobal = ::LoadResource (hModule, hRsrc); + if (hGlobal) { + LPVOID ptr = ::LockResource (hGlobal); + if (ptr) { + LPCTSTR path = (LPCTSTR) lParam; + string_t file = tool_StringT::format (_T ("%s\\%s"), path, get_file_name (lpType, lpName).c_str ()); + HANDLE hFile = ::CreateFile (file.c_str (), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + ::WriteFile (hFile, ptr, dwsz, &written, NULL); + ::CloseHandle (hFile); + } + UnlockResource (hGlobal); + } + ::FreeResource (hGlobal); + } + } + return TRUE; + } + + static BOOL CALLBACK enum_res_type_proc (HMODULE hModule, LPTSTR lpType, LONG_PTR lParam) { + ::EnumResourceNames (hModule, lpType, enum_res_name_proc, lParam/*, RESOURCE_ENUM_MUI | RESOURCE_ENUM_LN, NULL*/); + return TRUE; + } +}; + +#endif //__TOOL_PE_HPP__ diff --git a/NetToolbox/tools/tool_Path.hpp b/NetToolbox/tools/tool_Path.hpp new file mode 100644 index 0000000..17e4a3f --- /dev/null +++ b/NetToolbox/tools/tool_Path.hpp @@ -0,0 +1,107 @@ +#ifndef __TOOL_PATH_HPP__ +#define __TOOL_PATH_HPP__ + +#include +#include +#include +#include +#include + +#include + +#include "tool_String.hpp" +#include "tool_Encoding.hpp" +#include "tool_Process.hpp" + + + +class tool_Path { +public: + static std::vector &get_args () { return get_obj ().m_args; } + static string_t get_exe_path () { return get_obj ().m_exe_path; } + static string_t get_exe_name () { return get_obj ().m_exe_name; } + static void show_path (string_t path) { + tool_StringT::replace (path, _T ('/'), _T ('\\')); + string_t cmd = tool_StringT::format (_T ("/select,\"%s\""), path.c_str ()); + //system (tool_Encoding::get_gb18030 (cmd).c_str ()); + ::ShellExecute (NULL, _T ("open"), _T ("explorer.exe"), cmd.c_str (), nullptr, SW_SHOW); + } + + static bool file_exist (string_view_t file) { + WIN32_FIND_DATA wfd; + HANDLE hFind = ::FindFirstFile (file.data (), &wfd); + bool bExist = (hFind != INVALID_HANDLE_VALUE); + if (bExist) + ::FindClose (hFind); + return bExist; + } + static bool file_existA (std::string_view file) { + return file_exist (tool_Encoding::get_T (file)); + } + static bool file_existW (std::wstring_view file) { + return file_exist (tool_Encoding::get_T (file)); + } + + static std::string get_file_md5 (LPCTSTR path) { + HANDLE hFile = ::CreateFile (path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + return ""; + DWORD dwsz_high = 0; + DWORD dwsz = ::GetFileSize (hFile, &dwsz_high); + ::SetFilePointer (hFile, 0, nullptr, FILE_BEGIN); + int64_t file_length = dwsz | (((int64_t) dwsz_high) * 0x100000000); + constexpr size_t sz_1M = 1024 * 1024; + int8_t *buf = new int8_t[sz_1M]; + size_t i, block_count = (size_t) (file_length / sz_1M); + size_t last_size = (size_t) (file_length - (sz_1M * (int64_t) block_count)); + // + // ļHash + // + MD5_CTX _md5; + ::MD5_Init (&_md5); + // + for (i = 0; i < block_count; ++i) { + ::ReadFile (hFile, buf, sz_1M, &dwsz, nullptr); + ::MD5_Update (&_md5, buf, sz_1M); + } + if (last_size > 0) { + ::ReadFile (hFile, buf, (DWORD) last_size, &dwsz, nullptr); + ::MD5_Update (&_md5, buf, last_size); + } + // + unsigned char buf_md5[16] = { 0 }; + ::MD5_Final (buf_md5, &_md5); + // + std::string str_md5 = ""; + for (i = 0; i < 64; ++i) { + if (i < sizeof (buf_md5)) str_md5 += tool_StringA::byte_to_str (buf_md5[i]); + } + // + delete[] buf; + ::CloseHandle (hFile); + return str_md5; + } + +protected: + tool_Path () { + auto cmd_line = ::GetCommandLineW (); + int _argc = 0; + LPWSTR *_argv = ::CommandLineToArgvW (cmd_line, &_argc); + for (int i = 0; i < _argc; ++i) + m_args.push_back (tool_Encoding::get_T (_argv[i])); + tool_StringT::replace (m_args[0], _T ('/'), _T ('\\')); + size_t p = m_args[0].rfind (_T ('\\')) + 1; + m_exe_path = m_args[0].substr (0, p); + m_exe_name = m_args[0].substr (p); + } + static tool_Path &get_obj () { + static tool_Path s_obj; + return s_obj; + } + + std::vector m_args; + string_t m_exe_path; + string_t m_exe_name; +}; + +#endif //__TOOL_PATH_HPP__ diff --git a/NetToolbox/tools/tool_Priv.hpp b/NetToolbox/tools/tool_Priv.hpp new file mode 100644 index 0000000..0b56b2e --- /dev/null +++ b/NetToolbox/tools/tool_Priv.hpp @@ -0,0 +1,39 @@ +#ifndef __TOOL_PRIV_HPP__ +#define __TOOL_PRIV_HPP__ + +#include +#include + +#include "tool_String.hpp" +#include "tool_Path.hpp" + + + +class tool_Priv { +public: + static bool is_admin () { + return !!::IsUserAnAdmin (); + } + + static bool adjust_restart (size_t sel1, size_t sel2) { + if (IDOK != ::MessageBox (NULL, _T ("Ȩ޲㣬ǷԹԱȨ´򿪳"), _T ("ʾ"), MB_ICONQUESTION | MB_OKCANCEL)) + return false; + string_t str_param = tool_StringT::format (_T ("-jump %d,%d"), sel1, sel2); + return (size_t) ::ShellExecute (NULL, _T ("runas"), tool_Path::get_args ()[0].c_str (), str_param.c_str (), _T (""), SW_SHOWNORMAL) > 32; + } + + static bool adjust_debug () { + HANDLE hToken = NULL; + if (!::OpenProcessToken (::GetCurrentProcess (), TOKEN_ADJUST_PRIVILEGES, &hToken)) + return false; + TOKEN_PRIVILEGES tp { 1 }; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + bool bRet = false; + if (::LookupPrivilegeValue (nullptr, SE_DEBUG_NAME, &tp.Privileges[0].Luid)) + bRet = !!::AdjustTokenPrivileges (hToken, FALSE, &tp, sizeof (TOKEN_PRIVILEGES), nullptr, nullptr); + ::CloseHandle (hToken); + return bRet; + } +}; + +#endif //__TOOL_PRIV_HPP__ diff --git a/NetToolbox/tools/tool_Process.hpp b/NetToolbox/tools/tool_Process.hpp new file mode 100644 index 0000000..159806d --- /dev/null +++ b/NetToolbox/tools/tool_Process.hpp @@ -0,0 +1,100 @@ +#ifndef __TOOL_PROCESS_HPP__ +#define __TOOL_PROCESS_HPP__ + +#include +#include +#include +#include +#include + +#include "tool_String.hpp" +#include "tool_Utils.hpp" + + + +class tool_Process { +public: + static std::map get_processes () { + HANDLE hSnapShot = ::CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0); + PROCESSENTRY32 pe32 { sizeof (PROCESSENTRY32) }; + std::map m; + if (::Process32First (hSnapShot, &pe32)) { + do { + m[pe32.th32ProcessID] = pe32.szExeFile; + } while (::Process32Next (hSnapShot, &pe32)); + } + ::CloseHandle (hSnapShot); + return m; + } + + static void shell_exec (string_t url) { + ::ShellExecute (NULL, _T ("open"), url.c_str (), nullptr, nullptr, SW_SHOW); + } + + static bool process_exist (string_t file) { + HANDLE hSnapShot = ::CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0); + PROCESSENTRY32 pe32 { sizeof (PROCESSENTRY32) }; + std::map m; + if (::Process32First (hSnapShot, &pe32)) { + do { + string_t _path = pe32.szExeFile; + tool_StringT::replace (_path, _T ('/'), _T ('\\')); + size_t p = _path.rfind (_T ('\\')); + if (_path.substr (p + 1) == file) { + ::CloseHandle (hSnapShot); + return true; + } + } while (::Process32Next (hSnapShot, &pe32)); + } + ::CloseHandle (hSnapShot); + return false; + } + + static bool create_process (string_view_t file) { + STARTUPINFO si = { sizeof (STARTUPINFO) }; + PROCESS_INFORMATION pi = { 0 }; + bool bRet = !!::CreateProcess (file.data (), nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi); + if (bRet) { + CloseHandle (pi.hThread); + CloseHandle (pi.hProcess); + } else { + string_t err_info = tool_StringT::format (_T ("ʧܣ%s"), tool_Utils::get_error_info (::GetLastError ()).c_str ()); + ::MessageBox (NULL, err_info.c_str (), _T ("ʾ"), MB_ICONHAND); + } + return bRet; + } + + static bool kill (DWORD pid) { + HANDLE hProcess = ::OpenProcess (PROCESS_TERMINATE, FALSE, pid); + if (!hProcess) + return false; + bool bRet = !!::TerminateProcess (hProcess, 0); + ::CloseHandle (hProcess); + return bRet; + } + + static std::tuple get_path (DWORD pid) { + HANDLE hProcess = ::OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + if (!hProcess) { + DWORD d = ::GetLastError (); + return { d, _T ("") }; + } + HMODULE hModule = NULL; + DWORD need; + if (!::EnumProcessModulesEx (hProcess, &hModule, sizeof (HMODULE), &need, LIST_MODULES_ALL)) { + DWORD d = ::GetLastError (); + ::CloseHandle (hProcess); + return { d, _T ("") }; + } + TCHAR tBuf[2048] = { 0 }; + if (!::K32GetModuleFileNameExW/*GetModuleFileNameEx*/ (hProcess, hModule, tBuf, sizeof (tBuf) / sizeof (TCHAR))) { + DWORD d = ::GetLastError (); + ::CloseHandle (hProcess); + return { d, _T ("") }; + } + ::CloseHandle (hProcess); + return { 0, tBuf }; + } +}; + +#endif //__TOOL_PROCESS_HPP__ diff --git a/NetToolbox/tools/tool_Register.hpp b/NetToolbox/tools/tool_Register.hpp new file mode 100644 index 0000000..760ef29 --- /dev/null +++ b/NetToolbox/tools/tool_Register.hpp @@ -0,0 +1,194 @@ +#ifndef __TOOL_REGISTER_HPP__ +#define __TOOL_REGISTER_HPP__ + +#include +#include +#include +#include +#include + + + +class tool_Register { + tool_Register () = delete; + +public: + static bool set_path (std::wstring path, BYTE *data, DWORD data_len) { + HKEY main_key = parse_path (path); + return (ERROR_SUCCESS == ::RegSetValueExW (main_key, path.c_str (), 0, REG_BINARY, data, data_len)); + } + static bool set_path (std::wstring path, DWORD data) { + HKEY main_key = parse_path (path); + return (ERROR_SUCCESS == ::RegSetValueExW (main_key, path.c_str (), 0, REG_DWORD, (BYTE*) &data, sizeof (data))); + } + static bool set_path (std::wstring path, std::wstring data, bool expand = false) { + HKEY main_key = parse_path (path); + return (ERROR_SUCCESS == ::RegSetValueExW (main_key, path.c_str (), 0, (expand ? REG_EXPAND_SZ : REG_SZ), (BYTE*) &data[0], (DWORD) data.size ())); + } + static bool set_path (std::wstring path, std::vector data) { + HKEY main_key = parse_path (path); + std::wstring s = make_multi_sz (data); + return (ERROR_SUCCESS == ::RegSetValueExW (main_key, path.c_str (), 0, REG_MULTI_SZ, (BYTE*) &s[0], (DWORD) s.size ())); + } + + static bool set_key (std::wstring path, std::wstring key_name, BYTE *data, DWORD data_len) { + HKEY hKey = parse_path (path); + if (ERROR_SUCCESS != ::RegOpenKeyExW (hKey, path.c_str (), 0, KEY_WRITE, &hKey)) return false; + bool bRet = (ERROR_SUCCESS == ::RegSetValueExW (hKey, key_name.c_str (), 0, REG_BINARY, data, data_len)); + ::RegCloseKey (hKey); + return bRet; + } + static bool set_key (std::wstring path, std::wstring key_name, DWORD data) { + HKEY hKey = parse_path (path); + if (ERROR_SUCCESS != ::RegOpenKeyExW (hKey, path.c_str (), 0, KEY_WRITE, &hKey)) return false; + bool bRet = (ERROR_SUCCESS == ::RegSetValueExW (hKey, key_name.c_str (), 0, REG_BINARY, (BYTE*) &data, sizeof (data))); + ::RegCloseKey (hKey); + return bRet; + } + static bool set_key (std::wstring path, std::wstring key_name, std::wstring data, bool expand = false) { + HKEY hKey = parse_path (path); + if (ERROR_SUCCESS != ::RegOpenKeyExW (hKey, path.c_str (), 0, KEY_WRITE, &hKey)) return false; + bool bRet = (ERROR_SUCCESS == ::RegSetValueExW (hKey, key_name.c_str (), 0, (expand ? REG_EXPAND_SZ : REG_SZ), (BYTE*) &data[0], (DWORD) data.size ())); + ::RegCloseKey (hKey); + return bRet; + } + static bool set_key (std::wstring path, std::wstring key_name, std::vector data) { + HKEY hKey = parse_path (path); + if (ERROR_SUCCESS != ::RegOpenKeyExW (hKey, path.c_str (), 0, KEY_WRITE, &hKey)) return false; + std::wstring s = make_multi_sz (data); + bool bRet = (ERROR_SUCCESS == ::RegSetValueExW (hKey, key_name.c_str (), 0, REG_MULTI_SZ, (BYTE*) &s[0], (DWORD) s.size ())); + ::RegCloseKey (hKey); + return bRet; + } + + static bool get_path_value (std::wstring path, BYTE *&data, DWORD &data_len) { + HKEY main_key = parse_path (path); + return (ERROR_SUCCESS == ::RegQueryValueExW (main_key, path.c_str (), nullptr, nullptr, data, &data_len)); + } + static bool get_path_value (std::wstring path, DWORD &data) { + HKEY main_key = parse_path (path); + DWORD data_size = sizeof (data); + return (ERROR_SUCCESS == ::RegQueryValueExW (main_key, path.c_str (), nullptr, nullptr, (BYTE*) &data, &data_size)); + } + static bool get_path_value (std::wstring path, std::wstring &data) { + HKEY main_key = parse_path (path); + std::wstring _data = L""; + _data.reserve (4096); + DWORD sz = (DWORD) 4096 * sizeof (wchar_t); + bool bRet = (ERROR_SUCCESS == ::RegQueryValueExW (main_key, path.c_str (), nullptr, nullptr, (BYTE*) &_data[0], &sz)); + data = std::wstring (_data.c_str (), sz / 2); + return bRet; + } + static bool get_path_value (std::wstring path, std::vector &data) { + HKEY main_key = parse_path (path); + std::wstring _data = L""; + _data.reserve (4096); + DWORD sz = (DWORD) 4096 * sizeof (wchar_t); + bool bRet = (ERROR_SUCCESS == ::RegQueryValueExW (main_key, path.c_str (), nullptr, nullptr, (BYTE*) &_data[0], &sz)); + data.clear (); + wchar_t *p1 = &_data[0]; + while (*p1) { + int len = lstrlenW (p1); + data.push_back (std::wstring (p1, len)); + p1 += len; + } + return bRet; + } + + static bool get_key_value (std::wstring path, std::wstring key_name, BYTE *&data, DWORD &data_len) { + HKEY main_key = parse_path (path), sub_key = NULL; + if (ERROR_SUCCESS != ::RegOpenKeyExW (main_key, path.c_str (), 0, KEY_READ, &sub_key)) return false; + bool bRet = (ERROR_SUCCESS == ::RegQueryValueExW (sub_key, key_name.c_str (), nullptr, nullptr, data, &data_len)); + ::RegCloseKey (sub_key); + return bRet; + } + static bool get_key_value (std::wstring path, std::wstring key_name, DWORD &data) { + HKEY main_key = parse_path (path), sub_key = NULL; + DWORD data_size = sizeof (data); + if (ERROR_SUCCESS != ::RegOpenKeyExW (main_key, path.c_str (), 0, KEY_READ, &sub_key)) return false; + bool bRet = (ERROR_SUCCESS == ::RegQueryValueExW (sub_key, key_name.c_str (), nullptr, nullptr, (BYTE*) &data, &data_size)); + ::RegCloseKey (sub_key); + return bRet; + } + static bool get_key_value (std::wstring path, std::wstring key_name, std::wstring &data) { + HKEY main_key = parse_path (path), sub_key = NULL; + std::wstring _data = L""; + _data.reserve (4096); + DWORD sz = (DWORD) 4096 * sizeof (wchar_t); + if (ERROR_SUCCESS != ::RegOpenKeyExW (main_key, path.c_str (), 0, KEY_READ, &sub_key)) return false; + bool bRet = (ERROR_SUCCESS == ::RegQueryValueExW (sub_key, key_name.c_str (), nullptr, nullptr, (BYTE*) &_data[0], &sz)); + ::RegCloseKey (sub_key); + data = std::wstring (_data.c_str (), sz / 2); + return bRet; + } + static bool get_key_value (std::wstring path, std::wstring key_name, std::vector &data) { + HKEY main_key = parse_path (path), sub_key = NULL; + std::wstring _data = L""; + _data.reserve (4096); + DWORD sz = (DWORD) 4096 * sizeof (wchar_t); + if (ERROR_SUCCESS != ::RegOpenKeyExW (main_key, path.c_str (), 0, KEY_READ, &sub_key)) return false; + bool bRet = (ERROR_SUCCESS == ::RegQueryValueExW (sub_key, key_name.c_str (), nullptr, nullptr, (BYTE*) &_data[0], &sz)); + ::RegCloseKey (sub_key); + data.clear (); + wchar_t *p1 = &_data[0]; + while (*p1) { + int len = lstrlenW (p1); + data.push_back (std::wstring (p1, len)); + p1 += len; + } + return bRet; + } + + static bool delete_path (std::wstring path) { + HKEY main_key = parse_path (path); + return (ERROR_SUCCESS == ::RegDeleteKey (main_key, path.c_str ())); + } + static bool delete_key (std::wstring path, std::wstring key_name) { + HKEY main_key = parse_path (path), sub_key = NULL; + if (ERROR_SUCCESS != ::RegOpenKeyEx (main_key, path.c_str (), 0, KEY_ALL_ACCESS, &sub_key)) return false; + bool bRet = (ERROR_SUCCESS == ::RegDeleteKey (sub_key, path.c_str ())); + ::RegCloseKey (sub_key); + return bRet; + } + + static bool path_exist (std::wstring path) { + HKEY main_key = parse_path (path), sub_key = NULL; + if (ERROR_SUCCESS != ::RegOpenKeyEx (main_key, path.c_str (), 0, KEY_QUERY_VALUE, &sub_key)) return false; + ::RegCloseKey (sub_key); + return TRUE; + } + +protected: + static HKEY parse_path (std::wstring &path) { + static std::map mkeys { + { L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT }, + { L"HKCR", HKEY_CLASSES_ROOT }, + { L"HKEY_CURRENT_USER", HKEY_CURRENT_USER }, + { L"HKCU", HKEY_CURRENT_USER }, + { L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE }, + { L"HKLM", HKEY_LOCAL_MACHINE }, + { L"HKEY_USERS", HKEY_USERS }, + { L"HKU", HKEY_USERS }, + { L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG }, + { L"HKCC", HKEY_CURRENT_CONFIG }, + }; + + size_t p = path.find (L'\\'); + if (p == std::string::npos) + p = path.size (); + HKEY mkey = mkeys[path.substr (0, p)]; + path = (path.size () >= p + 1 ? path.substr (p + 1) : L""); + return mkey; + } + static std::wstring make_multi_sz (std::vector &data) { + std::wstring s = L""; + for (auto &_s : data) { + s += _s; + s += L'\0'; + } + s += L'\0'; + return s; + } +}; + +#endif //__TOOL_REGISTER_HPP__ diff --git a/NetToolbox/tools/tool_Rsa.hpp b/NetToolbox/tools/tool_Rsa.hpp new file mode 100644 index 0000000..4377714 --- /dev/null +++ b/NetToolbox/tools/tool_Rsa.hpp @@ -0,0 +1,65 @@ +#ifndef __TOOL_RSA_HPP__ +#define __TOOL_RSA_HPP__ + +#include +#include +#include + +#include "tool_Path.hpp" + + + +class tool_Rsa { +public: + static bool generate (int bits, std::string file_pubkey, std::string file_rsapubkey, std::string file_rsaprvkey) { + bool bExist = false; + std::string info = "ļѴڣǷ񸲸ǣ"; + if (tool_Path::file_existA (file_pubkey)) { + bExist = true; + info += "\n"; + info += file_pubkey; + } + if (tool_Path::file_existA (file_rsapubkey)) { + bExist = true; + info += "\n"; + info += file_rsapubkey; + } + if (tool_Path::file_existA (file_rsaprvkey)) { + bExist = true; + info += "\n"; + info += file_rsaprvkey; + } + if (bExist) { + if (IDOK != ::MessageBoxA (NULL, info.c_str (), "ʾ", MB_ICONQUESTION | MB_OKCANCEL)) + return false; + } + + RSA *r = ::RSA_new (); + BIGNUM *bn = ::BN_new (); + ::BN_set_word (bn, 65537); + ::RSA_generate_key_ex (r, 2048, bn, nullptr); + bool bSuccess = true; + // + int a = 0, b = 0, c = 0; + if (!file_pubkey.empty ()) { + BIO *bio = ::BIO_new_file (file_pubkey.c_str (), "w+"); + bSuccess &= !!::PEM_write_bio_RSA_PUBKEY (bio, r); + ::BIO_free (bio); + } + if (!file_rsapubkey.empty ()) { + BIO *bio = ::BIO_new_file (file_rsapubkey.c_str (), "w+"); + bSuccess &= !!::PEM_write_bio_RSAPublicKey (bio, r); + ::BIO_free (bio); + } + if (!file_rsaprvkey.empty ()) { + BIO *bio = ::BIO_new_file (file_rsaprvkey.c_str (), "w+"); + bSuccess &= !!::PEM_write_bio_RSAPrivateKey (bio, r, nullptr, nullptr, 0, nullptr, nullptr); + ::BIO_free (bio); + } + ::BN_free (bn); + ::RSA_free (r); + return bSuccess; + } +}; + +#endif //__TOOL_RSA_HPP__ diff --git a/NetToolbox/tools/tool_SerialPort.hpp b/NetToolbox/tools/tool_SerialPort.hpp new file mode 100644 index 0000000..c5f96d9 --- /dev/null +++ b/NetToolbox/tools/tool_SerialPort.hpp @@ -0,0 +1,120 @@ +#ifndef __TOOL_SERIAL_PORT_HPP__ +#define __TOOL_SERIAL_PORT_HPP__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../3rdparty/serial/serial.h" + + + +class tool_SerialPort { +public: + static std::vector get_list () { + std::vector v; + HDEVINFO hDevInfo = ::SetupDiGetClassDevsA (&GUID_DEVCLASS_PORTS, NULL, NULL, 0); + if (hDevInfo != INVALID_HANDLE_VALUE) { + SP_DEVINFO_DATA dd = { sizeof (SP_DEVINFO_DATA) }; + for (DWORD dw = 0; ::SetupDiEnumDeviceInfo (hDevInfo, dw, &dd); dw++) { + char tbuf[MAX_PATH] = { 0 }; + if (::SetupDiGetDeviceRegistryPropertyA (hDevInfo, &dd, SPDRP_FRIENDLYNAME, NULL, (PBYTE) tbuf, sizeof (tbuf), NULL)) + v.push_back (tbuf); + } + ::SetupDiDestroyDeviceInfoList (hDevInfo); + } + std::sort (v.begin (), v.end (), [] (std::string s1, std::string s2) { + size_t p1 = s1.find ('('), p2 = s2.find ('('); + return atoi (&s1[p1 + 4]) < atoi (&s2[p2 + 4]); + }); + return v; + } + + tool_SerialPort () { + m_thread = std::thread (&tool_SerialPort::thread_func, this); + } + virtual ~tool_SerialPort () { + m_exit = true; + m_thread.join (); + close (); + } + + bool open (std::string name, uint32_t baud_rate = 9600, uint8_t byte_size = 8, std::string parity = "none", std::string stopbits = "1") { + static std::map map_parity = { { "none", serial::parity_none }, { "odd", serial::parity_odd }, { "even", serial::parity_even }, { "mark", serial::parity_mark }, { "space", serial::parity_space } }; + static std::map map_stopbits = { { "1", serial::stopbits_one }, { "1.5", serial::stopbits_one_point_five }, { "2", serial::stopbits_two } }; + static serial::Timeout timeout = serial::Timeout::simpleTimeout (100); + + close (); + m_serial.setTimeout (timeout); + m_serial.setPort (name); + m_serial.setBaudrate (baud_rate); + m_serial.setBytesize ((serial::bytesize_t) byte_size); + m_serial.setParity (map_parity[parity]); + m_serial.setStopbits (map_stopbits[stopbits]); + m_serial.open (); + return (m_is_open = m_serial.isOpen ()); + } + void close () { + if (m_serial.isOpen ()) + m_serial.close (); + m_is_open = false; + } + size_t write (std::string data) { + if (!m_serial.isOpen ()) + return 0; + return m_serial.write (data); + } + + void set_on_event (std::function on_receive, std::function on_close) { + m_on_receive = on_receive; + m_on_close = on_close; + } + bool is_open () { return m_serial.isOpen (); } + std::string get_name () { return m_serial.getPort (); } + +protected: + void thread_func () { + while (!m_exit) { + if (m_serial.isOpen ()) { + size_t sz_old = 0, sz = m_serial.available (); + if (sz > 0) { + while (sz > sz_old) { + if (m_exit) + return; + std::this_thread::sleep_for (std::chrono::milliseconds (100)); + sz_old = sz; + sz = m_serial.available (); + } + if (m_on_receive) + m_on_receive (m_serial.read (sz)); + } + } else if (m_is_open) { + if (m_on_close) + m_on_close (); + m_is_open = false; + } + std::this_thread::sleep_for (std::chrono::milliseconds (100)); + //::Sleep (4000); + //m_on_receive ("ԴڵϢ"); + } + } + +protected: + serial::Serial m_serial; + bool m_is_open = false; + bool m_exit = false; + std::function m_on_receive = nullptr; + std::function m_on_close = nullptr; + std::thread m_thread; +}; + +#endif //__TOOL_SERIAL_PORT_HPP__ diff --git a/NetToolbox/tools/tool_String.hpp b/NetToolbox/tools/tool_String.hpp new file mode 100644 index 0000000..4ce761a --- /dev/null +++ b/NetToolbox/tools/tool_String.hpp @@ -0,0 +1,302 @@ +#ifndef __TOOL_STRING_HPP__ +#define __TOOL_STRING_HPP__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _T +#ifdef _UNICODE +#define _T(x) L##x +#else //_UNICODE +#define _T(x) x +#endif //_UNICODE +#endif + + + +template +class tool_String { +public: + //ַʼֿո + static void trim_left (std::basic_string &str) { + str.erase (0, str.find_first_not_of (' ')); + } + + //ַֿո + static void trimRight (std::basic_string &str) { + str.erase (str.find_last_not_of (' ') + 1); + } + + //˿ո + static void trim (std::basic_string &str) { + str.erase (0, str.find_first_not_of (' ')); + str.erase (str.find_last_not_of (' ') + 1); + } + + //ɾַַָ + static void erase (std::basic_string &str, const T &charactor) { + str.erase (remove_if (str.begin (), str.end (), bind2nd (std::equal_to (), charactor)), str.end ()); + } + + // indexɾַ + static void remove_at (std::basic_string &str, const int index) { + if (str.length () <= index) + return; + str.erase (str.begin () + index); + } + + //滻ַַָ + static size_t replace (std::basic_string &str, const std::basic_string &strSrc, const std::basic_string &strDest) { + size_t ret = 0, pos = str.find (strSrc); + while (pos != std::basic_string::npos) { + str.replace (pos, strSrc.size (), strDest); + ++ret; + pos = str.find (strSrc, pos + 2); + } + return ret; + } + + //滻ַַָ + static size_t replace (std::basic_string &str, const T src, const T dest) { + size_t ret = 0, pos = str.find (src); + for (size_t i = 0; i < str.length (); ++i) { + if (str[i] == src) { + str[i] = dest; + ++ret; + } + } + return ret; + } + + //ַض + static void split (std::basic_string s, std::vector> &v, T ch = _T (' ')) { + size_t start = 0, p, len = s.length (); + do { + p = s.find (ch, start); + if (p == std::basic_string::npos) p = len; + s[p] = '\0'; + if (s[start] != '\0') v.push_back (&s[start]); + start = p + 1; + } while (start < len); + } + static std::vector> split (std::basic_string_view s, T ch = _T (' '), std::basic_string_view ignore = _T (""), bool noempty = true) { + size_t start = 0, start_find = 0; + std::vector> v; + while (start_find < s.size ()) { + size_t p = s.find (ch, start_find); + if (p == std::basic_string::npos) p = s.size (); + if (ignore.size () > 0) { + if (s.size () >= start_find + ignore.size ()) { + if (s.substr (p, ignore.size ()) == ignore) { + start_find = p + 1; + continue; + } + } + } + if (!noempty || (noempty && p > start)) + v.push_back (std::basic_string (s.substr (start, p - start))); + start = start_find = p + 1; + } + return v; + } + + //ַʽ + static std::basic_string format (std::basic_string fmt_str, ...) { + std::basic_string str_result; + if (fmt_str.empty ()) + return str_result; + try { + va_list ap; +#ifndef __GNUC__ + //Դhttp://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf + ptrdiff_t final_n, n = ((ptrdiff_t) fmt_str.size ()) * 2; + std::unique_ptr formatted; + while (true) { + formatted.reset (new T[n]); + //strcpy_s (&formatted [0], fmt_str.size (), fmt_str.c_str ()); + va_start (ap, fmt_str); + // _vsntprintf_s + if constexpr (sizeof(T) == 2) + final_n = _vsnwprintf_s (&formatted[0], n, _TRUNCATE, fmt_str.c_str (), ap); + else + final_n = _vsnprintf_s (&formatted[0], n, _TRUNCATE, fmt_str.c_str (), ap); + va_end (ap); + if (final_n < 0 || final_n >= n) + n += abs (final_n - n + 1); + else + break; + } + str_result = formatted.get (); +#else //__GNUC__ + char *buf = nullptr; + va_start (ap, fmt_str); + int iresult = vasprintf (&buf, fmt_str.c_str (), ap); + va_end (ap); + if (buf) { + if (iresult >= 0) { + iresult = strlen (buf); + str_result.append (buf, iresult); + } + free (buf); + } +#endif //__GNUC__ + } catch (...) { + } + return str_result; + } + + // ֽתʮַ + static std::basic_string byte_to_str (uint8_t ch) { + T s[3] = { 0, 0, 0 }; + uint8_t t = (ch >> 4) & 0xf; + if (t < 0xa) s[0] = _T ('0') + (T) t; else s[0] = _T ('A') + (T) t - 0xa; + t = ch & 0xf; + if (t < 0xa) s[1] = _T ('0') + (T) t; else s[1] = _T ('A') + (T) t - 0xa; + return std::basic_string (s); + } + + // url + static std::string url_encode (std::string str) { + std::string str_ret = ""; + try { + std::function _to_hex = [] (uint8_t ch) { return (ch > 9 ? (ch + 'A' - 10) : ch + '0'); }; + str_ret.reserve (str.size ()); + for (char ch : str) { + if (isalnum (ch) || ch == '-' || ch == '_' || ch == '.' || ch == '~') { + str_ret += ch; + } else if (ch == ' ') { + str_ret += '+'; + } else { + str_ret += '%'; + str_ret += _to_hex (((uint8_t) ch >> 4) & 0xf); + str_ret += _to_hex ((uint8_t) ch & 0xf); + } + } + } catch (...) { + } + return str_ret; + } + + // url + static std::string url_decode (std::string str) { + std::string str_ret = ""; + try { + std::function _from_hex = [] (uint8_t ch) { + if (ch >= 'A' && ch <= 'F') + return ch - 'A' + 10; + else if (ch >= 'a' && ch <= 'f') + return ch - 'a' + 10; + else if (ch >= '0' && ch <= '9') + return ch - '0'; + return 0; + }; + str_ret.reserve (str.size ()); + for (int i = 0; i < str.length (); ++i) { + char ch = str[i]; + if (ch == '+') + str_ret += ' '; + else if (ch == '%') { + if (i + 2 >= str.length ()) + return ""; + ch = _from_hex (str[++i]) << 4; + ch |= _from_hex (str[++i]); + str_ret += ch; + } else + str_ret += ch; + } + } catch (...) { + } + return str_ret; + } + + // ɳΪnĿַ + static std::string make_space (int n) { + std::string s; + if (n > 0) + s.resize (n, ' '); + return s; + } + + // ʽ + static std::basic_string format_date () { + T buf_time[32] = { 0 }, buf_time2[32] = { 0 }; + auto time_now = std::chrono::system_clock::now (); + auto duration_in_ms = std::chrono::duration_cast(time_now.time_since_epoch ()); + auto ms_part = duration_in_ms - std::chrono::duration_cast(duration_in_ms); + time_t raw_time = std::chrono::system_clock::to_time_t (time_now); + tm *local_time_now = localtime (&raw_time);//_localtime64_s + if constexpr (sizeof (T) == 1) { + strftime (buf_time2, sizeof (buf_time2), "%Y%m%d-%H%M%S", local_time_now); + sprintf (buf_time, "%s-%03d", buf_time2, ms_part.count ()); + } else { + wcsftime (buf_time2, sizeof (buf_time2), L"%Y%m%d-%H%M%S", local_time_now); + wsprintf (buf_time, L"%s-%03d", buf_time2, ms_part.count ()); + } + //char *xx = std::put_time (local_time_now, "%Y-%m-%d %H:%M:%S"); + return std::basic_string (buf_time); + } + + // ƥ + static std::tuple> match_regex (std::string str_reg, std::string source) { + std::string err = ""; + std::vector v; + try { + std::regex r (str_reg); + std::smatch m; + std::string::const_iterator _begin = source.cbegin (), _end = source.cend (); + while (std::regex_search (_begin, _end, m, r)) { + v.push_back (m[0]); + _begin = m[0].second; + } + } catch (std::exception &e) { + err = e.what (); + } catch (...) { + err = "δ֪"; + } + return { err, v }; + } + + // ԴСдȽ + static bool is_equal_nocase (std::string_view a, std::string_view b) { + if (a.size () != b.size ()) + return false; + for (size_t i = 0; i < a.size (); ++i) { + if (a[i] != b[i]) { + char ch = a[i]; + if (ch >= 'a' && ch <= 'z') { + ch = ch - 'a' + 'A'; + } else if (ch >= 'A' && ch <= 'Z') { + ch = ch - 'A' + 'a'; + } + if (ch != b[i]) + return false; + } + } + return true; + } +}; + + + +typedef tool_String tool_StringA; +typedef tool_String tool_StringW; +#ifdef _UNICODE +typedef tool_StringW tool_StringT; +#else //_UNICODE +typedef tool_StringA tool_StringT; +#endif //_UNICODE + +#endif //__TOOL_STRING_HPP__ diff --git a/NetToolbox/tools/tool_SysInfo.hpp b/NetToolbox/tools/tool_SysInfo.hpp new file mode 100644 index 0000000..55aa7e4 --- /dev/null +++ b/NetToolbox/tools/tool_SysInfo.hpp @@ -0,0 +1,220 @@ +#ifndef __TOOL_SYS_INFO_HPP__ +#define __TOOL_SYS_INFO_HPP__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +//#include + +#include "tool_String.hpp" +#include "tool_Register.hpp" +#include "tool_Utils.hpp" + +#pragma warning (disable: 4996) + +class tool_SysInfo { +public: + //ȡƷ + static std::wstring get_trademark_name () { + std::wstring facturer, product; + tool_Register::get_key_value (L"HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\BIOS", L"SystemManufacturer", facturer); + tool_Register::get_key_value (L"HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\BIOS", L"SystemProductName", product); + return tool_StringW::format (L"%s - %s", facturer.c_str (), product.c_str ()); + } + + //ȡϵͳ汾Ϣ + static std::wstring get_system_info () { + std::wstring sysname = _T (""); + //OSVERSIONINFOEX osvi = { sizeof (OSVERSIONINFOEX) }; + //DWORDLONG dlcm = 0; + //int op = VER_GREATER_EQUAL; + //VER_SET_CONDITION (dlcm, VER_MINORVERSION, op); + //VER_SET_CONDITION (dlcm, VER_MAJORVERSION, op); + //VER_SET_CONDITION (dlcm, VER_BUILDNUMBER, op); + //VER_SET_CONDITION (dlcm, VER_PLATFORMID, op); + //VER_SET_CONDITION (dlcm, VER_SERVICEPACKMINOR, op); + //VER_SET_CONDITION (dlcm, VER_SERVICEPACKMAJOR, op); + //VER_SET_CONDITION (dlcm, VER_SUITENAME, op); + //VER_SET_CONDITION (dlcm, VER_PRODUCT_TYPE, op); + //::VerifyVersionInfo (&osvi, 0xff, dlcm); + + OSVERSIONINFOEX osvi = { sizeof (OSVERSIONINFOEX) }; + if (!::GetVersionEx ((OSVERSIONINFO*) &osvi)) + return _T (""); + SYSTEM_INFO si; + ::GetSystemInfo (&si); + //processor_num = (int) si.dwNumberOfProcessors; + + switch (osvi.dwPlatformId) { + case VER_PLATFORM_WIN32_NT: + if ((osvi.dwMajorVersion == 6 || osvi.dwMajorVersion == 10)) { + static std::map, LPCTSTR> map = { + { { 6, 0, true }, _T ("Windows Vista ") }, + { { 6, 0, false }, _T ("Windows Server 2008 ") }, + { { 6, 1, true }, _T ("Windows 7 ") }, + { { 6, 1, false }, _T ("Windows Server 2008 R2 ") }, + { { 6, 2, true }, _T ("Windows 8 ") }, + { { 6, 2, false }, _T ("Windows Server 2012 ") }, + { { 6, 3, true }, _T ("Windows 8.1 ") }, + { { 6, 3, false }, _T ("Windows Server 2012 R2 ") }, + { { 10, 0, true }, _T ("Windows 10 ") }, + { { 10, 0, false }, _T ("Windows Server 2016 ") }, + }; + sysname = map[{ osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.wProductType == VER_NT_WORKSTATION }]; + if (sysname.length () < 1) sysname = _T ("Unknown high version."); + } else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) { + if (GetSystemMetrics (SM_SERVERR2)) { + sysname = _T ("Microsoft Windows Server 2003 \"R2\" "); + } else if (osvi.wProductType == VER_NT_WORKSTATION && si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) { + sysname = _T ("Microsoft Windows XP Professional x64 Edition "); + } else { + sysname = _T ("Microsoft Windows Server 2003 "); + } + } else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) { + sysname = _T ("Microsoft Windows XP "); + } else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) { + sysname = _T ("Microsoft Windows 2000 "); + } else if (osvi.dwMajorVersion <= 4) { + sysname = _T ("Microsoft Windows NT "); + } + + // Test for specific product on Windows NT 4.0 SP6 and later. + if (sizeof (OSVERSIONINFOEX) == osvi.dwOSVersionInfoSize) { + if (osvi.wServicePackMajor > 0) + sysname += tool_StringW::format (_T ("Service Pack %d "), osvi.wServicePackMajor); + + // Test for the workstation type. + if (osvi.wProductType == VER_NT_WORKSTATION && si.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_AMD64) { + if (osvi.dwMajorVersion == 4) + sysname += _T ("Workstation 4.0 "); + else if (osvi.wSuiteMask & VER_SUITE_PERSONAL) + sysname += _T ("Home Edition "); + else + sysname += _T ("Professional "); + } else if (osvi.wProductType == VER_NT_SERVER || osvi.wProductType == VER_NT_DOMAIN_CONTROLLER) { + // Test for the server type. + if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) { + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) { + if (osvi.wSuiteMask & VER_SUITE_DATACENTER) + sysname += _T ("Datacenter Edition for Itanium-based Systems "); + else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) + sysname += _T ("Enterprise Edition for Itanium-based Systems "); + } else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) { + if (osvi.wSuiteMask & VER_SUITE_DATACENTER) + sysname += _T ("Datacenter x64 Edition "); + else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) + sysname += _T ("Enterprise x64 Edition "); + else + sysname += _T ("Standard x64 Edition "); + } else { + if (osvi.wSuiteMask & VER_SUITE_DATACENTER) + sysname += _T ("Datacenter Edition "); + else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) + sysname += _T ("Enterprise Edition "); + else if (osvi.wSuiteMask & VER_SUITE_BLADE) + sysname += _T ("Web Edition "); + else + sysname += _T ("Standard Edition "); + } + } else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) { + if (osvi.wSuiteMask & VER_SUITE_DATACENTER) + sysname += _T ("Datacenter Server "); + else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) + sysname += _T ("Advanced Server "); + else + sysname += _T ("Server "); + } else { // Windows NT 4.0 + if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) + sysname += _T ("Server 4.0, Enterprise Edition "); + else + sysname += _T ("Server 4.0 "); + } + } + } else { + // Test for specific product on Windows NT 4.0 SP5 and earlier + std::wstring _tmp; + tool_Register::get_key_value (_T ("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\ProductOptions"), _T ("ProductType"), _tmp); + if (lstrcmpi (_T ("WINNT"), &_tmp[0]) == 0) + sysname += _T ("Workstation "); + if (lstrcmpi (_T ("LANMANNT"), &_tmp[0]) == 0) + sysname += _T ("Server "); + if (lstrcmpi (_T ("SERVERNT"), &_tmp[0]) == 0) + sysname += _T ("Advanced Server "); + sysname += tool_StringW::format (_T ("%d.%d "), osvi.dwMajorVersion, osvi.dwMinorVersion); + } + + // Display service pack (if any) and build number. + if (osvi.dwMajorVersion == 4 && lstrcmpi (osvi.szCSDVersion, TEXT ("Service Pack 6")) == 0) { + // Test for SP6 versus SP6a. + if (tool_Register::path_exist (_T ("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Hotfix\\Q246009"))) { + sysname += tool_StringW::format (_T ("Service Pack 6a (Build %d)"), osvi.dwBuildNumber & 0xFFFF); + } else {// Windows NT 4.0 prior to SP6a + if (osvi.szCSDVersion[0] != _T ('\0')) { + sysname += osvi.szCSDVersion; + sysname += _T (' '); + } + sysname += tool_StringW::format (_T ("(Build %d)"), osvi.dwBuildNumber & 0xFFFF); + } + } else {// not Windows NT 4.0 + if (osvi.szCSDVersion[0] != _T ('\0')) { + sysname += osvi.szCSDVersion; + sysname += _T (' '); + } + sysname += tool_StringW::format (_T ("(Build %d)"), osvi.dwBuildNumber & 0xFFFF); + } + break; + + // Test for the Windows Me/98/95. + case VER_PLATFORM_WIN32_WINDOWS: + + if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0) { + sysname = _T ("Microsoft Windows 95"); + if (osvi.szCSDVersion[1] == 'C' || osvi.szCSDVersion[1] == 'B') sysname += _T (" OSR2"); + } else if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 10) { + sysname = _T ("Microsoft Windows 98"); + if (osvi.szCSDVersion[1] == 'A' || osvi.szCSDVersion[1] == 'B') sysname += _T (" SE"); + } else if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 90) { + sysname = _T ("Microsoft Windows Millennium Edition"); + } + break; + + case VER_PLATFORM_WIN32s: + sysname = _T ("Microsoft Win32s"); + break; + default: + return false; + } + return sysname; + } + + //ȡCPUϢ + static std::tuple get_cpu_info () { + //ȡCPU߳ get_system_info() + std::wstring s1, s2; + tool_Register::get_key_value (L"HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", L"ProcessorNameString", s1); + tool_Register::get_key_value (L"HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", L"Identifier", s2); + return { s1, s2 }; + } + + //ȡڴϢ + static string_t get_memory_info () { + MEMORYSTATUSEX ms = { sizeof (MEMORYSTATUSEX) }; + if (!::GlobalMemoryStatusEx (&ms)) + return { L"", L"" }; + return tool_StringT::format (_T ("ܹ %s / %s"), tool_Utils::format_unit (ms.ullTotalPhys).c_str (), tool_Utils::format_unit (ms.ullAvailPhys).c_str ()); + //return { + // tool_StringT::format (_T ("ܹ %s / %s"), tool_Utils::format_unit (ms.ullTotalPhys).c_str (), tool_Utils::format_unit (ms.ullAvailPhys).c_str ()), + // tool_StringT::format (_T ("ܹ %s / %s"), tool_Utils::format_unit (ms.ullTotalVirtual).c_str (), tool_Utils::format_unit (ms.ullAvailVirtual).c_str ()) + //}; + } +}; + +#endif //__TOOL_SYS_INFO_HPP__ diff --git a/NetToolbox/tools/tool_Tracert.hpp b/NetToolbox/tools/tool_Tracert.hpp new file mode 100644 index 0000000..c3e4258 --- /dev/null +++ b/NetToolbox/tools/tool_Tracert.hpp @@ -0,0 +1,116 @@ +#ifndef __TOOL_TRACERT_HPP__ +#define __TOOL_TRACERT_HPP__ + +#include +#include +#include +#include +#include +#include +#include + +#include "tool_String.hpp" +#include "tool_Utils.hpp" + + + +class tool_Tracert { +public: + static std::tuple start_ipv4 (std::string dest_ip, std::function f) { + IPAddr ul_dest_ip = ::inet_addr (&dest_ip[0]); + HANDLE hIcmp = ::IcmpCreateFile (); + if (hIcmp == INVALID_HANDLE_VALUE) + return { false, tool_StringT::format (_T ("IcmpCreateFile %s"), tool_Utils::get_error_info (::GetLastError ()).c_str ()) }; + constexpr DWORD reply_size = sizeof (ICMP_ECHO_REPLY) + sizeof (m_send); + BYTE b_reply[reply_size]; + PICMP_ECHO_REPLY reply = (PICMP_ECHO_REPLY) b_reply; + IP_OPTION_INFORMATION ioi = { 0, 0, 0, 0, nullptr }; + for (UCHAR ttl = 1; ttl <= m_max_ttl; ++ttl) { + ioi.Ttl = ttl; + f (ttl, 0, tool_StringA::format ("%d", ttl)); + std::string route_ip = "0.0.0.0"; + for (size_t i = 1; i <= m_test_count; ++i) { + DWORD dwRetVal = ::IcmpSendEcho (hIcmp, ul_dest_ip, m_send, sizeof (m_send), &ioi, reply, reply_size, 1000); + if (reply->Status == IP_TTL_EXPIRED_TRANSIT || reply->Status == IP_TTL_EXPIRED_REASSEM || reply->Status == IP_SUCCESS) { + if (reply->RoundTripTime == 0) { + f (ttl, i, "<1ms"); + } else { + f (ttl, i, tool_StringA::format ("%dms", reply->RoundTripTime)); + } + } else if (reply->Status == IP_REQ_TIMED_OUT) { + f (ttl, i, "*"); + } else { + f (ttl, i, "?"); + } + if (route_ip == "0.0.0.0") { + in_addr _addr { 0 }; + _addr.s_addr = reply->Address; + route_ip = ::inet_ntoa (_addr); + } + } + f (ttl, 4, route_ip); + if (dest_ip == route_ip) { + ::IcmpCloseHandle (hIcmp); + return { true, _T ("·ɸɡ") }; + } + } + ::IcmpCloseHandle (hIcmp); + return { true, _T ("·ɸٽѳɸٵԾ") }; + } + + static std::tuple start_ipv6 (std::string dest_ip, std::function f) { + in_addr6 ul_dest_ip = { 0 }; + ::inet_pton (AF_INET6, dest_ip.c_str (), &ul_dest_ip); + HANDLE hIcmp = ::Icmp6CreateFile (); + if (hIcmp == INVALID_HANDLE_VALUE) + return { false, tool_StringT::format (_T ("Icmp6CreateFile %s"), tool_Utils::get_error_info (::GetLastError ()).c_str ()) }; + constexpr DWORD reply_size = sizeof (ICMPV6_ECHO_REPLY) + sizeof (m_send); + BYTE b_reply[reply_size]; + PICMPV6_ECHO_REPLY reply = (PICMPV6_ECHO_REPLY) b_reply; + IP_OPTION_INFORMATION ioi = { 0, 0, 0, 0, nullptr }; + for (UCHAR ttl = 1; ttl <= m_max_ttl; ++ttl) { + ioi.Ttl = ttl; + f (ttl, 0, tool_StringA::format ("%d", ttl)); + std::string route_ip = "::"; + for (size_t i = 1; i <= m_test_count; ++i) { + sockaddr_in6 src_addr = { 0 }, dest_addr = { AF_INET6, 0, 0, ul_dest_ip, 0 }; + DWORD dwRetVal = ::Icmp6SendEcho2 (hIcmp, NULL, nullptr, nullptr, &src_addr, &dest_addr, m_send, sizeof (m_send), &ioi, reply, reply_size, 3000); + DWORD d = GetLastError (); + if (reply->Status == IP_TTL_EXPIRED_TRANSIT || reply->Status == IP_TTL_EXPIRED_REASSEM || reply->Status == IP_SUCCESS) { + if (reply->RoundTripTime == 0) { + f (ttl, i, "<1ms"); + } else { + f (ttl, i, tool_StringA::format ("%dms", reply->RoundTripTime)); + } + } else if (reply->Status == IP_REQ_TIMED_OUT) { + f (ttl, i, "*"); + } else { + f (ttl, i, "?"); + } + if (route_ip == "::") { + char _addr[64] = { 0 }; + inet_ntop (AF_INET6, reply->Address.sin6_addr, _addr, sizeof (_addr) / sizeof (_addr[0])); + route_ip = _addr; + } + } + f (ttl, 4, route_ip); + if (dest_ip == route_ip) { + ::IcmpCloseHandle (hIcmp); + return { true, _T ("·ɸɡ") }; + } + } + ::IcmpCloseHandle (hIcmp); + return { true, _T ("·ɸٽѳɸٵԾ") }; + } + +private: + static size_t m_test_count; + static UCHAR m_max_ttl; + static BYTE m_send[32]; +}; + +inline size_t tool_Tracert::m_test_count = 3; +inline UCHAR tool_Tracert::m_max_ttl = 30; +inline BYTE tool_Tracert::m_send[32] { 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65 }; + +#endif //__TOOL_TRACERT_HPP__ diff --git a/NetToolbox/tools/tool_Utils.hpp b/NetToolbox/tools/tool_Utils.hpp new file mode 100644 index 0000000..0d93d0b --- /dev/null +++ b/NetToolbox/tools/tool_Utils.hpp @@ -0,0 +1,57 @@ +#ifndef __TOOL_UTILS_HPP__ +#define __TOOL_UTILS_HPP__ + +#include +#include +#include +#include +#include + +#include "tool_String.hpp" + + + +class tool_Utils { +public: + static string_t format_unit (double byte_num) { + LPCTSTR units[] = { _T ("Byte"), _T ("KB"), _T ("MB"), _T ("GB"), _T ("TB"), _T ("PB"), _T ("EB"), _T ("ZB"), _T ("YB"), _T ("NB"), _T ("DB"), _T ("CB") }; + for (size_t i = 0; i < sizeof (units) / sizeof (units[0]); ++i, byte_num /= 1024) { + if (byte_num < 1024) { + return tool_StringT::format (_T ("%.2f%s"), byte_num, units[i]); + } + } + return _T (""); + } + + static string_t get_error_info (DWORD err_no) { + TCHAR tBuf[MAX_PATH] = { 0 }; + int n = _stprintf_s (tBuf, MAX_PATH, _T (" %d"), err_no); + if (::FormatMessageW (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, err_no, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), &tBuf[n], MAX_PATH - n, nullptr) == 0) + lstrcat (tBuf, _T ("δ֪")); + for (; tBuf[n] != _T ('\0') && n < MAX_PATH - 1; ++n) { + if (tBuf[n] == _T ('\r') || tBuf[n] == _T ('\n')) { + lstrcpyW (&tBuf[n], &tBuf[n + 1]); + --n; + } + } + return tBuf; + } + + static string_t format_ipv4 (IPAddr ip) { + return tool_StringT::format (_T ("%d.%d.%d.%d"), ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF); + } + + static string_t format_ipv4_my (uint32_t ip) { + return tool_StringT::format (_T ("%d.%d.%d.%d"), (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); + } + + static uint32_t from_ipv4_my (string_t sip) { + std::vector v; + tool_StringT::split (sip, v, _T ('.')); + if (v.size () < 4) + return 0; + return ((uint32_t) (_ttoi (v[0].c_str ())) << 24) + (_ttoi (v[1].c_str ()) << 16) + (_ttoi (v[2].c_str ()) << 8) + _ttoi (v[3].c_str ()); + } +}; + +#endif //__TOOL_UTILS_HPP__ diff --git a/NetToolbox/tools/tool_VP9.hpp b/NetToolbox/tools/tool_VP9.hpp new file mode 100644 index 0000000..358f713 --- /dev/null +++ b/NetToolbox/tools/tool_VP9.hpp @@ -0,0 +1,139 @@ +#ifndef __TOOL_VP9_HPP__ +#define __TOOL_VP9_HPP__ + +#include +#include +#include + +#include +#include +#include +#include + +#ifdef _UNICODE +using string_t = std::wstring; +using string_view_t = std::wstring_view; +#else +using string_t = std::string; +using string_view_t = std::string_view; +#endif + + + +class tool_VP9 { +public: + tool_VP9 (string_view_t path) { + //vpx_codec_ctx_t codec; + int frame_count = 0; + const int fps = 30; + const int bitrate = 200; + int keyframe_interval = 0; + int max_frames = 1000000000; // right? + int frames_encoded = 0; + const char *outfile_arg = NULL; + + const VpxInterface *encoder = get_vpx_encoder_by_name ("vp9"); + VpxVideoInfo info = { 0, 0, 0, { 0, 0 } }; + info.codec_fourcc = encoder->fourcc; + info.frame_width = 1920; + info.frame_height = 1080; + info.time_base.numerator = 1; + info.time_base.denominator = fps; + + if (info.frame_width <= 0 || info.frame_height <= 0 || + (info.frame_width % 2) != 0 || (info.frame_height % 2) != 0) { + die ("Invalid frame size: %dx%d", info.frame_width, info.frame_height); + } + + vpx_image_t raw; + if (!vpx_img_alloc (&raw, VPX_IMG_FMT_YV12, info.frame_width, info.frame_height, 1)) { + die ("Failed to allocate image."); + } + + printf ("Using %s\n", vpx_codec_iface_name (encoder->codec_interface ())); + + vpx_codec_enc_cfg_t cfg; + vpx_codec_err_t res = vpx_codec_enc_config_default (encoder->codec_interface (), &cfg, 0); + if (res) die_codec (&codec, "Failed to get default codec config."); + + cfg.g_w = info.frame_width; + cfg.g_h = info.frame_height; + cfg.g_timebase.num = info.time_base.numerator; + cfg.g_timebase.den = info.time_base.denominator; + cfg.rc_target_bitrate = bitrate; + cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT; // right? + + VpxVideoWriter *writer = vpx_video_writer_open (outfile_arg, kContainerIVF, &info); + if (!writer) die ("Failed to open %s for writing.", outfile_arg); + + if (vpx_codec_enc_init (&codec, encoder->codec_interface (), &cfg, 0)) + die_codec (&codec, "Failed to initialize encoder"); + + // Encode frames. + while (vpx_img_read (&raw, infile)) { + int flags = 0; + if (keyframe_interval > 0 && frame_count % keyframe_interval == 0) + flags |= VPX_EFLAG_FORCE_KF; + encode_frame (&codec, &raw, frame_count++, flags, writer); + frames_encoded++; + if (max_frames > 0 && frames_encoded >= max_frames) break; + } + + // Flush encoder. + while (encode_frame (&codec, NULL, -1, 0, writer)) { + } + + printf ("\n"); + fclose (infile); + printf ("Processed %d frames.\n", frame_count); + + vpx_img_free (&raw); + if (vpx_codec_destroy (&codec)) die_codec (&codec, "Failed to destroy codec."); + + vpx_video_writer_close (writer); + } + +private: + static bool encode_frame (vpx_codec_ctx_t *codec, vpx_image_t *img, int frame_index, VpxVideoWriter *writer) { + vpx_codec_iter_t iter = nullptr; + const vpx_codec_err_t res = vpx_codec_encode (codec, img, frame_index, 1, 0, VPX_DL_GOOD_QUALITY); + if (res != VPX_CODEC_OK) die_codec (codec, "Failed to encode frame"); + + const vpx_codec_cx_pkt_t *pkt = vpx_codec_get_cx_data (codec, &iter); + if (!pkt) + return false; + do { + if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { + if (!vpx_video_writer_write_frame (writer, (uint8_t*) pkt->data.frame.buf, pkt->data.frame.sz, pkt->data.frame.pts)) { + die_codec (codec, "Failed to write compressed frame"); + } + } + } while ((pkt = vpx_codec_get_cx_data (codec, &iter)) != nullptr); + return true; + } + + static int rgb24_to_yv12 (unsigned char* RGB, unsigned char* YV12, int width, int height) { + unsigned char* _Y = YV12; + unsigned char* _V = YV12 + width * height; + unsigned char* _U = YV12 + width * height + ((width + 1) / 2) * ((height + 1) / 2); +#pragma omp parallel for + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + int R = RGB[(j * width + i) * 3]; + int G = RGB[(j * width + i) * 3 + 1]; + int B = RGB[(j * width + i) * 3 + 2]; + float Y = 0.299f * (float) R + 0.587f * (float) G + 0.114f * (float) B; + _Y[j * width + i] = (unsigned char) (Y < 0 ? 0 : (Y > 255 ? 255 : Y)); + if (j % 2 == 0 && i % 2 == 0) { + float V = 0.615f * (float) R - 0.515f * (float) G - 0.100f * (float) B; + _V[(j / 2) * ((width + 1) / 2) + i / 2] = (unsigned char) (V < 0 ? 0 : (V > 255 ? 255 : V)); + float U = -0.147f * (float) R - 0.289f * (float) G + 0.436f * (float) B; + _U[(j / 2) * ((width + 1) / 2) + i / 2] = (unsigned char) (U < 0 ? 0 : (U > 255 ? 255 : U)); + } + } + } + return 0; + } +}; + +#endif //__TOOL_VP9_HPP__ diff --git a/NetToolbox/tools/tool_WMI.hpp b/NetToolbox/tools/tool_WMI.hpp new file mode 100644 index 0000000..a04abde --- /dev/null +++ b/NetToolbox/tools/tool_WMI.hpp @@ -0,0 +1,86 @@ +#ifndef __TOOL_WMI_HPP__ +#define __TOOL_WMI_HPP__ + +#include +#include +#include +#include + +//#pragma comment (lib, "comsuppw.lib") + +// WMIʹõWin32 +// http://ilcy2000.blog.sohu.com/132487993.html + + + +class tool_WMI { +public: + // ȡк + static std::wstring get_netcard_id () { + return _query_info (L"SELECT * FROM Win32_NetworkAdapter WHERE (MACAddress IS NOT NULL) AND (NOT (PNPDeviceID LIKE 'ROOT%'))", L"PNPDeviceID"); + } + + // ȡӲк + static std::wstring get_disk_id () { + return _query_info (L"SELECT * FROM Win32_DiskDrive WHERE (SerialNumber IS NOT NULL)", L"SerialNumber"); + } + + // ȡк + static std::wstring get_mainboard_id () { + return _query_info (L"SELECT * FROM Win32_BaseBoard WHERE (SerialNumber IS NOT NULL)", L"SerialNumber"); + } + + // ȡID + static std::wstring get_cpu_id () { + return _query_info (L"SELECT * FROM Win32_Processor WHERE (ProcessorId IS NOT NULL)", L"ProcessorId"); + } + + // ȡBIOSк + static std::wstring get_bios_id () { + return _query_info (L"SELECT * FROM Win32_BIOS WHERE (SerialNumber IS NOT NULL)", L"SerialNumber"); + } + + // ȡͺ + static std::wstring get_mainboard_type () { + return _query_info (L"SELECT * FROM Win32_BaseBoard WHERE (Product IS NOT NULL)", L"Product"); + } + + // ȡǰMACַ + static std::wstring get_netcard_mac () { + return _query_info (L"SELECT * FROM Win32_NetworkAdapter WHERE (MACAddress IS NOT NULL) AND (NOT (PNPDeviceID LIKE 'ROOT%'))", L"MACAddress"); + } + +protected: + static std::wstring _query_info (LPCWSTR sql, LPCWSTR prop) { + std::wstring val = L""; + IWbemLocator *pLoc = nullptr; + if (SUCCEEDED (::CoCreateInstance (CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*) &pLoc))) { + IWbemServices *pSvc = nullptr; + if (SUCCEEDED (pLoc->ConnectServer (BSTR (L"ROOT\\CIMV2"), nullptr, nullptr, nullptr, 0, nullptr, nullptr, &pSvc))) { + if (SUCCEEDED (::CoSetProxyBlanket (pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHN_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE))) { + IEnumWbemClassObject *pEnum = nullptr; + if (SUCCEEDED (pSvc->ExecQuery (BSTR (L"WQL"), BSTR (sql), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, nullptr, &pEnum))) { + IWbemClassObject *pObj = nullptr; + ULONG retn = 0; + if (SUCCEEDED (pEnum->Next (WBEM_INFINITE, 1, &pObj, &retn))) { + // on success, pObj may nullptr + if (pObj) { + VARIANT v; + ::VariantInit (&v); + if (SUCCEEDED (pObj->Get (prop, 0, &v, nullptr, nullptr))) + val = v.bstrVal; + ::VariantClear (&v); + } + } + pEnum->Release (); + } + } + pSvc->Release (); + } + pLoc->Release (); + } + return val; + } +}; + +#endif //__TOOL_WMI_HPP__ diff --git a/NetToolbox/tools/tool_WebRequest.hpp b/NetToolbox/tools/tool_WebRequest.hpp new file mode 100644 index 0000000..94969e9 --- /dev/null +++ b/NetToolbox/tools/tool_WebRequest.hpp @@ -0,0 +1,68 @@ +#ifndef __TOOL_WEB_REQUEST_HPP__ +#define __TOOL_WEB_REQUEST_HPP__ + +#include +#include +#include +#include + +#include "tool_String.hpp" + +#include "../3rdparty/Simple-Web-Server/client_https.hpp" + + + +class tool_WebRequest { +public: + static std::string get (std::string_view url) { + try { + auto[schema, host, path] = parse_url (url); + if (schema == "http://") { + SimpleWeb::Client client (host); + auto req = client.request ("GET", path); + return req->content.string (); + } else if (schema == "https://") { + SimpleWeb::Client client (host, false); + auto req = client.request ("GET", path); + return req->content.string (); + } + } catch (...) { + } + return ""; + } + + static std::string post (std::string_view url, std::string_view data) { + try { + auto[schema, host, path] = parse_url (url); + SimpleWeb::CaseInsensitiveMultimap header { { "Content-Type", "application/x-www-form-urlencoded" }, { "User-Agent", "NetToolbox" } }; + if (schema == "http://") { + SimpleWeb::Client client (host); + auto req = client.request ("POST", path, data, header); + return req->content.string (); + } else if (schema == "https://") { + SimpleWeb::Client client (host, false); + auto req = client.request ("POST", path, data, header); + return req->content.string (); + } + } catch (...) { + } + return ""; + } + +protected: + static std::tuple parse_url (std::string_view url) { + size_t p = url.find ("//"); + std::string schema = "http://"; + if (p != std::string::npos) { + p += 2; + schema = url.substr (0, p); + url = url.substr (p); + } + p = url.find ("/"); + std::string host (url.substr (0, p)); + std::string path (p >= url.size () ? "/" : url.substr (p)); + return { schema, host, path }; + } +}; + +#endif //__TOOL_WEB_REQUEST_HPP__ diff --git a/NetToolbox/tools/tool_Zoomer.hpp b/NetToolbox/tools/tool_Zoomer.hpp new file mode 100644 index 0000000..45a8fef --- /dev/null +++ b/NetToolbox/tools/tool_Zoomer.hpp @@ -0,0 +1,96 @@ +#ifndef __TOOL_ZOOMER_HPP__ +#define __TOOL_ZOOMER_HPP__ + +#include + +#include +#include + + + +class tool_Zoomer { +public: + static int get_desp_x () { return get_zoomer ().m_desp_x; } + static int get_desp_y () { return get_zoomer ().m_desp_y; } + static int get_real_x () { return get_zoomer ().m_real_x; } + static int get_real_y () { return get_zoomer ().m_real_y; } + static size_t zoom_x (size_t x) { + tool_Zoomer &zoomer = get_zoomer (); + if (!zoomer.m_is_scale) return x; + return (size_t) (x * zoomer.m_scale_x + 0.5); + } + static size_t zoom_y (size_t y) { + tool_Zoomer &zoomer = get_zoomer (); + if (!zoomer.m_is_scale) return y; + return (size_t) (y * zoomer.m_scale_y + 0.5); + } + static void zoom (POINT &o) { + tool_Zoomer &zoomer = get_zoomer (); + if (!zoomer.m_is_scale) return; + o.x = (decltype (o.x)) (o.x * zoomer.m_scale_x + 0.5); + o.y = (decltype (o.y)) (o.y * zoomer.m_scale_y + 0.5); + } + static void zoom (SIZE &o) { + tool_Zoomer &zoomer = get_zoomer (); + if (!zoomer.m_is_scale) return; + o.cx = (decltype (o.cx)) (o.cx * zoomer.m_scale_x + 0.5); + o.cy = (decltype (o.cy)) (o.cy * zoomer.m_scale_y + 0.5); + } + static void unzoom (SIZE &o) { + tool_Zoomer &zoomer = get_zoomer (); + if (!zoomer.m_is_scale) return; + o.cx = (decltype (o.cx)) (o.cx / zoomer.m_scale_x + 0.5); + o.cy = (decltype (o.cy)) (o.cy / zoomer.m_scale_y + 0.5); + } + static void zoom (RECT &o) { + tool_Zoomer &zoomer = get_zoomer (); + if (!zoomer.m_is_scale) return; + o.left = (decltype (o.left)) (o.left * zoomer.m_scale_x + 0.5); + o.top = (decltype (o.top)) (o.top * zoomer.m_scale_y + 0.5); + o.right = (decltype (o.right)) (o.right * zoomer.m_scale_x + 0.5); + o.bottom = (decltype (o.bottom)) (o.bottom * zoomer.m_scale_y + 0.5); + } + static void unzoom (RECT &o) { + tool_Zoomer &zoomer = get_zoomer (); + if (!zoomer.m_is_scale) return; + o.left = (decltype (o.left)) (o.left / zoomer.m_scale_x + 0.5); + o.top = (decltype (o.top)) (o.top / zoomer.m_scale_y + 0.5); + o.right = (decltype (o.right)) (o.right / zoomer.m_scale_x + 0.5); + o.bottom = (decltype (o.bottom)) (o.bottom / zoomer.m_scale_y + 0.5); + } + static void unzoom (Gdiplus::Bitmap **pbmp) { + tool_Zoomer &zoomer = get_zoomer (); + if (!zoomer.m_is_scale) return; + int src_width = (*pbmp)->GetWidth (); + int src_height = (*pbmp)->GetHeight (); + int dest_width = src_width / zoomer.m_scale_x; + int dest_height = src_height / zoomer.m_scale_y; + Gdiplus::Bitmap *tmp_bmp = new Gdiplus::Bitmap (dest_width, dest_height, (*pbmp)->GetPixelFormat ()); + Gdiplus::Graphics g (tmp_bmp); + g.DrawImage (*pbmp, Gdiplus::Rect (0, 0, dest_width, dest_height), 0, 0, src_width, src_height, Gdiplus::UnitPixel); + delete *pbmp; + *pbmp = tmp_bmp; + } + +private: + static tool_Zoomer &get_zoomer () { + static tool_Zoomer zoomer; + return zoomer; + } + tool_Zoomer () { + HDC hDC = ::GetDC (NULL); + m_desp_x = ::GetDeviceCaps (hDC, HORZRES); + m_desp_y = ::GetDeviceCaps (hDC, VERTRES); + m_real_x = ::GetDeviceCaps (hDC, DESKTOPHORZRES); + m_real_y = ::GetDeviceCaps (hDC, DESKTOPVERTRES); + m_scale_x = m_real_x / (double) m_desp_x; + m_scale_y = m_real_y / (double) m_desp_y; + ::ReleaseDC (NULL, hDC); + m_is_scale = (::abs (m_scale_x - 1) > 0.0001); + } + bool m_is_scale = false; + int m_desp_x = 0, m_desp_y = 0, m_real_x = 0, m_real_y = 0; + double m_scale_x = 1.0, m_scale_y = 1.0; +}; + +#endif //__TOOL_ZOOMER_HPP__ diff --git a/NetToolbox_publish.exe b/NetToolbox_publish.exe new file mode 100644 index 0000000000000000000000000000000000000000..ac280d397015d4bb7b44d10078aa01fd9125b3cb GIT binary patch literal 334848 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%0A6_}tIiF`qr4 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˺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 本地网络 + +1. 本机信息:展示本机各类系统信息,包括配置信息与CPU ID、主板ID等信息 +2. 本机网络:展示本机当前所有网络信息 +3. 网络连接:展示本机当前有哪些进程访问网络,列出连接端口地址,右键菜单打开进程位置或结束进程 + +> 网络工具 + +1. 路由跟踪:跟踪从当前主机到目标地址所经过的链接数 +2. HTTP请求:模拟实现HTTP(s)的GET/POST请求 +3. QPS测试:测试指定Web服务所能承受的最大HTTP连接数 + +> 格式工具 + +1. 正则表达式:用于测试验证正则表达式执行效果 +2. RSA生成:用于生成RSA公私钥对 +3. 转码解码:支持4种方式的转码及解码操作 + +> 单机工具 + +1. GIF录制:用于录制GIF图片 +2. 串口调试工具:用于调试串口 +3. 窗口工具:用于屏幕取色、窗口信息获取 +4. 文件工具:用于计算文件哈希值 +5. 步骤记录器:打开Windows自带的步骤记录器 + + + +## 编译步骤 +1. 安装VS2017、vcpkg、7-zip与upx +2. vcpkg安装boost、openssl、nlohmann-json等必须三方库 +3. 编译 + + + +## 修改日志 +2018.12.09 Community 开源;修改描述信息 + +2018.11.29 Community 优化在启动时阻塞请求网络的问题;优化网页首页展示;新增Base64编码解码;完成http、qps两个页面的参数编辑优化 + +2018.11.26 Community 修复更新失败的问题;新增部分常用正则;修复升级正则所造成的路由跟踪bug + +2018.11.24 Community 修复保存图片的bug;主机信息页面新增硬件信息展示;列表页优化;修复win7下崩溃的bug;修复本地连接为空时切换标签页无效的bug;修复存储图片无效的bug;优化文件存储失败提示 + +2018.11.21 Community 重构界面文件;新增取色指示框;文件覆盖提示;进程提权提示 + +2018.11.14 beta 修复网络连接列表在切换类型的地方没有及时更新的bug;主页内存数据实时更新 + +2018.11.14 alpha 本地连接列表更新优化;完成右键菜单的结束进程、打开文件所在位置功能 + +2018.11.13 alpha 修复颜色萃取器的位置缩放bug;网络连接拆开三种不同的连接方式;修复更新没有验证md5的bug + +2018.11.12 alpha 涉及缩放功能的bug的修复;gif录屏窗口保持最前;新增颜色萃取选择框 + +2018.11.11 alpha 新增转码解码功能 + +2018.11.08 beta 优化gif录屏框的显示;修复路由跟踪时显示bug + +2018.11.06 alpha gif录像完成鼠标绘制 + +2018.11.05 beta 修复列表bug;修复更新bug;优化服务器运行统计 + +2018.11.04 beta 修复gif窗口显示bug + +2018.11.04 alpha 修复路由跟踪时可多次点击的bug;新增屏幕gif录制功能 + +2018.11.03 alpha 修复http下post提交没有指定data的bug + +2018.11.02 beta 修复RichEdit无法复制bug;网络连接新增排序功能;关于页优化 + + + +## 许可证 +GPL-3.0 diff --git a/capture.jpg b/capture.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b439240457e586ef047c55803717da4a1a3845d1 GIT binary patch literal 98704 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%0Aj-_Eiz9;i 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

qh>uQF$VWfWP@YIUhsVr`wIe+?BWefHA);ZoVN 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 literal 0 HcmV?d00001 diff --git a/res/cur_findcolor.xml b/res/cur_findcolor.xml new file mode 100644 index 0000000..020ceec --- /dev/null +++ b/res/cur_findcolor.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/res/find_color.png b/res/find_color.png new file mode 100644 index 0000000000000000000000000000000000000000..698e90f160100cf806fbaa34d471fc2ab6840ae9 GIT binary patch literal 529 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 D{To$Q literal 0 HcmV?d00001 diff --git a/res/logo.ico b/res/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..3e8d60e0ce6da9976833684642fe4f95b426d454 GIT binary patch literal 16958 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 literal 0 HcmV?d00001 diff --git a/res/logo.png b/res/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..73c9188eb64de7a543528f39d87ee760db12fdd4 GIT binary patch literal 4902 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 Y4}`q-fC`a2Y5)KL07*qoM6N<$g4#Hf;{X5v literal 0 HcmV?d00001 diff --git a/res/main.xml b/res/main.xml new file mode 100644 index 0000000..0500a93 --- /dev/null +++ b/res/main.xml @@ -0,0 +1,579 @@ + + + + + + + + +