<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
	<channel>
		<title><![CDATA[PowerBASIC Users Meeting Point - Source Code Library ]]></title>
		<link>http://pump.richheimer.de/</link>
		<description><![CDATA[PowerBASIC Users Meeting Point - http://pump.richheimer.de]]></description>
		<pubDate>Fri, 01 May 2026 03:52:44 +0000</pubDate>
		<generator>MyBB</generator>
		<item>
			<title><![CDATA[Enter PIN popup dialog in DLL.]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=128</link>
			<pubDate>Thu, 23 Apr 2026 14:54:46 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=23">Dale Yarker</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=128</guid>
			<description><![CDATA[A popup for user to enter a PIN for return to your app. The number of digits is specified in the call, and checked by the function in the DLL.<br />
<br />
Longer description<br />
at <a href="https://www.yarker-dsyc.info/Programs/Misc/PIN/Enter_PIN_DLL.html" target="_blank" rel="noopener" class="mycode_url">https://www.yarker-dsyc.info/Programs/Mi...N_DLL.html</a><br />
<br />
Source, compiled, icons and Help file in ZIP<br />
at <a href="https://www.yarker-dsyc.info/Programs/Misc/PIN/Enter_PIN.zip" target="_blank" rel="noopener" class="mycode_url">https://www.yarker-dsyc.info/Programs/Mi...er_PIN.zip</a><br />
<br />
DLL source: <br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#compile dll<br />
#dim all<br />
'================================================================<br />
enum PIN_IDs singular<br />
  '%en_update group<br />
  ID_PINA1Txtbx = &amp;h3000&amp;<br />
  ID_PINA2Txtbx<br />
  ID_PINA3Txtbx<br />
  ID_PINA4Txtbx<br />
  ID_PINA5Txtbx<br />
  ID_PINA6Txtbx<br />
  ID_PINA7Txtbx<br />
  ID_PINA8Txtbx<br />
  ID_PINA9Txtbx<br />
  ID_PINB1Txtbx = &amp;h3010<br />
  ID_PINB2Txtbx<br />
  ID_PINB3Txtbx<br />
  ID_PINB4Txtbx<br />
  ID_PINB5Txtbx<br />
  ID_PINB6Txtbx<br />
  ID_PINB7Txtbx<br />
  ID_PINB8Txtbx<br />
  ID_PINB9Txtbx<br />
  '%bn_clicked group<br />
  ID_PINAExposeBtn<br />
  ID_PINBExposeBtn<br />
  ID_PINSubmitSnglBtn<br />
  ID_PINSubmitDuplBtn<br />
  ID_PINHelp<br />
  ID_PINCanx<br />
  'not selected in callback<br />
  ID_PINInstruLbl<br />
  ID_PINALbl<br />
  ID_PINBLbl                                    'type rect '<br />
end enum 'number up to &amp;h3FF reserved<br />
'<br />
%EM_SETPASSWORDCHAR = &amp;h00CC<br />
%wm_syscommand = &amp;h0112 '(not built into PBWin)<br />
%DT_CalcRect = &amp;h00000400<br />
#resource icon, PIN16, ".&#92;PIN16.ico"<br />
#resource icon, ShowPWon24, ".&#92;ShowPWon24.ico"<br />
#resource icon, ShowPWoff24, ".&#92;ShowPWoff24.ico"<br />
#resource icon, PINSubmit, ".&#92;PINSubmit48.ico"<br />
#resource icon, PINHelp, ".&#92;HelpQuesBtn48.ico"<br />
#resource icon, PINCanx, ".&#92;CancelPIN32.ico"<br />
global gIsVariableDigits, gNumOfDigits as long '(is in PIN_Enter and callback)<br />
global gEntryErrTitle as wstring<br />
declare function ShellExecute lib "Shell32.dll" alias "ShellExecuteW" ( _<br />
    byval hwnd as dword, lpOperation as wstringz, lpFile as wstringz, _<br />
    lpParameters as wstringz, lpDirectory as wstringz, byval nShowCmd as long) _<br />
    as dword<br />
<br />
'############################################################# the function ####<br />
function PIN_Enter alias "PIN_Enter" (byval hParent as dword, _<br />
                                      byval DualPIN as long, _<br />
                                      byval NumOfDigits as long) export as dword<br />
'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -<br />
  static Not1stCall, nFontMono14B as long<br />
  static gEntryErrTitle as wstring<br />
  static RatioX, RatioY as single<br />
  local hPIN_Dlg, PIN as dword<br />
  local TBY, TBX, ID_Dig, PosX as long<br />
  local InstruStr, DigCnt as wstring<br />
  '========================================================= initialization ====<br />
  '····························································· persistent ····<br />
  if Not1stCall = 0 then 'so it is first call<br />
    Not1stCall = -1<br />
    font new "Lucida Console", 14, 1, 1, 0, 0 to nFontMono14B<br />
    dialog default font "Segoe UI", 12, 0, 1<br />
    gEntryErrTitle = "PIN Entry Error"&#36;&#36;<br />
  end if<br />
  '······························································ each call ····<br />
  if (NumOfDigits &lt; 0) or (NumOfDigits &gt; 9) then 'check range<br />
    msgbox "The number of PIN digits can only be optionally"&#36;&#36; + &#36;&#36;crlf + _<br />
         &nbsp;&nbsp;"not used, or 0 to 9 digits long."&#36;&#36; + &#36;&#36;crlf + _<br />
         &nbsp;&nbsp;"Not used or 0 is for a variable length PIN of"&#36;&#36; + &#36;&#36;crlf + _<br />
         &nbsp;&nbsp;"1 to 9 digigits. Otherwise the number of digits is"&#36;&#36; + &#36;&#36;crlf + _<br />
         &nbsp;&nbsp;"fixed by the using program."&#36;&#36;, _<br />
         &nbsp;&nbsp;%mb_ok or %mb_iconerror or %mb_taskmodal, gEntryErrTitle<br />
    exit function<br />
  end if<br />
  if NumOfDigits = 0 then 'variable number<br />
    gIsVariableDigits = -1<br />
    gNumOfDigits = 9<br />
  else<br />
    gNumOfDigits = NumOfDigits<br />
  end if<br />
  '================================================================= dialog ====<br />
  dialog new hParent, "Enter PIN."&#36;&#36;, _<br />
      0, 10, 200, 120, _<br />
   &nbsp;&nbsp;%ds_3dlook or %ds_modalframe or %ds_nofailcreate or %ds_setfont or _<br />
   &nbsp;&nbsp;%ws_caption or %ws_clipsiblings or %ws_dlgframe or %ws_popup or _<br />
   &nbsp;&nbsp;%ws_sysmenu, %ws_ex_left or %ws_ex_ltrreading to hPIN_Dlg<br />
  dialog set icon hPIN_Dlg, "PIN16"<br />
  '------------------------------------------------------- unit/pixel ratio ----<br />
  #if %pb_revision = &amp;h1004<br />
    dialog units hPIN_Dlg, 1000, 1000 to pixels TBY, TBX 'precycle longs<br />
  #else<br />
    dialog units hPIN_Dlg, 1000, 1000 to pixels TBX, TBY<br />
  #endif<br />
  RatioX = 1000 /TBX : RatioY =  1000 / TBY 'mult img px for button units<br />
  '------------------------------------------------------------- PIN instru ----<br />
  if DualPIN then<br />
    InstruStr = "The application requires dual entry for the requested "&#36;&#36; + _<br />
                "task. "&#36;&#36;<br />
  end if<br />
  if NumOfDigits then<br />
    InstruStr +="Enter the "&#36;&#36; + dec&#36;(gNumOfDigits) + " digit PIN. "&#36;&#36;<br />
  else<br />
    InstruStr += "The number of digits is not fixed. The PIN may be 4 "&#36;&#36; + _<br />
               &nbsp;&nbsp;"to 9 digits. Leave unused digits at right empty. "&#36;&#36;<br />
  end if<br />
  InstruStr += "The only characters allowed are ""0"" to ""9""."&#36;&#36;<br />
  control add label, hPIN_Dlg, %ID_PINInstruLbl, InstruStr,  _<br />
   &nbsp;&nbsp;5, 4, 185, 34, %ss_left, %ws_ex_left<br />
  control set color hPIN_Dlg, %ID_PINInstruLbl, -1, &amp;hFAFAFA<br />
  '---------------------------------------------------- "A" digit textboxes ----<br />
  control add label, hPIN_Dlg, %ID_PINALbl, "Enter PIN:"&#36;&#36;, _<br />
   &nbsp;&nbsp;4, 45, 42, 10, %ss_right, %ws_ex_left &nbsp;&nbsp;''55<br />
  '<br />
  PosX = 49<br />
  for ID_Dig = %ID_PINA1Txtbx to %ID_PINA1Txtbx + gNumOfDigits - 1<br />
    control add textbox, hPIN_Dlg, ID_Dig, ""&#36;&#36;, _<br />
     &nbsp;&nbsp;PosX, 44, 12, 10, %es_center or %es_number or %ws_border or _<br />
     &nbsp;&nbsp;%ws_tabstop or %es_password, %ws_ex_clientedge or %ws_ex_left<br />
    control set font hPIN_Dlg, ID_Dig, nFontMono14B<br />
    PosX += 15<br />
  next<br />
  '<br />
  control add imgbutton, hPIN_Dlg, %ID_PINAExposeBtn, "ShowPWon24", _<br />
   &nbsp;&nbsp;181, 43, 28 * RatioX, 28 * RatioY<br />
  if DualPIN = 0 then<br />
    control add imgbutton, hPIN_Dlg, %ID_PINSubmitSnglBtn, "PINSubmit", _<br />
     &nbsp;&nbsp;49 - (52 * RatioX), 59, 52 * RatioX, 52 * RatioY<br />
 &nbsp;&nbsp;' dialog set size hPIN_Dlg, 200, 75 + (52 * RatioY)<br />
  else<br />
  '---------------------------------------------------- "B" digit textboxes ----<br />
    PosX = 49<br />
    control add label, hPIN_Dlg, %ID_PINBLbl, "Reenter PIN:"&#36;&#36;, _<br />
     &nbsp;&nbsp;4, 60, 42, 10, %ss_right, %ws_ex_left &nbsp;&nbsp;''55<br />
    for ID_Dig = %ID_PINB1Txtbx to %ID_PINB1Txtbx + gNumOfDigits - 1<br />
      control add textbox, hPIN_Dlg, ID_Dig, ""&#36;&#36;, _<br />
       &nbsp;&nbsp;PosX, 60, 12, 10, %es_center or %es_number or %ws_border or _<br />
       &nbsp;&nbsp;%ws_tabstop or %es_password, %ws_ex_clientedge or %ws_ex_left<br />
      control set font hPIN_Dlg, ID_Dig, nFontMono14B<br />
      PosX += 15<br />
    next<br />
  '<br />
    control add imgbutton, hPIN_Dlg, %ID_PINBExposeBtn, "ShowPWon24", _<br />
     &nbsp;&nbsp;181, 59, 28 * RatioX, 28 * RatioY<br />
    control add imgbutton, hPIN_Dlg, %ID_PINSubmitDuplBtn, "PINSubmit", _<br />
     &nbsp;&nbsp;49 - (52 * RatioX), 75, 52 * RatioX, 52 * RatioY<br />
  end if<br />
  '----------------------------------------------------- "A" and "B" common ----<br />
  if DualPIN = 0 then<br />
    control add imgbutton, hPIN_Dlg, %ID_PINHelp, "PINHelp", _<br />
     &nbsp;&nbsp;191 - (99 * RatioX), 59, 52 * RatioX, 52 * RatioY<br />
    control add imgbutton, hPIN_Dlg, %ID_PINCanx, "PINCanx", _<br />
     &nbsp;&nbsp;191 - (36 * RatioX), 59 + (16 * RatioY), (36 * RatioX), (36 * RatioY)<br />
    dialog set size hPIN_Dlg, 200, 76 + (52 * RatioY)<br />
  else<br />
    control add imgbutton, hPIN_Dlg, %ID_PINHelp, "PINHelp", _<br />
     &nbsp;&nbsp;191 - (99 * RatioX), 75, 52 * RatioX, 52 * RatioY<br />
<br />
    dialog set size hPIN_Dlg, 200, 92 + (52 * RatioY)<br />
    control add imgbutton, hPIN_Dlg, %ID_PINCanx, "PINCanx", _<br />
     &nbsp;&nbsp;191 - (36 * RatioX), 75 + (16 * RatioY), (36 * RatioX), (36 * RatioY)<br />
  end if<br />
  '<br />
  dialog show modal hPIN_Dlg call  PINDlgCB to PIN<br />
  function = PIN<br />
end function<br />
'================================================================= callback ====<br />
callback function PINDlgCB() as long<br />
  static A_IsExposed, B_IsExposed as long<br />
  static PINStr as wstring<br />
  local TmpL as long<br />
  local TmpS as wstring<br />
  if cb.msg = %wm_command then<br />
    if cb.ctlmsg = %en_update then<br />
      if (cb.ctl &gt;= %ID_PINA1Txtbx) and (cb.ctl &lt;= %ID_PINB9Txtbx) then<br />
        if (gNumOfDigits - 1) &gt; (&amp;h00000F and cb.ctl) then<br />
          control set focus cb.hndl, cb.ctl + 1<br />
        else<br />
          control set focus cb.hndl, %ID_PINB1Txtbx<br />
        end if<br />
      end if<br />
    elseif cb.ctlmsg = %bn_clicked then<br />
      select case as const cb.ctl<br />
        case %ID_PINAExposeBtn<br />
          if A_IsExposed then 'unexpose<br />
            for TmpL = %ID_PINA1Txtbx to %ID_PINA9Txtbx<br />
              control send cb.hndl, TmpL, %EM_SETPASSWORDCHAR,_<br />
               &nbsp;&nbsp;&amp;h2A, 0<br />
            next<br />
            control set imgbutton cb.hndl, %ID_PINAExposeBtn, "ShowPWon24"<br />
            A_IsExposed = 0<br />
          else 'expose<br />
            for TmpL = %ID_PINA1Txtbx to %ID_PINA9Txtbx<br />
              control send cb.hndl, TmpL, %EM_SETPASSWORDCHAR, 0, 0<br />
            next<br />
            control set imgbutton cb.hndl, %ID_PINAExposeBtn, "ShowPWoff24"<br />
            A_IsExposed = -1<br />
          end if<br />
          for TmpL = %ID_PINA1Txtbx to %ID_PINA9Txtbx<br />
            control redraw cb.hndl, TmpL<br />
          next<br />
        case %ID_PINBExposeBtn<br />
          if B_IsExposed then<br />
            for TmpL = %ID_PINB1Txtbx to %ID_PINB9Txtbx<br />
              control send cb.hndl, TmpL, %EM_SETPASSWORDCHAR,_<br />
               &nbsp;&nbsp;&amp;h2A, 0<br />
            next<br />
            control set imgbutton cb.hndl, %ID_PINBExposeBtn, "ShowPWon24"<br />
            B_IsExposed = 0<br />
          else 'expose<br />
            for TmpL = %ID_PINB1Txtbx to %ID_PINB9Txtbx<br />
              control send cb.hndl, TmpL, %EM_SETPASSWORDCHAR, 0, 0<br />
            next<br />
            control set imgbutton cb.hndl, %ID_PINBExposeBtn, "ShowPWoff24"<br />
            B_IsExposed = -1<br />
          end if<br />
          for TmpL = %ID_PINB1Txtbx to %ID_PINB9Txtbx<br />
            control redraw cb.hndl, TmpL<br />
          next<br />
        '············································· submit single button ····<br />
        case %ID_PINSubmitSnglBtn, %ID_PINSubmitDuplBtn<br />
          control get text cb.hndl, %ID_PINA1Txtbx to TmpS<br />
          PINStr = TmpS<br />
          for TmpL = 1 to 8<br />
            control get text cb.hndl, %ID_PINA1Txtbx + TmpL to TmpS<br />
            PINStr += TmpS<br />
          next<br />
          TmpL = len(PINStr)<br />
          if gIsVariableDigits then<br />
            if Tmpl &lt; 4 then<br />
              msgbox "Varible length PINs must be 4 to 9 digits "&#36;&#36; + _<br />
                   &nbsp;&nbsp;"and exactly the same as when it was created."&#36;&#36;, _<br />
                   &nbsp;&nbsp;%mb_ok or %mb_iconerror or %mb_taskmodal, gEntryErrTitle<br />
              exit function<br />
            end if<br />
          else<br />
            if TmpL &lt; gNumOfDigits then<br />
              msgbox "The program using this PIN requires "&#36;&#36; + _<br />
                   &nbsp;&nbsp;dec&#36;(gNumOfDigits) + " digits."&#36;&#36;, _<br />
                   &nbsp;&nbsp;%mb_ok or %mb_iconerror or %mb_taskmodal, gEntryErrTitle<br />
              exit function<br />
            end if<br />
          end if<br />
          if cb.ctl = %ID_PINSubmitDuplBtn then<br />
            for TmpL = 0 to 8<br />
              control get text cb.hndl, %ID_PINB1Txtbx + TmpL to TmpS<br />
                if TmpS = mid&#36;(PINStr, TmpL + 1, 1) then<br />
                  iterate for<br />
                else<br />
                  msgbox "The and the repeat do not match."&#36;&#36;, _<br />
                       &nbsp;&nbsp;%mb_ok or %mb_iconerror or %mb_taskmodal, _<br />
                       &nbsp;&nbsp;gEntryErrTitle<br />
                  exit function<br />
                end if<br />
              next<br />
          end if<br />
          if gIsVariableDigits then<br />
            TmpL = 9 - len(PINStr)<br />
            PINStr += string&#36;&#36;(TmpL, "0"&#36;&#36;)<br />
          end if<br />
          dialog end cb.hndl, val(PINStr)<br />
        case %ID_PINHelp<br />
       &nbsp;&nbsp;'' ShellExecute<br />
            ShellExecute (0, "open"&#36;&#36;, "PIN_Enter_Help.html", ""&#36;&#36;, ""&#36;&#36;, %sw_shownormal)<br />
        case %ID_PINCanx<br />
          goto NoPIN<br />
      end select<br />
    end if<br />
  elseif (lo(word, cb.wparam) = %sc_close) and (cb.msg = %wm_syscommand) then<br />
    goto NoPIN<br />
  end if<br />
  exit function<br />
  NoPIN:<br />
  TmpL = msgbox("""Yes"", to quit PIN entry."&#36;&#36; + &#36;&#36;crlf + _<br />
                """No"", to stay and enter a PIN."&#36;&#36;, _<br />
            %mb_yesno or %mb_iconquestion or %mb_defbutton2 or %mb_taskmodal, _<br />
            "Verify Quitting PIN Entry"&#36;&#36;)<br />
  if TmpL = %idno then<br />
    function = -1<br />
  else<br />
    dialog end cb.hndl<br />
  end if<br />
end function</code></div></div>Demo source: <br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'File PIN_SLL_demo.bas<br />
#compile exe<br />
#dim all<br />
#if %def(%pb_cc32)<br />
  #console off<br />
#endif<br />
declare function PIN_Enter lib "EnterPIN.dll" alias "PIN_Enter" _<br />
                                            (byval hParent as dword, _<br />
                                           &nbsp;&nbsp;byval DuplPIN as long, _<br />
                                           &nbsp;&nbsp;byval NumOfDigits as long) as dword<br />
function pbmain () as long<br />
  local hTWin, PIN as dword<br />
  local Rspnc as wstring<br />
  txt.window("PIN Enter Popup Demonstration"&#36;&#36;, 400, 70, 20, 75) to hTWin<br />
  txt.color = %rgb_green<br />
  txt.print "At any wait use any key to continue. (like now :) )"&#36;&#36;<br />
  txt.waitkey&#36;<br />
  '<br />
  txt.color = %rgb_blue<br />
  txt.print "A dual PIN entry with number of digits set to 4. "&#36;&#36;;<br />
  txt.color = %rgb_black<br />
  txt.print "Returned PIN is: "&#36;&#36; + dec&#36;(PIN_Enter(hTWin, 1, 4), 4)"."&#36;&#36;<br />
  txt.color = %rgb_green<br />
  txt.print<br />
  txt.print "Any key to continue."<br />
  txt.waitkey&#36;<br />
  '<br />
  txt.color = %rgb_blue<br />
  txt.print "A single PIN entry with number of digits set to 4. "&#36;&#36;;<br />
  txt.color = %rgb_black<br />
  txt.print "Returned PIN is: "&#36;&#36; + dec&#36;(PIN_Enter(hTWin, 0, 4), 4)"."&#36;&#36;<br />
  txt.print<br />
  txt.color = %rgb_green<br />
  txt.print """ESC"" to end demo, any other key to continue with next."<br />
  Rspnc = txt.waitkey&#36;<br />
  if Rspnc = &#36;&#36;esc then exit function<br />
  '<br />
  txt.color = %rgb_blue<br />
  txt.print "Number of PIN digits set to 9. "&#36;&#36;;<br />
  txt.color = %rgb_black<br />
  txt.print "Returned PIN is: "&#36;&#36; + dec&#36;(PIN_Enter(hTWin, 0, 9), 9)"."&#36;&#36;<br />
  txt.color = %rgb_blue<br />
  txt.print "You had to enter 9 digits, or ""Cancel PIN"" to get here."&#36;&#36;<br />
  txt.print<br />
  txt.color = %rgb_green<br />
  txt.print """ESC"" to end demo, any other key to continue with next."<br />
  Rspnc = txt.waitkey&#36;<br />
  if Rspnc = &#36;&#36;esc then exit function<br />
  txt.color = %rgb_blue<br />
  txt.print "Number of PIN digits set to 0 (user preference). "&#36;&#36;<br />
  txt.print "Looks like previous, but 4 to 9 digits allowed."&#36;&#36;;<br />
  txt.print "Returned PIN is: "&#36;&#36; + dec&#36;(PIN_Enter(hTWin, 0, 0), 9)"."&#36;&#36;<br />
  '<br />
<br />
  '---------------------------------------------------------------------<br />
  txt.color = %rgb_green<br />
  txt.print<br />
  txt.print "Any key will close."&#36;&#36;<br />
  txt.waitkey&#36;<br />
end function</code></div></div>]]></description>
			<content:encoded><![CDATA[A popup for user to enter a PIN for return to your app. The number of digits is specified in the call, and checked by the function in the DLL.<br />
<br />
Longer description<br />
at <a href="https://www.yarker-dsyc.info/Programs/Misc/PIN/Enter_PIN_DLL.html" target="_blank" rel="noopener" class="mycode_url">https://www.yarker-dsyc.info/Programs/Mi...N_DLL.html</a><br />
<br />
Source, compiled, icons and Help file in ZIP<br />
at <a href="https://www.yarker-dsyc.info/Programs/Misc/PIN/Enter_PIN.zip" target="_blank" rel="noopener" class="mycode_url">https://www.yarker-dsyc.info/Programs/Mi...er_PIN.zip</a><br />
<br />
DLL source: <br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#compile dll<br />
#dim all<br />
'================================================================<br />
enum PIN_IDs singular<br />
  '%en_update group<br />
  ID_PINA1Txtbx = &amp;h3000&amp;<br />
  ID_PINA2Txtbx<br />
  ID_PINA3Txtbx<br />
  ID_PINA4Txtbx<br />
  ID_PINA5Txtbx<br />
  ID_PINA6Txtbx<br />
  ID_PINA7Txtbx<br />
  ID_PINA8Txtbx<br />
  ID_PINA9Txtbx<br />
  ID_PINB1Txtbx = &amp;h3010<br />
  ID_PINB2Txtbx<br />
  ID_PINB3Txtbx<br />
  ID_PINB4Txtbx<br />
  ID_PINB5Txtbx<br />
  ID_PINB6Txtbx<br />
  ID_PINB7Txtbx<br />
  ID_PINB8Txtbx<br />
  ID_PINB9Txtbx<br />
  '%bn_clicked group<br />
  ID_PINAExposeBtn<br />
  ID_PINBExposeBtn<br />
  ID_PINSubmitSnglBtn<br />
  ID_PINSubmitDuplBtn<br />
  ID_PINHelp<br />
  ID_PINCanx<br />
  'not selected in callback<br />
  ID_PINInstruLbl<br />
  ID_PINALbl<br />
  ID_PINBLbl                                    'type rect '<br />
end enum 'number up to &amp;h3FF reserved<br />
'<br />
%EM_SETPASSWORDCHAR = &amp;h00CC<br />
%wm_syscommand = &amp;h0112 '(not built into PBWin)<br />
%DT_CalcRect = &amp;h00000400<br />
#resource icon, PIN16, ".&#92;PIN16.ico"<br />
#resource icon, ShowPWon24, ".&#92;ShowPWon24.ico"<br />
#resource icon, ShowPWoff24, ".&#92;ShowPWoff24.ico"<br />
#resource icon, PINSubmit, ".&#92;PINSubmit48.ico"<br />
#resource icon, PINHelp, ".&#92;HelpQuesBtn48.ico"<br />
#resource icon, PINCanx, ".&#92;CancelPIN32.ico"<br />
global gIsVariableDigits, gNumOfDigits as long '(is in PIN_Enter and callback)<br />
global gEntryErrTitle as wstring<br />
declare function ShellExecute lib "Shell32.dll" alias "ShellExecuteW" ( _<br />
    byval hwnd as dword, lpOperation as wstringz, lpFile as wstringz, _<br />
    lpParameters as wstringz, lpDirectory as wstringz, byval nShowCmd as long) _<br />
    as dword<br />
<br />
'############################################################# the function ####<br />
function PIN_Enter alias "PIN_Enter" (byval hParent as dword, _<br />
                                      byval DualPIN as long, _<br />
                                      byval NumOfDigits as long) export as dword<br />
'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -<br />
  static Not1stCall, nFontMono14B as long<br />
  static gEntryErrTitle as wstring<br />
  static RatioX, RatioY as single<br />
  local hPIN_Dlg, PIN as dword<br />
  local TBY, TBX, ID_Dig, PosX as long<br />
  local InstruStr, DigCnt as wstring<br />
  '========================================================= initialization ====<br />
  '····························································· persistent ····<br />
  if Not1stCall = 0 then 'so it is first call<br />
    Not1stCall = -1<br />
    font new "Lucida Console", 14, 1, 1, 0, 0 to nFontMono14B<br />
    dialog default font "Segoe UI", 12, 0, 1<br />
    gEntryErrTitle = "PIN Entry Error"&#36;&#36;<br />
  end if<br />
  '······························································ each call ····<br />
  if (NumOfDigits &lt; 0) or (NumOfDigits &gt; 9) then 'check range<br />
    msgbox "The number of PIN digits can only be optionally"&#36;&#36; + &#36;&#36;crlf + _<br />
         &nbsp;&nbsp;"not used, or 0 to 9 digits long."&#36;&#36; + &#36;&#36;crlf + _<br />
         &nbsp;&nbsp;"Not used or 0 is for a variable length PIN of"&#36;&#36; + &#36;&#36;crlf + _<br />
         &nbsp;&nbsp;"1 to 9 digigits. Otherwise the number of digits is"&#36;&#36; + &#36;&#36;crlf + _<br />
         &nbsp;&nbsp;"fixed by the using program."&#36;&#36;, _<br />
         &nbsp;&nbsp;%mb_ok or %mb_iconerror or %mb_taskmodal, gEntryErrTitle<br />
    exit function<br />
  end if<br />
  if NumOfDigits = 0 then 'variable number<br />
    gIsVariableDigits = -1<br />
    gNumOfDigits = 9<br />
  else<br />
    gNumOfDigits = NumOfDigits<br />
  end if<br />
  '================================================================= dialog ====<br />
  dialog new hParent, "Enter PIN."&#36;&#36;, _<br />
      0, 10, 200, 120, _<br />
   &nbsp;&nbsp;%ds_3dlook or %ds_modalframe or %ds_nofailcreate or %ds_setfont or _<br />
   &nbsp;&nbsp;%ws_caption or %ws_clipsiblings or %ws_dlgframe or %ws_popup or _<br />
   &nbsp;&nbsp;%ws_sysmenu, %ws_ex_left or %ws_ex_ltrreading to hPIN_Dlg<br />
  dialog set icon hPIN_Dlg, "PIN16"<br />
  '------------------------------------------------------- unit/pixel ratio ----<br />
  #if %pb_revision = &amp;h1004<br />
    dialog units hPIN_Dlg, 1000, 1000 to pixels TBY, TBX 'precycle longs<br />
  #else<br />
    dialog units hPIN_Dlg, 1000, 1000 to pixels TBX, TBY<br />
  #endif<br />
  RatioX = 1000 /TBX : RatioY =  1000 / TBY 'mult img px for button units<br />
  '------------------------------------------------------------- PIN instru ----<br />
  if DualPIN then<br />
    InstruStr = "The application requires dual entry for the requested "&#36;&#36; + _<br />
                "task. "&#36;&#36;<br />
  end if<br />
  if NumOfDigits then<br />
    InstruStr +="Enter the "&#36;&#36; + dec&#36;(gNumOfDigits) + " digit PIN. "&#36;&#36;<br />
  else<br />
    InstruStr += "The number of digits is not fixed. The PIN may be 4 "&#36;&#36; + _<br />
               &nbsp;&nbsp;"to 9 digits. Leave unused digits at right empty. "&#36;&#36;<br />
  end if<br />
  InstruStr += "The only characters allowed are ""0"" to ""9""."&#36;&#36;<br />
  control add label, hPIN_Dlg, %ID_PINInstruLbl, InstruStr,  _<br />
   &nbsp;&nbsp;5, 4, 185, 34, %ss_left, %ws_ex_left<br />
  control set color hPIN_Dlg, %ID_PINInstruLbl, -1, &amp;hFAFAFA<br />
  '---------------------------------------------------- "A" digit textboxes ----<br />
  control add label, hPIN_Dlg, %ID_PINALbl, "Enter PIN:"&#36;&#36;, _<br />
   &nbsp;&nbsp;4, 45, 42, 10, %ss_right, %ws_ex_left &nbsp;&nbsp;''55<br />
  '<br />
  PosX = 49<br />
  for ID_Dig = %ID_PINA1Txtbx to %ID_PINA1Txtbx + gNumOfDigits - 1<br />
    control add textbox, hPIN_Dlg, ID_Dig, ""&#36;&#36;, _<br />
     &nbsp;&nbsp;PosX, 44, 12, 10, %es_center or %es_number or %ws_border or _<br />
     &nbsp;&nbsp;%ws_tabstop or %es_password, %ws_ex_clientedge or %ws_ex_left<br />
    control set font hPIN_Dlg, ID_Dig, nFontMono14B<br />
    PosX += 15<br />
  next<br />
  '<br />
  control add imgbutton, hPIN_Dlg, %ID_PINAExposeBtn, "ShowPWon24", _<br />
   &nbsp;&nbsp;181, 43, 28 * RatioX, 28 * RatioY<br />
  if DualPIN = 0 then<br />
    control add imgbutton, hPIN_Dlg, %ID_PINSubmitSnglBtn, "PINSubmit", _<br />
     &nbsp;&nbsp;49 - (52 * RatioX), 59, 52 * RatioX, 52 * RatioY<br />
 &nbsp;&nbsp;' dialog set size hPIN_Dlg, 200, 75 + (52 * RatioY)<br />
  else<br />
  '---------------------------------------------------- "B" digit textboxes ----<br />
    PosX = 49<br />
    control add label, hPIN_Dlg, %ID_PINBLbl, "Reenter PIN:"&#36;&#36;, _<br />
     &nbsp;&nbsp;4, 60, 42, 10, %ss_right, %ws_ex_left &nbsp;&nbsp;''55<br />
    for ID_Dig = %ID_PINB1Txtbx to %ID_PINB1Txtbx + gNumOfDigits - 1<br />
      control add textbox, hPIN_Dlg, ID_Dig, ""&#36;&#36;, _<br />
       &nbsp;&nbsp;PosX, 60, 12, 10, %es_center or %es_number or %ws_border or _<br />
       &nbsp;&nbsp;%ws_tabstop or %es_password, %ws_ex_clientedge or %ws_ex_left<br />
      control set font hPIN_Dlg, ID_Dig, nFontMono14B<br />
      PosX += 15<br />
    next<br />
  '<br />
    control add imgbutton, hPIN_Dlg, %ID_PINBExposeBtn, "ShowPWon24", _<br />
     &nbsp;&nbsp;181, 59, 28 * RatioX, 28 * RatioY<br />
    control add imgbutton, hPIN_Dlg, %ID_PINSubmitDuplBtn, "PINSubmit", _<br />
     &nbsp;&nbsp;49 - (52 * RatioX), 75, 52 * RatioX, 52 * RatioY<br />
  end if<br />
  '----------------------------------------------------- "A" and "B" common ----<br />
  if DualPIN = 0 then<br />
    control add imgbutton, hPIN_Dlg, %ID_PINHelp, "PINHelp", _<br />
     &nbsp;&nbsp;191 - (99 * RatioX), 59, 52 * RatioX, 52 * RatioY<br />
    control add imgbutton, hPIN_Dlg, %ID_PINCanx, "PINCanx", _<br />
     &nbsp;&nbsp;191 - (36 * RatioX), 59 + (16 * RatioY), (36 * RatioX), (36 * RatioY)<br />
    dialog set size hPIN_Dlg, 200, 76 + (52 * RatioY)<br />
  else<br />
    control add imgbutton, hPIN_Dlg, %ID_PINHelp, "PINHelp", _<br />
     &nbsp;&nbsp;191 - (99 * RatioX), 75, 52 * RatioX, 52 * RatioY<br />
<br />
    dialog set size hPIN_Dlg, 200, 92 + (52 * RatioY)<br />
    control add imgbutton, hPIN_Dlg, %ID_PINCanx, "PINCanx", _<br />
     &nbsp;&nbsp;191 - (36 * RatioX), 75 + (16 * RatioY), (36 * RatioX), (36 * RatioY)<br />
  end if<br />
  '<br />
  dialog show modal hPIN_Dlg call  PINDlgCB to PIN<br />
  function = PIN<br />
end function<br />
'================================================================= callback ====<br />
callback function PINDlgCB() as long<br />
  static A_IsExposed, B_IsExposed as long<br />
  static PINStr as wstring<br />
  local TmpL as long<br />
  local TmpS as wstring<br />
  if cb.msg = %wm_command then<br />
    if cb.ctlmsg = %en_update then<br />
      if (cb.ctl &gt;= %ID_PINA1Txtbx) and (cb.ctl &lt;= %ID_PINB9Txtbx) then<br />
        if (gNumOfDigits - 1) &gt; (&amp;h00000F and cb.ctl) then<br />
          control set focus cb.hndl, cb.ctl + 1<br />
        else<br />
          control set focus cb.hndl, %ID_PINB1Txtbx<br />
        end if<br />
      end if<br />
    elseif cb.ctlmsg = %bn_clicked then<br />
      select case as const cb.ctl<br />
        case %ID_PINAExposeBtn<br />
          if A_IsExposed then 'unexpose<br />
            for TmpL = %ID_PINA1Txtbx to %ID_PINA9Txtbx<br />
              control send cb.hndl, TmpL, %EM_SETPASSWORDCHAR,_<br />
               &nbsp;&nbsp;&amp;h2A, 0<br />
            next<br />
            control set imgbutton cb.hndl, %ID_PINAExposeBtn, "ShowPWon24"<br />
            A_IsExposed = 0<br />
          else 'expose<br />
            for TmpL = %ID_PINA1Txtbx to %ID_PINA9Txtbx<br />
              control send cb.hndl, TmpL, %EM_SETPASSWORDCHAR, 0, 0<br />
            next<br />
            control set imgbutton cb.hndl, %ID_PINAExposeBtn, "ShowPWoff24"<br />
            A_IsExposed = -1<br />
          end if<br />
          for TmpL = %ID_PINA1Txtbx to %ID_PINA9Txtbx<br />
            control redraw cb.hndl, TmpL<br />
          next<br />
        case %ID_PINBExposeBtn<br />
          if B_IsExposed then<br />
            for TmpL = %ID_PINB1Txtbx to %ID_PINB9Txtbx<br />
              control send cb.hndl, TmpL, %EM_SETPASSWORDCHAR,_<br />
               &nbsp;&nbsp;&amp;h2A, 0<br />
            next<br />
            control set imgbutton cb.hndl, %ID_PINBExposeBtn, "ShowPWon24"<br />
            B_IsExposed = 0<br />
          else 'expose<br />
            for TmpL = %ID_PINB1Txtbx to %ID_PINB9Txtbx<br />
              control send cb.hndl, TmpL, %EM_SETPASSWORDCHAR, 0, 0<br />
            next<br />
            control set imgbutton cb.hndl, %ID_PINBExposeBtn, "ShowPWoff24"<br />
            B_IsExposed = -1<br />
          end if<br />
          for TmpL = %ID_PINB1Txtbx to %ID_PINB9Txtbx<br />
            control redraw cb.hndl, TmpL<br />
          next<br />
        '············································· submit single button ····<br />
        case %ID_PINSubmitSnglBtn, %ID_PINSubmitDuplBtn<br />
          control get text cb.hndl, %ID_PINA1Txtbx to TmpS<br />
          PINStr = TmpS<br />
          for TmpL = 1 to 8<br />
            control get text cb.hndl, %ID_PINA1Txtbx + TmpL to TmpS<br />
            PINStr += TmpS<br />
          next<br />
          TmpL = len(PINStr)<br />
          if gIsVariableDigits then<br />
            if Tmpl &lt; 4 then<br />
              msgbox "Varible length PINs must be 4 to 9 digits "&#36;&#36; + _<br />
                   &nbsp;&nbsp;"and exactly the same as when it was created."&#36;&#36;, _<br />
                   &nbsp;&nbsp;%mb_ok or %mb_iconerror or %mb_taskmodal, gEntryErrTitle<br />
              exit function<br />
            end if<br />
          else<br />
            if TmpL &lt; gNumOfDigits then<br />
              msgbox "The program using this PIN requires "&#36;&#36; + _<br />
                   &nbsp;&nbsp;dec&#36;(gNumOfDigits) + " digits."&#36;&#36;, _<br />
                   &nbsp;&nbsp;%mb_ok or %mb_iconerror or %mb_taskmodal, gEntryErrTitle<br />
              exit function<br />
            end if<br />
          end if<br />
          if cb.ctl = %ID_PINSubmitDuplBtn then<br />
            for TmpL = 0 to 8<br />
              control get text cb.hndl, %ID_PINB1Txtbx + TmpL to TmpS<br />
                if TmpS = mid&#36;(PINStr, TmpL + 1, 1) then<br />
                  iterate for<br />
                else<br />
                  msgbox "The and the repeat do not match."&#36;&#36;, _<br />
                       &nbsp;&nbsp;%mb_ok or %mb_iconerror or %mb_taskmodal, _<br />
                       &nbsp;&nbsp;gEntryErrTitle<br />
                  exit function<br />
                end if<br />
              next<br />
          end if<br />
          if gIsVariableDigits then<br />
            TmpL = 9 - len(PINStr)<br />
            PINStr += string&#36;&#36;(TmpL, "0"&#36;&#36;)<br />
          end if<br />
          dialog end cb.hndl, val(PINStr)<br />
        case %ID_PINHelp<br />
       &nbsp;&nbsp;'' ShellExecute<br />
            ShellExecute (0, "open"&#36;&#36;, "PIN_Enter_Help.html", ""&#36;&#36;, ""&#36;&#36;, %sw_shownormal)<br />
        case %ID_PINCanx<br />
          goto NoPIN<br />
      end select<br />
    end if<br />
  elseif (lo(word, cb.wparam) = %sc_close) and (cb.msg = %wm_syscommand) then<br />
    goto NoPIN<br />
  end if<br />
  exit function<br />
  NoPIN:<br />
  TmpL = msgbox("""Yes"", to quit PIN entry."&#36;&#36; + &#36;&#36;crlf + _<br />
                """No"", to stay and enter a PIN."&#36;&#36;, _<br />
            %mb_yesno or %mb_iconquestion or %mb_defbutton2 or %mb_taskmodal, _<br />
            "Verify Quitting PIN Entry"&#36;&#36;)<br />
  if TmpL = %idno then<br />
    function = -1<br />
  else<br />
    dialog end cb.hndl<br />
  end if<br />
end function</code></div></div>Demo source: <br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'File PIN_SLL_demo.bas<br />
#compile exe<br />
#dim all<br />
#if %def(%pb_cc32)<br />
  #console off<br />
#endif<br />
declare function PIN_Enter lib "EnterPIN.dll" alias "PIN_Enter" _<br />
                                            (byval hParent as dword, _<br />
                                           &nbsp;&nbsp;byval DuplPIN as long, _<br />
                                           &nbsp;&nbsp;byval NumOfDigits as long) as dword<br />
function pbmain () as long<br />
  local hTWin, PIN as dword<br />
  local Rspnc as wstring<br />
  txt.window("PIN Enter Popup Demonstration"&#36;&#36;, 400, 70, 20, 75) to hTWin<br />
  txt.color = %rgb_green<br />
  txt.print "At any wait use any key to continue. (like now :) )"&#36;&#36;<br />
  txt.waitkey&#36;<br />
  '<br />
  txt.color = %rgb_blue<br />
  txt.print "A dual PIN entry with number of digits set to 4. "&#36;&#36;;<br />
  txt.color = %rgb_black<br />
  txt.print "Returned PIN is: "&#36;&#36; + dec&#36;(PIN_Enter(hTWin, 1, 4), 4)"."&#36;&#36;<br />
  txt.color = %rgb_green<br />
  txt.print<br />
  txt.print "Any key to continue."<br />
  txt.waitkey&#36;<br />
  '<br />
  txt.color = %rgb_blue<br />
  txt.print "A single PIN entry with number of digits set to 4. "&#36;&#36;;<br />
  txt.color = %rgb_black<br />
  txt.print "Returned PIN is: "&#36;&#36; + dec&#36;(PIN_Enter(hTWin, 0, 4), 4)"."&#36;&#36;<br />
  txt.print<br />
  txt.color = %rgb_green<br />
  txt.print """ESC"" to end demo, any other key to continue with next."<br />
  Rspnc = txt.waitkey&#36;<br />
  if Rspnc = &#36;&#36;esc then exit function<br />
  '<br />
  txt.color = %rgb_blue<br />
  txt.print "Number of PIN digits set to 9. "&#36;&#36;;<br />
  txt.color = %rgb_black<br />
  txt.print "Returned PIN is: "&#36;&#36; + dec&#36;(PIN_Enter(hTWin, 0, 9), 9)"."&#36;&#36;<br />
  txt.color = %rgb_blue<br />
  txt.print "You had to enter 9 digits, or ""Cancel PIN"" to get here."&#36;&#36;<br />
  txt.print<br />
  txt.color = %rgb_green<br />
  txt.print """ESC"" to end demo, any other key to continue with next."<br />
  Rspnc = txt.waitkey&#36;<br />
  if Rspnc = &#36;&#36;esc then exit function<br />
  txt.color = %rgb_blue<br />
  txt.print "Number of PIN digits set to 0 (user preference). "&#36;&#36;<br />
  txt.print "Looks like previous, but 4 to 9 digits allowed."&#36;&#36;;<br />
  txt.print "Returned PIN is: "&#36;&#36; + dec&#36;(PIN_Enter(hTWin, 0, 0), 9)"."&#36;&#36;<br />
  '<br />
<br />
  '---------------------------------------------------------------------<br />
  txt.color = %rgb_green<br />
  txt.print<br />
  txt.print "Any key will close."&#36;&#36;<br />
  txt.waitkey&#36;<br />
end function</code></div></div>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[How many "1" bits /Is Number a Power of 2]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=117</link>
			<pubDate>Mon, 16 Mar 2026 09:54:49 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=23">Dale Yarker</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=117</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'The FASTPROCs here are for both PBWin 10 and PBCC 6.<br />
'For older PB versions change FASTPROC to FUNCTION.<br />
'The demo (in PBMAIN below) would need significant change to compile in older<br />
'versions of PB.<br />
#compile exe<br />
#dim all<br />
#if %def(%pb_cc32) 'ignore this in PBWin<br />
  #console off     'no unneeded in PBCC<br />
#endif<br />
'<br />
'#### Please start thread in Programming for comments/questions. ####<br />
'<br />
'I want a solitare game that was not in old PB formum Source Code.<br />
'Do it myself.<br />
'That requires shuffling cards. Instead of repeating shuffle code for other<br />
'games make a DLL.<br />
'Shuffling requires PRNG(s), and seeds for PRNDs. They are useful for more<br />
'than just card game, so another DLL.<br />
'One of the PRNG algorithms fails if seed is a power of 2.<br />
'<br />
'(PRNG is pseudo Random Number Generator, NUT is Number Under Test.)<br />
'<br />
'How many bits are "1"?<br />
''Simple use of the assembly POPCNT needed in power of 2 procs.<br />
''is DWORD/LONG agnostic<br />
fastproc BitCount(byval NUT as long) as long<br />
  ! popcnt eax, NUT<br />
  ! mov NUT, eax<br />
end fastproc = NUT<br />
'<br />
'Is a DWORD number a power of 2?<br />
fastproc IsPowerOf2Dw (byval NUT as long) as long<br />
  ! mov ebx, NUT        'so not to modify NUT<br />
  ! and ebx, &amp;h00000001 'without this 1 returns as PO2<br />
  !  jz IsEven<br />
  ! jmp NotPO2<br />
  IsEven:<br />
  ! popcnt ebx, NUT<br />
  ! cmp ebx, 1<br />
  ! jne NotPO2<br />
  ! mov NUT, -1<br />
  ! jmp Done<br />
  NotPO2:          '   -4 -8<br />
  ! mov NUT, 0<br />
  Done:<br />
end fastproc = NUT<br />
'<br />
'Is a LONG number a power of 2?<br />
''(Second procedure because in DWORD bit 31 is MSB of the number, while<br />
'''in a LONG it is the sign flag and remaining bits are in 2s compliment<br />
'''format.)<br />
fastproc IsPowerOf2Lg (byval NUT as long) as long<br />
  'check if NUT is negative<br />
  !  bt NUT, 31<br />
  ! jc NotPO2<br />
  ! mov ebx, NUT<br />
  ! and ebx, &amp;00000001<br />
  !  jz IsEven<br />
  ! jmp NotPO2<br />
  IsEven:<br />
  ! popcnt ebx, NUT<br />
  ! cmp ebx, 1<br />
  ! jne NotPO2<br />
  ! mov NUT, -1<br />
  ! jmp Done<br />
  NotPO2:<br />
  ! mov NUT, 0<br />
  Done:<br />
end fastproc = NUT<br />
'<br />
'###############################################################################<br />
function pbmain () as long<br />
  local hTWin, NumD as dword<br />
  local NumL, Retrn as long<br />
  txt.window("BitCount, IsPowerOf2Dw and IsPowerOf2Lg"&#36;&#36;, 75, 50, 27, 40) _<br />
                                                                        to hTWin<br />
  '<br />
  txt.color = %rgb_blue : txt.print "BitCount" : txt.color = %rgb_black<br />
  NumL = 1 : Retrn = BitCount(NumL) : gosub PrintCount<br />
  NumL = 2 : Retrn = BitCount(NumL) : gosub PrintCount<br />
  NumL = 15 : Retrn = BitCount(NumL) : gosub PrintCount<br />
  NumL = &amp;h80050006 : Retrn = BitCount(NumL) : gosub PrintCount<br />
  '<br />
  txt.print : txt.color = %rgb_blue<br />
  txt.print "Is DWORD a power of 2?"&#36;&#36; : txt.color = %rgb_black<br />
  NumD = 1 : Retrn = IsPowerOf2Dw(NumD) : gosub PrintDwd<br />
  NumD = 4 : Retrn = IsPowerOf2Dw(NumD) : gosub PrintDwd<br />
  NumD = 5 : Retrn = IsPowerOf2Dw(NumD) : gosub PrintDwd<br />
  NumD = 4294967294 : Retrn = IsPowerOf2Dw(NumD) : gosub PrintDwd<br />
  '<br />
  txt.print  : txt.color = %rgb_blue<br />
  txt.print "Is LONG a power of 2?"&#36;&#36; : txt.color = %rgb_black<br />
  NumL = 1 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = -1 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = 8 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = -8 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = 128 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = -128 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = 1024 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = -1024 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = 1026 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = -11026 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  '<br />
  txt.print<br />
  txt.color = %rgb_green<br />
  txt.print "Any key to close."&#36;&#36;<br />
  txt.waitkey&#36;<br />
  exit function<br />
  PrintCount:<br />
    txt.print dec&#36;(NumL) + " has " + dec&#36;(Retrn) + " ""1"" bits."<br />
  return<br />
  PrintDwd:<br />
    txt.print dec&#36;(NumD);<br />
    if Retrn then<br />
      txt.print " is"&#36;&#36;<br />
    else<br />
      txt.print " is not"&#36;&#36;<br />
    end if<br />
  return<br />
  PrintLng:<br />
    txt.print dec&#36;(NumL);<br />
    if Retrn then<br />
      txt.print " is"&#36;&#36;<br />
    else<br />
      txt.print " is not"&#36;&#36;<br />
    end if<br />
  return<br />
end function<br />
'#### Please start thread in Programming for comments/questions. ####</code></div></div>]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'The FASTPROCs here are for both PBWin 10 and PBCC 6.<br />
'For older PB versions change FASTPROC to FUNCTION.<br />
'The demo (in PBMAIN below) would need significant change to compile in older<br />
'versions of PB.<br />
#compile exe<br />
#dim all<br />
#if %def(%pb_cc32) 'ignore this in PBWin<br />
  #console off     'no unneeded in PBCC<br />
#endif<br />
'<br />
'#### Please start thread in Programming for comments/questions. ####<br />
'<br />
'I want a solitare game that was not in old PB formum Source Code.<br />
'Do it myself.<br />
'That requires shuffling cards. Instead of repeating shuffle code for other<br />
'games make a DLL.<br />
'Shuffling requires PRNG(s), and seeds for PRNDs. They are useful for more<br />
'than just card game, so another DLL.<br />
'One of the PRNG algorithms fails if seed is a power of 2.<br />
'<br />
'(PRNG is pseudo Random Number Generator, NUT is Number Under Test.)<br />
'<br />
'How many bits are "1"?<br />
''Simple use of the assembly POPCNT needed in power of 2 procs.<br />
''is DWORD/LONG agnostic<br />
fastproc BitCount(byval NUT as long) as long<br />
  ! popcnt eax, NUT<br />
  ! mov NUT, eax<br />
end fastproc = NUT<br />
'<br />
'Is a DWORD number a power of 2?<br />
fastproc IsPowerOf2Dw (byval NUT as long) as long<br />
  ! mov ebx, NUT        'so not to modify NUT<br />
  ! and ebx, &amp;h00000001 'without this 1 returns as PO2<br />
  !  jz IsEven<br />
  ! jmp NotPO2<br />
  IsEven:<br />
  ! popcnt ebx, NUT<br />
  ! cmp ebx, 1<br />
  ! jne NotPO2<br />
  ! mov NUT, -1<br />
  ! jmp Done<br />
  NotPO2:          '   -4 -8<br />
  ! mov NUT, 0<br />
  Done:<br />
end fastproc = NUT<br />
'<br />
'Is a LONG number a power of 2?<br />
''(Second procedure because in DWORD bit 31 is MSB of the number, while<br />
'''in a LONG it is the sign flag and remaining bits are in 2s compliment<br />
'''format.)<br />
fastproc IsPowerOf2Lg (byval NUT as long) as long<br />
  'check if NUT is negative<br />
  !  bt NUT, 31<br />
  ! jc NotPO2<br />
  ! mov ebx, NUT<br />
  ! and ebx, &amp;00000001<br />
  !  jz IsEven<br />
  ! jmp NotPO2<br />
  IsEven:<br />
  ! popcnt ebx, NUT<br />
  ! cmp ebx, 1<br />
  ! jne NotPO2<br />
  ! mov NUT, -1<br />
  ! jmp Done<br />
  NotPO2:<br />
  ! mov NUT, 0<br />
  Done:<br />
end fastproc = NUT<br />
'<br />
'###############################################################################<br />
function pbmain () as long<br />
  local hTWin, NumD as dword<br />
  local NumL, Retrn as long<br />
  txt.window("BitCount, IsPowerOf2Dw and IsPowerOf2Lg"&#36;&#36;, 75, 50, 27, 40) _<br />
                                                                        to hTWin<br />
  '<br />
  txt.color = %rgb_blue : txt.print "BitCount" : txt.color = %rgb_black<br />
  NumL = 1 : Retrn = BitCount(NumL) : gosub PrintCount<br />
  NumL = 2 : Retrn = BitCount(NumL) : gosub PrintCount<br />
  NumL = 15 : Retrn = BitCount(NumL) : gosub PrintCount<br />
  NumL = &amp;h80050006 : Retrn = BitCount(NumL) : gosub PrintCount<br />
  '<br />
  txt.print : txt.color = %rgb_blue<br />
  txt.print "Is DWORD a power of 2?"&#36;&#36; : txt.color = %rgb_black<br />
  NumD = 1 : Retrn = IsPowerOf2Dw(NumD) : gosub PrintDwd<br />
  NumD = 4 : Retrn = IsPowerOf2Dw(NumD) : gosub PrintDwd<br />
  NumD = 5 : Retrn = IsPowerOf2Dw(NumD) : gosub PrintDwd<br />
  NumD = 4294967294 : Retrn = IsPowerOf2Dw(NumD) : gosub PrintDwd<br />
  '<br />
  txt.print  : txt.color = %rgb_blue<br />
  txt.print "Is LONG a power of 2?"&#36;&#36; : txt.color = %rgb_black<br />
  NumL = 1 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = -1 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = 8 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = -8 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = 128 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = -128 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = 1024 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = -1024 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = 1026 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  NumL = -11026 : Retrn = IsPowerOf2Lg(NumL) : gosub PrintLng<br />
  '<br />
  txt.print<br />
  txt.color = %rgb_green<br />
  txt.print "Any key to close."&#36;&#36;<br />
  txt.waitkey&#36;<br />
  exit function<br />
  PrintCount:<br />
    txt.print dec&#36;(NumL) + " has " + dec&#36;(Retrn) + " ""1"" bits."<br />
  return<br />
  PrintDwd:<br />
    txt.print dec&#36;(NumD);<br />
    if Retrn then<br />
      txt.print " is"&#36;&#36;<br />
    else<br />
      txt.print " is not"&#36;&#36;<br />
    end if<br />
  return<br />
  PrintLng:<br />
    txt.print dec&#36;(NumL);<br />
    if Retrn then<br />
      txt.print " is"&#36;&#36;<br />
    else<br />
      txt.print " is not"&#36;&#36;<br />
    end if<br />
  return<br />
end function<br />
'#### Please start thread in Programming for comments/questions. ####</code></div></div>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[PB Hex Viewer]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=113</link>
			<pubDate>Tue, 27 Jan 2026 21:07:19 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=138">Eros Olmi</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=113</guid>
			<description><![CDATA[Ciao,<br />
<br />
I've attached a project I was working some time ago where I experimented AI (OpenAI ChatGPT and Google Gemini together) to code with some help ... well ... a lot of help.<br />
<br />
It's a classic visual HEX viewer control able to show some memory buffer.<br />
Clicking on the left hidden panel there are some options to change viewer<br />
<br />
Attached 100% 10.4 PowerBasic source code and executable.<br />
Do whatever with the sources.<br />
<br />
Hope it can help someone.<br />
<br />
Ciao<br />
Eros<br /><!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=66" target="_blank" title="">HexControl_v0.4.1.PNG</a> (Size: 189.24 KB / Downloads: 15)
<!-- end: postbit_attachments_attachment --><br /><!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/txt.png" title="Basic Program File" border="0" alt=".bas" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=67" target="_blank" title="">HexControl_v0.4.1.bas</a> (Size: 102 KB / Downloads: 21)
<!-- end: postbit_attachments_attachment --><br /><!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=68" target="_blank" title="">HexControl_v0.4.1.EXE.zip</a> (Size: 28.48 KB / Downloads: 8)
<!-- end: postbit_attachments_attachment --><br /><!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=69" target="_blank" title="">HexControl_v0.4.1_2.PNG</a> (Size: 205.51 KB / Downloads: 13)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[Ciao,<br />
<br />
I've attached a project I was working some time ago where I experimented AI (OpenAI ChatGPT and Google Gemini together) to code with some help ... well ... a lot of help.<br />
<br />
It's a classic visual HEX viewer control able to show some memory buffer.<br />
Clicking on the left hidden panel there are some options to change viewer<br />
<br />
Attached 100% 10.4 PowerBasic source code and executable.<br />
Do whatever with the sources.<br />
<br />
Hope it can help someone.<br />
<br />
Ciao<br />
Eros<br /><!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=66" target="_blank" title="">HexControl_v0.4.1.PNG</a> (Size: 189.24 KB / Downloads: 15)
<!-- end: postbit_attachments_attachment --><br /><!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/txt.png" title="Basic Program File" border="0" alt=".bas" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=67" target="_blank" title="">HexControl_v0.4.1.bas</a> (Size: 102 KB / Downloads: 21)
<!-- end: postbit_attachments_attachment --><br /><!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=68" target="_blank" title="">HexControl_v0.4.1.EXE.zip</a> (Size: 28.48 KB / Downloads: 8)
<!-- end: postbit_attachments_attachment --><br /><!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=69" target="_blank" title="">HexControl_v0.4.1_2.PNG</a> (Size: 205.51 KB / Downloads: 13)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Using #RESOURCE RCDATA to implement the missing #RESOURCE AVI]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=110</link>
			<pubDate>Wed, 14 Jan 2026 15:14:41 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=20">George Bleck</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=110</guid>
			<description><![CDATA[I posted this on the new community run PB Users Forum: <a href="https://pbusers.org/forum/viewtopic.php?t=239" target="_blank" rel="noopener" class="mycode_url">https://pbusers.org/forum/viewtopic.php?t=239</a><br />
Sharing here as well as I think it is useful for those that might not have joined there yet.<br />
<br />
============================<br />
<br />
<br />
It always bothered me that PowerBASIC never implemented a #RESOURCE AVI metastatement. If you want to include an AVI for the animation control via #RESOURCE metastatements it's impossible as you cannot animate directly from RCDATA (or other resource sections available via the #RESOURCE metastatement). It can only be done from an AVI resource section (or from a supplied file path). Yes, you can compile the AVI into an AVI resource section using an RC file compiler and add it as a PBR (which still works but was technically deprecated) or RES, but "you cannot add any other #RESOURCE metastatements in your program" as per the PowerBASIC documentation.<br />
<br />
What this code does is allow you to embed AVIs into your code using #RESOURCE RCDATA. After you compile your code to DLL or EXE you run this patcher by supplying the DLL/EXE file path on the command line (or via drag-and-drop). It proceeds to enumerate the AVIs and moves them to an AVI resource section using UpdateResource.<br />
<br />
<span style="color: #c10300;" class="mycode_color"><span style="font-weight: bold;" class="mycode_b">Be aware that you will need to repatch the DLL/EXE each time you re-compile your code.</span></span><br />
As such, this could be included in a tool chain to always run after a compile.<br />
<br />
Processing is written to a log (AVIRscPtch_Log.csv) in the directory the targeted module is located. To facilitate running in a toolchain it runs silently if no errors occur, but uses MESSAGEBOX to report any errors (in addition to the log).<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>' Compiles under PBWIN or PBCC without changes<br />
#COMPILE EXE "AVIRscPtch.exe"<br />
#DIM ALL<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
%UNICODE = 1<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
&#36;DDQ = &#36;DQ + &#36;DQ<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
#INCLUDE "Win32API.inc" ' Roca Headers III_107<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
GLOBAL v_ghMod AS DWORD<br />
GLOBAL v_ghUpd AS DWORD<br />
GLOBAL v_gsExe AS STRING<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION fn_sCSV(BYVAL v_sText AS WSTRING) AS STRING<br />
  IF INSTR(v_sText, ANY CHR&#36;(34, 44)) THEN<br />
      REPLACE &#36;DQ WITH &#36;DDQ IN v_sText<br />
      FUNCTION = &#36;DQ + v_sText + &#36;DQ<br />
  ELSE<br />
      FUNCTION = v_sText<br />
  END IF<br />
END FUNCTION<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION fn_sTimestamp() AS WSTRING<br />
  LOCAL v_uST AS SYSTEMTIME<br />
  GETSYSTEMTIME v_uST<br />
  FUNCTION = _<br />
      FORMAT&#36;(v_uST.wYear, "0000") + "-" + _<br />
      FORMAT&#36;(v_uST.wMonth, "00") + "-" + _<br />
      FORMAT&#36;(v_uST.wDay, "00") + " " + _<br />
      FORMAT&#36;(v_uST.wHour, "00") + ":" + _<br />
      FORMAT&#36;(v_uST.wMinute, "00") + ":" + _<br />
      FORMAT&#36;(v_uST.wSecond, "00") + "Z"<br />
END FUNCTION<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION fn_sGetErrorText(BYVAL v_lError AS LONG) AS WSTRING<br />
  LOCAL v_lReturn AS LONG<br />
  LOCAL v_szBuffer AS WSTRINGZ * 256<br />
  IF v_lError = %ERROR_SUCCESS THEN EXIT FUNCTION<br />
  v_lReturn = FORMATMESSAGE(%FORMAT_MESSAGE_FROM_SYSTEM, BYVAL 0&amp;, v_lError, BYVAL 0&amp;, v_szBuffer, SIZEOF(v_szBuffer), BYVAL 0&amp;)<br />
  IF v_lReturn &gt; 0 THEN FUNCTION = RTRIM&#36;(LEFT&#36;(v_szBuffer, v_lReturn), ANY &#36;CRLF)<br />
END FUNCTION<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
SUB sub_Log(BYVAL v_bError AS LONG, v_sText AS WSTRING)<br />
  LOCAL v_hFile AS LONG<br />
  LOCAL v_lError AS LONG<br />
  LOCAL v_sLogPath AS WSTRING<br />
  LOCAL v_sTimestamp AS WSTRING<br />
  v_sTimestamp = fn_sTimestamp<br />
  v_sLogPath = PATHNAME&#36;(PATH, v_gsExe) + "&#92;AVIRscPtch_Log.csv"<br />
  TRY<br />
      OPEN v_sLogPath FOR APPEND AS # v_hFile<br />
      PRINT # v_hFile, v_sTimestamp + "," + fn_sCSV(v_sText)<br />
      CLOSE # v_hFile<br />
      IF v_bError THEN MESSAGEBOX %HWND_DESKTOP, "Error during patching:" + &#36;LF + v_sText, EXE.NAMEX&#36;, %MB_ICONEXCLAMATION<br />
  CATCH<br />
      v_lError = ERR<br />
      MESSAGEBOX %HWND_DESKTOP, _<br />
        "Could not record message to the log file." + &#36;LF + &#36;LF + _<br />
        "Timestamp:" + &#36;LF + v_sTimestamp + &#36;LF + &#36;LF + _<br />
        "Log Path:" + &#36;LF + v_sLogPath + &#36;LF + &#36;LF + _<br />
        "Error:" + ERROR&#36;(v_lError) + &#36;LF + &#36;LF + _<br />
        "Msg:" + &#36;LF + v_sText, _<br />
        EXE.NAMEX&#36;, %MB_ICONEXCLAMATION<br />
      IF v_hFile THEN CLOSE # v_hFile<br />
  END TRY<br />
END SUB<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION fn_bIsAVI(BYVAL v_pData AS DWORD, BYVAL v_cbData AS DWORD) AS LONG<br />
  IF v_pData = 0 THEN sub_Log 0, "pdata = 0" : EXIT FUNCTION<br />
  IF v_cbData &lt; 12 THEN sub_Log 0, "cbData &lt; 12" : EXIT FUNCTION<br />
  IF PEEK&#36;(v_pData, 4) &lt;&gt; "RIFF" THEN sub_Log 0, "Not a RIFF" : EXIT FUNCTION<br />
  IF PEEK&#36;(v_pData + 8, 4) &lt;&gt; "AVI " THEN sub_Log 0, "Not an AVI" : EXIT FUNCTION<br />
  FUNCTION = %TRUE<br />
END FUNCTION<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION fn_lEnumLangProc( _<br />
      BYVAL v_hModule AS DWORD, _<br />
      BYVAL v_lpType AS DWORD, _<br />
      BYVAL v_lpName AS DWORD, _<br />
      BYVAL v_wLang AS WORD, _<br />
      BYVAL v_lParam AS LONG) AS LONG<br />
  LOCAL v_bOK AS LONG<br />
  LOCAL v_cbData AS DWORD<br />
  LOCAL v_hData AS DWORD<br />
  LOCAL v_hRes AS DWORD<br />
  LOCAL v_lError AS LONG<br />
  LOCAL v_pData AS DWORD<br />
  LOCAL v_wzTypeAVI AS WSTRINGZ * 4<br />
  ' FindResourceEx<br />
  v_hRes = FINDRESOURCEEX(v_hModule, BYVAL %RT_RCDATA, BYVAL v_lpName, v_wLang)<br />
  v_lError = GETLASTERROR<br />
  IF v_hRes = 0 THEN<br />
      sub_Log 0, "Error calling FindResourceEx: " + fn_sGetErrorText(v_lError)<br />
      FUNCTION = %TRUE<br />
      EXIT FUNCTION<br />
  END IF<br />
  v_cbData = SIZEOFRESOURCE(v_hModule, v_hRes)<br />
  v_hData = LOADRESOURCE(v_hModule, v_hRes)<br />
  v_pData = LOCKRESOURCE(v_hData)<br />
  IF fn_bIsAVI(v_pData, v_cbData) THEN<br />
      v_wzTypeAVI = "AVI"<br />
      ' UpdateResource - Copy resource to new resource type<br />
      v_bOK = UPDATERESOURCE(v_ghUpd, BYVAL VARPTR(v_wzTypeAVI), BYVAL v_lpName, v_wLang, BYVAL v_pData, v_cbData)<br />
      v_lError = GETLASTERROR<br />
      IF v_bOK THEN<br />
        ' UpdateResource - Remove resource from old resource type<br />
        v_bOK = UPDATERESOURCE(v_ghUpd, BYVAL %RT_RCDATA, BYVAL v_lpName, v_wLang, BYVAL %NULL, 0)<br />
        v_lError = GETLASTERROR<br />
        IF v_bOK = %FALSE THEN sub_Log 1, "Error calling UpdateResource (delete old): " + fn_sGetErrorText(v_lError)<br />
      ELSE<br />
        sub_Log 1, "Error calling UpdateResource (copy to new): " + fn_sGetErrorText(v_lError)<br />
      END IF<br />
  END IF<br />
  FUNCTION = %TRUE<br />
END FUNCTION<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION fn_lEnumNameProc( _<br />
      BYVAL v_hModule AS DWORD, _<br />
      BYVAL v_lpType AS DWORD, _<br />
      BYVAL v_lpName AS DWORD, _<br />
      BYVAL v_lParam AS LONG) AS LONG<br />
  ENUMRESOURCELANGUAGES(v_hModule, BYVAL %RT_RCDATA, BYVAL v_lpName, CODEPTR(fn_lEnumLangProc), 0)<br />
  FUNCTION = %TRUE<br />
END FUNCTION<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION PBMAIN() AS LONG<br />
  LOCAL v_bOK AS LONG<br />
  LOCAL v_lError AS LONG<br />
  LOCAL v_sCmd AS STRING<br />
  LOCAL v_wzExe AS WSTRINGZ * %MAX_PATH<br />
  v_sCmd = TRIM&#36;(COMMAND&#36;, ANY CHR&#36;(32, 34))<br />
  IF LEN(v_sCmd) THEN<br />
      v_gsExe = v_sCmd<br />
  ELSE<br />
      sub_Log 1, "Please supply a module (DLL/EXE) path to patch"<br />
      EXIT FUNCTION<br />
  END IF<br />
  IF ISFILE(v_gsExe) = %FALSE THEN<br />
      sub_Log 1, "Module (DLL/EXE) path does not exist"<br />
      EXIT FUNCTION<br />
  END IF<br />
  v_wzExe = v_gsExe<br />
  sub_Log 0, "Attemping to patch module: " + &#36;DQ + v_gsExe + &#36;DQ<br />
  v_ghMod = LOADLIBRARYEX(BYVAL VARPTR(v_wzExe), BYVAL %NULL, %LOAD_LIBRARY_AS_DATAFILE)<br />
  v_lError = GETLASTERROR<br />
  IF v_ghMod = 0 THEN<br />
      sub_Log 1, "Error loading module: " + fn_sGetErrorText(v_lError)<br />
      EXIT FUNCTION<br />
  END IF<br />
  ' BeginUpdateResource<br />
  v_ghUpd = BEGINUPDATERESOURCE(BYVAL VARPTR(v_wzExe), %FALSE)<br />
  v_lError = GETLASTERROR<br />
  IF v_ghUpd = 0 THEN<br />
      sub_Log 1, "Error calling BeginUpdateResource: " + fn_sGetErrorText(v_lError)<br />
      FREELIBRARY v_ghMod<br />
      EXIT FUNCTION<br />
  END IF<br />
  ' EnumResourceNames<br />
  v_bOK = ENUMRESOURCENAMES(v_ghMod, BYVAL %RT_RCDATA, CODEPTR(fn_lEnumNameProc), 0)<br />
  v_lError = GETLASTERROR<br />
  IF v_bOK = %FALSE THEN<br />
      sub_Log 1, "Error calling EnumResourceNames: " + fn_sGetErrorText(v_lError)<br />
      FREELIBRARY v_ghMod<br />
      EXIT FUNCTION<br />
  END IF<br />
  ' Release the module<br />
  FREELIBRARY v_ghMod<br />
  ' EndUpdateResource<br />
  v_bOK = ENDUPDATERESOURCE(v_ghUpd, %FALSE)<br />
  v_lError = GETLASTERROR<br />
  IF v_bOK = %FALSE THEN<br />
      sub_Log 1, "Error calling EndUpdateResource: " + fn_sGetErrorText(v_lError)<br />
      EXIT FUNCTION<br />
  END IF<br />
  sub_Log 0, "Module patched"<br />
END FUNCTION</code></div></div>]]></description>
			<content:encoded><![CDATA[I posted this on the new community run PB Users Forum: <a href="https://pbusers.org/forum/viewtopic.php?t=239" target="_blank" rel="noopener" class="mycode_url">https://pbusers.org/forum/viewtopic.php?t=239</a><br />
Sharing here as well as I think it is useful for those that might not have joined there yet.<br />
<br />
============================<br />
<br />
<br />
It always bothered me that PowerBASIC never implemented a #RESOURCE AVI metastatement. If you want to include an AVI for the animation control via #RESOURCE metastatements it's impossible as you cannot animate directly from RCDATA (or other resource sections available via the #RESOURCE metastatement). It can only be done from an AVI resource section (or from a supplied file path). Yes, you can compile the AVI into an AVI resource section using an RC file compiler and add it as a PBR (which still works but was technically deprecated) or RES, but "you cannot add any other #RESOURCE metastatements in your program" as per the PowerBASIC documentation.<br />
<br />
What this code does is allow you to embed AVIs into your code using #RESOURCE RCDATA. After you compile your code to DLL or EXE you run this patcher by supplying the DLL/EXE file path on the command line (or via drag-and-drop). It proceeds to enumerate the AVIs and moves them to an AVI resource section using UpdateResource.<br />
<br />
<span style="color: #c10300;" class="mycode_color"><span style="font-weight: bold;" class="mycode_b">Be aware that you will need to repatch the DLL/EXE each time you re-compile your code.</span></span><br />
As such, this could be included in a tool chain to always run after a compile.<br />
<br />
Processing is written to a log (AVIRscPtch_Log.csv) in the directory the targeted module is located. To facilitate running in a toolchain it runs silently if no errors occur, but uses MESSAGEBOX to report any errors (in addition to the log).<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>' Compiles under PBWIN or PBCC without changes<br />
#COMPILE EXE "AVIRscPtch.exe"<br />
#DIM ALL<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
%UNICODE = 1<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
&#36;DDQ = &#36;DQ + &#36;DQ<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
#INCLUDE "Win32API.inc" ' Roca Headers III_107<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
GLOBAL v_ghMod AS DWORD<br />
GLOBAL v_ghUpd AS DWORD<br />
GLOBAL v_gsExe AS STRING<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION fn_sCSV(BYVAL v_sText AS WSTRING) AS STRING<br />
  IF INSTR(v_sText, ANY CHR&#36;(34, 44)) THEN<br />
      REPLACE &#36;DQ WITH &#36;DDQ IN v_sText<br />
      FUNCTION = &#36;DQ + v_sText + &#36;DQ<br />
  ELSE<br />
      FUNCTION = v_sText<br />
  END IF<br />
END FUNCTION<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION fn_sTimestamp() AS WSTRING<br />
  LOCAL v_uST AS SYSTEMTIME<br />
  GETSYSTEMTIME v_uST<br />
  FUNCTION = _<br />
      FORMAT&#36;(v_uST.wYear, "0000") + "-" + _<br />
      FORMAT&#36;(v_uST.wMonth, "00") + "-" + _<br />
      FORMAT&#36;(v_uST.wDay, "00") + " " + _<br />
      FORMAT&#36;(v_uST.wHour, "00") + ":" + _<br />
      FORMAT&#36;(v_uST.wMinute, "00") + ":" + _<br />
      FORMAT&#36;(v_uST.wSecond, "00") + "Z"<br />
END FUNCTION<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION fn_sGetErrorText(BYVAL v_lError AS LONG) AS WSTRING<br />
  LOCAL v_lReturn AS LONG<br />
  LOCAL v_szBuffer AS WSTRINGZ * 256<br />
  IF v_lError = %ERROR_SUCCESS THEN EXIT FUNCTION<br />
  v_lReturn = FORMATMESSAGE(%FORMAT_MESSAGE_FROM_SYSTEM, BYVAL 0&amp;, v_lError, BYVAL 0&amp;, v_szBuffer, SIZEOF(v_szBuffer), BYVAL 0&amp;)<br />
  IF v_lReturn &gt; 0 THEN FUNCTION = RTRIM&#36;(LEFT&#36;(v_szBuffer, v_lReturn), ANY &#36;CRLF)<br />
END FUNCTION<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
SUB sub_Log(BYVAL v_bError AS LONG, v_sText AS WSTRING)<br />
  LOCAL v_hFile AS LONG<br />
  LOCAL v_lError AS LONG<br />
  LOCAL v_sLogPath AS WSTRING<br />
  LOCAL v_sTimestamp AS WSTRING<br />
  v_sTimestamp = fn_sTimestamp<br />
  v_sLogPath = PATHNAME&#36;(PATH, v_gsExe) + "&#92;AVIRscPtch_Log.csv"<br />
  TRY<br />
      OPEN v_sLogPath FOR APPEND AS # v_hFile<br />
      PRINT # v_hFile, v_sTimestamp + "," + fn_sCSV(v_sText)<br />
      CLOSE # v_hFile<br />
      IF v_bError THEN MESSAGEBOX %HWND_DESKTOP, "Error during patching:" + &#36;LF + v_sText, EXE.NAMEX&#36;, %MB_ICONEXCLAMATION<br />
  CATCH<br />
      v_lError = ERR<br />
      MESSAGEBOX %HWND_DESKTOP, _<br />
        "Could not record message to the log file." + &#36;LF + &#36;LF + _<br />
        "Timestamp:" + &#36;LF + v_sTimestamp + &#36;LF + &#36;LF + _<br />
        "Log Path:" + &#36;LF + v_sLogPath + &#36;LF + &#36;LF + _<br />
        "Error:" + ERROR&#36;(v_lError) + &#36;LF + &#36;LF + _<br />
        "Msg:" + &#36;LF + v_sText, _<br />
        EXE.NAMEX&#36;, %MB_ICONEXCLAMATION<br />
      IF v_hFile THEN CLOSE # v_hFile<br />
  END TRY<br />
END SUB<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION fn_bIsAVI(BYVAL v_pData AS DWORD, BYVAL v_cbData AS DWORD) AS LONG<br />
  IF v_pData = 0 THEN sub_Log 0, "pdata = 0" : EXIT FUNCTION<br />
  IF v_cbData &lt; 12 THEN sub_Log 0, "cbData &lt; 12" : EXIT FUNCTION<br />
  IF PEEK&#36;(v_pData, 4) &lt;&gt; "RIFF" THEN sub_Log 0, "Not a RIFF" : EXIT FUNCTION<br />
  IF PEEK&#36;(v_pData + 8, 4) &lt;&gt; "AVI " THEN sub_Log 0, "Not an AVI" : EXIT FUNCTION<br />
  FUNCTION = %TRUE<br />
END FUNCTION<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION fn_lEnumLangProc( _<br />
      BYVAL v_hModule AS DWORD, _<br />
      BYVAL v_lpType AS DWORD, _<br />
      BYVAL v_lpName AS DWORD, _<br />
      BYVAL v_wLang AS WORD, _<br />
      BYVAL v_lParam AS LONG) AS LONG<br />
  LOCAL v_bOK AS LONG<br />
  LOCAL v_cbData AS DWORD<br />
  LOCAL v_hData AS DWORD<br />
  LOCAL v_hRes AS DWORD<br />
  LOCAL v_lError AS LONG<br />
  LOCAL v_pData AS DWORD<br />
  LOCAL v_wzTypeAVI AS WSTRINGZ * 4<br />
  ' FindResourceEx<br />
  v_hRes = FINDRESOURCEEX(v_hModule, BYVAL %RT_RCDATA, BYVAL v_lpName, v_wLang)<br />
  v_lError = GETLASTERROR<br />
  IF v_hRes = 0 THEN<br />
      sub_Log 0, "Error calling FindResourceEx: " + fn_sGetErrorText(v_lError)<br />
      FUNCTION = %TRUE<br />
      EXIT FUNCTION<br />
  END IF<br />
  v_cbData = SIZEOFRESOURCE(v_hModule, v_hRes)<br />
  v_hData = LOADRESOURCE(v_hModule, v_hRes)<br />
  v_pData = LOCKRESOURCE(v_hData)<br />
  IF fn_bIsAVI(v_pData, v_cbData) THEN<br />
      v_wzTypeAVI = "AVI"<br />
      ' UpdateResource - Copy resource to new resource type<br />
      v_bOK = UPDATERESOURCE(v_ghUpd, BYVAL VARPTR(v_wzTypeAVI), BYVAL v_lpName, v_wLang, BYVAL v_pData, v_cbData)<br />
      v_lError = GETLASTERROR<br />
      IF v_bOK THEN<br />
        ' UpdateResource - Remove resource from old resource type<br />
        v_bOK = UPDATERESOURCE(v_ghUpd, BYVAL %RT_RCDATA, BYVAL v_lpName, v_wLang, BYVAL %NULL, 0)<br />
        v_lError = GETLASTERROR<br />
        IF v_bOK = %FALSE THEN sub_Log 1, "Error calling UpdateResource (delete old): " + fn_sGetErrorText(v_lError)<br />
      ELSE<br />
        sub_Log 1, "Error calling UpdateResource (copy to new): " + fn_sGetErrorText(v_lError)<br />
      END IF<br />
  END IF<br />
  FUNCTION = %TRUE<br />
END FUNCTION<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION fn_lEnumNameProc( _<br />
      BYVAL v_hModule AS DWORD, _<br />
      BYVAL v_lpType AS DWORD, _<br />
      BYVAL v_lpName AS DWORD, _<br />
      BYVAL v_lParam AS LONG) AS LONG<br />
  ENUMRESOURCELANGUAGES(v_hModule, BYVAL %RT_RCDATA, BYVAL v_lpName, CODEPTR(fn_lEnumLangProc), 0)<br />
  FUNCTION = %TRUE<br />
END FUNCTION<br />
<br />
'----------------------------------------------------------------------------(')<br />
<br />
FUNCTION PBMAIN() AS LONG<br />
  LOCAL v_bOK AS LONG<br />
  LOCAL v_lError AS LONG<br />
  LOCAL v_sCmd AS STRING<br />
  LOCAL v_wzExe AS WSTRINGZ * %MAX_PATH<br />
  v_sCmd = TRIM&#36;(COMMAND&#36;, ANY CHR&#36;(32, 34))<br />
  IF LEN(v_sCmd) THEN<br />
      v_gsExe = v_sCmd<br />
  ELSE<br />
      sub_Log 1, "Please supply a module (DLL/EXE) path to patch"<br />
      EXIT FUNCTION<br />
  END IF<br />
  IF ISFILE(v_gsExe) = %FALSE THEN<br />
      sub_Log 1, "Module (DLL/EXE) path does not exist"<br />
      EXIT FUNCTION<br />
  END IF<br />
  v_wzExe = v_gsExe<br />
  sub_Log 0, "Attemping to patch module: " + &#36;DQ + v_gsExe + &#36;DQ<br />
  v_ghMod = LOADLIBRARYEX(BYVAL VARPTR(v_wzExe), BYVAL %NULL, %LOAD_LIBRARY_AS_DATAFILE)<br />
  v_lError = GETLASTERROR<br />
  IF v_ghMod = 0 THEN<br />
      sub_Log 1, "Error loading module: " + fn_sGetErrorText(v_lError)<br />
      EXIT FUNCTION<br />
  END IF<br />
  ' BeginUpdateResource<br />
  v_ghUpd = BEGINUPDATERESOURCE(BYVAL VARPTR(v_wzExe), %FALSE)<br />
  v_lError = GETLASTERROR<br />
  IF v_ghUpd = 0 THEN<br />
      sub_Log 1, "Error calling BeginUpdateResource: " + fn_sGetErrorText(v_lError)<br />
      FREELIBRARY v_ghMod<br />
      EXIT FUNCTION<br />
  END IF<br />
  ' EnumResourceNames<br />
  v_bOK = ENUMRESOURCENAMES(v_ghMod, BYVAL %RT_RCDATA, CODEPTR(fn_lEnumNameProc), 0)<br />
  v_lError = GETLASTERROR<br />
  IF v_bOK = %FALSE THEN<br />
      sub_Log 1, "Error calling EnumResourceNames: " + fn_sGetErrorText(v_lError)<br />
      FREELIBRARY v_ghMod<br />
      EXIT FUNCTION<br />
  END IF<br />
  ' Release the module<br />
  FREELIBRARY v_ghMod<br />
  ' EndUpdateResource<br />
  v_bOK = ENDUPDATERESOURCE(v_ghUpd, %FALSE)<br />
  v_lError = GETLASTERROR<br />
  IF v_bOK = %FALSE THEN<br />
      sub_Log 1, "Error calling EndUpdateResource: " + fn_sGetErrorText(v_lError)<br />
      EXIT FUNCTION<br />
  END IF<br />
  sub_Log 0, "Module patched"<br />
END FUNCTION</code></div></div>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Doubly Linked String/WString List (Parser?)]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=91</link>
			<pubDate>Sun, 05 Oct 2025 08:15:44 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=164">Stanley Durham</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=91</guid>
			<description><![CDATA[Compiler? Do you write a compiler or parse code for an existing compiler?<br />
Two versions, String or WString. No dependencies. Fast, get 1,0000,000 values in less than 1/10 of a second.<br />
<br />
This is something I did thinking it might be useful for parsing code. It’s a list, queue, stack and deque.  You can push, peek pop from front or back.<br />
But it also has a cursor that can be moved forward and backwards. The cursor position also acts as a queue, stack and deque. You can push, peek, pop and delete before and after cursor position. The cursor can save its position one time; you can move around and then return.<br />
Supports Split/Join. <br />
It can load a text file and be saved as a text file (code file). WString version uses UTF8. <br />
It can be stored/restored to/from file, binary format.<br />
It can get a list of files, folders, sub-folders and all files in sub-folders matching a mask.<br />
 <br />
There are probably a lot better tools out there, but simplicity appeals to me. It seems like each different tool is a whole new programming language to learn. And different versions of the same type of compiler have different tools. <br />
 <br />
The idea is that you have an input and output list containing lines of code. One or more temporary list can be used to split a line of code apart. Push the output to the output list.<br />
 <br />
It’s an allocated container, must call …New() before use. Call PtrFree(p) to free handle or pointer. Errors are logged if %LogOnError is defined. Message on error if %MessageOnError is defined. You can use the ExitIf() macro to log parsing errors. <br />
 <br />
It might be useful, might not.<br />
<hr class="mycode_hr" />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'StrList.inc<br />
<br />
'Public domain, use at own risk. SDurham<br />
<br />
'Function StrListNew() As Long<br />
    'allocate new instance : return container pointer : call PtrFree(p) to free pointer<br />
<br />
'Sub StrListClear(ByVal p As StrListT Ptr)<br />
    'empty container<br />
<br />
'Function StrListCount(ByVal p As StrListT Ptr) As Long<br />
    'get item count<br />
<br />
'Sub StrListAdd(ByVal p As StrListT Ptr, ByRef value As String)<br />
    'append value<br />
<br />
'Function StrListCursor(ByVal p As StrListT Ptr) As Long<br />
    'true/false if cursor valid<br />
<br />
'Function StrListFirst(ByVal p As StrListT Ptr) As Long<br />
    'move cursor to first node : true/false success<br />
<br />
'Function StrListNext(ByVal p As StrListT Ptr) As Long<br />
    'move cursor to next node : true/false success<br />
<br />
'Function StrListLast(ByVal p As StrListT Ptr) As Long<br />
    'move cursor to last node : true/false success<br />
<br />
'Function StrListPervious(ByVal p As StrListT Ptr) As Long<br />
    'move cursor to previous node : true/false success<br />
<br />
'Function StrListRember(ByVal p As StrListT Ptr) As Long<br />
    'remember cursor position : fail if cursor null : true/false success<br />
<br />
'Function StrListReturn(ByVal p As StrListT Ptr) As Long<br />
    'return to remembered position : fail if node deleted : true/false success<br />
<br />
'Function StrListGet(ByVal p As StrListT Ptr) As String<br />
    'get value at cursor position : null if cursor null<br />
<br />
'Function StrListSet(ByVal p As StrListT Ptr, ByRef value As String) As Long<br />
    'set value at cursor position : fail if cursor null : true/false success<br />
<br />
'Function StrListDeleteForward(ByVal p As StrListT Ptr) As Long<br />
    'move to next node and delete node at cursor position : fail if cursor null : true/false success<br />
    'cursor will be null if no next node<br />
<br />
'Function StrListDeleteBackward(ByVal p As StrListT Ptr) As Long<br />
    'move to previous node and delete node at cursor position : fail if cursor null : true/false success<br />
    'cursor will be null if no previous node<br />
<br />
'Function StrListIsBefore(ByVal p As StrListT Ptr) As Long<br />
    'true/false if there is a node before cursor position<br />
<br />
'Function StrListIsAfter(ByVal p As StrListT Ptr) As Long<br />
    'true/false if there is a node after cursor position<br />
<br />
'Function StrListDeleteBefore(ByVal p As StrListT Ptr) As Long<br />
    'delete node before cursor position : fail if cursor null or no previous node : true/false success<br />
<br />
'Function StrListDeleteAfter(ByVal p As StrListT Ptr) As Long<br />
    'delete node after cursor position : fail if cursor null or no next node : true/false success<br />
<br />
'Function StrListPushBefore(ByVal p As StrListT Ptr, ByRef value As String) As Long<br />
    'add value before cursor position : fail if cursor null : true/false success<br />
<br />
'Function StrListPushAfter(ByVal p As StrListT Ptr, ByRef value As String) As Long<br />
    'add value after cursor position : fail if cursor null : true/false success<br />
    'add value before cursor position : fail if cursor null : true/false success<br />
<br />
'Function StrListPeekBefore(ByVal p As StrListT Ptr) As String<br />
    'get value before cursor position : null if cursor invalid or no previous node<br />
<br />
'Function StrListPeekAfter(ByVal p As StrListT Ptr) As String<br />
    'get value after cursor position : null if cursor invalid or no next node<br />
<br />
'Function StrListPopBefore(ByVal p As StrListT Ptr) As String<br />
    'get and remove value before cursor position : null if cursor invalid or no previous node<br />
<br />
'Function StrListPopAfter(ByVal p As StrListT Ptr) As String<br />
    'get and remove value after cursor position : null if cursor invalid or no next node<br />
<br />
'Sub StrListPushFirst(ByVal p As StrListT Ptr, ByRef value As String)<br />
    'insert at front<br />
<br />
'Sub StrListPushLast(ByVal p As StrListT Ptr, ByRef value As String)<br />
    'append to end<br />
<br />
'Function StrListPeekFirst(ByVal p As StrListT Ptr) As String<br />
    'get fist value : null if list empty<br />
<br />
'Function StrListPeekLast(ByVal p As StrListT Ptr) As String<br />
    'get last value : null list empty<br />
<br />
'Function StrListPopFirst(ByVal p As StrListT Ptr) As String<br />
    'get and remove fist value : null if list empty<br />
<br />
'Function StrListPopLast(ByVal p As StrListT Ptr) As String<br />
    'get and remove last value : null if list empty<br />
<br />
'Sub StrListImport(ByVal p As StrListT Ptr, a() As String)<br />
    'import PB array<br />
<br />
'Sub StrListExport(ByVal p As StrListT Ptr, a() As String)<br />
    'export to PB array<br />
<br />
'Sub StrListSplit(ByVal p As StrListT Ptr, ByVal delimited As String, ByVal delimiter As String)<br />
    'split array on delimited string<br />
<br />
'Function StrListJoin(ByVal p As StrListT Ptr, ByVal delimiter As String) As String<br />
    'join array on delimiter<br />
<br />
'Sub StrListToText(ByVal p As StrListT Ptr, ByVal file As WString)<br />
    'store list as text file<br />
<br />
'Sub StrListFromText(ByVal p As StrListT Ptr, ByVal file As WString)<br />
    'load text file<br />
<br />
'Sub StrListFolders(ByVal p As StrListT Ptr, ByVal rootFolder As String)<br />
    'get all folders in root folder<br />
<br />
'Sub StrListFiles(ByVal p As StrListT Ptr, ByVal folder As String, ByVal mask As String)<br />
    'get all files in folder matching mask<br />
<br />
'Sub StrListAllFolders(ByVal p As StrListT Ptr, ByVal rootFolder As String)<br />
    'get all folders in root folder and sub-folders, including root folder<br />
<br />
'Sub StrListAllFiles(ByVal p As StrListT Ptr, ByVal rootFolder As String, ByVal mask As String)<br />
    'get all files in root folder, and sub-folders, matching mask<br />
<br />
'Function StrListStore(ByVal p As StrListT Ptr) As String<br />
    'store container to String<br />
<br />
'Sub StrListRestore(ByVal p As StrListT Ptr, ByRef stored As String)<br />
    'restore container from String<br />
<br />
'Sub StrListFileStore(ByVal p As StrListT Ptr, ByVal file As WString)<br />
    'store container to File<br />
<br />
'Sub StrListFileRestore(ByVal p As StrListT Ptr, ByVal file As WString)<br />
    'restore container from File<br />
<br />
#If Not %Def(%ExitIf240526)<br />
    %ExitIf240526 = 1<br />
    '----------------------------------------------------------------------<br />
    'Error Exit Macro<br />
    'Exit procedure with error message<br />
    'if %LogOnError defined then errors logged to app folder<br />
    'if %MessageOnError defined then message on error<br />
    'if %HaltOnError defined then app halt with message on error<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    %ExitIfErr = 151<br />
    '----------------------------------------------------------------------<br />
    Macro ExitIf(test, message, exitWhat)<br />
        If test Then<br />
            ExitLog FuncName&#36; + "() " + message<br />
            Error %ExitIfErr<br />
            Exit exitWhat<br />
        End If<br />
    End Macro<br />
    Macro ExitF(test, message) = ExitIf(test, message, Function)<br />
    Macro ExitS(test, message) = ExitIf(test, message, Sub)<br />
    Macro ExitM(test, message) = ExitIf(test, message, Method)<br />
    Macro ExitP(test, message) = ExitIf(test, message, Property)<br />
    Macro ExitMC(test, message) = ExitIf(test, message, Macro)<br />
    'GoTo with error message<br />
    Macro GoToIf(test, message, goWhere)<br />
        If test Then<br />
            ExitLog FuncName&#36; +": "+ message<br />
            Error %ExitIfErr<br />
            GoTo goWhere<br />
        End If<br />
    End Macro<br />
    '----------------------------------------------------------------------<br />
    Sub ExitLog(ByVal message As String) Private<br />
        Local h As Long<br />
        h = FreeFile<br />
        #If %Def(%LogOnError) Or %Def(%MessageOnError) Or %Def(%HaltOnError)<br />
            Open Exe.Path&#36;+"Error.log" For Append As h<br />
            If Lof(h) &lt; 16000 Then<br />
                Print# h, Date&#36; +", "+ Time&#36; +", "+ Exe.Full&#36; +", "+ message<br />
            End If<br />
            Close h<br />
        #EndIf<br />
        #If %Def(%MessageOnError) Or %Def(%HaltOnError)<br />
            ? message,,"Error!"<br />
        #EndIf<br />
        #If %Def(%HaltOnError)<br />
            End<br />
        #EndIf<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
#EndIf '%ExitIf240526<br />
<br />
#If Not %Def(%Memory230925)<br />
    %Memory230925 = 1<br />
    '----------------------------------------------------------------------<br />
    'Memory Allocation<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    Declare Function GlobalAlloc Lib "Kernel32.dll" Alias "GlobalAlloc" (ByVal uFlags As Dword, ByVal dwBytes As Dword) As Dword<br />
    Declare Function GlobalReAlloc Lib "Kernel32.dll" Alias "GlobalReAlloc" (ByVal hMem As Dword, ByVal dwBytes As Dword, ByVal uFlags As Dword) As Dword<br />
    Declare Function GlobalFree Lib "Kernel32.dll" Alias "GlobalFree" (ByVal hMem As Dword) As Dword<br />
    %MEMFIXED = &amp;H0000 : %MEMMOVEABLE = &amp;H0002 : %MEMZEROINIT = &amp;H0040 : %MEMGPTR = (%MEMZEROINIT Or %MEMFIXED)<br />
    '----------------------------------------------------------------------<br />
    Function MemAlloc(ByVal bytes As Long) As Long<br />
        'allocate memory<br />
        If bytes Then Function = GlobalAlloc(ByVal %MEMGPTR, ByVal bytes)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function MemReAlloc(ByVal hMem As Long, ByVal bytes As Long) As Long<br />
        'reallocate new size<br />
        If hMem And bytes Then<br />
            Function = GlobalReAlloc(ByVal hMem, ByVal bytes, ByVal %MEMMOVEABLE Or %MEMZEROINIT)<br />
        ElseIf bytes Then<br />
            Function = GlobalAlloc(ByVal %MEMGPTR, ByVal bytes)<br />
        ElseIf hMem Then<br />
            Function = GlobalFree(ByVal hMem)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function MemFree(ByVal hMem As Long) As Long<br />
        'free memory<br />
        If hMem Then GlobalFree(ByVal hMem)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
#EndIf '%Memory230925<br />
<br />
#If Not %Def(%Ptr250717)<br />
    %Ptr250717 = 1<br />
    '----------------------------------------------------------------------<br />
    'Allocate UDT Pointer<br />
    'PtrT must be first item in UDT<br />
    'call PtrNew() to allocate pointer<br />
    'call PtrFree() to free pointer<br />
    'if PtrNewCallback() supplied then called after memory allocated<br />
    'if PtrFinalCallback() supplied then called before memory freed<br />
    'instance count supported but shouldn't be used unless needed<br />
    'should be made clear who is responsible for freeing pointer<br />
    '----------------------------------------------------------------------<br />
    Declare Sub PtrNewCallback(ByVal p As Long)<br />
        'if supplied then called after ponter allocated<br />
    Declare Sub PtrFinalCallback(ByVal p As Long)<br />
        'if supplied then called before ponter freed<br />
    Declare Function PtrStoreCallback(ByVal p As Long) As String<br />
        'return String holding pointer's associated data<br />
    Declare Function PtrRestorCallback(ByRef stored As String) As Long<br />
        'return new instance with data restored<br />
    '----------------------------------------------------------------------<br />
    %PtrTag = 648910727<br />
    Type PtrT<br />
        tag As Long<br />
        instances As Long<br />
        finalCB As Long<br />
    End Type<br />
    '----------------------------------------------------------------------<br />
    Function PtrNew(ByVal SizeOfUdt As Long, ByVal newCB As Long, ByVal finalCB As Long) As Long<br />
        'allocate new pointer instance : return pointer<br />
        'if new callback supplied then called after memory allocated<br />
        'if final callback supplied then called before memory freed<br />
        Local p As PtrT Ptr<br />
        ExitF(SizeOfUdt &lt; SizeOf(PtrT), "invalid size")<br />
            p = MemAlloc(SizeOfUdt)<br />
            If p Then<br />
                @p.tag = %PtrTag<br />
                @p.instances = 1<br />
                @p.finalCB = finalCB<br />
                If newCB Then Call Dword newCB Using PtrNewCallback(p)<br />
                Function = p<br />
            End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrFree(ByVal p As PtrT Ptr) As Long<br />
        'free pointer allocated by PtrNew() : return null<br />
        'if final callback was supplied then called before memory freed<br />
        If p Then<br />
            ExitF(@p.tag &lt;&gt; %PtrTag, "invalid ptr")<br />
            ExitF(@p.instances &lt; 1, "invalid instance count")<br />
            Decr @p.instances<br />
            If @p.instances = 0 Then<br />
                If @p.finalCB Then Call Dword @p.finalCB Using PtrFinalCallback(p)<br />
                MemFree(p)<br />
            End If<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub PtrIncr(ByVal p As PtrT Ptr)<br />
        'increment instance count<br />
        ExitS(p = 0 Or @p.tag &lt;&gt; %PtrTag, "invalid ptr")<br />
        Incr @p.instances<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
#EndIf '%Ptr250717<br />
<br />
#If Not %Def(%StrBuild250811)<br />
    %StrBuild250811 = 1<br />
    '----------------------------------------------------------------------<br />
    'String Builder<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    %BytSize = 1<br />
    %StrBuildBufferMax = 5000000<br />
    '----------------------------------------------------------------------<br />
    Type StrBuildT<br />
        mem As Long<br />
        count As Long<br />
        max As Long<br />
    End Type<br />
    '----------------------------------------------------------------------<br />
    Sub StrBuildPush(t As StrBuildT, ByVal value As String)<br />
        'append value<br />
        Local strlen, currentcount, buffer, newmax As Long<br />
        strlen = Len(value)<br />
        If strlen Then<br />
            If strlen &gt; t.max - t.count Then<br />
                currentcount = t.count : t.count = 0 : t.max = 0<br />
                buffer = Max&amp;(1, 2 * currentcount)<br />
                buffer = Min&amp;(buffer, %StrBuildBufferMax)<br />
                newmax = currentcount + buffer + strlen<br />
                t.mem = MemReAlloc(t.mem, newmax * %BytSize)<br />
                If t.mem = 0 Then Exit Sub<br />
                t.count = currentcount : t.max = newmax<br />
            End If<br />
            Memory Copy StrPtr(value), t.mem + (t.count * %BytSize), strlen * %BytSize<br />
            t.count += strlen<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrBuildPop(t As StrBuildT) As String<br />
        'get whole string : free memory<br />
        If t.mem And t.count Then Function = Peek&#36;(t.mem, t.count)<br />
        t.mem = MemFree(t.mem)<br />
        t.count = 0<br />
        t.max = 0<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
#EndIf '%StrBuild250811<br />
<br />
#If Not %Def(%FileStore250717)<br />
    %FileStore250717 = 1<br />
    Sub FilePut(ByVal file As WString, ByRef value As WString)<br />
        'store value to File<br />
        Local f As Long<br />
        If Len(file) = 0 Then Exit Sub<br />
        f = FreeFile<br />
        Open file For Binary As f<br />
        SetEof f<br />
        Put&#36; f, value<br />
        Close f<br />
    End Sub<br />
    Function FileGet(ByRef file As WString) As WString<br />
        'restore value from File<br />
        Local f As Long, value As WString<br />
        If IsFalse IsFile(file) Then Exit Function<br />
        f = FreeFile<br />
        Open file For Binary As f<br />
        Get&#36; f, Lof(f), value<br />
        Close f<br />
        Function = value<br />
    End Function<br />
#EndIf '%FileStore250717<br />
<br />
#If Not %Def(%PtrList250812)<br />
    %PtrList250812 = 1<br />
    '----------------------------------------------------------------------<br />
    'Pointer Doubly Linked List : Stack : Queue : Deque<br />
    'pointers to allocated UDTs<br />
    'PtrT must be first item in UDT<br />
    'pointer must have been allocated with PtrNew()<br />
    'stored pointers can't be null<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    Declare Function PtrStoreCallback(ByVal p As Long) As String<br />
        'return String holding pointer's associated data<br />
    Declare Function PtrRestorCallback(ByRef stored As String) As Long<br />
        'return new instance with data restored<br />
    '----------------------------------------------------------------------<br />
    %LngSize = 4<br />
    %PtrListTag = -699621965<br />
    Type PtrListNodeT<br />
        allocator As PtrT<br />
        next As PtrListNodeT Ptr<br />
        prev As PtrListNodeT Ptr<br />
        value As Long<br />
    End Type<br />
    Type PtrListT<br />
        allocator As PtrT<br />
        tag As Long<br />
        count As Long<br />
        first As PtrListNodeT Ptr<br />
        last As PtrListNodeT Ptr<br />
        cursor As PtrListNodeT Ptr<br />
        remember As PtrListNodeT Ptr<br />
    End Type<br />
    '----------------------------------------------------------------------<br />
    Function PtrListNew() As Long<br />
        'allocate new instance : return container pointer : call PtrFree(p) to free pointer<br />
        Function = PtrNew(SizeOf(PtrListT), CodePtr(PtrListNewCB), CodePtr(PtrListClear))<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListClear(ByVal p As PtrListT Ptr)<br />
        'empty container<br />
        Local node As PtrListNodeT Ptr<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        While @p.first<br />
            node = @p.first<br />
            @p.first = @node.next<br />
            PtrFree(node)<br />
        Wend<br />
        @p.last = 0<br />
        @p.count = 0<br />
        @p.cursor = 0<br />
        @p.remember = 0<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function PtrListCount(ByVal p As PtrListT Ptr) As Long<br />
        'get item count<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        Function = @p.count<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListAdd(ByVal p As PtrListT Ptr, ByVal pItem As Long)<br />
        'append value<br />
        PtrListPushLast p, pItem<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function PtrListCursor(ByVal p As PtrListT Ptr) As Long<br />
        'true/false if cursor valid<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListFirst(ByVal p As PtrListT Ptr) As Long<br />
        'move cursor to first node : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        @p.cursor = @p.first<br />
        If @p.cursor Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListNext(ByVal p As PtrListT Ptr) As Long<br />
        'move cursor to next node : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor Then @p.cursor = @p.@cursor.next<br />
        If @p.cursor Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListLast(ByVal p As PtrListT Ptr) As Long<br />
        'move cursor to last node : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        @p.cursor = @p.last<br />
        If @p.cursor Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPervious(ByVal p As PtrListT Ptr) As Long<br />
        'move cursor to previous node : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor Then @p.cursor = @p.@cursor.prev<br />
        If @p.cursor Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListRember(ByVal p As PtrListT Ptr) As Long<br />
        'remember cursor position : fail if cursor null : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        @p.remember = @p.cursor<br />
        If @p.remember Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListReturn(ByVal p As PtrListT Ptr) As Long<br />
        'return to remembered position : fail if node deleted : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        @p.cursor = @p.remember<br />
        If @p.cursor Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListGet(ByVal p As PtrListT Ptr) As Long<br />
        'get value at cursor position : null if cursor null<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor Then Function = @p.@cursor.value<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListDeleteForward(ByVal p As PtrListT Ptr) As Long<br />
        'move to next node and delete node at cursor position : fail if cursor null : true/false success<br />
        'cursor will be null if no next node<br />
        Local n As PtrListNodeT Ptr<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor Then<br />
            Function = 1<br />
            n = @p.cursor<br />
            @p.cursor = @p.@cursor.next<br />
            PtrListNodeDelete(p, n)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListDeleteBackward(ByVal p As PtrListT Ptr) As Long<br />
        'move to previous node and delete node at cursor position : fail if cursor null : true/false success<br />
        'cursor will be null if no previous node<br />
        Local n As PtrListNodeT Ptr<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor Then<br />
            Function = 1<br />
            n = @p.cursor<br />
            @p.cursor = @p.@cursor.prev<br />
            PtrListNodeDelete(p, n)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListIsBefore(ByVal p As PtrListT Ptr) As Long<br />
        'true/false if there is a node before cursor position<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor And @p.@cursor.prev Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListIsAfter(ByVal p As PtrListT Ptr) As Long<br />
        'true/false if there is a node after cursor position<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor And @p.@cursor.next Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListDeleteBefore(ByVal p As PtrListT Ptr) As Long<br />
        'delete node before cursor position : fail if cursor null or no previous node : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor And @p.@cursor.prev Then<br />
            PtrListNodeDelete p, @p.@cursor.prev<br />
            Function = 1<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListDeleteAfter(ByVal p As PtrListT Ptr) As Long<br />
        'delete node after cursor position : fail if cursor null or no next node : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor And @p.@cursor.next Then<br />
            PtrListNodeDelete p, @p.@cursor.next<br />
            Function = 1<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPushBefore(ByVal p As PtrListT Ptr, ByVal pItem As Long) As Long<br />
        'add value before cursor position : fail if cursor null : true/false success<br />
        Local node As PtrListNodeT Ptr<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitF(pItem = 0 Or Peek(Long, pItem) &lt;&gt; %PtrTag, "invalid item")<br />
        If @p.cursor Then<br />
            Function = 1<br />
            If @p.cursor = @p.first Then<br />
                PtrListPushFirst p, pItem<br />
            Else<br />
                ExitF(@p.@cursor.prev = 0, "previous node null")<br />
                node = PtrNew(SizeOf(PtrListNodeT), 0, CodePtr(PtrListNodeFinalCB))<br />
                ExitF(node = 0, "PtrNew fail")<br />
                @node.value = pItem<br />
                @node.next = @p.cursor<br />
                @node.prev = @p.@cursor.prev<br />
                @p.@cursor.@prev.next = node<br />
                @p.@cursor.prev = node<br />
            End If<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPushAfter(ByVal p As PtrListT Ptr, ByVal pItem As Long) As Long<br />
        'add value after cursor position : fail if cursor null : true/false success<br />
        Local node As PtrListNodeT Ptr<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitF(pItem = 0 Or Peek(Long, pItem) &lt;&gt; %PtrTag, "invalid item")<br />
        If @p.cursor Then<br />
            Function = 1<br />
            If @p.cursor = @p.last Then<br />
                PtrListPushLast p, pItem<br />
            Else<br />
                ExitF(@p.@cursor.next = 0, "next node null")<br />
                node = PtrNew(SizeOf(PtrListNodeT), 0, CodePtr(PtrListNodeFinalCB))<br />
                ExitF(node = 0, "PtrNew fail")<br />
                @node.value = pItem<br />
                @node.prev = @p.cursor<br />
                @node.next = @p.@cursor.next<br />
                @p.@cursor.@next.prev = node<br />
                @p.@cursor.next = node<br />
            End If<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPeekBefore(ByVal p As PtrListT Ptr) As Long<br />
        'get value before cursor position : null if cursor invalid or no previous node<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor And @p.@cursor.prev Then Function = @p.@cursor.@prev.value<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPeekAfter(ByVal p As PtrListT Ptr) As Long<br />
        'get value after cursor position : null if cursor invalid or no next node<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor And @p.@cursor.next Then Function = @p.@cursor.@next.value<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPopBefore(ByVal p As PtrListT Ptr) As Long<br />
        'get and remove value before cursor position : null if cursor invalid or no previous node<br />
        'user responsible for freeing popped pointers<br />
        Local h As Long<br />
        h = PtrListPeekBefore(p)<br />
        If h Then<br />
            PtrIncr(h)<br />
            Function = h<br />
            PtrListDeleteBefore(p)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPopAfter(ByVal p As PtrListT Ptr) As Long<br />
        'get and remove value after cursor position : null if cursor invalid or no next node<br />
        'user responsible for freeing popped pointers<br />
        Local h As Long<br />
        h = PtrListPeekAfter(p)<br />
        If h Then<br />
            PtrIncr(h)<br />
            Function = h<br />
            PtrListDeleteAfter(p)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListPushFirst(ByVal p As PtrListT Ptr, ByVal pItem As Long)<br />
        'insert at front<br />
        Local node As PtrListNodeT Ptr<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitS(pItem = 0 Or Peek(Long, pItem) &lt;&gt; %PtrTag, "invalid item")<br />
        node = PtrNew(SizeOf(PtrListNodeT), 0, CodePtr(PtrListNodeFinalCB))<br />
        ExitS(node = 0, "PtrNew fail")<br />
        @node.value = pItem<br />
        If @p.count Then<br />
            ExitS(@p.first = 0, "first node null")<br />
            @p.@first.prev = node<br />
            @node.next = @p.first<br />
            @p.first = node<br />
            Incr @p.count<br />
        Else<br />
            @p.first = node<br />
            @p.last = node<br />
            @p.count = 1<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListPushLast(ByVal p As PtrListT Ptr, ByVal pItem As Long)<br />
        'append to end<br />
        Local node As PtrListNodeT Ptr<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitS(pItem = 0 Or Peek(Long, pItem) &lt;&gt; %PtrTag, "invalid item")<br />
        node = PtrNew(SizeOf(PtrListNodeT), 0, CodePtr(PtrListNodeFinalCB))<br />
        ExitS(node = 0, "PtrNew fail")<br />
        @node.value = pItem<br />
        If @p.count Then<br />
            ExitS(@p.last = 0, "last node null")<br />
            @p.@last.next = node<br />
            @node.prev = @p.last<br />
            @p.last = node<br />
            Incr @p.count<br />
        Else<br />
            @p.first = node<br />
            @p.last = node<br />
            @p.count = 1<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPeekFirst(ByVal p As PtrListT Ptr) As Long<br />
        'get fist value : null if list empty<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.first Then Function = @p.@first.value<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPeekLast(ByVal p As PtrListT Ptr) As Long<br />
        'get last value : null list empty<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.last Then Function = @p.@last.value<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPopFirst(ByVal p As PtrListT Ptr) As Long<br />
        'get and remove fist value : null if list empty<br />
        'user responsible for freeing popped pointers<br />
        Local h As Long<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.first Then<br />
            h = @p.@first.value<br />
            PtrIncr(h)<br />
            Function = h<br />
            PtrListNodeDelete(p, @p.first)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPopLast(ByVal p As PtrListT Ptr) As Long<br />
        'get and remove last value : null if list empty<br />
        'user responsible for freeing popped pointers<br />
        Local h As Long<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.last Then<br />
            h = @p.@last.value<br />
            PtrIncr(h)<br />
            Function = h<br />
            PtrListNodeDelete(p, @p.last)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListStore(ByVal p As PtrListT Ptr, ByVal storeCB As Long) As String<br />
        'store container to String<br />
        'Declare Function PtrStoreCallback(ByVal p As Long) As String<br />
        Local more, h As Long<br />
        Local sb As StrBuildT<br />
        Local s As String<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitF(storeCB = 0, "null callback")<br />
        If @p.count Then<br />
            StrBuildPush sb, Mkl&#36;(@p.count)<br />
            more = PtrListFirst(p)<br />
            While more<br />
                h = PtrListGet(p)<br />
                ExitF(h = 0, "unexpected null")<br />
                Call Dword storeCB Using PtrStoreCallback(h) To s<br />
                StrBuildPush sb, Mkl&#36;(Len(s))<br />
                StrBuildPush sb, s<br />
                more = PtrListNext(p)<br />
            Wend<br />
        End If<br />
        Function = StrBuildPop(sb)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListRestore(ByVal p As PtrListT Ptr, ByRef stored As String, ByVal restoreCB As Long)<br />
        'restore container from String<br />
        'Declare Function PtrRestorCallback(ByRef stored As String) As Long<br />
        Register i As Long<br />
        Local items, bytes, h As Long<br />
        Local s As String<br />
        Local pl As Long Ptr<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitS(restoreCB = 0, "null callback")<br />
        PtrListClear p<br />
        If Len(stored) Then<br />
            pl = StrPtr(stored)<br />
            items = @pl : Incr pl<br />
            For i = 1 To items<br />
                bytes = @pl : Incr pl<br />
                s = Peek&#36;(pl, bytes) : pl += bytes<br />
                Call Dword restoreCB Using PtrRestorCallback(s) To h<br />
                ExitS(h = 0,"null callback return")<br />
                PtrListAdd p, h<br />
            Next i<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListFileStore(ByVal p As PtrListT Ptr, ByVal file As WString, ByVal storeCB As Long)<br />
        'store container to File<br />
        'Declare Function PtrStoreCallback(ByVal p As Long) As String<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitS(storeCB = 0, "null callback")<br />
        ExitS(Len(file) = 0, "invalid file")<br />
        FilePut file, PtrListStore(p, storeCB)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListFileRestore(ByVal p As PtrListT Ptr, ByVal file As WString, ByVal restoreCB As Long)<br />
        'restore container from File<br />
        'Declare Function PtrRestorCallback(ByRef stored As String) As Long<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitS(IsFalse IsFile(file), "invalid file" + file)<br />
        PtrListRestore p, FileGet(file), restoreCB<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    'PRIVATE:<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListNewCB(ByVal p As PtrListT Ptr) Private<br />
        'PRIVATE: list new callback<br />
        If p Then @p.tag = %PtrListTag<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListNodeFinalCB(ByVal node As PtrListNodeT Ptr)<br />
        If node Then<br />
            PtrFree(@node.value)<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListNodeDelete(ByVal p As PtrListT Ptr, ByVal node As PtrListNodeT Ptr) Private<br />
        'PRIVATE: remove node from list<br />
        If node Then<br />
            If @p.cursor = node Then @p.cursor = 0<br />
            If @p.remember = node Then @p.remember = 0<br />
            If @p.first = node Then @p.first = @node.next<br />
            If @p.last = node Then @p.last = @node.prev<br />
            If @node.prev Then @node.@prev.next = @node.next<br />
            If @node.next Then @node.@next.prev = @node.prev<br />
            ExitS(@p.count = 0, "invalid count")<br />
            Decr @p.count<br />
            PtrFree(node)<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
#EndIf '%PtrList250812<br />
<br />
#If Not %Def(%Str250724)<br />
    %Str250724 = 1<br />
    '----------------------------------------------------------------------<br />
    'Dynamic String Container<br />
    '----------------------------------------------------------------------<br />
    Declare Function StrCompareCallback(ByVal a As Long, ByVal b As Long) As Long<br />
        'a &lt; b : return &lt; 0    a = b : return = 0    a &gt; b : return &gt; 0<br />
    '----------------------------------------------------------------------<br />
    %BytSize = 1<br />
    %StrTag = 653170298<br />
    Type StrT<br />
        allocator As PtrT<br />
        tag As Long<br />
        mem As Byte Ptr<br />
        count As Long<br />
    End Type<br />
    '----------------------------------------------------------------------<br />
    Function StrNew() As Long<br />
        'allocate new instance : return container pointer : call PtrFree(p) to free pointer<br />
        Function = PtrNew(SizeOf(StrT), CodePtr(StrNewCB), CodePtr(StrClear))<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrClear(ByVal p As StrT Ptr)<br />
        'empty container<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %StrTag, "invalid ptr")<br />
        @p.mem = MemFree(@p.mem)<br />
        @p.count = 0<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrGet(ByVal p As StrT Ptr) As String<br />
        'get value<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %StrTag, "invalid ptr")<br />
        Function = Peek&#36;(@p.mem, @p.count)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrSet(ByVal p As StrT Ptr, ByRef value As String)<br />
        Local strlen As Long : strlen = Len(value)<br />
        Local bytes As Long : bytes = strlen * %BytSize<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %StrTag, "invalid ptr")<br />
        @p.mem = MemFree(@p.mem) : @p.count = 0<br />
        If strlen Then<br />
            @p.mem = MemAlloc(bytes)<br />
            If @p.mem Then<br />
                @p.count = strlen<br />
                Memory Copy StrPtr(value), @p.mem, bytes<br />
            End If<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrNewValue(ByRef value As String) As Long<br />
        'allocate new instance : store value : return pointer<br />
        Local h As Long<br />
        h = StrNew()<br />
        StrSet h, value<br />
        Function = h<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrHashIndex(ByVal p As StrT Ptr, ByVal capacity As Long) As Long<br />
        'get key's one-based hash index position<br />
        Register i As Long : Local total, temp As Long<br />
        Function = 1<br />
        If p Then<br />
            For i = 0 To @p.count - 1<br />
                temp += @p.@mem[i] + total<br />
                Shift Left total, 8<br />
                total += temp<br />
            Next i<br />
            Function = Abs(total Mod capacity) + 1<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrEqual(ByVal a As StrT Ptr, ByVal b As StrT Ptr) As Long<br />
        'true/false if two strings equal<br />
        Register i As Long<br />
        If a And b And @a.count = @b.count Then<br />
            For i = 0 To @a.count - 1<br />
                If @a.@mem[i] &lt;&gt; @b.@mem[i] Then Exit Function<br />
            Next i<br />
            Function = 1<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrCompare(ByVal a As StrT Ptr, ByVal b As StrT Ptr) As Long<br />
        'compare callback : case sensitive<br />
        Register i As Long : Register compare As Long<br />
        If a And b Then<br />
            For i = 0 To Min&amp;(@a.count, @b.count) - 1<br />
                compare = @a.@mem[i] - @b.@mem[i]<br />
                If compare Then<br />
                    Function = compare : Exit Function<br />
                End If<br />
            Next i<br />
            Function = @a.count - @b.count<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrCompareIgnore(ByVal a As StrT Ptr, ByVal b As StrT Ptr) As Long<br />
        'compare callback : case sensitive<br />
        Register i As Long : Register compare As Long : Local la, lb As Long<br />
        If a And b Then<br />
            For i = 0 To Min&amp;(@a.count, @b.count) - 1<br />
                la = @a.@mem[i] : lb = @b.@mem[i]<br />
                If la &gt; 64 And la &lt; 91 Then la += 32<br />
                If lb &gt; 64 And lb &lt; 91 Then lb += 32<br />
                compare = la - lb<br />
                If compare Then<br />
                    Function = compare : Exit Function<br />
                End If<br />
            Next i<br />
            Function = @a.count - @b.count<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrStoreCB(ByVal p As StrT Ptr) As String<br />
        'store callback<br />
        Function = ChrToUtf8&#36;(StrGet(p))<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrRestoreCB(ByRef value As String) As Long<br />
        'restore callback<br />
        Function = StrNewValue(Utf8ToChr&#36;(value))<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    'PRIVATE:<br />
    '----------------------------------------------------------------------<br />
    Sub StrNewCB(ByVal p As StrT Ptr) Private<br />
        'PRIVATE: new callback<br />
        If p Then @p.tag = %StrTag<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
#EndIf '%Str250724<br />
<br />
#If Not %Def(%StrList250812)<br />
    %StrList250812 = 1<br />
    '----------------------------------------------------------------------<br />
    'String Doubly Linked List : Stack : Queue : Deque<br />
    '----------------------------------------------------------------------<br />
    Macro StrListT = PtrListT<br />
    '----------------------------------------------------------------------<br />
    Function StrListNew() As Long<br />
        'allocate new instance : return container pointer : call PtrFree(p) to free pointer<br />
        Function = PtrListNew()<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrListClear(ByVal p As StrListT Ptr)<br />
        'empty container<br />
        PtrListClear p<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrListCount(ByVal p As StrListT Ptr) As Long<br />
        'get item count<br />
        Function = PtrListCount(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrListAdd(ByVal p As StrListT Ptr, ByRef value As String)<br />
        'append value<br />
        PtrListAdd p, StrNewValue(value)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrListCursor(ByVal p As StrListT Ptr) As Long<br />
        'true/false if cursor valid<br />
        Function = PtrListCursor(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListFirst(ByVal p As StrListT Ptr) As Long<br />
        'move cursor to first node : true/false success<br />
        Function = PtrListFirst(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListNext(ByVal p As StrListT Ptr) As Long<br />
        'move cursor to next node : true/false success<br />
        Function = PtrListNext(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListLast(ByVal p As StrListT Ptr) As Long<br />
        'move cursor to last node : true/false success<br />
        Function = PtrListLast(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPervious(ByVal p As StrListT Ptr) As Long<br />
        'move cursor to previous node : true/false success<br />
        Function = PtrListPervious(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListRember(ByVal p As StrListT Ptr) As Long<br />
        'remember cursor position : fail if cursor null : true/false success<br />
        Function = PtrListRember(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListReturn(ByVal p As StrListT Ptr) As Long<br />
        'return to remembered position : fail if node deleted : true/false success<br />
        Function = PtrListReturn(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListGet(ByVal p As StrListT Ptr) As String<br />
        'get value at cursor position : null if cursor null<br />
        Local h As Long<br />
        h = PtrListGet(p)<br />
        If h Then Function = StrGet(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListSet(ByVal p As StrListT Ptr, ByRef value As String) As Long<br />
        'set value at cursor position : fail if cursor null : true/false success<br />
        Local h As Long<br />
        h = PtrListGet(p)<br />
        If h Then<br />
            StrSet h, value<br />
            Function = 1<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListDeleteForward(ByVal p As StrListT Ptr) As Long<br />
        'move to next node and delete node at cursor position : fail if cursor null : true/false success<br />
        'cursor will be null if no next node<br />
        Function = PtrListDeleteForward(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListDeleteBackward(ByVal p As StrListT Ptr) As Long<br />
        'move to previous node and delete node at cursor position : fail if cursor null : true/false success<br />
        'cursor will be null if no previous node<br />
        Function = PtrListDeleteBackward(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListIsBefore(ByVal p As StrListT Ptr) As Long<br />
        'true/false if there is a node before cursor position<br />
        Function = PtrListIsBefore(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListIsAfter(ByVal p As StrListT Ptr) As Long<br />
        'true/false if there is a node after cursor position<br />
        Function = PtrListIsAfter(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListDeleteBefore(ByVal p As StrListT Ptr) As Long<br />
        'delete node before cursor position : fail if cursor null or no previous node : true/false success<br />
        Function = PtrListDeleteBefore(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListDeleteAfter(ByVal p As StrListT Ptr) As Long<br />
        'delete node after cursor position : fail if cursor null or no next node : true/false success<br />
        Function = PtrListDeleteAfter(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPushBefore(ByVal p As StrListT Ptr, ByRef value As String) As Long<br />
        'add value before cursor position : fail if cursor null : true/false success<br />
        Local h As Long : h = StrNewValue(value)<br />
        If IsFalse PtrListPushBefore(p, h) Then PtrFree(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPushAfter(ByVal p As StrListT Ptr, ByRef value As String) As Long<br />
        'add value after cursor position : fail if cursor null : true/false success<br />
        'add value before cursor position : fail if cursor null : true/false success<br />
        Local h As Long : h = StrNewValue(value)<br />
        If IsFalse PtrListPushAfter(p, h) Then PtrFree(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPeekBefore(ByVal p As StrListT Ptr) As String<br />
        'get value before cursor position : null if cursor invalid or no previous node<br />
        Local h As Long : h = PtrListPeekBefore(p)<br />
        If h Then Function = StrGet(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPeekAfter(ByVal p As StrListT Ptr) As String<br />
        'get value after cursor position : null if cursor invalid or no next node<br />
        Local h As Long : h = PtrListPeekAfter(p)<br />
        If h Then Function = StrGet(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPopBefore(ByVal p As StrListT Ptr) As String<br />
        'get and remove value before cursor position : null if cursor invalid or no previous node<br />
        Local h As Long : h = PtrListPopBefore(p)<br />
        If h Then<br />
            Function = StrGet(h)<br />
            PtrFree(h)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPopAfter(ByVal p As StrListT Ptr) As String<br />
        'get and remove value after cursor position : null if cursor invalid or no next node<br />
        Local h As Long : h = PtrListPopAfter(p)<br />
        If h Then<br />
            Function = StrGet(h)<br />
            PtrFree(h)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrListPushFirst(ByVal p As StrListT Ptr, ByRef value As String)<br />
        'insert at front<br />
        PtrListPushFirst p, StrNewValue(value)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListPushLast(ByVal p As StrListT Ptr, ByRef value As String)<br />
        'append to end<br />
        PtrListPushLast p, StrNewValue(value)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrListPeekFirst(ByVal p As StrListT Ptr) As String<br />
        'get fist value : null if list empty<br />
        Local h As Long : h = PtrListPeekFirst(p)<br />
        If h Then Function = StrGet(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPeekLast(ByVal p As StrListT Ptr) As String<br />
        'get last value : null list empty<br />
        Local h As Long : h = PtrListPeekLast(p)<br />
        If h Then Function = StrGet(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPopFirst(ByVal p As StrListT Ptr) As String<br />
        'get and remove fist value : null if list empty<br />
        Local h As Long : h = PtrListPopFirst(p)<br />
        If h Then<br />
            Function = StrGet(h)<br />
            PtrFree(h)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPopLast(ByVal p As StrListT Ptr) As String<br />
        'get and remove last value : null if list empty<br />
        Local h As Long : h = PtrListPopLast(p)<br />
        If h Then<br />
            Function = StrGet(h)<br />
            PtrFree(h)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrListImport(ByVal p As StrListT Ptr, a() As String)<br />
        'import PB array<br />
        Register i As Long<br />
        StrListClear p<br />
        For i = LBound(a) To UBound(a)<br />
            StrListAdd p, a(i)<br />
        Next i<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListExport(ByVal p As StrListT Ptr, a() As String)<br />
        'export to PB array<br />
        Register i As Long<br />
        Local more As Long<br />
        Erase a()<br />
        If StrListCount(p) Then<br />
            ReDim a(1 To StrListCount(p))<br />
            more = StrListFirst(p)<br />
            While more<br />
                Incr i<br />
                a(i) = StrListGet(p)<br />
                more = StrListNext(p)<br />
            Wend<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListSplit(ByVal p As StrListT Ptr, ByVal delimited As String, ByVal delimiter As String)<br />
        'split array on delimited string<br />
        Local items As Long<br />
        Local a() As String<br />
        StrListClear p<br />
        If Len(delimited) Then<br />
            items = ParseCount(delimited, delimiter)<br />
            If items Then<br />
                Dim a(1 To items)<br />
                Parse delimited, a(), delimiter<br />
                StrListImport p, a()<br />
            End If<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrListJoin(ByVal p As StrListT Ptr, ByVal delimiter As String) As String<br />
        'join array on delimiter<br />
        Local a() As String<br />
        StrListExport p, a()<br />
        Function = Join&#36;(a(), delimiter)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrListToText(ByVal p As StrListT Ptr, ByVal file As WString)<br />
        'store list as text file<br />
        If StrListCount(p) Then<br />
            FilePut file, RTrim&#36;(StrListJoin(p, &#36;CrLf))  + &#36;CrLf<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListFromText(ByVal p As StrListT Ptr, ByVal file As WString)<br />
        'load text file<br />
        Local contents As String<br />
        StrListClear p<br />
        contents = FileGet(file)<br />
        contents = Trim&#36;(contents, &#36;CrLf)<br />
        If Len(contents) Then<br />
            StrListSplit p, contents, &#36;CrLf<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListFolders(ByVal p As StrListT Ptr, ByVal rootFolder As String)<br />
        'get all folders in root folder<br />
        Local folder, folderMask, rootPath As String<br />
        Local DrD As DirData<br />
        StrListClear p<br />
        ExitS(IsFalse IsFolder(rootFolder), "invalid folder")<br />
        rootPath = RTrim&#36;(rootFolder, "&#92;") + "&#92;"<br />
        folderMask = rootPath<br />
        folder = Dir&#36;(folderMask, Only %SubDir To DrD)<br />
        While Len(folder)<br />
            StrListAdd p, rootPath + folder<br />
            folder = Dir&#36;<br />
        Wend<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListFiles(ByVal p As StrListT Ptr, ByVal folder As String, ByVal mask As String)<br />
        'get all files in folder matching mask<br />
        Local file, fileMask As String<br />
        StrListClear p<br />
        ExitS(IsFalse IsFolder(folder), "invalid folder")<br />
        folder = RTrim&#36;(folder, "&#92;") + "&#92;"<br />
        fileMask = folder + mask<br />
        file = Dir&#36;(fileMask)<br />
        While Len(file)<br />
            StrListAdd p, folder + file<br />
            file = Dir&#36;<br />
        Wend<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListAllFolders(ByVal p As StrListT Ptr, ByVal rootFolder As String)<br />
        'get all folders in root folder and sub-folders, including root folder<br />
        Local folderStack As Long : folderStack = StrListNew()<br />
        Local subFolderStack As Long : subFolderStack = StrListNew()<br />
        Local currentFolder As String<br />
        StrListClear p<br />
        ExitS(IsFalse IsFolder(rootFolder), "invalid folder")<br />
        StrListPushLast folderStack, rootFolder<br />
        While StrListCount(folderStack)<br />
            currentFolder = StrListPopLast(folderStack)<br />
            StrListAdd p, currentFolder<br />
            StrListFolders subFolderStack, currentFolder<br />
            While StrListCount(subFolderStack)<br />
                StrListPushLast folderStack, StrListPopLast(subFolderStack)<br />
            Wend<br />
        Wend<br />
        folderStack = PtrFree(folderStack)<br />
        subFolderStack = PtrFree(subFolderStack)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListAllFiles(ByVal p As StrListT Ptr, ByVal rootFolder As String, ByVal mask As String)<br />
        'get all files in root folder, and sub-folders, matching mask<br />
        Local allFolderStack As Long : allFolderStack = StrListNew()<br />
        Local folderFileStack As Long : folderFileStack = StrListNew()<br />
        StrListClear p<br />
        ExitS(IsFalse IsFolder(rootFolder), "invalid folder")<br />
        StrListAllFolders allFolderStack, rootFolder<br />
        While StrListCount(allFolderStack)<br />
            StrListFiles folderFileStack, StrListPopLast(allFolderStack), mask<br />
            While StrListCount(folderFileStack)<br />
                StrListAdd p, StrListPopLast(folderFileStack)<br />
            Wend<br />
        Wend<br />
        allFolderStack = PtrFree(allFolderStack)<br />
        folderFileStack = PtrFree(folderFileStack)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrListStore(ByVal p As StrListT Ptr) As String<br />
        'store container to String<br />
        Function = PtrListStore(p, CodePtr(StrStoreCB))<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrListRestore(ByVal p As StrListT Ptr, ByRef stored As String)<br />
        'restore container from String<br />
        PtrListRestore p, stored, CodePtr(StrRestoreCB)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListFileStore(ByVal p As StrListT Ptr, ByVal file As WString)<br />
        'store container to File<br />
        PtrListFileStore p, file, CodePtr(StrStoreCB)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListFileRestore(ByVal p As StrListT Ptr, ByVal file As WString)<br />
        'restore container from File<br />
        PtrListFileRestore p, file, CodePtr(StrRestoreCB)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
#EndIf '%StrList250812</code></div></div>]]></description>
			<content:encoded><![CDATA[Compiler? Do you write a compiler or parse code for an existing compiler?<br />
Two versions, String or WString. No dependencies. Fast, get 1,0000,000 values in less than 1/10 of a second.<br />
<br />
This is something I did thinking it might be useful for parsing code. It’s a list, queue, stack and deque.  You can push, peek pop from front or back.<br />
But it also has a cursor that can be moved forward and backwards. The cursor position also acts as a queue, stack and deque. You can push, peek, pop and delete before and after cursor position. The cursor can save its position one time; you can move around and then return.<br />
Supports Split/Join. <br />
It can load a text file and be saved as a text file (code file). WString version uses UTF8. <br />
It can be stored/restored to/from file, binary format.<br />
It can get a list of files, folders, sub-folders and all files in sub-folders matching a mask.<br />
 <br />
There are probably a lot better tools out there, but simplicity appeals to me. It seems like each different tool is a whole new programming language to learn. And different versions of the same type of compiler have different tools. <br />
 <br />
The idea is that you have an input and output list containing lines of code. One or more temporary list can be used to split a line of code apart. Push the output to the output list.<br />
 <br />
It’s an allocated container, must call …New() before use. Call PtrFree(p) to free handle or pointer. Errors are logged if %LogOnError is defined. Message on error if %MessageOnError is defined. You can use the ExitIf() macro to log parsing errors. <br />
 <br />
It might be useful, might not.<br />
<hr class="mycode_hr" />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'StrList.inc<br />
<br />
'Public domain, use at own risk. SDurham<br />
<br />
'Function StrListNew() As Long<br />
    'allocate new instance : return container pointer : call PtrFree(p) to free pointer<br />
<br />
'Sub StrListClear(ByVal p As StrListT Ptr)<br />
    'empty container<br />
<br />
'Function StrListCount(ByVal p As StrListT Ptr) As Long<br />
    'get item count<br />
<br />
'Sub StrListAdd(ByVal p As StrListT Ptr, ByRef value As String)<br />
    'append value<br />
<br />
'Function StrListCursor(ByVal p As StrListT Ptr) As Long<br />
    'true/false if cursor valid<br />
<br />
'Function StrListFirst(ByVal p As StrListT Ptr) As Long<br />
    'move cursor to first node : true/false success<br />
<br />
'Function StrListNext(ByVal p As StrListT Ptr) As Long<br />
    'move cursor to next node : true/false success<br />
<br />
'Function StrListLast(ByVal p As StrListT Ptr) As Long<br />
    'move cursor to last node : true/false success<br />
<br />
'Function StrListPervious(ByVal p As StrListT Ptr) As Long<br />
    'move cursor to previous node : true/false success<br />
<br />
'Function StrListRember(ByVal p As StrListT Ptr) As Long<br />
    'remember cursor position : fail if cursor null : true/false success<br />
<br />
'Function StrListReturn(ByVal p As StrListT Ptr) As Long<br />
    'return to remembered position : fail if node deleted : true/false success<br />
<br />
'Function StrListGet(ByVal p As StrListT Ptr) As String<br />
    'get value at cursor position : null if cursor null<br />
<br />
'Function StrListSet(ByVal p As StrListT Ptr, ByRef value As String) As Long<br />
    'set value at cursor position : fail if cursor null : true/false success<br />
<br />
'Function StrListDeleteForward(ByVal p As StrListT Ptr) As Long<br />
    'move to next node and delete node at cursor position : fail if cursor null : true/false success<br />
    'cursor will be null if no next node<br />
<br />
'Function StrListDeleteBackward(ByVal p As StrListT Ptr) As Long<br />
    'move to previous node and delete node at cursor position : fail if cursor null : true/false success<br />
    'cursor will be null if no previous node<br />
<br />
'Function StrListIsBefore(ByVal p As StrListT Ptr) As Long<br />
    'true/false if there is a node before cursor position<br />
<br />
'Function StrListIsAfter(ByVal p As StrListT Ptr) As Long<br />
    'true/false if there is a node after cursor position<br />
<br />
'Function StrListDeleteBefore(ByVal p As StrListT Ptr) As Long<br />
    'delete node before cursor position : fail if cursor null or no previous node : true/false success<br />
<br />
'Function StrListDeleteAfter(ByVal p As StrListT Ptr) As Long<br />
    'delete node after cursor position : fail if cursor null or no next node : true/false success<br />
<br />
'Function StrListPushBefore(ByVal p As StrListT Ptr, ByRef value As String) As Long<br />
    'add value before cursor position : fail if cursor null : true/false success<br />
<br />
'Function StrListPushAfter(ByVal p As StrListT Ptr, ByRef value As String) As Long<br />
    'add value after cursor position : fail if cursor null : true/false success<br />
    'add value before cursor position : fail if cursor null : true/false success<br />
<br />
'Function StrListPeekBefore(ByVal p As StrListT Ptr) As String<br />
    'get value before cursor position : null if cursor invalid or no previous node<br />
<br />
'Function StrListPeekAfter(ByVal p As StrListT Ptr) As String<br />
    'get value after cursor position : null if cursor invalid or no next node<br />
<br />
'Function StrListPopBefore(ByVal p As StrListT Ptr) As String<br />
    'get and remove value before cursor position : null if cursor invalid or no previous node<br />
<br />
'Function StrListPopAfter(ByVal p As StrListT Ptr) As String<br />
    'get and remove value after cursor position : null if cursor invalid or no next node<br />
<br />
'Sub StrListPushFirst(ByVal p As StrListT Ptr, ByRef value As String)<br />
    'insert at front<br />
<br />
'Sub StrListPushLast(ByVal p As StrListT Ptr, ByRef value As String)<br />
    'append to end<br />
<br />
'Function StrListPeekFirst(ByVal p As StrListT Ptr) As String<br />
    'get fist value : null if list empty<br />
<br />
'Function StrListPeekLast(ByVal p As StrListT Ptr) As String<br />
    'get last value : null list empty<br />
<br />
'Function StrListPopFirst(ByVal p As StrListT Ptr) As String<br />
    'get and remove fist value : null if list empty<br />
<br />
'Function StrListPopLast(ByVal p As StrListT Ptr) As String<br />
    'get and remove last value : null if list empty<br />
<br />
'Sub StrListImport(ByVal p As StrListT Ptr, a() As String)<br />
    'import PB array<br />
<br />
'Sub StrListExport(ByVal p As StrListT Ptr, a() As String)<br />
    'export to PB array<br />
<br />
'Sub StrListSplit(ByVal p As StrListT Ptr, ByVal delimited As String, ByVal delimiter As String)<br />
    'split array on delimited string<br />
<br />
'Function StrListJoin(ByVal p As StrListT Ptr, ByVal delimiter As String) As String<br />
    'join array on delimiter<br />
<br />
'Sub StrListToText(ByVal p As StrListT Ptr, ByVal file As WString)<br />
    'store list as text file<br />
<br />
'Sub StrListFromText(ByVal p As StrListT Ptr, ByVal file As WString)<br />
    'load text file<br />
<br />
'Sub StrListFolders(ByVal p As StrListT Ptr, ByVal rootFolder As String)<br />
    'get all folders in root folder<br />
<br />
'Sub StrListFiles(ByVal p As StrListT Ptr, ByVal folder As String, ByVal mask As String)<br />
    'get all files in folder matching mask<br />
<br />
'Sub StrListAllFolders(ByVal p As StrListT Ptr, ByVal rootFolder As String)<br />
    'get all folders in root folder and sub-folders, including root folder<br />
<br />
'Sub StrListAllFiles(ByVal p As StrListT Ptr, ByVal rootFolder As String, ByVal mask As String)<br />
    'get all files in root folder, and sub-folders, matching mask<br />
<br />
'Function StrListStore(ByVal p As StrListT Ptr) As String<br />
    'store container to String<br />
<br />
'Sub StrListRestore(ByVal p As StrListT Ptr, ByRef stored As String)<br />
    'restore container from String<br />
<br />
'Sub StrListFileStore(ByVal p As StrListT Ptr, ByVal file As WString)<br />
    'store container to File<br />
<br />
'Sub StrListFileRestore(ByVal p As StrListT Ptr, ByVal file As WString)<br />
    'restore container from File<br />
<br />
#If Not %Def(%ExitIf240526)<br />
    %ExitIf240526 = 1<br />
    '----------------------------------------------------------------------<br />
    'Error Exit Macro<br />
    'Exit procedure with error message<br />
    'if %LogOnError defined then errors logged to app folder<br />
    'if %MessageOnError defined then message on error<br />
    'if %HaltOnError defined then app halt with message on error<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    %ExitIfErr = 151<br />
    '----------------------------------------------------------------------<br />
    Macro ExitIf(test, message, exitWhat)<br />
        If test Then<br />
            ExitLog FuncName&#36; + "() " + message<br />
            Error %ExitIfErr<br />
            Exit exitWhat<br />
        End If<br />
    End Macro<br />
    Macro ExitF(test, message) = ExitIf(test, message, Function)<br />
    Macro ExitS(test, message) = ExitIf(test, message, Sub)<br />
    Macro ExitM(test, message) = ExitIf(test, message, Method)<br />
    Macro ExitP(test, message) = ExitIf(test, message, Property)<br />
    Macro ExitMC(test, message) = ExitIf(test, message, Macro)<br />
    'GoTo with error message<br />
    Macro GoToIf(test, message, goWhere)<br />
        If test Then<br />
            ExitLog FuncName&#36; +": "+ message<br />
            Error %ExitIfErr<br />
            GoTo goWhere<br />
        End If<br />
    End Macro<br />
    '----------------------------------------------------------------------<br />
    Sub ExitLog(ByVal message As String) Private<br />
        Local h As Long<br />
        h = FreeFile<br />
        #If %Def(%LogOnError) Or %Def(%MessageOnError) Or %Def(%HaltOnError)<br />
            Open Exe.Path&#36;+"Error.log" For Append As h<br />
            If Lof(h) &lt; 16000 Then<br />
                Print# h, Date&#36; +", "+ Time&#36; +", "+ Exe.Full&#36; +", "+ message<br />
            End If<br />
            Close h<br />
        #EndIf<br />
        #If %Def(%MessageOnError) Or %Def(%HaltOnError)<br />
            ? message,,"Error!"<br />
        #EndIf<br />
        #If %Def(%HaltOnError)<br />
            End<br />
        #EndIf<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
#EndIf '%ExitIf240526<br />
<br />
#If Not %Def(%Memory230925)<br />
    %Memory230925 = 1<br />
    '----------------------------------------------------------------------<br />
    'Memory Allocation<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    Declare Function GlobalAlloc Lib "Kernel32.dll" Alias "GlobalAlloc" (ByVal uFlags As Dword, ByVal dwBytes As Dword) As Dword<br />
    Declare Function GlobalReAlloc Lib "Kernel32.dll" Alias "GlobalReAlloc" (ByVal hMem As Dword, ByVal dwBytes As Dword, ByVal uFlags As Dword) As Dword<br />
    Declare Function GlobalFree Lib "Kernel32.dll" Alias "GlobalFree" (ByVal hMem As Dword) As Dword<br />
    %MEMFIXED = &amp;H0000 : %MEMMOVEABLE = &amp;H0002 : %MEMZEROINIT = &amp;H0040 : %MEMGPTR = (%MEMZEROINIT Or %MEMFIXED)<br />
    '----------------------------------------------------------------------<br />
    Function MemAlloc(ByVal bytes As Long) As Long<br />
        'allocate memory<br />
        If bytes Then Function = GlobalAlloc(ByVal %MEMGPTR, ByVal bytes)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function MemReAlloc(ByVal hMem As Long, ByVal bytes As Long) As Long<br />
        'reallocate new size<br />
        If hMem And bytes Then<br />
            Function = GlobalReAlloc(ByVal hMem, ByVal bytes, ByVal %MEMMOVEABLE Or %MEMZEROINIT)<br />
        ElseIf bytes Then<br />
            Function = GlobalAlloc(ByVal %MEMGPTR, ByVal bytes)<br />
        ElseIf hMem Then<br />
            Function = GlobalFree(ByVal hMem)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function MemFree(ByVal hMem As Long) As Long<br />
        'free memory<br />
        If hMem Then GlobalFree(ByVal hMem)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
#EndIf '%Memory230925<br />
<br />
#If Not %Def(%Ptr250717)<br />
    %Ptr250717 = 1<br />
    '----------------------------------------------------------------------<br />
    'Allocate UDT Pointer<br />
    'PtrT must be first item in UDT<br />
    'call PtrNew() to allocate pointer<br />
    'call PtrFree() to free pointer<br />
    'if PtrNewCallback() supplied then called after memory allocated<br />
    'if PtrFinalCallback() supplied then called before memory freed<br />
    'instance count supported but shouldn't be used unless needed<br />
    'should be made clear who is responsible for freeing pointer<br />
    '----------------------------------------------------------------------<br />
    Declare Sub PtrNewCallback(ByVal p As Long)<br />
        'if supplied then called after ponter allocated<br />
    Declare Sub PtrFinalCallback(ByVal p As Long)<br />
        'if supplied then called before ponter freed<br />
    Declare Function PtrStoreCallback(ByVal p As Long) As String<br />
        'return String holding pointer's associated data<br />
    Declare Function PtrRestorCallback(ByRef stored As String) As Long<br />
        'return new instance with data restored<br />
    '----------------------------------------------------------------------<br />
    %PtrTag = 648910727<br />
    Type PtrT<br />
        tag As Long<br />
        instances As Long<br />
        finalCB As Long<br />
    End Type<br />
    '----------------------------------------------------------------------<br />
    Function PtrNew(ByVal SizeOfUdt As Long, ByVal newCB As Long, ByVal finalCB As Long) As Long<br />
        'allocate new pointer instance : return pointer<br />
        'if new callback supplied then called after memory allocated<br />
        'if final callback supplied then called before memory freed<br />
        Local p As PtrT Ptr<br />
        ExitF(SizeOfUdt &lt; SizeOf(PtrT), "invalid size")<br />
            p = MemAlloc(SizeOfUdt)<br />
            If p Then<br />
                @p.tag = %PtrTag<br />
                @p.instances = 1<br />
                @p.finalCB = finalCB<br />
                If newCB Then Call Dword newCB Using PtrNewCallback(p)<br />
                Function = p<br />
            End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrFree(ByVal p As PtrT Ptr) As Long<br />
        'free pointer allocated by PtrNew() : return null<br />
        'if final callback was supplied then called before memory freed<br />
        If p Then<br />
            ExitF(@p.tag &lt;&gt; %PtrTag, "invalid ptr")<br />
            ExitF(@p.instances &lt; 1, "invalid instance count")<br />
            Decr @p.instances<br />
            If @p.instances = 0 Then<br />
                If @p.finalCB Then Call Dword @p.finalCB Using PtrFinalCallback(p)<br />
                MemFree(p)<br />
            End If<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub PtrIncr(ByVal p As PtrT Ptr)<br />
        'increment instance count<br />
        ExitS(p = 0 Or @p.tag &lt;&gt; %PtrTag, "invalid ptr")<br />
        Incr @p.instances<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
#EndIf '%Ptr250717<br />
<br />
#If Not %Def(%StrBuild250811)<br />
    %StrBuild250811 = 1<br />
    '----------------------------------------------------------------------<br />
    'String Builder<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    %BytSize = 1<br />
    %StrBuildBufferMax = 5000000<br />
    '----------------------------------------------------------------------<br />
    Type StrBuildT<br />
        mem As Long<br />
        count As Long<br />
        max As Long<br />
    End Type<br />
    '----------------------------------------------------------------------<br />
    Sub StrBuildPush(t As StrBuildT, ByVal value As String)<br />
        'append value<br />
        Local strlen, currentcount, buffer, newmax As Long<br />
        strlen = Len(value)<br />
        If strlen Then<br />
            If strlen &gt; t.max - t.count Then<br />
                currentcount = t.count : t.count = 0 : t.max = 0<br />
                buffer = Max&amp;(1, 2 * currentcount)<br />
                buffer = Min&amp;(buffer, %StrBuildBufferMax)<br />
                newmax = currentcount + buffer + strlen<br />
                t.mem = MemReAlloc(t.mem, newmax * %BytSize)<br />
                If t.mem = 0 Then Exit Sub<br />
                t.count = currentcount : t.max = newmax<br />
            End If<br />
            Memory Copy StrPtr(value), t.mem + (t.count * %BytSize), strlen * %BytSize<br />
            t.count += strlen<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrBuildPop(t As StrBuildT) As String<br />
        'get whole string : free memory<br />
        If t.mem And t.count Then Function = Peek&#36;(t.mem, t.count)<br />
        t.mem = MemFree(t.mem)<br />
        t.count = 0<br />
        t.max = 0<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
#EndIf '%StrBuild250811<br />
<br />
#If Not %Def(%FileStore250717)<br />
    %FileStore250717 = 1<br />
    Sub FilePut(ByVal file As WString, ByRef value As WString)<br />
        'store value to File<br />
        Local f As Long<br />
        If Len(file) = 0 Then Exit Sub<br />
        f = FreeFile<br />
        Open file For Binary As f<br />
        SetEof f<br />
        Put&#36; f, value<br />
        Close f<br />
    End Sub<br />
    Function FileGet(ByRef file As WString) As WString<br />
        'restore value from File<br />
        Local f As Long, value As WString<br />
        If IsFalse IsFile(file) Then Exit Function<br />
        f = FreeFile<br />
        Open file For Binary As f<br />
        Get&#36; f, Lof(f), value<br />
        Close f<br />
        Function = value<br />
    End Function<br />
#EndIf '%FileStore250717<br />
<br />
#If Not %Def(%PtrList250812)<br />
    %PtrList250812 = 1<br />
    '----------------------------------------------------------------------<br />
    'Pointer Doubly Linked List : Stack : Queue : Deque<br />
    'pointers to allocated UDTs<br />
    'PtrT must be first item in UDT<br />
    'pointer must have been allocated with PtrNew()<br />
    'stored pointers can't be null<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    Declare Function PtrStoreCallback(ByVal p As Long) As String<br />
        'return String holding pointer's associated data<br />
    Declare Function PtrRestorCallback(ByRef stored As String) As Long<br />
        'return new instance with data restored<br />
    '----------------------------------------------------------------------<br />
    %LngSize = 4<br />
    %PtrListTag = -699621965<br />
    Type PtrListNodeT<br />
        allocator As PtrT<br />
        next As PtrListNodeT Ptr<br />
        prev As PtrListNodeT Ptr<br />
        value As Long<br />
    End Type<br />
    Type PtrListT<br />
        allocator As PtrT<br />
        tag As Long<br />
        count As Long<br />
        first As PtrListNodeT Ptr<br />
        last As PtrListNodeT Ptr<br />
        cursor As PtrListNodeT Ptr<br />
        remember As PtrListNodeT Ptr<br />
    End Type<br />
    '----------------------------------------------------------------------<br />
    Function PtrListNew() As Long<br />
        'allocate new instance : return container pointer : call PtrFree(p) to free pointer<br />
        Function = PtrNew(SizeOf(PtrListT), CodePtr(PtrListNewCB), CodePtr(PtrListClear))<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListClear(ByVal p As PtrListT Ptr)<br />
        'empty container<br />
        Local node As PtrListNodeT Ptr<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        While @p.first<br />
            node = @p.first<br />
            @p.first = @node.next<br />
            PtrFree(node)<br />
        Wend<br />
        @p.last = 0<br />
        @p.count = 0<br />
        @p.cursor = 0<br />
        @p.remember = 0<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function PtrListCount(ByVal p As PtrListT Ptr) As Long<br />
        'get item count<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        Function = @p.count<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListAdd(ByVal p As PtrListT Ptr, ByVal pItem As Long)<br />
        'append value<br />
        PtrListPushLast p, pItem<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function PtrListCursor(ByVal p As PtrListT Ptr) As Long<br />
        'true/false if cursor valid<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListFirst(ByVal p As PtrListT Ptr) As Long<br />
        'move cursor to first node : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        @p.cursor = @p.first<br />
        If @p.cursor Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListNext(ByVal p As PtrListT Ptr) As Long<br />
        'move cursor to next node : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor Then @p.cursor = @p.@cursor.next<br />
        If @p.cursor Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListLast(ByVal p As PtrListT Ptr) As Long<br />
        'move cursor to last node : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        @p.cursor = @p.last<br />
        If @p.cursor Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPervious(ByVal p As PtrListT Ptr) As Long<br />
        'move cursor to previous node : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor Then @p.cursor = @p.@cursor.prev<br />
        If @p.cursor Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListRember(ByVal p As PtrListT Ptr) As Long<br />
        'remember cursor position : fail if cursor null : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        @p.remember = @p.cursor<br />
        If @p.remember Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListReturn(ByVal p As PtrListT Ptr) As Long<br />
        'return to remembered position : fail if node deleted : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        @p.cursor = @p.remember<br />
        If @p.cursor Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListGet(ByVal p As PtrListT Ptr) As Long<br />
        'get value at cursor position : null if cursor null<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor Then Function = @p.@cursor.value<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListDeleteForward(ByVal p As PtrListT Ptr) As Long<br />
        'move to next node and delete node at cursor position : fail if cursor null : true/false success<br />
        'cursor will be null if no next node<br />
        Local n As PtrListNodeT Ptr<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor Then<br />
            Function = 1<br />
            n = @p.cursor<br />
            @p.cursor = @p.@cursor.next<br />
            PtrListNodeDelete(p, n)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListDeleteBackward(ByVal p As PtrListT Ptr) As Long<br />
        'move to previous node and delete node at cursor position : fail if cursor null : true/false success<br />
        'cursor will be null if no previous node<br />
        Local n As PtrListNodeT Ptr<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor Then<br />
            Function = 1<br />
            n = @p.cursor<br />
            @p.cursor = @p.@cursor.prev<br />
            PtrListNodeDelete(p, n)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListIsBefore(ByVal p As PtrListT Ptr) As Long<br />
        'true/false if there is a node before cursor position<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor And @p.@cursor.prev Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListIsAfter(ByVal p As PtrListT Ptr) As Long<br />
        'true/false if there is a node after cursor position<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor And @p.@cursor.next Then Function = 1<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListDeleteBefore(ByVal p As PtrListT Ptr) As Long<br />
        'delete node before cursor position : fail if cursor null or no previous node : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor And @p.@cursor.prev Then<br />
            PtrListNodeDelete p, @p.@cursor.prev<br />
            Function = 1<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListDeleteAfter(ByVal p As PtrListT Ptr) As Long<br />
        'delete node after cursor position : fail if cursor null or no next node : true/false success<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor And @p.@cursor.next Then<br />
            PtrListNodeDelete p, @p.@cursor.next<br />
            Function = 1<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPushBefore(ByVal p As PtrListT Ptr, ByVal pItem As Long) As Long<br />
        'add value before cursor position : fail if cursor null : true/false success<br />
        Local node As PtrListNodeT Ptr<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitF(pItem = 0 Or Peek(Long, pItem) &lt;&gt; %PtrTag, "invalid item")<br />
        If @p.cursor Then<br />
            Function = 1<br />
            If @p.cursor = @p.first Then<br />
                PtrListPushFirst p, pItem<br />
            Else<br />
                ExitF(@p.@cursor.prev = 0, "previous node null")<br />
                node = PtrNew(SizeOf(PtrListNodeT), 0, CodePtr(PtrListNodeFinalCB))<br />
                ExitF(node = 0, "PtrNew fail")<br />
                @node.value = pItem<br />
                @node.next = @p.cursor<br />
                @node.prev = @p.@cursor.prev<br />
                @p.@cursor.@prev.next = node<br />
                @p.@cursor.prev = node<br />
            End If<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPushAfter(ByVal p As PtrListT Ptr, ByVal pItem As Long) As Long<br />
        'add value after cursor position : fail if cursor null : true/false success<br />
        Local node As PtrListNodeT Ptr<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitF(pItem = 0 Or Peek(Long, pItem) &lt;&gt; %PtrTag, "invalid item")<br />
        If @p.cursor Then<br />
            Function = 1<br />
            If @p.cursor = @p.last Then<br />
                PtrListPushLast p, pItem<br />
            Else<br />
                ExitF(@p.@cursor.next = 0, "next node null")<br />
                node = PtrNew(SizeOf(PtrListNodeT), 0, CodePtr(PtrListNodeFinalCB))<br />
                ExitF(node = 0, "PtrNew fail")<br />
                @node.value = pItem<br />
                @node.prev = @p.cursor<br />
                @node.next = @p.@cursor.next<br />
                @p.@cursor.@next.prev = node<br />
                @p.@cursor.next = node<br />
            End If<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPeekBefore(ByVal p As PtrListT Ptr) As Long<br />
        'get value before cursor position : null if cursor invalid or no previous node<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor And @p.@cursor.prev Then Function = @p.@cursor.@prev.value<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPeekAfter(ByVal p As PtrListT Ptr) As Long<br />
        'get value after cursor position : null if cursor invalid or no next node<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.cursor And @p.@cursor.next Then Function = @p.@cursor.@next.value<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPopBefore(ByVal p As PtrListT Ptr) As Long<br />
        'get and remove value before cursor position : null if cursor invalid or no previous node<br />
        'user responsible for freeing popped pointers<br />
        Local h As Long<br />
        h = PtrListPeekBefore(p)<br />
        If h Then<br />
            PtrIncr(h)<br />
            Function = h<br />
            PtrListDeleteBefore(p)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPopAfter(ByVal p As PtrListT Ptr) As Long<br />
        'get and remove value after cursor position : null if cursor invalid or no next node<br />
        'user responsible for freeing popped pointers<br />
        Local h As Long<br />
        h = PtrListPeekAfter(p)<br />
        If h Then<br />
            PtrIncr(h)<br />
            Function = h<br />
            PtrListDeleteAfter(p)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListPushFirst(ByVal p As PtrListT Ptr, ByVal pItem As Long)<br />
        'insert at front<br />
        Local node As PtrListNodeT Ptr<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitS(pItem = 0 Or Peek(Long, pItem) &lt;&gt; %PtrTag, "invalid item")<br />
        node = PtrNew(SizeOf(PtrListNodeT), 0, CodePtr(PtrListNodeFinalCB))<br />
        ExitS(node = 0, "PtrNew fail")<br />
        @node.value = pItem<br />
        If @p.count Then<br />
            ExitS(@p.first = 0, "first node null")<br />
            @p.@first.prev = node<br />
            @node.next = @p.first<br />
            @p.first = node<br />
            Incr @p.count<br />
        Else<br />
            @p.first = node<br />
            @p.last = node<br />
            @p.count = 1<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListPushLast(ByVal p As PtrListT Ptr, ByVal pItem As Long)<br />
        'append to end<br />
        Local node As PtrListNodeT Ptr<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitS(pItem = 0 Or Peek(Long, pItem) &lt;&gt; %PtrTag, "invalid item")<br />
        node = PtrNew(SizeOf(PtrListNodeT), 0, CodePtr(PtrListNodeFinalCB))<br />
        ExitS(node = 0, "PtrNew fail")<br />
        @node.value = pItem<br />
        If @p.count Then<br />
            ExitS(@p.last = 0, "last node null")<br />
            @p.@last.next = node<br />
            @node.prev = @p.last<br />
            @p.last = node<br />
            Incr @p.count<br />
        Else<br />
            @p.first = node<br />
            @p.last = node<br />
            @p.count = 1<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPeekFirst(ByVal p As PtrListT Ptr) As Long<br />
        'get fist value : null if list empty<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.first Then Function = @p.@first.value<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPeekLast(ByVal p As PtrListT Ptr) As Long<br />
        'get last value : null list empty<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.last Then Function = @p.@last.value<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPopFirst(ByVal p As PtrListT Ptr) As Long<br />
        'get and remove fist value : null if list empty<br />
        'user responsible for freeing popped pointers<br />
        Local h As Long<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.first Then<br />
            h = @p.@first.value<br />
            PtrIncr(h)<br />
            Function = h<br />
            PtrListNodeDelete(p, @p.first)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListPopLast(ByVal p As PtrListT Ptr) As Long<br />
        'get and remove last value : null if list empty<br />
        'user responsible for freeing popped pointers<br />
        Local h As Long<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        If @p.last Then<br />
            h = @p.@last.value<br />
            PtrIncr(h)<br />
            Function = h<br />
            PtrListNodeDelete(p, @p.last)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function PtrListStore(ByVal p As PtrListT Ptr, ByVal storeCB As Long) As String<br />
        'store container to String<br />
        'Declare Function PtrStoreCallback(ByVal p As Long) As String<br />
        Local more, h As Long<br />
        Local sb As StrBuildT<br />
        Local s As String<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitF(storeCB = 0, "null callback")<br />
        If @p.count Then<br />
            StrBuildPush sb, Mkl&#36;(@p.count)<br />
            more = PtrListFirst(p)<br />
            While more<br />
                h = PtrListGet(p)<br />
                ExitF(h = 0, "unexpected null")<br />
                Call Dword storeCB Using PtrStoreCallback(h) To s<br />
                StrBuildPush sb, Mkl&#36;(Len(s))<br />
                StrBuildPush sb, s<br />
                more = PtrListNext(p)<br />
            Wend<br />
        End If<br />
        Function = StrBuildPop(sb)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListRestore(ByVal p As PtrListT Ptr, ByRef stored As String, ByVal restoreCB As Long)<br />
        'restore container from String<br />
        'Declare Function PtrRestorCallback(ByRef stored As String) As Long<br />
        Register i As Long<br />
        Local items, bytes, h As Long<br />
        Local s As String<br />
        Local pl As Long Ptr<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitS(restoreCB = 0, "null callback")<br />
        PtrListClear p<br />
        If Len(stored) Then<br />
            pl = StrPtr(stored)<br />
            items = @pl : Incr pl<br />
            For i = 1 To items<br />
                bytes = @pl : Incr pl<br />
                s = Peek&#36;(pl, bytes) : pl += bytes<br />
                Call Dword restoreCB Using PtrRestorCallback(s) To h<br />
                ExitS(h = 0,"null callback return")<br />
                PtrListAdd p, h<br />
            Next i<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListFileStore(ByVal p As PtrListT Ptr, ByVal file As WString, ByVal storeCB As Long)<br />
        'store container to File<br />
        'Declare Function PtrStoreCallback(ByVal p As Long) As String<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitS(storeCB = 0, "null callback")<br />
        ExitS(Len(file) = 0, "invalid file")<br />
        FilePut file, PtrListStore(p, storeCB)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListFileRestore(ByVal p As PtrListT Ptr, ByVal file As WString, ByVal restoreCB As Long)<br />
        'restore container from File<br />
        'Declare Function PtrRestorCallback(ByRef stored As String) As Long<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %PtrListTag, "invalid ptr")<br />
        ExitS(IsFalse IsFile(file), "invalid file" + file)<br />
        PtrListRestore p, FileGet(file), restoreCB<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    'PRIVATE:<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListNewCB(ByVal p As PtrListT Ptr) Private<br />
        'PRIVATE: list new callback<br />
        If p Then @p.tag = %PtrListTag<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListNodeFinalCB(ByVal node As PtrListNodeT Ptr)<br />
        If node Then<br />
            PtrFree(@node.value)<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub PtrListNodeDelete(ByVal p As PtrListT Ptr, ByVal node As PtrListNodeT Ptr) Private<br />
        'PRIVATE: remove node from list<br />
        If node Then<br />
            If @p.cursor = node Then @p.cursor = 0<br />
            If @p.remember = node Then @p.remember = 0<br />
            If @p.first = node Then @p.first = @node.next<br />
            If @p.last = node Then @p.last = @node.prev<br />
            If @node.prev Then @node.@prev.next = @node.next<br />
            If @node.next Then @node.@next.prev = @node.prev<br />
            ExitS(@p.count = 0, "invalid count")<br />
            Decr @p.count<br />
            PtrFree(node)<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
#EndIf '%PtrList250812<br />
<br />
#If Not %Def(%Str250724)<br />
    %Str250724 = 1<br />
    '----------------------------------------------------------------------<br />
    'Dynamic String Container<br />
    '----------------------------------------------------------------------<br />
    Declare Function StrCompareCallback(ByVal a As Long, ByVal b As Long) As Long<br />
        'a &lt; b : return &lt; 0    a = b : return = 0    a &gt; b : return &gt; 0<br />
    '----------------------------------------------------------------------<br />
    %BytSize = 1<br />
    %StrTag = 653170298<br />
    Type StrT<br />
        allocator As PtrT<br />
        tag As Long<br />
        mem As Byte Ptr<br />
        count As Long<br />
    End Type<br />
    '----------------------------------------------------------------------<br />
    Function StrNew() As Long<br />
        'allocate new instance : return container pointer : call PtrFree(p) to free pointer<br />
        Function = PtrNew(SizeOf(StrT), CodePtr(StrNewCB), CodePtr(StrClear))<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrClear(ByVal p As StrT Ptr)<br />
        'empty container<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %StrTag, "invalid ptr")<br />
        @p.mem = MemFree(@p.mem)<br />
        @p.count = 0<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrGet(ByVal p As StrT Ptr) As String<br />
        'get value<br />
        ExitF(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %StrTag, "invalid ptr")<br />
        Function = Peek&#36;(@p.mem, @p.count)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrSet(ByVal p As StrT Ptr, ByRef value As String)<br />
        Local strlen As Long : strlen = Len(value)<br />
        Local bytes As Long : bytes = strlen * %BytSize<br />
        ExitS(p = 0 Or @p.allocator.tag &lt;&gt; %PtrTag Or @p.tag &lt;&gt; %StrTag, "invalid ptr")<br />
        @p.mem = MemFree(@p.mem) : @p.count = 0<br />
        If strlen Then<br />
            @p.mem = MemAlloc(bytes)<br />
            If @p.mem Then<br />
                @p.count = strlen<br />
                Memory Copy StrPtr(value), @p.mem, bytes<br />
            End If<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrNewValue(ByRef value As String) As Long<br />
        'allocate new instance : store value : return pointer<br />
        Local h As Long<br />
        h = StrNew()<br />
        StrSet h, value<br />
        Function = h<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrHashIndex(ByVal p As StrT Ptr, ByVal capacity As Long) As Long<br />
        'get key's one-based hash index position<br />
        Register i As Long : Local total, temp As Long<br />
        Function = 1<br />
        If p Then<br />
            For i = 0 To @p.count - 1<br />
                temp += @p.@mem[i] + total<br />
                Shift Left total, 8<br />
                total += temp<br />
            Next i<br />
            Function = Abs(total Mod capacity) + 1<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrEqual(ByVal a As StrT Ptr, ByVal b As StrT Ptr) As Long<br />
        'true/false if two strings equal<br />
        Register i As Long<br />
        If a And b And @a.count = @b.count Then<br />
            For i = 0 To @a.count - 1<br />
                If @a.@mem[i] &lt;&gt; @b.@mem[i] Then Exit Function<br />
            Next i<br />
            Function = 1<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrCompare(ByVal a As StrT Ptr, ByVal b As StrT Ptr) As Long<br />
        'compare callback : case sensitive<br />
        Register i As Long : Register compare As Long<br />
        If a And b Then<br />
            For i = 0 To Min&amp;(@a.count, @b.count) - 1<br />
                compare = @a.@mem[i] - @b.@mem[i]<br />
                If compare Then<br />
                    Function = compare : Exit Function<br />
                End If<br />
            Next i<br />
            Function = @a.count - @b.count<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrCompareIgnore(ByVal a As StrT Ptr, ByVal b As StrT Ptr) As Long<br />
        'compare callback : case sensitive<br />
        Register i As Long : Register compare As Long : Local la, lb As Long<br />
        If a And b Then<br />
            For i = 0 To Min&amp;(@a.count, @b.count) - 1<br />
                la = @a.@mem[i] : lb = @b.@mem[i]<br />
                If la &gt; 64 And la &lt; 91 Then la += 32<br />
                If lb &gt; 64 And lb &lt; 91 Then lb += 32<br />
                compare = la - lb<br />
                If compare Then<br />
                    Function = compare : Exit Function<br />
                End If<br />
            Next i<br />
            Function = @a.count - @b.count<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrStoreCB(ByVal p As StrT Ptr) As String<br />
        'store callback<br />
        Function = ChrToUtf8&#36;(StrGet(p))<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrRestoreCB(ByRef value As String) As Long<br />
        'restore callback<br />
        Function = StrNewValue(Utf8ToChr&#36;(value))<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    'PRIVATE:<br />
    '----------------------------------------------------------------------<br />
    Sub StrNewCB(ByVal p As StrT Ptr) Private<br />
        'PRIVATE: new callback<br />
        If p Then @p.tag = %StrTag<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
#EndIf '%Str250724<br />
<br />
#If Not %Def(%StrList250812)<br />
    %StrList250812 = 1<br />
    '----------------------------------------------------------------------<br />
    'String Doubly Linked List : Stack : Queue : Deque<br />
    '----------------------------------------------------------------------<br />
    Macro StrListT = PtrListT<br />
    '----------------------------------------------------------------------<br />
    Function StrListNew() As Long<br />
        'allocate new instance : return container pointer : call PtrFree(p) to free pointer<br />
        Function = PtrListNew()<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrListClear(ByVal p As StrListT Ptr)<br />
        'empty container<br />
        PtrListClear p<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrListCount(ByVal p As StrListT Ptr) As Long<br />
        'get item count<br />
        Function = PtrListCount(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrListAdd(ByVal p As StrListT Ptr, ByRef value As String)<br />
        'append value<br />
        PtrListAdd p, StrNewValue(value)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrListCursor(ByVal p As StrListT Ptr) As Long<br />
        'true/false if cursor valid<br />
        Function = PtrListCursor(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListFirst(ByVal p As StrListT Ptr) As Long<br />
        'move cursor to first node : true/false success<br />
        Function = PtrListFirst(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListNext(ByVal p As StrListT Ptr) As Long<br />
        'move cursor to next node : true/false success<br />
        Function = PtrListNext(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListLast(ByVal p As StrListT Ptr) As Long<br />
        'move cursor to last node : true/false success<br />
        Function = PtrListLast(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPervious(ByVal p As StrListT Ptr) As Long<br />
        'move cursor to previous node : true/false success<br />
        Function = PtrListPervious(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListRember(ByVal p As StrListT Ptr) As Long<br />
        'remember cursor position : fail if cursor null : true/false success<br />
        Function = PtrListRember(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListReturn(ByVal p As StrListT Ptr) As Long<br />
        'return to remembered position : fail if node deleted : true/false success<br />
        Function = PtrListReturn(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListGet(ByVal p As StrListT Ptr) As String<br />
        'get value at cursor position : null if cursor null<br />
        Local h As Long<br />
        h = PtrListGet(p)<br />
        If h Then Function = StrGet(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListSet(ByVal p As StrListT Ptr, ByRef value As String) As Long<br />
        'set value at cursor position : fail if cursor null : true/false success<br />
        Local h As Long<br />
        h = PtrListGet(p)<br />
        If h Then<br />
            StrSet h, value<br />
            Function = 1<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListDeleteForward(ByVal p As StrListT Ptr) As Long<br />
        'move to next node and delete node at cursor position : fail if cursor null : true/false success<br />
        'cursor will be null if no next node<br />
        Function = PtrListDeleteForward(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListDeleteBackward(ByVal p As StrListT Ptr) As Long<br />
        'move to previous node and delete node at cursor position : fail if cursor null : true/false success<br />
        'cursor will be null if no previous node<br />
        Function = PtrListDeleteBackward(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListIsBefore(ByVal p As StrListT Ptr) As Long<br />
        'true/false if there is a node before cursor position<br />
        Function = PtrListIsBefore(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListIsAfter(ByVal p As StrListT Ptr) As Long<br />
        'true/false if there is a node after cursor position<br />
        Function = PtrListIsAfter(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListDeleteBefore(ByVal p As StrListT Ptr) As Long<br />
        'delete node before cursor position : fail if cursor null or no previous node : true/false success<br />
        Function = PtrListDeleteBefore(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListDeleteAfter(ByVal p As StrListT Ptr) As Long<br />
        'delete node after cursor position : fail if cursor null or no next node : true/false success<br />
        Function = PtrListDeleteAfter(p)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPushBefore(ByVal p As StrListT Ptr, ByRef value As String) As Long<br />
        'add value before cursor position : fail if cursor null : true/false success<br />
        Local h As Long : h = StrNewValue(value)<br />
        If IsFalse PtrListPushBefore(p, h) Then PtrFree(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPushAfter(ByVal p As StrListT Ptr, ByRef value As String) As Long<br />
        'add value after cursor position : fail if cursor null : true/false success<br />
        'add value before cursor position : fail if cursor null : true/false success<br />
        Local h As Long : h = StrNewValue(value)<br />
        If IsFalse PtrListPushAfter(p, h) Then PtrFree(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPeekBefore(ByVal p As StrListT Ptr) As String<br />
        'get value before cursor position : null if cursor invalid or no previous node<br />
        Local h As Long : h = PtrListPeekBefore(p)<br />
        If h Then Function = StrGet(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPeekAfter(ByVal p As StrListT Ptr) As String<br />
        'get value after cursor position : null if cursor invalid or no next node<br />
        Local h As Long : h = PtrListPeekAfter(p)<br />
        If h Then Function = StrGet(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPopBefore(ByVal p As StrListT Ptr) As String<br />
        'get and remove value before cursor position : null if cursor invalid or no previous node<br />
        Local h As Long : h = PtrListPopBefore(p)<br />
        If h Then<br />
            Function = StrGet(h)<br />
            PtrFree(h)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPopAfter(ByVal p As StrListT Ptr) As String<br />
        'get and remove value after cursor position : null if cursor invalid or no next node<br />
        Local h As Long : h = PtrListPopAfter(p)<br />
        If h Then<br />
            Function = StrGet(h)<br />
            PtrFree(h)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrListPushFirst(ByVal p As StrListT Ptr, ByRef value As String)<br />
        'insert at front<br />
        PtrListPushFirst p, StrNewValue(value)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListPushLast(ByVal p As StrListT Ptr, ByRef value As String)<br />
        'append to end<br />
        PtrListPushLast p, StrNewValue(value)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrListPeekFirst(ByVal p As StrListT Ptr) As String<br />
        'get fist value : null if list empty<br />
        Local h As Long : h = PtrListPeekFirst(p)<br />
        If h Then Function = StrGet(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPeekLast(ByVal p As StrListT Ptr) As String<br />
        'get last value : null list empty<br />
        Local h As Long : h = PtrListPeekLast(p)<br />
        If h Then Function = StrGet(h)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPopFirst(ByVal p As StrListT Ptr) As String<br />
        'get and remove fist value : null if list empty<br />
        Local h As Long : h = PtrListPopFirst(p)<br />
        If h Then<br />
            Function = StrGet(h)<br />
            PtrFree(h)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function StrListPopLast(ByVal p As StrListT Ptr) As String<br />
        'get and remove last value : null if list empty<br />
        Local h As Long : h = PtrListPopLast(p)<br />
        If h Then<br />
            Function = StrGet(h)<br />
            PtrFree(h)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrListImport(ByVal p As StrListT Ptr, a() As String)<br />
        'import PB array<br />
        Register i As Long<br />
        StrListClear p<br />
        For i = LBound(a) To UBound(a)<br />
            StrListAdd p, a(i)<br />
        Next i<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListExport(ByVal p As StrListT Ptr, a() As String)<br />
        'export to PB array<br />
        Register i As Long<br />
        Local more As Long<br />
        Erase a()<br />
        If StrListCount(p) Then<br />
            ReDim a(1 To StrListCount(p))<br />
            more = StrListFirst(p)<br />
            While more<br />
                Incr i<br />
                a(i) = StrListGet(p)<br />
                more = StrListNext(p)<br />
            Wend<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListSplit(ByVal p As StrListT Ptr, ByVal delimited As String, ByVal delimiter As String)<br />
        'split array on delimited string<br />
        Local items As Long<br />
        Local a() As String<br />
        StrListClear p<br />
        If Len(delimited) Then<br />
            items = ParseCount(delimited, delimiter)<br />
            If items Then<br />
                Dim a(1 To items)<br />
                Parse delimited, a(), delimiter<br />
                StrListImport p, a()<br />
            End If<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrListJoin(ByVal p As StrListT Ptr, ByVal delimiter As String) As String<br />
        'join array on delimiter<br />
        Local a() As String<br />
        StrListExport p, a()<br />
        Function = Join&#36;(a(), delimiter)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrListToText(ByVal p As StrListT Ptr, ByVal file As WString)<br />
        'store list as text file<br />
        If StrListCount(p) Then<br />
            FilePut file, RTrim&#36;(StrListJoin(p, &#36;CrLf))  + &#36;CrLf<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListFromText(ByVal p As StrListT Ptr, ByVal file As WString)<br />
        'load text file<br />
        Local contents As String<br />
        StrListClear p<br />
        contents = FileGet(file)<br />
        contents = Trim&#36;(contents, &#36;CrLf)<br />
        If Len(contents) Then<br />
            StrListSplit p, contents, &#36;CrLf<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListFolders(ByVal p As StrListT Ptr, ByVal rootFolder As String)<br />
        'get all folders in root folder<br />
        Local folder, folderMask, rootPath As String<br />
        Local DrD As DirData<br />
        StrListClear p<br />
        ExitS(IsFalse IsFolder(rootFolder), "invalid folder")<br />
        rootPath = RTrim&#36;(rootFolder, "&#92;") + "&#92;"<br />
        folderMask = rootPath<br />
        folder = Dir&#36;(folderMask, Only %SubDir To DrD)<br />
        While Len(folder)<br />
            StrListAdd p, rootPath + folder<br />
            folder = Dir&#36;<br />
        Wend<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListFiles(ByVal p As StrListT Ptr, ByVal folder As String, ByVal mask As String)<br />
        'get all files in folder matching mask<br />
        Local file, fileMask As String<br />
        StrListClear p<br />
        ExitS(IsFalse IsFolder(folder), "invalid folder")<br />
        folder = RTrim&#36;(folder, "&#92;") + "&#92;"<br />
        fileMask = folder + mask<br />
        file = Dir&#36;(fileMask)<br />
        While Len(file)<br />
            StrListAdd p, folder + file<br />
            file = Dir&#36;<br />
        Wend<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListAllFolders(ByVal p As StrListT Ptr, ByVal rootFolder As String)<br />
        'get all folders in root folder and sub-folders, including root folder<br />
        Local folderStack As Long : folderStack = StrListNew()<br />
        Local subFolderStack As Long : subFolderStack = StrListNew()<br />
        Local currentFolder As String<br />
        StrListClear p<br />
        ExitS(IsFalse IsFolder(rootFolder), "invalid folder")<br />
        StrListPushLast folderStack, rootFolder<br />
        While StrListCount(folderStack)<br />
            currentFolder = StrListPopLast(folderStack)<br />
            StrListAdd p, currentFolder<br />
            StrListFolders subFolderStack, currentFolder<br />
            While StrListCount(subFolderStack)<br />
                StrListPushLast folderStack, StrListPopLast(subFolderStack)<br />
            Wend<br />
        Wend<br />
        folderStack = PtrFree(folderStack)<br />
        subFolderStack = PtrFree(subFolderStack)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListAllFiles(ByVal p As StrListT Ptr, ByVal rootFolder As String, ByVal mask As String)<br />
        'get all files in root folder, and sub-folders, matching mask<br />
        Local allFolderStack As Long : allFolderStack = StrListNew()<br />
        Local folderFileStack As Long : folderFileStack = StrListNew()<br />
        StrListClear p<br />
        ExitS(IsFalse IsFolder(rootFolder), "invalid folder")<br />
        StrListAllFolders allFolderStack, rootFolder<br />
        While StrListCount(allFolderStack)<br />
            StrListFiles folderFileStack, StrListPopLast(allFolderStack), mask<br />
            While StrListCount(folderFileStack)<br />
                StrListAdd p, StrListPopLast(folderFileStack)<br />
            Wend<br />
        Wend<br />
        allFolderStack = PtrFree(allFolderStack)<br />
        folderFileStack = PtrFree(folderFileStack)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrListStore(ByVal p As StrListT Ptr) As String<br />
        'store container to String<br />
        Function = PtrListStore(p, CodePtr(StrStoreCB))<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Sub StrListRestore(ByVal p As StrListT Ptr, ByRef stored As String)<br />
        'restore container from String<br />
        PtrListRestore p, stored, CodePtr(StrRestoreCB)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListFileStore(ByVal p As StrListT Ptr, ByVal file As WString)<br />
        'store container to File<br />
        PtrListFileStore p, file, CodePtr(StrStoreCB)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub StrListFileRestore(ByVal p As StrListT Ptr, ByVal file As WString)<br />
        'restore container from File<br />
        PtrListFileRestore p, file, CodePtr(StrRestoreCB)<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
#EndIf '%StrList250812</code></div></div>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Very Simple Round Gauge 0-100%]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=84</link>
			<pubDate>Wed, 24 Sep 2025 16:01:40 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=11">Jules Marchildon</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=84</guid>
			<description><![CDATA[I needed a very simple round gauge for one of our in-house measuring dashboards.  I didn't use any double buffer or GDI+ as it is only intended to be a static needle most of the time.  <br />
<br />
This one demo's percentage, but you can modify it to whatever; volts, amps, watts, temp, cats, dogs.   <img src="http://pump.richheimer.de/images/smilies/biggrin.png" alt="Big Grin" title="Big Grin" class="smilie smilie_4" /><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=60" target="_blank" title="">round guage.jpg</a> (Size: 43.71 KB / Downloads: 22)
<!-- end: postbit_attachments_attachment --><br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'<br />
' Very simple round gauge, 0-100%<br />
' Jules Marchildon, Sept. 24, 2025<br />
'<br />
<br />
#COMPILE EXE<br />
#DIM ALL<br />
<br />
#INCLUDE "WIN32API.INC"<br />
<br />
GLOBAL gValue AS LONG<br />
GLOBAL ghDlg AS DWORD<br />
<br />
'--------------------------------------------------------------------------------------------------<br />
'<br />
'--------------------------------------------------------------------------------------------------<br />
FUNCTION PBMAIN()<br />
    DIALOG NEW 0, "Round Gauge: 0-100%",,, 200, 200, %WS_SYSMENU TO ghDlg<br />
    DIALOG SHOW MODAL ghDlg CALL DlgProc<br />
END FUNCTION<br />
<br />
'--------------------------------------------------------------------------------------------------<br />
'<br />
'--------------------------------------------------------------------------------------------------<br />
CALLBACK FUNCTION DlgProc()<br />
<br />
    LOCAL ps AS PAINTSTRUCT<br />
    LOCAL hDC, hMemDC, hBitmap, hOldBitmap AS DWORD<br />
    LOCAL hGauge AS DWORD<br />
    LOCAL rc AS RECT<br />
    LOCAL cx AS LONG, cy AS LONG, radius AS LONG<br />
<br />
    SELECT CASE CB.MSG<br />
<br />
        CASE %WM_INITDIALOG<br />
            '-simulate gauge updates with a timer<br />
            SetTimer CB.HNDL, 1, 300, BYVAL %NULL<br />
<br />
        CASE %WM_TIMER<br />
            gValue = (gValue + 1) MOD 101<br />
            InvalidateRect CB.HNDL, BYVAL %NULL, %TRUE<br />
<br />
        CASE %WM_PAINT<br />
            hDC = BeginPaint(CB.HNDL, ps)<br />
                GetClientRect(CB.HNDL, rc)<br />
                hMemDC = CreateCompatibleDC(hDC)<br />
                hBitmap = CreateCompatibleBitmap(hDC, rc.nRight, rc.nBottom)<br />
                hOldBitmap = SelectObject(hMemDC, hBitmap)<br />
                FillRect hMemDC, rc, GetSysColorBrush(%COLOR_BTNFACE)<br />
                '-calc center and radius<br />
                cx = (rc.nRight - rc.nLeft) &#92; 2<br />
                cy = (rc.nBottom - rc.nTop) &#92; 2<br />
                radius = MIN(cx, cy) - 10<br />
                DrawGauge(hMemDC, cx, cy, radius, gValue)<br />
                BitBlt hDC, 0, 0, rc.nRight, rc.nBottom, hMemDC, 0, 0, %SRCCOPY<br />
                SelectObject hMemDC, hOldBitmap<br />
                DeleteObject hBitmap<br />
                DeleteDC hMemDC<br />
            EndPaint(CB.HNDL, ps)<br />
<br />
        CASE %WM_DESTROY<br />
            KillTimer CB.HNDL, 1<br />
            PostQuitMessage 0<br />
<br />
    END SELECT<br />
END FUNCTION<br />
<br />
'--------------------------------------------------------------------------------------------------<br />
'<br />
'--------------------------------------------------------------------------------------------------<br />
SUB DrawGauge(hDC AS DWORD, cx AS LONG, cy AS LONG, radius AS LONG, value AS LONG)<br />
<br />
    LOCAL angle, rad, offsetRad, aDeg, percent, needleWidth AS SINGLE<br />
    LOCAL hPen, hOldPen, hFont, hOldFont, hBrush, hOldBrush AS DWORD<br />
    LOCAL i, x1, y1, x2, y2, xTip, yTip, r, g, b AS LONG<br />
    LOCAL xBase1, yBase1, xBase2, yBase2 AS LONG<br />
    LOCAL sVal AS STRING, lsize AS SIZE, cherryPi AS EXT<br />
<br />
    cherryPi = 4*ATN(1)<br />
<br />
    '=== Arc background with green-yellow-red segments ===<br />
    FOR aDeg = 225 TO -45 STEP -1<br />
        rad = aDeg * cherryPi / 180<br />
        x1 = cx + radius * 0.85 * COS(rad)<br />
        y1 = cy - radius * 0.85 * SIN(rad)<br />
        x2 = cx + radius * 0.95 * COS(rad)<br />
        y2 = cy - radius * 0.95 * SIN(rad)<br />
        '-calc perc% across arc 0 to 1<br />
        percent = (225 - aDeg) / 270<br />
        '-interpolate color<br />
        IF percent &lt;= 0.25 THEN<br />
            '-green to Yellow<br />
            r = percent / 0.25 * 255<br />
            g = 255<br />
            b = 0<br />
        ELSEIF percent &lt;= 0.75 THEN<br />
            '-yellow to Red<br />
            r = 255<br />
            g = 255 - ((percent - 0.25) / 0.5 * 255)<br />
            b = 0<br />
        ELSE<br />
            '-solid Red<br />
            r = 255<br />
            g = 0<br />
            b = 0<br />
        END IF<br />
        hPen = CreatePen(%PS_SOLID, 1, RGB(r, g, b))<br />
        hOldPen = SelectObject(hDC, hPen)<br />
        MoveToEx hDC, x1, y1, BYVAL %NULL<br />
        LineTo hDC, x2, y2<br />
        SelectObject hDC, hOldPen<br />
        DeleteObject hPen<br />
    NEXT<br />
<br />
<br />
    #IF 1 '=== Optional chintzy bezel ring ===<br />
        hPen = CreatePen(%PS_SOLID, 4, RGB(190, 190, 190))<br />
        hOldPen = SelectObject(hDC, hPen)<br />
        LOCAL ltweak AS LONG<br />
        ltweak = 1 '-tweak gap to your own taste<br />
        ARC hDC, cx - radius - ltweak, cy - radius - ltweak, cx + radius + ltweak, cy + radius + ltweak, 0, 0, 0, 0<br />
        SelectObject hDC, hOldPen<br />
        DeleteObject hPen<br />
    #ENDIF<br />
<br />
    '=== tick marks and labels every 5 units ===<br />
    FOR i = 0 TO 100 STEP 5<br />
        angle = (225 - (i * 2.7!)) * cherryPi / 180<br />
        '-major tick length for labels every 10 units, short for 5 units<br />
        IF (i MOD 10) = 0 THEN<br />
            x1 = cx + radius * 0.80 * COS(angle)<br />
            y1 = cy - radius * 0.80 * SIN(angle)<br />
        ELSE<br />
            x1 = cx + radius * 0.84 * COS(angle)<br />
            y1 = cy - radius * 0.84 * SIN(angle)<br />
        END IF<br />
        x2 = cx + radius * 0.92 * COS(angle)<br />
        y2 = cy - radius * 0.92 * SIN(angle)<br />
        MoveToEx hDC, x1, y1, BYVAL %NULL<br />
        LineTo hDC, x2, y2<br />
        '-label every 10 units only<br />
        IF (i MOD 10) = 0 THEN<br />
            LOCAL sText AS STRING<br />
            sText = FORMAT&#36;(i)<br />
            SetBkMode hDC, %TRANSPARENT<br />
            SetTextColor hDC, RGB(40, 140, 240)<br />
            LOCAL tx AS LONG, ty AS LONG<br />
            tx = cx + radius * 0.65 * COS(angle) - 10<br />
            ty = cy - radius * 0.65 * SIN(angle) - 8<br />
            TextOut hDC, tx, ty, BYVAL STRPTR(sText), LEN(sText)<br />
        END IF<br />
    NEXT<br />
<br />
    '=== draw polygon needle ===<br />
    angle = (225 - (gValue * 2.7!))  '&lt;-map gValue (0-100) to 225 to -45<br />
    rad = angle * cherryPi / 180<br />
    '-tip of the needle<br />
    xTip = cx + radius * 0.75 * COS(rad)<br />
    yTip = cy - radius * 0.75 * SIN(rad)<br />
    '-base width and angle offset for polygon<br />
    needleWidth = 6.0  '&lt;-adjust width here<br />
    offsetRad = 90 * cherryPi / 180  '-perpendicular<br />
    '-two base corners *perpendicular to needle angle<br />
    xBase1 = cx + needleWidth * COS(rad + offsetRad)<br />
    yBase1 = cy - needleWidth * SIN(rad + offsetRad)<br />
    xBase2 = cx + needleWidth * COS(rad - offsetRad)<br />
    yBase2 = cy - needleWidth * SIN(rad - offsetRad)<br />
    '-define needle polygon points<br />
    DIM pts(0 TO 2) AS POINTAPI<br />
    pts(0).x = xTip : pts(0).y = yTip<br />
    pts(1).x = xBase1 : pts(1).y = yBase1<br />
    pts(2).x = xBase2 : pts(2).y = yBase2<br />
    '-draw needle<br />
    hBrush = CreateSolidBrush(RGB(255, 0, 0))    '-fill color<br />
    hOldBrush = SelectObject(hDC, hBrush)<br />
    hPen = CreatePen(%PS_SOLID, 1, RGB(0, 0, 0)) '-outline<br />
    hOldPen = SelectObject(hDC, hPen)<br />
    POLYGON hDC, pts(0), 3<br />
    SelectObject hDC, hOldBrush<br />
    DeleteObject hBrush<br />
    SelectObject hDC, hOldPen<br />
    DeleteObject hPen<br />
    '-center cap<br />
    ELLIPSE hDC, cx - 8, cy - 8, cx + 8, cy + 8<br />
    '=== digital value display in the center ===<br />
    sVal = FORMAT&#36;(value) + "%"<br />
    hFont = CreateFont(28, 0, 0, 0, %FW_BOLD, 0, 0, 0, _<br />
                %ANSI_CHARSET, %OUT_DEFAULT_PRECIS, %CLIP_DEFAULT_PRECIS, _<br />
                %DEFAULT_QUALITY, %DEFAULT_PITCH OR %FF_DONTCARE, "Segoe UI")<br />
    hOldFont = SelectObject(hDC, hFont)<br />
    SetBkMode hDC, %TRANSPARENT<br />
    SetTextColor hDC, RGB(0, 102, 204)<br />
    '-measure text size to center it<br />
    GetTextExtentPoint32 hDC, BYVAL STRPTR(sVal), LEN(sVal), lsize<br />
    TextOut hDC, cx - lsize.cx &#92; 2, cy - lsize.cy &#92; 2 +60, BYVAL STRPTR(sVal), LEN(sVal)<br />
    SelectObject hDC, hOldFont<br />
    DeleteObject hFont<br />
<br />
END SUB<br />
                                         </code></div></div>]]></description>
			<content:encoded><![CDATA[I needed a very simple round gauge for one of our in-house measuring dashboards.  I didn't use any double buffer or GDI+ as it is only intended to be a static needle most of the time.  <br />
<br />
This one demo's percentage, but you can modify it to whatever; volts, amps, watts, temp, cats, dogs.   <img src="http://pump.richheimer.de/images/smilies/biggrin.png" alt="Big Grin" title="Big Grin" class="smilie smilie_4" /><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=60" target="_blank" title="">round guage.jpg</a> (Size: 43.71 KB / Downloads: 22)
<!-- end: postbit_attachments_attachment --><br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'<br />
' Very simple round gauge, 0-100%<br />
' Jules Marchildon, Sept. 24, 2025<br />
'<br />
<br />
#COMPILE EXE<br />
#DIM ALL<br />
<br />
#INCLUDE "WIN32API.INC"<br />
<br />
GLOBAL gValue AS LONG<br />
GLOBAL ghDlg AS DWORD<br />
<br />
'--------------------------------------------------------------------------------------------------<br />
'<br />
'--------------------------------------------------------------------------------------------------<br />
FUNCTION PBMAIN()<br />
    DIALOG NEW 0, "Round Gauge: 0-100%",,, 200, 200, %WS_SYSMENU TO ghDlg<br />
    DIALOG SHOW MODAL ghDlg CALL DlgProc<br />
END FUNCTION<br />
<br />
'--------------------------------------------------------------------------------------------------<br />
'<br />
'--------------------------------------------------------------------------------------------------<br />
CALLBACK FUNCTION DlgProc()<br />
<br />
    LOCAL ps AS PAINTSTRUCT<br />
    LOCAL hDC, hMemDC, hBitmap, hOldBitmap AS DWORD<br />
    LOCAL hGauge AS DWORD<br />
    LOCAL rc AS RECT<br />
    LOCAL cx AS LONG, cy AS LONG, radius AS LONG<br />
<br />
    SELECT CASE CB.MSG<br />
<br />
        CASE %WM_INITDIALOG<br />
            '-simulate gauge updates with a timer<br />
            SetTimer CB.HNDL, 1, 300, BYVAL %NULL<br />
<br />
        CASE %WM_TIMER<br />
            gValue = (gValue + 1) MOD 101<br />
            InvalidateRect CB.HNDL, BYVAL %NULL, %TRUE<br />
<br />
        CASE %WM_PAINT<br />
            hDC = BeginPaint(CB.HNDL, ps)<br />
                GetClientRect(CB.HNDL, rc)<br />
                hMemDC = CreateCompatibleDC(hDC)<br />
                hBitmap = CreateCompatibleBitmap(hDC, rc.nRight, rc.nBottom)<br />
                hOldBitmap = SelectObject(hMemDC, hBitmap)<br />
                FillRect hMemDC, rc, GetSysColorBrush(%COLOR_BTNFACE)<br />
                '-calc center and radius<br />
                cx = (rc.nRight - rc.nLeft) &#92; 2<br />
                cy = (rc.nBottom - rc.nTop) &#92; 2<br />
                radius = MIN(cx, cy) - 10<br />
                DrawGauge(hMemDC, cx, cy, radius, gValue)<br />
                BitBlt hDC, 0, 0, rc.nRight, rc.nBottom, hMemDC, 0, 0, %SRCCOPY<br />
                SelectObject hMemDC, hOldBitmap<br />
                DeleteObject hBitmap<br />
                DeleteDC hMemDC<br />
            EndPaint(CB.HNDL, ps)<br />
<br />
        CASE %WM_DESTROY<br />
            KillTimer CB.HNDL, 1<br />
            PostQuitMessage 0<br />
<br />
    END SELECT<br />
END FUNCTION<br />
<br />
'--------------------------------------------------------------------------------------------------<br />
'<br />
'--------------------------------------------------------------------------------------------------<br />
SUB DrawGauge(hDC AS DWORD, cx AS LONG, cy AS LONG, radius AS LONG, value AS LONG)<br />
<br />
    LOCAL angle, rad, offsetRad, aDeg, percent, needleWidth AS SINGLE<br />
    LOCAL hPen, hOldPen, hFont, hOldFont, hBrush, hOldBrush AS DWORD<br />
    LOCAL i, x1, y1, x2, y2, xTip, yTip, r, g, b AS LONG<br />
    LOCAL xBase1, yBase1, xBase2, yBase2 AS LONG<br />
    LOCAL sVal AS STRING, lsize AS SIZE, cherryPi AS EXT<br />
<br />
    cherryPi = 4*ATN(1)<br />
<br />
    '=== Arc background with green-yellow-red segments ===<br />
    FOR aDeg = 225 TO -45 STEP -1<br />
        rad = aDeg * cherryPi / 180<br />
        x1 = cx + radius * 0.85 * COS(rad)<br />
        y1 = cy - radius * 0.85 * SIN(rad)<br />
        x2 = cx + radius * 0.95 * COS(rad)<br />
        y2 = cy - radius * 0.95 * SIN(rad)<br />
        '-calc perc% across arc 0 to 1<br />
        percent = (225 - aDeg) / 270<br />
        '-interpolate color<br />
        IF percent &lt;= 0.25 THEN<br />
            '-green to Yellow<br />
            r = percent / 0.25 * 255<br />
            g = 255<br />
            b = 0<br />
        ELSEIF percent &lt;= 0.75 THEN<br />
            '-yellow to Red<br />
            r = 255<br />
            g = 255 - ((percent - 0.25) / 0.5 * 255)<br />
            b = 0<br />
        ELSE<br />
            '-solid Red<br />
            r = 255<br />
            g = 0<br />
            b = 0<br />
        END IF<br />
        hPen = CreatePen(%PS_SOLID, 1, RGB(r, g, b))<br />
        hOldPen = SelectObject(hDC, hPen)<br />
        MoveToEx hDC, x1, y1, BYVAL %NULL<br />
        LineTo hDC, x2, y2<br />
        SelectObject hDC, hOldPen<br />
        DeleteObject hPen<br />
    NEXT<br />
<br />
<br />
    #IF 1 '=== Optional chintzy bezel ring ===<br />
        hPen = CreatePen(%PS_SOLID, 4, RGB(190, 190, 190))<br />
        hOldPen = SelectObject(hDC, hPen)<br />
        LOCAL ltweak AS LONG<br />
        ltweak = 1 '-tweak gap to your own taste<br />
        ARC hDC, cx - radius - ltweak, cy - radius - ltweak, cx + radius + ltweak, cy + radius + ltweak, 0, 0, 0, 0<br />
        SelectObject hDC, hOldPen<br />
        DeleteObject hPen<br />
    #ENDIF<br />
<br />
    '=== tick marks and labels every 5 units ===<br />
    FOR i = 0 TO 100 STEP 5<br />
        angle = (225 - (i * 2.7!)) * cherryPi / 180<br />
        '-major tick length for labels every 10 units, short for 5 units<br />
        IF (i MOD 10) = 0 THEN<br />
            x1 = cx + radius * 0.80 * COS(angle)<br />
            y1 = cy - radius * 0.80 * SIN(angle)<br />
        ELSE<br />
            x1 = cx + radius * 0.84 * COS(angle)<br />
            y1 = cy - radius * 0.84 * SIN(angle)<br />
        END IF<br />
        x2 = cx + radius * 0.92 * COS(angle)<br />
        y2 = cy - radius * 0.92 * SIN(angle)<br />
        MoveToEx hDC, x1, y1, BYVAL %NULL<br />
        LineTo hDC, x2, y2<br />
        '-label every 10 units only<br />
        IF (i MOD 10) = 0 THEN<br />
            LOCAL sText AS STRING<br />
            sText = FORMAT&#36;(i)<br />
            SetBkMode hDC, %TRANSPARENT<br />
            SetTextColor hDC, RGB(40, 140, 240)<br />
            LOCAL tx AS LONG, ty AS LONG<br />
            tx = cx + radius * 0.65 * COS(angle) - 10<br />
            ty = cy - radius * 0.65 * SIN(angle) - 8<br />
            TextOut hDC, tx, ty, BYVAL STRPTR(sText), LEN(sText)<br />
        END IF<br />
    NEXT<br />
<br />
    '=== draw polygon needle ===<br />
    angle = (225 - (gValue * 2.7!))  '&lt;-map gValue (0-100) to 225 to -45<br />
    rad = angle * cherryPi / 180<br />
    '-tip of the needle<br />
    xTip = cx + radius * 0.75 * COS(rad)<br />
    yTip = cy - radius * 0.75 * SIN(rad)<br />
    '-base width and angle offset for polygon<br />
    needleWidth = 6.0  '&lt;-adjust width here<br />
    offsetRad = 90 * cherryPi / 180  '-perpendicular<br />
    '-two base corners *perpendicular to needle angle<br />
    xBase1 = cx + needleWidth * COS(rad + offsetRad)<br />
    yBase1 = cy - needleWidth * SIN(rad + offsetRad)<br />
    xBase2 = cx + needleWidth * COS(rad - offsetRad)<br />
    yBase2 = cy - needleWidth * SIN(rad - offsetRad)<br />
    '-define needle polygon points<br />
    DIM pts(0 TO 2) AS POINTAPI<br />
    pts(0).x = xTip : pts(0).y = yTip<br />
    pts(1).x = xBase1 : pts(1).y = yBase1<br />
    pts(2).x = xBase2 : pts(2).y = yBase2<br />
    '-draw needle<br />
    hBrush = CreateSolidBrush(RGB(255, 0, 0))    '-fill color<br />
    hOldBrush = SelectObject(hDC, hBrush)<br />
    hPen = CreatePen(%PS_SOLID, 1, RGB(0, 0, 0)) '-outline<br />
    hOldPen = SelectObject(hDC, hPen)<br />
    POLYGON hDC, pts(0), 3<br />
    SelectObject hDC, hOldBrush<br />
    DeleteObject hBrush<br />
    SelectObject hDC, hOldPen<br />
    DeleteObject hPen<br />
    '-center cap<br />
    ELLIPSE hDC, cx - 8, cy - 8, cx + 8, cy + 8<br />
    '=== digital value display in the center ===<br />
    sVal = FORMAT&#36;(value) + "%"<br />
    hFont = CreateFont(28, 0, 0, 0, %FW_BOLD, 0, 0, 0, _<br />
                %ANSI_CHARSET, %OUT_DEFAULT_PRECIS, %CLIP_DEFAULT_PRECIS, _<br />
                %DEFAULT_QUALITY, %DEFAULT_PITCH OR %FF_DONTCARE, "Segoe UI")<br />
    hOldFont = SelectObject(hDC, hFont)<br />
    SetBkMode hDC, %TRANSPARENT<br />
    SetTextColor hDC, RGB(0, 102, 204)<br />
    '-measure text size to center it<br />
    GetTextExtentPoint32 hDC, BYVAL STRPTR(sVal), LEN(sVal), lsize<br />
    TextOut hDC, cx - lsize.cx &#92; 2, cy - lsize.cy &#92; 2 +60, BYVAL STRPTR(sVal), LEN(sVal)<br />
    SelectObject hDC, hOldFont<br />
    DeleteObject hFont<br />
<br />
END SUB<br />
                                         </code></div></div>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[gbScroller - Scroll Image]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=82</link>
			<pubDate>Tue, 23 Sep 2025 04:04:43 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=94">Gary Beene</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=82</guid>
			<description><![CDATA[gbScroller is the companion app to gbClientCapture.  Both are intended to support viewing of sheet music in a horizontal format.<br />
<br />
gbClientCapture captures multiple images from a music sheet and merges them into a single horizontal image.<br />
<br />
gbScroller displays the merged image, scrolling it to the left.<br />
<br />
Both are discussed in the same thread. <a href="http://pump.richheimer.de/showthread.php?tid=81&amp;pid=562#pid562" target="_blank" rel="noopener" class="mycode_url">http://pump.richheimer.de/showthread.php...562#pid562</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Features:</span><br />
1. Zoom the merged image<br />
2. Scroll the merged image, with speed control <br />
3. Display a vertical line between individual images<br />
<br />
<img src="https://garybeene.com/files/gbscroller.jpg" loading="lazy"  alt="[Image: gbscroller.jpg]" class="mycode_img" /><br />
<br />
Files for both gbClientCapture and gbScroller are in the zip <a href="https://garybeene.com/files/gbclientcapture.zip" target="_blank" rel="noopener" class="mycode_url">https://garybeene.com/files/gbclientcapture.zip</a><br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'Compilable Example: Jose Includes<br />
<br />
#Compile Exe  "gbscroller.exe"<br />
#Dim All<br />
<br />
#Debug Error On<br />
#Debug Display On<br />
<br />
%Unicode = 1<br />
<br />
#Include "WIN32API.INC"<br />
#Include "cgdiplus.inc" '&lt;-replace with Jose or you own GDI+ include file.<br />
<br />
Enum Equates Singular<br />
 &nbsp;&nbsp;IDC_Toolbar &nbsp;&nbsp;= 500<br />
 &nbsp;&nbsp;IDC_Graphic<br />
 &nbsp;&nbsp;IDT_Exit<br />
 &nbsp;&nbsp;IDT_Faster<br />
 &nbsp;&nbsp;IDT_Slower<br />
 &nbsp;&nbsp;IDT_View<br />
 &nbsp;&nbsp;IDT_ShowLine<br />
 &nbsp;&nbsp;IDT_Scroll<br />
 &nbsp;&nbsp;IDC_StatusBar<br />
 &nbsp;&nbsp;IDT_ResetZoom<br />
 &nbsp;&nbsp;IDT_ResetScroll<br />
 &nbsp;&nbsp;IDC_Up<br />
 &nbsp;&nbsp;IDC_Down<br />
 &nbsp;&nbsp;IDT_StopScroll<br />
 &nbsp;&nbsp;IDT_Plus<br />
 &nbsp;&nbsp;IDT_Minus<br />
 &nbsp;&nbsp;ID_Timer<br />
End Enum<br />
<br />
#Resource Manifest, 1,      "icons&#92;xptheme_dpiaware.xml"<br />
<br />
#Resource Icon logo, "icons&#92;scroll2.ico"<br />
#Resource Icon zexit, "icons&#92;power.ico"<br />
#Resource Icon zview, "icons&#92;view.ico"<br />
#Resource Icon zleft, "icons&#92;scrollleft.ico"<br />
#Resource Icon zright, "icons&#92;scrollright.ico"<br />
#Resource Icon zstop, "icons&#92;stop.ico"<br />
#Resource Icon zplus, "icons&#92;plus.ico"<br />
#Resource Icon zminus, "icons&#92;minus.ico"<br />
#Resource Icon zreset, "icons&#92;reset.ico"<br />
<br />
&#36;Ver = "1.1"<br />
<br />
Global hDlg, hToolbar, hList, hFont As Dword<br />
Global Scroll, ShowLine, TimerInterval, ImageWidth, ImageHeight,XDelta As Long<br />
Global SBW, SBH, TBW, TBH As Long, Zoom As Single<br />
<br />
Function PBMain() As Long<br />
 &nbsp;&nbsp;Dialog New Pixels, 0, "gbScroller v" + &#36;Ver,,,100,100, %WS_OverlappedWindow To hDlg<br />
 &nbsp;&nbsp;Dialog Set Icon hDlg, "logo"<br />
 &nbsp;&nbsp;Font New "Tahoma",10, 0 To hFont<br />
 &nbsp;&nbsp;CreateImageList<br />
 &nbsp;&nbsp;CreateToolbar<br />
 &nbsp;&nbsp;CreateStatusBar<br />
 &nbsp;&nbsp;Dialog Show Modal hDlg, Call DlgMain<br />
End Function<br />
<br />
CallBack Function DlgMain()<br />
 &nbsp;&nbsp;Local iReturn As Long<br />
 &nbsp;&nbsp;Select Case As Long Cb.Msg<br />
      Case %WM_InitDialog<br />
       &nbsp;&nbsp;BuildAcceleratorTable<br />
       &nbsp;&nbsp;settings_ini "get"<br />
       &nbsp;&nbsp;GetImageSize<br />
       &nbsp;&nbsp;CreateGraphic<br />
       &nbsp;&nbsp;Statusbar Set Text hDlg, %IDC_StatusBar, 1, 0, "gbScroller v" + &#36;Ver + "     &nbsp;&nbsp;Zoom = " + Str&#36;(Zoom) + "     &nbsp;&nbsp;Timer Interval = " + Str&#36;(TimerInterval)<br />
<br />
      Case %WM_Size : ResizeDialog<br />
<br />
      Case %WM_Timer : ScrollImage : SetStatusBar<br />
<br />
      Case %WM_Help<br />
<br />
      Case %WM_Command<br />
       &nbsp;&nbsp;Select Case Cb.Ctl<br />
            Case %IdCancel        : sBeep : Dialog End hDlg<br />
            Case %IDT_Exit        : sBeep : Dialog End hDlg<br />
            Case %IDT_Scroll      : sBeep : ManageScroll : CreateToolbar<br />
            Case %IDT_StopScroll  : sBeep : ManageScroll : CreateToolbar<br />
            Case %IDT_Faster      : sBeep : ChangeSpeed(-1) : CreateToolbar<br />
            Case %IDT_Slower      : sBeep : ChangeSpeed(+1) : CreateToolbar<br />
            Case %IDT_Plus        : sBeep : Magnify(+1)<br />
            Case %IDT_Minus     &nbsp;&nbsp;: sBeep : Magnify(-1)<br />
            Case %IDT_ResetZoom &nbsp;&nbsp;: sBeep : Magnify(0)<br />
            Case %IDT_ResetScroll : sBeep : ResetScroll<br />
            Case %IDT_ShowLine    : sBeep : ShowLine Xor=1 : DrawLines(IIf(ShowLine,%Red,-2)) : Graphic ReDraw<br />
            Case %IDT_View        : sBeep : iReturn = ShellExecute(hDlg, "Open", "merge.bmp", &#36;Nul, &#36;Nul, %SW_ShowNormal)<br />
<br />
       &nbsp;&nbsp;End Select<br />
<br />
      Case %WM_Destroy<br />
       &nbsp;&nbsp;settings_ini "save"<br />
 &nbsp;&nbsp;End Select<br />
End Function<br />
<br />
Sub CreateStatusBar<br />
 &nbsp;&nbsp;Control Add Statusbar, hDlg, %IDC_StatusBar, "Welcome to gbScroller v" + &#36;Ver, 0,0,0,0, %CCS_Bottom Or %SBars_SizeGrip<br />
 &nbsp;&nbsp;Control Get Size hDlg, %IDC_StatusBar To SBW, SBH<br />
 &nbsp;&nbsp;Control Set Font hDlg, %IDC_StatusBar, hFont<br />
End Sub<br />
<br />
Sub CreateGraphic<br />
 &nbsp;&nbsp;Local w,h As Long<br />
 &nbsp;&nbsp;Dialog Get Client hDlg To w,h<br />
 &nbsp;&nbsp;Control Add Graphic, hDlg, %IDC_Graphic, "", 10, TBH, w-20,h, %WS_Border<br />
 &nbsp;&nbsp;Graphic Attach hDlg, %IDC_Graphic, ReDraw<br />
 &nbsp;&nbsp;Graphic Set Virtual ImageWidth, ImageHeight<br />
 &nbsp;&nbsp;Graphic Color %Black, %White<br />
 &nbsp;&nbsp;Graphic Clear<br />
End Sub<br />
<br />
Sub CreateImageList<br />
 &nbsp;&nbsp;ImageList New Icon 48,48,32,20 To hList<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zexit" &nbsp;&nbsp;'1<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zleft" &nbsp;&nbsp;'2<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zright"  '3<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zstop"  '4<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zplus"  '5<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zminus"  '6<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zreset"  '7<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zview"  '8<br />
End Sub<br />
<br />
Sub CreateToolbar<br />
 &nbsp;&nbsp;Control Kill hDlg, %IDC_Toolbar<br />
 &nbsp;&nbsp;Control Add Toolbar, hDlg, %IDC_Toolbar, "", 0,0,0,0 , %TbStyle_Flat Or %CCS_NoDivider<br />
 &nbsp;&nbsp;Control Handle hDlg, %IDC_Toolbar To hToolbar<br />
 &nbsp;&nbsp;Toolbar Set ImageList hDlg, %IDC_Toolbar, hList, 0<br />
<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 1, %IDT_Exit, %TbStyle_Button, " Exit "<br />
<br />
 &nbsp;&nbsp;Toolbar Add Separator hDlg, %IDC_Toolbar, 50<br />
<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 5, %IDT_Plus, %TbStyle_Button, " ZoomIn "<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 6, %IDT_Minus, %TbStyle_Button, " ZoomOut "<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 7, %IDT_ResetZoom, %TbStyle_Button, " Reset "<br />
<br />
 &nbsp;&nbsp;Toolbar Add Separator hDlg, %IDC_Toolbar, 50<br />
<br />
 &nbsp;&nbsp;If Scroll = 0 Then Toolbar Add Button hDlg, %IDC_Toolbar, 8, %IDT_Scroll, %TbStyle_Button, " Scroll "<br />
 &nbsp;&nbsp;If Scroll = 1 Then Toolbar Add Button hDlg, %IDC_Toolbar, 4, %IDT_StopScroll, %TbStyle_Button, " Stop "<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 2, %IDT_Slower, %TbStyle_Button, " Slower "<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 3, %IDT_Faster, %TbStyle_Button, " Faster "<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 7, %IDT_ResetScroll, %TbStyle_Button, " Reset "<br />
<br />
 &nbsp;&nbsp;Control Set Font hDlg, %IDC_StatusBar, hToolbar<br />
 &nbsp;&nbsp;Control Get Size hDlg, %IDC_Toolbar To TBW, TBH<br />
<br />
End Sub<br />
<br />
Sub Settings_INI(Task&#36;)<br />
 &nbsp;&nbsp;Local x,y,w,h, tempz, INIFileName As WStringZ * %Max_Path, WinPla As WindowPlacement<br />
<br />
 &nbsp;&nbsp;'set ini filename<br />
 &nbsp;&nbsp;INIFileName = Exe.Path&#36; + Exe.Name&#36; + ".ini"    'get INI file name<br />
<br />
 &nbsp;&nbsp;Select Case Task&#36;<br />
      Case "get"<br />
       &nbsp;&nbsp;'get dialog width/height from INI file and use to set Dialog size<br />
       &nbsp;&nbsp;GetPrivateProfileString "All", "Width", "1200", w, %Max_Path, INIFileName<br />
       &nbsp;&nbsp;GetPrivateProfileString "All", "Height", "300", h, %Max_Path, INIFileName<br />
       &nbsp;&nbsp;Dialog Set Size hDlg,Val(w), Val(h) &nbsp;&nbsp;'width/height<br />
<br />
       &nbsp;&nbsp;'get dialog top/left from INI file and use to set Dialog location<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "Left", "0", x, %Max_Path, INIFileName<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "Top", "0",  y,  %Max_Path, INIFileName<br />
       &nbsp;&nbsp;If IsFile(INIFileName) Then Dialog Set Loc hDlg, Val(x), Val(y) &nbsp;&nbsp;'left/top but only once INIFileName exists<br />
<br />
       &nbsp;&nbsp;'get value for string variables<br />
'       &nbsp;&nbsp;GetPrivateProfileString "All", "FontName", "Arial Black", FontName, %Max_Path, INIFileName<br />
<br />
       &nbsp;&nbsp;'get value for numeric variables<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "ImageWidth", "",        tempz, %Max_Path, INIFileName &nbsp;&nbsp;: ImageWidth = Val(tempz)<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "ImageHeight", "",     &nbsp;&nbsp;tempz, %Max_Path, INIFileName &nbsp;&nbsp;: ImageHeight = Val(tempz)<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "TimerIntereval", "50", tempz, %Max_Path, INIFileName &nbsp;&nbsp;: TimerInterval = Val(tempz)<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "Zoom", "1",           &nbsp;&nbsp;tempz, %Max_Path, INIFileName &nbsp;&nbsp;: Zoom = Val(tempz)<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "XDelta", "2",         &nbsp;&nbsp;tempz, %Max_Path, INIFileName &nbsp;&nbsp;: XDelta = Val(tempz)<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "ShowLine", "1",       &nbsp;&nbsp;tempz, %Max_Path, INIFileName &nbsp;&nbsp;: ShowLine = Val(tempz)<br />
<br />
      Case "save"<br />
       &nbsp;&nbsp;If IsFile(INIFileName) Then Kill INIFileName    'clear the INI file Name to remove residual entries before saving<br />
       &nbsp;&nbsp;WinPla.Length = SizeOf(WinPla)<br />
       &nbsp;&nbsp;GetWindowPlacement hDlg, WinPla<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "Left", &nbsp;&nbsp;Str&#36;(WinPla.rcNormalPosition.nLeft), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "Top",    Str&#36;(WinPla.rcNormalPosition.nTop), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "Width",  Str&#36;(WinPla.rcNormalPosition.nRight - WinPla.rcNormalPosition.nLeft), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "Height", Str&#36;(WinPla.rcNormalPosition.nBottom - WinPla.rcNormalPosition.nTop), INIFileName<br />
<br />
       &nbsp;&nbsp;'save string variables<br />
'       &nbsp;&nbsp;WritePrivateProfileString "All", "FontName", &nbsp;&nbsp;FontName, INIFileName<br />
<br />
       &nbsp;&nbsp;'save numeric variables<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "ImageHeight",    Str&#36;(ImageHeight), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "ImageWidth",   &nbsp;&nbsp;Str&#36;(ImageWidth), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "TimerInterval",  Str&#36;(TimerInterval), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "Zoom",         &nbsp;&nbsp;Str&#36;(Zoom), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "XDelta",       &nbsp;&nbsp;Str&#36;(XDelta), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "ShowLine",     &nbsp;&nbsp;Str&#36;(ShowLine), INIFileName<br />
 &nbsp;&nbsp;End Select<br />
End Sub<br />
<br />
Sub sBeep : WinBeep(275,150) : End Sub<br />
<br />
Sub BuildAcceleratorTable<br />
 &nbsp;&nbsp;Local ac() As ACCELAPI, hAccelerator As Dword, c As Long  ' for keyboard accelator table values<br />
 &nbsp;&nbsp;Dim ac(2)<br />
 &nbsp;&nbsp;ac(c).fvirt = %FVIRTKEY : ac(c).key &nbsp;&nbsp;= %VK_V  : ac(c).cmd = %IDT_View        : Incr c<br />
 &nbsp;&nbsp;ac(c).fvirt = %FVIRTKEY : ac(c).key &nbsp;&nbsp;= %VK_L  : ac(c).cmd = %IDT_ShowLine    : Incr c<br />
 &nbsp;&nbsp;ac(c).fvirt = %FVIRTKEY : ac(c).key &nbsp;&nbsp;= %VK_S  : ac(c).cmd = %IDT_Scroll      : Incr c<br />
 &nbsp;&nbsp;Accel Attach hDlg, AC() To hAccelerator<br />
End Sub<br />
<br />
Sub ResizeDialog<br />
 &nbsp;&nbsp;Local w,h, x1,y1 As Long<br />
 &nbsp;&nbsp;Dialog Get Client hDlg To w,h<br />
 &nbsp;&nbsp;Control Set Size hDlg, %IDC_Graphic, w-20, h-TBH-SBH-10<br />
 &nbsp;&nbsp;Control Get Client hDlg, %IDC_Graphic To w,h<br />
<br />
 &nbsp;&nbsp;x1 = 0<br />
 &nbsp;&nbsp;y1 = (h-Zoom*ImageHeight)/2<br />
<br />
 &nbsp;&nbsp;Graphic Color %Black, %White<br />
 &nbsp;&nbsp;Graphic Width 2<br />
 &nbsp;&nbsp;Graphic Clear<br />
 &nbsp;&nbsp;Graphic Set StretchMode %HalfTone<br />
 &nbsp;&nbsp;Graphic Render Bitmap "merge.bmp", (x1,y1)-(x1+Zoom*ImageWidth,y1+Zoom*ImageHeight)<br />
 &nbsp;&nbsp;If ShowLine Then DrawLines (%Red)<br />
 &nbsp;&nbsp;Graphic ReDraw<br />
 &nbsp;&nbsp;SetStatusBar<br />
End Sub<br />
<br />
Sub DrawLines (LineColor As Long)<br />
 &nbsp;&nbsp;Local w,h,i, SingleImageX As Long<br />
 &nbsp;&nbsp;Graphic Get Canvas To w,h<br />
 &nbsp;&nbsp;SingleImageX = w / ImageCount<br />
 &nbsp;&nbsp;Graphic Color LineColor, %White<br />
 &nbsp;&nbsp;For i = 1 To ImageCount-1<br />
      Graphic Line (i*SingleImageX-5,0)-(i*SingleImageX-5,h)<br />
 &nbsp;&nbsp;Next i<br />
End Sub<br />
<br />
Sub SetStatusBar<br />
 &nbsp;&nbsp;Statusbar Set Text hDlg, %IDC_StatusBar, 1, 0, "gbScroller v" + &#36;Ver + "     &nbsp;&nbsp;Zoom = " + Str&#36;(Zoom) + "     &nbsp;&nbsp;Timer Interval = " + Str&#36;(TimerInterval) + "      " + Time&#36;<br />
End Sub<br />
<br />
Function ImageCount As Long<br />
 &nbsp;&nbsp;Local iCount As Long, temp&#36;<br />
 &nbsp;&nbsp;temp&#36; = Dir&#36;("images&#92;*.*")<br />
 &nbsp;&nbsp;While Len(temp&#36;)<br />
      Incr iCount<br />
      temp&#36; = Dir&#36;(Next)<br />
 &nbsp;&nbsp;Wend<br />
 &nbsp;&nbsp;Function = iCount<br />
End Function<br />
<br />
Sub GetImageSize 'get image size for bmp&#36;<br />
 &nbsp;&nbsp;Local hBMP As Dword<br />
 &nbsp;&nbsp;Graphic Bitmap Load "merge.bmp", 0, 0 To hBMP<br />
 &nbsp;&nbsp;Graphic Attach hBMP, 0<br />
 &nbsp;&nbsp;Graphic Get Canvas To ImageWidth, ImageHeight<br />
 &nbsp;&nbsp;Graphic Bitmap End<br />
End Sub<br />
<br />
Sub ChangeSpeed(iDelta As Long)<br />
 &nbsp;&nbsp;Select Case iDelta<br />
      Case 0 &nbsp;&nbsp;: TimerInterval  = 0  : Scroll = 0 'stopped<br />
      Case 1 &nbsp;&nbsp;: TimerInterval += 10 : Scroll = 1 'slower<br />
      Case -1  : TimerInterval -= 10 : Scroll = 1 'faster<br />
 &nbsp;&nbsp;End Select<br />
<br />
 &nbsp;&nbsp;If TimerInterval &lt; 10 Then TimerInterval = 10 : sBeep<br />
<br />
 &nbsp;&nbsp;KillTimer hDlg, %ID_Timer<br />
 &nbsp;&nbsp;SetTimer hDlg, %ID_Timer, TimerInterval, %Null<br />
 &nbsp;&nbsp;SetStatusBar<br />
End Sub<br />
<br />
Sub Magnify(iDelta As Long)<br />
 &nbsp;&nbsp;Select Case iDelta<br />
      Case 0  : Zoom = 1<br />
      Case 1  : Zoom += 0.25<br />
      Case -1 : Zoom -= 0.25<br />
 &nbsp;&nbsp;End Select<br />
 &nbsp;&nbsp;If Zoom &lt; 1 Then Zoom = 1<br />
 &nbsp;&nbsp;ResizeDialog<br />
End Sub<br />
<br />
Sub ManageScroll<br />
 &nbsp;&nbsp;Scroll Xor=1<br />
 &nbsp;&nbsp;If Scroll Then<br />
      SetTimer hDlg, %ID_Timer, TimerInterval, %Null<br />
 &nbsp;&nbsp;Else<br />
      KillTimer hDlg, %ID_Timer<br />
 &nbsp;&nbsp;End If<br />
End Sub<br />
<br />
Sub ResetScroll<br />
 &nbsp;&nbsp;Local w,h As Long<br />
 &nbsp;&nbsp;Graphic Get View To w,h<br />
 &nbsp;&nbsp;Graphic Set View 0, h<br />
End Sub<br />
<br />
Sub ScrollImage<br />
 &nbsp;&nbsp;Local w,h,ww,hh,www,hhh As Long<br />
 &nbsp;&nbsp;Graphic Get View To w,h<br />
 &nbsp;&nbsp;Graphic Set View w+XDelta, h<br />
<br />
 &nbsp;&nbsp;Control Get Client hDlg, %IDC_Graphic To www,hhh<br />
 &nbsp;&nbsp;Graphic Get Canvas To ww,hh<br />
 &nbsp;&nbsp;'Dialog Set Text hDlg, Str&#36;(w+XDelta) + Str&#36;(ImageWidth)<br />
<br />
 &nbsp;&nbsp;If w+XDelta &gt;= (ImageWidth-www) Then<br />
      sBeep : sBeep<br />
      ManageScroll : CreateToolbar<br />
 &nbsp;&nbsp;End If<br />
End Sub</code></div></div>]]></description>
			<content:encoded><![CDATA[gbScroller is the companion app to gbClientCapture.  Both are intended to support viewing of sheet music in a horizontal format.<br />
<br />
gbClientCapture captures multiple images from a music sheet and merges them into a single horizontal image.<br />
<br />
gbScroller displays the merged image, scrolling it to the left.<br />
<br />
Both are discussed in the same thread. <a href="http://pump.richheimer.de/showthread.php?tid=81&amp;pid=562#pid562" target="_blank" rel="noopener" class="mycode_url">http://pump.richheimer.de/showthread.php...562#pid562</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Features:</span><br />
1. Zoom the merged image<br />
2. Scroll the merged image, with speed control <br />
3. Display a vertical line between individual images<br />
<br />
<img src="https://garybeene.com/files/gbscroller.jpg" loading="lazy"  alt="[Image: gbscroller.jpg]" class="mycode_img" /><br />
<br />
Files for both gbClientCapture and gbScroller are in the zip <a href="https://garybeene.com/files/gbclientcapture.zip" target="_blank" rel="noopener" class="mycode_url">https://garybeene.com/files/gbclientcapture.zip</a><br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'Compilable Example: Jose Includes<br />
<br />
#Compile Exe  "gbscroller.exe"<br />
#Dim All<br />
<br />
#Debug Error On<br />
#Debug Display On<br />
<br />
%Unicode = 1<br />
<br />
#Include "WIN32API.INC"<br />
#Include "cgdiplus.inc" '&lt;-replace with Jose or you own GDI+ include file.<br />
<br />
Enum Equates Singular<br />
 &nbsp;&nbsp;IDC_Toolbar &nbsp;&nbsp;= 500<br />
 &nbsp;&nbsp;IDC_Graphic<br />
 &nbsp;&nbsp;IDT_Exit<br />
 &nbsp;&nbsp;IDT_Faster<br />
 &nbsp;&nbsp;IDT_Slower<br />
 &nbsp;&nbsp;IDT_View<br />
 &nbsp;&nbsp;IDT_ShowLine<br />
 &nbsp;&nbsp;IDT_Scroll<br />
 &nbsp;&nbsp;IDC_StatusBar<br />
 &nbsp;&nbsp;IDT_ResetZoom<br />
 &nbsp;&nbsp;IDT_ResetScroll<br />
 &nbsp;&nbsp;IDC_Up<br />
 &nbsp;&nbsp;IDC_Down<br />
 &nbsp;&nbsp;IDT_StopScroll<br />
 &nbsp;&nbsp;IDT_Plus<br />
 &nbsp;&nbsp;IDT_Minus<br />
 &nbsp;&nbsp;ID_Timer<br />
End Enum<br />
<br />
#Resource Manifest, 1,      "icons&#92;xptheme_dpiaware.xml"<br />
<br />
#Resource Icon logo, "icons&#92;scroll2.ico"<br />
#Resource Icon zexit, "icons&#92;power.ico"<br />
#Resource Icon zview, "icons&#92;view.ico"<br />
#Resource Icon zleft, "icons&#92;scrollleft.ico"<br />
#Resource Icon zright, "icons&#92;scrollright.ico"<br />
#Resource Icon zstop, "icons&#92;stop.ico"<br />
#Resource Icon zplus, "icons&#92;plus.ico"<br />
#Resource Icon zminus, "icons&#92;minus.ico"<br />
#Resource Icon zreset, "icons&#92;reset.ico"<br />
<br />
&#36;Ver = "1.1"<br />
<br />
Global hDlg, hToolbar, hList, hFont As Dword<br />
Global Scroll, ShowLine, TimerInterval, ImageWidth, ImageHeight,XDelta As Long<br />
Global SBW, SBH, TBW, TBH As Long, Zoom As Single<br />
<br />
Function PBMain() As Long<br />
 &nbsp;&nbsp;Dialog New Pixels, 0, "gbScroller v" + &#36;Ver,,,100,100, %WS_OverlappedWindow To hDlg<br />
 &nbsp;&nbsp;Dialog Set Icon hDlg, "logo"<br />
 &nbsp;&nbsp;Font New "Tahoma",10, 0 To hFont<br />
 &nbsp;&nbsp;CreateImageList<br />
 &nbsp;&nbsp;CreateToolbar<br />
 &nbsp;&nbsp;CreateStatusBar<br />
 &nbsp;&nbsp;Dialog Show Modal hDlg, Call DlgMain<br />
End Function<br />
<br />
CallBack Function DlgMain()<br />
 &nbsp;&nbsp;Local iReturn As Long<br />
 &nbsp;&nbsp;Select Case As Long Cb.Msg<br />
      Case %WM_InitDialog<br />
       &nbsp;&nbsp;BuildAcceleratorTable<br />
       &nbsp;&nbsp;settings_ini "get"<br />
       &nbsp;&nbsp;GetImageSize<br />
       &nbsp;&nbsp;CreateGraphic<br />
       &nbsp;&nbsp;Statusbar Set Text hDlg, %IDC_StatusBar, 1, 0, "gbScroller v" + &#36;Ver + "     &nbsp;&nbsp;Zoom = " + Str&#36;(Zoom) + "     &nbsp;&nbsp;Timer Interval = " + Str&#36;(TimerInterval)<br />
<br />
      Case %WM_Size : ResizeDialog<br />
<br />
      Case %WM_Timer : ScrollImage : SetStatusBar<br />
<br />
      Case %WM_Help<br />
<br />
      Case %WM_Command<br />
       &nbsp;&nbsp;Select Case Cb.Ctl<br />
            Case %IdCancel        : sBeep : Dialog End hDlg<br />
            Case %IDT_Exit        : sBeep : Dialog End hDlg<br />
            Case %IDT_Scroll      : sBeep : ManageScroll : CreateToolbar<br />
            Case %IDT_StopScroll  : sBeep : ManageScroll : CreateToolbar<br />
            Case %IDT_Faster      : sBeep : ChangeSpeed(-1) : CreateToolbar<br />
            Case %IDT_Slower      : sBeep : ChangeSpeed(+1) : CreateToolbar<br />
            Case %IDT_Plus        : sBeep : Magnify(+1)<br />
            Case %IDT_Minus     &nbsp;&nbsp;: sBeep : Magnify(-1)<br />
            Case %IDT_ResetZoom &nbsp;&nbsp;: sBeep : Magnify(0)<br />
            Case %IDT_ResetScroll : sBeep : ResetScroll<br />
            Case %IDT_ShowLine    : sBeep : ShowLine Xor=1 : DrawLines(IIf(ShowLine,%Red,-2)) : Graphic ReDraw<br />
            Case %IDT_View        : sBeep : iReturn = ShellExecute(hDlg, "Open", "merge.bmp", &#36;Nul, &#36;Nul, %SW_ShowNormal)<br />
<br />
       &nbsp;&nbsp;End Select<br />
<br />
      Case %WM_Destroy<br />
       &nbsp;&nbsp;settings_ini "save"<br />
 &nbsp;&nbsp;End Select<br />
End Function<br />
<br />
Sub CreateStatusBar<br />
 &nbsp;&nbsp;Control Add Statusbar, hDlg, %IDC_StatusBar, "Welcome to gbScroller v" + &#36;Ver, 0,0,0,0, %CCS_Bottom Or %SBars_SizeGrip<br />
 &nbsp;&nbsp;Control Get Size hDlg, %IDC_StatusBar To SBW, SBH<br />
 &nbsp;&nbsp;Control Set Font hDlg, %IDC_StatusBar, hFont<br />
End Sub<br />
<br />
Sub CreateGraphic<br />
 &nbsp;&nbsp;Local w,h As Long<br />
 &nbsp;&nbsp;Dialog Get Client hDlg To w,h<br />
 &nbsp;&nbsp;Control Add Graphic, hDlg, %IDC_Graphic, "", 10, TBH, w-20,h, %WS_Border<br />
 &nbsp;&nbsp;Graphic Attach hDlg, %IDC_Graphic, ReDraw<br />
 &nbsp;&nbsp;Graphic Set Virtual ImageWidth, ImageHeight<br />
 &nbsp;&nbsp;Graphic Color %Black, %White<br />
 &nbsp;&nbsp;Graphic Clear<br />
End Sub<br />
<br />
Sub CreateImageList<br />
 &nbsp;&nbsp;ImageList New Icon 48,48,32,20 To hList<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zexit" &nbsp;&nbsp;'1<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zleft" &nbsp;&nbsp;'2<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zright"  '3<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zstop"  '4<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zplus"  '5<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zminus"  '6<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zreset"  '7<br />
 &nbsp;&nbsp;ImageList Add Icon hList, "zview"  '8<br />
End Sub<br />
<br />
Sub CreateToolbar<br />
 &nbsp;&nbsp;Control Kill hDlg, %IDC_Toolbar<br />
 &nbsp;&nbsp;Control Add Toolbar, hDlg, %IDC_Toolbar, "", 0,0,0,0 , %TbStyle_Flat Or %CCS_NoDivider<br />
 &nbsp;&nbsp;Control Handle hDlg, %IDC_Toolbar To hToolbar<br />
 &nbsp;&nbsp;Toolbar Set ImageList hDlg, %IDC_Toolbar, hList, 0<br />
<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 1, %IDT_Exit, %TbStyle_Button, " Exit "<br />
<br />
 &nbsp;&nbsp;Toolbar Add Separator hDlg, %IDC_Toolbar, 50<br />
<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 5, %IDT_Plus, %TbStyle_Button, " ZoomIn "<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 6, %IDT_Minus, %TbStyle_Button, " ZoomOut "<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 7, %IDT_ResetZoom, %TbStyle_Button, " Reset "<br />
<br />
 &nbsp;&nbsp;Toolbar Add Separator hDlg, %IDC_Toolbar, 50<br />
<br />
 &nbsp;&nbsp;If Scroll = 0 Then Toolbar Add Button hDlg, %IDC_Toolbar, 8, %IDT_Scroll, %TbStyle_Button, " Scroll "<br />
 &nbsp;&nbsp;If Scroll = 1 Then Toolbar Add Button hDlg, %IDC_Toolbar, 4, %IDT_StopScroll, %TbStyle_Button, " Stop "<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 2, %IDT_Slower, %TbStyle_Button, " Slower "<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 3, %IDT_Faster, %TbStyle_Button, " Faster "<br />
 &nbsp;&nbsp;Toolbar Add Button hDlg, %IDC_Toolbar, 7, %IDT_ResetScroll, %TbStyle_Button, " Reset "<br />
<br />
 &nbsp;&nbsp;Control Set Font hDlg, %IDC_StatusBar, hToolbar<br />
 &nbsp;&nbsp;Control Get Size hDlg, %IDC_Toolbar To TBW, TBH<br />
<br />
End Sub<br />
<br />
Sub Settings_INI(Task&#36;)<br />
 &nbsp;&nbsp;Local x,y,w,h, tempz, INIFileName As WStringZ * %Max_Path, WinPla As WindowPlacement<br />
<br />
 &nbsp;&nbsp;'set ini filename<br />
 &nbsp;&nbsp;INIFileName = Exe.Path&#36; + Exe.Name&#36; + ".ini"    'get INI file name<br />
<br />
 &nbsp;&nbsp;Select Case Task&#36;<br />
      Case "get"<br />
       &nbsp;&nbsp;'get dialog width/height from INI file and use to set Dialog size<br />
       &nbsp;&nbsp;GetPrivateProfileString "All", "Width", "1200", w, %Max_Path, INIFileName<br />
       &nbsp;&nbsp;GetPrivateProfileString "All", "Height", "300", h, %Max_Path, INIFileName<br />
       &nbsp;&nbsp;Dialog Set Size hDlg,Val(w), Val(h) &nbsp;&nbsp;'width/height<br />
<br />
       &nbsp;&nbsp;'get dialog top/left from INI file and use to set Dialog location<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "Left", "0", x, %Max_Path, INIFileName<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "Top", "0",  y,  %Max_Path, INIFileName<br />
       &nbsp;&nbsp;If IsFile(INIFileName) Then Dialog Set Loc hDlg, Val(x), Val(y) &nbsp;&nbsp;'left/top but only once INIFileName exists<br />
<br />
       &nbsp;&nbsp;'get value for string variables<br />
'       &nbsp;&nbsp;GetPrivateProfileString "All", "FontName", "Arial Black", FontName, %Max_Path, INIFileName<br />
<br />
       &nbsp;&nbsp;'get value for numeric variables<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "ImageWidth", "",        tempz, %Max_Path, INIFileName &nbsp;&nbsp;: ImageWidth = Val(tempz)<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "ImageHeight", "",     &nbsp;&nbsp;tempz, %Max_Path, INIFileName &nbsp;&nbsp;: ImageHeight = Val(tempz)<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "TimerIntereval", "50", tempz, %Max_Path, INIFileName &nbsp;&nbsp;: TimerInterval = Val(tempz)<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "Zoom", "1",           &nbsp;&nbsp;tempz, %Max_Path, INIFileName &nbsp;&nbsp;: Zoom = Val(tempz)<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "XDelta", "2",         &nbsp;&nbsp;tempz, %Max_Path, INIFileName &nbsp;&nbsp;: XDelta = Val(tempz)<br />
       &nbsp;&nbsp;Getprivateprofilestring "All", "ShowLine", "1",       &nbsp;&nbsp;tempz, %Max_Path, INIFileName &nbsp;&nbsp;: ShowLine = Val(tempz)<br />
<br />
      Case "save"<br />
       &nbsp;&nbsp;If IsFile(INIFileName) Then Kill INIFileName    'clear the INI file Name to remove residual entries before saving<br />
       &nbsp;&nbsp;WinPla.Length = SizeOf(WinPla)<br />
       &nbsp;&nbsp;GetWindowPlacement hDlg, WinPla<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "Left", &nbsp;&nbsp;Str&#36;(WinPla.rcNormalPosition.nLeft), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "Top",    Str&#36;(WinPla.rcNormalPosition.nTop), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "Width",  Str&#36;(WinPla.rcNormalPosition.nRight - WinPla.rcNormalPosition.nLeft), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "Height", Str&#36;(WinPla.rcNormalPosition.nBottom - WinPla.rcNormalPosition.nTop), INIFileName<br />
<br />
       &nbsp;&nbsp;'save string variables<br />
'       &nbsp;&nbsp;WritePrivateProfileString "All", "FontName", &nbsp;&nbsp;FontName, INIFileName<br />
<br />
       &nbsp;&nbsp;'save numeric variables<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "ImageHeight",    Str&#36;(ImageHeight), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "ImageWidth",   &nbsp;&nbsp;Str&#36;(ImageWidth), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "TimerInterval",  Str&#36;(TimerInterval), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "Zoom",         &nbsp;&nbsp;Str&#36;(Zoom), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "XDelta",       &nbsp;&nbsp;Str&#36;(XDelta), INIFileName<br />
       &nbsp;&nbsp;WritePrivateProfileString "All", "ShowLine",     &nbsp;&nbsp;Str&#36;(ShowLine), INIFileName<br />
 &nbsp;&nbsp;End Select<br />
End Sub<br />
<br />
Sub sBeep : WinBeep(275,150) : End Sub<br />
<br />
Sub BuildAcceleratorTable<br />
 &nbsp;&nbsp;Local ac() As ACCELAPI, hAccelerator As Dword, c As Long  ' for keyboard accelator table values<br />
 &nbsp;&nbsp;Dim ac(2)<br />
 &nbsp;&nbsp;ac(c).fvirt = %FVIRTKEY : ac(c).key &nbsp;&nbsp;= %VK_V  : ac(c).cmd = %IDT_View        : Incr c<br />
 &nbsp;&nbsp;ac(c).fvirt = %FVIRTKEY : ac(c).key &nbsp;&nbsp;= %VK_L  : ac(c).cmd = %IDT_ShowLine    : Incr c<br />
 &nbsp;&nbsp;ac(c).fvirt = %FVIRTKEY : ac(c).key &nbsp;&nbsp;= %VK_S  : ac(c).cmd = %IDT_Scroll      : Incr c<br />
 &nbsp;&nbsp;Accel Attach hDlg, AC() To hAccelerator<br />
End Sub<br />
<br />
Sub ResizeDialog<br />
 &nbsp;&nbsp;Local w,h, x1,y1 As Long<br />
 &nbsp;&nbsp;Dialog Get Client hDlg To w,h<br />
 &nbsp;&nbsp;Control Set Size hDlg, %IDC_Graphic, w-20, h-TBH-SBH-10<br />
 &nbsp;&nbsp;Control Get Client hDlg, %IDC_Graphic To w,h<br />
<br />
 &nbsp;&nbsp;x1 = 0<br />
 &nbsp;&nbsp;y1 = (h-Zoom*ImageHeight)/2<br />
<br />
 &nbsp;&nbsp;Graphic Color %Black, %White<br />
 &nbsp;&nbsp;Graphic Width 2<br />
 &nbsp;&nbsp;Graphic Clear<br />
 &nbsp;&nbsp;Graphic Set StretchMode %HalfTone<br />
 &nbsp;&nbsp;Graphic Render Bitmap "merge.bmp", (x1,y1)-(x1+Zoom*ImageWidth,y1+Zoom*ImageHeight)<br />
 &nbsp;&nbsp;If ShowLine Then DrawLines (%Red)<br />
 &nbsp;&nbsp;Graphic ReDraw<br />
 &nbsp;&nbsp;SetStatusBar<br />
End Sub<br />
<br />
Sub DrawLines (LineColor As Long)<br />
 &nbsp;&nbsp;Local w,h,i, SingleImageX As Long<br />
 &nbsp;&nbsp;Graphic Get Canvas To w,h<br />
 &nbsp;&nbsp;SingleImageX = w / ImageCount<br />
 &nbsp;&nbsp;Graphic Color LineColor, %White<br />
 &nbsp;&nbsp;For i = 1 To ImageCount-1<br />
      Graphic Line (i*SingleImageX-5,0)-(i*SingleImageX-5,h)<br />
 &nbsp;&nbsp;Next i<br />
End Sub<br />
<br />
Sub SetStatusBar<br />
 &nbsp;&nbsp;Statusbar Set Text hDlg, %IDC_StatusBar, 1, 0, "gbScroller v" + &#36;Ver + "     &nbsp;&nbsp;Zoom = " + Str&#36;(Zoom) + "     &nbsp;&nbsp;Timer Interval = " + Str&#36;(TimerInterval) + "      " + Time&#36;<br />
End Sub<br />
<br />
Function ImageCount As Long<br />
 &nbsp;&nbsp;Local iCount As Long, temp&#36;<br />
 &nbsp;&nbsp;temp&#36; = Dir&#36;("images&#92;*.*")<br />
 &nbsp;&nbsp;While Len(temp&#36;)<br />
      Incr iCount<br />
      temp&#36; = Dir&#36;(Next)<br />
 &nbsp;&nbsp;Wend<br />
 &nbsp;&nbsp;Function = iCount<br />
End Function<br />
<br />
Sub GetImageSize 'get image size for bmp&#36;<br />
 &nbsp;&nbsp;Local hBMP As Dword<br />
 &nbsp;&nbsp;Graphic Bitmap Load "merge.bmp", 0, 0 To hBMP<br />
 &nbsp;&nbsp;Graphic Attach hBMP, 0<br />
 &nbsp;&nbsp;Graphic Get Canvas To ImageWidth, ImageHeight<br />
 &nbsp;&nbsp;Graphic Bitmap End<br />
End Sub<br />
<br />
Sub ChangeSpeed(iDelta As Long)<br />
 &nbsp;&nbsp;Select Case iDelta<br />
      Case 0 &nbsp;&nbsp;: TimerInterval  = 0  : Scroll = 0 'stopped<br />
      Case 1 &nbsp;&nbsp;: TimerInterval += 10 : Scroll = 1 'slower<br />
      Case -1  : TimerInterval -= 10 : Scroll = 1 'faster<br />
 &nbsp;&nbsp;End Select<br />
<br />
 &nbsp;&nbsp;If TimerInterval &lt; 10 Then TimerInterval = 10 : sBeep<br />
<br />
 &nbsp;&nbsp;KillTimer hDlg, %ID_Timer<br />
 &nbsp;&nbsp;SetTimer hDlg, %ID_Timer, TimerInterval, %Null<br />
 &nbsp;&nbsp;SetStatusBar<br />
End Sub<br />
<br />
Sub Magnify(iDelta As Long)<br />
 &nbsp;&nbsp;Select Case iDelta<br />
      Case 0  : Zoom = 1<br />
      Case 1  : Zoom += 0.25<br />
      Case -1 : Zoom -= 0.25<br />
 &nbsp;&nbsp;End Select<br />
 &nbsp;&nbsp;If Zoom &lt; 1 Then Zoom = 1<br />
 &nbsp;&nbsp;ResizeDialog<br />
End Sub<br />
<br />
Sub ManageScroll<br />
 &nbsp;&nbsp;Scroll Xor=1<br />
 &nbsp;&nbsp;If Scroll Then<br />
      SetTimer hDlg, %ID_Timer, TimerInterval, %Null<br />
 &nbsp;&nbsp;Else<br />
      KillTimer hDlg, %ID_Timer<br />
 &nbsp;&nbsp;End If<br />
End Sub<br />
<br />
Sub ResetScroll<br />
 &nbsp;&nbsp;Local w,h As Long<br />
 &nbsp;&nbsp;Graphic Get View To w,h<br />
 &nbsp;&nbsp;Graphic Set View 0, h<br />
End Sub<br />
<br />
Sub ScrollImage<br />
 &nbsp;&nbsp;Local w,h,ww,hh,www,hhh As Long<br />
 &nbsp;&nbsp;Graphic Get View To w,h<br />
 &nbsp;&nbsp;Graphic Set View w+XDelta, h<br />
<br />
 &nbsp;&nbsp;Control Get Client hDlg, %IDC_Graphic To www,hhh<br />
 &nbsp;&nbsp;Graphic Get Canvas To ww,hh<br />
 &nbsp;&nbsp;'Dialog Set Text hDlg, Str&#36;(w+XDelta) + Str&#36;(ImageWidth)<br />
<br />
 &nbsp;&nbsp;If w+XDelta &gt;= (ImageWidth-www) Then<br />
      sBeep : sBeep<br />
      ManageScroll : CreateToolbar<br />
 &nbsp;&nbsp;End If<br />
End Sub</code></div></div>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[gbClientCapture]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=80</link>
			<pubDate>Sat, 20 Sep 2025 17:38:06 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=94">Gary Beene</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=80</guid>
			<description><![CDATA[This app displays a transparent dialog, with a Toolbar and a Statusbar. Images under the open dialog client area are captured and merged into a horizontal image.  This will be used by my next app to scroll the horizontal image - corresponding to my post about breaking vertical sheet music into scrollable horizontal music.<br />
<br />
Discussion is <a href="http://pump.richheimer.de/showthread.php?tid=81&amp;pid=562#pid562" target="_blank" rel="noopener" class="mycode_url">here.</a><br />
<br />
Toolbar buttons:<br />
<br />
1. Exit - close gbClientCapture<br />
<br />
2. Copy - captures the desktop below the transparent dialog client and saves it as a BMP. each capture is merged with previous captures to form a horizontal image (first captured image on the left, last captured image on the right)<br />
<br />
3. New - deletes all images, including the merged image<br />
<br />
4. View - opens the current merged image in the default image viewer<br />
<br />
The Statusbar keeps track of how many images there are in the image folder.<br />
<br />
Here's a picture of the app, with some desktop showing through the client area.<br />
<br />
<img src="https://www.garybeene.com/files/capture.jpg" loading="lazy"  alt="[Image: capture.jpg]" class="mycode_img" /><br />
<br />
Code and images are here:  <a href="https://www.garybeene.com/files/gbclientcapture.zip" target="_blank" rel="noopener" class="mycode_url">https://www.garybeene.com/files/gbclientcapture.zip</a><br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'Compilable Example:  (Jose Includes)<br />
#Compile Exe  "gbclientcapture.exe"<br />
#Dim All<br />
<br />
#Debug Error On<br />
#Debug Display On<br />
<br />
%Unicode=1<br />
#Include "Win32API.inc"<br />
<br />
%IDC_Toolbar   = 500<br />
%IDT_Exit      = 501<br />
%IDT_Copy      = 502<br />
%IDT_New       = 503<br />
%IDT_View      = 504<br />
%IDC_StatusBar = 505<br />
<br />
#Resource Manifest, 1,      "icons&#92;xptheme_dpiaware.xml"<br />
#Resource VersionInfo<br />
#Resource StringInfo "0409", "04B0"<br />
#Resource Version&#36; "CompanyName", "New Vision Concepts"<br />
#Resource Version&#36; "ProductName", "gbClientCapture"<br />
#Resource Version&#36; "ProductVersion", "1.0"<br />
#Resource Version&#36; "FileDescription",  "gbClientCapture - Capture Desktop Behind Client"<br />
#Resource Version&#36; "LegalCopyright",   "Copyright 2025 New Vision Concepts"<br />
<br />
#Resource Icon logo, "icons&#92;alogo.ico"<br />
#Resource Icon zexit, "icons&#92;power.ico"<br />
#Resource Icon zcopy, "icons&#92;copy.ico"<br />
#Resource Icon znew, "icons&#92;new.ico"<br />
#Resource Icon zmerge, "icons&#92;view.ico"<br />
<br />
&#36;Ver = "1.0"<br />
<br />
Global hDlg, hToolbar, hList, hBrush, hFont As Dword<br />
Global ImageCount, ImageWidth, ImageHeight As Long<br />
Global SBW, SBH, TBW, TBH As Long<br />
<br />
Function PBMain() As Long<br />
   Dialog New Pixels, 0, "gbClientCapture  v" + &#36;Ver,300,50,200,100, %WS_OverlappedWindow, %WS_Ex_Layered To hDlg<br />
   Dialog Set Icon hDlg, "logo"<br />
   Dialog Set Color hDlg, %Black, %White<br />
   CreateToolbar<br />
<br />
   Control Add Statusbar, hDlg, %IDC_StatusBar, "", 0,0,0,0, %CCS_Bottom Or %SBars_SizeGrip<br />
   Control Get Size hDlg, %IDC_StatusBar To SBW, SBH<br />
   Control Set Color hDlg, %IDC_StatusBar, %Black, %Gray<br />
<br />
   Font New "Tahoma",10, 1 To hFont<br />
   Control Set Font hDlg, %IDC_Toolbar, hFont<br />
   Control Set Font hDlg, %IDC_StatusBar, hFont<br />
<br />
   Dialog Show Modal hDlg Call DlgProc<br />
End Function<br />
<br />
CallBack Function DlgProc() As Long<br />
   Select Case Cb.Msg<br />
      Case %WM_InitDialog<br />
         hBrush = CreateSolidBrush(RGB(243,243,243))<br />
         SetWindowPos hDlg, %HWND_TOPMOST, 0, 0, 0, 0, %SWP_NOMOVE Or %SWP_NOSIZE<br />
         If IsFalse IsFolder("images") Then MkDir "images"<br />
         Settings_INI "get"<br />
         Statusbar Set Text hDlg, %IDC_StatusBar, 1, 0, "ImageCount = " + Format&#36;(ImageCount)<br />
         SetLayeredWindowAttributes(hDlg, %White, 255, %LWA_ALPHA Or %LWA_Colorkey)<br />
      Case %WM_ContextMenu<br />
         sBeep : Dialog End hDlg<br />
      Case %WM_Command<br />
         Select Case Cb.Ctl<br />
            Case %IDT_Exit  : sBeep : Dialog End hDlg<br />
            Case %IDT_Copy  : sBeep : SaveDialogToClipboard : MergeImages<br />
            Case %IDT_New   : sBeep : NewProject<br />
            Case %IDT_View : sBeep : ViewMerge<br />
         End Select<br />
<br />
      Case %WM_Notify<br />
         Select Case Cb.NmId<br />
            Case %IDC_Toolbar<br />
               Local pTbCustDraw As NmTbCustomDraw Pointer<br />
               pTbCustDraw  = Cb.LParam<br />
               Select Case @pTbCustDraw.nmcd.dwDrawStage<br />
                  Case %CDDS_PREPAINT  ' paint entire toolbar<br />
                     FillRect(@pTbCustDraw.nmcd.hdc, @pTbCustDraw.nmcd.rc, hBrush)<br />
                     Function = %CDRF_NOTIFYITEMDRAW<br />
               End Select<br />
         End Select<br />
<br />
      Case %WM_Destroy<br />
         DeleteObject hBrush<br />
         Settings_INI "save"<br />
   End Select<br />
End Function<br />
<br />
Sub CreateToolbar<br />
   ImageList New Icon 32,32,32,20 To hList<br />
   ImageList Add Icon hList, "zexit"   '1<br />
   ImageList Add Icon hList, "zcopy"   '2<br />
   ImageList Add Icon hList, "znew"    '3<br />
   ImageList Add Icon hList, "zmerge"  '4<br />
<br />
   Control Add Toolbar, hDlg, %IDC_Toolbar, "", 0,0,0,0 ', %TbStyle_Flat<br />
   Control Handle hDlg, %IDC_Toolbar To hToolbar<br />
   Toolbar Set ImageList hDlg, %IDC_Toolbar, hList, 0<br />
<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 1, %IDT_Exit, %TbStyle_Button, " Exit "<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 2, %IDT_Copy, %TbStyle_Button, " Copy "<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 3, %IDT_New, %TbStyle_Button, " New "<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 4, %IDT_View, %TbStyle_Button, " View "<br />
<br />
   Control Get Size hDlg, %IDC_Toolbar To TBW, TBH<br />
<br />
End Sub<br />
<br />
Sub Settings_INI(Task&#36;)<br />
   Local x,y,w,h, tempz, INIFileName As WStringZ * %Max_Path, WinPla As WindowPlacement<br />
<br />
   'set ini filename<br />
   INIFileName = Exe.Path&#36; + Exe.Name&#36; + ".ini"    'get INI file name<br />
<br />
   Select Case Task&#36;<br />
      Case "get"<br />
         'get dialog width/height from INI file and use to set Dialog size<br />
         GetPrivateProfileString "All", "Width", "1200", w, %Max_Path, INIFileName<br />
         GetPrivateProfileString "All", "Height", "300", h, %Max_Path, INIFileName<br />
         Dialog Set Size hDlg,Val(w), Val(h)   'width/height<br />
<br />
         'get dialog top/left from INI file and use to set Dialog location<br />
         Getprivateprofilestring "All", "Left", "0", x, %Max_Path, INIFileName<br />
         Getprivateprofilestring "All", "Top", "0",  y,  %Max_Path, INIFileName<br />
         If IsFile(INIFileName) Then Dialog Set Loc hDlg, Val(x), Val(y)   'left/top but only once INIFileName exists<br />
<br />
         'get value for string variables<br />
'         GetPrivateProfileString "All", "FontName", "Arial Black", FontName, %Max_Path, INIFileName<br />
<br />
         'get value for numeric variables<br />
         Getprivateprofilestring "All", "ImageWidth", "",     tempz, %Max_Path, INIFileName   : ImageHeight = Val(tempz)<br />
         Getprivateprofilestring "All", "ImageHeight", "",    tempz, %Max_Path, INIFileName   : ImageWidth = Val(tempz)<br />
         Getprivateprofilestring "All", "ImageCount", "0",    tempz, %Max_Path, INIFileName   : ImageCount = Val(tempz)<br />
<br />
      Case "save"<br />
         If IsFile(INIFileName) Then Kill INIFileName    'clear the INI file Name to remove residual entries before saving<br />
         WinPla.Length = SizeOf(WinPla)<br />
         GetWindowPlacement hDlg, WinPla<br />
         WritePrivateProfileString "All", "Left",   Str&#36;(WinPla.rcNormalPosition.nLeft), INIFileName<br />
         WritePrivateProfileString "All", "Top",    Str&#36;(WinPla.rcNormalPosition.nTop), INIFileName<br />
         WritePrivateProfileString "All", "Width",  Str&#36;(WinPla.rcNormalPosition.nRight - WinPla.rcNormalPosition.nLeft), INIFileName<br />
         WritePrivateProfileString "All", "Height", Str&#36;(WinPla.rcNormalPosition.nBottom - WinPla.rcNormalPosition.nTop), INIFileName<br />
<br />
         'save string variables<br />
'         WritePrivateProfileString "All", "FontName",   FontName, INIFileName<br />
<br />
         'save numeric variables<br />
         WritePrivateProfileString "All", "ImageHeight",    Str&#36;(ImageHeight), INIFileName<br />
         WritePrivateProfileString "All", "ImageWidth",     Str&#36;(ImageWidth), INIFileName<br />
         WritePrivateProfileString "All", "ImageCount",     Str&#36;(ImageCount), INIFileName<br />
   End Select<br />
End Sub<br />
<br />
Sub sBeep : WinBeep(275,150) : End Sub<br />
<br />
Sub NewProject  'remove all &#92;images&#92;*.bmp<br />
   Local temp&#36;<br />
   Clipboard Reset<br />
   ImageCount = 0<br />
   temp&#36; = Dir&#36;("images&#92;*.bmp")<br />
   If Len(temp&#36;) Then Kill "images&#92;*.bmp"<br />
   Statusbar Set Text hDlg, %IDC_StatusBar, 1, 0, "ImageCount = " + Format&#36;(ImageCount)<br />
End Sub<br />
<br />
Sub SaveDialogToClipboard<br />
   Local x,y As Long, hBMP, hBMPDC, hDC As Dword       '         abcdegfg<br />
   'create memory bitmap the size of the dialog client (less Toolbar and less StatusBar)<br />
   Dialog Get Client hDlg To ImageWidth, ImageHeight<br />
   ImageWidth = ImageWidth - 1<br />
   ImageHeight = ImageHeight - SBH - TBH - 2<br />
<br />
   Graphic Bitmap New ImageWidth, ImageHeight To hBMP<br />
   Graphic Attach hBMP,0<br />
   Graphic Get DC To hBMPDC<br />
<br />
   'bitblt dialog rectangle from the screen to the me                            mory bitmap<br />
   Dialog Get Loc hDlg To x,y<br />
   x = x + 9<br />
   y = y + TBH + CaptionHeight - 7<br />
   hDC = GetDC(%Null)<br />
   BitBlt hBMPDC, 0,0,ImageWidth,ImageHeight, hDC, x,y, %SRCCopy 'copy desktop image to<br />
   ReleaseDC(%Null,hDC)<br />
<br />
   'save to file<br />
   Incr ImageCount<br />
   Graphic Save "images&#92;" + Format&#36;(ImageCount) + ".bmp"<br />
<br />
   'send to clipboard<br />
   Clipboard Reset<br />
   Clipboard Set Bitmap hBMP<br />
<br />
   'get rid of the bitmap<br />
   Graphic Bitmap End<br />
<br />
   'count images<br />
   Statusbar Set Text hDlg, %IDC_StatusBar, 1, 0, "ImageCount = " + Format&#36;(ImageCount)<br />
<br />
End Sub<br />
<br />
Function captionHeight As Long<br />
   Local cw,ch,ddw,ddh As Long<br />
   Dialog Get Size hDlg To ddw,ddh<br />
   Dialog Get Client hDlg To cw,ch<br />
   Function = ddh-ch<br />
End Function<br />
<br />
Sub MergeImages<br />
   Local i, x1, y1, x2, y2 As Long<br />
   Local hBMP, hBMPDC As Dword, ImageName&#36;<br />
<br />
   If IsFile("merge.bmp") Then Kill "merge.bmp"<br />
<br />
   Graphic Bitmap New ImageCount*ImageWidth,ImageHeight To hBMP<br />
   Graphic Attach hBMP,0<br />
   Graphic Get DC To hBMPDC<br />
<br />
   For i = 1 To ImageCount<br />
      ImageName&#36; = "images&#92;" + Format&#36;(i) + ".bmp"<br />
      x1 = (i-1) * ImageWidth<br />
      x2 = x1 + ImageWidth<br />
      y1 = 0<br />
      y2 = ImageHeight<br />
      Graphic Render Bitmap ImageName&#36;, (x1,y1)-(x2,y2)<br />
   Next i<br />
<br />
   Graphic Save "merge.bmp"<br />
   Graphic Bitmap End<br />
End Sub<br />
<br />
Sub ViewMerge<br />
   Local iReturn As Long, hBMP As Dword<br />
<br />
   Graphic Bitmap Load "merge.bmp", 0,0 To hBMP<br />
   Graphic Attach hBMP,0<br />
<br />
   Clipboard Reset<br />
   Clipboard Set Bitmap hBMP<br />
<br />
   Graphic Bitmap End<br />
<br />
   iReturn = ShellExecute(hDlg, "Open", "merge.bmp", &#36;Nul, &#36;Nul, %SW_ShowNormal)<br />
End Sub</code></div></div>]]></description>
			<content:encoded><![CDATA[This app displays a transparent dialog, with a Toolbar and a Statusbar. Images under the open dialog client area are captured and merged into a horizontal image.  This will be used by my next app to scroll the horizontal image - corresponding to my post about breaking vertical sheet music into scrollable horizontal music.<br />
<br />
Discussion is <a href="http://pump.richheimer.de/showthread.php?tid=81&amp;pid=562#pid562" target="_blank" rel="noopener" class="mycode_url">here.</a><br />
<br />
Toolbar buttons:<br />
<br />
1. Exit - close gbClientCapture<br />
<br />
2. Copy - captures the desktop below the transparent dialog client and saves it as a BMP. each capture is merged with previous captures to form a horizontal image (first captured image on the left, last captured image on the right)<br />
<br />
3. New - deletes all images, including the merged image<br />
<br />
4. View - opens the current merged image in the default image viewer<br />
<br />
The Statusbar keeps track of how many images there are in the image folder.<br />
<br />
Here's a picture of the app, with some desktop showing through the client area.<br />
<br />
<img src="https://www.garybeene.com/files/capture.jpg" loading="lazy"  alt="[Image: capture.jpg]" class="mycode_img" /><br />
<br />
Code and images are here:  <a href="https://www.garybeene.com/files/gbclientcapture.zip" target="_blank" rel="noopener" class="mycode_url">https://www.garybeene.com/files/gbclientcapture.zip</a><br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'Compilable Example:  (Jose Includes)<br />
#Compile Exe  "gbclientcapture.exe"<br />
#Dim All<br />
<br />
#Debug Error On<br />
#Debug Display On<br />
<br />
%Unicode=1<br />
#Include "Win32API.inc"<br />
<br />
%IDC_Toolbar   = 500<br />
%IDT_Exit      = 501<br />
%IDT_Copy      = 502<br />
%IDT_New       = 503<br />
%IDT_View      = 504<br />
%IDC_StatusBar = 505<br />
<br />
#Resource Manifest, 1,      "icons&#92;xptheme_dpiaware.xml"<br />
#Resource VersionInfo<br />
#Resource StringInfo "0409", "04B0"<br />
#Resource Version&#36; "CompanyName", "New Vision Concepts"<br />
#Resource Version&#36; "ProductName", "gbClientCapture"<br />
#Resource Version&#36; "ProductVersion", "1.0"<br />
#Resource Version&#36; "FileDescription",  "gbClientCapture - Capture Desktop Behind Client"<br />
#Resource Version&#36; "LegalCopyright",   "Copyright 2025 New Vision Concepts"<br />
<br />
#Resource Icon logo, "icons&#92;alogo.ico"<br />
#Resource Icon zexit, "icons&#92;power.ico"<br />
#Resource Icon zcopy, "icons&#92;copy.ico"<br />
#Resource Icon znew, "icons&#92;new.ico"<br />
#Resource Icon zmerge, "icons&#92;view.ico"<br />
<br />
&#36;Ver = "1.0"<br />
<br />
Global hDlg, hToolbar, hList, hBrush, hFont As Dword<br />
Global ImageCount, ImageWidth, ImageHeight As Long<br />
Global SBW, SBH, TBW, TBH As Long<br />
<br />
Function PBMain() As Long<br />
   Dialog New Pixels, 0, "gbClientCapture  v" + &#36;Ver,300,50,200,100, %WS_OverlappedWindow, %WS_Ex_Layered To hDlg<br />
   Dialog Set Icon hDlg, "logo"<br />
   Dialog Set Color hDlg, %Black, %White<br />
   CreateToolbar<br />
<br />
   Control Add Statusbar, hDlg, %IDC_StatusBar, "", 0,0,0,0, %CCS_Bottom Or %SBars_SizeGrip<br />
   Control Get Size hDlg, %IDC_StatusBar To SBW, SBH<br />
   Control Set Color hDlg, %IDC_StatusBar, %Black, %Gray<br />
<br />
   Font New "Tahoma",10, 1 To hFont<br />
   Control Set Font hDlg, %IDC_Toolbar, hFont<br />
   Control Set Font hDlg, %IDC_StatusBar, hFont<br />
<br />
   Dialog Show Modal hDlg Call DlgProc<br />
End Function<br />
<br />
CallBack Function DlgProc() As Long<br />
   Select Case Cb.Msg<br />
      Case %WM_InitDialog<br />
         hBrush = CreateSolidBrush(RGB(243,243,243))<br />
         SetWindowPos hDlg, %HWND_TOPMOST, 0, 0, 0, 0, %SWP_NOMOVE Or %SWP_NOSIZE<br />
         If IsFalse IsFolder("images") Then MkDir "images"<br />
         Settings_INI "get"<br />
         Statusbar Set Text hDlg, %IDC_StatusBar, 1, 0, "ImageCount = " + Format&#36;(ImageCount)<br />
         SetLayeredWindowAttributes(hDlg, %White, 255, %LWA_ALPHA Or %LWA_Colorkey)<br />
      Case %WM_ContextMenu<br />
         sBeep : Dialog End hDlg<br />
      Case %WM_Command<br />
         Select Case Cb.Ctl<br />
            Case %IDT_Exit  : sBeep : Dialog End hDlg<br />
            Case %IDT_Copy  : sBeep : SaveDialogToClipboard : MergeImages<br />
            Case %IDT_New   : sBeep : NewProject<br />
            Case %IDT_View : sBeep : ViewMerge<br />
         End Select<br />
<br />
      Case %WM_Notify<br />
         Select Case Cb.NmId<br />
            Case %IDC_Toolbar<br />
               Local pTbCustDraw As NmTbCustomDraw Pointer<br />
               pTbCustDraw  = Cb.LParam<br />
               Select Case @pTbCustDraw.nmcd.dwDrawStage<br />
                  Case %CDDS_PREPAINT  ' paint entire toolbar<br />
                     FillRect(@pTbCustDraw.nmcd.hdc, @pTbCustDraw.nmcd.rc, hBrush)<br />
                     Function = %CDRF_NOTIFYITEMDRAW<br />
               End Select<br />
         End Select<br />
<br />
      Case %WM_Destroy<br />
         DeleteObject hBrush<br />
         Settings_INI "save"<br />
   End Select<br />
End Function<br />
<br />
Sub CreateToolbar<br />
   ImageList New Icon 32,32,32,20 To hList<br />
   ImageList Add Icon hList, "zexit"   '1<br />
   ImageList Add Icon hList, "zcopy"   '2<br />
   ImageList Add Icon hList, "znew"    '3<br />
   ImageList Add Icon hList, "zmerge"  '4<br />
<br />
   Control Add Toolbar, hDlg, %IDC_Toolbar, "", 0,0,0,0 ', %TbStyle_Flat<br />
   Control Handle hDlg, %IDC_Toolbar To hToolbar<br />
   Toolbar Set ImageList hDlg, %IDC_Toolbar, hList, 0<br />
<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 1, %IDT_Exit, %TbStyle_Button, " Exit "<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 2, %IDT_Copy, %TbStyle_Button, " Copy "<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 3, %IDT_New, %TbStyle_Button, " New "<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 4, %IDT_View, %TbStyle_Button, " View "<br />
<br />
   Control Get Size hDlg, %IDC_Toolbar To TBW, TBH<br />
<br />
End Sub<br />
<br />
Sub Settings_INI(Task&#36;)<br />
   Local x,y,w,h, tempz, INIFileName As WStringZ * %Max_Path, WinPla As WindowPlacement<br />
<br />
   'set ini filename<br />
   INIFileName = Exe.Path&#36; + Exe.Name&#36; + ".ini"    'get INI file name<br />
<br />
   Select Case Task&#36;<br />
      Case "get"<br />
         'get dialog width/height from INI file and use to set Dialog size<br />
         GetPrivateProfileString "All", "Width", "1200", w, %Max_Path, INIFileName<br />
         GetPrivateProfileString "All", "Height", "300", h, %Max_Path, INIFileName<br />
         Dialog Set Size hDlg,Val(w), Val(h)   'width/height<br />
<br />
         'get dialog top/left from INI file and use to set Dialog location<br />
         Getprivateprofilestring "All", "Left", "0", x, %Max_Path, INIFileName<br />
         Getprivateprofilestring "All", "Top", "0",  y,  %Max_Path, INIFileName<br />
         If IsFile(INIFileName) Then Dialog Set Loc hDlg, Val(x), Val(y)   'left/top but only once INIFileName exists<br />
<br />
         'get value for string variables<br />
'         GetPrivateProfileString "All", "FontName", "Arial Black", FontName, %Max_Path, INIFileName<br />
<br />
         'get value for numeric variables<br />
         Getprivateprofilestring "All", "ImageWidth", "",     tempz, %Max_Path, INIFileName   : ImageHeight = Val(tempz)<br />
         Getprivateprofilestring "All", "ImageHeight", "",    tempz, %Max_Path, INIFileName   : ImageWidth = Val(tempz)<br />
         Getprivateprofilestring "All", "ImageCount", "0",    tempz, %Max_Path, INIFileName   : ImageCount = Val(tempz)<br />
<br />
      Case "save"<br />
         If IsFile(INIFileName) Then Kill INIFileName    'clear the INI file Name to remove residual entries before saving<br />
         WinPla.Length = SizeOf(WinPla)<br />
         GetWindowPlacement hDlg, WinPla<br />
         WritePrivateProfileString "All", "Left",   Str&#36;(WinPla.rcNormalPosition.nLeft), INIFileName<br />
         WritePrivateProfileString "All", "Top",    Str&#36;(WinPla.rcNormalPosition.nTop), INIFileName<br />
         WritePrivateProfileString "All", "Width",  Str&#36;(WinPla.rcNormalPosition.nRight - WinPla.rcNormalPosition.nLeft), INIFileName<br />
         WritePrivateProfileString "All", "Height", Str&#36;(WinPla.rcNormalPosition.nBottom - WinPla.rcNormalPosition.nTop), INIFileName<br />
<br />
         'save string variables<br />
'         WritePrivateProfileString "All", "FontName",   FontName, INIFileName<br />
<br />
         'save numeric variables<br />
         WritePrivateProfileString "All", "ImageHeight",    Str&#36;(ImageHeight), INIFileName<br />
         WritePrivateProfileString "All", "ImageWidth",     Str&#36;(ImageWidth), INIFileName<br />
         WritePrivateProfileString "All", "ImageCount",     Str&#36;(ImageCount), INIFileName<br />
   End Select<br />
End Sub<br />
<br />
Sub sBeep : WinBeep(275,150) : End Sub<br />
<br />
Sub NewProject  'remove all &#92;images&#92;*.bmp<br />
   Local temp&#36;<br />
   Clipboard Reset<br />
   ImageCount = 0<br />
   temp&#36; = Dir&#36;("images&#92;*.bmp")<br />
   If Len(temp&#36;) Then Kill "images&#92;*.bmp"<br />
   Statusbar Set Text hDlg, %IDC_StatusBar, 1, 0, "ImageCount = " + Format&#36;(ImageCount)<br />
End Sub<br />
<br />
Sub SaveDialogToClipboard<br />
   Local x,y As Long, hBMP, hBMPDC, hDC As Dword       '         abcdegfg<br />
   'create memory bitmap the size of the dialog client (less Toolbar and less StatusBar)<br />
   Dialog Get Client hDlg To ImageWidth, ImageHeight<br />
   ImageWidth = ImageWidth - 1<br />
   ImageHeight = ImageHeight - SBH - TBH - 2<br />
<br />
   Graphic Bitmap New ImageWidth, ImageHeight To hBMP<br />
   Graphic Attach hBMP,0<br />
   Graphic Get DC To hBMPDC<br />
<br />
   'bitblt dialog rectangle from the screen to the me                            mory bitmap<br />
   Dialog Get Loc hDlg To x,y<br />
   x = x + 9<br />
   y = y + TBH + CaptionHeight - 7<br />
   hDC = GetDC(%Null)<br />
   BitBlt hBMPDC, 0,0,ImageWidth,ImageHeight, hDC, x,y, %SRCCopy 'copy desktop image to<br />
   ReleaseDC(%Null,hDC)<br />
<br />
   'save to file<br />
   Incr ImageCount<br />
   Graphic Save "images&#92;" + Format&#36;(ImageCount) + ".bmp"<br />
<br />
   'send to clipboard<br />
   Clipboard Reset<br />
   Clipboard Set Bitmap hBMP<br />
<br />
   'get rid of the bitmap<br />
   Graphic Bitmap End<br />
<br />
   'count images<br />
   Statusbar Set Text hDlg, %IDC_StatusBar, 1, 0, "ImageCount = " + Format&#36;(ImageCount)<br />
<br />
End Sub<br />
<br />
Function captionHeight As Long<br />
   Local cw,ch,ddw,ddh As Long<br />
   Dialog Get Size hDlg To ddw,ddh<br />
   Dialog Get Client hDlg To cw,ch<br />
   Function = ddh-ch<br />
End Function<br />
<br />
Sub MergeImages<br />
   Local i, x1, y1, x2, y2 As Long<br />
   Local hBMP, hBMPDC As Dword, ImageName&#36;<br />
<br />
   If IsFile("merge.bmp") Then Kill "merge.bmp"<br />
<br />
   Graphic Bitmap New ImageCount*ImageWidth,ImageHeight To hBMP<br />
   Graphic Attach hBMP,0<br />
   Graphic Get DC To hBMPDC<br />
<br />
   For i = 1 To ImageCount<br />
      ImageName&#36; = "images&#92;" + Format&#36;(i) + ".bmp"<br />
      x1 = (i-1) * ImageWidth<br />
      x2 = x1 + ImageWidth<br />
      y1 = 0<br />
      y2 = ImageHeight<br />
      Graphic Render Bitmap ImageName&#36;, (x1,y1)-(x2,y2)<br />
   Next i<br />
<br />
   Graphic Save "merge.bmp"<br />
   Graphic Bitmap End<br />
End Sub<br />
<br />
Sub ViewMerge<br />
   Local iReturn As Long, hBMP As Dword<br />
<br />
   Graphic Bitmap Load "merge.bmp", 0,0 To hBMP<br />
   Graphic Attach hBMP,0<br />
<br />
   Clipboard Reset<br />
   Clipboard Set Bitmap hBMP<br />
<br />
   Graphic Bitmap End<br />
<br />
   iReturn = ShellExecute(hDlg, "Open", "merge.bmp", &#36;Nul, &#36;Nul, %SW_ShowNormal)<br />
End Sub</code></div></div>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Very Fast WString /Object Hash Table]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=65</link>
			<pubDate>Fri, 05 Sep 2025 00:29:00 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=164">Stanley Durham</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=65</guid>
			<description><![CDATA[find 100,000 random keys = 0.004 seconds<br />
find 1,000,000 random keys = 0.112 seconds<br />
find 5,000,000 random keys = 0.900 seconds<br />
 <br />
A unique key, WString, is used to store and retrieve a payload value, Object.<br />
Key is case sensitive.<br />
Property Set Capacity(ByVal value As Long), about number of expected items.<br />
 <br />
Can Store/Restore To/From File. To use that you need to provide callbacks. One to pack the object into a String and another to restore the object from a String.<br />
Declare Function ObjectStoreCallback(o As IUnknown) As String<br />
Declare Function ObjectRestoreCallback(ByRef stored As String) As IUnknown<br />
When you implement the callbacks, use the actual “Interface” instead of “IUnknown.” <br />
 <br />
INC has no dependencies.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'ObjectHash.inc<br />
'Public domain, use at own risk. SDurham<br />
#If Not %Def(%OHash250808)<br />
    %OHash250808 = 1<br />
<br />
    Declare Function ObjectStoreCallback(o As IUnknown) As String<br />
        'store object's data in a String and return the String<br />
        'when you implement the callback, replace IUnknown with the actual interface<br />
    Declare Function ObjectRestoreCallback(ByRef stored As String) As IUnknown<br />
        'restore object's data and return new instance<br />
        'when you implement the callback, replace IUnknown with the actual interface<br />
<br />
    Type WStrT<br />
        mem As Word Ptr<br />
        count As Long<br />
    End Type<br />
    Type OHashNodeT<br />
        key As WStrT<br />
        value As Long<br />
        next As OHashNodeT Ptr<br />
    End Type<br />
    Type StrBuildT<br />
        mem As Long<br />
        count As Long<br />
        max As Long<br />
    End Type<br />
<br />
    Class OHashC<br />
<br />
        'Key/Value WString/Object Hash Table<br />
        'value stored and retrieved using unique key<br />
        'key case sensitive<br />
<br />
        Instance mCapacity As Long<br />
        Instance mCount As Long<br />
        Instance mArr() As Long<br />
        Instance mIndex As Long<br />
        Instance mNode As OHashNodeT Ptr<br />
        Instance mStore As Long<br />
        Instance mRestore As Long<br />
<br />
        Class Method Create()<br />
            mCapacity = 30 'default capacity<br />
            ReDim mArr(0 To mCapacity - 1)<br />
        End Method<br />
        Class Method Destroy()<br />
            Me.ClearMe()<br />
            Erase mArr()<br />
        End Method<br />
<br />
        Interface OHashI : Inherit IUnknown<br />
<br />
            Property Set Capacity(ByVal value As Long)<br />
                'about number of expected items : hash table must be empty<br />
                If mCount = 0 And value &gt; 0 Then<br />
                    mCapacity = value<br />
                    ReDim mArr(0 To mCapacity - 1)<br />
                End If<br />
            End Property<br />
<br />
            Method Storage(ByVal storeCB As Long, ByVal restoreCB As Long)<br />
                'store and restore callbacks must be set to call Stor/Restore methods<br />
                If storeCB And restoreCB Then<br />
                    mStore = storeCB : mRestore = restoreCB<br />
                End If<br />
            End Method<br />
<br />
            Method Clear()<br />
                'empty container<br />
                Me.ClearMe()<br />
            End Method<br />
<br />
            Property Get Count() As Long<br />
                'get item count<br />
                Property = mCount<br />
            End Property<br />
<br />
            Method Set(ByRef key As WString, value As IUnknown)<br />
                'add key and associated value : key added if not in hash table<br />
                Local index, keymem, keylen As Long<br />
                Local node As OHashNodeT Ptr<br />
                If IsObject(value) Then<br />
                    keymem = StrPtr(key)<br />
                    keylen = Len(key)<br />
                    index = WStrHashIndex(keymem, keylen, mCapacity)<br />
                    node = mArr(index)<br />
                    While node<br />
                        If WStrEqual(keymem, keylen, @node.key.mem, @node.key.count) Then<br />
                            @node.value = Me.FreeObj(@node.value)<br />
                            @node.value = Me.LongFromObj(value)<br />
                            Exit Method<br />
                        End If<br />
                        node = @node.next<br />
                    Wend<br />
                    'key not in hash table<br />
                    node = MemAlloc(SizeOf(@node))<br />
                    If node Then<br />
                        WStrSet @node.key, key<br />
                        @node.value = Me.LongFromObj(value)<br />
                        @node.next = mArr(index)<br />
                        mArr(index) = node<br />
                        Incr mCount<br />
                    End If<br />
                End If<br />
            End Method<br />
<br />
            Method Get(ByRef key As WString) As IUnknown<br />
                'get key's associated value : null if key not in hash table<br />
                Local o As IUnknown<br />
                Local node As OHashNodeT Ptr<br />
                node = Me.Contains(key)<br />
                If node Then<br />
                    o = Me.ObjFromLong(@node.value)<br />
                    Method = o<br />
                End If<br />
            End Method<br />
<br />
            Method Contains(ByRef key As WString) As Long<br />
                'true/false if key in hash table<br />
                Local keymem, keylen As Long<br />
                Local node As OHashNodeT Ptr<br />
                If mCount Then<br />
                    keymem = StrPtr(key)<br />
                    keylen = Len(key)<br />
                    node = mArr(WStrHashIndex(keymem, keylen, mCapacity))<br />
                    While node<br />
                        If WStrEqual(keymem, keylen, @node.key.mem, @node.key.count) Then<br />
                            Method = node : Exit Method<br />
                        End If<br />
                        node = @node.next<br />
                    Wend<br />
                End If<br />
            End Method<br />
<br />
            Method Delete(ByRef key As WString)<br />
                'delete key and associated value<br />
                Local index, keymem, keylen As Long<br />
                Local node, prev As OHashNodeT Ptr<br />
                If mCount Then<br />
                    keymem = StrPtr(key)<br />
                    keylen = Len(key)<br />
                    index = WStrHashIndex(keymem, keylen, mCapacity)<br />
                    node = mArr(index)<br />
                    While node<br />
                        If WStrEqual(keymem, keylen, @node.key.mem, @node.key.count) Then<br />
                            If prev Then<br />
                                @prev.next = @node.next<br />
                            Else<br />
                                mArr(index) = @node.next<br />
                            End If<br />
                            node = Me.FreeNode(node)<br />
                            Decr MCount<br />
                            Exit Method<br />
                        End If<br />
                        node = @node.next<br />
                    Wend<br />
                End If<br />
            End Method<br />
<br />
            Method Start()<br />
                'reset cursor to start<br />
                mIndex = -1 : mNode = 0<br />
            End Method<br />
<br />
            Method Each() As Long<br />
                'move cursor to next key in hash table : true/false success<br />
                Register i As Long<br />
                If mCount Then<br />
                    If mNode And @mNode.next Then<br />
                        mNode = @mNode.next<br />
                        Method = 1 : Exit Method<br />
                    Else<br />
                        mNode = 0<br />
                        For i = mIndex + 1 To mCapacity - 1<br />
                            If mArr(i) Then<br />
                                mIndex = i<br />
                                mNode = mArr(i)<br />
                                Method = 1 : Exit Method<br />
                            End If<br />
                        Next i<br />
                    End If<br />
                End If<br />
                mIndex = -1 : mNode = 0<br />
            End Method<br />
<br />
            Method Key() As WString<br />
                'get key at cursor position : null if cursor invalid<br />
                If mNode Then Method = WStrGet(@mNode.key)<br />
            End Method<br />
<br />
            Method Value() As IUnknown<br />
                'get value at cursor position : null if cursor invalid<br />
                If mNode Then Method = Me.ObjFromLong(@mNode.value)<br />
            End Method<br />
<br />
            Method Store() As String<br />
                'store container as String<br />
                Local key, s As String<br />
                Local value As IUnknown<br />
                Local sb As StrBuildT<br />
                If mCount And mStore Then<br />
                    StrBuildPush sb, Mkl&#36;(mCount)<br />
                    Me.Start()<br />
                    While Me.Each()<br />
                        key = ChrToUtf8&#36;(Me.Key())<br />
                        value = Me.Value()<br />
                        If IsObject(value) Then<br />
                            Call Dword mStore Using ObjectStoreCallback(value) To s<br />
                            StrBuildPush sb, Mkl&#36;(Len(key))<br />
                            StrBuildPush sb, key<br />
                            StrBuildPush sb, Mkl&#36;(Len(s))<br />
                            StrBuildPush sb, s<br />
                        End If<br />
                    Wend<br />
                    Method = StrBuildPop(sb)<br />
                End If<br />
            End Method<br />
<br />
            Method Restore(ByRef stored As String)<br />
                'restore container from String<br />
                Register i As Long<br />
                Local items, bytes As Long<br />
                Local key, s As String<br />
                Local o As IUnknown<br />
                Local p As Long Ptr<br />
                Me.ClearMe()<br />
                If mRestore And Len(stored) Then<br />
                    p = StrPtr(stored)<br />
                    items = @p : Incr p<br />
                    For i = 1 To items<br />
                        bytes = @p : Incr p<br />
                        key = Peek&#36;(p, bytes) : p += bytes<br />
                        bytes = @p : Incr p<br />
                        s = Peek&#36;(p, bytes) : p += bytes<br />
                        Call Dword mRestore Using ObjectRestoreCallback(s) To o<br />
                        Me.Set(Utf8ToChr&#36;(key), o)<br />
                    Next i<br />
                End If<br />
            End Method<br />
<br />
            Method FileStore(ByVal file As WString)<br />
                'store container to File<br />
                If Len(file) And mStore Then Me.FilePut(file, Me.Store())<br />
            End Method<br />
<br />
            Method FileRestore(ByVal file As WString)<br />
                'restore container from File<br />
                If IsFile(file) And mRestore Then Me.Restore(Me.FileGet(file))<br />
            End Method<br />
<br />
        End Interface<br />
<br />
        Class Method ClearMe()<br />
            Register i As Long<br />
            Local node As OHashNodeT Ptr<br />
            For i = 0 To mCapacity - 1<br />
                While mArr(i)<br />
                    node = mArr(i)<br />
                    mArr(i) = @node.next<br />
                    Me.FreeNode(node)<br />
                Wend<br />
            Next i<br />
            mCount = 0<br />
        End Method<br />
<br />
        Class Method FreeNode(ByVal node As OHashNodeT Ptr) As Long<br />
            If node Then<br />
                WStrFinal @node.key<br />
                @node.value = Me.FreeObj(@node.value)<br />
                MemFree(node)<br />
            End If<br />
        End Method<br />
<br />
        Class Method LongFromObj(o As IUnknown) As Long<br />
            If IsObject(o) Then<br />
                o.AddRef()<br />
                Method = Peek(Long, VarPtr(o))<br />
            End If<br />
        End Method<br />
<br />
        Class Method ObjFromLong(ByVal h As Long) As IUnknown<br />
            Local o As IUnknown<br />
            If h Then<br />
                Poke Long, VarPtr(o), h<br />
                o.AddRef()<br />
                Method = o<br />
            End If<br />
        End Method<br />
<br />
        Class Method FreeObj(ByVal h As Long) As Long<br />
            Local o As IUnknown<br />
            If h Then<br />
                Poke Long, VarPtr(o), h<br />
                o = Nothing<br />
            End If<br />
        End Method<br />
<br />
<br />
        Class Method FilePut(ByVal file As WString, ByRef value As String)<br />
            Local f As Long<br />
            If Len(file) = 0 Then Exit Method<br />
            f = FreeFile<br />
            Open file For Binary As f<br />
            SetEof f<br />
            Put&#36; f, value<br />
            Close f<br />
        End Method<br />
<br />
        Class Method FileGet(ByVal file As WString) As String<br />
            Local f As Long, value As String<br />
            If IsFalse IsFile(file) Then Exit Method<br />
            f = FreeFile<br />
            Open file For Binary As f<br />
            Get&#36; f, Lof(f), value<br />
            Close f<br />
            Method = value<br />
        End Method<br />
<br />
    End Class<br />
#EndIf '%OHash250808<br />
<br />
#If Not %Def(%WStr250808)<br />
    %WStr250808 = 1<br />
    '----------------------------------------------------------------------<br />
    'WString Container = Word Array<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    %WrdSize = 2<br />
    Type WStrT<br />
        mem As Word Ptr<br />
        count As Long<br />
    End Type<br />
    '----------------------------------------------------------------------<br />
    Sub WStrFinal(t As WStrT)<br />
        'call before variable gores out of scope to free memory<br />
        WStrClear t<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub WStrClear(t As WStrT)<br />
        'empty container<br />
        t.mem = MemFree(t.mem)<br />
        t.count = 0<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function WStrCount(t As WStrT) As Long<br />
        'get character count<br />
        Function = t.count<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function WStrGet(t As WStrT) As WString<br />
        'get value<br />
        If t.mem Then Function = Peek&#36;&#36;(t.mem, t.count)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Macro WStrGetM(t) = Peek&#36;&#36;(t.mem, t.count)<br />
    '----------------------------------------------------------------------<br />
    Sub WStrSet(t As WStrT, ByRef value As WString)<br />
        'set value<br />
        Local characters As Long<br />
        t.mem = MemFree(t.mem)<br />
        t.count = 0<br />
        characters = Len(value)<br />
        If characters Then<br />
            t.mem = MemAlloc(characters * %WrdSize)<br />
            If t.mem Then<br />
                Memory Copy StrPtr(value), t.mem, characters * %WrdSize<br />
                t.count = characters<br />
            End If<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
#EndIf '%WStr250808<br />
<br />
#If Not %Def(%WStrHash250808)<br />
    %WStrHash250808 = 1<br />
    '----------------------------------------------------------------------<br />
    'WString Hash Index<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    Function WStrHashIndex(ByVal keymem As Word Ptr, ByVal keylen As Long, ByVal capacity As Long) As Long<br />
        'get key's hash index position<br />
        Register i As Long<br />
        Register total As Long<br />
        Local temp As Long<br />
        If keymem Then<br />
            For i = 0 To keylen - 1<br />
                temp += @keymem[i] + total<br />
                Shift Left total, 8<br />
                total += temp<br />
            Next i<br />
            Function = Abs(total Mod capacity)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
#EndIf '%WStrHash250808<br />
<br />
#If Not %Def(%WStrEqual250808)<br />
    %WStrEqual250808 = 1<br />
    '----------------------------------------------------------------------<br />
    Function WStrEqual(ByVal memA As Word Ptr, ByVal lenA As Long, ByVal memB As Word Ptr, ByVal lenB As Long) As Long<br />
        'true/false if two strings equal<br />
        Register i As Long<br />
        If memA And memB And lenA = lenB Then<br />
            For i = 0 To lenA - 1<br />
                If @memA[i] &lt;&gt; @memB[i] Then Exit Function<br />
            Next i<br />
            Function = 1<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
#EndIf '%WStrEqual250808<br />
<br />
#If Not %Def(%StrBuild250811)<br />
    %StrBuild250811 = 1<br />
    '----------------------------------------------------------------------<br />
    'String Builder<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    %BytSize = 1<br />
    %StrBuildBufferMax = 5000000<br />
    '----------------------------------------------------------------------<br />
    Type StrBuildT<br />
        mem As Long<br />
        count As Long<br />
        max As Long<br />
    End Type<br />
    '----------------------------------------------------------------------<br />
    Sub StrBuildPush(t As StrBuildT, ByVal value As String)<br />
        'append value<br />
        Local strlen, currentcount, buffer, newmax As Long<br />
        strlen = Len(value)<br />
        If strlen Then<br />
            If strlen &gt; t.max - t.count Then<br />
                currentcount = t.count : t.count = 0 : t.max = 0<br />
                buffer = Max&amp;(1, 2 * currentcount)<br />
                buffer = Min&amp;(buffer, %StrBuildBufferMax)<br />
                newmax = currentcount + buffer + strlen<br />
                t.mem = MemReAlloc(t.mem, newmax * %BytSize)<br />
                If t.mem = 0 Then Exit Sub<br />
                t.count = currentcount : t.max = newmax<br />
            End If<br />
            Memory Copy StrPtr(value), t.mem + (t.count * %BytSize), strlen * %BytSize<br />
            t.count += strlen<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrBuildPop(t As StrBuildT) As String<br />
        'get whole string : free memory<br />
        If t.mem And t.count Then Function = Peek&#36;(t.mem, t.count)<br />
        t.mem = MemFree(t.mem)<br />
        t.count = 0<br />
        t.max = 0<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
#EndIf '%StrBuild250811<br />
<br />
#If Not %Def(%Memory230925)<br />
    %Memory230925 = 1<br />
    '----------------------------------------------------------------------<br />
    'Memory Allocation<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    Declare Function GlobalAlloc Lib "Kernel32.dll" Alias "GlobalAlloc" (ByVal uFlags As Dword, ByVal dwBytes As Dword) As Dword<br />
    Declare Function GlobalReAlloc Lib "Kernel32.dll" Alias "GlobalReAlloc" (ByVal hMem As Dword, ByVal dwBytes As Dword, ByVal uFlags As Dword) As Dword<br />
    Declare Function GlobalFree Lib "Kernel32.dll" Alias "GlobalFree" (ByVal hMem As Dword) As Dword<br />
    %MEMFIXED = &amp;H0000 : %MEMMOVEABLE = &amp;H0002 : %MEMZEROINIT = &amp;H0040 : %MEMGPTR = (%MEMZEROINIT Or %MEMFIXED)<br />
    '----------------------------------------------------------------------<br />
    Function MemAlloc(ByVal bytes As Long) As Long<br />
        'allocate memory<br />
        If bytes Then Function = GlobalAlloc(ByVal %MEMGPTR, ByVal bytes)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function MemReAlloc(ByVal hMem As Long, ByVal bytes As Long) As Long<br />
        'reallocate new size<br />
        If hMem And bytes Then<br />
            Function = GlobalReAlloc(ByVal hMem, ByVal bytes, ByVal %MEMMOVEABLE Or %MEMZEROINIT)<br />
        ElseIf bytes Then<br />
            Function = GlobalAlloc(ByVal %MEMGPTR, ByVal bytes)<br />
        ElseIf hMem Then<br />
            Function = GlobalFree(ByVal hMem)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function MemFree(ByVal hMem As Long) As Long<br />
        'free memory<br />
        If hMem Then GlobalFree(ByVal hMem)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
#EndIf '%Memory230925</code></div></div><hr class="mycode_hr" />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'ObjectHash.bas<br />
#Option LargeMem32<br />
#Dim All<br />
#Compile Exe<br />
#Include Once "WIN32API.INC"<br />
#Include Once "..&#92;ObjectHash.inc"<br />
<br />
%TextBox = 101<br />
%BtnID = 102<br />
Global gDlg As Long<br />
Global gBuild As IStringBuilderW<br />
Sub SS(ByVal value As WString)<br />
    gBuild.Add(value + &#36;CrLf)<br />
End Sub<br />
<br />
Class TestC<br />
    Instance mX As Long<br />
    Interface TestI : Inherit IUnknown<br />
        Property Get X() As Long<br />
            Property = mX<br />
        End Property<br />
        Property Set X(ByVal value As Long)<br />
            mX = value<br />
        End Property<br />
        Property Get Str() As WString<br />
            Property = Format&#36;(mX)<br />
        End Property<br />
    End Interface<br />
End Class<br />
<br />
Function TestStore(o As TestI) As String<br />
    'store callback<br />
    'store object's data in a String and return the String<br />
    'when you implement the callback, replace IUnknown with the actual interface<br />
    If IsObject(o) Then Function = Mkl&#36;(o.X)<br />
End Function<br />
<br />
Function TestRestore(ByRef stored As String) As TestI<br />
    'restore callback<br />
    'restore object's data and return new instance<br />
    'when you implement the callback, replace IUnknown with the actual interface<br />
    Local o As TestI : o = Class "TestC"<br />
    o.X = Cvl(stored)<br />
    Function = o<br />
End Function<br />
<br />
Sub SampleCode()<br />
    Register i As Long<br />
    Local hash As OHashI : hash = Class "OHashC"<br />
    Local o As TestI : o = Class "TestC"<br />
    Local stored As String<br />
    Local a() As WString<br />
    Local d As Double<br />
    Local testcount As Long : testcount = 100000 'can change<br />
<br />
    Randomize<br />
    MousePtr 11<br />
    Control Set Text gDlg, %TextBox, ""<br />
<br />
    SS ""<br />
    SS "Property Set Capacity(ByVal value As Long)"<br />
    hash.capacity = testcount<br />
<br />
    SS ""<br />
    SS "Method Storage(ByVal storeCB As Long, ByVal restoreCB As Long)"<br />
    hash.Storage(CodePtr(TestStore), CodePtr(TestRestore))<br />
<br />
    SS ""<br />
    SS "Method Add(ByRef key As WString, value As IUnknown) As Long"<br />
    SS "add key/value items"<br />
    For i = 65 To 69<br />
        o = Class "TestC" : o.X = i<br />
        hash.Set(Chr&#36;&#36;(i), o)<br />
    Next i<br />
    hash.Start()<br />
    While hash.Each()<br />
        o = hash.Value()<br />
        If IsObject(o) Then SS "key = " + &#36;Dq + hash.Key() + &#36;Dq + " | value = " + o.Str<br />
    Wend<br />
<br />
    SS ""<br />
    SS "change value for key C"<br />
    o = Class "TestC" : o.X = 4444<br />
    hash.Set("C", o)<br />
    hash.Start()<br />
    While hash.Each()<br />
        o = hash.Value()<br />
        If IsObject(o) Then SS "key = " + &#36;Dq + hash.Key() + &#36;Dq + " | value = " + o.Str<br />
    Wend<br />
<br />
    SS ""<br />
    SS "Method Get(ByRef key As WString) As IUnknown"<br />
    SS "get object to change value for key C"<br />
    o = hash.Get("C") : If IsObject(o) Then o.X = 999999999<br />
    hash.Start()<br />
    While hash.Each()<br />
        o = hash.Value()<br />
        If IsObject(o) Then SS "key = " + &#36;Dq + hash.Key() + &#36;Dq + " | value = " + o.Str<br />
    Wend<br />
<br />
    SS ""<br />
    SS "Method Contains(ByRef key As WString)"<br />
    SS "hash table contains key B = " + Format&#36;(hash.Contains("B"))<br />
    SS "hash table contains key E = " + Format&#36;(hash.Contains("E"))<br />
    SS "hash table contains key ZZZ = " + Format&#36;(hash.Contains("ZZZ"))<br />
<br />
    SS ""<br />
    SS "Method Delete(ByRef key As WString)"<br />
    SS "delete key C"<br />
    hash.Delete("C")<br />
    hash.Start()<br />
    While hash.Each()<br />
        o = hash.Value()<br />
        If IsObject(o) Then SS "key = " + &#36;Dq + hash.Key() + &#36;Dq + " | value = " + o.Str<br />
    Wend<br />
<br />
    SS ""<br />
    SS "Method Store() As String"<br />
    stored = hash.Store()<br />
    SS "Method Restore(ByRef stored As String)"<br />
    hash.Restore(stored)<br />
    SS "Method FileStore(ByVal file As String)"<br />
    hash.FileStore("Stored.hash")<br />
    SS "Method FileRestore(ByVal file As String)"<br />
    hash.FileRestore("Stored.hash")<br />
    Kill "Stored.hash"<br />
    hash.Start()<br />
    While hash.Each()<br />
        o = hash.Value()<br />
        If IsObject(o) Then SS "key = " + &#36;Dq + hash.Key() + &#36;Dq + " | value = " + o.Str<br />
    Wend<br />
<br />
    ReDim a(1 To testcount)<br />
    For i = 1 To testcount<br />
        a(i) = RandomString()<br />
    Next i<br />
<br />
    SS ""<br />
    SS "add "+Format&#36;(testcount,"#,")+" random key/value items"<br />
    hash.Clear()<br />
    d = Timer<br />
    For i = 1 To testcount<br />
        o = Class "TestC" : o.X = i<br />
        hash.Set(a(i), o)<br />
    Next i<br />
    SS "Time = " + Format&#36;(Timer - d, "000.000")<br />
    SS "Count = " + Format&#36;(hash.Count, "#,") + " duplicate keys not allowed"<br />
<br />
    SS ""<br />
    SS "find "+Format&#36;(testcount,"#,")+" random keys"<br />
    d = Timer<br />
    For i = 1 To testcount<br />
        If hash.Contains(a(i)) = 0 Then<br />
            ? "hash table fail" : Exit For<br />
        End If<br />
    Next i<br />
    SS "Time = " + Format&#36;(Timer - d, "000.000")<br />
<br />
    SS ""<br />
    SS ""<br />
    Control Set Text gDlg, %TextBox, gBuild.String<br />
    gBuild.Clear<br />
    MousePtr 1<br />
End Sub<br />
<br />
Function RandomString() As String<br />
    Register i As Long<br />
    Local s As String<br />
    For i = 1 To Rnd(5, 8)<br />
        Select Case As Const Rnd(1, 2)<br />
            Case 1 : s += Chr&#36;(Rnd(65, 90))<br />
            Case 2 : s += Chr&#36;(Rnd(97, 122))<br />
        End Select<br />
    Next i<br />
    Function = s<br />
End Function<br />
<br />
Function PBMain()<br />
    gBuild = Class "StringBuilderW"<br />
    Dialog Default Font "courier new", 12, 0, 0<br />
    Dialog New 0, "Sample Code", 0, 0, 0, 0, %WS_Caption Or %WS_ClipSiblings Or %WS_MinimizeBox Or %WS_SysMenu Or %WS_ThickFrame, %WS_Ex_AppWindow To gDlg<br />
    Control Add TextBox, gDlg, %TextBox, "", 0, 0, 0, 0, %ES_AutoHScroll Or %ES_AutoVScroll Or %ES_MultiLine Or %ES_NoHideSel Or %ES_WantReturn Or %WS_HScroll Or %WS_VScroll Or %WS_Border, 0<br />
    Control Add Button,  gDlg, %BtnID, "Run", 275, 220, 60, 15, %BS_Flat, 0<br />
    SendMessageW(GetDlgItem(gDlg, %TextBox), %EM_SETLIMITTEXT, 4000000, 0)<br />
    Dialog Show Modeless gDlg, Call DlgCB<br />
    While IsWin(gDlg)<br />
        Dialog DoEvents<br />
    Wend<br />
End Function<br />
<br />
CallBack Function DlgCB()<br />
    Select Case As Long Cb.Msg<br />
        Case %WM_InitDialog<br />
            WM_InitDialog()<br />
        Case %WM_Size<br />
            WM_Size()<br />
        Case %WM_Command<br />
            Select Case As Long Cb.Ctl<br />
                Case %BtnID : If Cb.CtlMsg = %BN_Clicked Then SampleCode()<br />
            End Select<br />
    End Select<br />
End Function<br />
<br />
Sub WM_InitDialog()<br />
    Local clientW, clientH As Long<br />
    Desktop Get Client To clientW, clientH<br />
    clientW /= 7<br />
    clientH /= 4<br />
    Dialog Set Loc gDlg, clientW / 2, clientH / 7<br />
    Dialog Set Size gDlg, clientW, clientH<br />
End Sub<br />
<br />
Sub WM_Size()<br />
    Local clientW, clientH As Long<br />
    Local marg As Long<br />
    Local buttonW, buttonH As Long<br />
    Local txtWidth, txtHeight As Long<br />
    Local fromLeft, fromBottom As Long<br />
    Dialog Get Client gDlg To clientW, clientH<br />
    marg = 1 : buttonW = 30 : buttonH = 10<br />
    fromLeft = clientW - marg - marg - buttonW<br />
    fromBottom = clientH - marg - marg - buttonH<br />
    Control Set Size gDlg, %BtnID, buttonW, buttonH<br />
    Control Set Loc gDlg, %BtnID, fromLeft, fromBottom<br />
    txtWidth = clientW - marg - marg<br />
    txtHeight = clientH - marg - buttonH - marg - marg<br />
    Control Set Size gDlg, %TextBox, txtWidth, txtHeight<br />
    Control Set Loc gDlg, %TextBox, marg, marg<br />
End Sub</code></div></div><hr class="mycode_hr" />
<span style="font-weight: bold;" class="mycode_b">comments</span> <br />
<a href="http://pump.richheimer.de/showthread.php?tid=66" target="_blank" rel="noopener" class="mycode_url">Very Fast WString /Object Hash Table (Comments)</a>]]></description>
			<content:encoded><![CDATA[find 100,000 random keys = 0.004 seconds<br />
find 1,000,000 random keys = 0.112 seconds<br />
find 5,000,000 random keys = 0.900 seconds<br />
 <br />
A unique key, WString, is used to store and retrieve a payload value, Object.<br />
Key is case sensitive.<br />
Property Set Capacity(ByVal value As Long), about number of expected items.<br />
 <br />
Can Store/Restore To/From File. To use that you need to provide callbacks. One to pack the object into a String and another to restore the object from a String.<br />
Declare Function ObjectStoreCallback(o As IUnknown) As String<br />
Declare Function ObjectRestoreCallback(ByRef stored As String) As IUnknown<br />
When you implement the callbacks, use the actual “Interface” instead of “IUnknown.” <br />
 <br />
INC has no dependencies.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'ObjectHash.inc<br />
'Public domain, use at own risk. SDurham<br />
#If Not %Def(%OHash250808)<br />
    %OHash250808 = 1<br />
<br />
    Declare Function ObjectStoreCallback(o As IUnknown) As String<br />
        'store object's data in a String and return the String<br />
        'when you implement the callback, replace IUnknown with the actual interface<br />
    Declare Function ObjectRestoreCallback(ByRef stored As String) As IUnknown<br />
        'restore object's data and return new instance<br />
        'when you implement the callback, replace IUnknown with the actual interface<br />
<br />
    Type WStrT<br />
        mem As Word Ptr<br />
        count As Long<br />
    End Type<br />
    Type OHashNodeT<br />
        key As WStrT<br />
        value As Long<br />
        next As OHashNodeT Ptr<br />
    End Type<br />
    Type StrBuildT<br />
        mem As Long<br />
        count As Long<br />
        max As Long<br />
    End Type<br />
<br />
    Class OHashC<br />
<br />
        'Key/Value WString/Object Hash Table<br />
        'value stored and retrieved using unique key<br />
        'key case sensitive<br />
<br />
        Instance mCapacity As Long<br />
        Instance mCount As Long<br />
        Instance mArr() As Long<br />
        Instance mIndex As Long<br />
        Instance mNode As OHashNodeT Ptr<br />
        Instance mStore As Long<br />
        Instance mRestore As Long<br />
<br />
        Class Method Create()<br />
            mCapacity = 30 'default capacity<br />
            ReDim mArr(0 To mCapacity - 1)<br />
        End Method<br />
        Class Method Destroy()<br />
            Me.ClearMe()<br />
            Erase mArr()<br />
        End Method<br />
<br />
        Interface OHashI : Inherit IUnknown<br />
<br />
            Property Set Capacity(ByVal value As Long)<br />
                'about number of expected items : hash table must be empty<br />
                If mCount = 0 And value &gt; 0 Then<br />
                    mCapacity = value<br />
                    ReDim mArr(0 To mCapacity - 1)<br />
                End If<br />
            End Property<br />
<br />
            Method Storage(ByVal storeCB As Long, ByVal restoreCB As Long)<br />
                'store and restore callbacks must be set to call Stor/Restore methods<br />
                If storeCB And restoreCB Then<br />
                    mStore = storeCB : mRestore = restoreCB<br />
                End If<br />
            End Method<br />
<br />
            Method Clear()<br />
                'empty container<br />
                Me.ClearMe()<br />
            End Method<br />
<br />
            Property Get Count() As Long<br />
                'get item count<br />
                Property = mCount<br />
            End Property<br />
<br />
            Method Set(ByRef key As WString, value As IUnknown)<br />
                'add key and associated value : key added if not in hash table<br />
                Local index, keymem, keylen As Long<br />
                Local node As OHashNodeT Ptr<br />
                If IsObject(value) Then<br />
                    keymem = StrPtr(key)<br />
                    keylen = Len(key)<br />
                    index = WStrHashIndex(keymem, keylen, mCapacity)<br />
                    node = mArr(index)<br />
                    While node<br />
                        If WStrEqual(keymem, keylen, @node.key.mem, @node.key.count) Then<br />
                            @node.value = Me.FreeObj(@node.value)<br />
                            @node.value = Me.LongFromObj(value)<br />
                            Exit Method<br />
                        End If<br />
                        node = @node.next<br />
                    Wend<br />
                    'key not in hash table<br />
                    node = MemAlloc(SizeOf(@node))<br />
                    If node Then<br />
                        WStrSet @node.key, key<br />
                        @node.value = Me.LongFromObj(value)<br />
                        @node.next = mArr(index)<br />
                        mArr(index) = node<br />
                        Incr mCount<br />
                    End If<br />
                End If<br />
            End Method<br />
<br />
            Method Get(ByRef key As WString) As IUnknown<br />
                'get key's associated value : null if key not in hash table<br />
                Local o As IUnknown<br />
                Local node As OHashNodeT Ptr<br />
                node = Me.Contains(key)<br />
                If node Then<br />
                    o = Me.ObjFromLong(@node.value)<br />
                    Method = o<br />
                End If<br />
            End Method<br />
<br />
            Method Contains(ByRef key As WString) As Long<br />
                'true/false if key in hash table<br />
                Local keymem, keylen As Long<br />
                Local node As OHashNodeT Ptr<br />
                If mCount Then<br />
                    keymem = StrPtr(key)<br />
                    keylen = Len(key)<br />
                    node = mArr(WStrHashIndex(keymem, keylen, mCapacity))<br />
                    While node<br />
                        If WStrEqual(keymem, keylen, @node.key.mem, @node.key.count) Then<br />
                            Method = node : Exit Method<br />
                        End If<br />
                        node = @node.next<br />
                    Wend<br />
                End If<br />
            End Method<br />
<br />
            Method Delete(ByRef key As WString)<br />
                'delete key and associated value<br />
                Local index, keymem, keylen As Long<br />
                Local node, prev As OHashNodeT Ptr<br />
                If mCount Then<br />
                    keymem = StrPtr(key)<br />
                    keylen = Len(key)<br />
                    index = WStrHashIndex(keymem, keylen, mCapacity)<br />
                    node = mArr(index)<br />
                    While node<br />
                        If WStrEqual(keymem, keylen, @node.key.mem, @node.key.count) Then<br />
                            If prev Then<br />
                                @prev.next = @node.next<br />
                            Else<br />
                                mArr(index) = @node.next<br />
                            End If<br />
                            node = Me.FreeNode(node)<br />
                            Decr MCount<br />
                            Exit Method<br />
                        End If<br />
                        node = @node.next<br />
                    Wend<br />
                End If<br />
            End Method<br />
<br />
            Method Start()<br />
                'reset cursor to start<br />
                mIndex = -1 : mNode = 0<br />
            End Method<br />
<br />
            Method Each() As Long<br />
                'move cursor to next key in hash table : true/false success<br />
                Register i As Long<br />
                If mCount Then<br />
                    If mNode And @mNode.next Then<br />
                        mNode = @mNode.next<br />
                        Method = 1 : Exit Method<br />
                    Else<br />
                        mNode = 0<br />
                        For i = mIndex + 1 To mCapacity - 1<br />
                            If mArr(i) Then<br />
                                mIndex = i<br />
                                mNode = mArr(i)<br />
                                Method = 1 : Exit Method<br />
                            End If<br />
                        Next i<br />
                    End If<br />
                End If<br />
                mIndex = -1 : mNode = 0<br />
            End Method<br />
<br />
            Method Key() As WString<br />
                'get key at cursor position : null if cursor invalid<br />
                If mNode Then Method = WStrGet(@mNode.key)<br />
            End Method<br />
<br />
            Method Value() As IUnknown<br />
                'get value at cursor position : null if cursor invalid<br />
                If mNode Then Method = Me.ObjFromLong(@mNode.value)<br />
            End Method<br />
<br />
            Method Store() As String<br />
                'store container as String<br />
                Local key, s As String<br />
                Local value As IUnknown<br />
                Local sb As StrBuildT<br />
                If mCount And mStore Then<br />
                    StrBuildPush sb, Mkl&#36;(mCount)<br />
                    Me.Start()<br />
                    While Me.Each()<br />
                        key = ChrToUtf8&#36;(Me.Key())<br />
                        value = Me.Value()<br />
                        If IsObject(value) Then<br />
                            Call Dword mStore Using ObjectStoreCallback(value) To s<br />
                            StrBuildPush sb, Mkl&#36;(Len(key))<br />
                            StrBuildPush sb, key<br />
                            StrBuildPush sb, Mkl&#36;(Len(s))<br />
                            StrBuildPush sb, s<br />
                        End If<br />
                    Wend<br />
                    Method = StrBuildPop(sb)<br />
                End If<br />
            End Method<br />
<br />
            Method Restore(ByRef stored As String)<br />
                'restore container from String<br />
                Register i As Long<br />
                Local items, bytes As Long<br />
                Local key, s As String<br />
                Local o As IUnknown<br />
                Local p As Long Ptr<br />
                Me.ClearMe()<br />
                If mRestore And Len(stored) Then<br />
                    p = StrPtr(stored)<br />
                    items = @p : Incr p<br />
                    For i = 1 To items<br />
                        bytes = @p : Incr p<br />
                        key = Peek&#36;(p, bytes) : p += bytes<br />
                        bytes = @p : Incr p<br />
                        s = Peek&#36;(p, bytes) : p += bytes<br />
                        Call Dword mRestore Using ObjectRestoreCallback(s) To o<br />
                        Me.Set(Utf8ToChr&#36;(key), o)<br />
                    Next i<br />
                End If<br />
            End Method<br />
<br />
            Method FileStore(ByVal file As WString)<br />
                'store container to File<br />
                If Len(file) And mStore Then Me.FilePut(file, Me.Store())<br />
            End Method<br />
<br />
            Method FileRestore(ByVal file As WString)<br />
                'restore container from File<br />
                If IsFile(file) And mRestore Then Me.Restore(Me.FileGet(file))<br />
            End Method<br />
<br />
        End Interface<br />
<br />
        Class Method ClearMe()<br />
            Register i As Long<br />
            Local node As OHashNodeT Ptr<br />
            For i = 0 To mCapacity - 1<br />
                While mArr(i)<br />
                    node = mArr(i)<br />
                    mArr(i) = @node.next<br />
                    Me.FreeNode(node)<br />
                Wend<br />
            Next i<br />
            mCount = 0<br />
        End Method<br />
<br />
        Class Method FreeNode(ByVal node As OHashNodeT Ptr) As Long<br />
            If node Then<br />
                WStrFinal @node.key<br />
                @node.value = Me.FreeObj(@node.value)<br />
                MemFree(node)<br />
            End If<br />
        End Method<br />
<br />
        Class Method LongFromObj(o As IUnknown) As Long<br />
            If IsObject(o) Then<br />
                o.AddRef()<br />
                Method = Peek(Long, VarPtr(o))<br />
            End If<br />
        End Method<br />
<br />
        Class Method ObjFromLong(ByVal h As Long) As IUnknown<br />
            Local o As IUnknown<br />
            If h Then<br />
                Poke Long, VarPtr(o), h<br />
                o.AddRef()<br />
                Method = o<br />
            End If<br />
        End Method<br />
<br />
        Class Method FreeObj(ByVal h As Long) As Long<br />
            Local o As IUnknown<br />
            If h Then<br />
                Poke Long, VarPtr(o), h<br />
                o = Nothing<br />
            End If<br />
        End Method<br />
<br />
<br />
        Class Method FilePut(ByVal file As WString, ByRef value As String)<br />
            Local f As Long<br />
            If Len(file) = 0 Then Exit Method<br />
            f = FreeFile<br />
            Open file For Binary As f<br />
            SetEof f<br />
            Put&#36; f, value<br />
            Close f<br />
        End Method<br />
<br />
        Class Method FileGet(ByVal file As WString) As String<br />
            Local f As Long, value As String<br />
            If IsFalse IsFile(file) Then Exit Method<br />
            f = FreeFile<br />
            Open file For Binary As f<br />
            Get&#36; f, Lof(f), value<br />
            Close f<br />
            Method = value<br />
        End Method<br />
<br />
    End Class<br />
#EndIf '%OHash250808<br />
<br />
#If Not %Def(%WStr250808)<br />
    %WStr250808 = 1<br />
    '----------------------------------------------------------------------<br />
    'WString Container = Word Array<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    %WrdSize = 2<br />
    Type WStrT<br />
        mem As Word Ptr<br />
        count As Long<br />
    End Type<br />
    '----------------------------------------------------------------------<br />
    Sub WStrFinal(t As WStrT)<br />
        'call before variable gores out of scope to free memory<br />
        WStrClear t<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Sub WStrClear(t As WStrT)<br />
        'empty container<br />
        t.mem = MemFree(t.mem)<br />
        t.count = 0<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function WStrCount(t As WStrT) As Long<br />
        'get character count<br />
        Function = t.count<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function WStrGet(t As WStrT) As WString<br />
        'get value<br />
        If t.mem Then Function = Peek&#36;&#36;(t.mem, t.count)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Macro WStrGetM(t) = Peek&#36;&#36;(t.mem, t.count)<br />
    '----------------------------------------------------------------------<br />
    Sub WStrSet(t As WStrT, ByRef value As WString)<br />
        'set value<br />
        Local characters As Long<br />
        t.mem = MemFree(t.mem)<br />
        t.count = 0<br />
        characters = Len(value)<br />
        If characters Then<br />
            t.mem = MemAlloc(characters * %WrdSize)<br />
            If t.mem Then<br />
                Memory Copy StrPtr(value), t.mem, characters * %WrdSize<br />
                t.count = characters<br />
            End If<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
#EndIf '%WStr250808<br />
<br />
#If Not %Def(%WStrHash250808)<br />
    %WStrHash250808 = 1<br />
    '----------------------------------------------------------------------<br />
    'WString Hash Index<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    Function WStrHashIndex(ByVal keymem As Word Ptr, ByVal keylen As Long, ByVal capacity As Long) As Long<br />
        'get key's hash index position<br />
        Register i As Long<br />
        Register total As Long<br />
        Local temp As Long<br />
        If keymem Then<br />
            For i = 0 To keylen - 1<br />
                temp += @keymem[i] + total<br />
                Shift Left total, 8<br />
                total += temp<br />
            Next i<br />
            Function = Abs(total Mod capacity)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
#EndIf '%WStrHash250808<br />
<br />
#If Not %Def(%WStrEqual250808)<br />
    %WStrEqual250808 = 1<br />
    '----------------------------------------------------------------------<br />
    Function WStrEqual(ByVal memA As Word Ptr, ByVal lenA As Long, ByVal memB As Word Ptr, ByVal lenB As Long) As Long<br />
        'true/false if two strings equal<br />
        Register i As Long<br />
        If memA And memB And lenA = lenB Then<br />
            For i = 0 To lenA - 1<br />
                If @memA[i] &lt;&gt; @memB[i] Then Exit Function<br />
            Next i<br />
            Function = 1<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
#EndIf '%WStrEqual250808<br />
<br />
#If Not %Def(%StrBuild250811)<br />
    %StrBuild250811 = 1<br />
    '----------------------------------------------------------------------<br />
    'String Builder<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    %BytSize = 1<br />
    %StrBuildBufferMax = 5000000<br />
    '----------------------------------------------------------------------<br />
    Type StrBuildT<br />
        mem As Long<br />
        count As Long<br />
        max As Long<br />
    End Type<br />
    '----------------------------------------------------------------------<br />
    Sub StrBuildPush(t As StrBuildT, ByVal value As String)<br />
        'append value<br />
        Local strlen, currentcount, buffer, newmax As Long<br />
        strlen = Len(value)<br />
        If strlen Then<br />
            If strlen &gt; t.max - t.count Then<br />
                currentcount = t.count : t.count = 0 : t.max = 0<br />
                buffer = Max&amp;(1, 2 * currentcount)<br />
                buffer = Min&amp;(buffer, %StrBuildBufferMax)<br />
                newmax = currentcount + buffer + strlen<br />
                t.mem = MemReAlloc(t.mem, newmax * %BytSize)<br />
                If t.mem = 0 Then Exit Sub<br />
                t.count = currentcount : t.max = newmax<br />
            End If<br />
            Memory Copy StrPtr(value), t.mem + (t.count * %BytSize), strlen * %BytSize<br />
            t.count += strlen<br />
        End If<br />
    End Sub<br />
    '----------------------------------------------------------------------<br />
    Function StrBuildPop(t As StrBuildT) As String<br />
        'get whole string : free memory<br />
        If t.mem And t.count Then Function = Peek&#36;(t.mem, t.count)<br />
        t.mem = MemFree(t.mem)<br />
        t.count = 0<br />
        t.max = 0<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
#EndIf '%StrBuild250811<br />
<br />
#If Not %Def(%Memory230925)<br />
    %Memory230925 = 1<br />
    '----------------------------------------------------------------------<br />
    'Memory Allocation<br />
    'Public domain, use at own risk. SDurham<br />
    '----------------------------------------------------------------------<br />
    Declare Function GlobalAlloc Lib "Kernel32.dll" Alias "GlobalAlloc" (ByVal uFlags As Dword, ByVal dwBytes As Dword) As Dword<br />
    Declare Function GlobalReAlloc Lib "Kernel32.dll" Alias "GlobalReAlloc" (ByVal hMem As Dword, ByVal dwBytes As Dword, ByVal uFlags As Dword) As Dword<br />
    Declare Function GlobalFree Lib "Kernel32.dll" Alias "GlobalFree" (ByVal hMem As Dword) As Dword<br />
    %MEMFIXED = &amp;H0000 : %MEMMOVEABLE = &amp;H0002 : %MEMZEROINIT = &amp;H0040 : %MEMGPTR = (%MEMZEROINIT Or %MEMFIXED)<br />
    '----------------------------------------------------------------------<br />
    Function MemAlloc(ByVal bytes As Long) As Long<br />
        'allocate memory<br />
        If bytes Then Function = GlobalAlloc(ByVal %MEMGPTR, ByVal bytes)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function MemReAlloc(ByVal hMem As Long, ByVal bytes As Long) As Long<br />
        'reallocate new size<br />
        If hMem And bytes Then<br />
            Function = GlobalReAlloc(ByVal hMem, ByVal bytes, ByVal %MEMMOVEABLE Or %MEMZEROINIT)<br />
        ElseIf bytes Then<br />
            Function = GlobalAlloc(ByVal %MEMGPTR, ByVal bytes)<br />
        ElseIf hMem Then<br />
            Function = GlobalFree(ByVal hMem)<br />
        End If<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
    Function MemFree(ByVal hMem As Long) As Long<br />
        'free memory<br />
        If hMem Then GlobalFree(ByVal hMem)<br />
    End Function<br />
    '----------------------------------------------------------------------<br />
#EndIf '%Memory230925</code></div></div><hr class="mycode_hr" />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'ObjectHash.bas<br />
#Option LargeMem32<br />
#Dim All<br />
#Compile Exe<br />
#Include Once "WIN32API.INC"<br />
#Include Once "..&#92;ObjectHash.inc"<br />
<br />
%TextBox = 101<br />
%BtnID = 102<br />
Global gDlg As Long<br />
Global gBuild As IStringBuilderW<br />
Sub SS(ByVal value As WString)<br />
    gBuild.Add(value + &#36;CrLf)<br />
End Sub<br />
<br />
Class TestC<br />
    Instance mX As Long<br />
    Interface TestI : Inherit IUnknown<br />
        Property Get X() As Long<br />
            Property = mX<br />
        End Property<br />
        Property Set X(ByVal value As Long)<br />
            mX = value<br />
        End Property<br />
        Property Get Str() As WString<br />
            Property = Format&#36;(mX)<br />
        End Property<br />
    End Interface<br />
End Class<br />
<br />
Function TestStore(o As TestI) As String<br />
    'store callback<br />
    'store object's data in a String and return the String<br />
    'when you implement the callback, replace IUnknown with the actual interface<br />
    If IsObject(o) Then Function = Mkl&#36;(o.X)<br />
End Function<br />
<br />
Function TestRestore(ByRef stored As String) As TestI<br />
    'restore callback<br />
    'restore object's data and return new instance<br />
    'when you implement the callback, replace IUnknown with the actual interface<br />
    Local o As TestI : o = Class "TestC"<br />
    o.X = Cvl(stored)<br />
    Function = o<br />
End Function<br />
<br />
Sub SampleCode()<br />
    Register i As Long<br />
    Local hash As OHashI : hash = Class "OHashC"<br />
    Local o As TestI : o = Class "TestC"<br />
    Local stored As String<br />
    Local a() As WString<br />
    Local d As Double<br />
    Local testcount As Long : testcount = 100000 'can change<br />
<br />
    Randomize<br />
    MousePtr 11<br />
    Control Set Text gDlg, %TextBox, ""<br />
<br />
    SS ""<br />
    SS "Property Set Capacity(ByVal value As Long)"<br />
    hash.capacity = testcount<br />
<br />
    SS ""<br />
    SS "Method Storage(ByVal storeCB As Long, ByVal restoreCB As Long)"<br />
    hash.Storage(CodePtr(TestStore), CodePtr(TestRestore))<br />
<br />
    SS ""<br />
    SS "Method Add(ByRef key As WString, value As IUnknown) As Long"<br />
    SS "add key/value items"<br />
    For i = 65 To 69<br />
        o = Class "TestC" : o.X = i<br />
        hash.Set(Chr&#36;&#36;(i), o)<br />
    Next i<br />
    hash.Start()<br />
    While hash.Each()<br />
        o = hash.Value()<br />
        If IsObject(o) Then SS "key = " + &#36;Dq + hash.Key() + &#36;Dq + " | value = " + o.Str<br />
    Wend<br />
<br />
    SS ""<br />
    SS "change value for key C"<br />
    o = Class "TestC" : o.X = 4444<br />
    hash.Set("C", o)<br />
    hash.Start()<br />
    While hash.Each()<br />
        o = hash.Value()<br />
        If IsObject(o) Then SS "key = " + &#36;Dq + hash.Key() + &#36;Dq + " | value = " + o.Str<br />
    Wend<br />
<br />
    SS ""<br />
    SS "Method Get(ByRef key As WString) As IUnknown"<br />
    SS "get object to change value for key C"<br />
    o = hash.Get("C") : If IsObject(o) Then o.X = 999999999<br />
    hash.Start()<br />
    While hash.Each()<br />
        o = hash.Value()<br />
        If IsObject(o) Then SS "key = " + &#36;Dq + hash.Key() + &#36;Dq + " | value = " + o.Str<br />
    Wend<br />
<br />
    SS ""<br />
    SS "Method Contains(ByRef key As WString)"<br />
    SS "hash table contains key B = " + Format&#36;(hash.Contains("B"))<br />
    SS "hash table contains key E = " + Format&#36;(hash.Contains("E"))<br />
    SS "hash table contains key ZZZ = " + Format&#36;(hash.Contains("ZZZ"))<br />
<br />
    SS ""<br />
    SS "Method Delete(ByRef key As WString)"<br />
    SS "delete key C"<br />
    hash.Delete("C")<br />
    hash.Start()<br />
    While hash.Each()<br />
        o = hash.Value()<br />
        If IsObject(o) Then SS "key = " + &#36;Dq + hash.Key() + &#36;Dq + " | value = " + o.Str<br />
    Wend<br />
<br />
    SS ""<br />
    SS "Method Store() As String"<br />
    stored = hash.Store()<br />
    SS "Method Restore(ByRef stored As String)"<br />
    hash.Restore(stored)<br />
    SS "Method FileStore(ByVal file As String)"<br />
    hash.FileStore("Stored.hash")<br />
    SS "Method FileRestore(ByVal file As String)"<br />
    hash.FileRestore("Stored.hash")<br />
    Kill "Stored.hash"<br />
    hash.Start()<br />
    While hash.Each()<br />
        o = hash.Value()<br />
        If IsObject(o) Then SS "key = " + &#36;Dq + hash.Key() + &#36;Dq + " | value = " + o.Str<br />
    Wend<br />
<br />
    ReDim a(1 To testcount)<br />
    For i = 1 To testcount<br />
        a(i) = RandomString()<br />
    Next i<br />
<br />
    SS ""<br />
    SS "add "+Format&#36;(testcount,"#,")+" random key/value items"<br />
    hash.Clear()<br />
    d = Timer<br />
    For i = 1 To testcount<br />
        o = Class "TestC" : o.X = i<br />
        hash.Set(a(i), o)<br />
    Next i<br />
    SS "Time = " + Format&#36;(Timer - d, "000.000")<br />
    SS "Count = " + Format&#36;(hash.Count, "#,") + " duplicate keys not allowed"<br />
<br />
    SS ""<br />
    SS "find "+Format&#36;(testcount,"#,")+" random keys"<br />
    d = Timer<br />
    For i = 1 To testcount<br />
        If hash.Contains(a(i)) = 0 Then<br />
            ? "hash table fail" : Exit For<br />
        End If<br />
    Next i<br />
    SS "Time = " + Format&#36;(Timer - d, "000.000")<br />
<br />
    SS ""<br />
    SS ""<br />
    Control Set Text gDlg, %TextBox, gBuild.String<br />
    gBuild.Clear<br />
    MousePtr 1<br />
End Sub<br />
<br />
Function RandomString() As String<br />
    Register i As Long<br />
    Local s As String<br />
    For i = 1 To Rnd(5, 8)<br />
        Select Case As Const Rnd(1, 2)<br />
            Case 1 : s += Chr&#36;(Rnd(65, 90))<br />
            Case 2 : s += Chr&#36;(Rnd(97, 122))<br />
        End Select<br />
    Next i<br />
    Function = s<br />
End Function<br />
<br />
Function PBMain()<br />
    gBuild = Class "StringBuilderW"<br />
    Dialog Default Font "courier new", 12, 0, 0<br />
    Dialog New 0, "Sample Code", 0, 0, 0, 0, %WS_Caption Or %WS_ClipSiblings Or %WS_MinimizeBox Or %WS_SysMenu Or %WS_ThickFrame, %WS_Ex_AppWindow To gDlg<br />
    Control Add TextBox, gDlg, %TextBox, "", 0, 0, 0, 0, %ES_AutoHScroll Or %ES_AutoVScroll Or %ES_MultiLine Or %ES_NoHideSel Or %ES_WantReturn Or %WS_HScroll Or %WS_VScroll Or %WS_Border, 0<br />
    Control Add Button,  gDlg, %BtnID, "Run", 275, 220, 60, 15, %BS_Flat, 0<br />
    SendMessageW(GetDlgItem(gDlg, %TextBox), %EM_SETLIMITTEXT, 4000000, 0)<br />
    Dialog Show Modeless gDlg, Call DlgCB<br />
    While IsWin(gDlg)<br />
        Dialog DoEvents<br />
    Wend<br />
End Function<br />
<br />
CallBack Function DlgCB()<br />
    Select Case As Long Cb.Msg<br />
        Case %WM_InitDialog<br />
            WM_InitDialog()<br />
        Case %WM_Size<br />
            WM_Size()<br />
        Case %WM_Command<br />
            Select Case As Long Cb.Ctl<br />
                Case %BtnID : If Cb.CtlMsg = %BN_Clicked Then SampleCode()<br />
            End Select<br />
    End Select<br />
End Function<br />
<br />
Sub WM_InitDialog()<br />
    Local clientW, clientH As Long<br />
    Desktop Get Client To clientW, clientH<br />
    clientW /= 7<br />
    clientH /= 4<br />
    Dialog Set Loc gDlg, clientW / 2, clientH / 7<br />
    Dialog Set Size gDlg, clientW, clientH<br />
End Sub<br />
<br />
Sub WM_Size()<br />
    Local clientW, clientH As Long<br />
    Local marg As Long<br />
    Local buttonW, buttonH As Long<br />
    Local txtWidth, txtHeight As Long<br />
    Local fromLeft, fromBottom As Long<br />
    Dialog Get Client gDlg To clientW, clientH<br />
    marg = 1 : buttonW = 30 : buttonH = 10<br />
    fromLeft = clientW - marg - marg - buttonW<br />
    fromBottom = clientH - marg - marg - buttonH<br />
    Control Set Size gDlg, %BtnID, buttonW, buttonH<br />
    Control Set Loc gDlg, %BtnID, fromLeft, fromBottom<br />
    txtWidth = clientW - marg - marg<br />
    txtHeight = clientH - marg - buttonH - marg - marg<br />
    Control Set Size gDlg, %TextBox, txtWidth, txtHeight<br />
    Control Set Loc gDlg, %TextBox, marg, marg<br />
End Sub</code></div></div><hr class="mycode_hr" />
<span style="font-weight: bold;" class="mycode_b">comments</span> <br />
<a href="http://pump.richheimer.de/showthread.php?tid=66" target="_blank" rel="noopener" class="mycode_url">Very Fast WString /Object Hash Table (Comments)</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[gbNotes]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=63</link>
			<pubDate>Wed, 03 Sep 2025 19:54:29 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=94">Gary Beene</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=63</guid>
			<description><![CDATA[A while back I asked about CRM app recommendations. Not having found something as simple as I wanted, I wrote my own and have been using it the last couple of months. <br />
<br />
I still have a list of changes I want to make to it, but since I've been using for a while now, I guess it's worth offering up to anyone who wants it.<br />
<br />
Discussion:  <br />
Source Code:  <a href="https://garybeene.com/files/gbnotes.zip" target="_blank" rel="noopener" class="mycode_url">https://garybeene.com/files/gbnotes.zip</a><br />
<br />
<img src="https://garybeene.com/images/gbnotes.png" loading="lazy"  alt="[Image: gbnotes.png]" class="mycode_img" /><br />
<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'Compilable Example:  Requires JOSE includes.<br />
#Compile Exe  "gbnotes.exe"<br />
#Dim All<br />
<br />
#Debug Error On<br />
#Debug Display On<br />
<br />
%Unicode = 1<br />
#Include "Win32API.inc"<br />
<br />
&#36;Ver = "1.0"<br />
&#36;Delim = "++++"<br />
<br />
#Resource Icon xlogo, "icons&#92;n.ico"<br />
#Resource Icon xsave, "icons&#92;save.ico"<br />
#Resource Icon xnew, "icons&#92;new.ico"<br />
#Resource Icon xdelete, "icons&#92;delete.ico"<br />
#Resource Icon xsearch, "icons&#92;search.ico"<br />
#Resource Icon xall, "icons&#92;all.ico"<br />
#Resource Icon xsaveall, "icons&#92;saveall.ico"<br />
#Resource Icon xplus, "icons&#92;listplus.ico"<br />
#Resource Icon xup, "icons&#92;searchup.ico"<br />
#Resource Icon xdown, "icons&#92;searchdown.ico"<br />
#Resource Icon xexit, "icons&#92;exit.ico"<br />
<br />
%MultiLineREStyle_Wrap    = %WS_Child Or %WS_ClipSiblings Or %WS_Visible Or %ES_MultiLine Or %WS_VScroll Or _<br />
                            %ES_AutoVScroll Or %ES_WantReturn Or %ES_NoHideSel Or %WS_TabStop Or %ES_SaveSel<br />
<br />
Enum Equates Singular<br />
   IDC_ComboBox = 500<br />
   IDC_ListView<br />
   IDC_RichEdit<br />
   IDC_Toolbar<br />
   IDC_StatusBar<br />
   IDT_SaveCFN<br />
   IDT_New<br />
   IDT_Delete<br />
   IDT_Search<br />
   IDT_InsertDelimiter<br />
   IDT_ShowAll<br />
   IDT_AllContacts<br />
   IDT_SaveAll<br />
'   IDT_Sort<br />
   IDT_Exit<br />
   IDT_Mute<br />
   IDT_InsertDate<br />
   IDT_SetFocusRE<br />
   IDT_SearchUP<br />
   IDT_SearchDown<br />
   IDT_NextREDown<br />
   IDT_PrevREUp<br />
<br />
   IDC_InputTextBox<br />
   IDC_InputOk<br />
   IDC_InputCancel<br />
<br />
End Enum<br />
<br />
Global hDlg, hRichEdit, hListView, hComboBox, hList, hDlgI As Dword<br />
Global D() As String, REText&#36;, SearchTerm&#36;, SearchTerms&#36;<br />
Global RESearchTerm As WStringZ * 100<br />
Global SearchDirection, BackupNumber, CFN, iSkip, Maximized, Minimized, Mute As Long<br />
<br />
Function PBMain() As Long<br />
<br />
   Dialog Default Font "Arial Black", 16,1<br />
   Dialog New Pixels, 0, "gbNotes " + &#36;Ver,,,800,800, %WS_OverlappedWindow To hDlg<br />
   Dialog Set Icon hDlg, "xlogo"<br />
<br />
   Control Add ComboBox, hDlg, %IDC_ComboBox,, 10,10,400,200, %CBS_Simple Or %CBS_NoIntegralHeight Or %WS_VScroll Or %WS_TabStop, %WS_Ex_ClientEdge<br />
   Control Handle hDlg, %IDC_ComboBox To hComboBox<br />
<br />
   Control Add ListView, hDlg, %IDC_ListView, "",10,40,400,400, %WS_TabStop Or %LVS_Report Or %LVS_ShowSelAlways Or %LVS_SingleSel, %WS_Ex_ClientEdge<br />
   ListView Insert Column hDlg, %IDC_ListView, 1,"All Contacts",400,0<br />
   ListView Insert Column hDlg, %IDC_ListView, 2,"Position",150,0<br />
   Control Handle hDlg, %IDC_ListView To hListView<br />
<br />
   LoadLibrary("msftedit.dll")<br />
   Control Add "RichEdit50W", hDlg, %IDC_RichEdit, "", 410,10,400,400, %MultiLineREStyle_Wrap, %WS_Ex_ClientEdge<br />
   Control Handle hDlg, %IDC_RichEdit To hRichEdit<br />
   SendMessage hDlg, %EM_SetEventMask, 0, %ENM_SelChange<br />
   SendMessage hRichEdit, %EM_SetEventMask, 0, %ENM_SelChange Or %ENM_Link<br />
   SendMessage hRichEdit, %EM_AUTOURLDETECT, %True, 0<br />
<br />
   Control Add Toolbar, hDlg, %IDC_Toolbar, "", 0,0,0,0,%CCS_NoMoveY<br />
   ImageList New Icon 32,32,32,10 To hList<br />
   ImageList Add Icon hList, "xsearch"  '1<br />
   ImageList Add Icon hList, "xsave"    '2<br />
   ImageList Add Icon hList, "xnew"     '3<br />
   ImageList Add Icon hList, "xdelete"  '4<br />
   ImageList Add Icon hList, "xall"     '5<br />
   ImageList Add Icon hList, "xsaveall" '6<br />
   ImageList Add Icon hList, "xplus"    '7<br />
   ImageList Add Icon hList, "xexit"    '8<br />
   ImageList Add Icon hList, "xup"      '9<br />
   ImageList Add Icon hList, "xdown"    '10<br />
<br />
   Toolbar Set ImageList hDlg, %IDC_Toolbar, hList, 0<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 8, %IDT_Exit, %TbStyle_Button, "Exit"<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 5, %IDT_AllContacts, %TbStyle_Button, "All"<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 2, %IDT_SaveCFN, %TbStyle_Button, "Save"<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 3, %IDT_New, %TbStyle_Button, "New"<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 4, %IDT_Delete, %TbStyle_Button, "Delete"<br />
<br />
   Toolbar Add Separator hDlg, %IDC_Toolbar, 50<br />
<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 5, %IDT_ShowAll, %TbStyle_Button, "Show All"<br />
   Toolbar Add Separator hDlg, %IDC_Toolbar, 10<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 6, %IDT_SaveAll, %TbStyle_Button, "Save All"<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 10, %IDT_SearchUp, %TbStyle_Button, "Up"<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 9, %IDT_SearchDown, %TbStyle_Button, "Down"<br />
<br />
   Control Add Statusbar, hDlg, %IDC_StatusBar, "Welcome to gbNotes!", 0,0,0,90<br />
<br />
   Dialog Show Modal hDlg Call DlgProc<br />
End Function<br />
<br />
CallBack Function DlgProc() As Long<br />
   Local pNMLV As NMListView Ptr, iRow As Long, temp&#36;<br />
   Select Case Cb.Msg<br />
      Case %WM_InitDialog<br />
         If IsFalse IsFolder("backup") Then MkDir "backup"<br />
         Settings_INI "get"<br />
         BuildAcceleratorTable<br />
<br />
         LoadCRMFile       'create D() from CRM.txt<br />
         DisplayCRMArray   'put D() into LV<br />
<br />
         LoadComboBox<br />
         Control Set Text hDlg, %IDC_ComboBox, SearchTerm&#36;<br />
<br />
         If Maximized Then Dialog Show State hDlg, %SW_Maximize  'restore Maximized state if necessary<br />
         If Minimized Then Dialog Show State hDlg, %SW_Minimize       'restore Maximized state if necessary<br />
<br />
         Control Set Text hDlg, %IDC_RichEdit, D(CFN)<br />
         ListView Select hDlg, %IDC_ListView, CFN<br />
         ListView_SetItemState hListView, CFN-1, %LVIS_Focused, %LVIS_Focused  '&lt;--- keyboard synchronizing code<br />
         ListView Visible hDlg, %IDC_ListView, CFN<br />
<br />
      Case %WM_Size : Resize<br />
<br />
      Case %WM_Command<br />
         Select Case Cb.Ctl<br />
'            Case %IdCancel : Dialog End hDlg<br />
            Case %IdOk 'Enter Key<br />
               If GetParent(GetFocus) = hComboBox Then<br />
                  sBeep<br />
                  Control Get Text hDlg, %IDC_ComboBox To SearchTerm&#36;<br />
                  Search<br />
                  AddSearchTerm<br />
               End If<br />
            Case %IDC_ComboBox<br />
               Select Case Cb.CtlMsg<br />
                  Case %CBN_SelChange<br />
                     'get post-selection text<br />
                     Control Send hDlg, %IDC_ComboBox, %CB_GetCurSel, 0, 0 To iRow<br />
                     ComboBox Get Text hDlg, %IDC_ComboBox, iRow+1 To SearchTerm&#36;<br />
                     Dialog Set Text hDlg, "SearchTerm = " + SearchTerm&#36;  + "   " + Time&#36;<br />
                     Search<br />
               End Select<br />
            Case %IDT_Exit       : sBeep : Dialog End hDlg<br />
            Case %IDT_SaveCFN    : sBeep : SaveCFN          : ST Trim&#36;(Parse&#36;(D(CFN),&#36;CrLf,1)) + " saved"<br />
            Case %IDT_New        : NewCFN                   : ST "New Contact"<br />
            Case %IDT_Delete     : sBeep : DeleteCFN        : ST ""<br />
            Case %IDT_Search     : sBeep : Control Get Text hDlg, %IDC_ComboBox To SearchTerm&#36; : Search  'ST<br />
            Case %IDT_InsertDate : sBeep : InsertDate       : ST ""<br />
            Case %IDT_InsertDelimiter : sBeep : InsertDelimiter : ST ""<br />
            Case %IDT_ShowAll    : sBeep : ShowAll          : ST "All CRM.txt Loaded in Editor"<br />
            Case %IDT_SaveAll    : sBeep : SaveAll          : ST "Editor Content saved to crm.txt"<br />
'            Case %IDT_Sort       : sBeep : SortData<br />
            Case %IDT_Mute       : WinBeep(275,150) : Mute Xor=1 : ST "Mute is " + IIf&#36;(Mute,"ON","OFF")<br />
            Case %IDT_SetFocusRE : SetFocus hRichEdit<br />
            Case %IDT_SearchDown     : SearchDirection = 1 : FindRE : ST ""<br />
            Case %IDT_SearchUp       : SearchDirection = 0 : FindRE<br />
            Case %IDT_NextREDown     : sBeep : SearchDirection = 1 :    SearchText RESearchTerm, %FR_Down<br />
            Case %IDT_AllContacts    : DisplayCRMArray<br />
<br />
         End Select<br />
      Case %WM_Notify<br />
         Select Case Cb.NmId<br />
            Case %IDC_RichEdit<br />
               Select Case Cb.NmCode<br />
                  Case %EN_Link : OpenLink(Cb.LParam)<br />
               End Select<br />
            Case %IDC_ListView<br />
               Select Case Cb.NmCode<br />
                  Case %LVN_ItemChanged  '%NM_Click<br />
                     pNMLV = Cb.LParam<br />
                     If (@pNMLV.uChanged And %LVIF_STATE) = %LVIF_STATE Then  ' if state has changed<br />
                         If (@pNMLV.unewstate And %LVIS_SELECTED) = %LVIS_SELECTED Then<br />
                            ListView Get Select hDlg, %IDC_ListView To iRow<br />
                            ListView Get Text hDlg, %IDC_Listview, iRow, 2 To temp&#36;<br />
                            CFN = Val(temp&#36;)<br />
                            Control Set Text hDlg, %IDC_RichEdit, D(CFN)<br />
                            sBeep<br />
                         End If<br />
                     End If<br />
               End Select<br />
         End Select<br />
      Case %WM_Destroy<br />
         SaveBackup<br />
         Settings_INI "save"<br />
<br />
   End Select<br />
End Function<br />
<br />
Sub Resize<br />
   Local x,y,w,h,wc,hc,wt,ht As Long<br />
   Control Get Size hDlg, %IDC_Toolbar To wt,ht<br />
   Dialog Get Client hDlg To w,h<br />
<br />
   Control Set Loc hDlg, %IDC_ComboBox, 10,ht+10<br />
   Control Get Size hDlg, %IDC_ComboBox To wc,hc<br />
<br />
   Control Set Loc hDlg, %IDC_ListView, 10,ht+hc+20<br />
   Control Set Size hDlg, %IDC_ListView, 400, h-ht-hc-30-40<br />
<br />
   Control Set Loc hDlg, %IDC_RichEdit, wc+15,ht+10<br />
   Control Set Size hDlg, %IDC_RichEdit, w-wc-25,h-ht-30-30<br />
<br />
End Sub<br />
<br />
Function OpenLink(ByVal lpLink As Dword) As Long<br />
   Local enlinkPtr As ENLINK Ptr, linkText As String, iReturn As Long<br />
   Control Get Text hDlg, %IDC_RichEdit To REText&#36;<br />
   enlinkPtr  = lpLink<br />
   If @enLinkPtr.Msg = %WM_LButtonUp Then<br />
      LinkText = Mid&#36;(REText&#36;,@enLinkPtr.chrg.cpMin+1 To @enLinkPtr.chrg.cpMax)<br />
      LinkText = Remove&#36;(LinkText, Any &#36;Cr+&#36;Lf+&#36;Spc)<br />
      iReturn  = ShellExecute(hDlg, "Open", (LinkText), &#36;Nul, &#36;Nul, %SW_ShowNormal)<br />
   End If<br />
End Function<br />
<br />
Sub LoadComboBox<br />
   Local temp&#36;, i As Long<br />
   Open "searchterms.txt" For Binary As #1 : Get&#36; #1, Lof(1), SearchTerms&#36; : Close #1<br />
   If Len(SearchTerms&#36;) = 0 Then SearchTerms&#36; = "Texas" + &#36;CrLf + "Papua New Guinea"<br />
   SearchTerms&#36; = Trim&#36;(SearchTerms&#36;, Any &#36;CrLf + &#36;Spc)<br />
   SearchTerm&#36; = Parse&#36;(SearchTerms&#36;,&#36;CrLf,1)<br />
<br />
   ComboBox Reset hDlg, %IDC_ComboBox<br />
   Control Set Text hDlg, %IDC_ComboBox, SearchTerm&#36;<br />
<br />
   For i = 1 To Min(5,ParseCount(searchterms&#36;,&#36;CrLf))<br />
      ComboBox Insert hDlg, %IDC_ComboBox, i, Parse&#36;(SearchTerms&#36;,&#36;CrLf,i)<br />
   Next i<br />
End Sub<br />
<br />
Sub LoadCRMFile<br />
   Local tmp&#36;, temp&#36;, i,iCount As Long<br />
   Open "crm.txt" For Binary As #1 : Get&#36; #1, Lof(1), temp&#36; : Close #1<br />
   If Len(Trim&#36;(temp&#36;)) = 0 Then<br />
      temp&#36; = "Gary Beene" + &#36;CrLf + "Dallas, Texas" + &#36;CrLf + &#36;Delim + &#36;CrLf + "Stuart McLachlan" + &#36;CrLf + "Papua New Guinea"<br />
      Open "crm.txt" For Output As #1 : Print #1, temp&#36;; : Close #1<br />
   End If<br />
   iCount = ParseCount(temp&#36;, &#36;Delim)<br />
   ReDim D(1 To iCount)<br />
   Parse temp&#36;, D(), &#36;Delim<br />
<br />
   For i = 1 To iCount<br />
      D(i) = Trim&#36;(D(i), Any &#36;CrLf + &#36;Spc)<br />
      tmp&#36; = Parse&#36;(D(i),&#36;CrLf,1)<br />
      If Trim&#36;(tmp&#36;) = "" Then ? "Data Issue:" + &#36;CrLf + D(i-1)<br />
   Next i<br />
<br />
   ST "Records: " + Format&#36;(iCount,"##,###")<br />
End Sub<br />
<br />
Sub DisplayCRMArray<br />
   Local temp&#36;, i As Long<br />
   ListView Reset hDlg, %IDC_ListView<br />
   For i = 1 To UBound(D)<br />
      D(i) = Trim&#36;(D(i))<br />
      ListView Insert Item hDlg, %IDC_ListView, i,0,Parse&#36;(D(i),&#36;CrLf,1)<br />
      ListView Set Text hDlg, %IDC_ListView, i,2,Format&#36;(i)<br />
   Next i<br />
End Sub<br />
<br />
Sub Search<br />
   Local temp&#36;, i,iCount As Long<br />
<br />
   ListView Reset hDlg, %IDC_ListView<br />
   For i = 1 To UBound(D)<br />
      If InStr(LCase&#36;(D(i)), LCase&#36;(SearchTerm&#36;)) Then<br />
         Incr iCount<br />
         ListView Insert Item hDlg, %IDC_ListView, iCount, 0, Parse&#36;(D(i),&#36;CrLf,1)<br />
         ListView Set Text hDlg, %IDC_ListView, iCount, 2, Format&#36;(i)<br />
      End If<br />
   Next i<br />
<br />
   ListView Select hDlg, %IDC_ListView, 1<br />
   ListView_SetItemState hListView, 0, %LVIS_Focused, %LVIS_Focused  '&lt;--- keyboard synchronizing code<br />
   ListView Set Header hDlg, %IDC_ListView, 1, "Matches: " + Format&#36;(iCount) + "   " + &#36;Dq + SearchTerm&#36; + &#36;Dq<br />
End Sub<br />
<br />
Sub Settings_INI(Task&#36;)<br />
   Local x,y,w,h, tempz, INIFileName As WStringZ * %Max_Path, WinPla As WindowPlacement<br />
<br />
   'set ini filename<br />
   INIFileName = Exe.Path&#36; + Exe.Name&#36; + ".ini"    'get INI file name<br />
<br />
   Select Case Task&#36;<br />
      Case "get"<br />
         'get dialog width/height from INI file and use to set Dialog size<br />
         GetPrivateProfileString "All", "Width", "800", w, %Max_Path, INIFileName<br />
         GetPrivateProfileString "All", "Height", "800", h, %Max_Path, INIFileName<br />
         Dialog Set Size hDlg,Val(w), Val(h)   'width/height<br />
<br />
         'get dialog top/left from INI file and use to set Dialog location<br />
         Getprivateprofilestring "All", "Left", "0", x, %Max_Path, INIFileName<br />
         Getprivateprofilestring "All", "Top", "0",  y,  %Max_Path, INIFileName<br />
         If IsFile(INIFileName) Then Dialog Set Loc hDlg, Val(x), Val(y)   'left/top but only once INIFileName exists<br />
<br />
         'get value for string variables<br />
         GetPrivateProfileString "All", "RESearchTerm", "TWC", RESearchTerm, %Max_Path, INIFileName<br />
<br />
         'get value for numeric variables<br />
         Getprivateprofilestring "All", "Minimized", "0", tempz, %Max_Path, INIFileName   : Minimized = Val(tempz)<br />
         Getprivateprofilestring "All", "Maximized", "0", tempz, %Max_Path, INIFileName   : Maximized = Val(tempz)<br />
         Getprivateprofilestring "All", "CFN", "1",       tempz, %Max_Path, INIFileName   : CFN = Val(tempz)<br />
         Getprivateprofilestring "All", "Mute", "0",      tempz, %Max_Path, INIFileName   : Mute = Val(tempz)<br />
         Getprivateprofilestring "All", "BackupNumber", "0",      tempz, %Max_Path, INIFileName   : BackupNumber = Val(tempz)<br />
<br />
      Case "save"<br />
         If IsFile(INIFileName) Then Kill INIFileName    'clear the INI file Name to remove residual entries before saving<br />
         WinPla.Length = SizeOf(WinPla)<br />
         GetWindowPlacement hDlg, WinPla<br />
         WritePrivateProfileString "All", "Left", Str&#36;(WinPla.rcNormalPosition.nLeft), INIFileName<br />
         WritePrivateProfileString "All", "Top", Str&#36;(WinPla.rcNormalPosition.nTop), INIFileName<br />
         WritePrivateProfileString "All", "Width", Str&#36;(WinPla.rcNormalPosition.nRight - WinPla.rcNormalPosition.nLeft), INIFileName<br />
         WritePrivateProfileString "All", "Height", Str&#36;(WinPla.rcNormalPosition.nBottom - WinPla.rcNormalPosition.nTop), INIFileName<br />
<br />
         'save string variables<br />
         WritePrivateProfileString "All", "RESearchTerm", RESearchTerm, INIFileName<br />
<br />
         'save numeric variables<br />
         Minimized = IsIconic(hDlg)<br />
         Maximized = IsZoomed(hDlg)<br />
         WritePrivateProfileString "All", "Minimized", Str&#36;(Minimized), INIFileName<br />
         WritePrivateProfileString "All", "Maximized", Str&#36;(Maximized), INIFileName<br />
         WritePrivateProfileString "All", "CFN", Str&#36;(CFN), INIFileName<br />
         WritePrivateProfileString "All", "Mute", Str&#36;(Mute), INIFileName<br />
         WritePrivateProfileString "All", "BackupNumber", Str&#36;(BackupNumber), INIFileName<br />
<br />
   End Select<br />
End Sub<br />
<br />
Sub SaveCFN<br />
   Local temp&#36;<br />
   Control Get Text hDlg, %IDC_RichEdit To temp&#36;<br />
<br />
   If InStr(temp&#36;, &#36;Delim) Then ? "Cannot Save!" : Exit Sub<br />
<br />
   D(CFN) = temp&#36;<br />
   Open "crm.txt" For Output As #1 : Print #1, Join&#36;(D(), &#36;CrLf + &#36;Delim + &#36;CrLf); : Close #1<br />
   ListView Set Text hDlg, %IDC_ListView, CFN, 1, Parse&#36;(D(CFN),&#36;CrLf,1)<br />
   SetFocus hRichEdit<br />
End Sub<br />
<br />
Sub ShowAll<br />
   Control Set Text hDlg, %IDC_RichEdit, Join&#36;(D(),&#36;CrLf + &#36;Delim + &#36;CrLf)<br />
   SendMessage hRichEdit, %EM_SetSel, 0, 0<br />
   SetFocus hRichEdit<br />
End Sub<br />
<br />
Sub SaveAll<br />
   Local temp&#36;<br />
   If MsgBox("Update All CRM Data?", %MB_YesNo Or %MB_IconQuestion Or %MB_TaskModal, "Update CRM") = %IdYes Then<br />
      Control Get Text hDlg, %IDC_RichEdit To temp&#36;<br />
      temp&#36; = Trim&#36;(temp&#36;, Any &#36;CrLf + " +")<br />
      Open "crm.txt" For Output As #1<br />
      Print #1, temp&#36;;<br />
      Close #1<br />
<br />
      LoadCRMFile       'create D() from CRM.txt<br />
      DisplayCRMArray   'put D() into LV<br />
<br />
   End If<br />
End Sub<br />
<br />
Sub sBeep<br />
   If Mute Then Exit Sub<br />
   WinBeep(275,150)<br />
End Sub<br />
<br />
Sub BuildAcceleratorTable<br />
   Local ac() As ACCELAPI, hAccelerator As Dword, c As Long  ' for keyboard accelator table values<br />
   Dim ac(10)<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_Delete : ac(c).cmd = %IDT_Delete          : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_D : ac(c).cmd = %IDT_InsertDate        : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_E : ac(c).cmd = %IDT_ShowAll           : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_F : ac(c).cmd = %IDT_SearchDown        : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_I : ac(c).cmd = %IDT_InsertDelimiter   : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_R : ac(c).cmd = %IDT_SetFocusRE        : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_M : ac(c).cmd = %IDT_Mute              : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_N : ac(c).cmd = %IDT_NextREDown        : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_S : ac(c).cmd = %IDT_SaveCFN           : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL Or %FSHIFT : ac(c).key = %VK_F : ac(c).cmd = %IDT_SearchUp : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL Or %FSHIFT : ac(c).key = %VK_S : ac(c).cmd = %IDT_SaveAll  : Incr c<br />
   Accel Attach hDlg, AC() To hAccelerator<br />
End Sub<br />
<br />
Sub InsertDelimiter<br />
   Clipboard Reset<br />
   Clipboard Set Text &#36;CrLf + &#36;Delim + &#36;CrLf + &#36;CrLf<br />
   SendMessage hRichEdit, %WM_Paste, 0, 0<br />
End Sub<br />
<br />
Sub InsertDate<br />
   Clipboard Reset<br />
   Clipboard Set Text &#36;CrLf + "Discussion: " + Date&#36; + "  " + Time&#36; + "  ---------" + &#36;CrLf + &#36;CrLf<br />
   SendMessage hRichEdit, %WM_Paste, 0, 0<br />
End Sub<br />
<br />
'Sub SortData<br />
'   Array Sort D(1)<br />
'   Open "crm.txt" For Output As #1 : Print #1, Join&#36;(D(), &#36;CrLf + &#36;Delim + &#36;CrLf); : Close #1<br />
'   LoadCRMFile       'create D() from CRM.txt<br />
'   DisplayCRMArray   'put D() into LV<br />
'End Sub<br />
<br />
Sub AddSearchTerm<br />
   Local i As Long<br />
<br />
   'If SearchTerm alread in use, do nothing<br />
   For i = 1 To Min(5,ParseCount(SearchTerms&#36;,&#36;CrLf))<br />
      If LCase&#36;(Parse&#36;(SearchTerms&#36;,&#36;CrLf,i)) = LCase&#36;(SearchTerm&#36;) Then Exit Sub<br />
   Next i<br />
<br />
   'If new search term, add it top of the list of SearchTerms&#36;<br />
   SearchTerms&#36; = SearchTerm&#36; + &#36;CrLf + SearchTerms&#36;<br />
   SearchTerms&#36; = Trim&#36;(SearchTerms&#36;, Any &#36;CrLf + &#36;Spc)<br />
   Open "searchterms.txt" For Output As #1 : Print #1, SearchTerms&#36;; : Close #1<br />
<br />
   LoadComboBox<br />
End Sub<br />
<br />
Sub SaveBackup<br />
   Local temp&#36;<br />
   temp&#36; = Time&#36;<br />
   Replace  ":" With "." In temp&#36;<br />
   Incr BackupNumber<br />
   Open "backup&#92;crm_" + Format&#36;(BackupNumber) + "_" + Date&#36; + "_" + temp&#36; + ".txt" For Output As #1<br />
   Print #1, Join&#36;(D(), &#36;CrLf + &#36;Delim + &#36;CrLf); : Close #1<br />
End Sub<br />
<br />
Sub FindRE<br />
   GetRESearchTerm<br />
   SearchText RESearchTerm, %FR_Down<br />
End Sub<br />
<br />
Function SearchText(ByVal sTextToSearchFor As WString, SearchDirection As Long) As Long<br />
 Local  FindTextText         As FINDTEXTEX<br />
 Local  NextMatch, SelStart, SelEnd As Long<br />
<br />
 SendMessage(hRichEdit, %EM_GETSEL, VarPtr(SelStart), VarPtr(SelEnd))<br />
 FindTextText.lpStrText = StrPtr(sTextToSearchFor)<br />
<br />
 If SearchDirection = 1 Then<br />
   FindTextText.chrg.cpMin = SelEnd + 1 'Search from current position<br />
   FindTextText.chrg.cpMax = -1         '- till the end<br />
 Else '%FR_UP<br />
   FindTextText.chrg.cpMin = SelStart   'Search from current position<br />
   FindTextText.chrg.cpMax = 0          '- up to the start<br />
 End If<br />
<br />
 NextMatch = SendMessage(hRichEdit, %EM_FINDTEXTEX, SearchDirection, VarPtr(FindTextText)) 'Return next match or -1 for no more<br />
<br />
 If NextMatch = -1 Then WinBeep(250,300) :Exit Function<br />
 SendMessage(hRichEdit, %EM_SETSEL, FindTextText.chrgText.cpMin, FindTextText.chrgText.cpMax)<br />
<br />
End Function<br />
<br />
Sub GetRESearchTerm<br />
   Dialog New Pixels, 0, "Search RE",,,200,80, %WS_SysMenu To hDlgI<br />
   Dialog Set Icon hDlgI, "xsearch"<br />
   Control Add TextBox, hDlgI, %IDC_InputTextBox,RESearchTerm, 20,10,160,30<br />
   Control Add Button, hDlgI, %IDC_InputOk, "Ok", 20,50,50,25<br />
   Control Add Button, hDlgI, %IDC_InputCancel, "Cancel", 90,50,90,25<br />
   RESearchTerm = ""<br />
   Dialog Show Modal hdlgI Call hDlgIProc<br />
End Sub<br />
<br />
CallBack Function hDlgIProc() As Long<br />
   Select Case Cb.Msg<br />
      Case %WM_Command<br />
         Select Case Cb.Ctl<br />
            Case %IdOk    , %IDC_InputOk     : Control Get Text hDlgI, %IDC_InputTextBox To RESearchTerm : Dialog End hDlgI<br />
            Case %IdCancel, %IDC_InputCancel : Dialog End hDlgI<br />
         End Select<br />
   End Select<br />
End Function<br />
<br />
Sub NewCFN<br />
   ReDim Preserve D(1 To UBound(D)+1)<br />
   D(UBound(D)) = "New Entry"<br />
   CFN = UBound(D)<br />
<br />
   ListView Insert Item hDlg, %IDC_ListView, CFN, 0, "New Entry " + &#36;CrLf + "Date: " + Date&#36;<br />
   ListView Set Text hDlg, %IDC_ListView, CFN, 2, Format&#36;(CFN)<br />
<br />
   ListView Select hDlg, %IDC_ListView, CFN<br />
   ListView_SetItemState hListView, CFN-1, %LVIS_Focused, %LVIS_Focused  '&lt;--- keyboard synchronizing code<br />
   ListView Visible hDlg, %IDC_ListView, CFN<br />
<br />
   Control Set Text hDlg, %IDC_RichEdit, D(CFN)<br />
   SetFocus hRichEdit<br />
End Sub<br />
<br />
Sub DeleteCFN<br />
<br />
   If GetFocus = hListView Then<br />
      'list of all item<br />
      If UBound(D) = 1 Then D(1) = "New Entry" : Exit Sub<br />
      ListView Delete Item hDlg, %IDC_ListView, CFN<br />
<br />
      Array Delete D(CFN)<br />
      ReDim Preserve D(UBound(D)-1)<br />
      If CFN &gt; UBound(D) Then CFN = CFN - 1<br />
<br />
      Open "crm.txt" For Output As #1 : Print #1, Join&#36;(D(), &#36;CrLf + &#36;Delim + &#36;CrLf); : Close #1<br />
<br />
      LoadCRMFile       'create D() from CRM.txt<br />
      DisplayCRMArray   'put D() into LV<br />
<br />
   Else<br />
      ? "Delete Allowed Only on Main List"<br />
   End If<br />
<br />
End Sub<br />
<br />
Sub ST(temp&#36;)<br />
   Statusbar Set Text hDlg, %IDC_StatusBar, 1, 0, temp&#36;<br />
End Sub</code></div></div><br /><!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=43" target="_blank" title="">gbnotes.png</a> (Size: 63.61 KB / Downloads: 7)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[A while back I asked about CRM app recommendations. Not having found something as simple as I wanted, I wrote my own and have been using it the last couple of months. <br />
<br />
I still have a list of changes I want to make to it, but since I've been using for a while now, I guess it's worth offering up to anyone who wants it.<br />
<br />
Discussion:  <br />
Source Code:  <a href="https://garybeene.com/files/gbnotes.zip" target="_blank" rel="noopener" class="mycode_url">https://garybeene.com/files/gbnotes.zip</a><br />
<br />
<img src="https://garybeene.com/images/gbnotes.png" loading="lazy"  alt="[Image: gbnotes.png]" class="mycode_img" /><br />
<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>'Compilable Example:  Requires JOSE includes.<br />
#Compile Exe  "gbnotes.exe"<br />
#Dim All<br />
<br />
#Debug Error On<br />
#Debug Display On<br />
<br />
%Unicode = 1<br />
#Include "Win32API.inc"<br />
<br />
&#36;Ver = "1.0"<br />
&#36;Delim = "++++"<br />
<br />
#Resource Icon xlogo, "icons&#92;n.ico"<br />
#Resource Icon xsave, "icons&#92;save.ico"<br />
#Resource Icon xnew, "icons&#92;new.ico"<br />
#Resource Icon xdelete, "icons&#92;delete.ico"<br />
#Resource Icon xsearch, "icons&#92;search.ico"<br />
#Resource Icon xall, "icons&#92;all.ico"<br />
#Resource Icon xsaveall, "icons&#92;saveall.ico"<br />
#Resource Icon xplus, "icons&#92;listplus.ico"<br />
#Resource Icon xup, "icons&#92;searchup.ico"<br />
#Resource Icon xdown, "icons&#92;searchdown.ico"<br />
#Resource Icon xexit, "icons&#92;exit.ico"<br />
<br />
%MultiLineREStyle_Wrap    = %WS_Child Or %WS_ClipSiblings Or %WS_Visible Or %ES_MultiLine Or %WS_VScroll Or _<br />
                            %ES_AutoVScroll Or %ES_WantReturn Or %ES_NoHideSel Or %WS_TabStop Or %ES_SaveSel<br />
<br />
Enum Equates Singular<br />
   IDC_ComboBox = 500<br />
   IDC_ListView<br />
   IDC_RichEdit<br />
   IDC_Toolbar<br />
   IDC_StatusBar<br />
   IDT_SaveCFN<br />
   IDT_New<br />
   IDT_Delete<br />
   IDT_Search<br />
   IDT_InsertDelimiter<br />
   IDT_ShowAll<br />
   IDT_AllContacts<br />
   IDT_SaveAll<br />
'   IDT_Sort<br />
   IDT_Exit<br />
   IDT_Mute<br />
   IDT_InsertDate<br />
   IDT_SetFocusRE<br />
   IDT_SearchUP<br />
   IDT_SearchDown<br />
   IDT_NextREDown<br />
   IDT_PrevREUp<br />
<br />
   IDC_InputTextBox<br />
   IDC_InputOk<br />
   IDC_InputCancel<br />
<br />
End Enum<br />
<br />
Global hDlg, hRichEdit, hListView, hComboBox, hList, hDlgI As Dword<br />
Global D() As String, REText&#36;, SearchTerm&#36;, SearchTerms&#36;<br />
Global RESearchTerm As WStringZ * 100<br />
Global SearchDirection, BackupNumber, CFN, iSkip, Maximized, Minimized, Mute As Long<br />
<br />
Function PBMain() As Long<br />
<br />
   Dialog Default Font "Arial Black", 16,1<br />
   Dialog New Pixels, 0, "gbNotes " + &#36;Ver,,,800,800, %WS_OverlappedWindow To hDlg<br />
   Dialog Set Icon hDlg, "xlogo"<br />
<br />
   Control Add ComboBox, hDlg, %IDC_ComboBox,, 10,10,400,200, %CBS_Simple Or %CBS_NoIntegralHeight Or %WS_VScroll Or %WS_TabStop, %WS_Ex_ClientEdge<br />
   Control Handle hDlg, %IDC_ComboBox To hComboBox<br />
<br />
   Control Add ListView, hDlg, %IDC_ListView, "",10,40,400,400, %WS_TabStop Or %LVS_Report Or %LVS_ShowSelAlways Or %LVS_SingleSel, %WS_Ex_ClientEdge<br />
   ListView Insert Column hDlg, %IDC_ListView, 1,"All Contacts",400,0<br />
   ListView Insert Column hDlg, %IDC_ListView, 2,"Position",150,0<br />
   Control Handle hDlg, %IDC_ListView To hListView<br />
<br />
   LoadLibrary("msftedit.dll")<br />
   Control Add "RichEdit50W", hDlg, %IDC_RichEdit, "", 410,10,400,400, %MultiLineREStyle_Wrap, %WS_Ex_ClientEdge<br />
   Control Handle hDlg, %IDC_RichEdit To hRichEdit<br />
   SendMessage hDlg, %EM_SetEventMask, 0, %ENM_SelChange<br />
   SendMessage hRichEdit, %EM_SetEventMask, 0, %ENM_SelChange Or %ENM_Link<br />
   SendMessage hRichEdit, %EM_AUTOURLDETECT, %True, 0<br />
<br />
   Control Add Toolbar, hDlg, %IDC_Toolbar, "", 0,0,0,0,%CCS_NoMoveY<br />
   ImageList New Icon 32,32,32,10 To hList<br />
   ImageList Add Icon hList, "xsearch"  '1<br />
   ImageList Add Icon hList, "xsave"    '2<br />
   ImageList Add Icon hList, "xnew"     '3<br />
   ImageList Add Icon hList, "xdelete"  '4<br />
   ImageList Add Icon hList, "xall"     '5<br />
   ImageList Add Icon hList, "xsaveall" '6<br />
   ImageList Add Icon hList, "xplus"    '7<br />
   ImageList Add Icon hList, "xexit"    '8<br />
   ImageList Add Icon hList, "xup"      '9<br />
   ImageList Add Icon hList, "xdown"    '10<br />
<br />
   Toolbar Set ImageList hDlg, %IDC_Toolbar, hList, 0<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 8, %IDT_Exit, %TbStyle_Button, "Exit"<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 5, %IDT_AllContacts, %TbStyle_Button, "All"<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 2, %IDT_SaveCFN, %TbStyle_Button, "Save"<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 3, %IDT_New, %TbStyle_Button, "New"<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 4, %IDT_Delete, %TbStyle_Button, "Delete"<br />
<br />
   Toolbar Add Separator hDlg, %IDC_Toolbar, 50<br />
<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 5, %IDT_ShowAll, %TbStyle_Button, "Show All"<br />
   Toolbar Add Separator hDlg, %IDC_Toolbar, 10<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 6, %IDT_SaveAll, %TbStyle_Button, "Save All"<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 10, %IDT_SearchUp, %TbStyle_Button, "Up"<br />
   Toolbar Add Button hDlg, %IDC_Toolbar, 9, %IDT_SearchDown, %TbStyle_Button, "Down"<br />
<br />
   Control Add Statusbar, hDlg, %IDC_StatusBar, "Welcome to gbNotes!", 0,0,0,90<br />
<br />
   Dialog Show Modal hDlg Call DlgProc<br />
End Function<br />
<br />
CallBack Function DlgProc() As Long<br />
   Local pNMLV As NMListView Ptr, iRow As Long, temp&#36;<br />
   Select Case Cb.Msg<br />
      Case %WM_InitDialog<br />
         If IsFalse IsFolder("backup") Then MkDir "backup"<br />
         Settings_INI "get"<br />
         BuildAcceleratorTable<br />
<br />
         LoadCRMFile       'create D() from CRM.txt<br />
         DisplayCRMArray   'put D() into LV<br />
<br />
         LoadComboBox<br />
         Control Set Text hDlg, %IDC_ComboBox, SearchTerm&#36;<br />
<br />
         If Maximized Then Dialog Show State hDlg, %SW_Maximize  'restore Maximized state if necessary<br />
         If Minimized Then Dialog Show State hDlg, %SW_Minimize       'restore Maximized state if necessary<br />
<br />
         Control Set Text hDlg, %IDC_RichEdit, D(CFN)<br />
         ListView Select hDlg, %IDC_ListView, CFN<br />
         ListView_SetItemState hListView, CFN-1, %LVIS_Focused, %LVIS_Focused  '&lt;--- keyboard synchronizing code<br />
         ListView Visible hDlg, %IDC_ListView, CFN<br />
<br />
      Case %WM_Size : Resize<br />
<br />
      Case %WM_Command<br />
         Select Case Cb.Ctl<br />
'            Case %IdCancel : Dialog End hDlg<br />
            Case %IdOk 'Enter Key<br />
               If GetParent(GetFocus) = hComboBox Then<br />
                  sBeep<br />
                  Control Get Text hDlg, %IDC_ComboBox To SearchTerm&#36;<br />
                  Search<br />
                  AddSearchTerm<br />
               End If<br />
            Case %IDC_ComboBox<br />
               Select Case Cb.CtlMsg<br />
                  Case %CBN_SelChange<br />
                     'get post-selection text<br />
                     Control Send hDlg, %IDC_ComboBox, %CB_GetCurSel, 0, 0 To iRow<br />
                     ComboBox Get Text hDlg, %IDC_ComboBox, iRow+1 To SearchTerm&#36;<br />
                     Dialog Set Text hDlg, "SearchTerm = " + SearchTerm&#36;  + "   " + Time&#36;<br />
                     Search<br />
               End Select<br />
            Case %IDT_Exit       : sBeep : Dialog End hDlg<br />
            Case %IDT_SaveCFN    : sBeep : SaveCFN          : ST Trim&#36;(Parse&#36;(D(CFN),&#36;CrLf,1)) + " saved"<br />
            Case %IDT_New        : NewCFN                   : ST "New Contact"<br />
            Case %IDT_Delete     : sBeep : DeleteCFN        : ST ""<br />
            Case %IDT_Search     : sBeep : Control Get Text hDlg, %IDC_ComboBox To SearchTerm&#36; : Search  'ST<br />
            Case %IDT_InsertDate : sBeep : InsertDate       : ST ""<br />
            Case %IDT_InsertDelimiter : sBeep : InsertDelimiter : ST ""<br />
            Case %IDT_ShowAll    : sBeep : ShowAll          : ST "All CRM.txt Loaded in Editor"<br />
            Case %IDT_SaveAll    : sBeep : SaveAll          : ST "Editor Content saved to crm.txt"<br />
'            Case %IDT_Sort       : sBeep : SortData<br />
            Case %IDT_Mute       : WinBeep(275,150) : Mute Xor=1 : ST "Mute is " + IIf&#36;(Mute,"ON","OFF")<br />
            Case %IDT_SetFocusRE : SetFocus hRichEdit<br />
            Case %IDT_SearchDown     : SearchDirection = 1 : FindRE : ST ""<br />
            Case %IDT_SearchUp       : SearchDirection = 0 : FindRE<br />
            Case %IDT_NextREDown     : sBeep : SearchDirection = 1 :    SearchText RESearchTerm, %FR_Down<br />
            Case %IDT_AllContacts    : DisplayCRMArray<br />
<br />
         End Select<br />
      Case %WM_Notify<br />
         Select Case Cb.NmId<br />
            Case %IDC_RichEdit<br />
               Select Case Cb.NmCode<br />
                  Case %EN_Link : OpenLink(Cb.LParam)<br />
               End Select<br />
            Case %IDC_ListView<br />
               Select Case Cb.NmCode<br />
                  Case %LVN_ItemChanged  '%NM_Click<br />
                     pNMLV = Cb.LParam<br />
                     If (@pNMLV.uChanged And %LVIF_STATE) = %LVIF_STATE Then  ' if state has changed<br />
                         If (@pNMLV.unewstate And %LVIS_SELECTED) = %LVIS_SELECTED Then<br />
                            ListView Get Select hDlg, %IDC_ListView To iRow<br />
                            ListView Get Text hDlg, %IDC_Listview, iRow, 2 To temp&#36;<br />
                            CFN = Val(temp&#36;)<br />
                            Control Set Text hDlg, %IDC_RichEdit, D(CFN)<br />
                            sBeep<br />
                         End If<br />
                     End If<br />
               End Select<br />
         End Select<br />
      Case %WM_Destroy<br />
         SaveBackup<br />
         Settings_INI "save"<br />
<br />
   End Select<br />
End Function<br />
<br />
Sub Resize<br />
   Local x,y,w,h,wc,hc,wt,ht As Long<br />
   Control Get Size hDlg, %IDC_Toolbar To wt,ht<br />
   Dialog Get Client hDlg To w,h<br />
<br />
   Control Set Loc hDlg, %IDC_ComboBox, 10,ht+10<br />
   Control Get Size hDlg, %IDC_ComboBox To wc,hc<br />
<br />
   Control Set Loc hDlg, %IDC_ListView, 10,ht+hc+20<br />
   Control Set Size hDlg, %IDC_ListView, 400, h-ht-hc-30-40<br />
<br />
   Control Set Loc hDlg, %IDC_RichEdit, wc+15,ht+10<br />
   Control Set Size hDlg, %IDC_RichEdit, w-wc-25,h-ht-30-30<br />
<br />
End Sub<br />
<br />
Function OpenLink(ByVal lpLink As Dword) As Long<br />
   Local enlinkPtr As ENLINK Ptr, linkText As String, iReturn As Long<br />
   Control Get Text hDlg, %IDC_RichEdit To REText&#36;<br />
   enlinkPtr  = lpLink<br />
   If @enLinkPtr.Msg = %WM_LButtonUp Then<br />
      LinkText = Mid&#36;(REText&#36;,@enLinkPtr.chrg.cpMin+1 To @enLinkPtr.chrg.cpMax)<br />
      LinkText = Remove&#36;(LinkText, Any &#36;Cr+&#36;Lf+&#36;Spc)<br />
      iReturn  = ShellExecute(hDlg, "Open", (LinkText), &#36;Nul, &#36;Nul, %SW_ShowNormal)<br />
   End If<br />
End Function<br />
<br />
Sub LoadComboBox<br />
   Local temp&#36;, i As Long<br />
   Open "searchterms.txt" For Binary As #1 : Get&#36; #1, Lof(1), SearchTerms&#36; : Close #1<br />
   If Len(SearchTerms&#36;) = 0 Then SearchTerms&#36; = "Texas" + &#36;CrLf + "Papua New Guinea"<br />
   SearchTerms&#36; = Trim&#36;(SearchTerms&#36;, Any &#36;CrLf + &#36;Spc)<br />
   SearchTerm&#36; = Parse&#36;(SearchTerms&#36;,&#36;CrLf,1)<br />
<br />
   ComboBox Reset hDlg, %IDC_ComboBox<br />
   Control Set Text hDlg, %IDC_ComboBox, SearchTerm&#36;<br />
<br />
   For i = 1 To Min(5,ParseCount(searchterms&#36;,&#36;CrLf))<br />
      ComboBox Insert hDlg, %IDC_ComboBox, i, Parse&#36;(SearchTerms&#36;,&#36;CrLf,i)<br />
   Next i<br />
End Sub<br />
<br />
Sub LoadCRMFile<br />
   Local tmp&#36;, temp&#36;, i,iCount As Long<br />
   Open "crm.txt" For Binary As #1 : Get&#36; #1, Lof(1), temp&#36; : Close #1<br />
   If Len(Trim&#36;(temp&#36;)) = 0 Then<br />
      temp&#36; = "Gary Beene" + &#36;CrLf + "Dallas, Texas" + &#36;CrLf + &#36;Delim + &#36;CrLf + "Stuart McLachlan" + &#36;CrLf + "Papua New Guinea"<br />
      Open "crm.txt" For Output As #1 : Print #1, temp&#36;; : Close #1<br />
   End If<br />
   iCount = ParseCount(temp&#36;, &#36;Delim)<br />
   ReDim D(1 To iCount)<br />
   Parse temp&#36;, D(), &#36;Delim<br />
<br />
   For i = 1 To iCount<br />
      D(i) = Trim&#36;(D(i), Any &#36;CrLf + &#36;Spc)<br />
      tmp&#36; = Parse&#36;(D(i),&#36;CrLf,1)<br />
      If Trim&#36;(tmp&#36;) = "" Then ? "Data Issue:" + &#36;CrLf + D(i-1)<br />
   Next i<br />
<br />
   ST "Records: " + Format&#36;(iCount,"##,###")<br />
End Sub<br />
<br />
Sub DisplayCRMArray<br />
   Local temp&#36;, i As Long<br />
   ListView Reset hDlg, %IDC_ListView<br />
   For i = 1 To UBound(D)<br />
      D(i) = Trim&#36;(D(i))<br />
      ListView Insert Item hDlg, %IDC_ListView, i,0,Parse&#36;(D(i),&#36;CrLf,1)<br />
      ListView Set Text hDlg, %IDC_ListView, i,2,Format&#36;(i)<br />
   Next i<br />
End Sub<br />
<br />
Sub Search<br />
   Local temp&#36;, i,iCount As Long<br />
<br />
   ListView Reset hDlg, %IDC_ListView<br />
   For i = 1 To UBound(D)<br />
      If InStr(LCase&#36;(D(i)), LCase&#36;(SearchTerm&#36;)) Then<br />
         Incr iCount<br />
         ListView Insert Item hDlg, %IDC_ListView, iCount, 0, Parse&#36;(D(i),&#36;CrLf,1)<br />
         ListView Set Text hDlg, %IDC_ListView, iCount, 2, Format&#36;(i)<br />
      End If<br />
   Next i<br />
<br />
   ListView Select hDlg, %IDC_ListView, 1<br />
   ListView_SetItemState hListView, 0, %LVIS_Focused, %LVIS_Focused  '&lt;--- keyboard synchronizing code<br />
   ListView Set Header hDlg, %IDC_ListView, 1, "Matches: " + Format&#36;(iCount) + "   " + &#36;Dq + SearchTerm&#36; + &#36;Dq<br />
End Sub<br />
<br />
Sub Settings_INI(Task&#36;)<br />
   Local x,y,w,h, tempz, INIFileName As WStringZ * %Max_Path, WinPla As WindowPlacement<br />
<br />
   'set ini filename<br />
   INIFileName = Exe.Path&#36; + Exe.Name&#36; + ".ini"    'get INI file name<br />
<br />
   Select Case Task&#36;<br />
      Case "get"<br />
         'get dialog width/height from INI file and use to set Dialog size<br />
         GetPrivateProfileString "All", "Width", "800", w, %Max_Path, INIFileName<br />
         GetPrivateProfileString "All", "Height", "800", h, %Max_Path, INIFileName<br />
         Dialog Set Size hDlg,Val(w), Val(h)   'width/height<br />
<br />
         'get dialog top/left from INI file and use to set Dialog location<br />
         Getprivateprofilestring "All", "Left", "0", x, %Max_Path, INIFileName<br />
         Getprivateprofilestring "All", "Top", "0",  y,  %Max_Path, INIFileName<br />
         If IsFile(INIFileName) Then Dialog Set Loc hDlg, Val(x), Val(y)   'left/top but only once INIFileName exists<br />
<br />
         'get value for string variables<br />
         GetPrivateProfileString "All", "RESearchTerm", "TWC", RESearchTerm, %Max_Path, INIFileName<br />
<br />
         'get value for numeric variables<br />
         Getprivateprofilestring "All", "Minimized", "0", tempz, %Max_Path, INIFileName   : Minimized = Val(tempz)<br />
         Getprivateprofilestring "All", "Maximized", "0", tempz, %Max_Path, INIFileName   : Maximized = Val(tempz)<br />
         Getprivateprofilestring "All", "CFN", "1",       tempz, %Max_Path, INIFileName   : CFN = Val(tempz)<br />
         Getprivateprofilestring "All", "Mute", "0",      tempz, %Max_Path, INIFileName   : Mute = Val(tempz)<br />
         Getprivateprofilestring "All", "BackupNumber", "0",      tempz, %Max_Path, INIFileName   : BackupNumber = Val(tempz)<br />
<br />
      Case "save"<br />
         If IsFile(INIFileName) Then Kill INIFileName    'clear the INI file Name to remove residual entries before saving<br />
         WinPla.Length = SizeOf(WinPla)<br />
         GetWindowPlacement hDlg, WinPla<br />
         WritePrivateProfileString "All", "Left", Str&#36;(WinPla.rcNormalPosition.nLeft), INIFileName<br />
         WritePrivateProfileString "All", "Top", Str&#36;(WinPla.rcNormalPosition.nTop), INIFileName<br />
         WritePrivateProfileString "All", "Width", Str&#36;(WinPla.rcNormalPosition.nRight - WinPla.rcNormalPosition.nLeft), INIFileName<br />
         WritePrivateProfileString "All", "Height", Str&#36;(WinPla.rcNormalPosition.nBottom - WinPla.rcNormalPosition.nTop), INIFileName<br />
<br />
         'save string variables<br />
         WritePrivateProfileString "All", "RESearchTerm", RESearchTerm, INIFileName<br />
<br />
         'save numeric variables<br />
         Minimized = IsIconic(hDlg)<br />
         Maximized = IsZoomed(hDlg)<br />
         WritePrivateProfileString "All", "Minimized", Str&#36;(Minimized), INIFileName<br />
         WritePrivateProfileString "All", "Maximized", Str&#36;(Maximized), INIFileName<br />
         WritePrivateProfileString "All", "CFN", Str&#36;(CFN), INIFileName<br />
         WritePrivateProfileString "All", "Mute", Str&#36;(Mute), INIFileName<br />
         WritePrivateProfileString "All", "BackupNumber", Str&#36;(BackupNumber), INIFileName<br />
<br />
   End Select<br />
End Sub<br />
<br />
Sub SaveCFN<br />
   Local temp&#36;<br />
   Control Get Text hDlg, %IDC_RichEdit To temp&#36;<br />
<br />
   If InStr(temp&#36;, &#36;Delim) Then ? "Cannot Save!" : Exit Sub<br />
<br />
   D(CFN) = temp&#36;<br />
   Open "crm.txt" For Output As #1 : Print #1, Join&#36;(D(), &#36;CrLf + &#36;Delim + &#36;CrLf); : Close #1<br />
   ListView Set Text hDlg, %IDC_ListView, CFN, 1, Parse&#36;(D(CFN),&#36;CrLf,1)<br />
   SetFocus hRichEdit<br />
End Sub<br />
<br />
Sub ShowAll<br />
   Control Set Text hDlg, %IDC_RichEdit, Join&#36;(D(),&#36;CrLf + &#36;Delim + &#36;CrLf)<br />
   SendMessage hRichEdit, %EM_SetSel, 0, 0<br />
   SetFocus hRichEdit<br />
End Sub<br />
<br />
Sub SaveAll<br />
   Local temp&#36;<br />
   If MsgBox("Update All CRM Data?", %MB_YesNo Or %MB_IconQuestion Or %MB_TaskModal, "Update CRM") = %IdYes Then<br />
      Control Get Text hDlg, %IDC_RichEdit To temp&#36;<br />
      temp&#36; = Trim&#36;(temp&#36;, Any &#36;CrLf + " +")<br />
      Open "crm.txt" For Output As #1<br />
      Print #1, temp&#36;;<br />
      Close #1<br />
<br />
      LoadCRMFile       'create D() from CRM.txt<br />
      DisplayCRMArray   'put D() into LV<br />
<br />
   End If<br />
End Sub<br />
<br />
Sub sBeep<br />
   If Mute Then Exit Sub<br />
   WinBeep(275,150)<br />
End Sub<br />
<br />
Sub BuildAcceleratorTable<br />
   Local ac() As ACCELAPI, hAccelerator As Dword, c As Long  ' for keyboard accelator table values<br />
   Dim ac(10)<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_Delete : ac(c).cmd = %IDT_Delete          : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_D : ac(c).cmd = %IDT_InsertDate        : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_E : ac(c).cmd = %IDT_ShowAll           : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_F : ac(c).cmd = %IDT_SearchDown        : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_I : ac(c).cmd = %IDT_InsertDelimiter   : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_R : ac(c).cmd = %IDT_SetFocusRE        : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_M : ac(c).cmd = %IDT_Mute              : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_N : ac(c).cmd = %IDT_NextREDown        : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL : ac(c).key   = %VK_S : ac(c).cmd = %IDT_SaveCFN           : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL Or %FSHIFT : ac(c).key = %VK_F : ac(c).cmd = %IDT_SearchUp : Incr c<br />
   ac(c).fvirt = %FVIRTKEY Or %FCONTROL Or %FSHIFT : ac(c).key = %VK_S : ac(c).cmd = %IDT_SaveAll  : Incr c<br />
   Accel Attach hDlg, AC() To hAccelerator<br />
End Sub<br />
<br />
Sub InsertDelimiter<br />
   Clipboard Reset<br />
   Clipboard Set Text &#36;CrLf + &#36;Delim + &#36;CrLf + &#36;CrLf<br />
   SendMessage hRichEdit, %WM_Paste, 0, 0<br />
End Sub<br />
<br />
Sub InsertDate<br />
   Clipboard Reset<br />
   Clipboard Set Text &#36;CrLf + "Discussion: " + Date&#36; + "  " + Time&#36; + "  ---------" + &#36;CrLf + &#36;CrLf<br />
   SendMessage hRichEdit, %WM_Paste, 0, 0<br />
End Sub<br />
<br />
'Sub SortData<br />
'   Array Sort D(1)<br />
'   Open "crm.txt" For Output As #1 : Print #1, Join&#36;(D(), &#36;CrLf + &#36;Delim + &#36;CrLf); : Close #1<br />
'   LoadCRMFile       'create D() from CRM.txt<br />
'   DisplayCRMArray   'put D() into LV<br />
'End Sub<br />
<br />
Sub AddSearchTerm<br />
   Local i As Long<br />
<br />
   'If SearchTerm alread in use, do nothing<br />
   For i = 1 To Min(5,ParseCount(SearchTerms&#36;,&#36;CrLf))<br />
      If LCase&#36;(Parse&#36;(SearchTerms&#36;,&#36;CrLf,i)) = LCase&#36;(SearchTerm&#36;) Then Exit Sub<br />
   Next i<br />
<br />
   'If new search term, add it top of the list of SearchTerms&#36;<br />
   SearchTerms&#36; = SearchTerm&#36; + &#36;CrLf + SearchTerms&#36;<br />
   SearchTerms&#36; = Trim&#36;(SearchTerms&#36;, Any &#36;CrLf + &#36;Spc)<br />
   Open "searchterms.txt" For Output As #1 : Print #1, SearchTerms&#36;; : Close #1<br />
<br />
   LoadComboBox<br />
End Sub<br />
<br />
Sub SaveBackup<br />
   Local temp&#36;<br />
   temp&#36; = Time&#36;<br />
   Replace  ":" With "." In temp&#36;<br />
   Incr BackupNumber<br />
   Open "backup&#92;crm_" + Format&#36;(BackupNumber) + "_" + Date&#36; + "_" + temp&#36; + ".txt" For Output As #1<br />
   Print #1, Join&#36;(D(), &#36;CrLf + &#36;Delim + &#36;CrLf); : Close #1<br />
End Sub<br />
<br />
Sub FindRE<br />
   GetRESearchTerm<br />
   SearchText RESearchTerm, %FR_Down<br />
End Sub<br />
<br />
Function SearchText(ByVal sTextToSearchFor As WString, SearchDirection As Long) As Long<br />
 Local  FindTextText         As FINDTEXTEX<br />
 Local  NextMatch, SelStart, SelEnd As Long<br />
<br />
 SendMessage(hRichEdit, %EM_GETSEL, VarPtr(SelStart), VarPtr(SelEnd))<br />
 FindTextText.lpStrText = StrPtr(sTextToSearchFor)<br />
<br />
 If SearchDirection = 1 Then<br />
   FindTextText.chrg.cpMin = SelEnd + 1 'Search from current position<br />
   FindTextText.chrg.cpMax = -1         '- till the end<br />
 Else '%FR_UP<br />
   FindTextText.chrg.cpMin = SelStart   'Search from current position<br />
   FindTextText.chrg.cpMax = 0          '- up to the start<br />
 End If<br />
<br />
 NextMatch = SendMessage(hRichEdit, %EM_FINDTEXTEX, SearchDirection, VarPtr(FindTextText)) 'Return next match or -1 for no more<br />
<br />
 If NextMatch = -1 Then WinBeep(250,300) :Exit Function<br />
 SendMessage(hRichEdit, %EM_SETSEL, FindTextText.chrgText.cpMin, FindTextText.chrgText.cpMax)<br />
<br />
End Function<br />
<br />
Sub GetRESearchTerm<br />
   Dialog New Pixels, 0, "Search RE",,,200,80, %WS_SysMenu To hDlgI<br />
   Dialog Set Icon hDlgI, "xsearch"<br />
   Control Add TextBox, hDlgI, %IDC_InputTextBox,RESearchTerm, 20,10,160,30<br />
   Control Add Button, hDlgI, %IDC_InputOk, "Ok", 20,50,50,25<br />
   Control Add Button, hDlgI, %IDC_InputCancel, "Cancel", 90,50,90,25<br />
   RESearchTerm = ""<br />
   Dialog Show Modal hdlgI Call hDlgIProc<br />
End Sub<br />
<br />
CallBack Function hDlgIProc() As Long<br />
   Select Case Cb.Msg<br />
      Case %WM_Command<br />
         Select Case Cb.Ctl<br />
            Case %IdOk    , %IDC_InputOk     : Control Get Text hDlgI, %IDC_InputTextBox To RESearchTerm : Dialog End hDlgI<br />
            Case %IdCancel, %IDC_InputCancel : Dialog End hDlgI<br />
         End Select<br />
   End Select<br />
End Function<br />
<br />
Sub NewCFN<br />
   ReDim Preserve D(1 To UBound(D)+1)<br />
   D(UBound(D)) = "New Entry"<br />
   CFN = UBound(D)<br />
<br />
   ListView Insert Item hDlg, %IDC_ListView, CFN, 0, "New Entry " + &#36;CrLf + "Date: " + Date&#36;<br />
   ListView Set Text hDlg, %IDC_ListView, CFN, 2, Format&#36;(CFN)<br />
<br />
   ListView Select hDlg, %IDC_ListView, CFN<br />
   ListView_SetItemState hListView, CFN-1, %LVIS_Focused, %LVIS_Focused  '&lt;--- keyboard synchronizing code<br />
   ListView Visible hDlg, %IDC_ListView, CFN<br />
<br />
   Control Set Text hDlg, %IDC_RichEdit, D(CFN)<br />
   SetFocus hRichEdit<br />
End Sub<br />
<br />
Sub DeleteCFN<br />
<br />
   If GetFocus = hListView Then<br />
      'list of all item<br />
      If UBound(D) = 1 Then D(1) = "New Entry" : Exit Sub<br />
      ListView Delete Item hDlg, %IDC_ListView, CFN<br />
<br />
      Array Delete D(CFN)<br />
      ReDim Preserve D(UBound(D)-1)<br />
      If CFN &gt; UBound(D) Then CFN = CFN - 1<br />
<br />
      Open "crm.txt" For Output As #1 : Print #1, Join&#36;(D(), &#36;CrLf + &#36;Delim + &#36;CrLf); : Close #1<br />
<br />
      LoadCRMFile       'create D() from CRM.txt<br />
      DisplayCRMArray   'put D() into LV<br />
<br />
   Else<br />
      ? "Delete Allowed Only on Main List"<br />
   End If<br />
<br />
End Sub<br />
<br />
Sub ST(temp&#36;)<br />
   Statusbar Set Text hDlg, %IDC_StatusBar, 1, 0, temp&#36;<br />
End Sub</code></div></div><br /><!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=43" target="_blank" title="">gbnotes.png</a> (Size: 63.61 KB / Downloads: 7)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[gbLocator]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=61</link>
			<pubDate>Wed, 03 Sep 2025 05:28:13 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=94">Gary Beene</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=61</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">Howdy!</span><br />
<br />
I've continued working on <span style="font-weight: bold;" class="mycode_b">gbLocator</span>, which provides a user interface to Everything for blind and low vision users.  I'm up to v4.2.<br />
<br />
Web Site: <a href="https://newvisionconcepts.com/gblocator/gblocator.htm" target="_blank" rel="noopener" class="mycode_url">https://newvisionconcepts.com/gblocator/gblocator.htm</a><br />
Installation File:   <a href="https://garybeene.com/files/gblocator_setup.exe" target="_blank" rel="noopener" class="mycode_url">https://garybeene.com/files/gblocator_setup.exe</a><br />
Source Code File:   <a href="https://garybeene.com/files/gblocator.zip" target="_blank" rel="noopener" class="mycode_url">https://garybeene.com/files/gblocator.zip</a><br />
<br />
You'll recall that I spoke with David (Everything author) and he encouraged me to post something on his forum. I've just done that and hope to get some feedback from folks on his forum.<br />
<br />
Here are gbLocator images - one with the toolbar showing (low vision users) and one with the toolbar hidden (blind users).<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=40" target="_blank" title="">startup.png</a> (Size: 58.63 KB / Downloads: 12)
<!-- end: postbit_attachments_attachment --><br />
<br />
... does this forum allow inline images or just thumbnails that are links to the full size image?]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">Howdy!</span><br />
<br />
I've continued working on <span style="font-weight: bold;" class="mycode_b">gbLocator</span>, which provides a user interface to Everything for blind and low vision users.  I'm up to v4.2.<br />
<br />
Web Site: <a href="https://newvisionconcepts.com/gblocator/gblocator.htm" target="_blank" rel="noopener" class="mycode_url">https://newvisionconcepts.com/gblocator/gblocator.htm</a><br />
Installation File:   <a href="https://garybeene.com/files/gblocator_setup.exe" target="_blank" rel="noopener" class="mycode_url">https://garybeene.com/files/gblocator_setup.exe</a><br />
Source Code File:   <a href="https://garybeene.com/files/gblocator.zip" target="_blank" rel="noopener" class="mycode_url">https://garybeene.com/files/gblocator.zip</a><br />
<br />
You'll recall that I spoke with David (Everything author) and he encouraged me to post something on his forum. I've just done that and hope to get some feedback from folks on his forum.<br />
<br />
Here are gbLocator images - one with the toolbar showing (low vision users) and one with the toolbar hidden (blind users).<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="http://pump.richheimer.de/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=40" target="_blank" title="">startup.png</a> (Size: 58.63 KB / Downloads: 12)
<!-- end: postbit_attachments_attachment --><br />
<br />
... does this forum allow inline images or just thumbnails that are links to the full size image?]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[High resolution replacement for Sleep]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=59</link>
			<pubDate>Mon, 01 Sep 2025 21:33:03 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=10">David Roberts</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=59</guid>
			<description><![CDATA[SleepX()<br />
<br />
SleepX() is a high resolution replacement for Sleep.<br />
<br />
The principle used has an analogy with HiFi were a class B amplifier is used to get us within a neighbourhood of a desired voltage and then uses a class A amplifier to fine tune. The cost of this approach is much less than using only a class A amplifier, which are expensive.<br />
<br />
The class B amplifier analogy uses Sleep and the class A amplifier analogy uses the Performance Counter, which has a resolution of 100ns with Windows 10 and later.<br />
<br />
The following code has the SleepX() code and a usage example.<br />
<br />
This is a typical output.<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>Quarter of a millisecond: .2503 ms<br />
<br />
2 seconds: 2.0000003 s<br />
<br />
Silly: 1234.0003 ms<br />
<br />
1  1.0001 ms<br />
2  2.0001 ms<br />
3  3.0002 ms<br />
4  4.0001 ms<br />
5  5.0002 ms<br />
6  6.0002 ms<br />
7  7.0002 ms<br />
8  8.0001 ms<br />
9  9.0001 ms<br />
10  10 ms<br />
11  11.0002 ms<br />
12  12.0002 ms<br />
13  13.0001 ms<br />
14  14.0001 ms<br />
15  15.0001 ms<br />
16  16.0002 ms<br />
17  17.0002 ms<br />
18  18.0002 ms<br />
19  19.0002 ms<br />
20  20.0001 ms<br />
21  21.0003 ms<br />
22  22.0002 ms<br />
23  23.0001 ms<br />
24  24.0001 ms<br />
25  25 ms<br />
26  26.0002 ms<br />
27  27.0002 ms<br />
28  28.0002 ms<br />
29  29.0002 ms<br />
30  30.0002 ms</code></div></div><br />
The first example looks at a delay of a quarter of a millisecond. I doubt that anyone will have a use for that. The second example looks at a delay of two seconds. The third example looks at a 'silly' delay of 1234ms, The following looks at delays from 1ms to 30ms in steps of 1ms.<br />
<br />
All the results have a sub micro accuracy inline with the Performance Counter on Windows 10 and later.<br />
<br />
SleepX() has two parts: The first part is used for delays &lt;= to 3ms and only polls the Performance Counter. This is expensive but is a short-lived expense and should not impact on the system performance. The second part uses the construct 'Sleep ( n-3 )' to get us within a neighbourhood of the target delay, and then we poll the Performance Counter to fine tune. 'Sleep ( n-3 )' needs a resolution of 1ms and why we use SetHiRes.<br />
<br />
'Sleep ( n-3 )' does not use any CPU load. The CPU load only kicks in when we enter the 'class A' mode, so is an absolute value and should not impact on the system performance.<br />
<br />
Why was 'n &lt;= 3'.  It is reasonable to expect a 1ms resolution to give a delay of between n ms and n+1 ms. In practice, we can exceed n+1 ms and about one third of delays do just that. It is very rare, but values approaching n+1.5 ms have been seen. To mitigate that issue, 'n &lt;= 3' was chosen.<br />
<br />
That is it: A very simple idea to give a high resolution replacement for Sleep with a negligible CPU load.<br />
<br />
It is worth noting that<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>SetHiRes<br />
&lt;Code to time&gt;<br />
RevokeHiRes</code></div></div>is acceptable, but it should not be used within a loop; for example a graphics application with 60, or so, fps. That will upset the system clock. It is better to use SetHiRes at the beginning of an application and RevokeHiRes when the higher resolution is no longer needed. If you forget to use RevokeHiRes SetHiRes will be cancelled when the process terminates. Microsoft neglects to mention that.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#COMPILE EXE<br />
#DIM ALL<br />
#INCLUDE "win32api.inc"<br />
<br />
' SOURCE CODE:<br />
<br />
Macro QPC = QueryPerformanceCounter qTimeNow<br />
<br />
Macro SetHiRes<br />
  MacroTemp Time<br />
  Dim Time As TIMECAPS<br />
  TimeGetDevCaps( Time, SizeOf(Time) )<br />
  TimeBeginPeriod(Time.wPeriodMin)<br />
  'Sleep 16 ' Pre Windows 10 - high resolution does not 'byte' until next clock tick.<br />
End Macro<br />
<br />
Macro RevokeHiRes<br />
  MacroTemp Time<br />
  Dim Time As TIMECAPS<br />
  TimeGetDevCaps( Time, SizeOf(Time) )<br />
  TimeEndPeriod(Time.wPeriodMin)<br />
End Macro<br />
<br />
Global qFreq As Quad<br />
<br />
Sub SleepX( ByVal n As Double )<br />
  Local qTarget, qTimeNow As Quad<br />
  QPC<br />
  qTarget = qTimeNow + n*qFreq*0.001<br />
  If n &lt;= 3 then<br />
    Do : QPC : Loop Until qTimeNow &gt;= qTarget ' Class A amplifier analogy<br />
  Else<br />
    ' Class B amplifier analogy followed by Classs A amplifier analogy<br />
    Sleep ( n-3 ) : Do : QPC : Loop Until qTimeNow &gt;= qTarget<br />
  End If<br />
End Sub<br />
<br />
' ====================<br />
<br />
' EXAMPLE USAGE:<br />
<br />
Function Pbmain () As Long<br />
Local i as Long<br />
Local qStart, qStop As Quad<br />
<br />
  QueryPerformanceFrequency qFreq<br />
<br />
  SetHiRes<br />
<br />
  QueryPerformanceCounter qStart<br />
    SleepX(0.25)<br />
  QueryPerformanceCounter qStop<br />
  Print "Quarter of a millisecond:";(qStop - qStart)*1000/qFreq;"ms"<br />
  Print<br />
<br />
  QueryPerformanceCounter qStart<br />
    SleepX(2000)<br />
  QueryPerformanceCounter qStop<br />
  Print "2 seconds:";(qStop - qStart)/qFreq;"s"<br />
  Print<br />
<br />
  QueryPerformanceCounter qStart<br />
    SleepX(1234)<br />
  QueryPerformanceCounter qStop<br />
  Print "Silly:";(qStop - qStart)*1000/qFreq;"ms"<br />
  Print<br />
<br />
  For i = 1 to 30<br />
    QueryPerformanceCounter qStart<br />
      SleepX(i)<br />
    QueryPerformanceCounter qStop<br />
    Print i;" ";(qStop - qStart)*1000/qFreq;"ms"<br />
  Next<br />
<br />
  RevokeHiRes<br />
<br />
  WaitKey&#36;<br />
<br />
End Function</code></div></div>]]></description>
			<content:encoded><![CDATA[SleepX()<br />
<br />
SleepX() is a high resolution replacement for Sleep.<br />
<br />
The principle used has an analogy with HiFi were a class B amplifier is used to get us within a neighbourhood of a desired voltage and then uses a class A amplifier to fine tune. The cost of this approach is much less than using only a class A amplifier, which are expensive.<br />
<br />
The class B amplifier analogy uses Sleep and the class A amplifier analogy uses the Performance Counter, which has a resolution of 100ns with Windows 10 and later.<br />
<br />
The following code has the SleepX() code and a usage example.<br />
<br />
This is a typical output.<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>Quarter of a millisecond: .2503 ms<br />
<br />
2 seconds: 2.0000003 s<br />
<br />
Silly: 1234.0003 ms<br />
<br />
1  1.0001 ms<br />
2  2.0001 ms<br />
3  3.0002 ms<br />
4  4.0001 ms<br />
5  5.0002 ms<br />
6  6.0002 ms<br />
7  7.0002 ms<br />
8  8.0001 ms<br />
9  9.0001 ms<br />
10  10 ms<br />
11  11.0002 ms<br />
12  12.0002 ms<br />
13  13.0001 ms<br />
14  14.0001 ms<br />
15  15.0001 ms<br />
16  16.0002 ms<br />
17  17.0002 ms<br />
18  18.0002 ms<br />
19  19.0002 ms<br />
20  20.0001 ms<br />
21  21.0003 ms<br />
22  22.0002 ms<br />
23  23.0001 ms<br />
24  24.0001 ms<br />
25  25 ms<br />
26  26.0002 ms<br />
27  27.0002 ms<br />
28  28.0002 ms<br />
29  29.0002 ms<br />
30  30.0002 ms</code></div></div><br />
The first example looks at a delay of a quarter of a millisecond. I doubt that anyone will have a use for that. The second example looks at a delay of two seconds. The third example looks at a 'silly' delay of 1234ms, The following looks at delays from 1ms to 30ms in steps of 1ms.<br />
<br />
All the results have a sub micro accuracy inline with the Performance Counter on Windows 10 and later.<br />
<br />
SleepX() has two parts: The first part is used for delays &lt;= to 3ms and only polls the Performance Counter. This is expensive but is a short-lived expense and should not impact on the system performance. The second part uses the construct 'Sleep ( n-3 )' to get us within a neighbourhood of the target delay, and then we poll the Performance Counter to fine tune. 'Sleep ( n-3 )' needs a resolution of 1ms and why we use SetHiRes.<br />
<br />
'Sleep ( n-3 )' does not use any CPU load. The CPU load only kicks in when we enter the 'class A' mode, so is an absolute value and should not impact on the system performance.<br />
<br />
Why was 'n &lt;= 3'.  It is reasonable to expect a 1ms resolution to give a delay of between n ms and n+1 ms. In practice, we can exceed n+1 ms and about one third of delays do just that. It is very rare, but values approaching n+1.5 ms have been seen. To mitigate that issue, 'n &lt;= 3' was chosen.<br />
<br />
That is it: A very simple idea to give a high resolution replacement for Sleep with a negligible CPU load.<br />
<br />
It is worth noting that<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>SetHiRes<br />
&lt;Code to time&gt;<br />
RevokeHiRes</code></div></div>is acceptable, but it should not be used within a loop; for example a graphics application with 60, or so, fps. That will upset the system clock. It is better to use SetHiRes at the beginning of an application and RevokeHiRes when the higher resolution is no longer needed. If you forget to use RevokeHiRes SetHiRes will be cancelled when the process terminates. Microsoft neglects to mention that.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#COMPILE EXE<br />
#DIM ALL<br />
#INCLUDE "win32api.inc"<br />
<br />
' SOURCE CODE:<br />
<br />
Macro QPC = QueryPerformanceCounter qTimeNow<br />
<br />
Macro SetHiRes<br />
  MacroTemp Time<br />
  Dim Time As TIMECAPS<br />
  TimeGetDevCaps( Time, SizeOf(Time) )<br />
  TimeBeginPeriod(Time.wPeriodMin)<br />
  'Sleep 16 ' Pre Windows 10 - high resolution does not 'byte' until next clock tick.<br />
End Macro<br />
<br />
Macro RevokeHiRes<br />
  MacroTemp Time<br />
  Dim Time As TIMECAPS<br />
  TimeGetDevCaps( Time, SizeOf(Time) )<br />
  TimeEndPeriod(Time.wPeriodMin)<br />
End Macro<br />
<br />
Global qFreq As Quad<br />
<br />
Sub SleepX( ByVal n As Double )<br />
  Local qTarget, qTimeNow As Quad<br />
  QPC<br />
  qTarget = qTimeNow + n*qFreq*0.001<br />
  If n &lt;= 3 then<br />
    Do : QPC : Loop Until qTimeNow &gt;= qTarget ' Class A amplifier analogy<br />
  Else<br />
    ' Class B amplifier analogy followed by Classs A amplifier analogy<br />
    Sleep ( n-3 ) : Do : QPC : Loop Until qTimeNow &gt;= qTarget<br />
  End If<br />
End Sub<br />
<br />
' ====================<br />
<br />
' EXAMPLE USAGE:<br />
<br />
Function Pbmain () As Long<br />
Local i as Long<br />
Local qStart, qStop As Quad<br />
<br />
  QueryPerformanceFrequency qFreq<br />
<br />
  SetHiRes<br />
<br />
  QueryPerformanceCounter qStart<br />
    SleepX(0.25)<br />
  QueryPerformanceCounter qStop<br />
  Print "Quarter of a millisecond:";(qStop - qStart)*1000/qFreq;"ms"<br />
  Print<br />
<br />
  QueryPerformanceCounter qStart<br />
    SleepX(2000)<br />
  QueryPerformanceCounter qStop<br />
  Print "2 seconds:";(qStop - qStart)/qFreq;"s"<br />
  Print<br />
<br />
  QueryPerformanceCounter qStart<br />
    SleepX(1234)<br />
  QueryPerformanceCounter qStop<br />
  Print "Silly:";(qStop - qStart)*1000/qFreq;"ms"<br />
  Print<br />
<br />
  For i = 1 to 30<br />
    QueryPerformanceCounter qStart<br />
      SleepX(i)<br />
    QueryPerformanceCounter qStop<br />
    Print i;" ";(qStop - qStart)*1000/qFreq;"ms"<br />
  Next<br />
<br />
  RevokeHiRes<br />
<br />
  WaitKey&#36;<br />
<br />
End Function</code></div></div>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[PB 3.5 8-to-7 Bit Text Compression]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=50</link>
			<pubDate>Thu, 03 Apr 2025 21:55:44 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=140">Andy Dee</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=50</guid>
			<description><![CDATA[Hello everyone, <br />
I developed a method to compress short texts by reducing 8-bit characters to 7-bit. While the compression ratio is low for large files, it efficiently reduces small strings.  <br />
<br />
<span style="font-weight: bold;" class="mycode_b"><span style="text-decoration: underline;" class="mycode_u">Key Steps</span></span>:  <br />
<ol type="1" class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Normalization</span>: The input text is restricted <span style="font-weight: bold;" class="mycode_b">to 7-bit ASCII (32–127)</span>.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Offset Adjustment</span>: Each character is decremented by 32 to fit within 0–95. <br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Compression</span>: <br />
For every 8 bytes, extract bits 0–6 (discarding bit 7).Pack these 7-bit segments into 7 bytes, using all 8 bits per byte. <br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Remaining Bytes</span>: If the text length isn’t a multiple of 8, the last 1–7 bytes are copied uncompressed.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Decompression</span>: <br />
Reverse the process: Expand 7-bit segments to 8 bits, then increment by 32. <br />
</li>
</ol>
<br />
Here is the code, including some testing:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>&#36;Compile exe<br />
&#36;Dim All<br />
&#36;Lib All Off<br />
&#36;Error All Off<br />
&#36;String 2<br />
&#36;Com 0<br />
&#36;Float Procedure<br />
&#36;Optimize Size'Speed<br />
&#36;Cpu 80386<br />
&#36;Sound 1<br />
&#36;Stack 1536<br />
<br />
<br />
'----------------------------------------------------------------------------<br />
' Packe limitierten Text von 8 auf 7 Bit.<br />
' Nutze dafür Zeiger zum Erhöhen der Geschwindigkeit<br />
Declare _<br />
FUNCTION PackTxt87 (sQuelle AS STRING) AS STRING<br />
FUNCTION PackTxt87 (sQuelle AS STRING) AS STRING<br />
  ' lokale Variablen<br />
  DIM iQlen  AS LOCAL INTEGER          ' Länge von sQuelle<br />
  DIM Q_ptr  AS LOCAL BYTE PTR         ' Zeichen-Pointer auf sQuelle<br />
  Dim sZiel  As Local String           ' Komprimiertes sZiel<br />
  DIM iZlen  AS LOCAL INTEGER          ' Länge von sZiel<br />
  DIM Z_ptr  AS LOCAL BYTE PTR         ' Zeichen-Pointer auf sZiel<br />
  DIM iZPos  AS LOCAL INTEGER          ' Position in sZiel<br />
  DIM iQPos  AS LOCAL INTEGER          ' Position in sQuelle<br />
  DIM iQLoop AS LOCAL INTEGER          ' Anzahl KompressionsSchleifen<br />
  DIM iKompS AS LOCAL INTEGER          ' Anzahl komprimierbarer Zeichen<br />
  Dim iRest  As Local Integer          ' Anzahl unkomprimierbarer Zeichen<br />
  Dim bWert  As Local Byte             ' ASCII-32-Wert eines Zeichens.<br />
  Dim iBitQ  As Local Integer          ' Aktuelle Bitbosition in sQuelle<br />
  Dim iBitZ  As Local Integer          ' Aktuelle Bitbosition in sZiel<br />
  Dim sTemp  As Local String<br />
<br />
                                       '<br />
  iQlen = LEN(sQuelle)                 ' Hole Länge der Quelle<br />
<br />
  '--------------------------------------------------------------------------<br />
  ' Fehler 1 abfangen<br />
  If iQlen &lt; 8 Then _                  ' String zu kurz?<br />
    Function = sQuelle: _            &nbsp;&nbsp;' Gib Original zurück<br />
    Exit Function                    &nbsp;&nbsp;' und raus<br />
<br />
  iQLoop  = Fix(iQlen/8)               ' Hole Anzahl KompressionsSchleifen<br />
  iRest  = iQlen Mod 8                 ' Hole Anzahl unkomprimierbarer Zeichen<br />
  iKompS = iQLoop * 8                  ' Hole Anzahl komprimierbarer Zeichen<br />
  iZlen  = iQLoop * 7 + iRest          ' Hole Länge komprimierten Strings<br />
                                       '<br />
  DECR iQlen                           ' Null-basiert für Pointer<br />
  Q_ptr = STRPTR32( sQuelle )          ' Setze Pointer auf sQuelle<br />
<br />
  '--------------------------------------------------------------------------<br />
  ' Fehler 2 abfangen<br />
  For iQPos = 0 To iQlen               ' Überprüfe auf ungültige Zeichen<br />
    IF @Q_ptr[iQPos] &lt; 32 _<br />
      Or @Q_ptr[iQPos] &gt; 159 _<br />
      THEN<br />
      sZiel = sZiel _                  ' sammle diese <br />
            + Chr&#36;(@Q_ptr[iQPos])<br />
    End If<br />
  Next iQPos<br />
<br />
  If Len(sZiel) Then _<br />
    Function = "Falsche Quelle: "+sZiel : _<br />
    Exit Function                    &nbsp;&nbsp;' und gib sie zurück<br />
<br />
  sZiel = Repeat&#36;(iZlen,Chr&#36;(0))       ' Platz für Kompression in sZiel<br />
<br />
  Z_ptr = STRPTR32( sZiel  )           ' Setze Pointer auf sZiel<br />
  iZPos = 0                            ' Position im Pointer = 0<br />
  iBitQ = 0                            ' Bitbosition in sQuelle auf 0<br />
  iBitZ = 0                            ' Bitbosition in sZiel  auf 0<br />
<br />
  DECR iKompS                          ' Null-basiert für Pointer<br />
<br />
  '--------------------------------------------------------------------------<br />
  ' Kompression:<br />
  For iQPos = 0 To iKompS              ' Für komprimierbare Zeichen in sQuelle:<br />
                                       ' 1. Schritt:<br />
    bWert = @Q_ptr[iQPos] - 32         ' ASCII-Wert um 32 vermindern, dass<br />
                                       ' ASCII zwischen 0-159 ist<br />
    For iBitQ = 0 To 6                 ' Lies aus sQuelle Bit 0-6<br />
<br />
      If Bit(bWert,iBitQ) = 1 Then     ' Schreibe Ziel-Bits 0-7<br />
        Bit Set @Z_ptr[iZPos], iBitZ   '<br />
      Else                             '<br />
        Bit ReSet @Z_ptr[iZPos], iBitZ '<br />
      End If<br />
<br />
      If iBitZ &lt; 7 Then                ' Solange in Ziel zwischen Bit 0 und 7<br />
        Incr iBitZ                     ' Nächstes Bit<br />
      Else                             ' sonst<br />
        iBitZ = 0                      ' Bit 0 in<br />
        Incr iZPos                     ' nächstem Zeichen<br />
      End If<br />
    Next iBitQ<br />
  Next iQPos<br />
<br />
  iZPos = iZlen-iRest                  ' Setze Zeiger auf erstes unpackbares<br />
                                       ' Zeichen<br />
  For iQPos = iQlen-iRest+1 To iQlen   ' Für unpackbare Zeichen in sQuelle<br />
    @Z_ptr[iZPos] = @Q_ptr[iQPos]      ' Schreibe unpackbare Zeichen<br />
    Incr iZPos<br />
  Next iQPos<br />
<br />
<br />
  Function = sZiel<br />
<br />
END FUNCTION<br />
<br />
<br />
'----------------------------------------------------------------------------<br />
' EntPacke limitierten Text von 8 auf 7 Bit.<br />
' Nutze dafür Zeiger zum Erhöhen der Geschwindigkeit<br />
Declare _<br />
FUNCTION DePackTxt87 (sQuelle AS STRING) AS STRING<br />
FUNCTION DePackTxt87 (sQuelle AS STRING) AS STRING<br />
  ' lokale Variablen<br />
  DIM iQlen  AS LOCAL INTEGER          ' Länge von sQuelle<br />
  DIM Q_ptr  AS LOCAL BYTE PTR         ' Zeichen-Pointer auf sQuelle<br />
  Dim sZiel  As Local String           ' Komprimiertes sZiel<br />
  DIM iZlen  AS LOCAL INTEGER          ' Länge von sZiel<br />
  DIM Z_ptr  AS LOCAL BYTE PTR         ' Zeichen-Pointer auf sZiel<br />
  DIM iZPos  AS LOCAL INTEGER          ' Position in sZiel<br />
  DIM iQPos  AS LOCAL INTEGER          ' Position in sQuelle<br />
  DIM iQLoop AS LOCAL INTEGER          ' Anzahl KompressionsSchleifen<br />
  DIM iKompS AS LOCAL INTEGER          ' Anzahl komprimierbarer Zeichen<br />
  Dim iRest  As Local Integer          ' Anzahl unkomprimierbarer Zeichen<br />
  Dim bWert  As Local Byte             ' ASCII-32-Wert eines Zeichens.<br />
  Dim iBitQ  As Local Integer          ' Aktuelle Bitbosition in sQuelle<br />
  Dim iBitZ  As Local Integer          ' Aktuelle Bitbosition in sZiel<br />
  Dim sTemp  As Local String<br />
  Dim bPack  As Local Byte             ' Gepackte Bytes an sQuelleEnde<br />
<br />
  iQlen = LEN(sQuelle)                 ' Hole Länge der Quelle<br />
<br />
  '--------------------------------------------------------------------------<br />
  ' Fehler 1 abfangen<br />
  If iQlen &lt; 7 Then _                  ' String zu kurz?<br />
    Function = sQuelle: _            &nbsp;&nbsp;' Gib Original zurück<br />
    Exit Function                    &nbsp;&nbsp;' und raus<br />
<br />
  Q_ptr = STRPTR32( sQuelle )          ' Setze Pointer auf sQuelle<br />
<br />
  iQLoop  = Fix(iQlen/7)               ' Hole Anzahl EntPacksSchleifen<br />
  iRest  = iQlen Mod 7                 ' Anzahl ungepackte Zeichen?<br />
<br />
  '--------------------------------------------------------------------------<br />
  ' Fehler 2 abfangen<br />
<br />
If iRest = 0 Then                      ' Scheint alles gepackt zu sein, dann<br />
  bPack = 0                            ' GePacktes = 0<br />
  For iQPos = iQlen - 7 To iQlen-1     ' Überprfe letzte 7 Zeichen auf<br />
    IF @Q_ptr[iQPos] &lt; 32 _            ' komprimierte Zeichen<br />
      Or @Q_ptr[iQPos] &gt; 127 _         ' Wenn welche da, dann<br />
      THEN Incr bPack                &nbsp;&nbsp;' Erhöhe Gepacktes (bPack)<br />
  Next iQPos<br />
End If<br />
<br />
<br />
  If bPack = 0? _                      ' Wenn keine gepackten Zeichen (bPack)<br />
    And iRest = 0 _                  &nbsp;&nbsp;' und keine ungepackten Zeichen (iRest)<br />
    Then                               ' dann<br />
    iRest = 7                          ' ungepackte Zeichen (iRest) = 7<br />
    iKompS = iQlen - iRest             ' Setze Anzahl entpackbarer Zeichen<br />
  Else                                 ' sonst<br />
    iKompS = iQLoop * 7                ' Setze Anzahl entpackbarer Zeichen<br />
  End If<br />
<br />
<br />
  If IsFalse(bPack) _                  ' Sind die iRest-lichen Zeichen<br />
    And iRest = 7 _                  &nbsp;&nbsp;' ungepackt, dann reduziere<br />
    Then Decr iQLoop                   ' die Anzahl der Entpackaufrufe<br />
<br />
<br />
  iZlen  = iQLoop * 8 + iRest          ' Hole Länge komprimierten Strings<br />
                                       '<br />
  DECR iQlen                           ' Null-basiert für Pointer<br />
<br />
  sZiel = Repeat&#36;(iZlen,Chr&#36;(0))       ' Platz für Kompression in sZiel<br />
<br />
  Z_ptr = STRPTR32( sZiel  )           ' Setze Pointer auf sZiel<br />
  iZPos = 0                            ' Position im Pointer = 0<br />
  iBitQ = 0                            ' Bitbosition in sQuelle auf 0<br />
  iBitZ = 0                            ' Bitbosition in sZiel  auf 0<br />
<br />
  '--------------------------------------------------------------------------<br />
  ' DeKompression: Rechne Zeichen in sQuelle auf ASCII 32-159 um<br />
  For iQPos = 0 To iKompS              ' Fr entkomprimierbare Zeichen in sQuelle:<br />
                                       ' 1. Schritt:<br />
    For iBitQ = 0 To 7                 ' Lies aus sQuelle Bit 0-7<br />
<br />
      If Bit(@Q_ptr[iQPos],iBitQ) = 1 Then ' Schreibe Ziel-Bits 0-7<br />
        Bit Set @Z_ptr[iZPos], iBitZ   '<br />
      Else                             '<br />
        Bit ReSet @Z_ptr[iZPos], iBitZ '<br />
      End If<br />
<br />
      If iBitZ &lt; 6 Then                ' Solange in Ziel zwischen Bit 0 und 6<br />
        Incr iBitZ                     ' Nächstes Bit<br />
      Else                             ' sonst<br />
        iBitZ = 0                      ' Bit 0<br />
        Incr @Z_ptr[iZPos], 32         ' Erstelle das richtige ZielZeichen<br />
        Incr iZPos                     ' und nächstes ZielZeichen<br />
      End If<br />
<br />
    Next iBitQ<br />
<br />
  Next iQPos<br />
<br />
  iZPos = iZlen-iRest                  ' Setze Zeiger auf erstes unpackbares<br />
                                       ' Zeichen<br />
  For iQPos = iQlen-iRest+1 To iQlen   ' Für ungepackte Zeichen in sQuelle<br />
    @Z_ptr[iZPos] = @Q_ptr[iQPos]      ' Schreibe ungepackte Zeichen<br />
    Incr iZPos<br />
  Next iQPos<br />
<br />
<br />
  Function = sZiel<br />
<br />
END FUNCTION<br />
<br />
<br />
' Test-Code'<br />
<br />
DIM originalText AS STRING, _<br />
    compressedText AS STRING, _<br />
    decompressedText AS STRING, _<br />
    iIndex As Integer, _<br />
    iLOrg  As Integer, _<br />
    iLPck  As Integer, _<br />
    iLEpk  As Integer<br />
<br />
For iIndex = 07 To 19<br />
  '?<br />
  originalText = Left&#36;("PowerBASIC 3.5 is a wonderful tool for writing fast and usefull programs.",iIndex)<br />
<br />
  iLOrg = Len(originalText)<br />
  compressedText = PackTxt87(originalText)<br />
  iLPck = Len(compressedText)<br />
  If Instr(compressedText, "Falsche Quelle: ") = 1 Then ?compressedText:end<br />
  decompressedText = DePackTxt87(compressedText)<br />
  iLEpk = Len(decompressedText)<br />
  'PRINT "Original:      ";<br />
  PRINT "Original: ";<br />
  Print originalText;<br />
  &#36;If 0<br />
  Print " ist =";iLOrg;" Byte lang"<br />
  PRINT "Komprimiert:  ";<br />
  Print compressedText;<br />
  Print " ist =";iLPck;" Byte lang"<br />
  PRINT "Dekomprimiert: ";<br />
  Print decompressedText;<br />
  Print " ist =";iLEpk;" Byte lang"<br />
  &#36;EndIf<br />
  ? " --&gt; Test verlief: [erfolg";<br />
  If originalText = decompressedText Then<br />
    Print "reich] :-)  :";iLOrg;iLPck;iLEpk<br />
  Else<br />
  Print "los] ;-(";iLOrg;iLPck;iLEpk<br />
  ?"Original und wieder Entpacktes unterscheiden sich an folgenden Stellen:"<br />
  For iIndex = 1 To iLOrg<br />
    If ASCII(originalText,iIndex) &lt;&gt; ASCII(decompressedText,iIndex) Then<br />
        ?"Position";iIndex;" - Zeichen Original: ";Chr&#36;(ASCII(originalText,iIndex)); _<br />
        " - Zeichen Entpackt: ";Chr&#36;(ASCII(decompressedText,iIndex))<br />
      End If<br />
    Next iIndex<br />
  End If<br />
<br />
Next iIndex<br />
<br />
Sleep<br />
<br />
originalText = "PowerBASIC 3.5 is a wonderful tool for writing fast, small and usefull programs under DOS."<br />
iLOrg = Len(originalText)<br />
compressedText = PackTxt87(originalText)<br />
iLPck = Len(compressedText)<br />
If Instr(compressedText, "Falsche Quelle: ") = 1 Then ?compressedText:end<br />
decompressedText = DePackTxt87(compressedText)<br />
iLEpk = Len(decompressedText)<br />
  <br />
  PRINT "Original:"<br />
  Print originalText;<br />
  ? " --&gt; Test verlief: [erfolg";<br />
  If originalText = decompressedText Then<br />
    Print "reich] :-)  :";iLOrg;iLPck;iLEpk<br />
  Else<br />
  Print "los] ;-(";iLOrg;iLPck;iLEpk<br />
  ?"Original und wieder Entpacktes unterscheiden sich an folgenden Stellen:"<br />
  For iIndex = 1 To iLOrg<br />
    If ASCII(originalText,iIndex) &lt;&gt; ASCII(decompressedText,iIndex) Then<br />
        ?"Position";iIndex;" - Zeichen Original: ";Chr&#36;(ASCII(originalText,iIndex)); _<br />
        " - Zeichen Entpackt: ";Chr&#36;(ASCII(decompressedText,iIndex))<br />
      End If<br />
    Next iIndex<br />
  End If<br />
  <br />
  PRINT "Komprimiert:"<br />
  Print compressedText<br />
  Print "ist =";iLPck;" Byte lang"<br />
  PRINT "Dekomprimiert:"<br />
  Print decompressedText<br />
  Print "ist =";iLEpk;" Byte lang"<br />
<br />
<br />
<br />
originalText = "PowerBASIC 3.5 ist ein wundervolles Werkzeug fr schnelle, kleine bis groáe und vor allen ¯ntzliche® Programme unter DOS."<br />
?originalText<br />
iLOrg = Len(originalText)<br />
compressedText = PackTxt87(originalText)<br />
iLPck = Len(compressedText)<br />
If Instr(compressedText, "Falsche Quelle: ") = 1 Then ?compressedText:end<br />
decompressedText = DePackTxt87(compressedText)<br />
iLEpk = Len(decompressedText)<br />
  <br />
  PRINT "Original:"<br />
  Print originalText;<br />
  ? " --&gt; Test verlief: [erfolg";<br />
  If originalText = decompressedText Then<br />
    Print "reich] :-)  :";iLOrg;iLPck;iLEpk<br />
  Else<br />
  Print "los] ;-(";iLOrg;iLPck;iLEpk<br />
  ?"Original und wieder Entpacktes unterscheiden sich an folgenden Stellen:"<br />
  For iIndex = 1 To iLOrg<br />
    If ASCII(originalText,iIndex) &lt;&gt; ASCII(decompressedText,iIndex) Then<br />
        ?"Position";iIndex;" - Zeichen Original: ";Chr&#36;(ASCII(originalText,iIndex)); _<br />
        " - Zeichen Entpackt: ";Chr&#36;(ASCII(decompressedText,iIndex))<br />
      End If<br />
    Next iIndex<br />
  End If<br />
  <br />
  PRINT "Komprimiert:"<br />
  Print compressedText<br />
  Print "ist =";iLPck;" Byte lang"<br />
  PRINT "Dekomprimiert:"<br />
  Print decompressedText<br />
  Print "ist =";iLEpk;" Byte lang"</code></div></div><br />
<br />
This is the DOS-output:<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>Original: PowerBA --&gt; Test verlief: [erfolgreich] :-)  : 7  7  7<br />
Original: PowerBAS --&gt; Test verlief: [erfolgreich] :-)  : 8  7  8<br />
Original: PowerBASI --&gt; Test verlief: [erfolgreich] :-)  : 9  8  9<br />
Original: PowerBASIC --&gt; Test verlief: [erfolgreich] :-)  : 10  9  10<br />
Original: PowerBASIC  --&gt; Test verlief: [erfolgreich] :-)  : 11  10  11<br />
Original: PowerBASIC 3 --&gt; Test verlief: [erfolgreich] :-)  : 12  11  12<br />
Original: PowerBASIC 3. --&gt; Test verlief: [erfolgreich] :-)  : 13  12  13<br />
Original: PowerBASIC 3.5 --&gt; Test verlief: [erfolgreich] :-)  : 14  13  14<br />
Original: PowerBASIC 3.5  --&gt; Test verlief: [erfolgreich] :-)  : 15  14  15<br />
Original: PowerBASIC 3.5 i --&gt; Test verlief: [erfolgreich] :-)  : 16  14  16<br />
Original: PowerBASIC 3.5 is --&gt; Test verlief: [erfolgreich] :-)  : 17  15  17<br />
Original: PowerBASIC 3.5 is  --&gt; Test verlief: [erfolgreich] :-)  : 18  16  18<br />
Original: PowerBASIC 3.5 is a --&gt; Test verlief: [erfolgreich] :-)  : 19  17  19<br />
<br />
Original:PowerBASIC 3.5 is a wonderful tool for writing fast, small and usefull<br />
programs under DOS. --&gt; Test verlief: [erfolgreich] :-)  : 90  79  90<br />
Komprimiert:<br />
░τ╡(§àf⌐◄`Γ¿ ÆS@►p}:ëE⌐▒╩♦PƒO&amp;└°ö☻«╥&#36;5Θ&lt;☻î┴)ò☺ÿ6âL&amp; Φ&#36;☻¬╙ó▒╩d☻á╥τQ→lN☺U'▒(♣É^S.<br />
ist = 79  Byte lang<br />
Dekomprimiert:<br />
PowerBASIC 3.5 is a wonderful tool for writing fast, small and usefull programs<br />
under DOS.<br />
ist = 90  Byte lang<br />
PowerBASIC 3.5 ist ein wundervolles Werkzeug für schnelle, kleine bis große und<br />
vor allen »nützliche« Programme unter DOS. <br />
Falsche Quelle: ß»« </code></div></div> <br />
Perhaps the code will also be of use to others...]]></description>
			<content:encoded><![CDATA[Hello everyone, <br />
I developed a method to compress short texts by reducing 8-bit characters to 7-bit. While the compression ratio is low for large files, it efficiently reduces small strings.  <br />
<br />
<span style="font-weight: bold;" class="mycode_b"><span style="text-decoration: underline;" class="mycode_u">Key Steps</span></span>:  <br />
<ol type="1" class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Normalization</span>: The input text is restricted <span style="font-weight: bold;" class="mycode_b">to 7-bit ASCII (32–127)</span>.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Offset Adjustment</span>: Each character is decremented by 32 to fit within 0–95. <br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Compression</span>: <br />
For every 8 bytes, extract bits 0–6 (discarding bit 7).Pack these 7-bit segments into 7 bytes, using all 8 bits per byte. <br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Remaining Bytes</span>: If the text length isn’t a multiple of 8, the last 1–7 bytes are copied uncompressed.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Decompression</span>: <br />
Reverse the process: Expand 7-bit segments to 8 bits, then increment by 32. <br />
</li>
</ol>
<br />
Here is the code, including some testing:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>&#36;Compile exe<br />
&#36;Dim All<br />
&#36;Lib All Off<br />
&#36;Error All Off<br />
&#36;String 2<br />
&#36;Com 0<br />
&#36;Float Procedure<br />
&#36;Optimize Size'Speed<br />
&#36;Cpu 80386<br />
&#36;Sound 1<br />
&#36;Stack 1536<br />
<br />
<br />
'----------------------------------------------------------------------------<br />
' Packe limitierten Text von 8 auf 7 Bit.<br />
' Nutze dafür Zeiger zum Erhöhen der Geschwindigkeit<br />
Declare _<br />
FUNCTION PackTxt87 (sQuelle AS STRING) AS STRING<br />
FUNCTION PackTxt87 (sQuelle AS STRING) AS STRING<br />
  ' lokale Variablen<br />
  DIM iQlen  AS LOCAL INTEGER          ' Länge von sQuelle<br />
  DIM Q_ptr  AS LOCAL BYTE PTR         ' Zeichen-Pointer auf sQuelle<br />
  Dim sZiel  As Local String           ' Komprimiertes sZiel<br />
  DIM iZlen  AS LOCAL INTEGER          ' Länge von sZiel<br />
  DIM Z_ptr  AS LOCAL BYTE PTR         ' Zeichen-Pointer auf sZiel<br />
  DIM iZPos  AS LOCAL INTEGER          ' Position in sZiel<br />
  DIM iQPos  AS LOCAL INTEGER          ' Position in sQuelle<br />
  DIM iQLoop AS LOCAL INTEGER          ' Anzahl KompressionsSchleifen<br />
  DIM iKompS AS LOCAL INTEGER          ' Anzahl komprimierbarer Zeichen<br />
  Dim iRest  As Local Integer          ' Anzahl unkomprimierbarer Zeichen<br />
  Dim bWert  As Local Byte             ' ASCII-32-Wert eines Zeichens.<br />
  Dim iBitQ  As Local Integer          ' Aktuelle Bitbosition in sQuelle<br />
  Dim iBitZ  As Local Integer          ' Aktuelle Bitbosition in sZiel<br />
  Dim sTemp  As Local String<br />
<br />
                                       '<br />
  iQlen = LEN(sQuelle)                 ' Hole Länge der Quelle<br />
<br />
  '--------------------------------------------------------------------------<br />
  ' Fehler 1 abfangen<br />
  If iQlen &lt; 8 Then _                  ' String zu kurz?<br />
    Function = sQuelle: _            &nbsp;&nbsp;' Gib Original zurück<br />
    Exit Function                    &nbsp;&nbsp;' und raus<br />
<br />
  iQLoop  = Fix(iQlen/8)               ' Hole Anzahl KompressionsSchleifen<br />
  iRest  = iQlen Mod 8                 ' Hole Anzahl unkomprimierbarer Zeichen<br />
  iKompS = iQLoop * 8                  ' Hole Anzahl komprimierbarer Zeichen<br />
  iZlen  = iQLoop * 7 + iRest          ' Hole Länge komprimierten Strings<br />
                                       '<br />
  DECR iQlen                           ' Null-basiert für Pointer<br />
  Q_ptr = STRPTR32( sQuelle )          ' Setze Pointer auf sQuelle<br />
<br />
  '--------------------------------------------------------------------------<br />
  ' Fehler 2 abfangen<br />
  For iQPos = 0 To iQlen               ' Überprüfe auf ungültige Zeichen<br />
    IF @Q_ptr[iQPos] &lt; 32 _<br />
      Or @Q_ptr[iQPos] &gt; 159 _<br />
      THEN<br />
      sZiel = sZiel _                  ' sammle diese <br />
            + Chr&#36;(@Q_ptr[iQPos])<br />
    End If<br />
  Next iQPos<br />
<br />
  If Len(sZiel) Then _<br />
    Function = "Falsche Quelle: "+sZiel : _<br />
    Exit Function                    &nbsp;&nbsp;' und gib sie zurück<br />
<br />
  sZiel = Repeat&#36;(iZlen,Chr&#36;(0))       ' Platz für Kompression in sZiel<br />
<br />
  Z_ptr = STRPTR32( sZiel  )           ' Setze Pointer auf sZiel<br />
  iZPos = 0                            ' Position im Pointer = 0<br />
  iBitQ = 0                            ' Bitbosition in sQuelle auf 0<br />
  iBitZ = 0                            ' Bitbosition in sZiel  auf 0<br />
<br />
  DECR iKompS                          ' Null-basiert für Pointer<br />
<br />
  '--------------------------------------------------------------------------<br />
  ' Kompression:<br />
  For iQPos = 0 To iKompS              ' Für komprimierbare Zeichen in sQuelle:<br />
                                       ' 1. Schritt:<br />
    bWert = @Q_ptr[iQPos] - 32         ' ASCII-Wert um 32 vermindern, dass<br />
                                       ' ASCII zwischen 0-159 ist<br />
    For iBitQ = 0 To 6                 ' Lies aus sQuelle Bit 0-6<br />
<br />
      If Bit(bWert,iBitQ) = 1 Then     ' Schreibe Ziel-Bits 0-7<br />
        Bit Set @Z_ptr[iZPos], iBitZ   '<br />
      Else                             '<br />
        Bit ReSet @Z_ptr[iZPos], iBitZ '<br />
      End If<br />
<br />
      If iBitZ &lt; 7 Then                ' Solange in Ziel zwischen Bit 0 und 7<br />
        Incr iBitZ                     ' Nächstes Bit<br />
      Else                             ' sonst<br />
        iBitZ = 0                      ' Bit 0 in<br />
        Incr iZPos                     ' nächstem Zeichen<br />
      End If<br />
    Next iBitQ<br />
  Next iQPos<br />
<br />
  iZPos = iZlen-iRest                  ' Setze Zeiger auf erstes unpackbares<br />
                                       ' Zeichen<br />
  For iQPos = iQlen-iRest+1 To iQlen   ' Für unpackbare Zeichen in sQuelle<br />
    @Z_ptr[iZPos] = @Q_ptr[iQPos]      ' Schreibe unpackbare Zeichen<br />
    Incr iZPos<br />
  Next iQPos<br />
<br />
<br />
  Function = sZiel<br />
<br />
END FUNCTION<br />
<br />
<br />
'----------------------------------------------------------------------------<br />
' EntPacke limitierten Text von 8 auf 7 Bit.<br />
' Nutze dafür Zeiger zum Erhöhen der Geschwindigkeit<br />
Declare _<br />
FUNCTION DePackTxt87 (sQuelle AS STRING) AS STRING<br />
FUNCTION DePackTxt87 (sQuelle AS STRING) AS STRING<br />
  ' lokale Variablen<br />
  DIM iQlen  AS LOCAL INTEGER          ' Länge von sQuelle<br />
  DIM Q_ptr  AS LOCAL BYTE PTR         ' Zeichen-Pointer auf sQuelle<br />
  Dim sZiel  As Local String           ' Komprimiertes sZiel<br />
  DIM iZlen  AS LOCAL INTEGER          ' Länge von sZiel<br />
  DIM Z_ptr  AS LOCAL BYTE PTR         ' Zeichen-Pointer auf sZiel<br />
  DIM iZPos  AS LOCAL INTEGER          ' Position in sZiel<br />
  DIM iQPos  AS LOCAL INTEGER          ' Position in sQuelle<br />
  DIM iQLoop AS LOCAL INTEGER          ' Anzahl KompressionsSchleifen<br />
  DIM iKompS AS LOCAL INTEGER          ' Anzahl komprimierbarer Zeichen<br />
  Dim iRest  As Local Integer          ' Anzahl unkomprimierbarer Zeichen<br />
  Dim bWert  As Local Byte             ' ASCII-32-Wert eines Zeichens.<br />
  Dim iBitQ  As Local Integer          ' Aktuelle Bitbosition in sQuelle<br />
  Dim iBitZ  As Local Integer          ' Aktuelle Bitbosition in sZiel<br />
  Dim sTemp  As Local String<br />
  Dim bPack  As Local Byte             ' Gepackte Bytes an sQuelleEnde<br />
<br />
  iQlen = LEN(sQuelle)                 ' Hole Länge der Quelle<br />
<br />
  '--------------------------------------------------------------------------<br />
  ' Fehler 1 abfangen<br />
  If iQlen &lt; 7 Then _                  ' String zu kurz?<br />
    Function = sQuelle: _            &nbsp;&nbsp;' Gib Original zurück<br />
    Exit Function                    &nbsp;&nbsp;' und raus<br />
<br />
  Q_ptr = STRPTR32( sQuelle )          ' Setze Pointer auf sQuelle<br />
<br />
  iQLoop  = Fix(iQlen/7)               ' Hole Anzahl EntPacksSchleifen<br />
  iRest  = iQlen Mod 7                 ' Anzahl ungepackte Zeichen?<br />
<br />
  '--------------------------------------------------------------------------<br />
  ' Fehler 2 abfangen<br />
<br />
If iRest = 0 Then                      ' Scheint alles gepackt zu sein, dann<br />
  bPack = 0                            ' GePacktes = 0<br />
  For iQPos = iQlen - 7 To iQlen-1     ' Überprfe letzte 7 Zeichen auf<br />
    IF @Q_ptr[iQPos] &lt; 32 _            ' komprimierte Zeichen<br />
      Or @Q_ptr[iQPos] &gt; 127 _         ' Wenn welche da, dann<br />
      THEN Incr bPack                &nbsp;&nbsp;' Erhöhe Gepacktes (bPack)<br />
  Next iQPos<br />
End If<br />
<br />
<br />
  If bPack = 0? _                      ' Wenn keine gepackten Zeichen (bPack)<br />
    And iRest = 0 _                  &nbsp;&nbsp;' und keine ungepackten Zeichen (iRest)<br />
    Then                               ' dann<br />
    iRest = 7                          ' ungepackte Zeichen (iRest) = 7<br />
    iKompS = iQlen - iRest             ' Setze Anzahl entpackbarer Zeichen<br />
  Else                                 ' sonst<br />
    iKompS = iQLoop * 7                ' Setze Anzahl entpackbarer Zeichen<br />
  End If<br />
<br />
<br />
  If IsFalse(bPack) _                  ' Sind die iRest-lichen Zeichen<br />
    And iRest = 7 _                  &nbsp;&nbsp;' ungepackt, dann reduziere<br />
    Then Decr iQLoop                   ' die Anzahl der Entpackaufrufe<br />
<br />
<br />
  iZlen  = iQLoop * 8 + iRest          ' Hole Länge komprimierten Strings<br />
                                       '<br />
  DECR iQlen                           ' Null-basiert für Pointer<br />
<br />
  sZiel = Repeat&#36;(iZlen,Chr&#36;(0))       ' Platz für Kompression in sZiel<br />
<br />
  Z_ptr = STRPTR32( sZiel  )           ' Setze Pointer auf sZiel<br />
  iZPos = 0                            ' Position im Pointer = 0<br />
  iBitQ = 0                            ' Bitbosition in sQuelle auf 0<br />
  iBitZ = 0                            ' Bitbosition in sZiel  auf 0<br />
<br />
  '--------------------------------------------------------------------------<br />
  ' DeKompression: Rechne Zeichen in sQuelle auf ASCII 32-159 um<br />
  For iQPos = 0 To iKompS              ' Fr entkomprimierbare Zeichen in sQuelle:<br />
                                       ' 1. Schritt:<br />
    For iBitQ = 0 To 7                 ' Lies aus sQuelle Bit 0-7<br />
<br />
      If Bit(@Q_ptr[iQPos],iBitQ) = 1 Then ' Schreibe Ziel-Bits 0-7<br />
        Bit Set @Z_ptr[iZPos], iBitZ   '<br />
      Else                             '<br />
        Bit ReSet @Z_ptr[iZPos], iBitZ '<br />
      End If<br />
<br />
      If iBitZ &lt; 6 Then                ' Solange in Ziel zwischen Bit 0 und 6<br />
        Incr iBitZ                     ' Nächstes Bit<br />
      Else                             ' sonst<br />
        iBitZ = 0                      ' Bit 0<br />
        Incr @Z_ptr[iZPos], 32         ' Erstelle das richtige ZielZeichen<br />
        Incr iZPos                     ' und nächstes ZielZeichen<br />
      End If<br />
<br />
    Next iBitQ<br />
<br />
  Next iQPos<br />
<br />
  iZPos = iZlen-iRest                  ' Setze Zeiger auf erstes unpackbares<br />
                                       ' Zeichen<br />
  For iQPos = iQlen-iRest+1 To iQlen   ' Für ungepackte Zeichen in sQuelle<br />
    @Z_ptr[iZPos] = @Q_ptr[iQPos]      ' Schreibe ungepackte Zeichen<br />
    Incr iZPos<br />
  Next iQPos<br />
<br />
<br />
  Function = sZiel<br />
<br />
END FUNCTION<br />
<br />
<br />
' Test-Code'<br />
<br />
DIM originalText AS STRING, _<br />
    compressedText AS STRING, _<br />
    decompressedText AS STRING, _<br />
    iIndex As Integer, _<br />
    iLOrg  As Integer, _<br />
    iLPck  As Integer, _<br />
    iLEpk  As Integer<br />
<br />
For iIndex = 07 To 19<br />
  '?<br />
  originalText = Left&#36;("PowerBASIC 3.5 is a wonderful tool for writing fast and usefull programs.",iIndex)<br />
<br />
  iLOrg = Len(originalText)<br />
  compressedText = PackTxt87(originalText)<br />
  iLPck = Len(compressedText)<br />
  If Instr(compressedText, "Falsche Quelle: ") = 1 Then ?compressedText:end<br />
  decompressedText = DePackTxt87(compressedText)<br />
  iLEpk = Len(decompressedText)<br />
  'PRINT "Original:      ";<br />
  PRINT "Original: ";<br />
  Print originalText;<br />
  &#36;If 0<br />
  Print " ist =";iLOrg;" Byte lang"<br />
  PRINT "Komprimiert:  ";<br />
  Print compressedText;<br />
  Print " ist =";iLPck;" Byte lang"<br />
  PRINT "Dekomprimiert: ";<br />
  Print decompressedText;<br />
  Print " ist =";iLEpk;" Byte lang"<br />
  &#36;EndIf<br />
  ? " --&gt; Test verlief: [erfolg";<br />
  If originalText = decompressedText Then<br />
    Print "reich] :-)  :";iLOrg;iLPck;iLEpk<br />
  Else<br />
  Print "los] ;-(";iLOrg;iLPck;iLEpk<br />
  ?"Original und wieder Entpacktes unterscheiden sich an folgenden Stellen:"<br />
  For iIndex = 1 To iLOrg<br />
    If ASCII(originalText,iIndex) &lt;&gt; ASCII(decompressedText,iIndex) Then<br />
        ?"Position";iIndex;" - Zeichen Original: ";Chr&#36;(ASCII(originalText,iIndex)); _<br />
        " - Zeichen Entpackt: ";Chr&#36;(ASCII(decompressedText,iIndex))<br />
      End If<br />
    Next iIndex<br />
  End If<br />
<br />
Next iIndex<br />
<br />
Sleep<br />
<br />
originalText = "PowerBASIC 3.5 is a wonderful tool for writing fast, small and usefull programs under DOS."<br />
iLOrg = Len(originalText)<br />
compressedText = PackTxt87(originalText)<br />
iLPck = Len(compressedText)<br />
If Instr(compressedText, "Falsche Quelle: ") = 1 Then ?compressedText:end<br />
decompressedText = DePackTxt87(compressedText)<br />
iLEpk = Len(decompressedText)<br />
  <br />
  PRINT "Original:"<br />
  Print originalText;<br />
  ? " --&gt; Test verlief: [erfolg";<br />
  If originalText = decompressedText Then<br />
    Print "reich] :-)  :";iLOrg;iLPck;iLEpk<br />
  Else<br />
  Print "los] ;-(";iLOrg;iLPck;iLEpk<br />
  ?"Original und wieder Entpacktes unterscheiden sich an folgenden Stellen:"<br />
  For iIndex = 1 To iLOrg<br />
    If ASCII(originalText,iIndex) &lt;&gt; ASCII(decompressedText,iIndex) Then<br />
        ?"Position";iIndex;" - Zeichen Original: ";Chr&#36;(ASCII(originalText,iIndex)); _<br />
        " - Zeichen Entpackt: ";Chr&#36;(ASCII(decompressedText,iIndex))<br />
      End If<br />
    Next iIndex<br />
  End If<br />
  <br />
  PRINT "Komprimiert:"<br />
  Print compressedText<br />
  Print "ist =";iLPck;" Byte lang"<br />
  PRINT "Dekomprimiert:"<br />
  Print decompressedText<br />
  Print "ist =";iLEpk;" Byte lang"<br />
<br />
<br />
<br />
originalText = "PowerBASIC 3.5 ist ein wundervolles Werkzeug fr schnelle, kleine bis groáe und vor allen ¯ntzliche® Programme unter DOS."<br />
?originalText<br />
iLOrg = Len(originalText)<br />
compressedText = PackTxt87(originalText)<br />
iLPck = Len(compressedText)<br />
If Instr(compressedText, "Falsche Quelle: ") = 1 Then ?compressedText:end<br />
decompressedText = DePackTxt87(compressedText)<br />
iLEpk = Len(decompressedText)<br />
  <br />
  PRINT "Original:"<br />
  Print originalText;<br />
  ? " --&gt; Test verlief: [erfolg";<br />
  If originalText = decompressedText Then<br />
    Print "reich] :-)  :";iLOrg;iLPck;iLEpk<br />
  Else<br />
  Print "los] ;-(";iLOrg;iLPck;iLEpk<br />
  ?"Original und wieder Entpacktes unterscheiden sich an folgenden Stellen:"<br />
  For iIndex = 1 To iLOrg<br />
    If ASCII(originalText,iIndex) &lt;&gt; ASCII(decompressedText,iIndex) Then<br />
        ?"Position";iIndex;" - Zeichen Original: ";Chr&#36;(ASCII(originalText,iIndex)); _<br />
        " - Zeichen Entpackt: ";Chr&#36;(ASCII(decompressedText,iIndex))<br />
      End If<br />
    Next iIndex<br />
  End If<br />
  <br />
  PRINT "Komprimiert:"<br />
  Print compressedText<br />
  Print "ist =";iLPck;" Byte lang"<br />
  PRINT "Dekomprimiert:"<br />
  Print decompressedText<br />
  Print "ist =";iLEpk;" Byte lang"</code></div></div><br />
<br />
This is the DOS-output:<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>Original: PowerBA --&gt; Test verlief: [erfolgreich] :-)  : 7  7  7<br />
Original: PowerBAS --&gt; Test verlief: [erfolgreich] :-)  : 8  7  8<br />
Original: PowerBASI --&gt; Test verlief: [erfolgreich] :-)  : 9  8  9<br />
Original: PowerBASIC --&gt; Test verlief: [erfolgreich] :-)  : 10  9  10<br />
Original: PowerBASIC  --&gt; Test verlief: [erfolgreich] :-)  : 11  10  11<br />
Original: PowerBASIC 3 --&gt; Test verlief: [erfolgreich] :-)  : 12  11  12<br />
Original: PowerBASIC 3. --&gt; Test verlief: [erfolgreich] :-)  : 13  12  13<br />
Original: PowerBASIC 3.5 --&gt; Test verlief: [erfolgreich] :-)  : 14  13  14<br />
Original: PowerBASIC 3.5  --&gt; Test verlief: [erfolgreich] :-)  : 15  14  15<br />
Original: PowerBASIC 3.5 i --&gt; Test verlief: [erfolgreich] :-)  : 16  14  16<br />
Original: PowerBASIC 3.5 is --&gt; Test verlief: [erfolgreich] :-)  : 17  15  17<br />
Original: PowerBASIC 3.5 is  --&gt; Test verlief: [erfolgreich] :-)  : 18  16  18<br />
Original: PowerBASIC 3.5 is a --&gt; Test verlief: [erfolgreich] :-)  : 19  17  19<br />
<br />
Original:PowerBASIC 3.5 is a wonderful tool for writing fast, small and usefull<br />
programs under DOS. --&gt; Test verlief: [erfolgreich] :-)  : 90  79  90<br />
Komprimiert:<br />
░τ╡(§àf⌐◄`Γ¿ ÆS@►p}:ëE⌐▒╩♦PƒO&amp;└°ö☻«╥&#36;5Θ&lt;☻î┴)ò☺ÿ6âL&amp; Φ&#36;☻¬╙ó▒╩d☻á╥τQ→lN☺U'▒(♣É^S.<br />
ist = 79  Byte lang<br />
Dekomprimiert:<br />
PowerBASIC 3.5 is a wonderful tool for writing fast, small and usefull programs<br />
under DOS.<br />
ist = 90  Byte lang<br />
PowerBASIC 3.5 ist ein wundervolles Werkzeug für schnelle, kleine bis große und<br />
vor allen »nützliche« Programme unter DOS. <br />
Falsche Quelle: ß»« </code></div></div> <br />
Perhaps the code will also be of use to others...]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Leap days (or not) in February for PBDOS]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=48</link>
			<pubDate>Sat, 08 Mar 2025 07:48:07 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=23">Dale Yarker</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=48</guid>
			<description><![CDATA[<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'DECLARE not needed if FUNCTION source is before the CALL</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'here the CALL is in PBMAIN</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'I call it bottom up because PBMAIN is last and flow goes up</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'Pretty sure PUSHes and POPs not needed in FUNCTION</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'They will be needed to use "inside" code in-line with BASIC</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'Used PBCC v6 to test. I no longer have PBDOS available.</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">function Tage_im_Februar(byval iJahr as integer) as integer</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  local FebruarTage as integer</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! push ax</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov FebruarTage, 28&amp;  'not a leap year, pre-set to 28</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov ax, 1582%</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! cmp ax, iJahr</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jge Julian</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov ax, 3%</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! and ax, iJahr    'conditionally equivalent MOD 4</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jz MOD100        '0 is possible leap year</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jmp Done</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  MOD100:</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! push bx          'for divisor</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! push dx          'for high part of dividend, remainder (MOD)</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! xor dx, dx</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov ax, iJahr</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov bx, 100%</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! div bx</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! cmp dx, 0%    'does MOD 100 = 0 ?</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! pop dx</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! pop bx</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jz MOD400        '0 is possibly not a leap year</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jmp Is29Days      'non 0 is a leap year</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  MOD400:</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! and ax, 3%    'EAX has Year\100, so conditionally equivalent MOD 400</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jnz Done</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  Julian:</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov ax, 3%</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! and ax, iJahr</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jnz Done</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  Is29Days:</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov FebruarTage, 29%</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  Done:</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! pop ax</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  function = FebruarTage</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">end function</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">function pbmain () as long 'put only to demonstrate Tage_im_Februar</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  local iJahr as integer</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  iJahr = 2000</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print Tage_im_Februar(iJahr); " 2000, divisible by 400, is leap"</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  iJahr = 2100</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print Tage_im_Februar(iJahr); " 2100, divisible by 100 not 400, not leap"</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  iJahr = 2104</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print Tage_im_Februar(iJahr); " 2104, divisible by 4 not 100, is leap"</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  iJahr = 2103</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print Tage_im_Februar(iJahr); " 2103, not divide 4, not leap</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print "julian, did not check rule for prior to Gregorian myself"</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  iJahr = 1204</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print Tage_im_Februar(iJahr); " 1204, divisible by 4, is leap</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  iJahr = 1201</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print Tage_im_Februar(iJahr)  " 1201, not divisible by 4, not leap</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  waitkey&#36;</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">end function</span></span>]]></description>
			<content:encoded><![CDATA[<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'DECLARE not needed if FUNCTION source is before the CALL</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'here the CALL is in PBMAIN</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'I call it bottom up because PBMAIN is last and flow goes up</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'Pretty sure PUSHes and POPs not needed in FUNCTION</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'They will be needed to use "inside" code in-line with BASIC</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">'Used PBCC v6 to test. I no longer have PBDOS available.</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">function Tage_im_Februar(byval iJahr as integer) as integer</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  local FebruarTage as integer</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! push ax</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov FebruarTage, 28&amp;  'not a leap year, pre-set to 28</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov ax, 1582%</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! cmp ax, iJahr</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jge Julian</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov ax, 3%</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! and ax, iJahr    'conditionally equivalent MOD 4</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jz MOD100        '0 is possible leap year</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jmp Done</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  MOD100:</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! push bx          'for divisor</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! push dx          'for high part of dividend, remainder (MOD)</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! xor dx, dx</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov ax, iJahr</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov bx, 100%</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! div bx</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! cmp dx, 0%    'does MOD 100 = 0 ?</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! pop dx</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! pop bx</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jz MOD400        '0 is possibly not a leap year</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jmp Is29Days      'non 0 is a leap year</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  MOD400:</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! and ax, 3%    'EAX has Year\100, so conditionally equivalent MOD 400</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jnz Done</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  Julian:</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov ax, 3%</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! and ax, iJahr</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! jnz Done</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  Is29Days:</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! mov FebruarTage, 29%</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  Done:</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  ! pop ax</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  function = FebruarTage</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">end function</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">function pbmain () as long 'put only to demonstrate Tage_im_Februar</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  local iJahr as integer</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  iJahr = 2000</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print Tage_im_Februar(iJahr); " 2000, divisible by 400, is leap"</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  iJahr = 2100</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print Tage_im_Februar(iJahr); " 2100, divisible by 100 not 400, not leap"</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  iJahr = 2104</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print Tage_im_Februar(iJahr); " 2104, divisible by 4 not 100, is leap"</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  iJahr = 2103</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print Tage_im_Februar(iJahr); " 2103, not divide 4, not leap</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print "julian, did not check rule for prior to Gregorian myself"</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  iJahr = 1204</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print Tage_im_Februar(iJahr); " 1204, divisible by 4, is leap</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  iJahr = 1201</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  print Tage_im_Februar(iJahr)  " 1201, not divisible by 4, not leap</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">  waitkey&#36;</span></span><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-family: Times New Roman;" class="mycode_font">end function</span></span>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Detect QEMU Virtual Machine]]></title>
			<link>http://pump.richheimer.de/showthread.php?tid=29</link>
			<pubDate>Wed, 29 Jan 2025 20:39:50 +0000</pubDate>
			<dc:creator><![CDATA[<a href="http://pump.richheimer.de/member.php?action=profile&uid=8">Anne Wilson</a>]]></dc:creator>
			<guid isPermaLink="false">http://pump.richheimer.de/showthread.php?tid=29</guid>
			<description><![CDATA[This program will detect if it is being run in a QEMU  Virtual Machine VM.<br />
Note that hackers will place your programs to run in a VM so that they can<br />
pirate or hack your programs.  <br />
<br />
This is to detect whether the user is using a QEMU  VM and to do the <br />
necessary counter action.   <br />
<br />
Please let me know if you encounter issue with this program.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>  ' Detect QEMU.bas<br />
<br />
<br />
 ' &nbsp;&nbsp;This program uses multiple detection methods for QEMU VM to increase accuracy.<br />
 ' &nbsp;&nbsp;However, no single method is foolproof, as virtualization environments<br />
 ' &nbsp;&nbsp;can be customized or masked by hackers.<br />
<br />
    #COMPILE EXE<br />
    #DIM ALL<br />
    #INCLUDE "Win32Api.inc"<br />
<br />
   &nbsp;&nbsp;%KEY_QUERY_VALUE   &nbsp;&nbsp;= &amp;H0001<br />
   &nbsp;&nbsp;%ERROR_SUCCESS     &nbsp;&nbsp;= 0<br />
<br />
<br />
<br />
<br />
 '============================<br />
  FUNCTION PBMAIN () AS LONG<br />
    IF IsQEMU() THEN<br />
        ? "Running inside a QEMU virtual machine."<br />
    ELSE<br />
        ? "Not running inside a QEMU virtual machine."<br />
    END IF<br />
 END FUNCTION<br />
<br />
<br />
<br />
<br />
<br />
'===============================<br />
' &nbsp;&nbsp;Detects QEMU VM using several methods<br />
FUNCTION IsQEMU() AS LONG<br />
    LOCAL hqeKey  AS DWORD<br />
    LOCAL dwType  AS DWORD<br />
    LOCAL dwData  AS DWORD<br />
    LOCAL cbData  AS DWORD<br />
    LOCAL qresult AS LONG<br />
<br />
  ' Indicator for various QEMU types<br />
    LOCAL tmpQe &nbsp;&nbsp;AS LONG<br />
    tmpQe = 0<br />
<br />
    ' Check for QEMU-specific registry key (System Manufacturer)<br />
    ' HARDWARE&#92;DESCRIPTION&#92;System&#92;BIOS<br />
    qresult = RegOpenKeyEx(%HKEY_LOCAL_MACHINE, hwBios, 0, %KEY_QUERY_VALUE, hqeKey)<br />
    IF qresult = %ERROR_SUCCESS THEN<br />
        cbData = 256<br />
      ' SystemManufacturer<br />
        qresult = RegQueryValueEx(hqeKey, SysManf , 0, dwType, BYVAL VARPTR(dwData), cbData)<br />
       &nbsp;&nbsp;IF qresult = %ERROR_SUCCESS THEN<br />
            'QEMU<br />
            IF INSTR(UCASE&#36;(PEEK&#36;(VARPTR(dwData), cbData)), StQE) &gt; 0 THEN<br />
                tmpQe = 1<br />
            END IF<br />
        END IF<br />
        RegCloseKey hqeKey<br />
    END IF<br />
<br />
    IF tmpQe &gt; 0 THEN<br />
     &nbsp;&nbsp;IsQEMU = 1<br />
     &nbsp;&nbsp;EXIT FUNCTION<br />
    END IF<br />
<br />
<br />
    ' Check for QEMU-specific driver (qxl.sys or virtio drivers)<br />
    ' such as QXL video adapter or VirtIO devices<br />
    ' C:&#92;Windows&#92;System32&#92;drivers&#92;qxl.sys     &nbsp;&nbsp;and<br />
    ' C:&#92;Windows&#92;System32&#92;drivers&#92;vioinput.sys<br />
    IF ISFILE(qxlS ) OR ISFILE(vioinp) THEN<br />
        tmpQe = 2<br />
    END IF<br />
<br />
    IF tmpQe &gt; 0 THEN<br />
     &nbsp;&nbsp;IsQEMU = 1<br />
     &nbsp;&nbsp;EXIT FUNCTION<br />
    END IF<br />
<br />
    ' Check for QEMU-specific hardware (QXL video or VirtIO devices)<br />
    ' C:&#92;Windows&#92;System32&#92;drivers&#92;qxl.dll    and<br />
    ' C:&#92;Windows&#92;System32&#92;drivers&#92;viostor.sys<br />
    IF ISFILE(stQxl) OR ISFILE(stVio) THEN<br />
        tmpQe = 3<br />
    END IF<br />
<br />
    IF tmpQe &gt; 0 THEN<br />
     &nbsp;&nbsp;IsQEMU = 1<br />
     &nbsp;&nbsp;EXIT FUNCTION<br />
    END IF<br />
<br />
  ' Not running inside QEMU<br />
    IsQEMU = 0<br />
END FUNCTION<br />
<br />
<br />
<br />
<br />
<br />
 ' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
 '    C:&#92;Windows&#92;System32&#92;drivers&#92;vioinput.sys<br />
FUNCTION vioinp() AS STRING<br />
<br />
  ' Text is 40 bytes excluding the terminating zero<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL src  AS DWORD<br />
    LOCAL dst  AS DWORD<br />
    LOCAL outpt&#36;<br />
<br />
    src = CODEPTR(datalabel)<br />
<br />
    outpt&#36; = NUL&#36;(40)<br />
    dst = STRPTR(outpt&#36;)<br />
<br />
  ' -------------------<br />
  ' copy data to string<br />
  ' -------------------<br />
    ! mov esi, src<br />
    ! mov edi, dst<br />
    ! mov ecx, 40<br />
    ! rep movsb<br />
<br />
    src = CODEPTR(paddlabel)<br />
<br />
  ' -----------------------------<br />
  ' xor string data to unique pad<br />
  ' -----------------------------<br />
    ! mov esi, dst<br />
    ! mov ebx, 40<br />
    ! mov edi, src<br />
    ! add esi, ebx<br />
    ! add edi, ebx<br />
    ! neg ebx<br />
<br />
  lbl0:<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jnz lbl0<br />
<br />
  lbl1:<br />
    FUNCTION = outpt&#36;<br />
    EXIT FUNCTION<br />
<br />
  #ALIGN 4<br />
  datalabel:<br />
    ! db 137,244,134,19,90,252,4,157,27,48,199,3,14,247,228,3<br />
    ! db 175,250,190,186,216,209,84,46,134,104,244,174,243,136,210,100<br />
    ! db 103,146,120,43,36,182,157,78,0<br />
<br />
  #ALIGN 4<br />
  paddlabel:<br />
    ! db 202,206,218,68,51,146,96,242,108,67,155,80,119,132,144,102<br />
    ! db 194,201,140,230,188,163,61,88,227,26,135,242,133,225,189,13<br />
    ! db 9,226,13,95,10,197,228,61,0<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
<br />
<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
 &nbsp;&nbsp;'  C:&#92;Windows&#92;System32&#92;drivers&#92;qxl.sys<br />
FUNCTION qxlS() AS STRING<br />
<br />
  ' Text is 35 bytes excluding the terminating zero<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL src  AS DWORD<br />
    LOCAL dst  AS DWORD<br />
    LOCAL outpt&#36;<br />
<br />
    src = CODEPTR(datalabel)<br />
<br />
    outpt&#36; = NUL&#36;(35)<br />
    dst = STRPTR(outpt&#36;)<br />
<br />
  ' -------------------<br />
  ' copy data to string<br />
  ' -------------------<br />
    ! mov esi, src<br />
    ! mov edi, dst<br />
    ! mov ecx, 35<br />
    ! rep movsb<br />
<br />
    src = CODEPTR(paddlabel)<br />
<br />
  ' -----------------------------<br />
  ' xor string data to unique pad<br />
  ' -----------------------------<br />
    ! mov esi, dst<br />
    ! mov ebx, 35<br />
    ! mov edi, src<br />
    ! add esi, ebx<br />
    ! add edi, ebx<br />
    ! neg ebx<br />
<br />
  lbl0:<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jnz lbl0<br />
<br />
  lbl1:<br />
    FUNCTION = outpt&#36;<br />
    EXIT FUNCTION<br />
<br />
  #ALIGN 4<br />
  datalabel:<br />
    ! db 39,193,199,194,34,252,156,45,109,153,235,30,232,30,74,199<br />
    ! db 100,250,27,119,124,175,212,177,7,207,147,66,236,149,73,81<br />
    ! db 143,69,39,0<br />
<br />
  #ALIGN 4<br />
  paddlabel:<br />
    ! db 100,251,155,149,75,146,248,66,26,234,183,77,145,109,62,162<br />
    ! db 9,201,41,43,24,221,189,199,98,189,224,30,157,237,37,127<br />
    ! db 252,60,84,0<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
<br />
<br />
<br />
<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
 'C:&#92;Windows&#92;System32&#92;drivers&#92;qxl.dll<br />
FUNCTION stQxl() AS STRING<br />
<br />
  ' Text is 35 bytes excluding the terminating zero<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL src  AS DWORD<br />
    LOCAL dst  AS DWORD<br />
    LOCAL outpt&#36;<br />
<br />
    src = CODEPTR(datalabel)<br />
<br />
    outpt&#36; = NUL&#36;(35)<br />
    dst = STRPTR(outpt&#36;)<br />
<br />
  ' -------------------<br />
  ' copy data to string<br />
  ' -------------------<br />
    ! mov esi, src<br />
    ! mov edi, dst<br />
    ! mov ecx, 35<br />
    ! rep movsb<br />
<br />
    src = CODEPTR(paddlabel)<br />
<br />
  ' -----------------------------<br />
  ' xor string data to unique pad<br />
  ' -----------------------------<br />
    ! mov esi, dst<br />
    ! mov ebx, 35<br />
    ! mov edi, src<br />
    ! add esi, ebx<br />
    ! add edi, ebx<br />
    ! neg ebx<br />
<br />
  lbl0:<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jnz lbl0<br />
<br />
  lbl1:<br />
    FUNCTION = outpt&#36;<br />
    EXIT FUNCTION<br />
<br />
  #ALIGN 4<br />
  datalabel:<br />
    ! db 107,242,156,222,105,186,235,71,251,111,207,178,223,54,223,160<br />
    ! db 48,66,192,5,85,78,114,228,105,10,125,30,253,8,13,29<br />
    ! db 29,250,74,0<br />
<br />
  #ALIGN 4<br />
  paddlabel:<br />
    ! db 40,200,192,137,0,212,143,40,140,28,147,225,166,69,171,197<br />
    ! db 93,113,242,89,49,60,27,146,12,120,14,66,140,112,97,51<br />
    ! db 121,150,38,0<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
 &nbsp;&nbsp;' &nbsp;&nbsp;C:&#92;Windows&#92;System32&#92;drivers&#92;viostor.sys<br />
FUNCTION stVio() AS STRING<br />
<br />
  ' Text is 39 bytes excluding the terminating zero<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL src  AS DWORD<br />
    LOCAL dst  AS DWORD<br />
    LOCAL outpt&#36;<br />
<br />
    src = CODEPTR(datalabel)<br />
<br />
    outpt&#36; = NUL&#36;(39)<br />
    dst = STRPTR(outpt&#36;)<br />
<br />
  ' -------------------<br />
  ' copy data to string<br />
  ' -------------------<br />
    ! mov esi, src<br />
    ! mov edi, dst<br />
    ! mov ecx, 39<br />
    ! rep movsb<br />
<br />
    src = CODEPTR(paddlabel)<br />
<br />
  ' -----------------------------<br />
  ' xor string data to unique pad<br />
  ' -----------------------------<br />
    ! mov esi, dst<br />
    ! mov ebx, 39<br />
    ! mov edi, src<br />
    ! add esi, ebx<br />
    ! add edi, ebx<br />
    ! neg ebx<br />
<br />
  lbl0:<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jnz lbl0<br />
<br />
  lbl1:<br />
    FUNCTION = outpt&#36;<br />
    EXIT FUNCTION<br />
<br />
  #ALIGN 4<br />
  datalabel:<br />
    ! db 249,253,198,251,223,113,140,156,245,139,234,192,79,79,251,90<br />
    ! db 10,141,82,54,82,155,166,16,138,158,122,123,208,158,228,122<br />
    ! db 211,170,16,201,173,76,240,0<br />
<br />
  #ALIGN 4<br />
  paddlabel:<br />
    ! db 186,199,154,172,182,31,232,243,130,248,182,147,54,60,143,63<br />
    ! db 103,190,96,106,54,233,207,102,239,236,9,39,166,247,139,9<br />
    ! db 167,197,98,231,222,53,131,0<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
<br />
<br />
<br />
<br />
 ' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
   &nbsp;&nbsp;'  QEMU<br />
FUNCTION StQE() AS STRING<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL pstr AS DWORD<br />
    LOCAL a&#36;<br />
<br />
    a&#36; = NUL&#36;(4)<br />
    pstr = STRPTR(a&#36;)<br />
<br />
    ! mov esi, pstr<br />
<br />
    ! mov BYTE PTR [esi+0], 81<br />
    ! mov BYTE PTR [esi+2], 77<br />
    ! mov BYTE PTR [esi+1], 69<br />
    ! mov BYTE PTR [esi+3], 85<br />
<br />
    FUNCTION = a&#36;<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
<br />
<br />
<br />
<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
  ' SystemManufacturer<br />
FUNCTION SysManf() AS STRING<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL pstr AS DWORD<br />
    LOCAL a&#36;<br />
<br />
    a&#36; = NUL&#36;(18)<br />
    pstr = STRPTR(a&#36;)<br />
<br />
    ! mov esi, pstr<br />
<br />
    ! mov BYTE PTR [esi+14], 117<br />
    ! mov BYTE PTR [esi+5], 109<br />
    ! mov BYTE PTR [esi+9], 117<br />
    ! mov BYTE PTR [esi+16], 101<br />
    ! mov BYTE PTR [esi+15], 114<br />
    ! mov BYTE PTR [esi+17], 114<br />
    ! mov BYTE PTR [esi+11], 97<br />
    ! mov BYTE PTR [esi+8], 110<br />
    ! mov BYTE PTR [esi+13], 116<br />
    ! mov BYTE PTR [esi+3], 116<br />
    ! mov BYTE PTR [esi+4], 101<br />
    ! mov BYTE PTR [esi+2], 115<br />
    ! mov BYTE PTR [esi+0], 83<br />
    ! mov BYTE PTR [esi+1], 121<br />
    ! mov BYTE PTR [esi+7], 97<br />
    ! mov BYTE PTR [esi+12], 99<br />
    ! mov BYTE PTR [esi+6], 77<br />
    ! mov BYTE PTR [esi+10], 102<br />
<br />
    FUNCTION = a&#36;<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
<br />
<br />
<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
 &nbsp;&nbsp;' HARDWARE&#92;DESCRIPTION&#92;System&#92;BIOS<br />
FUNCTION hwBios() AS STRING<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL pstr AS DWORD<br />
    LOCAL a&#36;<br />
<br />
    a&#36; = NUL&#36;(32)<br />
    pstr = STRPTR(a&#36;)<br />
<br />
    ! mov esi, pstr<br />
<br />
    ! mov BYTE PTR [esi+28], 66<br />
    ! mov BYTE PTR [esi+29], 73<br />
    ! mov BYTE PTR [esi+27], 92<br />
    ! mov BYTE PTR [esi+17], 73<br />
    ! mov BYTE PTR [esi+4], 87<br />
    ! mov BYTE PTR [esi+30], 79<br />
    ! mov BYTE PTR [esi+20], 92<br />
    ! mov BYTE PTR [esi+16], 84<br />
    ! mov BYTE PTR [esi+21], 83<br />
    ! mov BYTE PTR [esi+25], 101<br />
    ! mov BYTE PTR [esi+31], 83<br />
    ! mov BYTE PTR [esi+12], 67<br />
    ! mov BYTE PTR [esi+15], 80<br />
    ! mov BYTE PTR [esi+22], 121<br />
    ! mov BYTE PTR [esi+9], 68<br />
    ! mov BYTE PTR [esi+1], 65<br />
    ! mov BYTE PTR [esi+3], 68<br />
    ! mov BYTE PTR [esi+6], 82<br />
    ! mov BYTE PTR [esi+0], 72<br />
    ! mov BYTE PTR [esi+18], 79<br />
    ! mov BYTE PTR [esi+23], 115<br />
    ! mov BYTE PTR [esi+11], 83<br />
    ! mov BYTE PTR [esi+7], 69<br />
    ! mov BYTE PTR [esi+19], 78<br />
    ! mov BYTE PTR [esi+26], 109<br />
    ! mov BYTE PTR [esi+8], 92<br />
    ! mov BYTE PTR [esi+2], 82<br />
    ! mov BYTE PTR [esi+5], 65<br />
    ! mov BYTE PTR [esi+10], 69<br />
    ! mov BYTE PTR [esi+13], 82<br />
    ! mov BYTE PTR [esi+14], 73<br />
    ! mov BYTE PTR [esi+24], 116<br />
<br />
    FUNCTION = a&#36;<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤</code></div></div>]]></description>
			<content:encoded><![CDATA[This program will detect if it is being run in a QEMU  Virtual Machine VM.<br />
Note that hackers will place your programs to run in a VM so that they can<br />
pirate or hack your programs.  <br />
<br />
This is to detect whether the user is using a QEMU  VM and to do the <br />
necessary counter action.   <br />
<br />
Please let me know if you encounter issue with this program.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>  ' Detect QEMU.bas<br />
<br />
<br />
 ' &nbsp;&nbsp;This program uses multiple detection methods for QEMU VM to increase accuracy.<br />
 ' &nbsp;&nbsp;However, no single method is foolproof, as virtualization environments<br />
 ' &nbsp;&nbsp;can be customized or masked by hackers.<br />
<br />
    #COMPILE EXE<br />
    #DIM ALL<br />
    #INCLUDE "Win32Api.inc"<br />
<br />
   &nbsp;&nbsp;%KEY_QUERY_VALUE   &nbsp;&nbsp;= &amp;H0001<br />
   &nbsp;&nbsp;%ERROR_SUCCESS     &nbsp;&nbsp;= 0<br />
<br />
<br />
<br />
<br />
 '============================<br />
  FUNCTION PBMAIN () AS LONG<br />
    IF IsQEMU() THEN<br />
        ? "Running inside a QEMU virtual machine."<br />
    ELSE<br />
        ? "Not running inside a QEMU virtual machine."<br />
    END IF<br />
 END FUNCTION<br />
<br />
<br />
<br />
<br />
<br />
'===============================<br />
' &nbsp;&nbsp;Detects QEMU VM using several methods<br />
FUNCTION IsQEMU() AS LONG<br />
    LOCAL hqeKey  AS DWORD<br />
    LOCAL dwType  AS DWORD<br />
    LOCAL dwData  AS DWORD<br />
    LOCAL cbData  AS DWORD<br />
    LOCAL qresult AS LONG<br />
<br />
  ' Indicator for various QEMU types<br />
    LOCAL tmpQe &nbsp;&nbsp;AS LONG<br />
    tmpQe = 0<br />
<br />
    ' Check for QEMU-specific registry key (System Manufacturer)<br />
    ' HARDWARE&#92;DESCRIPTION&#92;System&#92;BIOS<br />
    qresult = RegOpenKeyEx(%HKEY_LOCAL_MACHINE, hwBios, 0, %KEY_QUERY_VALUE, hqeKey)<br />
    IF qresult = %ERROR_SUCCESS THEN<br />
        cbData = 256<br />
      ' SystemManufacturer<br />
        qresult = RegQueryValueEx(hqeKey, SysManf , 0, dwType, BYVAL VARPTR(dwData), cbData)<br />
       &nbsp;&nbsp;IF qresult = %ERROR_SUCCESS THEN<br />
            'QEMU<br />
            IF INSTR(UCASE&#36;(PEEK&#36;(VARPTR(dwData), cbData)), StQE) &gt; 0 THEN<br />
                tmpQe = 1<br />
            END IF<br />
        END IF<br />
        RegCloseKey hqeKey<br />
    END IF<br />
<br />
    IF tmpQe &gt; 0 THEN<br />
     &nbsp;&nbsp;IsQEMU = 1<br />
     &nbsp;&nbsp;EXIT FUNCTION<br />
    END IF<br />
<br />
<br />
    ' Check for QEMU-specific driver (qxl.sys or virtio drivers)<br />
    ' such as QXL video adapter or VirtIO devices<br />
    ' C:&#92;Windows&#92;System32&#92;drivers&#92;qxl.sys     &nbsp;&nbsp;and<br />
    ' C:&#92;Windows&#92;System32&#92;drivers&#92;vioinput.sys<br />
    IF ISFILE(qxlS ) OR ISFILE(vioinp) THEN<br />
        tmpQe = 2<br />
    END IF<br />
<br />
    IF tmpQe &gt; 0 THEN<br />
     &nbsp;&nbsp;IsQEMU = 1<br />
     &nbsp;&nbsp;EXIT FUNCTION<br />
    END IF<br />
<br />
    ' Check for QEMU-specific hardware (QXL video or VirtIO devices)<br />
    ' C:&#92;Windows&#92;System32&#92;drivers&#92;qxl.dll    and<br />
    ' C:&#92;Windows&#92;System32&#92;drivers&#92;viostor.sys<br />
    IF ISFILE(stQxl) OR ISFILE(stVio) THEN<br />
        tmpQe = 3<br />
    END IF<br />
<br />
    IF tmpQe &gt; 0 THEN<br />
     &nbsp;&nbsp;IsQEMU = 1<br />
     &nbsp;&nbsp;EXIT FUNCTION<br />
    END IF<br />
<br />
  ' Not running inside QEMU<br />
    IsQEMU = 0<br />
END FUNCTION<br />
<br />
<br />
<br />
<br />
<br />
 ' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
 '    C:&#92;Windows&#92;System32&#92;drivers&#92;vioinput.sys<br />
FUNCTION vioinp() AS STRING<br />
<br />
  ' Text is 40 bytes excluding the terminating zero<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL src  AS DWORD<br />
    LOCAL dst  AS DWORD<br />
    LOCAL outpt&#36;<br />
<br />
    src = CODEPTR(datalabel)<br />
<br />
    outpt&#36; = NUL&#36;(40)<br />
    dst = STRPTR(outpt&#36;)<br />
<br />
  ' -------------------<br />
  ' copy data to string<br />
  ' -------------------<br />
    ! mov esi, src<br />
    ! mov edi, dst<br />
    ! mov ecx, 40<br />
    ! rep movsb<br />
<br />
    src = CODEPTR(paddlabel)<br />
<br />
  ' -----------------------------<br />
  ' xor string data to unique pad<br />
  ' -----------------------------<br />
    ! mov esi, dst<br />
    ! mov ebx, 40<br />
    ! mov edi, src<br />
    ! add esi, ebx<br />
    ! add edi, ebx<br />
    ! neg ebx<br />
<br />
  lbl0:<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jnz lbl0<br />
<br />
  lbl1:<br />
    FUNCTION = outpt&#36;<br />
    EXIT FUNCTION<br />
<br />
  #ALIGN 4<br />
  datalabel:<br />
    ! db 137,244,134,19,90,252,4,157,27,48,199,3,14,247,228,3<br />
    ! db 175,250,190,186,216,209,84,46,134,104,244,174,243,136,210,100<br />
    ! db 103,146,120,43,36,182,157,78,0<br />
<br />
  #ALIGN 4<br />
  paddlabel:<br />
    ! db 202,206,218,68,51,146,96,242,108,67,155,80,119,132,144,102<br />
    ! db 194,201,140,230,188,163,61,88,227,26,135,242,133,225,189,13<br />
    ! db 9,226,13,95,10,197,228,61,0<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
<br />
<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
 &nbsp;&nbsp;'  C:&#92;Windows&#92;System32&#92;drivers&#92;qxl.sys<br />
FUNCTION qxlS() AS STRING<br />
<br />
  ' Text is 35 bytes excluding the terminating zero<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL src  AS DWORD<br />
    LOCAL dst  AS DWORD<br />
    LOCAL outpt&#36;<br />
<br />
    src = CODEPTR(datalabel)<br />
<br />
    outpt&#36; = NUL&#36;(35)<br />
    dst = STRPTR(outpt&#36;)<br />
<br />
  ' -------------------<br />
  ' copy data to string<br />
  ' -------------------<br />
    ! mov esi, src<br />
    ! mov edi, dst<br />
    ! mov ecx, 35<br />
    ! rep movsb<br />
<br />
    src = CODEPTR(paddlabel)<br />
<br />
  ' -----------------------------<br />
  ' xor string data to unique pad<br />
  ' -----------------------------<br />
    ! mov esi, dst<br />
    ! mov ebx, 35<br />
    ! mov edi, src<br />
    ! add esi, ebx<br />
    ! add edi, ebx<br />
    ! neg ebx<br />
<br />
  lbl0:<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jnz lbl0<br />
<br />
  lbl1:<br />
    FUNCTION = outpt&#36;<br />
    EXIT FUNCTION<br />
<br />
  #ALIGN 4<br />
  datalabel:<br />
    ! db 39,193,199,194,34,252,156,45,109,153,235,30,232,30,74,199<br />
    ! db 100,250,27,119,124,175,212,177,7,207,147,66,236,149,73,81<br />
    ! db 143,69,39,0<br />
<br />
  #ALIGN 4<br />
  paddlabel:<br />
    ! db 100,251,155,149,75,146,248,66,26,234,183,77,145,109,62,162<br />
    ! db 9,201,41,43,24,221,189,199,98,189,224,30,157,237,37,127<br />
    ! db 252,60,84,0<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
<br />
<br />
<br />
<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
 'C:&#92;Windows&#92;System32&#92;drivers&#92;qxl.dll<br />
FUNCTION stQxl() AS STRING<br />
<br />
  ' Text is 35 bytes excluding the terminating zero<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL src  AS DWORD<br />
    LOCAL dst  AS DWORD<br />
    LOCAL outpt&#36;<br />
<br />
    src = CODEPTR(datalabel)<br />
<br />
    outpt&#36; = NUL&#36;(35)<br />
    dst = STRPTR(outpt&#36;)<br />
<br />
  ' -------------------<br />
  ' copy data to string<br />
  ' -------------------<br />
    ! mov esi, src<br />
    ! mov edi, dst<br />
    ! mov ecx, 35<br />
    ! rep movsb<br />
<br />
    src = CODEPTR(paddlabel)<br />
<br />
  ' -----------------------------<br />
  ' xor string data to unique pad<br />
  ' -----------------------------<br />
    ! mov esi, dst<br />
    ! mov ebx, 35<br />
    ! mov edi, src<br />
    ! add esi, ebx<br />
    ! add edi, ebx<br />
    ! neg ebx<br />
<br />
  lbl0:<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jnz lbl0<br />
<br />
  lbl1:<br />
    FUNCTION = outpt&#36;<br />
    EXIT FUNCTION<br />
<br />
  #ALIGN 4<br />
  datalabel:<br />
    ! db 107,242,156,222,105,186,235,71,251,111,207,178,223,54,223,160<br />
    ! db 48,66,192,5,85,78,114,228,105,10,125,30,253,8,13,29<br />
    ! db 29,250,74,0<br />
<br />
  #ALIGN 4<br />
  paddlabel:<br />
    ! db 40,200,192,137,0,212,143,40,140,28,147,225,166,69,171,197<br />
    ! db 93,113,242,89,49,60,27,146,12,120,14,66,140,112,97,51<br />
    ! db 121,150,38,0<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
 &nbsp;&nbsp;' &nbsp;&nbsp;C:&#92;Windows&#92;System32&#92;drivers&#92;viostor.sys<br />
FUNCTION stVio() AS STRING<br />
<br />
  ' Text is 39 bytes excluding the terminating zero<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL src  AS DWORD<br />
    LOCAL dst  AS DWORD<br />
    LOCAL outpt&#36;<br />
<br />
    src = CODEPTR(datalabel)<br />
<br />
    outpt&#36; = NUL&#36;(39)<br />
    dst = STRPTR(outpt&#36;)<br />
<br />
  ' -------------------<br />
  ' copy data to string<br />
  ' -------------------<br />
    ! mov esi, src<br />
    ! mov edi, dst<br />
    ! mov ecx, 39<br />
    ! rep movsb<br />
<br />
    src = CODEPTR(paddlabel)<br />
<br />
  ' -----------------------------<br />
  ' xor string data to unique pad<br />
  ' -----------------------------<br />
    ! mov esi, dst<br />
    ! mov ebx, 39<br />
    ! mov edi, src<br />
    ! add esi, ebx<br />
    ! add edi, ebx<br />
    ! neg ebx<br />
<br />
  lbl0:<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jz lbl1<br />
    ! movzx eax, BYTE PTR [edi+ebx]<br />
    ! xor [esi+ebx], al<br />
    ! add ebx, 1<br />
    ! jnz lbl0<br />
<br />
  lbl1:<br />
    FUNCTION = outpt&#36;<br />
    EXIT FUNCTION<br />
<br />
  #ALIGN 4<br />
  datalabel:<br />
    ! db 249,253,198,251,223,113,140,156,245,139,234,192,79,79,251,90<br />
    ! db 10,141,82,54,82,155,166,16,138,158,122,123,208,158,228,122<br />
    ! db 211,170,16,201,173,76,240,0<br />
<br />
  #ALIGN 4<br />
  paddlabel:<br />
    ! db 186,199,154,172,182,31,232,243,130,248,182,147,54,60,143,63<br />
    ! db 103,190,96,106,54,233,207,102,239,236,9,39,166,247,139,9<br />
    ! db 167,197,98,231,222,53,131,0<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
<br />
<br />
<br />
<br />
 ' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
   &nbsp;&nbsp;'  QEMU<br />
FUNCTION StQE() AS STRING<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL pstr AS DWORD<br />
    LOCAL a&#36;<br />
<br />
    a&#36; = NUL&#36;(4)<br />
    pstr = STRPTR(a&#36;)<br />
<br />
    ! mov esi, pstr<br />
<br />
    ! mov BYTE PTR [esi+0], 81<br />
    ! mov BYTE PTR [esi+2], 77<br />
    ! mov BYTE PTR [esi+1], 69<br />
    ! mov BYTE PTR [esi+3], 85<br />
<br />
    FUNCTION = a&#36;<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
<br />
<br />
<br />
<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
  ' SystemManufacturer<br />
FUNCTION SysManf() AS STRING<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL pstr AS DWORD<br />
    LOCAL a&#36;<br />
<br />
    a&#36; = NUL&#36;(18)<br />
    pstr = STRPTR(a&#36;)<br />
<br />
    ! mov esi, pstr<br />
<br />
    ! mov BYTE PTR [esi+14], 117<br />
    ! mov BYTE PTR [esi+5], 109<br />
    ! mov BYTE PTR [esi+9], 117<br />
    ! mov BYTE PTR [esi+16], 101<br />
    ! mov BYTE PTR [esi+15], 114<br />
    ! mov BYTE PTR [esi+17], 114<br />
    ! mov BYTE PTR [esi+11], 97<br />
    ! mov BYTE PTR [esi+8], 110<br />
    ! mov BYTE PTR [esi+13], 116<br />
    ! mov BYTE PTR [esi+3], 116<br />
    ! mov BYTE PTR [esi+4], 101<br />
    ! mov BYTE PTR [esi+2], 115<br />
    ! mov BYTE PTR [esi+0], 83<br />
    ! mov BYTE PTR [esi+1], 121<br />
    ! mov BYTE PTR [esi+7], 97<br />
    ! mov BYTE PTR [esi+12], 99<br />
    ! mov BYTE PTR [esi+6], 77<br />
    ! mov BYTE PTR [esi+10], 102<br />
<br />
    FUNCTION = a&#36;<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
<br />
<br />
<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤<br />
 &nbsp;&nbsp;' HARDWARE&#92;DESCRIPTION&#92;System&#92;BIOS<br />
FUNCTION hwBios() AS STRING<br />
<br />
    #REGISTER NONE<br />
<br />
    LOCAL pstr AS DWORD<br />
    LOCAL a&#36;<br />
<br />
    a&#36; = NUL&#36;(32)<br />
    pstr = STRPTR(a&#36;)<br />
<br />
    ! mov esi, pstr<br />
<br />
    ! mov BYTE PTR [esi+28], 66<br />
    ! mov BYTE PTR [esi+29], 73<br />
    ! mov BYTE PTR [esi+27], 92<br />
    ! mov BYTE PTR [esi+17], 73<br />
    ! mov BYTE PTR [esi+4], 87<br />
    ! mov BYTE PTR [esi+30], 79<br />
    ! mov BYTE PTR [esi+20], 92<br />
    ! mov BYTE PTR [esi+16], 84<br />
    ! mov BYTE PTR [esi+21], 83<br />
    ! mov BYTE PTR [esi+25], 101<br />
    ! mov BYTE PTR [esi+31], 83<br />
    ! mov BYTE PTR [esi+12], 67<br />
    ! mov BYTE PTR [esi+15], 80<br />
    ! mov BYTE PTR [esi+22], 121<br />
    ! mov BYTE PTR [esi+9], 68<br />
    ! mov BYTE PTR [esi+1], 65<br />
    ! mov BYTE PTR [esi+3], 68<br />
    ! mov BYTE PTR [esi+6], 82<br />
    ! mov BYTE PTR [esi+0], 72<br />
    ! mov BYTE PTR [esi+18], 79<br />
    ! mov BYTE PTR [esi+23], 115<br />
    ! mov BYTE PTR [esi+11], 83<br />
    ! mov BYTE PTR [esi+7], 69<br />
    ! mov BYTE PTR [esi+19], 78<br />
    ! mov BYTE PTR [esi+26], 109<br />
    ! mov BYTE PTR [esi+8], 92<br />
    ! mov BYTE PTR [esi+2], 82<br />
    ! mov BYTE PTR [esi+5], 65<br />
    ! mov BYTE PTR [esi+10], 69<br />
    ! mov BYTE PTR [esi+13], 82<br />
    ! mov BYTE PTR [esi+14], 73<br />
    ! mov BYTE PTR [esi+24], 116<br />
<br />
    FUNCTION = a&#36;<br />
<br />
END FUNCTION<br />
<br />
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤</code></div></div>]]></content:encoded>
		</item>
	</channel>
</rss>